Files
devplacepy/devplacepy/docs_api/groups/skills.py
T
retoorandClaude Sonnet 5.5 f55c7ee5f4
DevPlace CI / test (push) Failing after 31m42s
Add community node protocol spec, Agent Skills, and make the failing tests hermetic
Community nodes (Phase 1 of plan.md): the protocol specification in
services/nodes/spec.py as the single source of truth, GET /nodes/spec.json,
a generated protocol docs page, the Community nodes API docs group, and the
implementation plan plus the residential proxy research. The no-open-proxy rule
is enforced in the spec (open_proxy cannot be set).

Agent Skills: register the Skills API docs group, cascade skills on account
deletion, add the nested CLAUDE.md, README and routes docs, and cover the
parser, the fetcher (end to end against a mock origin), the data layer and the
routes with 73 unit and 16 api tests.

Tests made hermetic with the repo's existing patterns instead of ambient
environment: embeddings tests set OPENROUTER_API_KEY via monkeypatch, the
workspace tests install FakeBackend via runtime.set_backend, and the skills
install tests use run_async. DockerCliBackend now returns 127 (command not
found) when the CLI is missing instead of raising FileNotFoundError, so a host
without docker answers with a clean error rather than a 500. The Providers tab
empty state no longer names the Model routes tab.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 07:13:25 +02:00

146 lines
6.7 KiB
Python

# retoor <retoor@molodetz.nl>
from .._shared import endpoint, field
SKILL_SAMPLE = {
"uid": "SKILL_UID",
"name": "seriousdevelopment",
"description": "Scaffold a new project or upgrade an existing one to a spec-first engineering bar.",
"enabled": True,
"source_url": "https://static.molodetz.nl/seriousdevelopment/SKILL.md",
"license": "",
"compatibility": "",
"allowed_tools": "",
"sha256": "SHA256_OF_BODY",
"file_count": 1,
"fetched_at": "2026-01-01T00:00:00+00:00",
"created_at": "2026-01-01T00:00:00+00:00",
"updated_at": "2026-01-01T00:00:00+00:00",
}
SKILL_DETAIL_SAMPLE = {
**SKILL_SAMPLE,
"metadata": {},
"body": "# Serious Development\n\nOne method for building software seriously.",
"files": {"assets/templates/SPEC.md": "# SPEC"},
"skipped": [{"path": "assets/img/hero.png", "reason": "binary or unsupported file type; only text files are stored"}],
"warnings": [],
}
GROUP = {
"slug": "skills",
"title": "Skills",
"intro": """
# Skills
A skill is an [Agent Skills](https://agentskills.io) package: a `SKILL.md` file (YAML frontmatter
with a `name` and a `description`, then a markdown body) plus optional text files under
`scripts/`, `references/` and `assets/`. Install one from a URL and Devii can use it.
- **Private.** Your skills are visible only to you. An administrator may delete any skill.
- **On demand.** Only the name and description of your *enabled* skills are added to Devii's
system prompt. Devii loads the full body and bundled files with `get_skill` when a request
matches. Installing, enabling, disabling or deleting a skill takes effect on the next turn.
- **Untrusted text.** A skill is third-party text. It guides Devii within what you asked for; it
never bypasses a confirmation, never grants a tool (`allowed-tools` is stored, not honoured),
and its scripts are stored as text and never executed automatically.
- **Install.** Pass a direct `.../SKILL.md` URL, or a directory URL (redirects to an index page
are reduced to their directory and `SKILL.md` is fetched from there). Bundled files are fetched
only when the body references them by relative path under `scripts/`, `references/` or
`assets/`, on the same origin, as text, within the size and count limits. Anything skipped is
reported with a reason. Installing the same `name` again updates it in place.
Every endpoint follows the shared [Conventions & Errors](/docs/conventions.html) (auth, content
negotiation, pagination, status codes); see [Authentication](/docs/authentication.html) for the
four ways to sign requests.
""",
"endpoints": [
endpoint(
id="skills-list",
method="GET",
path="/skills",
title="List your skills",
summary="Your installed skills. Returns an HTML page, or JSON with `Accept: application/json`.",
auth="user",
interactive=True,
sample_response={
"items": [SKILL_SAMPLE],
"skill_total": 1,
"skill_limit": 50,
"install_error": None,
},
),
endpoint(
id="skills-detail",
method="GET",
path="/skills/{name}",
title="Get a skill",
summary="One of your skills with its full body, bundled files and the files that were skipped.",
auth="user",
interactive=True,
params=[
field("name", "path", "string", True, "seriousdevelopment", "Skill name."),
],
sample_response={"skill": SKILL_DETAIL_SAMPLE},
notes=["Returns `404` when you have no skill with that name; skills of other users are never visible."],
),
endpoint(
id="skills-install",
method="POST",
path="/skills/install",
title="Install a skill",
summary="Fetch a skill from a URL and store it privately. Re-installing a name updates it in place.",
auth="user",
ajax=True,
encoding="form",
params=[
field(
"url",
"form",
"string",
True,
"https://static.molodetz.nl/seriousdevelopment",
"A direct URL ending in SKILL.md, or a directory URL that resolves to one.",
),
],
sample_response={"action": "install", "skill": SKILL_DETAIL_SAMPLE},
notes=[
"`action` is `install` for a new skill and `update` when the name already existed.",
"Fails with `400` and a reason for a missing or invalid frontmatter, an invalid name, a blocked address, an unreachable URL, or when you already have the maximum number of skills.",
"Without `X-Requested-With: fetch` or `Accept: application/json` the browser is redirected to `/skills`.",
],
),
endpoint(
id="skills-enabled",
method="POST",
path="/skills/{name}/enabled",
title="Enable or disable a skill",
summary="Only enabled skills are listed in Devii's system prompt. Omit `enabled` to flip the current state.",
auth="user",
ajax=True,
encoding="form",
params=[
field("name", "path", "string", True, "seriousdevelopment", "Skill name."),
field("enabled", "form", "boolean", False, "true", "`true` to enable, `false` to disable; omit to toggle."),
],
sample_response={"name": "seriousdevelopment", "enabled": False},
),
endpoint(
id="skills-delete",
method="POST",
path="/skills/{name}/delete",
title="Delete a skill",
summary="Remove one of your skills. An administrator may remove another user's skill by passing `owner`.",
auth="user",
ajax=True,
destructive=True,
encoding="form",
params=[
field("name", "path", "string", True, "seriousdevelopment", "Skill name."),
field("owner", "form", "string", False, "", "Administrators only: username of the skill's owner."),
],
sample_response={"deleted": True, "name": "seriousdevelopment"},
),
],
}