@restregular/dsh-skill-center 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ # The SKILL.md contract
2
+
3
+ ## File shape
4
+
5
+ ```markdown
6
+ ---
7
+ name: my-skill
8
+ description: "Use when …"
9
+ ---
10
+
11
+ # Body
12
+
13
+ Instructions, loaded only when the skill is invoked.
14
+ ```
15
+
16
+ - The first line must be exactly `---`. A UTF-8 BOM before it breaks the check in `dsh-skill-filesystem`, and the file is then ignored as "missing YAML frontmatter" — this plugin's own frontmatter editor tolerates a BOM, the discovery parser does not.
17
+ - The frontmatter ends at the next line that is exactly `---`. The body is everything after it, **trimmed** at both ends.
18
+ - The frontmatter is parsed as YAML and must be a mapping. An array or a bare scalar is not frontmatter.
19
+ - A directory bundle holds this file as `<root>/<name>/SKILL.md`; the flat form `<root>/<name>.md` is equivalent, but its resource base directory is the whole root, so use the bundle form for anything with resources.
20
+
21
+ ## Fields
22
+
23
+ | Field | Required | Meaning |
24
+ |---|---|---|
25
+ | `name` | yes | routing name; must match `^[a-z0-9]+(?:-[a-z0-9]+)*$` — lowercase kebab-case, no underscores, no double or trailing hyphens |
26
+ | `description` | yes | the routing sentence the model sees in the catalog; write "Use when …" and name the tasks |
27
+ | `whenToUse` | no | provider metadata; **not** rendered into the session catalog, so it never helps routing — put routing text in `description` |
28
+ | `metadata` | no | free-form object, preserved for consumers |
29
+ | `disable-model-invocation` | no | `true` keeps the skill out of the model catalog and the `skill` tool; only an explicit `/name` from the user loads it |
30
+ | `user-invocable` | no | `false` disables `/name` for humans while the model keeps access |
31
+
32
+ Invocation keys accept a boolean or `1`/`0`/`true`/`false`/`yes`/`no`/`on`/`off` (case-insensitive). Any other value throws, and the skill is skipped.
33
+
34
+ ### The spelling trap
35
+
36
+ The camelCase forms are **rejected on purpose**:
37
+
38
+ - `disableModelInvocation` → use `disable-model-invocation`
39
+ - `modelInvocable` → use `disable-model-invocation`
40
+ - `userInvocable` → use `user-invocable`
41
+
42
+ A file carrying one is skipped with `frontmatter field "…" is unsupported; use "…"`. There is no defaulting, and no partial load.
43
+
44
+ ## The four invocation states
45
+
46
+ | Frontmatter | Model can load | `/name` works |
47
+ |---|---|---|
48
+ | (neither key) | yes | yes |
49
+ | `disable-model-invocation: true` | no | yes |
50
+ | `user-invocable: false` | yes | no |
51
+ | both | no | no — the skill is registered but unreachable; almost always a mistake |
52
+
53
+ `disable-model-invocation: true` is the right choice for a workflow a human must start deliberately (a release, a destructive migration): the catalog and the `skill` tool never advertise it, and the only entry point is the user typing `/name`.
54
+
55
+ ## Why a skill does not load
56
+
57
+ Discovery logs one warning and skips the file; nothing reaches the model catalog, so a broken skill is indistinguishable from an absent one from inside the session. Check the host log for `skill file <path> ignored:` and the reason:
58
+
59
+ | Warning | Cause |
60
+ |---|---|
61
+ | `missing YAML frontmatter` | no leading `---`, a BOM before it, or no closing `---` |
62
+ | `invalid YAML frontmatter` | the block does not parse (tabs, unbalanced quotes, bad nesting) |
63
+ | `frontmatter requires name and description` | either key missing, or not a non-empty string |
64
+ | `invalid skill name "…"` | uppercase, underscore, space, or a doubled/trailing hyphen |
65
+ | `invalid invocation frontmatter` | a non-boolean value, or a camelCase legacy key |
66
+
67
+ ## Rules that keep the skill manageable
68
+
69
+ - **Keep `name` equal to the directory name.** Discovery takes the name from frontmatter, but the Skill Center panel and `skill_create` locate a skill on disk as `<root>/<name>/SKILL.md`. A mismatch (or two directories claiming one name) makes a skill that loads but cannot be opened, edited or deleted.
70
+ - **One skill per bundle.** A second `SKILL.md` at a scanned root's direct child level becomes a second skill; only put extra `SKILL.md` files deeper, where discovery does not look.
71
+ - **Do not write these files by hand-editing YAML with a formatter.** The panel rewrites only the invocation keys and preserves the rest byte for byte, but an external formatter that reflows the block can turn `description` into a folded scalar and change what the model sees.
@@ -0,0 +1,89 @@
1
+ # Shipping skills inside a plugin
2
+
3
+ A plugin that owns a workflow should publish the skill with the code it describes, not copy files into the user's roots: the package versions both together, and every session in the profile sees the same instructions. The host half needs only `skills` in its `inject` list.
4
+
5
+ ## Provider registration (recommended)
6
+
7
+ `ctx.skills.registerProvider()` registers a catalog of skills that live in this package's own asset directory. Candidates are validated by the registry before they are cached, and each one can carry a `resourceBase` that tells the model where its resource files are.
8
+
9
+ ```js
10
+ import { readFileSync } from 'node:fs';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { join } from 'node:path';
13
+
14
+ const NAME = 'my-skill';
15
+ const ROOT = fileURLToPath(new URL('../skills/', import.meta.url)); // package assets
16
+ const RANK_BUNDLED = 600; // @deepseek-ai/dsh-skill BUNDLED_SKILL_RANK
17
+
18
+ export function apply(ctx) {
19
+ const directory = join(ROOT, NAME);
20
+ const path = join(directory, 'SKILL.md');
21
+ const raw = readFileSync(path, 'utf8');
22
+ const description = /* parse frontmatter, require a non-empty description */;
23
+ const candidate = {
24
+ name: NAME,
25
+ description,
26
+ invocation: { modelInvocable: true, userInvocable: true },
27
+ provider: 'my-plugin', // must equal the provider's name
28
+ source: 'bundled',
29
+ rank: RANK_BUNDLED,
30
+ resourceBase: { kind: 'directory', path: directory },
31
+ locator: path, // opaque; handed back to get()
32
+ path, // shown by discovery consumers such as this panel
33
+ };
34
+ ctx.effect(() => ctx.skills.registerProvider(() => ({
35
+ name: 'my-plugin',
36
+ list: () => Promise.resolve([candidate]),
37
+ async get(winner, options) {
38
+ const { rank, locator, ...summary } = winner;
39
+ return { ...summary, content: /* body after the frontmatter */ };
40
+ },
41
+ })), 'my-plugin: skill provider');
42
+ }
43
+ ```
44
+
45
+ Rules the registry enforces:
46
+
47
+ - `provider` on every candidate must equal the provider's own `name`; two providers in one layer may not share a name, and `runtime` is reserved.
48
+ - `name` must be kebab-case, `description` non-empty, `invocation` exactly two booleans, `rank` a finite number, `source` a string. One malformed candidate fails the whole observation, so validate while building the list, not inside `get()`.
49
+ - `list()` returns an array, or `{ candidates, complete: false }` when discovery was partial. Read files inside `get()` — **bodies are never cached**, so re-reading keeps edits visible; only the catalog is cached until you call the `invalidate()` handed to the provider factory.
50
+ - `get()` must return the selected candidate's `name` unchanged. A mismatch makes the registry drop its cached catalogs.
51
+ - Register inside `ctx.effect(...)` so disposal removes the provider and invalidates the catalog when the plugin reloads.
52
+
53
+ `resourceBase: { kind: 'directory', path }` is what makes the skill's resources usable: the model receives that absolute directory and is told to resolve relative paths against it. Without it the hint degrades to "resources are managed by provider X" and a resource-referencing skill becomes unusable.
54
+
55
+ ## Runtime registration (in-memory)
56
+
57
+ `ctx.skills.register(skill)` inserts one in-memory skill into the calling context's layer, labelled with the reserved `runtime` provider and rank 250. It is the right tool when the instructions are generated per deployment (paths, versions, feature flags) rather than shipped as files:
58
+
59
+ ```js
60
+ ctx.skills.register({
61
+ name: 'deployment-notes',
62
+ description: 'Use when …',
63
+ source: 'generated',
64
+ content: '# Deployment\n\n' + generatedBody,
65
+ invocation: { modelInvocable: true, userInvocable: true },
66
+ });
67
+ ```
68
+
69
+ `register()` takes the **definition**, not a candidate: `content` is required, `source` is required, and the object itself is handed back as the loaded definition. It accepts anything the provider form accepts except `rank`, `locator` and `get()`. Prefer a provider whenever the skill refers to resource files.
70
+
71
+ ## Bundle-provided root
72
+
73
+ A profile bundle can instead add a plain directory root to the filesystem provider with a patch row, which buys the watcher and needs no provider code:
74
+
75
+ ```yaml
76
+ - id: skill-filesystem
77
+ name: '@deepseek-ai/dsh-skill-filesystem'
78
+ config:
79
+ bundledSkillDir: '<absolute directory holding <name>/SKILL.md bundles>'
80
+ ```
81
+
82
+ That row scans the directory at rank 600 (`$DSH_BUNDLED_SKILL_DIR` is the default when the field is absent). It is a single directory, it is only configurable from the profile patch, and it cannot express a URL or opaque resource base.
83
+
84
+ ## Consequences to state in the package README
85
+
86
+ - **Read-only.** Such skills live outside the managed roots, so the Skill Center panel lists them as *embedded*: invocation switches, editing and deletion are disabled. That is intentional — the package owns them.
87
+ - **Rank 600.** Any project or user skill with the same name shadows it, which is how a user overrides a shipped workflow.
88
+ - **`app.asar`.** In the desktop build a bundled package may live inside `app.asar`; the Host side reads those paths fine, but shell commands, `glob`, `grep`, `node` and package managers cannot. Skills that tell the model to *run* a bundled script must say to copy it into the workspace first.
89
+ - **Frontmatter is parsed twice.** The provider reads the description for the catalog and the body for the load; keep one parser for both so the two can never disagree.
@@ -0,0 +1,51 @@
1
+ # Skill resources
2
+
3
+ ## How the model is told where they are
4
+
5
+ When a skill is loaded, the body is rendered inside `<skill_content>` together with a `<skill_resources>` block that carries the base location:
6
+
7
+ | Provider sets | `<skill_resources>` says |
8
+ |---|---|
9
+ | `resourceBase: { kind: 'directory', path }` | `Base directory for this skill: <path>` — "Resolve relative paths mentioned by this skill against the base directory before using them. Load referenced resources only as needed." |
10
+ | `resourceBase: { kind: 'url', url }` | `Base URL for this skill: <url>` |
11
+ | `resourceBase: { kind: 'opaque', description }` | `Resources for this skill: <description>` |
12
+ | nothing | "Resources for this skill are managed by provider …" |
13
+
14
+ For every filesystem skill the base directory is the **bundle directory** (`<root>/<name>/`); for the flat `<name>.md` form it is the whole root. Resources are a *hint*, not an attachment: nothing lists the directory and nothing fetches a referenced file. The body has to name the file and say when to read it.
15
+
16
+ ## Layout
17
+
18
+ ```
19
+ <root>/<name>/
20
+ SKILL.md routing + the workflow itself
21
+ references/ detail too long for the body: contracts, tables, edge cases
22
+ scripts/ commands the body tells the model to run
23
+ assets/ fixtures, images, sample data
24
+ templates/ files meant to be copied into the workspace
25
+ ```
26
+
27
+ These names are a convention, not a requirement — only the paths your `SKILL.md` actually mentions matter. Keep them stable: a renamed reference breaks every body that points at it.
28
+
29
+ ## Path rules
30
+
31
+ - Write paths **relative to the base directory** (`references/api.md`, `scripts/check.py`). Never write an absolute path into a skill: the root differs per machine and per install.
32
+ - Never rely on the working directory. A skill loads in whatever cwd the session has; the base directory is the only stable anchor.
33
+ - Do not reference files outside the bundle directory. A resource in the workspace, another skill, or `$DSH_HOME` may be absent at load time, and the base directory is the one location the model is told to trust.
34
+ - For a script, spell out how to run it: interpreter first, absolute path built from the base directory, and the expected cwd. On Windows the shell is PowerShell; `python`/`node` availability differs per host, so state the interpreter explicitly and prefer a command that fails loudly over one that silently does nothing.
35
+ - Keep scripts side-effect-free or clearly gated: a skill body is followed by a model that will run what it says.
36
+
37
+ ## Size and cost
38
+
39
+ - A loaded body has **no size cap** — everything in `SKILL.md` lands in context on every load. Keep it to routing plus the workflow, and push long-form detail into `references/` files that the body names one by one.
40
+ - Reference files cost nothing until read, so favor several focused files over one large one, and say in the body when each is needed ("read `references/roots.md` before choosing a directory").
41
+ - The catalog shows only `description` (truncated to the configured limit, 500 characters by default), so a long description buys nothing beyond the first sentence or two.
42
+
43
+ ## Writing resources
44
+
45
+ - Agent file-write tools are sandboxed to the session workspace, so a resource that belongs in a user-level root (`~/.agents/skills/…`) cannot be written with them. Use the `skill_create` tool's `resources` argument (a Host-side write confined to managed roots), or ask for approval.
46
+ - Editing or adding a resource file under an existing bundle does **not** change the catalog and needs no refresh — resource files are read directly by the file tools when the body asks for them. Adding a whole new skill bundle *is* a catalog change.
47
+ - Keep the plugin's write guard in mind: `skill_create` only writes inside `<root>/<name>/` and rejects absolute paths, `..` segments and a second `SKILL.md`, so resources cannot be smuggled out of the bundle.
48
+
49
+ ## Desktop caveat
50
+
51
+ Skills shipped inside a plugin that lives in `app.asar` (the first-party Office skills, for example) sit on a path that only the Host process's own file reads can open. Shell commands, the glob and search tools, `node` and package managers all fail on `app.asar` paths — copy such a file into the workspace with the file-read/file-write tools before running anything against it. Skills under the user and project roots are ordinary files with no such limit.
@@ -0,0 +1,44 @@
1
+ # Where skills live
2
+
3
+ Local skills are discovered by `@deepseek-ai/dsh-skill-filesystem`. It scans a fixed list of **roots** and, in each root, only that root's **direct children**: a directory bundle `<name>/SKILL.md`, or a flat file `<name>.md`. Nested `**/SKILL.md` search is deliberately unsupported, so a skill inside `assets/` or `templates/` of another skill is never discovered.
4
+
5
+ ## The root table
6
+
7
+ `<projectRoot>` is the nearest ancestor of the session's working directory that contains `.git`; if no ancestor has one, the working directory itself is the project root.
8
+
9
+ | Rank | Source id | Path | Scope |
10
+ |---|---|---|---|
11
+ | 100 | `project-dsh` | `<projectRoot>/.dsh/skills` | this project |
12
+ | 200 | `project-agents` | `<projectRoot>/.agents/skills` | this project |
13
+ | 250 | `runtime` | in-memory (`ctx.skills.register()`) | plugin-provided |
14
+ | 300 | `custom` | profile `customSkillDirs` entries | deployment |
15
+ | 400 | `user-dsh` | `$DSH_HOME/skills` (`~/.dsh/skills`) | all projects |
16
+ | 500 | `user-agents` | `$DSH_AGENTS_HOME/skills` (`~/.agents/skills`) | all projects |
17
+ | 600 | `bundled` | `bundledSkillDir` / `$DSH_BUNDLED_SKILL_DIR` | shipped with the app or a plugin |
18
+
19
+ `$DSH_HOME` defaults to `~/.dsh` and `$DSH_AGENTS_HOME` to `~/.agents`; both can be overridden by the environment. The `.system` subdirectory of `$DSH_HOME/skills` is skipped.
20
+
21
+ ## Choosing a root
22
+
23
+ - **Project-specific workflow** (build/test/release steps of one repository): `<projectRoot>/.agents/skills`. It travels with the repository; `<projectRoot>/.dsh/skills` has the same scope and outranks it.
24
+ - **Reusable across projects**: `$DSH_AGENTS_HOME/skills` (`~/.agents/skills`). This is what the Skill Center panel and `skill_create` use by default.
25
+ - **Personal Harness-only workflow**: `$DSH_HOME/skills` (`~/.dsh/skills`).
26
+ - Never hand-write into `custom` or `bundled` roots: they belong to the deployment and the package that ships them.
27
+
28
+ Only the four roots marked `project-dsh`, `project-agents`, `user-dsh` and `user-agents` are **managed** by this plugin: the panel and `skill_create` may create and write there, and every write is confined to a root that exists on disk (or to a user root, which they create). A skill that lives anywhere else — inside a plugin package, a `custom` directory, or under a `bundled` root — is listed as **embedded** and read-only.
29
+
30
+ ## Precedence
31
+
32
+ One name resolves to exactly one skill:
33
+
34
+ 1. **Layer first.** The registry keeps a host layer plus one layer per agent-preset scope; the nearest layer wins outright, so a preset-mounted repository plugin can shadow a host skill.
35
+ 2. **Rank second**, within one layer: the lowest rank wins, which is why a project skill shadows a user skill of the same name and both shadow a `bundled` one.
36
+ 3. Registration order, then provider-local order break the remaining ties.
37
+
38
+ Shadowed duplicates are logged and hidden; there is no API that lists them. If an edit seems to have no effect, check for a same-named skill in a nearer layer or a lower rank — that is the usual cause.
39
+
40
+ ## Presence and refresh
41
+
42
+ - A root that does not exist yet is simply empty; missing roots are polled until they appear, so creating `~/.agents/skills` later still works without a restart.
43
+ - Discovered skills are re-read on every load, but the *catalog* (names, descriptions, invocation policy) is cached per workspace and only refreshes when the provider invalidates it: adding, renaming or deleting a bundle, a flat `<name>.md`, or a direct `SKILL.md` triggers that invalidation. Changes under a bundle's resource subtrees do not.
44
+ - A skill present on disk but not in the catalog is usually invalid frontmatter, not a caching problem — see `frontmatter.md`.
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: <kebab-case-name>
3
+ description: "Use when <the task or question this skill answers, named in the user's terms>."
4
+ ---
5
+
6
+ # <Title>
7
+
8
+ <One paragraph: what this skill produces and the boundary of the request it answers.>
9
+
10
+ ## Workflow
11
+
12
+ 1. <First concrete step, with the command or file it acts on.>
13
+ 2. <Second step.>
14
+ 3. <Verification step: what proves the result is correct.>
15
+
16
+ ## Constraints
17
+
18
+ - <Rules that are not obvious from the steps: formats, ordering, what must not be touched.>
19
+ - <Where output belongs, and what must never be written.>
20
+
21
+ ## Read next
22
+
23
+ | Task | File |
24
+ |---|---|
25
+ | <the sub-task that needs detail> | `references/<topic>.md` |
26
+ | <a repeatable action> | `scripts/<tool>.py` |
27
+
28
+ Delete any row this skill does not need, and delete this instruction along with it. Keep every referenced file inside this bundle directory.