From 569033aa4cc84b612ef4cbd361aedf68ea913f33 Mon Sep 17 00:00:00 2001 From: aniongithub Date: Sat, 9 May 2026 09:47:45 -0700 Subject: [PATCH 1/3] feat: tag-based skill composition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace hardcoded fragment list and WSL special-casing in build.rs with a generic tag-based composition system. Each skill fragment and tool list now has YAML frontmatter: --- tags: [core] # required tags (AND logic) order: 50 # sort order in assembled output --- Build.rs resolves active tags from CARGO_CFG_TARGET_OS: linux → {core, linux} macos → {core, macos, docker-desktop} windows → {core, windows, docker-desktop, wsl} A fragment is included when all its required tags are present in the active set (subset check). Fragments are auto-discovered from skills/ directory — no code changes needed to add new fragments. This enables platform-specific skill variations (e.g. Docker Desktop caveats on macOS/Windows) and future feature-gated backends, all without touching build.rs. --- crates/devcontainer-mcp/build.rs | 202 +++++++++++++++++++++---------- skills/_tools/auth.txt | 3 + skills/_tools/codespaces.txt | 3 + skills/_tools/devcontainer.txt | 3 + skills/_tools/devpod.txt | 3 + skills/_tools/wsl.txt | 3 + skills/auth.md | 4 + skills/choosing-backend.md | 4 + skills/codespaces.md | 4 + skills/core-rule.md | 4 + skills/devcontainer.md | 4 + skills/devpod.md | 4 + skills/file-ops.md | 4 + skills/footer.md | 4 + skills/header.md | 4 + skills/self-healing.md | 4 + skills/wsl.md | 4 + 17 files changed, 196 insertions(+), 65 deletions(-) diff --git a/crates/devcontainer-mcp/build.rs b/crates/devcontainer-mcp/build.rs index 9313903..90f23ac 100644 --- a/crates/devcontainer-mcp/build.rs +++ b/crates/devcontainer-mcp/build.rs @@ -1,24 +1,139 @@ +use std::collections::HashSet; use std::fs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; const FRONTMATTER_NAME: &str = "devcontainer-mcp"; const FRONTMATTER_DESC: &str = "Manage dev container environments via MCP tools (DevPod, devcontainer CLI, Codespaces)"; -/// Fragment order for the assembled SKILL.md body. -const FRAGMENTS: &[&str] = &[ - "header.md", - "core-rule.md", - "auth.md", - "choosing-backend.md", - "devpod.md", - "devcontainer.md", - "codespaces.md", - // WSL fragment inserted here on Windows builds - "self-healing.md", - "footer.md", - "file-ops.md", -]; +/// Resolve the set of active tags for the current build target. +fn active_tags() -> HashSet { + let mut tags = HashSet::new(); + tags.insert("core".to_string()); + + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); + match target_os.as_str() { + "windows" => { + tags.insert("windows".to_string()); + tags.insert("docker-desktop".to_string()); + tags.insert("wsl".to_string()); + } + "macos" => { + tags.insert("macos".to_string()); + tags.insert("docker-desktop".to_string()); + } + "linux" => { + tags.insert("linux".to_string()); + } + _ => {} + } + + tags +} + +/// Parse YAML frontmatter from a file's content. +/// Returns (tags, order, body) where body is everything after the closing `---`. +/// If no frontmatter is found, returns empty tags, order 0, and the full content. +fn parse_frontmatter(content: &str) -> (Vec, i64, &str) { + let trimmed = content.trim_start(); + if !trimmed.starts_with("---") { + return (vec![], 0, content); + } + + // Find the closing --- + let after_open = &trimmed[3..]; + let close_pos = match after_open.find("\n---") { + Some(pos) => pos, + None => return (vec![], 0, content), + }; + + let frontmatter = &after_open[..close_pos]; + let body_start = 3 + close_pos + 4; // "---" + frontmatter + "\n---" + let body = trimmed[body_start..].trim_start_matches('\n'); + + let mut tags = vec![]; + let mut order: i64 = 0; + + for line in frontmatter.lines() { + let line = line.trim(); + if let Some(rest) = line.strip_prefix("tags:") { + // Parse [tag1, tag2] or tag1, tag2 + let rest = rest.trim().trim_start_matches('[').trim_end_matches(']'); + for tag in rest.split(',') { + let tag = tag.trim(); + if !tag.is_empty() { + tags.push(tag.to_string()); + } + } + } else if let Some(rest) = line.strip_prefix("order:") { + order = rest.trim().parse().unwrap_or(0); + } + } + + (tags, order, body) +} + +/// Check if a fragment's required tags are all present in the active set. +/// Empty tags means always included. +fn tags_match(required: &[String], active: &HashSet) -> bool { + required.iter().all(|tag| active.contains(tag)) +} + +/// Discover and sort all .md files in a directory, filtering by active tags. +fn collect_fragments(dir: &Path, active: &HashSet) -> Vec<(i64, String)> { + let mut entries: Vec = fs::read_dir(dir) + .unwrap_or_else(|e| panic!("cannot read {}: {e}", dir.display())) + .filter_map(Result::ok) + .map(|e| e.path()) + .filter(|p| p.extension().is_some_and(|ext| ext == "md")) + .collect(); + entries.sort(); + + let mut fragments: Vec<(i64, String)> = Vec::new(); + + for path in entries { + let content = fs::read_to_string(&path) + .unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display())); + let (tags, order, body) = parse_frontmatter(&content); + + if tags_match(&tags, active) { + fragments.push((order, body.to_string())); + } + } + + fragments.sort_by_key(|(order, _)| *order); + fragments +} + +/// Discover all .txt tool lists, filtering by active tags. +fn collect_tools(dir: &Path, active: &HashSet) -> Vec { + let mut entries: Vec = fs::read_dir(dir) + .unwrap_or_else(|e| panic!("cannot read {}: {e}", dir.display())) + .filter_map(Result::ok) + .map(|e| e.path()) + .filter(|p| p.extension().is_some_and(|ext| ext == "txt")) + .collect(); + entries.sort(); + + let mut tools = Vec::new(); + + for path in entries { + let content = fs::read_to_string(&path) + .unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display())); + let (tags, _, body) = parse_frontmatter(&content); + + if tags_match(&tags, active) { + for line in body.lines() { + let trimmed = line.trim(); + if !trimmed.is_empty() { + tools.push(trimmed.to_string()); + } + } + } + } + + tools +} fn main() { let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); @@ -31,38 +146,10 @@ fn main() { let tools_dir = skills_dir.join("_tools"); let output_path = workspace_root.join("SKILL.md"); - // Use CARGO_CFG_TARGET_OS (the *target* platform, not the host) so that - // cross-compiling for Windows from Linux still includes WSL content. - let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); - let is_windows_target = target_os == "windows"; + let active = active_tags(); - // --- Collect tool names from _tools/*.txt ----------------------------------- - let mut tools: Vec = Vec::new(); - let mut tool_files: Vec = fs::read_dir(&tools_dir) - .unwrap_or_else(|e| panic!("cannot read {}: {e}", tools_dir.display())) - .filter_map(Result::ok) - .map(|e| e.path()) - .filter(|p| p.extension().is_some_and(|ext| ext == "txt")) - .collect(); - tool_files.sort(); - - // On non-Windows targets, skip wsl.txt - for path in &tool_files { - let is_wsl = path - .file_stem() - .is_some_and(|s| s.to_str().is_some_and(|s| s == "wsl")); - if is_wsl && !is_windows_target { - continue; - } - let content = fs::read_to_string(path) - .unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display())); - for line in content.lines() { - let trimmed = line.trim(); - if !trimmed.is_empty() { - tools.push(trimmed.to_string()); - } - } - } + // --- Collect tool names ----------------------------------------------------- + let tools = collect_tools(&tools_dir, &active); // --- Build YAML frontmatter ------------------------------------------------- let mut output = String::from("---\n"); @@ -74,26 +161,11 @@ fn main() { } output.push_str("---\n"); - // --- Assemble markdown body ------------------------------------------------- - let insert_wsl_after = "codespaces.md"; - - for &fragment_name in FRAGMENTS { - let path = skills_dir.join(fragment_name); - let content = fs::read_to_string(&path) - .unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display())); + // --- Assemble markdown body (ordered by frontmatter `order` field) ----------- + let fragments = collect_fragments(&skills_dir, &active); + for (_, body) in &fragments { output.push('\n'); - output.push_str(&content); - - // On Windows targets, insert WSL section right after codespaces - if is_windows_target && fragment_name == insert_wsl_after { - let wsl_path = skills_dir.join("wsl.md"); - if wsl_path.exists() { - let wsl_content = fs::read_to_string(&wsl_path) - .unwrap_or_else(|e| panic!("cannot read {}: {e}", wsl_path.display())); - output.push('\n'); - output.push_str(&wsl_content); - } - } + output.push_str(body); } // --- Write output ----------------------------------------------------------- diff --git a/skills/_tools/auth.txt b/skills/_tools/auth.txt index e3a6656..a2415d0 100644 --- a/skills/_tools/auth.txt +++ b/skills/_tools/auth.txt @@ -1,3 +1,6 @@ +--- +tags: [core] +--- auth_status auth_login auth_select diff --git a/skills/_tools/codespaces.txt b/skills/_tools/codespaces.txt index 79b1033..b2ed329 100644 --- a/skills/_tools/codespaces.txt +++ b/skills/_tools/codespaces.txt @@ -1,3 +1,6 @@ +--- +tags: [core] +--- codespaces_create codespaces_list codespaces_ssh diff --git a/skills/_tools/devcontainer.txt b/skills/_tools/devcontainer.txt index dfebd4c..2eb1aaf 100644 --- a/skills/_tools/devcontainer.txt +++ b/skills/_tools/devcontainer.txt @@ -1,3 +1,6 @@ +--- +tags: [core] +--- devcontainer_up devcontainer_exec devcontainer_build diff --git a/skills/_tools/devpod.txt b/skills/_tools/devpod.txt index 224de7a..ed9003f 100644 --- a/skills/_tools/devpod.txt +++ b/skills/_tools/devpod.txt @@ -1,3 +1,6 @@ +--- +tags: [core] +--- devpod_up devpod_stop devpod_delete diff --git a/skills/_tools/wsl.txt b/skills/_tools/wsl.txt index b86751b..279ee46 100644 --- a/skills/_tools/wsl.txt +++ b/skills/_tools/wsl.txt @@ -1,3 +1,6 @@ +--- +tags: [wsl] +--- wsl_list wsl_exec wsl_terminate diff --git a/skills/auth.md b/skills/auth.md index dbeef14..8a7990b 100644 --- a/skills/auth.md +++ b/skills/auth.md @@ -1,3 +1,7 @@ +--- +tags: [core] +order: 30 +--- ## Authentication **Before using Codespaces tools, you MUST obtain an auth handle.** diff --git a/skills/choosing-backend.md b/skills/choosing-backend.md index 8d3e4f9..b11f62a 100644 --- a/skills/choosing-backend.md +++ b/skills/choosing-backend.md @@ -1,3 +1,7 @@ +--- +tags: [core] +order: 40 +--- ## Choosing a Backend 1. **Local Docker + devcontainer CLI** — simplest for local development, no auth needed diff --git a/skills/codespaces.md b/skills/codespaces.md index e904fd4..4efa494 100644 --- a/skills/codespaces.md +++ b/skills/codespaces.md @@ -1,3 +1,7 @@ +--- +tags: [core] +order: 70 +--- ## Workflow: Codespaces ### 1. Authenticate diff --git a/skills/core-rule.md b/skills/core-rule.md index 6e7d6d0..7810ca6 100644 --- a/skills/core-rule.md +++ b/skills/core-rule.md @@ -1,3 +1,7 @@ +--- +tags: [core] +order: 20 +--- ## Core Rule **If a project has `.devcontainer/devcontainer.json`, ALL work MUST happen inside a dev container — never install dependencies, run builds, or execute code directly on the host.** diff --git a/skills/devcontainer.md b/skills/devcontainer.md index ff99de3..c7521dd 100644 --- a/skills/devcontainer.md +++ b/skills/devcontainer.md @@ -1,3 +1,7 @@ +--- +tags: [core] +order: 60 +--- ## Workflow: devcontainer CLI ### 1. Start the dev container diff --git a/skills/devpod.md b/skills/devpod.md index 318df3f..43d3a86 100644 --- a/skills/devpod.md +++ b/skills/devpod.md @@ -1,3 +1,7 @@ +--- +tags: [core] +order: 50 +--- ## Workflow: DevPod ### 1. Create or start the workspace diff --git a/skills/file-ops.md b/skills/file-ops.md index cb10ab3..6cf1526 100644 --- a/skills/file-ops.md +++ b/skills/file-ops.md @@ -1,3 +1,7 @@ +--- +tags: [core] +order: 100 +--- ## File Operations **All backends support built-in file operations — no need to construct shell commands.** diff --git a/skills/footer.md b/skills/footer.md index a307b85..5503370 100644 --- a/skills/footer.md +++ b/skills/footer.md @@ -1,3 +1,7 @@ +--- +tags: [core] +order: 90 +--- ## What NOT to do - ❌ Do NOT install packages on the host diff --git a/skills/header.md b/skills/header.md index de02373..716a08f 100644 --- a/skills/header.md +++ b/skills/header.md @@ -1,3 +1,7 @@ +--- +tags: [core] +order: 10 +--- # DevContainer MCP Skill You have access to `devcontainer-mcp`, an MCP server that manages dev container environments across three backends: diff --git a/skills/self-healing.md b/skills/self-healing.md index 746241e..ac5575d 100644 --- a/skills/self-healing.md +++ b/skills/self-healing.md @@ -1,3 +1,7 @@ +--- +tags: [core] +order: 80 +--- ## Self-Healing If `devpod_up`, `devcontainer_up`, or `codespaces_create` returns errors: diff --git a/skills/wsl.md b/skills/wsl.md index 50f61ea..45cb266 100644 --- a/skills/wsl.md +++ b/skills/wsl.md @@ -1,3 +1,7 @@ +--- +tags: [wsl] +order: 75 +--- ## Workflow: WSL (Windows only) WSL tools are available when running on Windows. They let you manage and interact with WSL distributions directly. From c1206929b64861b0ddaa2a4a36c4d7b37ec26932 Mon Sep 17 00:00:00 2001 From: aniongithub Date: Sat, 9 May 2026 09:51:40 -0700 Subject: [PATCH 2/3] fix: replace hand-rolled shell_escape with shell-escape crate The minimal single-quote escaper only handled single quotes. The shell-escape crate properly handles spaces, dollar signs, backticks, backslashes, and all other shell metacharacters. --- Cargo.lock | 7 +++ crates/devcontainer-mcp-core/Cargo.toml | 1 + crates/devcontainer-mcp-core/src/file_ops.rs | 49 ++++++++++++++------ 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a60602a..2987616 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -319,6 +319,7 @@ dependencies = [ "futures-util", "serde", "serde_json", + "shell-escape", "thiserror", "tokio", "tracing", @@ -1188,6 +1189,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shell-escape" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" + [[package]] name = "shlex" version = "1.3.0" diff --git a/crates/devcontainer-mcp-core/Cargo.toml b/crates/devcontainer-mcp-core/Cargo.toml index 6ab462c..b737647 100644 --- a/crates/devcontainer-mcp-core/Cargo.toml +++ b/crates/devcontainer-mcp-core/Cargo.toml @@ -16,3 +16,4 @@ tracing = { workspace = true } futures-util = "0.3" async-trait = "0.1" base64 = "0.22" +shell-escape = "0.1" diff --git a/crates/devcontainer-mcp-core/src/file_ops.rs b/crates/devcontainer-mcp-core/src/file_ops.rs index 2799376..0efaaab 100644 --- a/crates/devcontainer-mcp-core/src/file_ops.rs +++ b/crates/devcontainer-mcp-core/src/file_ops.rs @@ -4,7 +4,10 @@ //! formatting, and helpers to build shell commands for reading/writing files //! through any backend (DevPod SSH, devcontainer exec, Codespaces SSH). +use std::borrow::Cow; + use base64::{engine::general_purpose::STANDARD, Engine}; +use shell_escape::escape; use crate::error::{Error, Result}; @@ -52,34 +55,32 @@ pub fn apply_edit(content: &str, old_str: &str, new_str: &str) -> Result Ok(content.replacen(old_str, new_str, 1)) } +/// Shell-escape a string for safe embedding in a shell command. +fn quote(s: &str) -> String { + escape(Cow::Borrowed(s)).into_owned() +} + /// Build a shell command that reads a file via `cat`. pub fn read_file_command(path: &str) -> String { - format!("cat '{}'", shell_escape(path)) + format!("cat {}", quote(path)) } /// Build a shell command that writes base64-encoded content to a file, /// creating parent directories as needed. pub fn write_file_command(path: &str, content: &str) -> String { - let escaped = shell_escape(path); + let path = quote(path); let encoded = STANDARD.encode(content.as_bytes()); - format!( - "mkdir -p \"$(dirname '{escaped}')\" && printf '%s' '{encoded}' | base64 -d > '{escaped}'" - ) + format!("mkdir -p \"$(dirname {path})\" && printf '%s' '{encoded}' | base64 -d > {path}") } /// Build a shell command that lists a directory (non-hidden, up to 2 levels). pub fn list_dir_command(path: &str) -> String { format!( - "find '{}' -maxdepth 2 -not -path '*/.*' | sort", - shell_escape(path) + "find {} -maxdepth 2 -not -path '*/.*' | sort", + quote(path) ) } -/// Minimal single-quote escaping for shell arguments. -fn shell_escape(s: &str) -> String { - s.replace('\'', "'\\''") -} - #[cfg(test)] mod tests { use super::*; @@ -122,7 +123,27 @@ mod tests { } #[test] - fn test_shell_escape() { - assert_eq!(shell_escape("it's"), "it'\\''s"); + fn test_quote_simple_path() { + assert_eq!(quote("simple"), "simple"); + } + + #[test] + fn test_quote_path_with_spaces() { + let result = quote("path with spaces"); + assert!(result.contains('\'') || result.contains('\\')); + } + + #[test] + fn test_quote_path_with_single_quote() { + let result = quote("it's"); + // Should not break when used in a shell command + assert!(!result.contains("it's") || result.contains("\\'")); + } + + #[test] + fn test_quote_path_with_dollar() { + let result = quote("$HOME/file"); + // Should be escaped so $HOME is not expanded + assert_ne!(result, "$HOME/file"); } } From 416e32288eaef00095fa59f51b367ed7335c4aea Mon Sep 17 00:00:00 2001 From: aniongithub Date: Sat, 9 May 2026 09:54:41 -0700 Subject: [PATCH 3/3] fix: strengthen skill constraints against direct CLI usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add explicit 'use only MCP tools' constraint to core rules (early in prompt) and per-workflow reminders before each example section. Agents were bypassing MCP tools and invoking docker/devcontainer/gh CLIs directly. Constraints now appear at three reinforcement points: - Core rules (order 20) — primary constraint, seen first - Each workflow section — reminder before examples - Footer checklist — summary reinforcement at end --- SKILL.md | 11 ++++++++++- skills/codespaces.md | 2 ++ skills/core-rule.md | 4 +++- skills/devcontainer.md | 2 ++ skills/devpod.md | 2 ++ skills/footer.md | 1 + skills/wsl.md | 2 ++ 7 files changed, 22 insertions(+), 2 deletions(-) diff --git a/SKILL.md b/SKILL.md index 9745d46..a394ef6 100644 --- a/SKILL.md +++ b/SKILL.md @@ -56,10 +56,12 @@ You have access to `devcontainer-mcp`, an MCP server that manages dev container - **devcontainer CLI** (`devcontainer_*` tools) — local Docker via the official CLI - **GitHub Codespaces** (`codespaces_*` tools) — cloud-hosted environments -## Core Rule +## Core Rules **If a project has `.devcontainer/devcontainer.json`, ALL work MUST happen inside a dev container — never install dependencies, run builds, or execute code directly on the host.** +**Use ONLY the MCP tools listed here.** Do not invoke `docker`, `devcontainer`, `devpod`, `gh`, or `wsl` CLI commands directly — the MCP tools wrap these CLIs with proper error handling, auth resolution, and escaping. Direct CLI usage bypasses these safeguards. + ## Authentication **Before using Codespaces tools, you MUST obtain an auth handle.** @@ -96,6 +98,8 @@ Supported auth providers: `github`, `aws`, `azure`, `gcloud`, `kubernetes` ## Workflow: DevPod +> **Use these tools — not raw `devpod` CLI commands.** + ### 1. Create or start the workspace ``` devpod_up(args: "/path/to/project --id my-project --provider docker") @@ -118,6 +122,8 @@ devpod_stop(workspace: "my-project") ## Workflow: devcontainer CLI +> **Use these tools — not raw `devcontainer` or `docker` CLI commands.** + ### 1. Start the dev container ``` devcontainer_up(workspace_folder: "/path/to/project") @@ -135,6 +141,8 @@ devcontainer_stop(workspace_folder: "/path/to/project") ## Workflow: Codespaces +> **Use these tools — not raw `gh codespace` CLI commands.** + ### 1. Authenticate ``` auth_status(provider: "github") @@ -171,6 +179,7 @@ If `devpod_up`, `devcontainer_up`, or `codespaces_create` returns errors: - ❌ Do NOT install packages on the host - ❌ Do NOT run builds on the host - ❌ Do NOT modify the host's global config +- ❌ Do NOT run `docker`, `devcontainer`, `devpod`, `gh`, or `wsl` CLI commands directly — use the MCP tools - ✅ DO authenticate before using codespaces tools - ✅ DO ask the user which account/machine type to use - ✅ DO use `devpod_ssh`, `devcontainer_exec`, or `codespaces_ssh` for everything diff --git a/skills/codespaces.md b/skills/codespaces.md index 4efa494..607adce 100644 --- a/skills/codespaces.md +++ b/skills/codespaces.md @@ -4,6 +4,8 @@ order: 70 --- ## Workflow: Codespaces +> **Use these tools — not raw `gh codespace` CLI commands.** + ### 1. Authenticate ``` auth_status(provider: "github") diff --git a/skills/core-rule.md b/skills/core-rule.md index 7810ca6..429a177 100644 --- a/skills/core-rule.md +++ b/skills/core-rule.md @@ -2,6 +2,8 @@ tags: [core] order: 20 --- -## Core Rule +## Core Rules **If a project has `.devcontainer/devcontainer.json`, ALL work MUST happen inside a dev container — never install dependencies, run builds, or execute code directly on the host.** + +**Use ONLY the MCP tools listed here.** Do not invoke `docker`, `devcontainer`, `devpod`, `gh`, or `wsl` CLI commands directly — the MCP tools wrap these CLIs with proper error handling, auth resolution, and escaping. Direct CLI usage bypasses these safeguards. diff --git a/skills/devcontainer.md b/skills/devcontainer.md index c7521dd..877c2ed 100644 --- a/skills/devcontainer.md +++ b/skills/devcontainer.md @@ -4,6 +4,8 @@ order: 60 --- ## Workflow: devcontainer CLI +> **Use these tools — not raw `devcontainer` or `docker` CLI commands.** + ### 1. Start the dev container ``` devcontainer_up(workspace_folder: "/path/to/project") diff --git a/skills/devpod.md b/skills/devpod.md index 43d3a86..b495aaa 100644 --- a/skills/devpod.md +++ b/skills/devpod.md @@ -4,6 +4,8 @@ order: 50 --- ## Workflow: DevPod +> **Use these tools — not raw `devpod` CLI commands.** + ### 1. Create or start the workspace ``` devpod_up(args: "/path/to/project --id my-project --provider docker") diff --git a/skills/footer.md b/skills/footer.md index 5503370..a14a9b5 100644 --- a/skills/footer.md +++ b/skills/footer.md @@ -7,6 +7,7 @@ order: 90 - ❌ Do NOT install packages on the host - ❌ Do NOT run builds on the host - ❌ Do NOT modify the host's global config +- ❌ Do NOT run `docker`, `devcontainer`, `devpod`, `gh`, or `wsl` CLI commands directly — use the MCP tools - ✅ DO authenticate before using codespaces tools - ✅ DO ask the user which account/machine type to use - ✅ DO use `devpod_ssh`, `devcontainer_exec`, or `codespaces_ssh` for everything diff --git a/skills/wsl.md b/skills/wsl.md index 45cb266..c86558b 100644 --- a/skills/wsl.md +++ b/skills/wsl.md @@ -4,6 +4,8 @@ order: 75 --- ## Workflow: WSL (Windows only) +> **Use these tools — not raw `wsl.exe` commands.** + WSL tools are available when running on Windows. They let you manage and interact with WSL distributions directly. ### 1. List available distributions