🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

dimcode-linux-arm64

Package Overview
Dependencies
Maintainers
3
Versions
140
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

dimcode-linux-arm64 - npm Package Compare versions

Comparing version
0.2.11
to
0.2.12-beta.0
+4
bin/skills-assets/dim-modality/agents/openai.yaml
interface:
display_name: "Dim Modality"
short_description: "Use DimCode multimodal CLI"
default_prompt: "Use $dim-modality to create or process media with the DimCode multimodal CLI."
# CLI Recipes
Use `--json` for all agent/script calls. Prefer output paths inside the current workspace.
## Check Configuration
```bash
dim modality list --json
dim modality get image.generate --json
```
## Images
Generate an image:
```bash
mkdir -p ./artifacts
dim image generate --prompt "A clean product poster on a white background with soft natural lighting" --out ./artifacts/image.png --json
```
Edit an image:
```bash
mkdir -p ./artifacts
dim image edit --image ./input.png --prompt "Keep the subject, change the lighting to golden hour" --out ./artifacts/edited.png --json
```
Multi-image edit:
```bash
dim image edit --image ./person.png --image ./style.png --prompt "Render the person in the illustration style of the reference image" --out ./artifacts/edited.png --json
```
Enhance an image:
```bash
dim image enhance --image ./input.png --operation upscale --out ./artifacts/upscaled.png --json
```
## Video
Text-to-video:
```bash
mkdir -p ./artifacts
dim video generate --prompt "A robot walking slowly through a rainy neon street, cinematic camera movement" --duration 5 --out ./artifacts/video.mp4 --json --timeout 1800000
```
Image-to-video:
```bash
dim video generate --prompt "Make the person turn naturally toward the camera" --image ./input.png --duration 5 --out ./artifacts/video.mp4 --json --timeout 1800000
```
Edit a video:
```bash
dim video edit --video ./input.mp4 --operation add-audio --audio ./voice.mp3 --out ./artifacts/edited.mp4 --json
```
Enhance a video:
```bash
dim video enhance --video ./input.mp4 --operation stabilize --out ./artifacts/stable.mp4 --json
```
## Speech
Text-to-speech:
```bash
mkdir -p ./artifacts
dim speech synthesize --text "Hello, this is a speech generation test." --out ./artifacts/speech.mp3 --json
```
Specify a voice:
```bash
dim speech synthesize --text "Hello, this is a speech generation test." --voice "cosyvoice2:anna" --out ./artifacts/speech.mp3 --json
```
Transcribe audio:
```bash
dim speech transcribe --audio ./meeting.mp3 --out ./artifacts/transcript.txt --json
```
## Music And Audio
Generate music:
```bash
mkdir -p ./artifacts
dim music generate --prompt "Warm lo-fi piano for a podcast intro" --title "warm intro" --out ./artifacts/music.mp3 --json
```
Generate a sound effect:
```bash
dim audio generate --operation sound-effect --prompt "Raindrops tapping on a window" --duration 8 --out ./artifacts/rain.mp3 --json
```
Extract or generate audio from video:
```bash
dim audio generate --operation video-to-audio --video ./input.mp4 --out ./artifacts/audio.mp3 --json
```
## 3D Asset And Motion
```bash
dim asset generate --prompt "low poly robot game asset" --out ./artifacts/robot.glb --json
dim motion generate --prompt "walk cycle for a friendly robot" --out ./artifacts/walk.fbx --json
```
## Temporary Provider Or Model Override
Use this only when the user explicitly asks for a specific provider or model:
```bash
dim image generate --provider siliconflow --model Qwen/Qwen-Image --prompt "..." --out ./artifacts/image.png --json
```
## Provider-Specific Options
Write a JSON object file, then pass it with `--options`:
```json
{
"seed": 1234,
"negativePrompt": "blurry, low quality"
}
```
```bash
dim image generate --prompt "..." --options ./options.json --out ./artifacts/image.png --json
```
# Model Notes
This file is the entry point for provider/model experience. When a provider or model needs special handling, add a row here and put detailed notes under `references/models/`.
## Current Entries
| Provider / model | Capability | Note |
|---|---|---|
| CosyVoice2 | `speech.synthesize` | `--voice` uses `model-name:voice-name` |
| Video generation models | `video.generate` | Generation can take a long time; use a generous `--timeout` and keep operation id / JSON errors |
| Image edit models | `image.edit` | Requires `--image` and `--prompt`; pass repeated `--image` flags for multi-image edit |
| ASR models | `speech.transcribe` | Input is `--audio`; output should be a text file |
## CosyVoice2 Voices
CosyVoice2 `--voice` format:
```text
model-name:voice-name
```
Available voice names:
```text
alex
benjamin
charles
david
anna
bella
claire
diana
```
`alex`, `benjamin`, `charles`, and `david` are male voices. `anna`, `bella`, `claire`, and `diana` are female voices.
Example:
```bash
dim speech synthesize --text "Hello, this is a test." --voice "CosyVoice2:anna" --out ./artifacts/speech.mp3 --json
```
If the configured model id is not exactly `CosyVoice2`, replace the part before `:` with the configured model name or model id.
## Adding Future Model Notes
When adding a model note:
1. Create a file under `references/models/` named after the provider and model.
2. Add a row to the table above.
3. Include:
- Supported capability.
- Required inputs.
- Recommended output format.
- Known slow paths or failure modes.
- One runnable `dim ... --json` example.
---
name: dim-modality
description: Use DimCode multimodal CLI when the user asks the agent to generate or edit images, generate video, synthesize speech, transcribe audio, generate music or sound effects, create 3D assets or motion data, or inspect available multimodal defaults through `dim modality`. This skill teaches when to use the CLI, the main execution path, failure handling, and model-specific notes.
---
# Dim Modality
Use this skill when media work should go through the DimCode multimodal CLI instead of legacy image tools or ad hoc provider calls.
## When To Use
Use this skill for requests that involve:
- Generating, editing, enhancing, upscaling, or restoring images.
- Generating video from text, or generating video from an input image.
- Text-to-speech or speech-to-text.
- Music, sound effects, ambient audio, or audio extraction.
- 3D assets or motion data.
- Checking which multimodal defaults are configured.
- Debugging why a multimodal command failed.
Do not look for the old image-generation tool for these tasks. Do not add a native agent tool for each modality. The normal path is `exec` running `dim ... --json`.
## Main Path
1. Inspect configured multimodal defaults first:
```bash
dim modality list --json
```
2. Map the user request to a capability:
| User goal | Capability | CLI entry |
|---|---|---|
| Generate an image | `image.generate` | `dim image generate` |
| Edit an image | `image.edit` | `dim image edit` |
| Enhance an image | `image.enhance` | `dim image enhance` |
| Generate a video | `video.generate` | `dim video generate` |
| Synthesize speech | `speech.synthesize` | `dim speech synthesize` |
| Transcribe speech | `speech.transcribe` | `dim speech transcribe` |
| Generate music | `music.generate` | `dim music generate` |
| Generate audio or sound effects | `audio.generate` | `dim audio generate` |
| Generate a 3D asset | `asset.generate` | `dim asset generate` |
| Generate motion data | `motion.generate` | `dim motion generate` |
3. Write outputs under the current workspace, usually `./artifacts/` unless the user asked for another path.
4. Always use `--json`. Read output paths, provider/model ids, and errors from JSON instead of scraping human text.
5. After success, reference the absolute output path. Images can be shown with Markdown image syntax. Audio and video should be reported as absolute paths until Desktop preview support is richer.
## Operating Rules
- If the user did not specify provider/model, use the configured default for that capability.
- Only pass `--provider` and `--model` when the user explicitly asks for a temporary override. Overrides do not change defaults.
- If a default is missing, tell the user to configure that capability in Settings / Agent Defaults.
- Put provider-specific parameters in a JSON object file and pass it with `--options <path>`.
- Video generation can be slow. Use a generous `--timeout`, and never claim completion unless an output file exists.
- For failed commands, explain `error.code`, `error.message`, and `details` from the JSON output. Do not hide the original provider or CLI error.
- Keep LLM model selection separate from multimodal defaults. A chat model change does not change modality defaults.
- Do not expose API keys, credentials, headers, or provider secrets.
## Recipes And References
Read `references/cli-recipes.md` when you need command templates.
Read `references/model-notes.md` when a provider or model may need special handling. Future model-specific reference docs should be linked from that file.
## Failure Handling
| Situation | Response |
|---|---|
| Missing default model | Ask the user to configure the capability in Settings / Agent Defaults |
| Provider disconnected or missing key | Ask the user to check provider connection and API key |
| Model disabled | Ask the user to enable the model in the provider model list |
| Unsupported capability | Use a model that supports the capability, or ask the user to configure one |
| Output file missing | Do not report success; explain the CLI JSON error and stderr |
| Video timeout | Report timeout details and operation id when available |
## Final Response Style
When reporting back:
- Say which capability was used.
- Give the output file path.
- If it failed, state the failure point and the next user action.
- Keep the response short unless the user asks for detailed debugging.
# Installing and updating Dim plugins
A Dim plugin is a self-describing directory under `~/.agents/plugins/<name>/`. Disk is the
single source of truth: Dim scans that directory, reads each `.claude-plugin/plugin.json`, and
loads the plugin's components. Installing a plugin means placing its folder there; removing the
folder removes the plugin.
## Local development
1. Scaffold the plugin directly under `~/.agents/plugins/<name>/` (the default of
`scripts/create_basic_plugin.py`):
```bash
python3 scripts/create_basic_plugin.py my-plugin --with-skills
```
2. Edit the manifest and component files.
3. Start a **new session**. Dim re-scans `~/.agents/plugins`, and the plugin's skills, hooks,
and MCP servers load. A new session is the boundary at which changes are picked up.
To iterate, edit files in place and start another session. Removing the plugin directory
removes the plugin.
## Activation by component
- **Skills** — discovered from `~/.agents/plugins/*/skills/` and loaded into the system prompt
automatically. They also appear in the desktop Skills list.
- **Hooks** — read from the file named by the manifest `hooks` field and registered on session
start. Command paths use `${CLAUDE_PLUGIN_ROOT}`, which Dim replaces with the plugin's
absolute root.
- **MCP servers** — read from `.mcp.json` (or the inline `mcpServers` object) and merged into
the MCP runtime. Server ids are namespaced `plugin:<plugin-name>/<serverId>`.
## Toggling components
In the Dim desktop app, open Plugins → select the plugin to see its detail page. Each skill,
hook, and MCP server has its own on/off switch, plus a master switch to enable or disable all
components at once.
## Sharing via git
To distribute a plugin to other users:
1. Build the plugin in a directory you will push to git (use `--path`):
```bash
python3 scripts/create_basic_plugin.py my-plugin --path ./plugins --with-skills
```
2. Commit and push the plugin directory (including `.claude-plugin/plugin.json`) to a git repo.
3. In the Dim desktop app, open Plugins → Add plugin and paste the git source — either
`owner/repo` or a full git URL, with an optional ref (branch/tag). Dim clones the repo,
reads the manifest, and installs the plugin into `~/.agents/plugins/<name>/`, recording the
source in a `dim-install.json` provenance file.
Updating a git-installed plugin is done from the same Add-plugin flow (reinstall from the
source) or by editing the on-disk copy under `~/.agents/plugins` for local tweaks.
# Plugin manifest spec (`.claude-plugin/plugin.json`)
Dim recognizes a plugin by the presence of `.claude-plugin/plugin.json` at the plugin root.
It parses only the fields below; any other field is ignored. Keep the manifest minimal and
valid.
```json
{
"name": "my-plugin",
"version": "0.1.0",
"description": "Brief plugin description",
"interface": {
"displayName": "My Plugin",
"shortDescription": "Short subtitle shown in the plugin list",
"logo": "./assets/logo.png"
},
"skills": "./skills/",
"hooks": "./hooks.json",
"mcpServers": "./.mcp.json"
}
```
## Field guide
### Top-level fields
- `name` (`string`, required): Plugin identifier (lower-case hyphen-case, no spaces). Matches
the plugin folder name. Sanitized to `[A-Za-z0-9._-]`, max 128 chars.
- `version` (`string`, optional): Strict semver, e.g. `0.1.0`.
- `description` (`string`, optional): Short purpose summary. Falls back to
`interface.shortDescription` in the UI when absent.
- `interface` (`object`, optional): Presentation metadata (see below).
- `skills` (`string`, optional): Relative path to the skills directory, normally `./skills/`.
- `hooks` (`string`, optional): Relative path to the hooks config file, e.g. `./hooks.json`.
- `mcpServers` (`string`, optional): Relative path to an `.mcp.json` file, e.g. `./.mcp.json`.
An inline object is not supported — the runtime loads only a path string.
### `interface` fields
- `displayName` (`string`): User-facing title. Defaults to `name` when absent.
- `shortDescription` (`string`): Brief subtitle for compact views.
- `logo` (`string`): Relative path to a logo asset inside the plugin (for example
`./assets/logo.png`).
### Path conventions
- Component path values are relative to the plugin root and begin with `./`.
- A component path appears in the manifest only when its file/directory exists. Do not declare
`skills`, `hooks`, or `mcpServers` when their companion files were not created.
## Components
### Skills
Skills live under `skills/<skill-name>/SKILL.md`. Each `SKILL.md` starts with YAML
frontmatter:
```markdown
---
name: my-skill
description: What it does and when to use it.
---
# My Skill
Instructions for the assistant.
```
Dim discovers plugin skills automatically from `~/.agents/plugins/*/skills/` and loads them
into the system prompt.
### Hooks
`hooks` points to a JSON file mapping lifecycle events to commands. Use
`${CLAUDE_PLUGIN_ROOT}` in command paths — Dim substitutes the plugin's absolute root at
runtime:
```json
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume|clear|compact",
"hooks": [
{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/on-start.js\"" }
]
}
]
}
}
```
### MCP servers
`mcpServers` points to an `.mcp.json` file. Server ids are namespaced as
`plugin:<plugin-name>/<serverId>`. Command paths support the same `${CLAUDE_PLUGIN_ROOT}`
substitution. The referenced `.mcp.json` has this shape:
```json
{
"mcpServers": {
"example": {
"type": "stdio",
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/mcp/server.js"]
}
}
}
```
## Validation notes
- `name` must be a non-empty string; the folder name must match it.
- `version`, when present, must be strict semver.
- `interface.logo` and any declared component path must point to real files/directories inside
the plugin.
- Sub-skill `SKILL.md` files must have non-empty `name` and `description` frontmatter.
- The manifest must not contain `[TODO: ...]` placeholders.
Run `scripts/validate_plugin.py <plugin-path>` before handing back a generated plugin.
#!/usr/bin/env python3
"""Scaffold a Dim plugin directory with a `.claude-plugin/plugin.json` manifest.
Dim discovers plugins from ``~/.agents/plugins/<name>/``. The default parent is
``~/.agents/plugins`` so a scaffolded plugin is live on the next session. Pass ``--path`` to
build a plugin elsewhere (e.g. a git repo to share).
"""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
from typing import Any
MAX_PLUGIN_NAME_LENGTH = 64
DEFAULT_PLUGIN_PARENT = Path.home() / ".agents" / "plugins"
MANIFEST_DIR = ".claude-plugin"
MANIFEST_FILE = "plugin.json"
def normalize_plugin_name(plugin_name: str) -> str:
"""Normalize a plugin name to lowercase hyphen-case."""
normalized = plugin_name.strip().lower()
normalized = re.sub(r"[^a-z0-9]+", "-", normalized)
normalized = normalized.strip("-")
normalized = re.sub(r"-{2,}", "-", normalized)
return normalized
def validate_plugin_name(plugin_name: str) -> None:
if not plugin_name:
raise ValueError("Plugin name must include at least one letter or digit.")
if len(plugin_name) > MAX_PLUGIN_NAME_LENGTH:
raise ValueError(
f"Plugin name '{plugin_name}' is too long ({len(plugin_name)} characters). "
f"Maximum is {MAX_PLUGIN_NAME_LENGTH} characters."
)
def display_name_from_plugin_name(plugin_name: str) -> str:
return " ".join(part.capitalize() for part in re.split(r"[-_]+", plugin_name))
def build_plugin_json(
plugin_name: str,
*,
with_skills: bool,
with_hooks: bool,
with_mcp: bool,
) -> dict[str, Any]:
"""Build the manifest Dim reads. Only the fields Dim parses are written."""
display_name = display_name_from_plugin_name(plugin_name)
payload: dict[str, Any] = {
"name": plugin_name,
"version": "0.1.0",
"description": f"{display_name} plugin",
"interface": {
"displayName": display_name,
"shortDescription": f"Use {display_name} in Dim.",
},
}
# Component paths are added only when their companion files are created, so the manifest
# never points at something that does not exist.
if with_skills:
payload["skills"] = "./skills/"
if with_hooks:
payload["hooks"] = "./hooks.json"
if with_mcp:
payload["mcpServers"] = "./.mcp.json"
return payload
def build_starter_skill(plugin_name: str) -> str:
"""A minimal, valid starter skill so the plugin contributes something usable."""
display_name = display_name_from_plugin_name(plugin_name)
return (
"---\n"
f"name: {plugin_name}\n"
f"description: {display_name} starter skill. Replace this description with the real "
f"trigger so Dim knows when to use {display_name}.\n"
"---\n\n"
f"# {display_name}\n\n"
"Describe what this skill does and the steps the assistant should follow when it is "
"invoked. Keep instructions concrete and task-focused.\n"
)
def write_json(path: Path, data: dict, force: bool) -> None:
if path.exists() and not force:
raise FileExistsError(f"{path} already exists. Use --force to overwrite.")
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as handle:
json.dump(data, handle, indent=2)
handle.write("\n")
def write_text(path: Path, text: str, force: bool) -> None:
if path.exists() and not force:
return
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Create a Dim plugin skeleton with a validation-ready plugin.json."
)
parser.add_argument("plugin_name")
parser.add_argument(
"--path",
default=str(DEFAULT_PLUGIN_PARENT),
help=(
"Parent directory for the plugin (defaults to ~/.agents/plugins, where Dim "
"discovers plugins). Pass another directory to build a plugin for git sharing."
),
)
parser.add_argument(
"--with-skills",
action="store_true",
help="Create skills/<name>/SKILL.md starter skill and set manifest `skills`.",
)
parser.add_argument(
"--with-hooks",
action="store_true",
help="Create hooks.json and set manifest `hooks`.",
)
parser.add_argument(
"--with-mcp",
action="store_true",
help="Create .mcp.json and set manifest `mcpServers`.",
)
parser.add_argument(
"--with-assets",
action="store_true",
help="Create an assets/ directory for icons and logos.",
)
parser.add_argument("--force", action="store_true", help="Overwrite existing files")
return parser.parse_args()
def main() -> None:
args = parse_args()
raw_plugin_name = args.plugin_name
plugin_name = normalize_plugin_name(raw_plugin_name)
if plugin_name != raw_plugin_name:
print(f"Note: Normalized plugin name from '{raw_plugin_name}' to '{plugin_name}'.")
validate_plugin_name(plugin_name)
plugin_root = Path(args.path).expanduser().resolve() / plugin_name
plugin_root.mkdir(parents=True, exist_ok=True)
manifest_path = plugin_root / MANIFEST_DIR / MANIFEST_FILE
write_json(
manifest_path,
build_plugin_json(
plugin_name,
with_skills=args.with_skills,
with_hooks=args.with_hooks,
with_mcp=args.with_mcp,
),
args.force,
)
if args.with_skills:
write_text(
plugin_root / "skills" / plugin_name / "SKILL.md",
build_starter_skill(plugin_name),
args.force,
)
if args.with_hooks:
# Empty hooks map; fill events with commands that use ${CLAUDE_PLUGIN_ROOT} paths.
write_json(plugin_root / "hooks.json", {"hooks": {}}, args.force)
if args.with_mcp:
write_json(plugin_root / ".mcp.json", {"mcpServers": {}}, args.force)
if args.with_assets:
(plugin_root / "assets").mkdir(parents=True, exist_ok=True)
print(f"Created plugin scaffold: {plugin_root}")
print(f"plugin manifest: {manifest_path}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Validate a generated Dim plugin against the manifest contract Dim actually reads.
Dim recognizes a plugin by ``.claude-plugin/plugin.json`` and parses a small set of fields:
``name`` (required), ``version``, ``description``, ``interface.{displayName,shortDescription,
logo}``, and the component paths ``skills`` / ``hooks`` / ``mcpServers``. Unknown fields are
ignored by Dim, so they are not rejected here. This validator also rejects leftover
``[TODO: ...]`` placeholders and checks that declared component paths exist.
"""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path, PurePosixPath
from typing import Any
TODO_MARKER = "[TODO:"
SEMVER_RE = re.compile(
r"^(0|[1-9]\d*)\."
r"(0|[1-9]\d*)\."
r"(0|[1-9]\d*)"
r"(?:-(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\."
r"(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*)?"
r"(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$"
)
MANIFEST_REL = ".claude-plugin/plugin.json"
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Validate a local Dim plugin.")
parser.add_argument("plugin_path", help="Path to the plugin root directory")
return parser.parse_args()
def main() -> None:
args = parse_args()
plugin_root = Path(args.plugin_path).expanduser().resolve()
errors = validate_plugin(plugin_root)
if errors:
print("Plugin validation failed:")
for error in errors:
print(f"- {error}")
raise SystemExit(1)
print(f"Plugin validation passed: {plugin_root}")
def validate_plugin(plugin_root: Path) -> list[str]:
errors: list[str] = []
manifest_path = plugin_root / ".claude-plugin" / "plugin.json"
manifest = load_json_object(manifest_path, errors)
if manifest is None:
return errors
reject_todo_markers(manifest, "$", errors)
validate_manifest(plugin_root, manifest, errors)
return errors
def load_json_object(path: Path, errors: list[str]) -> dict[str, Any] | None:
if not path.is_file():
errors.append(f"missing `{MANIFEST_REL}`")
return None
try:
payload = json.loads(path.read_text(encoding="utf-8"))
except OSError:
errors.append(f"unable to read `{MANIFEST_REL}`")
return None
except json.JSONDecodeError:
errors.append(f"`{MANIFEST_REL}` must be valid JSON")
return None
if not isinstance(payload, dict):
errors.append(f"`{MANIFEST_REL}` must contain a JSON object")
return None
return payload
def reject_todo_markers(value: Any, path: str, errors: list[str]) -> None:
if isinstance(value, str):
if TODO_MARKER in value:
errors.append(f"{path} still contains a `[TODO: ...]` placeholder")
return
if isinstance(value, list):
for index, item in enumerate(value):
reject_todo_markers(item, f"{path}[{index}]", errors)
return
if isinstance(value, dict):
for key, item in value.items():
reject_todo_markers(item, f"{path}.{key}", errors)
def validate_manifest(plugin_root: Path, manifest: dict[str, Any], errors: list[str]) -> None:
require_non_empty_string(manifest, "name", errors)
version = manifest.get("version")
if version is not None:
if not isinstance(version, str) or SEMVER_RE.fullmatch(version) is None:
errors.append("plugin.json field `version` must be strict semver")
validate_optional_non_empty_string(manifest, "description", errors)
interface = manifest.get("interface")
if interface is not None:
if not isinstance(interface, dict):
errors.append("plugin.json field `interface` must be an object")
else:
validate_optional_non_empty_string(interface, "displayName", errors, prefix="interface")
validate_optional_non_empty_string(interface, "shortDescription", errors, prefix="interface")
validate_optional_asset_path(plugin_root, interface, "logo", errors)
validate_component_path(plugin_root, manifest, "skills", errors)
validate_component_path(plugin_root, manifest, "hooks", errors)
validate_mcp_servers(plugin_root, manifest, errors)
validate_skill_manifests(plugin_root, errors)
def require_non_empty_string(
payload: dict[str, Any],
key: str,
errors: list[str],
*,
prefix: str | None = None,
) -> None:
value = payload.get(key)
field = f"{prefix}.{key}" if prefix is not None else key
if not isinstance(value, str) or not value.strip():
errors.append(f"plugin.json field `{field}` must be a non-empty string")
def validate_optional_non_empty_string(
payload: dict[str, Any],
key: str,
errors: list[str],
*,
prefix: str | None = None,
) -> None:
value = payload.get(key)
if value is None:
return
field = f"{prefix}.{key}" if prefix is not None else key
if not isinstance(value, str) or not value.strip():
errors.append(f"plugin.json field `{field}` must be a non-empty string")
def validate_component_path(
plugin_root: Path,
manifest: dict[str, Any],
key: str,
errors: list[str],
) -> None:
"""`skills` resolves to a directory; `hooks` resolves to a file. Both must exist."""
value = manifest.get(key)
if value is None:
return
if not isinstance(value, str) or not value.strip():
errors.append(f"plugin.json field `{key}` must be a non-empty relative path")
return
resolved = resolve_inside(plugin_root, value, key, errors)
if resolved is None:
return
if key == "skills" and not resolved.is_dir():
errors.append(f"plugin.json field `skills` points to a missing directory")
if key == "hooks" and not resolved.is_file():
errors.append(f"plugin.json field `hooks` points to a missing file")
def validate_mcp_servers(plugin_root: Path, manifest: dict[str, Any], errors: list[str]) -> None:
value = manifest.get("mcpServers")
if value is None:
return
# Runtime only loads `mcpServers` as a path to an `.mcp.json` file; an inline
# object is silently ignored by the loader, so reject it here too.
if isinstance(value, str) and value.strip():
resolved = resolve_inside(plugin_root, value, "mcpServers", errors)
if resolved is not None and not resolved.is_file():
errors.append("plugin.json field `mcpServers` points to a missing file")
return
errors.append("plugin.json field `mcpServers` must be a relative path string to an `.mcp.json` file")
def validate_optional_asset_path(
plugin_root: Path,
payload: dict[str, Any],
key: str,
errors: list[str],
) -> None:
value = payload.get(key)
if value is None:
return
if not isinstance(value, str) or not value.strip():
errors.append(f"plugin.json field `interface.{key}` must be a non-empty relative path")
return
resolved = resolve_inside(plugin_root, value, f"interface.{key}", errors)
if resolved is not None and not resolved.is_file():
errors.append(f"plugin.json field `interface.{key}` points to a missing file")
def resolve_inside(
plugin_root: Path,
raw_path: str,
field: str,
errors: list[str],
) -> Path | None:
"""Resolve a relative path and confirm it stays inside the plugin root."""
candidate = PurePosixPath(raw_path.replace("\\", "/"))
if candidate.is_absolute() or ".." in candidate.parts:
errors.append(f"plugin.json field `{field}` must stay inside the plugin directory")
return None
resolved = (plugin_root / candidate.as_posix()).resolve()
if not resolved.is_relative_to(plugin_root.resolve()):
errors.append(f"plugin.json field `{field}` must stay inside the plugin directory")
return None
return resolved
def validate_skill_manifests(plugin_root: Path, errors: list[str]) -> None:
skills_root = plugin_root / "skills"
if not skills_root.is_dir():
return
for skill_root in sorted(skills_root.iterdir(), key=lambda path: path.name):
if skill_root.name.startswith(".") or not skill_root.is_dir():
continue
validate_skill_manifest(skill_root, errors)
def validate_skill_manifest(skill_root: Path, errors: list[str]) -> None:
skill_md_path = skill_root / "SKILL.md"
if not skill_md_path.is_file():
errors.append(f"skill `{skill_root.name}` is missing `SKILL.md`")
return
try:
contents = skill_md_path.read_text(encoding="utf-8")
except OSError:
errors.append(f"unable to read skill `{skill_root.name}`")
return
frontmatter = parse_frontmatter(contents)
if frontmatter is None:
errors.append(f"skill `{skill_root.name}` must start with closed YAML frontmatter")
return
if not frontmatter_field(frontmatter, "name"):
errors.append(f"skill `{skill_root.name}` frontmatter field `name` must be non-empty")
if not frontmatter_field(frontmatter, "description"):
errors.append(f"skill `{skill_root.name}` frontmatter field `description` must be non-empty")
def parse_frontmatter(contents: str) -> str | None:
"""Return the raw frontmatter block, or None when it is missing/unclosed.
Parsed by hand (no yaml dependency) — only the closed `---` fenced block is needed.
"""
if not contents.startswith("---\n"):
return None
end = contents.find("\n---", 4)
if end == -1:
return None
return contents[4:end]
def frontmatter_field(frontmatter: str, key: str) -> bool:
"""True when `key:` appears with a non-empty value on its own line."""
pattern = re.compile(rf"^{re.escape(key)}:\s*(.+?)\s*$", re.MULTILINE)
match = pattern.search(frontmatter)
return bool(match and match.group(1).strip())
if __name__ == "__main__":
main()
---
name: plugin-creator
description: Create and scaffold Dim plugin directories with a required `.claude-plugin/plugin.json` manifest, optional skills/hooks/MCP components, and valid manifest defaults. Use when the user wants to create a new Dim plugin, add skills/hooks/MCP servers to a plugin, validate a plugin before sharing, or set up a plugin for local development under `~/.agents/plugins`.
metadata:
short-description: Create or scaffold a Dim plugin
---
# Plugin Creator
Scaffold a Dim plugin: a directory with a `.claude-plugin/plugin.json` manifest plus
optional `skills/`, a `hooks` file, and an `.mcp.json`. Dim discovers plugins from
`~/.agents/plugins/<name>/`: each plugin folder is self-describing, so placing one under
`~/.agents/plugins` makes its skills, hooks, and MCP servers load on the next session.
## Quick Start
1. Scaffold the plugin. By default it is created live under `~/.agents/plugins/<name>/`,
so a new session picks it up immediately.
```bash
# Names are normalized to lower-case hyphen-case (<= 64 chars). The folder name and the
# manifest `name` are always identical. Run from the skill root (the directory holding
# this SKILL.md).
python3 scripts/create_basic_plugin.py <plugin-name>
```
2. Edit `<plugin-path>/.claude-plugin/plugin.json` when the request gives specific
metadata (description, display name, version). The scaffold starts with valid defaults
and must not contain `[TODO: ...]` placeholders.
3. Add the components the plugin needs:
```bash
python3 scripts/create_basic_plugin.py <plugin-name> \
--with-skills --with-hooks --with-mcp --with-assets
```
- `--with-skills` creates `skills/<plugin-name>/SKILL.md`, a working starter skill, and
sets manifest `"skills": "./skills/"`.
- `--with-hooks` creates `hooks.json` and sets manifest `"hooks": "./hooks.json"`.
- `--with-mcp` creates `.mcp.json` and sets manifest `"mcpServers": "./.mcp.json"`.
- `--with-assets` creates an `assets/` folder for icons/logos.
4. Build a plugin for git distribution instead of local dev with `--path`:
```bash
# Create under a repo or scratch directory you will push to git.
python3 scripts/create_basic_plugin.py <plugin-name> --path <parent-directory>
```
5. Validate before handing back or sharing:
```bash
python3 scripts/validate_plugin.py <plugin-path>
```
6. **Tell the user to start a new session/thread.** Dim loads a plugin's skills, hooks, and
MCP servers when a session starts, so a plugin created or changed in the *current* session
only becomes usable in the *next* one. If you scaffolded the plugin mid-conversation, the
skill will not be callable here yet — the user must open a new session to use it.
## What this skill creates
- Plugin root at `<parent-directory>/<plugin-name>/` (default parent `~/.agents/plugins`).
- Always `<plugin-root>/.claude-plugin/plugin.json` with the manifest shape Dim reads.
- `<plugin-name>` is normalized:
- `My Plugin` → `my-plugin`
- `My--Plugin` → `my-plugin`
- underscores, spaces, and punctuation become `-`; consecutive hyphens collapse; result is
lower-case.
- Optional, created only when requested:
- `skills/<plugin-name>/SKILL.md` (a starter skill)
- `hooks.json`
- `.mcp.json`
- `assets/`
## Manifest
Dim reads a small set of fields from `plugin.json`. The scaffold writes valid defaults for
all of them:
```json
{
"name": "my-plugin",
"version": "0.1.0",
"description": "My Plugin plugin",
"interface": {
"displayName": "My Plugin",
"shortDescription": "Use My Plugin in Dim."
},
"skills": "./skills/",
"hooks": "./hooks.json",
"mcpServers": "./.mcp.json"
}
```
Component paths (`skills`, `hooks`, `mcpServers`) appear only when their files exist. See
`references/plugin-json-spec.md` for the full field guide and the exact sample.
## Components
- **Skills** — directories under `skills/<name>/` each containing a `SKILL.md` with valid
frontmatter (`name`, `description`). Dim discovers them automatically and loads them into
the system prompt.
- **Hooks** — a JSON file (manifest `hooks` field) mapping lifecycle events to commands. Use
`${CLAUDE_PLUGIN_ROOT}` in command paths; Dim substitutes the plugin's absolute root at
runtime, e.g. `node "${CLAUDE_PLUGIN_ROOT}/hooks/on-start.js"`.
- **MCP** — an `.mcp.json` with an `mcpServers` object. Server ids are namespaced as
`plugin:<plugin-name>/<serverId>`. Command paths support the same `${CLAUDE_PLUGIN_ROOT}`
substitution.
## Required behavior
- The outer folder name and `plugin.json` `"name"` are always the same normalized name.
- Keep `.claude-plugin/plugin.json` present; never remove the required manifest.
- Do not leave `[TODO: ...]` placeholders in the manifest.
- Add a component path to the manifest only when its file actually exists. Keep `skills`,
`hooks`, and `mcpServers` out of the manifest when their companion files were not created.
- Use `--force` only when overwriting an existing plugin path is intentional.
- After creating or modifying a plugin, end by telling the user to start a new session/thread.
The current session loaded its skills, hooks, and MCP servers at startup and will not pick up
the new ones until a fresh session — do not claim the new skill is usable in this session.
## Install and update
For the full local-development loop, the git-sharing path, and component toggling, see
`references/installing-and-updating.md`. In short:
- **Local dev** — scaffold into `~/.agents/plugins/<name>/`, then start a new session. Dim
re-scans the directory and loads the plugin's skills, hooks, and MCP servers. Editing files
in place and starting another session picks up the changes.
- **Share** — push the plugin directory to a git repo, then in the Dim desktop app open
Plugins → Add plugin and paste the git URL (`owner/repo` or a full URL). Dim reads the
manifest and installs the plugin into `~/.agents/plugins`.
## Validation
After editing this `SKILL.md`, run:
```bash
python3 ../skill-creator/scripts/quick_validate.py .
```
Before handing back a generated plugin, run:
```bash
python3 scripts/validate_plugin.py <plugin-path>
```
+1
-1
{
"name": "dimcode-linux-arm64",
"version": "0.2.11",
"version": "0.2.12-beta.0",
"description": "dimcode binary for Linux ARM64",

@@ -5,0 +5,0 @@ "os": [

Sorry, the diff of this file is not supported yet