Claude Skill

skill-creator

Create, update, validate, and evaluate Wisp skills. Use when authoring a project-local or installable skill, refining its trigger description, adding deterministic scripts or Python sidecars, or testing whether another Agent can follow the workflow.

LLM Mart · 0 points · 13 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download xuzhougeng-wisp-science-skills_skill-creator-a3f7f7b.zip · 5 KB
Part of xuzhougeng/wisp-science — 25 skills

Install

skills CLI npx skills add https://github.com/xuzhougeng/wisp-science/tree/main/skills/skill-creator
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install xuzhougeng-wisp-science@llmmart
Git git clone https://github.com/xuzhougeng/wisp-science.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole xuzhougeng/wisp-science collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Create Wisp skills

Author project-local skills under .wisp/skills/<name>/. Wisp also discovers bundled skills, user-installed skills, and paths configured by WISP_SKILLS_PATH, but only normal project paths are directly writable through Agent file tools.

Structure

<skill-name>/
├── SKILL.md               # frontmatter trigger + procedure body
├── runtime.py             # optional helpers for the persistent Python runtime
├── runtime.r              # optional helpers for the persistent R runtime
├── scripts/               # optional standalone deterministic programs
├── references/            # optional detailed domain material
└── assets/                # optional output templates or static inputs

Keep SKILL.md concise. Put triggering information in frontmatter description; put essential procedure in the body; move detailed variants to one-level-deep references. Add only resources the workflow actually uses.

Workflow

  1. Define concrete user requests that should trigger the skill and the expected outputs.
  2. Search existing skills before creating a duplicate.
  3. Choose a lowercase hyphenated name and create .wisp/skills/<name>/SKILL.md with write.
  4. Add reusable scripts before writing long inline code examples. Execute every new script on representative local data.
  5. Add root-level runtime.py and/or runtime.r when helpers need to work with persistent interpreter state. The rendered skill supplies a one-time loading instruction for each file: exec(compile(...)) through python, or source(..., local = TRUE) through r. Skill loading itself does not execute them or inject Wisp tools. These files run inside the selected runtime; do not invoke them as standalone CLI scripts.
  6. Validate structure with this skill's scripts/quick_validate.py <skill-directory>.
  7. Refresh or reopen the project if the new skill does not yet appear, then find it with search_skills and load it with use_skill.
  8. Exercise the skill on realistic tasks. When explicit Wisp delegation is available, use a fresh bounded task with only the skill path and user-style request; do not leak the expected answer into the evaluation prompt.

For a user-wide installation, ask the user to install the validated folder via Settings → Skills. There is no Agent-side publish, overwrite, or delete API.

Frontmatter

At minimum include:

---
name: my-skill
description: Perform X. Use when the user asks for Y, Z, or related output.
---

The folder name and name should match. The description is the primary trigger; state both what the skill does and when it should be selected.

Runtime sidecar rules

Keep top-level code definition-only:

  • allow imports, function definitions, and literal constant assignments;
  • defer optional third-party imports into function bodies;
  • do not run work, access the network, or modify files at load time;
  • do not depend on injected Agent, Run, credential, artifact, or model objects;
  • pass paths and configuration explicitly;
  • use the corresponding python or r tool after loading; reload when that runtime restarts or the conversation/execution context changes;
  • Python and R retain separate state; use prefixed names to avoid collisions with other helpers within each language's namespace;
  • Python loading runs in __main__, so put self-checks in explicitly called functions rather than a __main__ guard.

Use scripts/ when a helper is a standalone CLI, needs argument parsing, or should run through run_in_context. Choose by state reuse and execution needs, not file length. scripts/runtime.py and scripts/runtime.r are ordinary scripts; only the reserved root-level filenames get runtime loading guidance.

Bundled scripts

  • scripts/quick_validate.py <skill-dir> — checks frontmatter shape, kebab-case naming, folder/name match, and length limits; prints every problem at once.
  • scripts/package_skill.py <skill-dir> [out-dir] — validates, then zips the folder into <name>.skill for user-wide installation, skipping build junk and a root-level evals/ folder.

To evaluate a skill, run it on realistic tasks yourself (step 8 above) and keep evaluation artifacts out of the skill folder unless they are intentional reusable resources.

Wisp boundaries

  • search_skills and use_skill do not edit the catalog.
  • Project file tools cannot manage user-wide installed skills outside granted workspace paths.
  • run_in_context executes deterministic work; it does not publish skills or call models.
  • Specialist creation is separate. Load customize and use save_specialist only when that explicit tool is advertised.
Files (wisp-science)
  • scripts
    • package_skill.py 2.1 KB
      #!/usr/bin/env python3
      """Zip a validated skill folder into a distributable `<name>.skill` archive.
      
      Usage: python package_skill.py <skill-directory> [output-directory]
      
      Validates first (via quick_validate.check_skill), then writes the archive with
      the skill folder name as the top-level entry. Build junk (__pycache__,
      node_modules, *.pyc, .DS_Store) and a root-level `evals/` folder are skipped.
      """
      
      import sys
      import zipfile
      from pathlib import Path
      
      sys.path.insert(0, str(Path(__file__).parent))
      from quick_validate import check_skill
      
      SKIP_ANYWHERE = {"__pycache__", "node_modules", ".DS_Store"}
      SKIP_SUFFIXES = {".pyc"}
      SKIP_AT_ROOT = {"evals"}
      
      
      def _included(rel):
          """rel is relative to the skill folder itself."""
          if rel.parts and rel.parts[0] in SKIP_AT_ROOT:
              return False
          if set(rel.parts) & SKIP_ANYWHERE:
              return False
          return rel.suffix not in SKIP_SUFFIXES
      
      
      def package(skill_dir, out_dir=None):
          """Return the written archive path, raising ValueError on a bad skill."""
          skill_dir = Path(skill_dir).resolve()
          problems = check_skill(skill_dir)
          if problems:
              raise ValueError("; ".join(problems))
      
          out_dir = Path(out_dir).resolve() if out_dir else Path.cwd()
          out_dir.mkdir(parents=True, exist_ok=True)
          archive = out_dir / f"{skill_dir.name}.skill"
      
          files = sorted(
              p for p in skill_dir.rglob("*")
              if p.is_file() and _included(p.relative_to(skill_dir))
          )
          with zipfile.ZipFile(archive, "w", zipfile.ZIP_DEFLATED) as zf:
              for p in files:
                  zf.write(p, Path(skill_dir.name) / p.relative_to(skill_dir))
          return archive, files
      
      
      def main(argv):
          if len(argv) not in (2, 3):
              print(__doc__.strip().splitlines()[2])
              return 1
          try:
              archive, files = package(argv[1], argv[2] if len(argv) == 3 else None)
          except ValueError as e:
              print(f"validation failed: {e}")
              return 1
          for p in files:
              print(f"  + {Path(p).relative_to(Path(argv[1]).resolve())}")
          print(f"wrote {archive} ({len(files)} files)")
          return 0
      
      
      if __name__ == "__main__":
          sys.exit(main(sys.argv))
      
    • quick_validate.py 4.3 KB
      #!/usr/bin/env python3
      """Validate a Wisp skill folder: frontmatter shape, naming, and length limits.
      
      Usage: python quick_validate.py <skill-directory>
      
      Prints every problem found (not just the first) and exits non-zero if any.
      """
      
      import re
      import sys
      from pathlib import Path
      
      KNOWN_KEYS = {
          "name", "description", "license", "allowed-tools",
          "metadata", "compatibility", "fold_cue", "wisp",
      }
      NAME_RE = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
      MAX_NAME = 64
      MAX_DESCRIPTION = 1024
      MAX_COMPATIBILITY = 500
      
      
      def _parse_minimal(block):
          """Fallback frontmatter parser for machines without pyyaml.
      
          Handles what skill frontmatter actually uses: top-level `key: value`
          pairs, quoted scalars, and `>`/`|` block scalars. Nested values are kept
          only as opaque markers since validation never inspects them.
          """
          lines = block.split("\n")
          data, i = {}, 0
          while i < len(lines):
              m = re.match(r"^([A-Za-z][\w-]*):\s*(.*)$", lines[i])
              if not m:
                  i += 1
                  continue
              key, rest = m.group(1), m.group(2).strip()
              i += 1
              body = []
              while i < len(lines) and (not lines[i].strip() or lines[i].startswith((" ", "\t"))):
                  body.append(lines[i].strip())
                  i += 1
              if rest in (">", "|", ">-", "|-", ""):
                  data[key] = " ".join(filter(None, body)) if body else {}
              else:
                  data[key] = rest.strip("'\"")
          return data
      
      
      def _frontmatter(text):
          """Return (dict, error) for the YAML block between the leading --- fences."""
          m = re.match(r"^---\n(.*?)\n---", text, re.DOTALL)
          if not m:
              return None, "SKILL.md must open with a `---` fenced YAML frontmatter block"
          try:
              import yaml
          except ModuleNotFoundError:
              return _parse_minimal(m.group(1)), None
          try:
              data = yaml.safe_load(m.group(1))
          except yaml.YAMLError as e:
              return None, f"frontmatter is not valid YAML: {e}"
          if not isinstance(data, dict):
              return None, "frontmatter must be a YAML mapping"
          return data, None
      
      
      def check_skill(skill_dir):
          """Return a list of problem strings; empty list means the skill passes."""
          skill_dir = Path(skill_dir)
          skill_md = skill_dir / "SKILL.md"
          if not skill_md.is_file():
              return [f"{skill_md} does not exist"]
      
          fm, err = _frontmatter(skill_md.read_text(encoding="utf-8"))
          if err:
              return [err]
      
          problems = []
          problems += [
              f"unknown frontmatter key `{k}` (known: {', '.join(sorted(KNOWN_KEYS))})"
              for k in sorted(set(fm) - KNOWN_KEYS)
          ]
      
          name = fm.get("name")
          if not isinstance(name, str) or not name.strip():
              problems.append("frontmatter needs a non-empty string `name`")
          else:
              name = name.strip()
              if not NAME_RE.fullmatch(name):
                  problems.append(
                      f"`name: {name}` must be kebab-case: lowercase/digit runs joined by single hyphens"
                  )
              if len(name) > MAX_NAME:
                  problems.append(f"`name` exceeds {MAX_NAME} chars ({len(name)})")
              if name != skill_dir.resolve().name:
                  problems.append(
                      f"`name: {name}` should match its folder `{skill_dir.resolve().name}`"
                  )
      
          desc = fm.get("description")
          if not isinstance(desc, str) or not desc.strip():
              problems.append("frontmatter needs a non-empty string `description`")
          else:
              if len(desc) > MAX_DESCRIPTION:
                  problems.append(f"`description` exceeds {MAX_DESCRIPTION} chars ({len(desc)})")
              if "<" in desc or ">" in desc:
                  problems.append("`description` must not contain angle brackets")
      
          compat = fm.get("compatibility")
          if compat is not None:
              if not isinstance(compat, str):
                  problems.append("`compatibility` must be a string when present")
              elif len(compat) > MAX_COMPATIBILITY:
                  problems.append(f"`compatibility` exceeds {MAX_COMPATIBILITY} chars ({len(compat)})")
      
          return problems
      
      
      def main(argv):
          if len(argv) != 2:
              print(__doc__.strip().splitlines()[2])
              return 1
          problems = check_skill(argv[1])
          for p in problems:
              print(f"FAIL: {p}")
          if not problems:
              print("OK: skill passes validation")
          return 1 if problems else 0
      
      
      if __name__ == "__main__":
          sys.exit(main(sys.argv))
      
  • SKILL.md 4.9 KB
    ---
    name: skill-creator
    description: Create, update, validate, and evaluate Wisp skills. Use when authoring a project-local or installable skill, refining its trigger description, adding deterministic scripts or Python/R runtime sidecars, or testing whether another Agent can follow the workflow.
    ---
    
    # Create Wisp skills
    
    Author project-local skills under `.wisp/skills/<name>/`. Wisp also discovers
    bundled skills, user-installed skills, and paths configured by
    `WISP_SKILLS_PATH`, but only normal project paths are directly writable through
    Agent file tools.
    
    ## Structure
    
    ```text
    <skill-name>/
    ├── SKILL.md               # frontmatter trigger + procedure body
    ├── runtime.py             # optional helpers for the persistent Python runtime
    ├── runtime.r              # optional helpers for the persistent R runtime
    ├── scripts/               # optional standalone deterministic programs
    ├── references/            # optional detailed domain material
    └── assets/                # optional output templates or static inputs
    ```
    
    Keep `SKILL.md` concise. Put triggering information in frontmatter
    `description`; put essential procedure in the body; move detailed variants to
    one-level-deep references. Add only resources the workflow actually uses.
    
    ## Workflow
    
    1. Define concrete user requests that should trigger the skill and the expected
       outputs.
    2. Search existing skills before creating a duplicate.
    3. Choose a lowercase hyphenated name and create
       `.wisp/skills/<name>/SKILL.md` with `write`.
    4. Add reusable scripts before writing long inline code examples. Execute every
       new script on representative local data.
    5. Add root-level `runtime.py` and/or `runtime.r` when helpers need to work with
       persistent interpreter state. The rendered skill supplies a one-time loading
       instruction for each file: `exec(compile(...))` through `python`, or
       `source(..., local = TRUE)` through `r`. Skill loading itself does not execute
       them or inject Wisp tools. These files run inside the selected runtime;
       do not invoke them as standalone CLI scripts.
    6. Validate structure with this skill's
       `scripts/quick_validate.py <skill-directory>`.
    7. Refresh or reopen the project if the new skill does not yet appear, then find
       it with `search_skills` and load it with `use_skill`.
    8. Exercise the skill on realistic tasks. When explicit Wisp delegation is
       available, use a fresh bounded task with only the skill path and user-style
       request; do not leak the expected answer into the evaluation prompt.
    
    For a user-wide installation, ask the user to install the validated folder via
    **Settings → Skills**. There is no Agent-side publish, overwrite, or delete API.
    
    ## Frontmatter
    
    At minimum include:
    
    ```yaml
    ---
    name: my-skill
    description: Perform X. Use when the user asks for Y, Z, or related output.
    ---
    ```
    
    The folder name and `name` should match. The description is the primary trigger;
    state both what the skill does and when it should be selected.
    
    ## Runtime sidecar rules
    
    Keep top-level code definition-only:
    
    - allow imports, function definitions, and literal constant assignments;
    - defer optional third-party imports into function bodies;
    - do not run work, access the network, or modify files at load time;
    - do not depend on injected Agent, Run, credential, artifact, or model objects;
    - pass paths and configuration explicitly;
    - use the corresponding `python` or `r` tool after loading; reload when that
      runtime restarts or the conversation/execution context changes;
    - Python and R retain separate state; use prefixed names to avoid collisions
      with other helpers within each language's namespace;
    - Python loading runs in `__main__`, so put self-checks in explicitly called
      functions rather than a `__main__` guard.
    
    Use `scripts/` when a helper is a standalone CLI, needs argument parsing, or
    should run through `run_in_context`. Choose by state reuse and execution needs,
    not file length. `scripts/runtime.py` and `scripts/runtime.r` are ordinary
    scripts; only the reserved root-level filenames get runtime loading guidance.
    
    ## Bundled scripts
    
    - `scripts/quick_validate.py <skill-dir>` — checks frontmatter shape, kebab-case
      naming, folder/name match, and length limits; prints every problem at once.
    - `scripts/package_skill.py <skill-dir> [out-dir]` — validates, then zips the
      folder into `<name>.skill` for user-wide installation, skipping build junk
      and a root-level `evals/` folder.
    
    To evaluate a skill, run it on realistic tasks yourself (step 8 above) and keep
    evaluation artifacts out of the skill folder unless they are intentional
    reusable resources.
    
    ## Wisp boundaries
    
    - `search_skills` and `use_skill` do not edit the catalog.
    - Project file tools cannot manage user-wide installed skills outside granted
      workspace paths.
    - `run_in_context` executes deterministic work; it does not publish skills or
      call models.
    - Specialist creation is separate. Load `customize` and use
      `save_specialist` only when that explicit tool is advertised.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related