DevPlace CI / test (push) Failing after 31m42s
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>
146 lines
6.7 KiB
Python
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"},
|
|
),
|
|
],
|
|
}
|