This commit is contained in:
Duanzhoutao 2026-08-15 23:11:28 +08:00 committed by GitHub
commit fd8dd5ce3e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 4792 additions and 0 deletions

202
skills/vscode/LICENSE.txt Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2026 Anthropic, PBC.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

311
skills/vscode/SKILL.md Normal file
View File

@ -0,0 +1,311 @@
---
name: vscode
description: "Configure and operate VS Code-family IDEs (VS Code, VS Code Insiders, Cursor, VSCodium, Trae, Qoder, CodeBuddy) via CLI and structured JSON files. Handles settings.json (user and project scope), .vscode/{settings,tasks,launch,extensions}.json, keybindings.json, snippets, profiles, extensions, Python interpreter, SSH and WSL Remote. Qoder and CodeBuddy support is limited to their VS Code-compatible configuration surface, not proprietary AI chat, accounts, models, agents, marketplaces, or internal state. Uses environment variables and a config file for portable paths so the skill works on any machine. Trigger phrases: VS Code, VSCode, VS Code 配置, settings.json, .vscode, tasks.json, launch.json, keybindings.json, extensions.json, snippets, profiles, Cursor, VSCodium, Trae, Qoder, CodeBuddy, Remote SSH, WSL, Python interpreter, VSIX, MCP, theme, font."
---
# VS Code
A portable skill for configuring and operating VS Code-family IDEs (VS Code, VS Code Insiders, Cursor, VSCodium, Trae, Qoder, CodeBuddy) through the product CLI and structured JSON config files. Defaults use only standard locations; explicit paths are taken from environment variables and a config file so the skill can run on any machine without editing.
> Platform note: **Cross-platform** via the Python gateway `scripts/vscode_skill.py`. On Windows it forwards to the PowerShell driver `vscode-cli.ps1` (the original implementation, 11/11 tests pass). On macOS and Linux it implements `info`, `ext list`, `settings path/get/set/smoke`, and `python set-interpreter` natively in Python; everything else shells out to your local `code` binary.
## Quick Start
### Cross-platform (recommended)
```bash
# Show detected product, CLI path, and key locations
python scripts/vscode_skill.py info
# List installed extensions
python scripts/vscode_skill.py ext list
# Show user settings.json path and current core settings
python scripts/vscode_skill.py settings path
python scripts/vscode_skill.py settings get
# Change font size in user settings (writes only with --Apply)
python scripts/vscode_skill.py settings set \
--FontSize 16 --FontFamily "JetBrains Mono, Consolas, monospace" --Apply
# Project-scope settings
python scripts/vscode_skill.py settings set \
--Scope project --ProjectPath /path/to/project \
--FormatOnSave true --Apply
# Python interpreter for a project
python scripts/vscode_skill.py python set-interpreter \
--ProjectPath /path/to/project --VenvName .venv
```
### Windows-only (PowerShell)
The same commands work directly through `vscode-cli.ps1` if you prefer PowerShell:
```powershell
pwsh -File $PSScriptRoot\scripts\vscode-cli.ps1 info
pwsh -File $PSScriptRoot\scripts\vscode-cli.ps1 ssh open `
--HostName devbox --LinuxPath /home/user/project
```
`--Product` values:
- `auto` (default): probe VS Code, then VS Code Insiders.
- `vscode`: use `code` / `code.cmd`.
- `vscode-insiders`: use `code-insiders` / `code-insiders.cmd`.
- Any configured product slug such as `cursor`, `vscodium`, `trae-cn`, `qoder`, or `codebuddy-cn`: use `products.<slug>.cliPath`, `userDataPath`, and `extensionsPath` from the config file.
Set the default product explicitly with `VSCODE_SKILL_PRODUCT` or via the config file (see [Configuration](#configuration)).
## VS Code-Compatible Products
Built-in auto-detection is strongest for VS Code and VS Code Insiders. Cursor, VSCodium, Trae, Qoder, and CodeBuddy are handled through the generic product configuration path unless their CLI is already discoverable through an environment-specific wrapper.
This skill only claims the VS Code-compatible configuration surface for derived products:
- User and project settings, including `.vscode/settings.json`.
- Project `.vscode/tasks.json`, `.vscode/launch.json`, and `.vscode/extensions.json`.
- Keybindings, snippets, profiles, extensions, templates, Python interpreter selection, and Remote-style open flows where the local product CLI supports them.
Do not use this skill as support for proprietary AI chat, accounts, model settings, agent stores, product-specific marketplaces, hidden SQLite state, tokens, or internal runtime files. For Qoder and CodeBuddy in particular, keep support to the VS Code-compatible editor/configuration layer unless the user provides official product-specific documentation and asks for a separate adapter.
## Configuration
Paths and per-machine preferences are resolved in this order:
1. **Environment variable** (highest priority).
2. **Config file** at `$VSCODE_SKILL_CONFIG` if set, otherwise the platform default:
- Windows: `%APPDATA%\vscode-skill\config.json`
- macOS / Linux: `$XDG_CONFIG_HOME/vscode-skill/config.json` or `~/.config/vscode-skill/config.json`
3. **Built-in defaults** (lowest priority).
### Environment Variables
| Variable | Purpose | Default |
|---|---|---|
| `VSCODE_SKILL_HOME` | Skill install location | parent of the running script |
| `VSCODE_SKILL_CONFIG` | Override config file path | platform default above |
| `VSCODE_SKILL_TEMP` | Override temp directory for tests | `$TEMP/vscode-skill` |
| `VSCODE_SKILL_PRODUCT` | Default `--Product` value | `auto` |
| `VSCODE_CODE_CLI` | Explicit path to VS Code CLI | auto-detected |
| `VSCODE_INSIDERS_CLI` | Explicit path to VS Code Insiders CLI | auto-detected |
| `VSCODE_EXTENSIONS_DIR` | Override extension install dir for any product | product default or config |
| `VSCODE_USER_DATA` | Override user data dir (rare) | platform default |
### Config File Schema
Copy `config.example.json` from the skill root to your config location and edit. All keys are optional.
```json
{
"product": "auto",
"tempDir": null,
"products": {
"vscode": {
"cliPath": "C:\\Users\\you\\scoop\\apps\\vscode\\current\\bin\\code.cmd"
},
"vscode-insiders": {
"cliPath": "C:\\Users\\you\\scoop\\apps\\vscode-insiders\\current\\bin\\code-insiders.cmd"
},
"cursor": {
"cliPath": "C:\\Users\\you\\AppData\\Local\\Programs\\cursor\\resources\\app\\bin\\cursor.cmd",
"userDataPath": "C:\\Users\\you\\AppData\\Roaming\\Cursor\\User",
"extensionsPath": "C:\\Users\\you\\.cursor\\extensions"
},
"vscodium": {
"cliPath": "C:\\Users\\you\\scoop\\apps\\vscodium\\current\\bin\\codium.cmd",
"userDataPath": "C:\\Users\\you\\AppData\\Roaming\\VSCodium\\User",
"extensionsPath": "C:\\Users\\you\\.vscode-oss\\extensions"
},
"trae-cn": {
"cliPath": "C:\\Users\\you\\AppData\\Local\\Programs\\Trae CN\\bin\\trae-cn.cmd",
"userDataPath": "C:\\Users\\you\\AppData\\Roaming\\Trae CN\\User",
"extensionsPath": "C:\\Users\\you\\.trae-cn\\extensions"
},
"qoder": {
"cliPath": "C:\\Users\\you\\AppData\\Local\\Programs\\Qoder\\bin\\qoder.cmd",
"userDataPath": "C:\\Users\\you\\AppData\\Roaming\\Qoder\\User",
"extensionsPath": "C:\\Users\\you\\.qoder\\extensions"
},
"codebuddy-cn": {
"cliPath": "C:\\Users\\you\\AppData\\Local\\Programs\\CodeBuddy CN\\bin\\codebuddy.cmd",
"userDataPath": "C:\\Users\\you\\AppData\\Roaming\\CodeBuddy CN\\User",
"extensionsPath": "C:\\Users\\you\\.codebuddycn\\extensions"
}
},
"ssh": {
"defaultHost": "localhost",
"defaultUser": "you"
},
"wsl": {
"defaultDistro": "Ubuntu-24.04"
}
}
```
Run `pwsh -File scripts/init-config.ps1` to write a starter config file in the default location.
## Scope Discipline
Before writing any IDE state, classify it into one of three scopes and confirm with the user when ambiguous:
- **Read-only inspection**: `info`, `ext list`/`locate`, `settings get`/`path`. Always safe.
- **Project configuration**: `.vscode\settings.json`, `tasks.json`, `launch.json`, `extensions.json`, project snippets. Commit-worthy; do not include personal themes, fonts, account info, or machine-local absolute paths.
- **User / global configuration**: user `settings.json`, `keybindings.json`, user snippets, profile settings, installed extensions. Always state target product, file, key, old value (when readable), new value, and rollback path before applying.
Rules:
- `settings set` requires explicit `--Apply`; without it, the command only validates inputs.
- `ext install` calls the product CLI's `--install-extension`. If the gallery fails, the script does **not** silently retry with Open VSX; a clear warning is printed and the caller decides.
- Project settings written via this skill should never include `python.defaultInterpreterPath` pointing at a machine-local venv unless the project is meant to be reproducible on that specific machine.
## Settings Reference
Common user-settings paths:
- VS Code: `%APPDATA%\Code\User\settings.json`
- VS Code Insiders: `%APPDATA%\Code - Insiders\User\settings.json`
Common keys (all settable through `settings set`):
| Key | Flag | Type |
|---|---|---|
| `editor.fontFamily` | `--FontFamily` | string |
| `editor.fontSize` | `--FontSize` | int |
| `editor.lineHeight` | `--LineHeight` | number |
| `workbench.colorTheme` | `--ColorTheme` | string |
| `workbench.iconTheme` | `--IconTheme` | string |
| `workbench.productIconTheme` | `--ProductIconTheme` | string |
| `editor.wordWrap` | `--WordWrap` | string |
| `editor.tabSize` | `--TabSize` | int |
| `editor.formatOnSave` | `--FormatOnSave` | bool |
| `files.autoSave` | `--AutoSave` | string |
| `editor.minimap.enabled` | `--Minimap` | bool |
| `python.defaultInterpreterPath` | (set via `python set-interpreter`) | string |
For arbitrary keys, pass `--SettingKey <key> --SettingValue <value>`. Values are coerced: `true`/`false` → bool, integer/float → number, `{...}` / `[...]` → JSON, otherwise string.
JSON files may use VS Code's JSON-with-Comments style; the script's standard JSON parser handles only pure JSON. Files containing comments should be edited through the editor or a JSONC-aware tool.
## Commands
```powershell
pwsh -File scripts/vscode-cli.ps1 info
pwsh -File scripts/vscode-cli.ps1 ext list
pwsh -File scripts/vscode-cli.ps1 ext locate --Id ms-python.python
pwsh -File scripts/vscode-cli.ps1 ext install --Id ms-python.python
pwsh -File scripts/vscode-cli.ps1 ext install --Path D:\downloads\some-extension.vsix
pwsh -File scripts/vscode-cli.ps1 settings path
pwsh -File scripts/vscode-cli.ps1 settings get
pwsh -File scripts/vscode-cli.ps1 settings set --FontSize 16 --Apply
pwsh -File scripts/vscode-cli.ps1 settings set --Scope project --ProjectPath D:\projects\demo --FormatOnSave true --Apply
pwsh -File scripts/vscode-cli.ps1 settings smoke
pwsh -File scripts/vscode-cli.ps1 python make-venvs --ProjectPath D:\projects\demo
pwsh -File scripts/vscode-cli.ps1 python set-interpreter --ProjectPath D:\projects\demo --VenvName .venv
pwsh -File scripts/vscode-cli.ps1 python verify --ProjectPath D:\projects\demo --ExpectedVenvName .venv
pwsh -File scripts/vscode-cli.ps1 ssh smoke --HostName localhost
pwsh -File scripts/vscode-cli.ps1 ssh open --HostName devbox --LinuxPath /home/user/project
pwsh -File scripts/vscode-cli.ps1 wsl list
pwsh -File scripts/vscode-cli.ps1 wsl open --Distro Ubuntu-24.04 --LinuxPath /home/user/project
```
CLI resolution order: explicit env (`VSCODE_CODE_CLI` / `VSCODE_INSIDERS_CLI`) → `config.products.<product>.cliPath` → `Get-Command code.cmd` (PATH) → `Get-Command code` (PATH) → `%LOCALAPPDATA%\Programs\Microsoft VS Code\bin\code.cmd` (Windows default). Generic products fail clearly when `products.<product>.cliPath` is absent; they are never silently mapped to VS Code.
## Templates and Bundled Content
The skill ships with curated content under `references/`:
* `settings-index.md` — 80 most-used settings with type, default, scope.
* `settings-precedence.md` — 11-layer precedence model + multi-root workspace behavior.
* `tasks-templates.md` + `templates/tasks/*.json` — 10 ready-made `tasks.json` for Node, TypeScript, Python, Go, Rust, Docker, Make.
* `launch-templates.md` + `templates/launch/*.json` — 9 ready-made `launch.json` for the same languages plus Chrome.
* `extension-packs.md` — 8 curated stacks (frontend-web, backend-python, backend-node, data-science, systems, devops, polyglot-baseline, ai-coding).
* `keybindings-cheatsheet.md` — 50 most-remapped shortcuts.
```powershell
# List available tasks templates
python scripts/vscode_skill.py tasks list
# Generate .vscode/tasks.json from a template (refuses to overwrite without --Force)
python scripts/vscode_skill.py tasks init --Template python-pytest --ProjectPath /path/to/project --Apply
# Same shape for launch templates
python scripts/vscode_skill.py launch init --Template python-current-file --ProjectPath /path/to/project --Apply
# Curated extension stacks
python scripts/vscode_skill.py extensions stacks
python scripts/vscode_skill.py extensions recommend --Stack frontend-web
python scripts/vscode_skill.py extensions install --Stack backend-python
python scripts/vscode_skill.py extensions pin --Stack polyglot-baseline --ProjectPath /path/to/project
```
## Profiles, Settings Sync, Multi-root Workspaces
```powershell
# Inspect profiles (read-only)
python scripts/vscode_skill.py profile list
python scripts/vscode_skill.py profile show --Profile <name>
# Check whether settings sync storage exists locally (does not read payloads)
python scripts/vscode_skill.py settings-sync status
# Multi-root workspace helpers
python scripts/vscode_skill.py workspace info --ProjectPath /path/to/project
python scripts/vscode_skill.py workspace folder-settings-get --ProjectPath /path --Folder frontend
python scripts/vscode_skill.py workspace folder-settings-set --ProjectPath /path --Folder frontend --SettingKey editor.defaultFormatter --SettingValue dbaeumer.vscode-eslint --Apply
```
Settings Sync is **never enabled or disabled programmatically** — the skill only reports whether the sync storage folder exists. Enable/disable it via the VS Code Accounts UI to avoid accidental data leaks.
## Remote Development
- `ssh open` builds a `vscode-remote://ssh-remote+<host><path>` URI and launches the resolved CLI with `--folder-uri`.
- `wsl open` builds a `vscode-remote://wsl+<distro><path>` URI similarly.
- `ssh smoke` runs `ssh -V`, `ssh -G <host>`, and `Test-NetConnection` to confirm the lower layer; it does **not** guarantee the Remote-SSH extension is installed or that the VS Code server has started.
For deeper remote troubleshooting, check `Help → Toggle Developer Tools`, the Remote-SSH extension output channel, or the workspaceStorage entry of the relevant product.
## Testing
The bundled `run_vscode_skill_tests.py` runs safe no-write checks by default:
```powershell
python -X utf8 scripts/run_vscode_skill_tests.py --basic
```
Optional groups (require the corresponding environment):
```powershell
python -X utf8 scripts/run_vscode_skill_tests.py --include-python-venv
python -X utf8 scripts/run_vscode_skill_tests.py --clean
```
Tests write temporary artifacts to an isolated subdirectory under `%TEMP%\vscode-skill-tests\` (or `$VSCODE_SKILL_TEMP` if set), never to user settings or project files outside the temp root.
Validation against the Agent Skills spec:
```powershell
python -X utf8 scripts\quick_validate.py .
```
(`quick_validate.py` is the standalone script in the skill-creator reference; clone or vendor it into this skill if you do not have it locally.)
## See Also
- [references/settings-index.md](references/settings-index.md) — curated 80-setting reference.
- [references/settings-precedence.md](references/settings-precedence.md) — 11-layer precedence + multi-root.
- [references/tasks-templates.md](references/tasks-templates.md) — 10 tasks templates.
- [references/launch-templates.md](references/launch-templates.md) — 9 launch templates.
- [references/extension-packs.md](references/extension-packs.md) — 8 curated extension stacks.
- [references/keybindings-cheatsheet.md](references/keybindings-cheatsheet.md) — 50 most-remapped shortcuts.
- [references/vscode-config-files.md](references/vscode-config-files.md) — project-config file formats reference.
## Cleanup
After running tests:
- Delete the temp root (`%TEMP%\vscode-skill-tests\` by default).
- If a settings write was rolled back by `settings smoke`, the original file is restored in-place; no extra cleanup needed.

View File

@ -0,0 +1,4 @@
interface:
display_name: "VS Code"
short_description: "VS Code-family config for Cursor, Trae, Qoder, CodeBuddy"
default_prompt: "Use $vscode to configure and operate VS Code-family IDEs such as VS Code, VS Code Insiders, Cursor, VSCodium, Trae, Qoder, and CodeBuddy. The default entry point is the cross-platform Python gateway at scripts/vscode_skill.py; on Windows it forwards to the PowerShell driver vscode-cli.ps1, on macOS and Linux it implements core areas natively in Python. Prefer the product CLI and structured JSON config files over ad-hoc edits. Resolve the target CLI and user data paths through environment variables (VSCODE_CODE_CLI, VSCODE_INSIDERS_CLI, VSCODE_SKILL_CONFIG, VSCODE_SKILL_TEMP) and the config file before falling back to platform defaults. For Cursor, VSCodium, Trae, Qoder, and CodeBuddy, use products.<slug>.cliPath, userDataPath, and extensionsPath unless the product is explicitly supported by local auto-detection. Limit Qoder and CodeBuddy work to their VS Code-compatible settings, .vscode files, extensions, keybindings, snippets, profiles, tasks/launch templates, and Remote-style workflows; do not claim support for proprietary AI chat, accounts, models, agents, marketplaces, or internal state. Classify every write as read-only inspection, project configuration, or user/global configuration; for user/global writes state the product, file, key, old value when readable, new value, and rollback path before applying --Apply. Never enable or disable Settings Sync programmatically."

View File

@ -0,0 +1,48 @@
{
"_comment": "Copy this file to your config location (Windows: %APPDATA%\\vscode-skill\\config.json; macOS/Linux: $XDG_CONFIG_HOME/vscode-skill/config.json or ~/.config/vscode-skill/config.json) and uncomment/edit the values you need. All keys are optional.",
"product": "auto",
"tempDir": null,
"products": {
"vscode": {
"cliPath": "C:\\Users\\you\\scoop\\apps\\vscode\\current\\bin\\code.cmd"
},
"vscode-insiders": {
"cliPath": "C:\\Users\\you\\scoop\\apps\\vscode-insiders\\current\\bin\\code-insiders.cmd"
},
"cursor": {
"cliPath": "C:\\Users\\you\\AppData\\Local\\Programs\\cursor\\resources\\app\\bin\\cursor.cmd",
"userDataPath": "C:\\Users\\you\\AppData\\Roaming\\Cursor\\User",
"extensionsPath": "C:\\Users\\you\\.cursor\\extensions"
},
"vscodium": {
"cliPath": "C:\\Users\\you\\scoop\\apps\\vscodium\\current\\bin\\codium.cmd",
"userDataPath": "C:\\Users\\you\\AppData\\Roaming\\VSCodium\\User",
"extensionsPath": "C:\\Users\\you\\.vscode-oss\\extensions"
},
"trae-cn": {
"_note": "Example only. Configure the actual Trae CLI path on your machine before using --Product trae-cn.",
"cliPath": "C:\\Users\\you\\AppData\\Local\\Programs\\Trae CN\\bin\\trae-cn.cmd",
"userDataPath": "C:\\Users\\you\\AppData\\Roaming\\Trae CN\\User",
"extensionsPath": "C:\\Users\\you\\.trae-cn\\extensions"
},
"qoder": {
"_note": "VS Code-compatible editor/configuration surface only; proprietary Qoder AI features are out of scope.",
"cliPath": "C:\\Users\\you\\AppData\\Local\\Programs\\Qoder\\bin\\qoder.cmd",
"userDataPath": "C:\\Users\\you\\AppData\\Roaming\\Qoder\\User",
"extensionsPath": "C:\\Users\\you\\.qoder\\extensions"
},
"codebuddy-cn": {
"_note": "VS Code-compatible editor/configuration surface only; proprietary CodeBuddy AI features and marketplace state are out of scope.",
"cliPath": "C:\\Users\\you\\AppData\\Local\\Programs\\CodeBuddy CN\\bin\\codebuddy.cmd",
"userDataPath": "C:\\Users\\you\\AppData\\Roaming\\CodeBuddy CN\\User",
"extensionsPath": "C:\\Users\\you\\.codebuddycn\\extensions"
}
},
"ssh": {
"defaultHost": "localhost",
"defaultUser": "you"
},
"wsl": {
"defaultDistro": "Ubuntu-24.04"
}
}

View File

@ -0,0 +1,144 @@
# Recommended Extension Packs
Curated extension lists for common workflows. The skill's `extensions recommend --stack <name>` command prints the matching IDs and (with `--Install`) installs them via the product CLI.
Each ID here is the canonical publisher.extension form expected by `code --install-extension` and Open VSX. The skill does not maintain or vet these extensions; recommendations are subject to change with marketplace churn. Always verify on <https://marketplace.visualstudio.com/> before committing to a stack.
## Stacks
### `frontend-web` — Modern frontend (HTML / CSS / JS / TS / React / Vue)
| ID | Purpose |
|---|---|
| `dbaeumer.vscode-eslint` | ESLint integration |
| `esbenp.prettier-vscode` | Prettier formatter |
| `EditorConfig.EditorConfig` | .editorconfig support |
| `christian-kohler.path-intellisense` | Path autocomplete |
| `formulahendry.auto-rename-tag` | Auto-rename HTML/XML tags |
| `dsznajder.es7-react-js-snippets` | React snippets |
| `styled-components.vscode-styled-components` | styled-components IntelliSense |
| `bradlc.vscode-tailwindcss` | Tailwind CSS IntelliSense |
| `ms-playwright.playwright` | Playwright test runner |
### `backend-python` — Python services
| ID | Purpose |
|---|---|
| `ms-python.python` | Python language support |
| `ms-python.vscode-pylance` | Pylance language server |
| `ms-python.debugpy` | Python debugger |
| `ms-python.black-formatter` | Black formatter |
| `njpwerner.autodocstring` | Auto-generate docstrings |
| `ms-toolsai.jupyter` | Jupyter notebook support |
| `tamasfe.even-better-toml` | TOML support for pyproject.toml |
### `backend-node` — Node.js services
| ID | Purpose |
|---|---|
| `dbaeumer.vscode-eslint` | ESLint |
| `esbenp.prettier-vscode` | Prettier |
| `ms-vscode.vscode-typescript-next` | TypeScript nightly |
| `pranaygp.vscode-css-peek` | CSS class peek |
| `humao.rest-client` | REST API testing |
### `data-science` — Jupyter / data analysis
| ID | Purpose |
|---|---|
| `ms-toolsai.jupyter` | Jupyter notebook |
| `ms-toolsai.vscode-jupyter-powertoys` | Jupyter PowerToys |
| `ms-python.vscode-pylance` | Pylance |
| `mechatroner.rainbow-csv` | Rainbow CSV |
| `randomfractalsinc.vscode-rainbow-brackets` | Rainbow brackets |
| `ms-vsliveshare.vsliveshare` | Live Share collaboration |
### `systems` — Go / Rust / C++
| ID | Purpose |
|---|---|
| `golang.go` | Go language support |
| `rust-lang.rust-analyzer` | Rust language server |
| `ms-vscode.cpptools` | C/C++ extension |
| `ms-azuretools.vscode-docker` | Docker |
| `hashicorp.terraform` | Terraform |
### `devops` — Docker / Kubernetes / CI
| ID | Purpose |
|---|---|
| `ms-azuretools.vscode-docker` | Docker |
| `ms-kubernetes-tools.vscode-kubernetes-tools` | Kubernetes |
| `redhat.vscode-yaml` | YAML with schema validation |
| `hashicorp.terraform` | Terraform |
| `github.vscode-pull-request-github` | GitHub PRs |
### `polyglot-baseline` — Universal starting pack
For users who do not know where to start:
| ID | Purpose |
|---|---|
| `EditorConfig.EditorConfig` | .editorconfig |
| `dbaeumer.vscode-eslint` | ESLint (also helps non-JS) |
| `esbenp.prettier-vscode` | Prettier |
| `eamodio.gitlens` | GitLens |
| `usernamehw.errorlens` | Inline error highlighting |
| `wayou.vscode-todo-highlight` | TODO/FIXME highlighting |
| `oderwat.indent-rainbow` | Indent colors |
| `PKief.material-icon-theme` | File icons |
| `catppuccin.catppuccin-vsc` | Theme (alt: `dracula-theme.theme-dracula`) |
### `ai-coding` — AI / LLM-assisted workflows
| ID | Purpose |
|---|---|
| `Continue.continue` | Open-source AI assistant |
| `RooVeterinaryInc.roo-cline` | AI agent inside VS Code |
| `saoudrizwan.claude-dev` | Claude Code integration |
| `github.copilot` | GitHub Copilot (paid) |
| `github.copilot-chat` | GitHub Copilot Chat (paid) |
> Some AI extensions require their own API keys, OAuth, or paid subscriptions. The skill does not read or store these credentials.
## Usage
### List a stack without installing
```bash
python scripts/vscode_skill.py extensions recommend --Stack frontend-web
```
Output: a JSON array of `{id, purpose}` entries.
### Install a stack
```bash
python scripts/vscode_skill.py extensions install --Stack backend-python
```
This calls `code --install-extension <id>` for each entry, in order. Failures are reported but do not stop the loop. Use `--ForceOpenVsx` to fall back to Open VSX when the official gallery fails.
### Pin in a project
To recommend a stack in `.vscode/extensions.json` (so VS Code prompts the user to install them):
```bash
python scripts/vscode_skill.py extensions pin --Stack frontend-web --ProjectPath /path/to/project
```
The resulting `extensions.json`:
```json
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"..."
]
}
```
## Adding Your Own
Edit this file to add a new stack section. Each stack must have a unique lowercase-hyphenated name. Use the `recommendations` table format consistently so future parsers can extract IDs reliably.

View File

@ -0,0 +1,127 @@
# Keyboard Shortcuts Cheat Sheet
A curated list of the **most remapped** VS Code shortcuts. Use this when a user asks "how do I rebind X?" or "what's the default for Y?". The full default list (~1000 entries) lives at <https://code.visualstudio.com/docs/getstarted/keybindings>.
For the live editor that lists every default, open **File → Preferences → Keyboard Shortcuts** (Ctrl+K Ctrl+S).
## Format
Each row lists the **default chord** (Windows / Linux; macOS differs — see live docs for macOS symbols) and a one-line description. Key notation:
* `Ctrl` — control on Windows / Linux, ⌘ on macOS
* `Shift` — shift on all platforms
* `Alt` — alt on Windows / Linux, ⌥ on macOS
* `Chord` — two keys in sequence (e.g., `Ctrl+K Ctrl+S` means press Ctrl+K, release, then Ctrl+S)
## File & Editor
| Chord | Command |
|---|---|
| `Ctrl+N` | New file |
| `Ctrl+O` | Open file |
| `Ctrl+S` | Save |
| `Ctrl+Shift+S` | Save As |
| `Ctrl+K Ctrl+S` | Open keyboard shortcuts (chord) |
| `Ctrl+W` | Close editor |
| `Ctrl+Shift+T` | Reopen closed editor |
| `Ctrl+P` | Quick open file |
| `Ctrl+Shift+P` | Show all commands (Command Palette) |
| `Ctrl+G` | Go to line |
| `Ctrl+T` | Show all symbols by name |
| `Ctrl+Shift+O` | Go to symbol in file |
| `F12` | Go to definition |
| `Alt+F12` | Peek definition |
| `F2` | Rename symbol |
| `Ctrl+F2` | Find all references |
| `Ctrl+.` | Quick fix (lightbulb) |
| `Ctrl+Shift+K` | Delete line |
| `Alt+↑ / Alt+↓` | Move line up / down |
| `Shift+Alt+↑ / Shift+Alt+↓` | Copy line up / down |
| `Ctrl+/` | Toggle line comment |
| `Ctrl+Shift+A` | Toggle block comment |
| `Ctrl+D` | Add selection to next find match |
| `Ctrl+Shift+L` | Select all occurrences |
| `Alt+Click` | Insert cursor |
| `Ctrl+Alt+↑ / Ctrl+Alt+↓` | Insert cursor above / below |
## Navigation
| Chord | Command |
|---|---|
| `Ctrl+Tab` | Cycle through editors |
| `Ctrl+Shift+Tab` | Cycle through editors (reverse) |
| `Ctrl+1 / Ctrl+2 / ...` | Focus editor group 1, 2, ... |
| `Ctrl+K Ctrl+←/→` | Move editor left / right |
| `Ctrl+Alt+←/→` | Navigate back / forward |
| `Ctrl+E` | Quick open file (alt) |
| `Ctrl+B` | Toggle sidebar |
| `Ctrl+J` | Toggle panel |
| `Ctrl+`` ` | Toggle terminal |
| `Ctrl+Shift+`` ` | Create new terminal |
| `Ctrl+Shift+E` | Focus Explorer |
| `Ctrl+Shift+F` | Focus Search |
| `Ctrl+Shift+G` | Focus Source Control |
| `Ctrl+Shift+D` | Focus Run and Debug |
| `Ctrl+Shift+X` | Focus Extensions |
## Search & Replace
| Chord | Command |
|---|---|
| `Ctrl+F` | Find |
| `Ctrl+H` | Replace |
| `Ctrl+Shift+F` | Find in files |
| `Ctrl+Shift+H` | Replace in files |
| `Alt+C` | Toggle case-sensitive |
| `Alt+W` | Toggle whole-word match |
| `Alt+R` | Toggle regex |
## Debug
| Chord | Command |
|---|---|
| `F5` | Start / continue debugging |
| `Shift+F5` | Stop debugging |
| `F9` | Toggle breakpoint |
| `F10` | Step over |
| `F11` | Step into |
| `Shift+F11` | Step out |
| `Ctrl+Shift+F5` | Restart debugging |
## Tasks & Refactor
| Chord | Command |
|---|---|
| `Ctrl+Shift+B` | Run build task |
| `Ctrl+Shift+P → "Tasks: Run Task"` | Pick a task |
| `Ctrl+Shift+P → "Format Document"` | Format whole document |
| `Ctrl+Shift+P → "Organize Imports"` | Organize imports |
## Common Customizations
These are the most-requested rebinds:
| From | To | Why |
|---|---|---|
| `Ctrl+P` | `Ctrl+P` (unchanged) | Keep as quick open — already great. |
| `Ctrl+Shift+P` | `F1` | Many users keep `F1` as the canonical "command palette". |
| `Ctrl+`` ` | `Ctrl+`` ` (unchanged) | Toggle terminal — convenient. |
| `Alt+Click` | `Ctrl+Alt+Click` | Avoid accidental multi-cursor when using Alt for menu navigation. |
To rebind a chord, edit `keybindings.json` (user-level) or `.vscode/keybindings.json` (workspace-level). Format:
```json
[
{
"key": "ctrl+shift+p",
"command": "workbench.action.showCommands"
}
]
```
The skill does not currently provide a `keybindings set` command; edit `keybindings.json` directly.
## See Also
* Live default list: <https://code.visualstudio.com/docs/getstarted/keybindings>
* Live editor: File → Preferences → Keyboard Shortcuts (Ctrl+K Ctrl+S)

View File

@ -0,0 +1,110 @@
# VS Code Launch Configurations Templates
Ready-to-use `launch.json` templates for the most common debug scenarios. Use the skill's `launch init --template <name> --ProjectPath <path>` command to copy a template into `<project>/.vscode/launch.json`.
For variables and the full schema, see the live docs at <https://code.visualstudio.com/docs/editor/debugging>.
## Available Templates
| Template ID | Language | Purpose |
|---|---|---|
| `node-current-file` | Node.js | Debug the currently active JavaScript file. |
| `node-mocha` | Node.js | Run and debug Mocha tests. |
| `python-current-file` | Python | Debug the currently active Python file. |
| `python-pytest` | Python | Debug a single pytest run. |
| `python-flask` | Python | Debug a Flask app. |
| `go-current-file` | Go | Debug the currently active Go file. |
| `rust-current-file` | Rust | Debug the currently active Rust binary. |
| `chrome-launch` | Browser | Launch Chrome against an HTML file. |
| `chrome-attach` | Browser | Attach to an already-running Chrome instance. |
## Per-Template Details
### `node-current-file`
Runs the active JavaScript / TypeScript file with the workspace folder as `cwd`.
Variables:
* `runtimeExecutable` — node binary (default: `node`).
* `runtimeArgs` — extra node args (default: empty).
### `node-mocha`
Runs Mocha with the workspace's test files.
Variables:
* `mocha.tests` — glob for tests (default: `${workspaceFolder}/test/**/*.js`).
### `python-current-file`
Uses the project's selected Python interpreter.
Variables:
* `python.args` — extra args passed to the script.
### `python-pytest`
Debugs pytest with the active test file.
Variables:
* `pytest.args` — extra pytest args (default: `-v`).
### `python-flask`
Launches a Flask app with debug mode.
Variables:
* `flask.app` — entry point (default: `app.py`).
### `go-current-file`
Uses `dlv` (delve) to debug the current Go file.
Variables:
* `go.file` — file to debug (default: `${file}`).
### `rust-current-file`
Debugs the currently active Rust file using `lldb`.
### `chrome-launch`
Launches Chrome against the workspace's `index.html`.
Variables:
* `url` — URL to open (default: `http://localhost:3000`).
### `chrome-attach`
Attaches to a Chrome instance started with `--remote-debugging-port=9222`.
Variables:
* `url` — URL to attach to (default: `http://localhost:9222`).
## Usage
```powershell
# Windows / PowerShell
pwsh -File scripts/vscode-cli.ps1 launch init --Template python-pytest --ProjectPath D:\projects\demo --Apply
# Cross-platform
python scripts/vscode_skill.py launch init --Template python-pytest --ProjectPath /path/to/demo --Apply
```
The command:
1. Resolves the template under `references/templates/launch/<id>.json`.
2. Writes `<project>/.vscode/launch.json` (creating the directory if missing).
3. Refuses to overwrite an existing file unless `--Force` is passed.
## Adding Your Own
Drop a JSON file at `references/templates/launch/<your-id>.json` and the `launch init --list` command will pick it up automatically. Keep the schema compatible with VS Code's `launch.json` schema (version 0.2.0).

View File

@ -0,0 +1,194 @@
# VS Code Settings Index
A curated reference of the ~80 most-used VS Code settings, grouped by area. Use this index when an agent or user asks "which key controls X" or wants to batch-edit a category.
For the full list (~1000+ settings), see the live documentation at <https://code.visualstudio.com/docs/getstarted/settings>.
## How to Use
* Each entry lists the **key**, **default value**, **type**, and a one-line description.
* `scope` tells you whether the setting is allowed in user-scope, workspace-scope, or both. Some settings are user-only for security reasons (e.g., `terminal.external.*Exec`).
* When the skill needs to **change** a setting, prefer the `settings set` command over hand-editing JSON, so the write is auditable and reversible.
* When a key is not in this index, search the live docs: <https://code.visualstudio.com/search?settings=1>.
## Editor — Appearance
| Key | Default | Type | Scope | Description |
|---|---|---|---|---|
| `editor.fontFamily` | platform default | string | user | Font family for the editor. |
| `editor.fontSize` | 14 | int | both | Font size in pixels. |
| `editor.fontLigatures` | false | bool | both | Enable font ligatures. |
| `editor.fontWeight` | "normal" | string | both | "normal" or "bold" or a number 1-1000. |
| `editor.lineHeight` | 0 | number | both | Line height in px; 0 = auto. |
| `editor.letterSpacing` | 0 | number | both | Letter spacing in px. |
| `editor.cursorStyle` | "line" | enum | both | line / block / underline / line-thin / block-outline / underline-thin. |
| `editor.cursorBlinking` | "blink" | enum | both | blink / smooth / phase / expand / solid. |
| `editor.cursorWidth` | 0 | int | both | Cursor width in px; 0 = auto. |
| `editor.renderWhitespace` | "selection" | enum | both | none / boundary / selection / all / trailing. |
| `editor.renderLineHighlight` | "all" | enum | both | none / gutter / line / all. |
| `editor.renderControlCharacters` | false | bool | both | Show control characters. |
| `editor.wordWrap` | "off" | enum | both | off / on / wordWrapColumn / bounded. |
| `editor.wordWrapColumn` | 80 | int | both | Column for wordWrapColumn/bounded. |
| `editor.minimap.enabled` | true | bool | both | Show minimap. |
| `editor.minimap.maxColumn` | 120 | int | both | Minimap width in columns. |
| `editor.minimap.renderCharacters` | true | bool | both | Render actual characters on minimap. |
| `editor.overviewRulerBorder` | true | bool | both | Border around overview ruler. |
| `editor.scrollBeyondLastLine` | true | bool | both | Allow scrolling past the last line. |
| `editor.smoothScrolling` | false | bool | both | Animate scrolling. |
| `editor.mouseWheelZoom` | false | bool | both | Ctrl + wheel zooms font size. |
| `editor.tabSize` | 4 | int | both | Tab size in spaces. |
| `editor.insertSpaces` | true | bool | both | Insert spaces when pressing Tab. |
| `editor.detectIndentation` | true | bool | user | Auto-detect indentation on file open. |
| `editor.formatOnSave` | false | bool | both | Run formatter on save. |
| `editor.formatOnPaste` | false | bool | both | Run formatter on paste. |
| `editor.formatOnType` | false | bool | both | Run formatter while typing. |
| `editor.defaultFormatter` | null | string | both | Default formatter ID. |
| `editor.codeActionsOnSave` | {} | object | both | Run code actions on save (e.g. organize imports). |
| `editor.bracketPairColorization.enabled` | false | bool | both | Colorize matching brackets. |
| `editor.guides.bracketPairs` | "active" | enum | both | active / always / false. |
| `editor.stickyScroll.enabled` | false | bool | both | Show sticky scroll header. |
| `editor.inlineSuggest.enabled` | true | bool | both | Show inline suggestions. |
## Editor — Behavior
| Key | Default | Type | Scope | Description |
|---|---|---|---|---|
| `editor.acceptSuggestionOnCommitCharacter` | true | bool | both | Accept suggestion on commit character. |
| `editor.acceptSuggestionOnEnter` | "on" | enum | both | on / smart / off. |
| `editor.suggestSelection` | "first" | enum | both | first / recentlyUsed / values. |
| `editor.snippetSuggestions` | "inline" | enum | both | top / bottom / inline / none. |
| `editor.tabCompletion` | "off" | enum | both | on / off / onlySnippets. |
| `editor.quickSuggestions` | {"other": true, "comments": false, "strings": false} | object | both | When to show quick suggestions. |
| `editor.parameterHints.enabled` | true | bool | both | Show parameter hints. |
| `editor.hover.enabled` | true | bool | both | Show hover. |
| `editor.lightbulb.enabled` | true | bool | both | Show code action lightbulb. |
| `editor.foldingStrategy` | "auto" | enum | both | auto / indentation / languageDefined. |
| `editor.showFoldingControls` | "mouseover" | enum | both | always / never / mouseover. |
| `editor.gotoLocation.multiple` | "alt" | enum | both | Default modifier for multi-cursor goto. |
| `editor.multiCursorModifier` | "alt" | enum | both | Modifier for adding cursors. |
| `editor.accessibilitySupport` | "auto" | enum | both | auto / on / off. |
## Files
| Key | Default | Type | Scope | Description |
|---|---|---|---|---|
| `files.autoSave` | "off" | enum | both | off / afterDelay / onFocusChange / onWindowChange. |
| `files.autoSaveDelay` | 1000 | int | both | Delay in ms for autoSave "afterDelay". |
| `files.encoding` | "utf8" | string | both | Default encoding. |
| `files.eol` | "\n" | enum | both | Default EOL: "\n" or "\r\n" or "auto". |
| `files.trimTrailingWhitespace` | false | bool | both | Trim trailing whitespace on save. |
| `files.insertFinalNewline` | false | bool | both | Insert final newline on save. |
| `files.trimFinalNewlines` | false | bool | both | Trim final newlines on save. |
| `files.exclude` | { ... } | object | both | Glob patterns to hide from explorer. |
| `files.watcherExclude` | { ... } | object | both | Globs to exclude from file watcher. |
| `files.associations` | {} | object | both | Map file extension to language ID. |
| `files.defaultLanguage` | null | string | both | Default language for untitled files. |
## Workbench
| Key | Default | Type | Scope | Description |
|---|---|---|---|---|
| `workbench.colorTheme` | "Default Dark+" | string | user | UI theme. |
| `workbench.iconTheme` | "vs-seti" | string | user | File icon theme. |
| `workbench.productIconTheme` | "Default" | string | user | Product icon theme. |
| `workbench.editor.enablePreview` | true | bool | user | Open files in preview mode. |
| `workbench.editor.preferHistoryBasedLanguageAssociations` | true | bool | user | Pick language based on history. |
| `workbench.editor.restoreViewState` | true | bool | user | Restore editor view state on reopen. |
| `workbench.startupEditor` | "welcomePage" | enum | user | none / welcomePage / readme / newUntitledFile / welcomePageInEmptyWorkbench / terminal. |
| `workbench.layoutControl.enabled` | true | bool | user | Show layout control in title bar. |
| `workbench.secondarySideBar.defaultVisibility` | "hidden" | enum | user | hidden / visible / maximized. |
| `workbench.panel.defaultLocation` | "bottom" | enum | user | bottom / left / right / top. |
| `workbench.sideBar.location` | "left" | enum | user | left / right. |
| `workbench.statusBar.visible` | true | bool | user | Show status bar. |
| `workbench.activityBar.visible` | true | bool | user | Show activity bar. |
| `workbench.tips.enabled` | true | bool | user | Show tips on startup. |
| `workbench.settings.editor` | "ui" | enum | user | ui / json. |
| `workbench.settings.enableNaturalLanguageSearch` | true | bool | user | Use natural language in settings search. |
| `workbench.commandPalette.history` | 50 | int | user | Number of recent commands to keep. |
| `workbench.fontAliasing` | "default" | enum | user | default / antialiased / none / grayscale. |
## Window
| Key | Default | Type | Scope | Description |
|---|---|---|---|---|
| `window.title` | "${dirty}${activeEditorShort}${separator}${rootName}" | string | both | Title bar format. |
| `window.titleBarStyle` | platform default | enum | user | native / custom / tabs. |
| `window.openFilesInNewWindow` | "off" | enum | user | on / off / default. |
| `window.dialogStyle` | "native" | enum | user | native / custom. |
| `window.restoreFullscreen` | false | bool | user | Restore fullscreen state. |
| `window.zoomLevel` | 0 | number | user | Zoom level. |
| `window.autoDetectColorScheme` | true | bool | user | Auto-switch light/dark based on OS. |
| `window.systemColorTheme` | "auto" | enum | user | auto / default / light / dark. |
## Terminal
| Key | Default | Type | Scope | Description |
|---|---|---|---|---|
| `terminal.integrated.defaultProfile.windows` | platform default | string | user | Default terminal profile on Windows. |
| `terminal.integrated.defaultProfile.linux` | platform default | string | user | Default terminal profile on Linux. |
| `terminal.integrated.defaultProfile.osx` | platform default | string | user | Default terminal profile on macOS. |
| `terminal.integrated.fontFamily` | platform default | string | both | Terminal font. |
| `terminal.integrated.fontSize` | platform default | int | both | Terminal font size. |
| `terminal.integrated.lineHeight` | 1.0 | number | both | Terminal line height multiplier. |
| `terminal.integrated.cursorStyle` | "block" | enum | both | block / underline / line. |
| `terminal.integrated.cursorBlinking` | false | bool | both | Cursor blinking. |
| `terminal.integrated.scrollback` | 1000 | int | both | Scrollback lines. |
| `terminal.integrated.copyOnSelection` | false | bool | both | Copy to clipboard on selection. |
| `terminal.integrated.enableFileLinks` | "on" | enum | both | on / off / onDetect. |
| `terminal.integrated.gpuAcceleration` | "auto" | enum | both | auto / on / off. |
| `terminal.integrated.shellIntegration.enabled` | true | bool | both | Enable shell integration. |
| `terminal.external.windowsExec` | null | string | user | External terminal binary (user-only for security). |
## Source Control
| Key | Default | Type | Scope | Description |
|---|---|---|---|---|
| `git.enabled` | true | bool | user | Enable Git. |
| `git.path` | null | string | user | Git binary path. |
| `git.autofetch` | false | bool | both | Auto-fetch from remotes. |
| `git.autofetchPeriod` | 180 | int | both | Autofetch period in seconds. |
| `git.confirmSync` | true | bool | user | Confirm before sync. |
| `git.enableSmartCommit` | true | bool | both | Commit all without explicit stage. |
| `git.smartCommitChanges` | "all" | enum | both | all / tracked. |
| `git.inputValidationSubjectLength` | 72 | int | user | Subject line max length. |
| `git.branchSortOrder` | "committerdate" | enum | user | Sort branches by. |
| `scm.diffDecorations` | "all" | enum | both | Diff gutter decorations: all / gutter / overview / minimap / none. |
## Extensions
| Key | Default | Type | Scope | Description |
|---|---|---|---|---|
| `extensions.supportUntrustedWorkspaces` | {} | object | user | Per-extension untrusted workspace support. |
| `extensions.webWorkers` | true | bool | user | Run extensions in web workers. |
| `extensions.checkForProposedApi` | false | bool | user | Show proposed API warnings. |
## Security
| Key | Default | Type | Scope | Description |
|---|---|---|---|---|
| `security.workspace.trust.enabled` | true | bool | user | Enable workspace trust. |
| `security.workspace.trust.startupPrompt` | "oneOnDomainMismatch" | enum | user | always / oneOnDomainMismatch / never. |
| `security.workspace.trust.emptyWindow` | true | bool | user | Trust empty windows by default. |
| `security.workspace.trust.untrustedFiles` | "prompt" | enum | user | prompt / open / silent. |
| `security.workspace.trust.banner` | "show" | enum | user | show / alwaysShow / never. |
## Search
| Key | Default | Type | Scope | Description |
|---|---|---|---|---|
| `search.exclude` | { ... } | object | both | Exclude patterns from search. |
| `search.useGlobalIgnoreFiles` | false | bool | both | Use .gitignore. |
| `search.followSymlinks` | true | bool | both | Follow symlinks. |
| `search.smartCase` | false | bool | both | Smart case search. |
| `search.globalFindClipboard` | false | bool | user | Global find uses clipboard. |
| `search.showLineNumbers` | true | bool | both | Show line numbers in results. |
## Notes on Coverage Gaps
The above is a curated subset. Settings that did not fit here but may matter for specific workflows:
* **Language-specific settings** — language IDs prefix the key (e.g., `typescript.preferences.importModuleSpecifier`, `[python] { ... }` in settings.json).
* **Per-extension settings** — the extension marketplace shows them in the extension's detail page.
* **Telemetry and privacy** — `telemetry.telemetryLevel` and related settings change frequently; check the live docs.
For the **canonical, always-current** list, use <https://code.visualstudio.com/docs/getstarted/settings> as the live source.

View File

@ -0,0 +1,147 @@
# VS Code Settings Precedence
A reference for understanding which setting wins when multiple sources define the same key. Use this when a user reports "I set X but it's still Y" — most "settings not working" issues are caused by a higher-precedence layer overriding the lower one.
## Precedence Layers (low → high)
1. **Default settings** — built into VS Code; not user-visible.
2. **User settings** — `settings.json` in the user data directory.
3. **Remote settings** — `[remoteName]` keyed blocks; active in remote (SSH/WSL/Container) windows.
4. **Workspace settings** — `.vscode/settings.json` (single-root) or `.code-workspace` file (multi-root).
5. **Workspace Folder settings** — multi-root only; targets a specific folder.
6. **Language-specific default settings** — built-in defaults per language ID.
7. **Language-specific user settings** — `"[languageId]": { ... }` block in user `settings.json`.
8. **Language-specific remote settings** — same idea, remote scope.
9. **Language-specific workspace settings** — same idea, workspace scope.
10. **Language-specific workspace folder settings** — multi-root + language combination.
11. **Policy settings** — installed by a system administrator; **always wins**.
## Merge Rules
When the same key appears in multiple layers, two outcomes are possible:
* **Primitive or array types** — entirely replaced by the higher-precedence value.
* **Object types** — merged key-by-key, with higher-precedence values winning on conflicts.
Examples:
```jsonc
// User settings (layer 2)
{
"files.exclude": {
"**/.git": true,
"**/.DS_Store": true
}
}
// Workspace settings (layer 4)
{
"files.exclude": {
"**/node_modules": true // added
// **/.git and **/.DS_Store are kept (object merge)
}
}
```
```jsonc
// User settings
{
"editor.fontSize": 14 // primitive — entirely replaced below
}
// Workspace settings
{
"editor.fontSize": 16 // wins
}
```
## Multi-root Workspaces
Multi-root workspaces (`.code-workspace` file or "Save Workspace As...") introduce **Workspace Folder settings**, which apply to a single folder inside the workspace.
```jsonc
// my-project.code-workspace
{
"folders": [
{ "path": "frontend" },
{ "path": "backend" }
],
"settings": {
// workspace settings (apply to all folders)
"editor.formatOnSave": true
},
"extensions": {
"recommendations": ["dbaeumer.vscode-eslint"]
}
}
```
For per-folder overrides, use the **folder-scoped** settings editor (right-click a folder in Explorer → "Settings"). These are stored as folder-scoped entries in the `.code-workspace` file:
```jsonc
{
"folders": [
{ "path": "frontend", "settings": { "editor.defaultFormatter": "dbaeumer.vscode-eslint" } },
{ "path": "backend", "settings": { "editor.defaultFormatter": "ms-python.black-formatter" } }
]
}
```
## Language-Specific Scopes
A block like `"[python]": { ... }` is itself a key in the JSON, but its precedence follows the language-specific layer of its parent scope. Combined scopes:
```jsonc
{
"[javascript][typescript]": { ... } // applies to both JS and TS (full string match)
"[jsonc]": { ... } // JSON with comments
}
```
The scope string is matched **as a whole string**, not per-language. `"[javascript][typescript]"` does **not** apply to plain JavaScript or plain TypeScript in isolation — VS Code matches the full string against the file's current language scope.
## User-only Settings (Security)
Some settings are restricted to user scope and cannot be set in workspace or workspace-folder layers. These are typically:
* Executable paths (`terminal.external.*Exec`, `git.path`, `python.pythonPath` since v2024)
* Trust-related settings (`security.*`)
* Privacy and telemetry settings (`telemetry.*`)
* Authentication-related settings
If a workspace-scoped setting is silently ignored, check the live docs for the user-only designation.
## Policy Settings (Layer 11)
System administrators can install policies via:
* Windows: registry keys under `HKLM\Software\Policies\Microsoft\VisualStudio\...`
* macOS: managed preferences via MDM profiles
* Linux: `/etc/vscode/policies/` JSON files
Policies always win. If a user reports that they cannot change a setting, verify that no policy is in effect before debugging the precedence chain.
## Debugging Precedence Issues
1. Open **Settings** (Ctrl+,) and search for the key.
2. Note the scope dropdown next to the setting — VS Code shows which layer is currently winning.
3. Right-click a setting → "Modified Elsewhere" shows all layers that set this key.
4. Use `Developer: Inspect Editor Tokens and Scopes` (Ctrl+Shift+P) for language-scope debugging.
## Settings Sync
Settings Sync shares user-scope settings, keybindings, and installed extensions across machines. It does **not** sync:
* Workspace-scoped settings (these stay per-machine)
* Profiles
* Remote-specific settings
* Extensions installed in remote (SSH/WSL) windows
When the same key has been edited on multiple machines between syncs, the most-recent edit wins.
## See Also
* [settings-index.md](settings-index.md) — the curated ~80-setting reference.
* [extension-packs.md](extension-packs.md) — recommended extension bundles.
* [keybindings-cheatsheet.md](keybindings-cheatsheet.md) — keyboard shortcut customization.
* Live docs: <https://code.visualstudio.com/docs/getstarted/settings#_settings-precedence>

View File

@ -0,0 +1,126 @@
# VS Code Tasks Templates
Ready-to-use `tasks.json` templates for common workflows. The skill's `tasks init --template <name> --ProjectPath <path>` command copies the matching template into `<project>/.vscode/tasks.json` and never overwrites an existing file unless `--Force` is passed.
For each template, the **Variables** column lists the VS Code task variables you can edit after copying. See the live docs at <https://code.visualstudio.com/docs/editor/variables-reference>.
## Available Templates
| Template ID | Language | Purpose |
|---|---|---|
| `node-npm` | Node.js | Run `npm` scripts defined in package.json. |
| `node-tsc` | TypeScript | Watch and compile TypeScript. |
| `python-pytest` | Python | Run pytest with current file or all tests. |
| `python-flask` | Python | Run Flask development server. |
| `python-django` | Python | Run Django manage.py. |
| `go-build` | Go | Build, test, and run. |
| `rust-cargo` | Rust | Cargo build / run / test. |
| `docker-build` | Docker | Build a Docker image. |
| `docker-compose-up` | Docker | docker-compose up a stack. |
| `make-default` | Make | Run `make` with selectable target. |
## Per-Template Details
### `node-npm`
Runs npm scripts. Lists scripts defined in `package.json` and lets you pick one.
Variables:
* `script` — name of the npm script to run (e.g., `dev`, `build`, `test`).
### `node-tsc`
TypeScript compile in watch mode.
Variables:
* `tsc.path` — path to the TypeScript compiler (default: `${workspaceFolder}/node_modules/.bin/tsc`).
* `tsconfig` — path to tsconfig.json (default: `${workspaceFolder}/tsconfig.json`).
### `python-pytest`
Runs pytest. Detects whether to run the current file or the full test suite based on `${file}`.
Variables:
* `pytest.args` — extra pytest arguments (default: `-v`).
### `python-flask`
Runs `flask run` with debug mode.
Variables:
* `flask.app` — Flask app entry point (default: `app.py`).
* `flask.env` — FLASK_ENV value (default: `development`).
### `python-django`
Runs Django's `manage.py` with `runserver`.
Variables:
* `django.app` — Django app module (default: `app`).
### `go-build`, `go-test`, `go-run`
Build, test, or run Go code.
Variables:
* `go.package` — package path (default: `${workspaceFolder}`).
### `rust-cargo`
Cargo build / run / test.
Variables:
* `cargo.command` — cargo subcommand (`build`, `run`, `test`, `check`).
### `docker-build`
Builds a Docker image from a Dockerfile in the workspace root.
Variables:
* `image.name` — image tag (default: `${workspaceFolderBasename}:latest`).
* `dockerfile` — Dockerfile path (default: `${workspaceFolder}/Dockerfile`).
### `docker-compose-up`
Brings up a docker-compose stack.
Variables:
* `compose.file` — compose file path (default: `${workspaceFolder}/docker-compose.yml`).
### `make-default`
Runs `make` and prompts for a target.
Variables:
* `make.target` — make target (default: empty, will prompt).
## Usage
```powershell
# Windows / PowerShell
pwsh -File scripts/vscode-cli.ps1 tasks init --Template python-pytest --ProjectPath D:\projects\demo --Apply
# Cross-platform
python scripts/vscode_skill.py tasks init --Template python-pytest --ProjectPath /path/to/demo --Apply
```
The command:
1. Resolves the template under `references/templates/tasks/<id>.json`.
2. Substitutes placeholders with their default values.
3. Writes `<project>/.vscode/tasks.json` (creating the directory if missing).
4. Refuses to overwrite an existing file unless `--Force` is passed.
## Adding Your Own
Drop a JSON file at `references/templates/tasks/<your-id>.json` and the `tasks init --list` command will pick it up automatically. Keep the schema compatible with VS Code's `tasks.json` schema (version 2.0.0).

View File

@ -0,0 +1,12 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Chrome",
"type": "chrome",
"request": "attach",
"url": "http://localhost:9222",
"webRoot": "${workspaceFolder}"
}
]
}

View File

@ -0,0 +1,12 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Chrome against localhost",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
}
]
}

View File

@ -0,0 +1,12 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Go: Current File",
"type": "go",
"request": "launch",
"program": "${file}",
"cwd": "${workspaceFolder}"
}
]
}

View File

@ -0,0 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Run current file",
"program": "${file}",
"cwd": "${workspaceFolder}",
"skipFiles": ["<node_internals>/**"]
}
]
}

View File

@ -0,0 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Mocha tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"args": ["${workspaceFolder}/test/**/*.js"],
"cwd": "${workspaceFolder}",
"skipFiles": ["<node_internals>/**"]
}
]
}

View File

@ -0,0 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}"
}
]
}

View File

@ -0,0 +1,18 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Flask",
"type": "debugpy",
"request": "launch",
"module": "flask",
"args": ["--app", "${workspaceFolder}/app.py", "run", "--debug", "--no-reload"],
"jinja": true,
"console": "integratedTerminal",
"cwd": "${workspaceFolder}",
"env": {
"FLASK_ENV": "development"
}
}
]
}

View File

@ -0,0 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: pytest",
"type": "debugpy",
"request": "launch",
"module": "pytest",
"args": ["${file}", "-v"],
"console": "integratedTerminal",
"cwd": "${workspaceFolder}"
}
]
}

View File

@ -0,0 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Rust: Current File",
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/target/debug/${workspaceFolderBasename}",
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

View File

@ -0,0 +1,24 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "docker: build",
"type": "shell",
"command": "docker",
"args": ["build", "-t", "${input:imageName}", "-f", "${workspaceFolder}/Dockerfile", "${workspaceFolder}"],
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
},
"inputs": [
{
"id": "imageName",
"type": "promptString",
"description": "Image tag",
"default": "${workspaceFolderBasename}:latest"
}
]
}
]
}

View File

@ -0,0 +1,24 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "compose: up",
"type": "shell",
"command": "docker",
"args": ["compose", "-f", "${workspaceFolder}/docker-compose.yml", "up"],
"isBackground": true,
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "compose: down",
"type": "shell",
"command": "docker",
"args": ["compose", "-f", "${workspaceFolder}/docker-compose.yml", "down"],
"problemMatcher": []
}
]
}

View File

@ -0,0 +1,31 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "go: build",
"type": "shell",
"command": "go",
"args": ["build", "./..."],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": ["$go"]
},
{
"label": "go: test",
"type": "shell",
"command": "go",
"args": ["test", "./..."],
"group": "test",
"problemMatcher": ["$go"]
},
{
"label": "go: run current file",
"type": "shell",
"command": "go",
"args": ["run", "${file}"],
"problemMatcher": ["$go"]
}
]
}

View File

@ -0,0 +1,24 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "make: target",
"type": "shell",
"command": "make",
"args": ["${input:makeTarget}"],
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
},
"inputs": [
{
"id": "makeTarget",
"type": "promptString",
"description": "make target",
"default": ""
}
]
}
]
}

View File

@ -0,0 +1,32 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "npm: install",
"type": "shell",
"command": "npm",
"args": ["install"],
"group": "build",
"problemMatcher": []
},
{
"label": "npm: run script",
"type": "shell",
"command": "npm",
"args": ["run", "${input:script}"],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": ["$tsc"],
"inputs": [
{
"id": "script",
"type": "pickString",
"description": "Pick an npm script from package.json",
"options": ["dev", "build", "test", "lint", "start"]
}
]
}
]
}

View File

@ -0,0 +1,24 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "tsc: watch",
"type": "shell",
"command": "${workspaceFolder}/node_modules/.bin/tsc",
"args": ["-p", "${workspaceFolder}/tsconfig.json", "--watch"],
"isBackground": true,
"problemMatcher": ["$tsc-watch"],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "tsc: build",
"type": "shell",
"command": "${workspaceFolder}/node_modules/.bin/tsc",
"args": ["-p", "${workspaceFolder}/tsconfig.json"],
"problemMatcher": ["$tsc"]
}
]
}

View File

@ -0,0 +1,31 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "django: makemigrations",
"type": "shell",
"command": "${command:python.interpreterPath}",
"args": ["manage.py", "makemigrations"],
"problemMatcher": []
},
{
"label": "django: migrate",
"type": "shell",
"command": "${command:python.interpreterPath}",
"args": ["manage.py", "migrate"],
"problemMatcher": []
},
{
"label": "django: runserver",
"type": "shell",
"command": "${command:python.interpreterPath}",
"args": ["manage.py", "runserver"],
"isBackground": true,
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@ -0,0 +1,36 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "flask: run",
"type": "shell",
"command": "${command:python.interpreterPath}",
"args": ["-m", "flask", "--app", "${input:flaskApp}", "run", "--debug"],
"options": {
"env": {
"FLASK_ENV": "${input:flaskEnv}",
"FLASK_DEBUG": "1"
}
},
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [],
"inputs": [
{
"id": "flaskApp",
"type": "promptString",
"description": "Flask app entry point",
"default": "app.py"
},
{
"id": "flaskEnv",
"type": "pickString",
"description": "FLASK_ENV value",
"options": ["development", "production"]
}
]
}
]
}

View File

@ -0,0 +1,24 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "pytest: current file",
"type": "shell",
"command": "${command:python.interpreterPath}",
"args": ["-m", "pytest", "${file}", "-v"],
"group": {
"kind": "test",
"isDefault": true
},
"problemMatcher": []
},
{
"label": "pytest: all",
"type": "shell",
"command": "${command:python.interpreterPath}",
"args": ["-m", "pytest", "-v"],
"group": "test",
"problemMatcher": []
}
]
}

View File

@ -0,0 +1,39 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "cargo: build",
"type": "shell",
"command": "cargo",
"args": ["build"],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": ["$rustc"]
},
{
"label": "cargo: run",
"type": "shell",
"command": "cargo",
"args": ["run"],
"group": "build",
"problemMatcher": ["$rustc"]
},
{
"label": "cargo: test",
"type": "shell",
"command": "cargo",
"args": ["test"],
"group": "test",
"problemMatcher": ["$rustc"]
},
{
"label": "cargo: check",
"type": "shell",
"command": "cargo",
"args": ["check"],
"problemMatcher": ["$rustc"]
}
]
}

View File

@ -0,0 +1,130 @@
# VS Code-Compatible Configuration Files
This reference covers the shared configuration model used by VS Code-family editors. It applies directly to VS Code and VS Code Insiders, and conditionally to derived or compatible products such as Cursor, VSCodium, Trae, Qoder, and CodeBuddy when their local CLI and data paths are configured.
Official VS Code references:
- Settings: https://code.visualstudio.com/docs/configure/settings
- Command line: https://code.visualstudio.com/docs/configure/command-line
- Keybindings: https://code.visualstudio.com/docs/configure/keybindings
- Tasks: https://code.visualstudio.com/docs/debugtest/tasks
- Launch config: https://code.visualstudio.com/docs/debugtest/debugging-configuration
- Snippets: https://code.visualstudio.com/docs/editing/userdefinedsnippets
- Variables: https://code.visualstudio.com/docs/reference/variables-reference
- Profiles: https://code.visualstudio.com/docs/configure/profiles
## Product Matrix
| Product slug | Typical CLI | User settings path on Windows | Extension path on Windows | Support level |
|---|---|---|---|---|
| `vscode` | `code` / `code.cmd` | `%APPDATA%\Code\User\settings.json` | `%USERPROFILE%\.vscode\extensions` | Built-in auto-detection. |
| `vscode-insiders` | `code-insiders` / `code-insiders.cmd` | `%APPDATA%\Code - Insiders\User\settings.json` | `%USERPROFILE%\.vscode-insiders\extensions` | Built-in auto-detection. |
| `cursor` | `cursor` / `cursor.cmd` | `%APPDATA%\Cursor\User\settings.json` | `%USERPROFILE%\.cursor\extensions` | Configure through `products.cursor.*`; CLI may already be on PATH. |
| `vscodium` | `codium` / `codium.cmd` | `%APPDATA%\VSCodium\User\settings.json` | `%USERPROFILE%\.vscode-oss\extensions` | Configure through `products.vscodium.*`. |
| `trae-cn` | `trae-cn.cmd` or product-specific launcher | `%APPDATA%\Trae CN\User\settings.json` | `%USERPROFILE%\.trae-cn\extensions` | VS Code-compatible config only unless a separate Trae adapter is documented. |
| `qoder` | `qoder` / `qoder.cmd` when installed | `%APPDATA%\Qoder\User\settings.json` | `%USERPROFILE%\.qoder\extensions` | Config-only example; proprietary AI features are out of scope. |
| `codebuddy-cn` | `codebuddy` / `codebuddy.cmd` when installed | `%APPDATA%\CodeBuddy CN\User\settings.json` | `%USERPROFILE%\.codebuddycn\extensions` | Config-only example; proprietary AI features and marketplace state are out of scope. |
For any slug other than `vscode` and `vscode-insiders`, set at least:
```json
{
"products": {
"codebuddy-cn": {
"cliPath": "C:\\Users\\you\\AppData\\Local\\Programs\\CodeBuddy CN\\bin\\codebuddy.cmd",
"userDataPath": "C:\\Users\\you\\AppData\\Roaming\\CodeBuddy CN\\User",
"extensionsPath": "C:\\Users\\you\\.codebuddycn\\extensions"
}
}
}
```
Generic products intentionally fail when `cliPath` is missing. Do not silently map Qoder, CodeBuddy, Trae, Cursor, or VSCodium to the normal VS Code CLI; that would modify or inspect the wrong product.
## Scope Layers
Common settings precedence from broad to specific:
- Default settings: product-provided, not edited directly.
- User settings: current user across windows.
- Profile settings: current profile.
- Workspace or project settings: usually `.vscode/settings.json`.
- Language-specific settings such as `[python]` and `[markdown]`.
Use user settings for personal preferences such as fonts, themes, and window behavior. Use project settings for shared formatter, linter, test, language-server, and file-exclude rules. Do not commit machine-local absolute paths, accounts, tokens, private servers, or temporary directories unless the repository is explicitly a single-machine workspace.
## Project `.vscode` Files
`.vscode/settings.json`
- Stores project-level editor and extension settings.
- Good for formatter, lint, type checking, tests, language tools, and file exclusions.
- Bad for personal themes, fonts, machine paths, account state, or secrets.
`.vscode/launch.json`
- Stores debugger configurations.
- Common fields: `name`, `type`, `request`, `program`, `args`, `cwd`, `env`, `preLaunchTask`.
- Do not place real secrets in `env`; reference environment variables or local untracked files.
`.vscode/tasks.json`
- Stores build, test, script, and external-command tasks.
- Common fields: `label`, `type`, `command`, `args`, `group`, `problemMatcher`, `dependsOn`.
- `launch.json` can call tasks through `preLaunchTask`.
`.vscode/extensions.json`
- Stores recommended and unwanted extensions.
- Recommendations are advisory and do not imply automatic installation.
## Common Settings Keys
- `editor.fontFamily`: editor font.
- `editor.fontSize`: font size.
- `editor.lineHeight`: line height.
- `editor.wordWrap`: wrapping mode, such as `on`, `off`, `wordWrapColumn`, or `bounded`.
- `editor.tabSize`: tab width.
- `editor.formatOnSave`: format on save.
- `editor.defaultFormatter`: default formatter extension.
- `files.autoSave`: auto save mode, such as `off`, `afterDelay`, `onFocusChange`, or `onWindowChange`.
- `files.exclude`: Explorer hide rules.
- `search.exclude`: Search exclude rules.
- `workbench.colorTheme`: color theme.
- `workbench.iconTheme`: file icon theme.
- `workbench.productIconTheme`: product icon theme.
- `terminal.integrated.defaultProfile.windows`: Windows terminal profile.
- `python.defaultInterpreterPath`: Python extension default interpreter.
## Variables
`tasks.json` and `launch.json` commonly use:
- `${workspaceFolder}`: current workspace root.
- `${file}`: active file absolute path.
- `${fileBasename}`: active file name.
- `${relativeFile}`: active file path relative to workspace.
- `${env:NAME}`: environment variable.
- `${config:KEY}`: VS Code setting value.
- `${command:COMMAND_ID}`: command result.
Variables reduce absolute paths but do not remove security requirements. Secrets should still come from environment variables or local private files.
## JSON Notes
VS Code settings often use JSON with Comments, including comments and trailing commas. PowerShell `ConvertFrom-Json` and Python's standard JSON parser only handle strict JSON. Before automated edits, confirm the file is strict JSON or use a JSONC-aware tool.
When modifying settings:
- Preserve unrelated keys.
- Do not rewrite unknown objects as empty objects.
- Read, modify, and write only the intended target.
- Verify target keys after writing.
## Product Boundaries
VS Code-compatible products may share `.vscode` project configuration while using different command names, user data paths, extension roots, and remote server folders.
- Keep this skill on the editor/configuration layer.
- Treat product-specific AI chats, agents, accounts, models, proprietary marketplaces, local databases, tokens, and cache/state files as out of scope.
- Add a separate adapter only when the user provides official product-specific documentation and explicitly asks for that product's private surface.

View File

@ -0,0 +1,55 @@
# init-config.ps1 - write a starter config file in the platform default location.
#
# Usage:
# pwsh -File scripts/init-config.ps1 # uses default location
# pwsh -File scripts/init-config.ps1 -Path C:\path\to\config.json
# pwsh -File scripts/init-config.ps1 -Force # overwrite existing
[CmdletBinding()]
param(
[string]$Path,
[switch]$Force
)
$ErrorActionPreference = "Stop"
$scriptRoot = Split-Path -Parent $PSCommandPath
$examplePath = Join-Path $scriptRoot "..\config.example.json"
if (-not (Test-Path -LiteralPath $examplePath)) {
throw "Cannot find example config at $examplePath. Run from the skill root or fix the script layout."
}
if (-not $Path) {
if ($env:VSCODE_SKILL_CONFIG) {
$Path = $env:VSCODE_SKILL_CONFIG
} elseif ($env:APPDATA) {
$Path = Join-Path $env:APPDATA "vscode-skill\config.json"
} elseif ($env:XDG_CONFIG_HOME) {
$Path = Join-Path $env:XDG_CONFIG_HOME "vscode-skill/config.json"
} elseif ($env:HOME) {
$Path = Join-Path $env:HOME ".config/vscode-skill/config.json"
} else {
throw "Cannot determine a default config path. Pass -Path explicitly."
}
}
if ([System.IO.Path]::IsPathRooted($Path)) {
$resolvedPath = [System.IO.Path]::GetFullPath($Path)
} else {
$resolvedPath = [System.IO.Path]::GetFullPath((Join-Path (Get-Location) $Path))
}
if ((Test-Path -LiteralPath $resolvedPath) -and -not $Force) {
Write-Host "Config already exists at $resolvedPath. Re-run with -Force to overwrite."
return
}
$parent = Split-Path -Parent $resolvedPath
if ($parent) {
New-Item -ItemType Directory -Path $parent -Force | Out-Null
}
Copy-Item -LiteralPath $examplePath -Destination $resolvedPath -Force
Write-Host "Wrote starter config to $resolvedPath"
Write-Host "Edit the file, then re-run any vscode-cli.ps1 command; the new paths will be picked up automatically."

View File

@ -0,0 +1,378 @@
#!/usr/bin/env python3
"""
Safe test runner for the vscode skill.
Default tests do not write user settings, install extensions, open remote
windows, or modify any IDE state outside a temporary scratch directory. Temp
artifacts land in an isolated subdirectory under $VSCODE_SKILL_TEMP when set,
otherwise under the platform temp directory.
"""
from __future__ import annotations
import argparse
import json
import os
import shutil
import subprocess
import sys
import tempfile
import hashlib
from contextlib import contextmanager
from pathlib import Path
from typing import Any
SKILL_DIR = Path(__file__).resolve().parents[1]
SCRIPT = SKILL_DIR / "scripts" / "vscode-cli.ps1"
SSHD_SCRIPT = SKILL_DIR / "scripts" / "setup-local-sshd.ps1"
def _default_quick_validate() -> Path | None:
"""Look for quick_validate.py from the Agent Skills skill-creator reference.
Search order:
1. $VSCODE_SKILL_QUICK_VALIDATE (explicit override)
2. <skill-creator>/scripts/quick_validate.py under common dev locations
Returns None if not found; the caller decides whether to skip the test.
"""
env = os.environ.get("VSCODE_SKILL_QUICK_VALIDATE")
if env:
p = Path(env)
return p if p.is_file() else None
candidates: list[Path] = []
for base in (
os.environ.get("VSCODE_SKILL_CREATOR_HOME"),
os.environ.get("CODEX_HOME"),
os.environ.get("ZCODE_HOME"),
os.environ.get("USERPROFILE"),
os.environ.get("HOME"),
):
if not base:
continue
root = Path(base)
candidates.extend(
[
root / ".codex" / "skills" / ".system" / "skill-creator" / "scripts" / "quick_validate.py",
root / ".zcode" / "skills" / ".system" / "skill-creator" / "scripts" / "quick_validate.py",
root / ".agents" / "skills" / "skill-creator" / "scripts" / "quick_validate.py",
]
)
seen: set[Path] = set()
for c in candidates:
try:
resolved = c.resolve()
except OSError:
continue
if resolved in seen:
continue
seen.add(resolved)
if resolved.is_file():
return resolved
return None
QUICK_VALIDATE = _default_quick_validate()
def _default_temp_base() -> Path:
override = os.environ.get("VSCODE_SKILL_TEMP")
base = Path(override) if override else Path(os.environ.get("TEMP", tempfile.gettempdir()))
return base / "vscode-skill-tests"
def _default_temp_root() -> Path:
digest = hashlib.sha1(str(SKILL_DIR).encode("utf-8")).hexdigest()[:10]
return _default_temp_base() / f"{SKILL_DIR.name}-{digest}"
TEMP_ROOT = _default_temp_root()
def ps_quote(value: str) -> str:
return "'" + value.replace("'", "''") + "'"
def run(
args: list[str],
*,
name: str,
timeout: int = 60,
expect_ok: bool = True,
cwd: Path | None = None,
) -> dict[str, Any]:
proc = subprocess.run(
args,
cwd=str(cwd) if cwd else None,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=timeout,
shell=False,
)
ok = proc.returncode == 0
passed = ok if expect_ok else not ok
return {
"name": name,
"command": args,
"returncode": proc.returncode,
"expectOk": expect_ok,
"passed": passed,
"stdout": proc.stdout[-6000:],
"stderr": proc.stderr[-6000:],
}
def run_ps(command: str, *, name: str, timeout: int = 60, expect_ok: bool = True) -> dict[str, Any]:
return run(
["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", command],
name=name,
timeout=timeout,
expect_ok=expect_ok,
)
def run_pwsh(command: str, *, name: str, timeout: int = 60, expect_ok: bool = True) -> dict[str, Any]:
"""Run through PowerShell 7 (pwsh) when available, fall back to Windows PowerShell."""
candidates: list[list[str]] = []
if shutil.which("pwsh"):
candidates.append(["pwsh", "-NoProfile", "-Command", command])
candidates.append(["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", command])
last: dict[str, Any] | None = None
for cmd in candidates:
result = run(cmd, name=name, timeout=timeout, expect_ok=expect_ok)
last = result
if result["passed"]:
return result
return last or {"name": name, "passed": False, "stderr": "no powershell host found"}
def run_vscode_cli(area: str, action: str | None = None, *extra: str, name: str, timeout: int = 60, expect_ok: bool = True) -> dict[str, Any]:
cmd = ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", str(SCRIPT), area]
if action:
cmd.append(action)
cmd.extend(extra)
return run(cmd, name=name, timeout=timeout, expect_ok=expect_ok)
def powershell_parse(path: Path, name: str) -> dict[str, Any]:
command = (
"$tokens=$null; $errors=$null; "
f"[System.Management.Automation.Language.Parser]::ParseFile({ps_quote(str(path))},[ref]$tokens,[ref]$errors) | Out-Null; "
"if ($errors.Count) { $errors | Format-List Message,Extent; exit 1 } else { 'parse OK' }"
)
return run_pwsh(command, name=name)
def add(results: list[dict[str, Any]], result: dict[str, Any]) -> None:
results.append(result)
@contextmanager
def temporary_env(updates: dict[str, str]):
original = {key: os.environ.get(key) for key in updates}
os.environ.update(updates)
try:
yield
finally:
for key, value in original.items():
if value is None:
os.environ.pop(key, None)
else:
os.environ[key] = value
def write_fake_cli(path: Path, label: str = "Fake VS Code CLI") -> None:
if os.name == "nt":
path.write_text(
"@echo off\r\n"
"if \"%~1\"==\"--version\" (\r\n"
f" echo {label} 0.0.0\r\n"
" echo test-commit\r\n"
" echo x64\r\n"
" exit /b 0\r\n"
")\r\n"
"if \"%~1\"==\"--list-extensions\" exit /b 0\r\n"
f"echo {label} %*\r\n"
"exit /b 0\r\n",
encoding="utf-8",
)
else:
path.write_text(
"#!/usr/bin/env sh\n"
"case \"$1\" in\n"
f" --version) printf '%s\\n' '{label} 0.0.0' 'test-commit' 'x64'; exit 0 ;;\n"
" --list-extensions) exit 0 ;;\n"
f" *) printf '%s %s\\n' '{label}' \"$*\"; exit 0 ;;\n"
"esac\n",
encoding="utf-8",
)
path.chmod(0o755)
def basic_tests(results: list[dict[str, Any]], scratch: Path) -> None:
if QUICK_VALIDATE is not None:
add(results, run([sys.executable, "-X", "utf8", str(QUICK_VALIDATE), str(SKILL_DIR)], name="quick_validate"))
else:
results.append(
{
"name": "quick_validate",
"command": ["quick_validate.py"],
"returncode": 0,
"expectOk": False,
"passed": True,
"stdout": "skipped (quick_validate.py not located; set VSCODE_SKILL_QUICK_VALIDATE to enable)",
"stderr": "",
}
)
add(results, powershell_parse(SCRIPT, "parse vscode-cli.ps1"))
if SSHD_SCRIPT.exists():
add(results, powershell_parse(SSHD_SCRIPT, "parse setup-local-sshd.ps1"))
fake_code = scratch / ("fake-code.cmd" if os.name == "nt" else "fake-code")
fake_user = scratch / "Code" / "User"
fake_ext = scratch / ".vscode" / "extensions"
write_fake_cli(fake_code)
fake_user.mkdir(parents=True, exist_ok=True)
fake_ext.mkdir(parents=True, exist_ok=True)
with temporary_env({
"VSCODE_CODE_CLI": str(fake_code),
"VSCODE_USER_DATA": str(fake_user),
"VSCODE_EXTENSIONS_DIR": str(fake_ext),
}):
add(results, run_vscode_cli("info", None, "--Product", "auto", name="info auto with fake cli", timeout=90))
add(results, run_vscode_cli("ext", "list", "--Product", "auto", name="ext list auto with fake cli", timeout=90))
add(results, run_vscode_cli("settings-sync", "status", name="settings-sync status"))
add(results, run_vscode_cli("profile", "list", name="profile list"))
add(results, run_vscode_cli("settings", "path", "--Product", "vscode", name="settings path vscode"))
add(results, run_vscode_cli("settings", "get", "--Product", "vscode", name="settings get vscode"))
add(results, run_vscode_cli("settings", "path", "--Product", "codebuddy-cn", name="generic product requires configured path", expect_ok=False))
generic_cli = scratch / ("fake-codebuddy.cmd" if os.name == "nt" else "fake-codebuddy")
write_fake_cli(generic_cli, "Fake CodeBuddy CLI")
generic_user = scratch / "CodeBuddy CN" / "User"
generic_ext = scratch / ".codebuddycn" / "extensions"
generic_user.mkdir(parents=True, exist_ok=True)
generic_ext.mkdir(parents=True, exist_ok=True)
generic_config = scratch / "generic-product-config.json"
generic_config.write_text(
json.dumps(
{
"product": "codebuddy-cn",
"products": {
"codebuddy-cn": {
"cliPath": str(generic_cli),
"userDataPath": str(generic_user),
"extensionsPath": str(generic_ext),
}
},
},
indent=2,
),
encoding="utf-8",
)
original_config = os.environ.get("VSCODE_SKILL_CONFIG")
os.environ["VSCODE_SKILL_CONFIG"] = str(generic_config)
try:
add(results, run_vscode_cli("settings", "path", "--Product", "codebuddy-cn", name="generic product configured settings path"))
add(results, run_vscode_cli("info", None, "--Product", "codebuddy-cn", name="generic product configured info"))
finally:
if original_config is None:
os.environ.pop("VSCODE_SKILL_CONFIG", None)
else:
os.environ["VSCODE_SKILL_CONFIG"] = original_config
project = scratch / "project-settings-smoke"
project.mkdir(parents=True, exist_ok=True)
add(
results,
run_vscode_cli(
"settings",
"smoke",
"--Product",
"vscode",
"--Scope",
"project",
"--ProjectPath",
str(project),
name="project settings smoke vscode",
),
)
# New areas (v1.1): templates, extensions, settings-sync, profile, workspace.
add(results, run_vscode_cli("tasks", "list", name="tasks list"))
add(results, run_vscode_cli("launch", "list", name="launch list"))
add(results, run_vscode_cli("extensions", "stacks", name="extensions stacks"))
add(results, run_vscode_cli("extensions", "recommend", "--Stack", "polyglot-baseline", name="extensions recommend"))
tasks_project = scratch / "tasks-init"
tasks_project.mkdir(parents=True, exist_ok=True)
add(
results,
run_vscode_cli(
"tasks", "init",
"--Template", "python-pytest",
"--ProjectPath", str(tasks_project),
"--Apply",
name="tasks init python-pytest",
),
)
if (tasks_project / ".vscode" / "tasks.json").is_file():
add(results, {"name": "tasks init wrote file", "command": [], "returncode": 0, "expectOk": True, "passed": True, "stdout": "", "stderr": ""})
else:
add(results, {"name": "tasks init wrote file", "command": [], "returncode": 1, "expectOk": True, "passed": False, "stdout": "", "stderr": "tasks.json missing"})
def optional_python_venv(results: list[dict[str, Any]], scratch: Path) -> None:
project = scratch / "python-venv"
add(results, run_vscode_cli("python", "make-venvs", "--ProjectPath", str(project), name="python make-venvs", timeout=180))
add(results, run_vscode_cli("python", "set-interpreter", "--ProjectPath", str(project), "--VenvName", ".venv-a", name="python set interpreter"))
add(results, run_vscode_cli("python", "verify", "--ProjectPath", str(project), "--ExpectedVenvName", ".venv-a", name="python verify interpreter"))
def main() -> int:
parser = argparse.ArgumentParser(description="Run safe checks for the vscode skill.")
parser.add_argument("--basic", action="store_true", help="Run basic safe tests. Default when no group is selected.")
parser.add_argument("--include-python-venv", action="store_true")
parser.add_argument("--clean", action="store_true", help="Delete old temp root before testing.")
args = parser.parse_args()
if args.clean and TEMP_ROOT.exists():
shutil.rmtree(TEMP_ROOT)
TEMP_ROOT.mkdir(parents=True, exist_ok=True)
results: list[dict[str, Any]] = []
if args.basic or not args.include_python_venv:
basic_tests(results, TEMP_ROOT)
if args.include_python_venv:
optional_python_venv(results, TEMP_ROOT)
passed = sum(1 for item in results if item["passed"])
failed = len(results) - passed
summary = {
"ok": failed == 0,
"passed": passed,
"failed": failed,
"tempRoot": str(TEMP_ROOT),
"skillDir": str(SKILL_DIR),
"results": results,
}
out_path = TEMP_ROOT / "test-summary.json"
out_path.write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps({k: v for k, v in summary.items() if k != "results"}, ensure_ascii=False, indent=2))
if failed:
for item in results:
if not item["passed"]:
print(f"FAILED: {item['name']} rc={item['returncode']}", file=sys.stderr)
if item.get("stderr"):
print(item["stderr"][-1200:], file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -0,0 +1,163 @@
param(
[switch]$Apply
)
$ErrorActionPreference = "Stop"
$keyPath = Join-Path $env:USERPROFILE ".ssh\id_ed25519_trae_local_test"
$pubKeyPath = "$keyPath.pub"
$sshDir = Join-Path $env:USERPROFILE ".ssh"
$authorizedKeys = Join-Path $sshDir "authorized_keys"
$sshConfig = Join-Path $sshDir "config"
$hostBlockStart = "# >>> trae-localhost managed by vscode skill"
$hostBlockEnd = "# <<< trae-localhost managed by vscode skill"
function Test-IsAdmin {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Get-OpenSshServerCapability {
try {
return Get-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 -ErrorAction Stop
} catch {
return $null
}
}
function Write-State {
$isAdmin = Test-IsAdmin
$sshd = Get-Service -Name sshd -ErrorAction SilentlyContinue
$agent = Get-Service -Name ssh-agent -ErrorAction SilentlyContinue
$cap = Get-OpenSshServerCapability
$port = Test-NetConnection localhost -Port 22 -WarningAction SilentlyContinue
[pscustomobject]@{
IsAdmin = $isAdmin
OpenSshServerState = if ($cap) { $cap.State } else { "UnknownOrRequiresElevation" }
SshdService = if ($sshd) { $sshd.Status } else { "Missing" }
SshdStartType = if ($sshd) { $sshd.StartType } else { "Missing" }
SshAgentService = if ($agent) { $agent.Status } else { "Missing" }
Localhost22 = $port.TcpTestSucceeded
TestKeyExists = (Test-Path -LiteralPath $keyPath)
AuthorizedKeysExists = (Test-Path -LiteralPath $authorizedKeys)
SshConfigExists = (Test-Path -LiteralPath $sshConfig)
} | Format-List
}
function Ensure-Admin {
if (-not (Test-IsAdmin)) {
throw "setup-local-sshd.ps1 -Apply requires an elevated Administrator PowerShell. Re-run this script as Administrator."
}
}
function Ensure-OpenSshServer {
$cap = Get-OpenSshServerCapability
if (-not $cap) {
throw "Cannot inspect OpenSSH Server capability. Run from an elevated Administrator PowerShell."
}
if ($cap.State -ne "Installed") {
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 | Out-Null
}
$sshd = Get-Service -Name sshd -ErrorAction SilentlyContinue
if (-not $sshd) {
throw "OpenSSH Server capability install completed, but sshd service was not found."
}
Set-Service -Name sshd -StartupType Automatic
Start-Service -Name sshd
$rule = Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue
if (-not $rule) {
New-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -DisplayName "OpenSSH Server (sshd)" -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 | Out-Null
} else {
Enable-NetFirewallRule -Name "OpenSSH-Server-In-TCP" | Out-Null
}
}
function Ensure-TestKey {
New-Item -ItemType Directory -Path $sshDir -Force | Out-Null
if (-not (Test-Path -LiteralPath $keyPath)) {
ssh-keygen -t ed25519 -N "" -f $keyPath -C "trae-localhost-test" | Out-Null
}
if (-not (Test-Path -LiteralPath $pubKeyPath)) {
throw "Public key was not created: $pubKeyPath"
}
}
function Ensure-AuthorizedKey {
$pub = Get-Content -LiteralPath $pubKeyPath -Encoding ASCII -Raw
if ([string]::IsNullOrWhiteSpace($pub)) {
throw "Public key is empty: $pubKeyPath"
}
if (-not (Test-Path -LiteralPath $authorizedKeys)) {
New-Item -ItemType File -Path $authorizedKeys -Force | Out-Null
}
$existing = Get-Content -LiteralPath $authorizedKeys -Encoding ASCII -Raw -ErrorAction SilentlyContinue
if ($existing -notmatch [regex]::Escape($pub.Trim())) {
Add-Content -LiteralPath $authorizedKeys -Value $pub.Trim() -Encoding ASCII
}
}
function Ensure-SshConfig {
if (-not (Test-Path -LiteralPath $sshConfig)) {
New-Item -ItemType File -Path $sshConfig -Force | Out-Null
}
$content = Get-Content -LiteralPath $sshConfig -Encoding UTF8 -Raw -ErrorAction SilentlyContinue
if ($null -eq $content) {
$content = ""
}
$block = @"
$hostBlockStart
Host trae-localhost
HostName localhost
Port 22
User $env:USERNAME
IdentityFile $keyPath
IdentitiesOnly yes
StrictHostKeyChecking accept-new
$hostBlockEnd
"@
$pattern = "(?s)$([regex]::Escape($hostBlockStart)).*?$([regex]::Escape($hostBlockEnd))"
if ($content -match $pattern) {
$content = [regex]::Replace($content, $pattern, $block)
} else {
if (-not $content.EndsWith("`n") -and $content.Length -gt 0) {
$content += "`r`n"
}
$content += $block + "`r`n"
}
Set-Content -LiteralPath $sshConfig -Value $content -Encoding UTF8
}
Write-Host "== Current state =="
Write-State
if (-not $Apply) {
Write-Host ""
Write-Host "Dry run only. Re-run with -Apply in an elevated Administrator PowerShell to enable local sshd."
exit 0
}
Ensure-Admin
Ensure-OpenSshServer
Ensure-TestKey
Ensure-AuthorizedKey
Ensure-SshConfig
Write-Host ""
Write-Host "== After apply =="
Write-State
Write-Host ""
Write-Host "Run this to verify:"
Write-Host "ssh trae-localhost `"echo trae-ssh-ok`""

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,801 @@
#!/usr/bin/env python3
"""Cross-platform gateway for the VS Code agent skill.
Routing rules:
- On Windows: forwards directly to scripts/vscode-cli.ps1 (the PowerShell
driver that has passed 11/11 safe tests). All flags and areas are passed
through unchanged so behavior on Windows is bit-for-bit identical to the
previous skill.
- On macOS / Linux: provides native Python implementations for the
read/write areas that are safe and portable (info, ext list, settings
get/path/set/smoke, python set-interpreter). Operations requiring VS
Code itself (ext install, ssh open, wsl open) still shell out to the
``code`` CLI.
The gateway never modifies PATH, never reads tokens, and never writes
outside the resolved settings file (or the temp scratch directory during
``settings smoke``).
"""
from __future__ import annotations
import argparse
import json
import os
import platform
import re
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Any
SKILL_ROOT = Path(__file__).resolve().parents[1]
PS_DRIVER = SKILL_ROOT / "scripts" / "vscode-cli.ps1"
IS_WINDOWS = platform.system() == "Windows"
# --------------------------------------------------------------------------- #
# Path resolution (cross-platform)
# --------------------------------------------------------------------------- #
def _resolve_config_path() -> Path | None:
env = os.environ.get("VSCODE_SKILL_CONFIG")
if env:
return Path(env)
if IS_WINDOWS:
appdata = os.environ.get("APPDATA")
if appdata:
return Path(appdata) / "vscode-skill" / "config.json"
xdg = os.environ.get("XDG_CONFIG_HOME")
if xdg:
return Path(xdg) / "vscode-skill" / "config.json"
home = Path.home()
return home / ".config" / "vscode-skill" / "config.json"
def _load_config() -> dict[str, Any]:
cfg_path = _resolve_config_path()
if cfg_path and cfg_path.is_file():
try:
return json.loads(cfg_path.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return {}
return {}
def _resolve_cli(product: str) -> str:
cfg = _load_config()
products = cfg.get("products") or {}
entry = products.get(product) or {}
cli_path = entry.get("cliPath")
if cli_path and Path(cli_path).is_file():
return cli_path
if product == "vscode":
env_override = os.environ.get("VSCODE_CODE_CLI")
if env_override and Path(env_override).is_file():
return env_override
for cand in ("code.cmd", "code"):
found = shutil.which(cand)
if found:
return found
if IS_WINDOWS:
local = os.environ.get("LOCALAPPDATA")
if local:
p = Path(local) / "Programs" / "Microsoft VS Code" / "bin" / "code.cmd"
if p.is_file():
return str(p)
if product == "vscode-insiders":
env_override = os.environ.get("VSCODE_INSIDERS_CLI")
if env_override and Path(env_override).is_file():
return env_override
for cand in ("code-insiders.cmd", "code-insiders"):
found = shutil.which(cand)
if found:
return found
raise FileNotFoundError(f"Cannot resolve CLI for product '{product}'. "
f"Set products.{product}.cliPath in your config or add it to PATH.")
def _user_data_dir(product: str) -> Path:
env_override = os.environ.get("VSCODE_USER_DATA")
if env_override:
return Path(env_override)
cfg = _load_config()
entry = (cfg.get("products") or {}).get(product) or {}
custom = entry.get("userDataPath")
if custom:
return Path(custom)
if product == "vscode":
if IS_WINDOWS:
return Path(os.environ["APPDATA"]) / "Code" / "User"
if platform.system() == "Darwin":
return Path.home() / "Library" / "Application Support" / "Code" / "User"
xdg = os.environ.get("XDG_CONFIG_HOME") or str(Path.home() / ".config")
return Path(xdg) / "Code" / "User"
if product == "vscode-insiders":
if IS_WINDOWS:
return Path(os.environ["APPDATA"]) / "Code - Insiders" / "User"
if platform.system() == "Darwin":
return Path.home() / "Library" / "Application Support" / "Code - Insiders" / "User"
xdg = os.environ.get("XDG_CONFIG_HOME") or str(Path.home() / ".config")
return Path(xdg) / "Code - Insiders" / "User"
raise FileNotFoundError(f"No userDataPath known for product '{product}'.")
def _settings_path(scope: str, project: str | None) -> Path:
if scope == "project":
if not project:
raise ValueError("Project scope requires --ProjectPath.")
return Path(project) / ".vscode" / "settings.json"
if scope == "user":
return _user_data_dir(_resolve_default_product()) / "settings.json"
raise ValueError(f"Unknown scope '{scope}'.")
def _resolve_default_product() -> str:
env = os.environ.get("VSCODE_SKILL_PRODUCT")
if env:
return env
cfg = _load_config()
p = cfg.get("product")
if p:
return str(p)
return "vscode"
# --------------------------------------------------------------------------- #
# Forwarding to PowerShell on Windows
# --------------------------------------------------------------------------- #
def _forward_powershell(argv: list[str]) -> int:
if not PS_DRIVER.is_file():
print(f"error: PowerShell driver not found at {PS_DRIVER}", file=sys.stderr)
return 2
args = ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", str(PS_DRIVER), *argv]
return subprocess.call(args)
# --------------------------------------------------------------------------- #
# Native macOS / Linux implementations
# --------------------------------------------------------------------------- #
def _read_settings(path: Path) -> dict[str, Any]:
if not path.is_file():
return {}
raw = path.read_text(encoding="utf-8")
if not raw.strip():
return {}
try:
return json.loads(raw)
except json.JSONDecodeError:
print(f"warning: {path} is not valid JSON; treating as empty.", file=sys.stderr)
return {}
def _write_settings(path: Path, data: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, indent=4, ensure_ascii=False) + "\n", encoding="utf-8")
def _coerce(value: str) -> Any:
s = value.strip()
if s.lower() in ("true", "false"):
return s.lower() == "true"
if s.lstrip("-").isdigit():
return int(s)
try:
return float(s)
except ValueError:
pass
if (s.startswith("{") and s.endswith("}")) or (s.startswith("[") and s.endswith("]")):
try:
return json.loads(s)
except json.JSONDecodeError:
pass
return value
SETTINGS_FLAG_MAP = {
"--FontSize": ("editor.fontSize", int),
"--LineHeight": ("editor.lineHeight", float),
"--FontFamily": ("editor.fontFamily", None),
"--ColorTheme": ("workbench.colorTheme", None),
"--IconTheme": ("workbench.iconTheme", None),
"--ProductIconTheme": ("workbench.productIconTheme", None),
"--WordWrap": ("editor.wordWrap", None),
"--TabSize": ("editor.tabSize", int),
"--FormatOnSave": ("editor.formatOnSave", _coerce),
"--AutoSave": ("files.autoSave", None),
"--Minimap": ("editor.minimap.enabled", _coerce),
}
def cmd_info(_args: argparse.Namespace) -> int:
product = _resolve_default_product()
cli = _resolve_cli(product)
user = _user_data_dir(product)
print(f"== {product} ==")
print(f" CliPath : {cli}")
print(f" UserSettings : {user / 'settings.json'}")
print(f" Extensions : {user.parent.parent / 'extensions'}")
print(f" ConfigFile : {_resolve_config_path()}")
print(f"== Version ==")
subprocess.call([cli, "--version"])
return 0
def cmd_ext_list(_args: argparse.Namespace) -> int:
product = _resolve_default_product()
cli = _resolve_cli(product)
return subprocess.call([cli, "--list-extensions", "--show-versions"])
def cmd_settings_path(args: argparse.Namespace) -> int:
path = _settings_path(args.scope or "user", args.ProjectPath)
print(json.dumps({
"Scope": args.scope or "user",
"Path": str(path),
"Exists": path.is_file(),
}, indent=2))
return 0
def cmd_settings_get(args: argparse.Namespace) -> int:
path = _settings_path(args.scope or "user", args.ProjectPath)
settings = _read_settings(path)
keys = [
"editor.fontFamily", "editor.fontSize", "editor.lineHeight",
"workbench.colorTheme", "workbench.iconTheme",
"workbench.productIconTheme", "editor.wordWrap", "editor.tabSize",
"editor.formatOnSave", "files.autoSave", "editor.minimap.enabled",
]
print(f"== Settings file ({path}) ==")
print(json.dumps({k: settings.get(k) for k in keys}, indent=2, ensure_ascii=False))
return 0
def cmd_settings_set(args: argparse.Namespace) -> int:
if not args.apply:
print("error: settings set is a write operation. Pass --Apply.", file=sys.stderr)
return 2
scope = args.scope or "user"
path = _settings_path(scope, args.ProjectPath)
settings = _read_settings(path)
flag_dict = vars(args)
for flag, (key, coerce) in SETTINGS_FLAG_MAP.items():
val = flag_dict.get(flag.lstrip("-").replace("-", "_").lower())
if val is not None and val != "":
settings[key] = coerce(val) if coerce else val
if args.SettingKey:
settings[args.SettingKey] = _coerce(args.SettingValue or "")
_write_settings(path, settings)
print(json.dumps({"Path": str(path), "Written": True}, indent=2))
return 0
def cmd_settings_smoke(args: argparse.Namespace) -> int:
scope = "user"
path = _settings_path(scope, None)
existed = path.is_file()
original = path.read_text(encoding="utf-8") if existed else None
try:
settings = _read_settings(path)
settings.update({
"editor.fontFamily": "Consolas, 'Courier New', monospace",
"editor.fontSize": 15,
"editor.lineHeight": 22,
"workbench.colorTheme": "Default Dark Modern",
"editor.wordWrap": "on",
"editor.tabSize": 2,
"editor.formatOnSave": False,
"files.autoSave": "off",
"editor.minimap.enabled": False,
})
_write_settings(path, settings)
verify = _read_settings(path)
ok = (
verify["editor.fontFamily"] == "Consolas, 'Courier New', monospace"
and verify["editor.fontSize"] == 15
and verify["editor.lineHeight"] == 22
and verify["workbench.colorTheme"] == "Default Dark Modern"
and verify["editor.wordWrap"] == "on"
and verify["editor.tabSize"] == 2
and verify["editor.formatOnSave"] is False
and verify["files.autoSave"] == "off"
and verify["editor.minimap.enabled"] is False
)
if not ok:
print("error: settings smoke verification failed.", file=sys.stderr)
return 1
print(json.dumps({"Path": str(path), "TemporaryWriteVerified": True}, indent=2))
finally:
if existed and original is not None:
path.write_text(original, encoding="utf-8")
elif path.is_file():
path.unlink()
print(json.dumps({"Path": str(path), "RestoredOriginal": True}, indent=2))
return 0
def cmd_python_set_interpreter(args: argparse.Namespace) -> int:
project = Path(args.ProjectPath).resolve()
venv = project / args.VenvName
candidates = [
venv / "bin" / "python",
venv / "Scripts" / "python.exe",
]
py = next((c for c in candidates if c.is_file()), None)
if py is None:
print(f"error: python interpreter not found; tried: {[str(c) for c in candidates]}", file=sys.stderr)
return 2
settings_path = project / ".vscode" / "settings.json"
settings = _read_settings(settings_path)
settings["python.defaultInterpreterPath"] = str(py)
_write_settings(settings_path, settings)
print(json.dumps({"Settings": str(settings_path), "Interpreter": str(py)}, indent=2))
return 0
# --------------------------------------------------------------------------- #
# Templates (tasks.json / launch.json)
# --------------------------------------------------------------------------- #
TEMPLATES_DIR = SKILL_ROOT / "references" / "templates"
def _list_template_ids(kind: str) -> list[str]:
d = TEMPLATES_DIR / kind
if not d.is_dir():
return []
return sorted(p.stem for p in d.glob("*.json"))
def _load_template(kind: str, template_id: str) -> dict[str, Any]:
path = TEMPLATES_DIR / kind / f"{template_id}.json"
if not path.is_file():
raise FileNotFoundError(f"Template '{template_id}' not found under {TEMPLATES_DIR / kind}. "
f"Available: {_list_template_ids(kind)}")
return json.loads(path.read_text(encoding="utf-8"))
def cmd_tasks_list(_args: argparse.Namespace) -> int:
print(json.dumps({"templates": _list_template_ids("tasks")}, indent=2))
return 0
def cmd_tasks_init(args: argparse.Namespace) -> int:
project = Path(args.ProjectPath).resolve()
target = project / ".vscode" / "tasks.json"
if target.is_file() and not args.Force:
print(f"error: {target} already exists. Pass --Force to overwrite.", file=sys.stderr)
return 2
data = _load_template("tasks", args.Template)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(json.dumps(data, indent=4, ensure_ascii=False) + "\n", encoding="utf-8")
print(json.dumps({"Path": str(target), "Template": args.Template, "Written": True}, indent=2))
return 0
def cmd_launch_list(_args: argparse.Namespace) -> int:
print(json.dumps({"templates": _list_template_ids("launch")}, indent=2))
return 0
def cmd_launch_init(args: argparse.Namespace) -> int:
project = Path(args.ProjectPath).resolve()
target = project / ".vscode" / "launch.json"
if target.is_file() and not args.Force:
print(f"error: {target} already exists. Pass --Force to overwrite.", file=sys.stderr)
return 2
data = _load_template("launch", args.Template)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(json.dumps(data, indent=4, ensure_ascii=False) + "\n", encoding="utf-8")
print(json.dumps({"Path": str(target), "Template": args.Template, "Written": True}, indent=2))
return 0
# --------------------------------------------------------------------------- #
# Extension packs
# --------------------------------------------------------------------------- #
EXTENSION_PACKS_FILE = SKILL_ROOT / "references" / "extension-packs.md"
def _parse_extension_packs() -> dict[str, list[dict[str, str]]]:
r"""Parse references/extension-packs.md into {stack_name: [{id, purpose}]}.
The parser is intentionally simple: it looks for `### `<stack>` headers
followed by tables whose first column is the extension ID and second is purpose.
"""
if not EXTENSION_PACKS_FILE.is_file():
return {}
text = EXTENSION_PACKS_FILE.read_text(encoding="utf-8")
packs: dict[str, list[dict[str, str]]] = {}
current_stack: str | None = None
for line in text.splitlines():
m = re.match(r"^### `([a-z0-9-]+)`", line)
if m:
current_stack = m.group(1)
packs[current_stack] = []
continue
if current_stack is None:
continue
if not line.startswith("|"):
continue
cells = [c.strip().strip("`") for c in line.strip().strip("|").split("|")]
if len(cells) < 2:
continue
if cells[0] in ("ID", "---", "---"):
continue
if re.match(r"^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_.-]+$", cells[0]):
packs[current_stack].append({"id": cells[0], "purpose": cells[1]})
return packs
def cmd_extensions_recommend(args: argparse.Namespace) -> int:
packs = _parse_extension_packs()
if args.Stack not in packs:
print(f"error: unknown stack '{args.Stack}'. Available: {sorted(packs.keys())}", file=sys.stderr)
return 2
print(json.dumps({"stack": args.Stack, "extensions": packs[args.Stack]}, indent=2, ensure_ascii=False))
return 0
def cmd_extensions_install(args: argparse.Namespace) -> int:
packs = _parse_extension_packs()
if args.Stack not in packs:
print(f"error: unknown stack '{args.Stack}'. Available: {sorted(packs.keys())}", file=sys.stderr)
return 2
cli = _resolve_cli(_resolve_default_product())
failures: list[dict[str, str]] = []
for entry in packs[args.Stack]:
ext_id = entry["id"]
print(f"installing {ext_id}...", file=sys.stderr)
rc = subprocess.call([cli, "--install-extension", ext_id])
if rc != 0:
failures.append({"id": ext_id, "purpose": entry["purpose"], "exitCode": str(rc)})
summary = {
"stack": args.Stack,
"total": len(packs[args.Stack]),
"failures": failures,
}
print(json.dumps(summary, indent=2, ensure_ascii=False))
return 1 if failures else 0
def cmd_extensions_pin(args: argparse.Namespace) -> int:
packs = _parse_extension_packs()
if args.Stack not in packs:
print(f"error: unknown stack '{args.Stack}'. Available: {sorted(packs.keys())}", file=sys.stderr)
return 2
project = Path(args.ProjectPath).resolve()
target = project / ".vscode" / "extensions.json"
existing = _read_settings(target) if target.is_file() else {}
recommendations = existing.get("recommendations") or []
for entry in packs[args.Stack]:
if entry["id"] not in recommendations:
recommendations.append(entry["id"])
existing["recommendations"] = recommendations
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(json.dumps(existing, indent=4, ensure_ascii=False) + "\n", encoding="utf-8")
print(json.dumps({"Path": str(target), "Stack": args.Stack, "Recommendations": recommendations}, indent=2, ensure_ascii=False))
return 0
def cmd_extensions_stacks(_args: argparse.Namespace) -> int:
packs = _parse_extension_packs()
print(json.dumps({"stacks": sorted(packs.keys())}, indent=2))
return 0
# --------------------------------------------------------------------------- #
# Settings Sync (read-only first)
# --------------------------------------------------------------------------- #
def _sync_storage_paths() -> list[Path]:
"""Locate VS Code's settings sync storage locations for inspection only."""
candidates: list[Path] = []
if IS_WINDOWS:
app = os.environ.get("APPDATA")
if app:
candidates.append(Path(app) / "Code" / "User" / "sync")
candidates.append(Path(app) / "Code - Insiders" / "User" / "sync")
elif platform.system() == "Darwin":
candidates.append(Path.home() / "Library" / "Application Support" / "Code" / "User" / "sync")
candidates.append(Path.home() / "Library" / "Application Support" / "Code - Insiders" / "User" / "sync")
else:
xdg = os.environ.get("XDG_CONFIG_HOME") or str(Path.home() / ".config")
candidates.append(Path(xdg) / "Code" / "User" / "sync")
candidates.append(Path(xdg) / "Code - Insiders" / "User" / "sync")
return [p for p in candidates if p.exists()]
def cmd_settings_sync_status(_args: argparse.Namespace) -> int:
"""Report whether settings sync storage exists on this machine.
Settings sync is a per-account feature; this command does not read tokens
or any sync payload, only the on-disk presence of the sync folder.
"""
paths = _sync_storage_paths()
print(json.dumps({
"syncStorageFound": bool(paths),
"locations": [str(p) for p in paths],
"note": "Settings sync is controlled via the VS Code UI (Accounts → Backup and Sync Settings). "
"The skill does not enable or disable sync programmatically to avoid accidental data leaks.",
}, indent=2))
return 0
# --------------------------------------------------------------------------- #
# Multi-root workspace
# --------------------------------------------------------------------------- #
def _find_workspace_file(project: Path) -> Path | None:
candidates = list(project.glob("*.code-workspace"))
return candidates[0] if candidates else None
def cmd_workspace_info(args: argparse.Namespace) -> int:
if not args.ProjectPath:
print("error: workspace info requires --ProjectPath.", file=sys.stderr)
return 2
project = Path(args.ProjectPath).resolve()
workspace_file = _find_workspace_file(project)
if not workspace_file:
print(json.dumps({
"ProjectPath": str(project),
"IsMultiRoot": False,
"note": "No .code-workspace file found. This is a single-root workspace; use 'settings set --Scope project'.",
}, indent=2))
return 0
data = json.loads(workspace_file.read_text(encoding="utf-8"))
folders = data.get("folders") or []
summary = {
"ProjectPath": str(project),
"IsMultiRoot": len(folders) > 1,
"WorkspaceFile": str(workspace_file),
"Folders": [f.get("path") for f in folders],
"FolderSettings": [
{"path": f.get("path"), "settings": f.get("settings", {})}
for f in folders if f.get("settings")
],
}
print(json.dumps(summary, indent=2, ensure_ascii=False))
return 0
def cmd_workspace_folder_settings_get(args: argparse.Namespace) -> int:
if not args.ProjectPath or not args.Folder:
print("error: workspace folder-settings-get requires --ProjectPath and --Folder.", file=sys.stderr)
return 2
project = Path(args.ProjectPath).resolve()
workspace_file = _find_workspace_file(project)
if not workspace_file:
print("error: not a multi-root workspace (no .code-workspace file).", file=sys.stderr)
return 2
data = json.loads(workspace_file.read_text(encoding="utf-8"))
folder_entry = next((f for f in data.get("folders", []) if f.get("path") == args.Folder), None)
if folder_entry is None:
print(f"error: folder '{args.Folder}' not found in workspace file.", file=sys.stderr)
return 2
folder_settings = folder_entry.get("settings") or {}
if args.SettingKey:
print(json.dumps({"key": args.SettingKey, "value": folder_settings.get(args.SettingKey)}, indent=2, ensure_ascii=False))
else:
print(json.dumps({"folder": args.Folder, "settings": folder_settings}, indent=2, ensure_ascii=False))
return 0
def cmd_workspace_folder_settings_set(args: argparse.Namespace) -> int:
if not args.ProjectPath or not args.Folder or not args.SettingKey:
print("error: workspace folder-settings-set requires --ProjectPath, --Folder, --SettingKey.", file=sys.stderr)
return 2
if not args.Apply:
print("error: workspace folder-settings-set is a write operation. Pass --Apply.", file=sys.stderr)
return 2
project = Path(args.ProjectPath).resolve()
workspace_file = _find_workspace_file(project)
if not workspace_file:
print("error: not a multi-root workspace (no .code-workspace file).", file=sys.stderr)
return 2
data = json.loads(workspace_file.read_text(encoding="utf-8"))
folders = data.get("folders") or []
target = next((f for f in folders if f.get("path") == args.Folder), None)
if target is None:
print(f"error: folder '{args.Folder}' not found in workspace file.", file=sys.stderr)
return 2
target.setdefault("settings", {})[args.SettingKey] = _coerce(args.SettingValue or "")
data["folders"] = folders
workspace_file.write_text(json.dumps(data, indent=4, ensure_ascii=False) + "\n", encoding="utf-8")
print(json.dumps({"WorkspaceFile": str(workspace_file), "Folder": args.Folder, "Key": args.SettingKey, "Value": target["settings"][args.SettingKey]}, indent=2, ensure_ascii=False))
return 0
# --------------------------------------------------------------------------- #
# Profiles (read-only)
# --------------------------------------------------------------------------- #
def _profiles_dir() -> Path | None:
"""Return the profiles directory of the resolved product, or None if absent."""
user_data = _user_data_dir(_resolve_default_product())
return user_data / "profiles"
def cmd_profile_list(_args: argparse.Namespace) -> int:
profiles_dir = _profiles_dir()
if profiles_dir is None or not profiles_dir.is_dir():
print(json.dumps({"profiles": [], "note": "No profiles directory found; no profiles defined."}, indent=2))
return 0
entries: list[dict[str, Any]] = []
for child in sorted(profiles_dir.iterdir()):
if not child.is_dir():
continue
settings_path = child / "settings.json"
entry: dict[str, Any] = {
"name": child.name,
"settingsFileExists": settings_path.is_file(),
}
if settings_path.is_file():
try:
data = json.loads(settings_path.read_text(encoding="utf-8"))
entry["settingCount"] = len(data)
entry["sampleKeys"] = sorted(list(data.keys()))[:5]
except json.JSONDecodeError:
entry["parseError"] = "settings.json is not valid JSON"
entries.append(entry)
print(json.dumps({"profilesDir": str(profiles_dir), "profiles": entries}, indent=2, ensure_ascii=False))
return 0
def cmd_profile_show(args: argparse.Namespace) -> int:
if not args.Profile:
print("error: profile show requires --Profile.", file=sys.stderr)
return 2
profiles_dir = _profiles_dir()
if profiles_dir is None:
print("error: profiles directory does not exist for this product.", file=sys.stderr)
return 2
profile_dir = profiles_dir / args.Profile
settings_path = profile_dir / "settings.json"
if not settings_path.is_file():
print(f"error: profile '{args.Profile}' not found or has no settings.json.", file=sys.stderr)
return 2
print(json.dumps({
"profile": args.Profile,
"settingsFile": str(settings_path),
"settings": json.loads(settings_path.read_text(encoding="utf-8")),
}, indent=2, ensure_ascii=False))
return 0
# --------------------------------------------------------------------------- #
# Argument parsing
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
# Argument parsing
# --------------------------------------------------------------------------- #
def build_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(prog="vscode_skill", description="VS Code skill cross-platform gateway.")
sub = p.add_subparsers(dest="area", required=True)
sub.add_parser("info", help="Show product, CLI path, key locations.")
ext = sub.add_parser("ext", help="Manage extensions.")
ext.add_argument("action", choices=["list"], help="Currently only 'list' is supported on macOS/Linux.")
settings = sub.add_parser("settings", help="Read or write settings.")
settings.add_argument("action", choices=["path", "get", "set", "smoke"])
settings.add_argument("--Scope", choices=["user", "project"], default="user")
settings.add_argument("--ProjectPath")
settings.add_argument("--Apply", action="store_true")
settings.add_argument("--FontSize", type=str)
settings.add_argument("--LineHeight", type=str)
settings.add_argument("--FontFamily", type=str)
settings.add_argument("--ColorTheme", type=str)
settings.add_argument("--IconTheme", type=str)
settings.add_argument("--ProductIconTheme", type=str)
settings.add_argument("--WordWrap", type=str)
settings.add_argument("--TabSize", type=str)
settings.add_argument("--FormatOnSave", type=str)
settings.add_argument("--AutoSave", type=str)
settings.add_argument("--Minimap", type=str)
settings.add_argument("--SettingKey", type=str)
settings.add_argument("--SettingValue", type=str)
py = sub.add_parser("python", help="Python interpreter helpers.")
py.add_argument("action", choices=["set-interpreter"])
py.add_argument("--ProjectPath", required=True)
py.add_argument("--VenvName", required=True)
tasks = sub.add_parser("tasks", help="Manage .vscode/tasks.json from templates.")
tasks.add_argument("action", choices=["list", "init"])
tasks.add_argument("--Template")
tasks.add_argument("--ProjectPath")
tasks.add_argument("--Force", action="store_true")
launch = sub.add_parser("launch", help="Manage .vscode/launch.json from templates.")
launch.add_argument("action", choices=["list", "init"])
launch.add_argument("--Template")
launch.add_argument("--ProjectPath")
launch.add_argument("--Force", action="store_true")
ext2 = sub.add_parser("extensions", help="Manage recommended extension stacks.")
ext2.add_argument("action", choices=["stacks", "recommend", "install", "pin"])
ext2.add_argument("--Stack")
ext2.add_argument("--ProjectPath")
sync = sub.add_parser("settings-sync", help="Inspect VS Code settings sync state.")
sync.add_argument("action", choices=["status"])
workspace = sub.add_parser("workspace", help="Inspect and edit multi-root workspace settings.")
workspace.add_argument("action", choices=["info", "folder-settings-set", "folder-settings-get"])
workspace.add_argument("--ProjectPath")
workspace.add_argument("--Folder", help="Multi-root folder name to scope to")
workspace.add_argument("--SettingKey")
workspace.add_argument("--SettingValue")
workspace.add_argument("--Apply", action="store_true")
profile = sub.add_parser("profile", help="Inspect VS Code profiles (read-only).")
profile.add_argument("action", choices=["list", "show"])
profile.add_argument("--Profile")
return p
# --------------------------------------------------------------------------- #
# Native dispatch table
# --------------------------------------------------------------------------- #
NATIVE_DISPATCH = {
("info",): cmd_info,
("ext", "list"): cmd_ext_list,
("settings", "path"): cmd_settings_path,
("settings", "get"): cmd_settings_get,
("settings", "set"): cmd_settings_set,
("settings", "smoke"): cmd_settings_smoke,
("python", "set-interpreter"): cmd_python_set_interpreter,
("tasks", "list"): cmd_tasks_list,
("tasks", "init"): cmd_tasks_init,
("launch", "list"): cmd_launch_list,
("launch", "init"): cmd_launch_init,
("extensions", "stacks"): cmd_extensions_stacks,
("extensions", "recommend"): cmd_extensions_recommend,
("extensions", "install"): cmd_extensions_install,
("extensions", "pin"): cmd_extensions_pin,
("settings-sync", "status"): cmd_settings_sync_status,
("workspace", "info"): cmd_workspace_info,
("workspace", "folder-settings-get"): cmd_workspace_folder_settings_get,
("workspace", "folder-settings-set"): cmd_workspace_folder_settings_set,
("profile", "list"): cmd_profile_list,
("profile", "show"): cmd_profile_show,
}
def main(argv: list[str]) -> int:
# On Windows: always forward to the PowerShell driver (zero behavior drift).
if IS_WINDOWS:
return _forward_powershell(argv)
# On macOS / Linux: parse and dispatch natively.
parser = build_parser()
args = parser.parse_args(argv)
handler = NATIVE_DISPATCH.get((args.area, getattr(args, "action", None)))
if handler is None:
print(f"error: native implementation for '{args.area} {getattr(args, 'action', '')}' is not yet available on this platform.", file=sys.stderr)
print("hint : run this command on Windows, or open an issue at https://github.com/Duanzhoutao/vscode-skill/issues", file=sys.stderr)
return 2
return handler(args)
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))

View File

@ -0,0 +1,42 @@
[
{
"id": "project-settings",
"prompt": "给当前项目配置 VS Code 保存时格式化和 Python 解释器。",
"expected": "写项目 .vscode/settings.json;使用结构化 JSON;避免用户级主题/字体;可运行 settings smoke(项目 scope)验证回滚。"
},
{
"id": "user-font-theme",
"prompt": "把 VS Code 用户字体改成 JetBrains Mono 16 号,主题换成 Dark+。",
"expected": "先 settings get 展示当前值;写前声明目标文件、key、old/new value、回滚路径;用 settings set --FontSize 16 --FontFamily '...' --ColorTheme 'Dark+' --Apply;再次 settings get 验证落盘。"
},
{
"id": "ssh-remote",
"prompt": "在远程 devbox 上打开 ~/projects/web 项目。",
"expected": "先 ssh smoke --HostName devbox 验证底层连接;再 ssh open --HostName devbox --LinuxPath ~/projects/web 调用 CLI 打开;不在 smoke 中假设 Remote-SSH 扩展已安装。"
},
{
"id": "extension-install-fallback",
"prompt": "帮我装 ms-python.python 扩展,官方 Marketplace 不通就用 Open VSX。",
"expected": "先 ext list 确认未安装;执行 ext install --Id ms-python.python;失败时明确告知用户,并提示重跑时加 --ForceOpenVsx 才会走 Open VSX 回退;不在主流程里静默切换 source。"
},
{
"id": "config-init",
"prompt": "帮我生成一份 skill 配置文件。",
"expected": "运行 scripts/init-config.ps1;输出实际写入的路径;提示用户编辑后重启 agent 才会生效。"
},
{
"id": "trae-compatible-config",
"prompt": "帮我检查 Trae CN 的 VS Code 配置路径和项目 .vscode/settings.json,不要动聊天记录和模型设置。",
"expected": "使用 --Product trae-cn 或 products.trae-cn 配置;只处理 VS Code-compatible settings/.vscode/extension 路径;不读取或修改 chat、model、account、agent、MCP、SQLite、token 或内部状态。"
},
{
"id": "qoder-config-boundary",
"prompt": "把 Qoder 项目的保存时格式化和 tasks.json 配好。",
"expected": "明确 Qoder 仅按 VS Code-compatible 配置面处理;要求/使用 products.qoder.cliPath、userDataPath、extensionsPath;写项目 .vscode/settings.json/tasks.json;不声称支持 Qoder 私有 AI/账号/模型能力。"
},
{
"id": "codebuddy-config-boundary",
"prompt": "帮我在 CodeBuddy CN 里推荐 Python 扩展并设置项目解释器。",
"expected": "使用 products.codebuddy-cn 配置或要求用户提供 CLI 路径;只处理 extensions.json、扩展安装/定位、python.defaultInterpreterPath 等 VS Code-compatible 内容;不碰 CodeBuddy marketplace、skills、models、agent 或账号状态。"
}
]