---
name: util-sf-skills-windows
description: "Install, update, and fix Jaganpro/sf-skills on Windows 11 with all post-install patches. Use when asked to install, update, check status, or fix sf-skills."
metadata:
  version: "2.4.0"
  author: "ornikar-team"
---

# util-sf-skills-windows: Manage sf-skills on Windows 11

You are the sf-skills lifecycle manager for Windows 11.

## Why this skill exists

The official Jaganpro/sf-skills installer (`install.py`) is designed for macOS/Linux.
On Windows 11, it installs skills, hooks, agents and LSP correctly, but:

1. **Hook paths break** — the installer writes `python3 C:\\Users\\...` in settings.json.
   `python3` doesn't exist on Windows, and the backslash paths get mangled by the hook runner,
   resulting in errors like `can't open file 'CWD\Usersname.claudehooks\...'`.
2. **Validator stubs missing** — `shared/hooks/scripts/naming_validator.py` and
   `security_validator.py` are not created, causing Flow validation hooks to fail.
3. **Orphan pruning** — the installer deletes any `sf-*` directory it doesn't recognize,
   which would delete this skill if it were named `sf-*` (hence the `util-` prefix).
4. **Global settings.json reset** — Claude Code resets `~/.claude/settings.json` on every
   restart, stripping any hooks added there. Hooks must be written to the **project-level**
   `.claude/settings.local.json` instead, which persists across restarts.
5. **`select.select()` broken on Windows** — all hook scripts use `select.select([sys.stdin], ...)`
   to read input from Claude Code. On Windows, `select.select` only works with sockets, not file
   objects. The `OSError` is silently caught, so hooks run but receive no input — they're blind.
   Fix: use `sys.stdin.read()` directly on Windows.
6. **`auto-approve.py` path comparison broken** — uses `os.path.expanduser("~/.claude/")` which
   returns backslash paths on Windows (`C:\Users\...\.claude\`), but Claude Code sends forward-slash
   paths (`C:/Users/.../.claude/`). The `startswith` check always fails, so Read auto-approval
   for `~/.claude/` files never works. Fix: normalize both paths with `.replace("\\", "/")`.
7. **`validator-dispatcher.py` uses `python3`** — the `subprocess.run(["python3", ...])` call
   to invoke skill-specific validators fails on Windows where `python3` doesn't exist.
   Fix: replace with `python`.

This skill automates the full install + Windows fixes so the user doesn't have to
remember any of this.

## Skill naming

This skill MUST be named `util-sf-skills-windows` (with `util-` prefix).
The installer prunes any `sf-*` directory not in the official repo.

## Critical Rules

- **NEVER use `npx skills`** — that's vercel-labs/skills, a different tool entirely.
- **ALWAYS use `install.py`** — the official Jaganpro installer.
- **ALWAYS use `python`, NEVER `python3`** — `python3` doesn't exist on Windows by default.
- **ALWAYS prefix with `PYTHONUTF8=1`** — avoids Windows encoding errors.
- **Skills live in `~/.claude/skills/sf-*/` only** — never duplicate to `~/.agents/skills/sf-*/`.

## Operations

Detect user intent:
- "install" / "installer" → **Install**
- "update" / "mettre à jour" / "updater" → **Update**
- "status" / "état" / "check" → **Status**
- "fix" / "réparer" → **Fix only** (skip install, apply Windows fixes)
- "uninstall" / "désinstaller" → **Uninstall**

---

### Install

**Step 1 — Run official installer:**
```bash
curl -sSL https://raw.githubusercontent.com/Jaganpro/sf-skills/main/tools/install.py -o /tmp/install.py
PYTHONUTF8=1 python /tmp/install.py --force
```

**Step 2 — Apply Windows fixes** (see "Windows Fixes" section below).

**Step 3 — Run full verification** (see "Verification" section below).

**Step 4 — Show user guide** (see "User Guide" section below).

---

### Update

**Step 1 — Run official update:**
```bash
PYTHONUTF8=1 python ~/.claude/sf-skills-install.py --update
```
If the local copy doesn't exist, fall back to fresh install (Step 1 of Install).

**Step 2 — Apply Windows fixes.**

**Step 3 — Run full verification.**

**Step 4 — Show user guide.**

---

### Status

**Step 1 — Run official status:**
```bash
PYTHONUTF8=1 python ~/.claude/sf-skills-install.py --status
```

**Step 2 — Run full verification** (hooks, agents, validators, etc.).

**Step 3 — Show user guide.**

---

### Fix only

Skip install/update. Just apply Windows fixes + run verification + show user guide.

---

### Uninstall

```bash
PYTHONUTF8=1 python ~/.claude/sf-skills-install.py --uninstall
```

---

## Windows Fixes

Apply ALL FIVE fixes after every install or update.

### Fix 1: Validator Stubs

Check:
```bash
ls ~/.claude/skills/shared/hooks/scripts/naming_validator.py 2>/dev/null && echo "EXISTS" || echo "MISSING"
```

If MISSING, create directory and write both files using the Write tool:

**`~/.claude/skills/shared/hooks/scripts/naming_validator.py`**:
```python
#!/usr/bin/env python3
import xml.etree.ElementTree as ET
class NamingValidator:
    def __init__(self, flow_xml_path: str):
        self.flow_path = flow_xml_path
        try:
            self.tree = ET.parse(flow_xml_path)
            self.root = self.tree.getroot()
        except Exception:
            self.tree = None
            self.root = None
        self.namespace = {'sf': 'http://soap.sforce.com/2006/04/metadata'}
    def validate(self):
        return {'score': 20, 'max_score': 20, 'issues': [], 'recommendations': [], 'follows_convention': True, 'suggested_names': [], 'element_naming_issues': [], 'variable_naming_issues': []}
```

**`~/.claude/skills/shared/hooks/scripts/security_validator.py`**:
```python
#!/usr/bin/env python3
import xml.etree.ElementTree as ET
class SecurityValidator:
    def __init__(self, flow_xml_path: str):
        self.flow_path = flow_xml_path
        try:
            self.tree = ET.parse(flow_xml_path)
            self.root = self.tree.getroot()
        except Exception:
            self.tree = None
            self.root = None
        self.namespace = {'sf': 'http://soap.sforce.com/2006/04/metadata'}
    def validate(self):
        running_mode = 'User'
        bypasses_permissions = False
        if self.root is not None:
            run_in_mode = self.root.find('sf:runInMode', self.namespace)
            if run_in_mode is not None and run_in_mode.text == 'SystemModeWithoutSharing':
                running_mode = 'System'
                bypasses_permissions = True
        return {'score': 15, 'max_score': 15, 'issues': [], 'recommendations': [], 'running_mode': {'mode': running_mode, 'bypasses_permissions': bypasses_permissions}, 'hardcoded_ids': [], 'privilege_escalation': [], 'sensitive_fields': []}
```

### Fix 2: Hook Registration in settings.local.json (project-level)

**CRITICAL**: Do NOT write hooks to `~/.claude/settings.json` (global). Claude Code resets
this file on every restart, stripping all hooks. Instead, write hooks to the **project-level**
`.claude/settings.local.json` file in the current project directory.

**Step 1 — Read the installer's output from `~/.claude/settings.json`:**

After `install.py` runs, it writes hooks to `~/.claude/settings.json`. Read this file to
extract the hook definitions. The installer's hooks will have:
- `python3` (broken on Windows) — must replace with `python`
- Backslash paths — must replace with quoted forward-slash paths
- `"_sf_skills": true` fields — must remove (not in schema, causes hooks to be stripped)

**Step 2 — Fix the hook commands:**

For every hook command:
- Replace `python3` with `python`
- Replace the path with a quoted forward-slash version
- **REMOVE the `"_sf_skills": true` field** from every hook block

**Step 3 — Write fixed hooks to `.claude/settings.local.json`:**

Read the project's `.claude/settings.local.json` (create `.claude/` dir if needed).
Merge the fixed hooks into this file, preserving any existing permissions or other settings.

Before (broken — written by installer to `~/.claude/settings.json`):
```json
{
  "matcher": "Bash",
  "hooks": [
    {
      "type": "command",
      "command": "python3 C:\\Users\\username\\.claude\\hooks/scripts/guardrails.py",
      "timeout": 5000
    }
  ],
  "_sf_skills": true
}
```

After (fixed — written to `.claude/settings.local.json`):
```json
{
  "matcher": "Bash",
  "hooks": [
    {
      "type": "command",
      "command": "python \"C:/Users/username/.claude/hooks/scripts/guardrails.py\"",
      "timeout": 5000
    }
  ]
}
```

**Step 4 — Clean up global settings.json:**

Remove the hooks section from `~/.claude/settings.json` (since they'll be stripped on
restart anyway). Keep only the `permissions` key.

**IMPORTANT**: Preserve all existing `.claude/settings.local.json` content (permissions,
non-sf-skills hooks, etc.).

### Fix 3: Patch `select.select()` in hook scripts

All hook scripts use `select.select([sys.stdin], ...)` to read JSON input from Claude Code.
On Windows, `select.select` only works with sockets, not file objects — it throws `OSError`
which is silently caught, making hooks blind (they run but receive no input).

**Patch**: In every `~/.claude/hooks/scripts/*.py` file, find the `read_stdin_safe` function
and replace the `select.select` block with a Windows-compatible version:

Before (broken on Windows):
```python
    try:
        readable, _, _ = select.select([sys.stdin], [], [], timeout_seconds)
        if not readable:
            return {}
        return json.load(sys.stdin)
```

After (cross-platform):
```python
    try:
        if sys.platform == 'win32':
            data = sys.stdin.read()
            return json.loads(data) if data.strip() else {}
        else:
            readable, _, _ = select.select([sys.stdin], [], [], timeout_seconds)
            if not readable:
                return {}
            return json.load(sys.stdin)
```

**Files to patch** (9 total):
`guardrails.py`, `api-version-check.py`, `auto-approve.py`, `validator-dispatcher.py`,
`session-init.py`, `org-preflight.py`, `lsp-prewarm.py`, `llm-eval.py`, `stdin_utils.py`

### Fix 4: Normalize paths in `auto-approve.py`

In `~/.claude/hooks/scripts/auto-approve.py`, find the Read tool handler and normalize
both the input path and the expanded `~/.claude/` path to use forward slashes:

Before (broken on Windows):
```python
        file_path = tool_input.get("file_path", "")
        claude_dir = os.path.expanduser("~/.claude/")
```

After (cross-platform):
```python
        file_path = tool_input.get("file_path", "").replace("\\", "/")
        claude_dir = os.path.expanduser("~/.claude/").replace("\\", "/")
```

### Fix 5: Replace `python3` in `validator-dispatcher.py`

In `~/.claude/hooks/scripts/validator-dispatcher.py`, find the `run_validator` function
and replace `python3` with `python` in the subprocess call:

Before:
```python
            ["python3", validator_path],
```

After:
```python
            ["python", validator_path],
```

---

## Verification

After every operation (install, update, status, fix), run ALL these checks and present
a single summary table.

### 1. Skills count
```bash
ls -d ~/.claude/skills/sf-* 2>/dev/null | wc -l
```
Expected: 19

### 2. Hook scripts on disk
```bash
ls ~/.claude/hooks/scripts/*.py 2>/dev/null | wc -l
```
Expected: 9+

### 3. Hooks active in settings.local.json (project-level)
Read `.claude/settings.local.json` (in the current project directory) and check:
- Does a `"hooks"` key exist?
- How many hook events are registered? (SessionStart, PreToolUse, PostToolUse, PermissionRequest)
- Do the commands use `python` (not `python3`)?
- Do the paths use forward slashes and quotes?
- Is `~/.claude/settings.json` clean (no hooks — they'd be stripped on restart anyway)?

Expected: 4 events in `.claude/settings.local.json`, `python` (not `python3`), forward-slash quoted paths.

### 4. Agents
```bash
ls ~/.agents/*.md 2>/dev/null | wc -l
```
Expected: 7

### 5. LSP Engine
```bash
ls ~/.claude/lsp-engine/ 2>/dev/null | wc -l
```
Expected: 8

### 6. Validator stubs
```bash
ls ~/.claude/skills/shared/hooks/scripts/*.py 2>/dev/null | wc -l
```
Expected: 2

### 7. Functional smoke tests

Run each hook script with simulated JSON input via `echo '...' | python <script>`.
Parse the JSON output and verify the decision matches the expected result.

**guardrails.py (PreToolUse)** — pipe and check `permissionDecision`:
```bash
echo '{"tool_name":"Bash","tool_input":{"command":"DELETE FROM Account;"}}' | PYTHONUTF8=1 python ~/.claude/hooks/scripts/guardrails.py
```
Expected: `"permissionDecision": "deny"` (CRITICAL — DELETE without WHERE)

```bash
echo '{"tool_name":"Bash","tool_input":{"command":"sf data query --query \"SELECT Id FROM Account\" --target-org fake"}}' | PYTHONUTF8=1 python ~/.claude/hooks/scripts/guardrails.py
```
Expected: `"permissionDecision": "allow"` with `additionalContext` containing "SOQL without USER_MODE" (MEDIUM warning)

**auto-approve.py (PermissionRequest)** — pipe and check `autoApprove`:
```bash
echo '{"tool_name":"Bash","tool_input":{"command":"sf org display --target-org fake"}}' | PYTHONUTF8=1 python ~/.claude/hooks/scripts/auto-approve.py
```
Expected: `"autoApprove": true` (safe read-only operation)

```bash
echo '{"tool_name":"Bash","tool_input":{"command":"sf org delete scratch --target-org fake"}}' | PYTHONUTF8=1 python ~/.claude/hooks/scripts/auto-approve.py
```
Expected: `"autoApprove": false` (dangerous — org deletion)

```bash
echo '{"tool_name":"Read","tool_input":{"file_path":"C:/Users/USERNAME/.claude/skills/sf-apex/SKILL.md"}}' | PYTHONUTF8=1 python ~/.claude/hooks/scripts/auto-approve.py
```
Expected: `"autoApprove": true` (safe read of ~/.claude/) — **replace USERNAME with actual username**

**validator-dispatcher.py (PostToolUse)** — pipe and check for output:
```bash
echo '{"tool_name":"Write","tool_input":{"file_path":"/fake/test.cls","content":"public class Test {}"}}' | PYTHONUTF8=1 python ~/.claude/hooks/scripts/validator-dispatcher.py
```
Expected: JSON output with `"hookEventName": "PostToolUse"` and `additionalContext` containing "VALIDATION RESULTS"

```bash
echo '{"tool_name":"Write","tool_input":{"file_path":"/fake/readme.md","content":"# Hello"}}' | PYTHONUTF8=1 python ~/.claude/hooks/scripts/validator-dispatcher.py
```
Expected: No output (non-SF file, silently ignored)

**api-version-check.py (PreToolUse)** — pipe and check `permissionDecision`:
```bash
echo '{"tool_name":"Bash","tool_input":{"command":"sf project deploy start --target-org fake"}}' | PYTHONUTF8=1 python ~/.claude/hooks/scripts/api-version-check.py
```
Expected: `"permissionDecision": "allow"` (no sfdx-project.json = nothing to check)

### Summary table format

Present results like this:

```
| Component          | Status | Detail                          |
|--------------------|--------|---------------------------------|
| Skills             | OK/ERR | 19/19                           |
| Hook scripts       | OK/ERR | 9 on disk                       |
| Hooks in settings  | OK/ERR | 4 events, paths fixed           |
| Agents             | OK/ERR | 7/7 (FDE + PS)                  |
| LSP Engine         | OK/ERR | 8 files                         |
| Validator stubs    | OK/ERR | 2/2                             |
| Smoke tests        | OK/ERR | 7/7 passed                      |
```

If any check is ERR, explain what's wrong and offer to fix it.

---

## User Guide

After EVERY operation (install, update, status, fix), display this to the user:

```
sf-skills is ready! Here's what you can ask me:

STATUS & MAINTENANCE:
  "sf-skills status"          → Full health check
  "update sf-skills"          → Update to latest version + re-apply fixes
  "fix sf-skills"             → Re-apply Windows fixes without reinstalling

SALESFORCE DEVELOPMENT (via skills):
  /sf-apex                    → Write Apex classes, triggers, tests
  /sf-flow                    → Build and validate Flows
  /sf-lwc                     → Lightning Web Components
  /sf-deploy                  → Deploy metadata to org
  /sf-soql                    → Write and optimize SOQL queries
  /sf-testing                 → Run tests and fix failures
  /sf-debug                   → Analyze debug logs
  /sf-metadata                → Create objects, fields, permissions
  /sf-integration             → REST/SOAP callouts, Named Credentials
  /sf-diagram-mermaid         → Architecture diagrams

AGENTFORCE (via skills):
  /sf-ai-agentforce           → Build Agentforce agents
  /sf-ai-agentscript          → Agent Script DSL development
  /sf-ai-agentforce-testing   → Test agents
  /sf-ai-agentforce-conversationdesign → Conversation design

SPECIALIZED AGENTS (via Task tool):
  "Use the fde-engineer to..."              → Hands-on Apex/metadata dev
  "Use the fde-strategist to..."            → Architecture planning
  "Use the ps-solution-architect to..."     → Declarative design, permissions
  "Use the ps-technical-architect to..."    → Apex, integrations, data model
  "Use the fde-qa-engineer to..."           → Testing and debugging
  "Use the fde-release-engineer to..."      → Deployments and CI/CD
  "Use the fde-experience-specialist to..." → Conversational UX design
```

---

## Installation of this skill

```bash
mkdir -p ~/.claude/skills/util-sf-skills-windows
curl -sSL https://gist.github.com/Eyji/dedc6a0baf171134dcfce1e1543ce760/raw -o ~/.claude/skills/util-sf-skills-windows/SKILL.md
```

Then restart Claude Code.

## Edge Cases

- **`ln -s` on Windows**: Does NOT create real symlinks — silently copies. Use PowerShell `New-Item -ItemType Junction` if needed.
- **`~/.claude/sf-skills-install.py` missing**: Fall back to downloading install.py from GitHub.
- **Validator stubs already exist**: Skip creation, just verify.
- **Skill deleted after install**: You named it `sf-*`. Must be `util-*`.
- **Hooks removed from `~/.claude/settings.json`**: This is expected — Claude Code resets this file on restart. Hooks must live in `.claude/settings.local.json` (project-level). Re-apply Fix 2.
- **No `.claude/` directory in project**: Create it with `mkdir -p .claude` before writing `settings.local.json`.

## What NOT to Do

| Wrong | Right |
|-------|-------|
| `npx skills update` | `python ~/.claude/sf-skills-install.py --update` |
| `npx skills check` | `python ~/.claude/sf-skills-install.py --status` |
| `npx skills add Jaganpro/sf-skills` | `python /tmp/install.py --force` |
| Name this skill `sf-*` | Name it `util-sf-skills-windows` |
| Copy skills to `~/.agents/skills/sf-*` | Keep only in `~/.claude/skills/sf-*/` |
| Use `python3` | Use `python` |
| Skip `PYTHONUTF8=1` | Always prefix with it |
| Write hooks to `~/.claude/settings.json` | Write hooks to `.claude/settings.local.json` |
