# opc-skills **Multi-target** skill manager for **opencode** + **Claude Code** + **Feynman**. Single source-of-truth (parked archive in the personas repo), per-target lazy activation. Default operations target all three platforms; an interactive target-picker prompts when picking via fzf. ## Why opencode (≥1.14) auto-scans `~/.claude/skills/` as well as its own `~/.config/opencode/skills/` — so a 1000-skill Claude Code archive ends up bloating the opencode prompt. Feynman has its own `~/.feynman/agent/skills/`. opc-skills inverts this: keep all three target dirs *empty by default*, copy individual skills into them on demand. For full agent / skill / persona generation across multiple AI platforms, see the upstream [`personas`](https://gitea.taygun.net.tr/salvacybersec/personas) repo. ## Architecture ### Storage model — one source-of-truth, three lazy mirrors ``` ┌─────────────────────────────────────────┐ │ ~/Documents/personas/skills-archive/ │ │ (single source-of-truth, 1072+ skills) │ │ │ │ /SKILL.md ← frontmatter: │ │ name, desc, │ │ domain, sub, │ │ tags │ │ INDEX.json ← per-target active map │ │ INDEX.md ← human-readable table │ └─────────────────┬───────────────────────┘ │ │ cp -r (lazy, on demand, │ per --target) ▼ ┌─────────────────────────┼─────────────────────────┐ ▼ ▼ ▼ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ opencode │ │ claude │ │ feynman │ │ ~/.config/ │ │ ~/.claude/ │ │ ~/.feynman/ │ │ opencode/ │ │ skills/ │ │ agent/ │ │ skills/ │ │ │ │ skills/ │ └───────────────┘ └───────────────┘ └───────────────┘ ``` The parked archive is the **only** dir that's authoritative. The three target dirs are mutable mirrors — `enable` copies in, `disable` removes from. No skill is ever deleted from parked by opc-skills. ### Data flow — enable / disable ``` enable --target T1,T2,... │ ├──► for each target T: │ cp -r PARKED/ → ACTIVE_T/ │ sync shared refs (e.g. _platform-mapping.md) │ INDEX.json: append T to record's `active[]` array │ └──► no global reindex required (incremental update) disable --target T1,T2,... │ ├──► for each target T: │ rm -rf ACTIVE_T/ │ INDEX.json: remove T from `active[]` │ └──► PARKED copy stays intact ``` ### INDEX schema Each entry is a deduped record across all 3 targets: ```json { "folder": "performing-network-traffic-analysis-with-tshark", "name": "performing-network-traffic-analysis-with-tshark", "description": "Automate network traffic analysis using tshark…", "domain": "cybersecurity", "subdomain": "network-security", "tags": ["network-analysis", "pcap", "tshark"], "active": ["claude", "feynman"], // per-target presence "status": "active" // "active" if any target, else "parked" } ``` `reindex` rebuilds this from filesystem state across all 4 dirs (parked + 3 actives). `enable`/`disable` mutate `active[]` and `status` incrementally so bulk axis-ops (`enable-domain`, `enable-tag`) stay accurate without rescanning. ### Interactive flows (`pick` / `disable-pick`) ``` pick: disable-pick: ───── ──────────── ① fzf: pick category ① fzf: pick category (verb-prefix: performing, (from union-of-actives across detecting, analyzing…) opencode + claude + feynman) ② fzf --multi: pick skills ② fzf --multi: pick skills preview: PARKED//SKILL.md preview: same [TAB toggles, ENTER confirms] rows annotated [oc,cl,fy] ③ fzf: pick target(s) ③ fzf: pick target(s) to remove from ┌───────────────────────────┐ same picker │ all (default) │ ENTER on highlighted "all" │ opencode (N active) │ = remove from every target │ claude (N active) │ where it's currently active │ feynman (N active) │ └───────────────────────────┘ [TAB multi-select supported] ④ cp -r per-target ④ rm -rf per-target ``` The target picker leverages an fzf quirk: `--multi` returns the highlighted line as the sole selection if the user never pressed `TAB`. Putting `all` first means a default `ENTER` falls through to all three targets. ### fzf `{}` quoting (gotcha) fzf shell-escapes `{}` substitutions automatically (wraps in single quotes). Wrapping `{}` again in outer quotes embeds those quote literals into the path: ```bash # ❌ becomes: sed -n '1,40p' ".../skills-archive/'name'/SKILL.md" --preview="sed -n '1,40p' \"$PARKED/{}/SKILL.md\" 2>/dev/null" # ✅ becomes: sed -n '1,40p' ".../skills-archive"/'name'/SKILL.md # (bash concatenates adjacent quoted/unquoted strings cleanly) --preview="sed -n '1,40p' \"$PARKED\"/{}/SKILL.md 2>/dev/null" ``` All preview lines in `bin/opc-skills` follow the second pattern. ## Install ```bash ln -s ~/Documents/opc-skills/bin/opc-skills ~/.local/bin/opc-skills ``` Ensure `~/.local/bin` is on your `PATH`. ## Layout it expects ``` ~/Documents/personas/skills-archive/ # full catalog — single source-of-truth /SKILL.md INDEX.json INDEX.md ~/.config/opencode/skills/ # currently-enabled (opencode target) /SKILL.md ~/.claude/skills/ # currently-enabled (claude target) /SKILL.md ~/.feynman/agent/skills/ # currently-enabled (feynman target) /SKILL.md ``` (`~/Documents/opencode-skills-parked` is symlinked to `personas/skills-archive` for backward-compat.) Override via env: - `OPC_PARKED` — parked catalog root (default `~/Documents/personas/skills-archive`) - `OPC_ACTIVE` — opencode active dir (default `~/.config/opencode/skills`) - `OPC_CLAUDE_ACTIVE` — claude active dir (default `~/.claude/skills`) - `OPC_FEYNMAN_ACTIVE` — feynman active dir (default `~/.feynman/agent/skills`) - `OPC_TARGETS` — comma-separated default targets (default `opencode,claude,feynman`) ## Targets `enable`, `disable`, and `disable-all` accept `--target` / `-t`: | flag | meaning | |---|---| | `--target opencode` | only `~/.config/opencode/skills/` | | `--target claude` | only `~/.claude/skills/` | | `--target feynman` | only `~/.feynman/agent/skills/` | | `--target all` | all three | | `--target both` | legacy `opencode,claude` (no feynman) | | `--target opencode,feynman` | comma-separated subset | When omitted, the default is taken from `OPC_TARGETS` (default `opencode,claude,feynman`). ## Commands ### Inspection ``` opc-skills status # parked + per-target active counts (3 targets) opc-skills list {active|parked|all} # skill folder names opc-skills categories # verb-prefix counts (performing, detecting, ...) opc-skills domains # frontmatter `domain:` distribution opc-skills subdomains # frontmatter `subdomain:` distribution opc-skills tags # frontmatter `tags:` distribution ``` ### Single-skill operations ``` opc-skills enable [--target T] # copy parked → active in selected targets opc-skills disable [--target T] # remove from active in selected targets opc-skills disable-all [--target T] [-y|--yes] # disable every active skill across targets ``` ### Bulk by axis ``` opc-skills enable-category # fzf multi-pick within a verb-prefix opc-skills disable-category opc-skills enable-domain # bulk by frontmatter domain (eg. cybersecurity) opc-skills disable-domain opc-skills enable-subdomain # bulk by subdomain (eg. malware-analysis) opc-skills disable-subdomain opc-skills enable-tag [--all] ... # bulk by tags (default: any; --all: intersection) opc-skills disable-tag [--all] ... ``` ### Interactive / search ``` opc-skills pick # fzf: category → multi-select skills → target picker → enable opc-skills disable-pick # fzf: union-of-actives → category → multi-select → target picker → disable opc-skills search [query] # fzf fuzzy over name+description (enables on selection) opc-skills disable-search [query] # fzf over ACTIVE only (disables on selection) opc-skills reindex # rebuild INDEX.json / INDEX.md ``` #### `pick` flow 1. Choose a verb-prefix category (`performing`, `detecting`, …) 2. fzf shows skills in that category — `TAB` toggles, `ENTER` confirms 3. **Target picker** appears: `all` / `opencode` / `claude` / `feynman` — `TAB` for multi-select; `ENTER` on highlighted `all` (default) sends to all three 4. Selected skills are enabled in the chosen target(s) #### `disable-pick` flow Now multi-target aware: lists the **union of all active skills across the three targets**, with each entry annotated `[oc,cl,fy]` showing where it's currently active. After multi-select, the same target picker decides which target(s) to remove from. `fzf` is required for the interactive commands; `jq` for the bulk-by-axis commands; `python3` (with optional `PyYAML`) for `reindex`. ### INDEX schema `reindex` parses each `SKILL.md` frontmatter and emits records with: `folder`, `name`, `description`, `domain`, `subdomain`, `tags[]`, `active[]` (per-target list — `opencode`/`claude`/`feynman`), `status`. `enable` / `disable` perform an incremental status update on `INDEX.json` so `enable-domain` / `disable-tag` etc. stay accurate without a full reindex. ## Shared reference files Some skills reference sibling files via `../.md` (e.g. the 20 Feynman research skills use `../_platform-mapping.md` for cross-platform tool mapping). `opc-skills enable` auto-syncs any such files from PARKED → ACTIVE in **all three targets** so the relative references keep resolving. Currently synced: - `_platform-mapping.md` — Feynman-skills cross-platform subagent/scheduling/persistence mapping If you add new shared-reference files at the PARKED root, extend `sync_shared_refs_to_targets()` in `bin/opc-skills`. ## Implementation notes - `fzf` placeholder `{}` is shell-escaped automatically — preview commands use `"$PARKED"/{}/SKILL.md` (variable quoted, `{}` unquoted) so bash concatenation produces a clean path. Wrapping `{}` in extra quotes (`"$PARKED/{}/SKILL.md"`) embeds fzf's escape literals into the path and breaks preview. ## Known interaction The `personas` repo's `build.py --install opencode` currently wipes and repopulates `~/.config/opencode/skills/` as part of installation, which destroys the active set curated here. Use `opc-skills` *after* any personas install, or skip the skills half of `build.py` installs. `build.py --install opencode` and `build.py --install claude-skills` both emit `_platform-mapping.md` at the skills-root alongside the 20 Feynman skills (`deep-research`, `literature-review`, `paper-code-audit`, `peer-review`, `paper-writing`, `replication`, `source-comparison`, `summarize`, `alpha-research`, `eli5`, `autoresearch`, `docker`, `modal-compute`, `runpod-compute`, `session-log`, `session-search`, `jobs`, `watch`, `preview`, `contributing`).