EdisonYang1982/dsh-skill-switch ↗★ 0

dsh-skill-switch

会话级技能开关与技能组挂载工具 用于控制会话加载的技能,适合需要纯净聊天、免除技能开销的用户。

包名
dsh-skill-switch
兼容性
待验证
Harness 依赖范围
^0.1.0-rc.2 || ^0.1.0-rc.6 || ^0.1.5-rc.1 || ^0.2.0-rc.1 || ^0.2.0-rc.2
Cordis 依赖范围
^4.0.1
版本
0.1.0
许可证
MIT
最近更新
2026年10月8日

同名包的其他仓库

安装

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:EdisonYang1982/dsh-skill-switch

Configuration

Defaults are neutral and live in the bundled cordis.patch.yml. Everything deployment-specific belongs in your profile patch ( /cordis.patch.yml), keyed by the row id:

- id: skill-switch
  name: dsh-skill-switch
  config:
    defaultMode: all          # posture a new session starts in: all | none | work
    workSkills: []            # skills the `work` posture enables
    syncScript: ''            # absolute path of a sync script; empty hides the control
    hubDir: ''                # absolute path of the hub the host scans (needed for skillGroups)
    skillGroups: []           # all-or-nothing bundles, see below

defaultMode: all keeps the stock behaviour — you turn things off when you want to. Set none if you would rather opt in per session.

Sync (syncScript)

The host reads skills from a hub directory, not from wherever you author them. If your skill repository lives elsewhere (a network mount, a synced folder), a script bridges the two:

StageWhoAutomatic?
Repository → hub (copy)syncScript❌ a button in the strip
Hub → strip (display)file watcher + SSE✅ automatic

Point syncScript at your script and a Sync skills button appears. Clicking it runs the script, invalidates the catalog cache, and pushes a refresh to every open strip.

Manual on purpose. If the repository is on a network mount, every scan costs something; you decide when to pay it, and nothing runs while you are not looking. Both halves are required — without the sync the host does not know the new skill exists, so there is nothing for the strip to refresh.

Feedback follows the macOS idiom rather than a persistent log pane:

PhaseButton showsThen
Running◌ Syncing—
Success✓ Syncedreverts after 2s — no layout cost
Failure⚠ Sync failed (red)stays, and unfolds the script's output

Success needs no acknowledgement, so it withdraws. Failure stays because it asks something of you, and its text is what tells you what went wrong.

Skill groups (skillGroups)

Some skill families are both huge and interdependent: a member's SKILL.md links its siblings by relative path (lark-doc → ../lark-shared). A per-skill switch cannot express them — loading one alone breaks its references, and listing all of them costs their catalog lines whether or not the session uses them.

A skill group parks the family outside the hub and moves it in or out as one unit:

StateCatalog costMembers live in
Parked0parkDir
Mountedone line eachhubDir
hubDir: '/home/you/.agents/skills'
skillGroups:
  - id: lark
    label: 'Feishu CLI'
    description: '27 Feishu skills, mounted and parked as one unit'
    parkDir: '/home/you/.agents-parked/lark'
    discover: true          # read members from parkDir; no hand-maintained list

The strip shows a dashed chip per group: ⬡ Feishu CLI 27 when parked, solid when mounted. Clicking moves the whole family.

Why whole groups, and why moving directories rather than hiding lines. Three design decisions, each buying tokens:

  1. Parked costs 0, hidden costs its lines. The cheapest token is one never sent. Hiding a skill in the catalog still requires the registry to discover it first, and discovery is what renders the line — so per-skill filtering can only ever remove the line it already paid to compute. Moving the directory out of the hub means the registry never sees it at all. On the measured family above that is the difference between 0 and 5,559 characters.
  2. All-or-nothing, because the family is genuinely atomic. Members reference each other by relative path; a per-member switch would let you build a broken state (one member mounted, its lark-shared dependency parked). A group switch cannot express that state, which is the point.
  3. A roster file, so the count survives a restart. After mounting, the park directory is empty — membership has to be recorded somewhere, and an in-memory list would report 0 members on the next host start, which is exactly the state the user just switched into.

Member roster. Mounting writes the member names to /.members. After a mount the park directory is empty, so that file is the only record of membership — an in-memory list would not survive a host restart. Unmounting reads it to decide what moves back.

Safety boundary. Group membership is read from parkDir only, never by scanning the hub root — the hub holds unrelated skills, and scanning it would adopt them into the group and sweep them into the park directory on the next unmount. test/group.test.mjs locks this down.