Source profileQuality 91/100Review permissions

clacky-ai/openclacky/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md

ext-develop

Build, debug, or publish an OpenClacky extension — scaffold a new one from an idea, fix a broken/invisible panel/api/skill/agent, or ship it to the marketplace. Trigger on create/start extension, plugin, panel, ext verify error, "won't load", "not showing up", publish/ship/unpublish an extension.

Source repository stars
1,177
Declared platforms
0
Static risk flags
4
Last source update
2026-08-28
Source checked
2026-08-28

Decision brief

What it does: where it fits

Build an OpenClacky extension end to end — scaffold, edit, verify, hot-reload, and (only when asked) publish. Prefer editing real files and verifying over describing.

Best for

    Not for

    • Tasks that require unconfirmed production actions or broad system permissions.
    • Environments where the pinned source and install steps cannot be inspected.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/clacky-ai/openclacky --skill "lib/clacky/default_extensions/ext-studio/skills/ext-develop"
    Safe inspection promptEditorial

    Inspect the Agent Skill "ext-develop" from https://github.com/clacky-ai/openclacky/blob/781ec56c49849bed15e0d6e4584af4f0f537daf9/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md at commit 781ec56c49849bed15e0d6e4584af4f0f537daf9. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      The extension model (ground truth)

      An extension is one directory with a single ext.yml manifest declaring contributes:. Nothing is nested — units reference each other by id. It survives gem update and never requires editing gem source.

      builtin — bundled in the gem (defaultextensions/)installed — /.clacky/ext/installed// (from ext install)local — /.clacky/ext/local// (where users develop; ext new lands here)
    2. 02

      Hard rules — never break these

      ❌ Never edit the gem source. Do NOT bundle show openclacky and change files

      ❌ Never edit the gem source. Do NOT bundle show openclacky and change files❌ Never restart the server to apply a change. Hot reload is per-request —❌ Never declare success on "it should work." A task is done only when
    3. 03

      Which section do I need?

      Pick exactly ONE and follow it top to bottom. Don't blend the three.

      Starting a new extension from an idea → Scaffold.Something is broken, verify errors, or a change didn't show up → Debug & verify.The user explicitly wants to share/ship it to others → Publish (optional; skip
    4. 04

      Reference: the contracts

      Read the relevant reference doc with webfetch before writing code — don't guess field names, hook events, adapter methods, or the Clacky.ext WebUI contract. These docs are long (well over the default cap); pass maxlength: 20000 so you get the whole page in one fetch instead of a…

      Extension system overview → https://www.openclacky.com/docs/extension-systemext.yml manifest — every field (names, avatar, titlezh, order, …) → https://www.openclacky.com/docs/ext-manifestPanels (WebUI) → https://www.openclacky.com/docs/extend-webui
    5. 05

      Authoritative documentation

      Extension system overview → https://www.openclacky.com/docs/extension-system

      Extension system overview → https://www.openclacky.com/docs/extension-systemext.yml manifest — every field (names, avatar, titlezh, order, …) → https://www.openclacky.com/docs/ext-manifestPanels (WebUI) → https://www.openclacky.com/docs/extend-webui

    Permission review

    Static risk signals and limitations

    Network access

    medium · line 162

    The documentation includes network, browsing, or remote request actions.

    A panel can also `fetch("/api/...")` the host's own REST endpoints directly (same origin,

    Writes files

    medium · line 165

    The documentation asks the agent to create, modify, or delete local files.

    done" (e.g. "delete a file but keep it recoverable"), check whether the host already

    Network access

    medium · line 214

    The documentation includes network, browsing, or remote request actions.

    A panel can also `fetch("/api/projects")` directly (same-origin, no-auth; see

    Runs scripts

    medium · line 401

    The documentation asks the agent to run terminal commands or scripts.

    ruby test/handler_test.rb

    Reads files

    low · line 463

    The documentation asks the agent to read local files, directories, or repositories.

    YAML. **Do:** open the `file` path in the error; make sure it exists and the path in

    Writes files

    medium · line 480

    The documentation asks the agent to create, modify, or delete local files.

    patch: `target file fingerprint on_mismatch`

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score91/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars1,177SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    clacky-ai/openclacky
    Skill path
    lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md
    Commit
    781ec56c49849bed15e0d6e4584af4f0f537daf9
    License
    MIT
    Collected
    2026-08-28
    Default branch
    main
    View the original SKILL.md

    Extension Development

    Build an OpenClacky extension end to end — scaffold, edit, verify, hot-reload, and (only when asked) publish. Prefer editing real files and verifying over describing.

    The extension model (ground truth)

    An extension is one directory with a single ext.yml manifest declaring contributes:. Nothing is nested — units reference each other by id. It survives gem update and never requires editing gem source.

    Three layers, override precedence local > installed > builtin:

    • builtin — bundled in the gem (default_extensions/)
    • installed~/.clacky/ext/installed/<id>/ (from ext install)
    • local~/.clacky/ext/local/<id>/ (where users develop; ext new lands here)

    Eight contributes: types (use one, several, or all):

    • panels — WebUI panels (a view.js, no build step, no React, no iframe)
    • api — one backend file api/handler.rb, mounted at /api/ext/<id>/
    • skills — a SKILL.md under skills/<id>/ (prompt-only capability)
    • agents — a system_prompt.md; can reference panels: [id] and skills: [id]; hidden: true hides from the new-session picker
    • channels — an IM adapter
    • patches — monkey-patch a real class (advanced, supply-chain risk)
    • hooks — lifecycle hooks like before_tool_use (advanced)
    • tools — a custom agent tool (a Clacky::Tools::Base subclass in tools/<id>.rb)

    Hot reload is per-request: after editing view.js, handler.rb, or a SKILL.md, the user just reloads the WebUI page — no server restart. Editing ext.yml also applies on the next load.

    Hard rules — never break these

    • Never edit the gem source. Do NOT bundle show openclacky and change files in there. Everything lives in ~/.clacky/ext/local/<id>/ and survives gem update.
    • Never restart the server to apply a change. Hot reload is per-request — the user just reloads the WebUI page. If you're telling them to restart, you're wrong.
    • Never declare success on "it should work." A task is done only when clacky ext verify is clean AND the user reloaded and saw it work. Run verify — don't imagine its output.
    • Never add patches: or hooks: unless the user explicitly asks. They run arbitrary Ruby and carry supply-chain risk. Default to panels/api/skills/agents.
    • Never publish on your own initiative. Publishing is opt-in — see Publish.
    • Never write window.Sessions / "Sessions" in window in view.js. Host services are const bindings, not window properties — such checks return undefined/false even when loaded. Always use Clacky.Sessions.* etc.
    • Always work in the local layer (~/.clacky/ext/local/<id>/). ext new lands there; that's the only layer you edit.

    Which section do I need?

    Pick exactly ONE and follow it top to bottom. Don't blend the three.

    • Starting a new extension from an idea → Scaffold.
    • Something is broken, verify errors, or a change didn't show up → Debug & verify.
    • The user explicitly wants to share/ship it to others → Publish (optional; skip it entirely for extensions the user only runs themselves).

    Reference: the contracts is not a path — it's the field/slot/event/API ground truth you consult from whichever path you're on.


    Reference: the contracts

    Read the relevant reference doc with web_fetch before writing code — don't guess field names, hook events, adapter methods, or the Clacky.ext WebUI contract. These docs are long (well over the default cap); pass max_length: 20000 so you get the whole page in one fetch instead of a truncated head full of nav chrome.

    Authoritative documentation

    WebUI panels: the Clacky.ext contract

    A panel is a plain view.js (no build step, no React, no iframe). It reaches the host only through window.Clacky — everything else on the page is off-limits. There are exactly three capabilities:

    Clacky.ext.ui.mount(slot, spec, opts)     // inject UI into a named slot
    Clacky.ext.subscribe(event, handler)      // observe store events + live session events (read-only)
    Clacky.ext.api.register(name, fn)         // expose a named data source; api.resolve(name)
    

    subscribe(event, handler) - two event families: (1) host store events (skills:changed, tasks:changed, profile:changed, …) and (2) live session events mirrored from the WebSocket stream — conversation (session:assistant-message, session:tool-call, session:tool-result), status/errors (session:error, session:warning, session:update), lifecycle (session:renamed, session:deleted, …). Payload is { sessionId, ...wsFields }. Full list in the "Core Events" section of the extend-webui docs. Handlers are read-only.

    ui.mount(slot, spec, opts)spec is either (container, ctx, runtime) => … or { create?, render }. The render function:

    • gets a host-owned container DOM element — append into it, or return a Node / HTML string and the host appends for you;
    • returning a function registers it as a teardown callback;
    • returning null/undefined renders nothing (safe) — a red "crashed" placeholder means the render threw; the #1 cause is a wrong signature shifting every argument (e.g. (ctx) => … instead of (container, ctx) => …). Match the signature exactly.

    ctx carries { sessionId, agentProfile }. opts: order (lower renders first, default 100), tab: { id, label, badge? } (required for tabbed slots — session.aside is tabbed), agents: [profile] (override auto scope), workspace: id (for nav items).

    Valid slot names (mounting into any other name silently renders nothing, warned once):

    header.left  header.right
    sidebar.nav.top  sidebar.nav  sidebar.nav.bottom  sidebar.footer
    main.workspace
    session.banner  session.composer  session.aside      (session.aside is tabbed)
    settings.tabs  settings.body
    

    Agent scope is automatic: mounts into session.* / settings.* slots only show for the panel's owning agent(s); all other slots (sidebar.*, header.*, main.workspace) are global chrome. You rarely set agents: by hand.

    Per-session state — for session.aside/banner/composer, pass { create(ctx), render }: create runs once per session and returns a runtime (put timers/recorders/subscriptions there), render(container, ctx, runtime) runs on each show, and runtime.dispose() runs when the session leaves. State survives tab switches; use this instead of module globals.

    Full-page workspace - Clacky.ext.ui.registerWorkspace(id, { title, render }) takes over the main area with its own #ext/<id> URL; open it with Clacky.ext.ui.openWorkspace(id), typically from a sidebar.nav item mounted with opts.workspace: id.

    Sidebar nav items - render the host nav-item structure (div.task-item.task-item-summary > div.task-row > svg.task-icon + div.task-info > span.task-name - copy navRow() from the full scaffold template) with opts.workspace: id so the item inherits theme styling and the Router's active highlight.

    Safe mode - ?pure=true makes the whole registry a no-op; never rely on side effects outside these calls.

    Other host services under Clacky.*

    Beyond Clacky.ext, the host exposes stores as properties on window.Clacky. Use them instead of bare globals:

    Clacky.Sessions.on("switched", handler);   // active session store
    Clacky.Router.go("session");                // top-level view routing
    Clacky.Router.navigate("session", { id }); // navigate with params
    Clacky.I18n.t("some.key");                  // translations
    Clacky.Modal.confirm("Delete?");            // dialogs
    Clacky.Modal.toast("Saved", "success");     // toasts (not window.alert)
    Clacky.Auth.passed;                          // auth state
    Clacky.Workspace.list(dir);                 // working-directory files
    Clacky.Skills.list();                       // skill catalog
    Clacky.WS.send({ type: "..." });            // send a WebSocket message to the agent
    
    • Prefer Clacky.Xxx.method(...) — the recommended, forward-stable form. Never test with window.Sessions / "Sessions" in window (see Hard rules).

    A panel can also fetch("/api/...") the host's own REST endpoints directly (same origin, auth is automatic) — sessions, trash/file-recovery, skills, memories, cron, billing, media, and more each have a ready-made endpoint. Before telling a user a feature "can't be done" (e.g. "delete a file but keep it recoverable"), check whether the host already exposes it — web_fetch https://www.openclacky.com/docs/extend-host-api for the callable list. Don't rebuild what the host already provides.

    API backend: the Clacky::ApiExtension contract

    api/handler.rb subclasses Clacky::ApiExtension. Routes mount under /api/ext/<ext_id>/. This base class already wires up auth, JSON envelopes, timeouts, and path params — you only write business logic. Full surface:

    class MyExt < Clacky::ApiExtension
      timeout 30                              # class-wide default (max 600s)
    
      get "/summary" do
        json(count: session_manager.list.size)   # json(key: val) → 200 JSON
      end
    
      post "/items/:id" do                    # :id → params["id"]
        body = json_body                      # parsed request JSON (Hash)
        q    = query["page"]                  # query string params
        File.write(data_path("items", "#{params['id']}.json"), body.to_json)  # persistence
        json({ ok: true }, status: 201)
      end
    
      get "/export", timeout: 60 do
        send_data(bytes, content_type: "text/csv", filename: "out.csv")
      end
    end
    

    Response helpers: json / text(str) / send_data(bytes, content_type:, filename:) / error!(msg, status:). Request: params (path), query, json_body, req.

    • data_path(*parts) is the official way to persist user data — it returns a path under ~/.clacky/ext-data/<id>/, outside the package tree, so it survives reloads, gem update, and even uninstall/reinstall (uninstall keeps it by default; the user opts in to deleting it via a checkbox). Never write user data into the extension's code dir (ext_dir / File.join(ext_dir, ...)) — uninstall deletes the whole package, so anything there is lost. Package-internal writes are only for disposable caches.
    • Host context (white-listed): session_manager, registry, agent_config, config (from ext.yml), logger, ext_id, ext_dir, project_manager.
    • Drive sessions from the backend: create_session(prompt:, profile:, …), submit_task(session_id, prompt), dispatch_to_session(session_id, prompt) (runs a side task on a fork and returns its reply without touching the conversation).
    • Projects: project_manager.all lists projects, find(id) returns one (or nil), create(name:, working_dir:, …) / update(id, …) / delete(id) mutate. Pass project_id: to create_session to bind a session to a project - its working_dir is inherited (unless overridden) and agent.project_id is persisted. A panel can also fetch("/api/projects") directly (same-origin, no-auth; see Host API).
    • Session source grouping (⚠️ opt-in only): create_session accepts source: "manual" (default) or source: "ext"; anything else is rejected with 400. Use "ext" only when the extension creates sessions the user did not individually ask for (bookkeeping, background workers, one per webhook) and would otherwise flood the list — they collapse under a single "Extensions" sidebar entry with their own 200-session cleanup pool. Default to "manual": a session the user asked for is one they should be able to find, and folded sessions are easy to overlook while the separate pool silently consumes storage until evicted. Sessions bound to a project always show in that project's area regardless of source, and count toward the regular pool rather than the ext one.
    • Public (no-auth) endpoints: call public_endpoint("/path") in the class and set public: true at ext.yml top level — both are required.

    Patches & hooks (advanced — only when asked)

    • Patch (contributes.patches: [{ target, file, fingerprint?, on_mismatch }]): overrides a method via Module#prepend without editing gem source. target is "Clacky::Tools::WebSearch#execute" (# = instance, . = class). fingerprint is a SHA of the original method source; on drift the patch is disabled (on_mismatch: disable, default) or warned (warn).
    • Hook (contributes.hooks: [{ event, file }]): registers a lifecycle callback. Valid event values (exactly these): before_tool_use after_tool_use on_tool_error on_start on_complete on_iteration session_rollback. A before_tool_use hook returning { action: :deny, reason: "…" } blocks the tool call — this is how you audit or gate dangerous commands. Every event passes the agent as its last block arg (|call, result, agent| for after_tool_use); call agent.emit_event("ext.<id>.<event>", persist: true, **data) to push structured events to the frontend. persist: false (default) vanishes on refresh — emit a persist: true terminal event at the end.

    Custom tools

    Tool (contributes.tools: [{ id, file }]): adds a brand-new tool to an agent's schema. The tool is injected only into agents that declare it via tools: [<id>] in the same container's agent spec — an agent can only reference tools from its own container. file points at a Ruby file in the container (tools/<id>.rb) defining Clacky::Tools::<Camelized id> — the file name IS the class-name mapping (tools/hello.rbClacky::Tools::Hello). The class contract is identical to a built-in tool:

    module Clacky
      module Tools
        class Weather < Base
          self.tool_name = "weather"          # the name the model calls
          self.tool_description = "Get current weather for a city."
          self.tool_category = "general"
          self.tool_parameters = {
            type: "object",
            properties: { city: { type: "string", description: "City name" } },
            required: %w[city]
          }
    
          def execute(city:, **)
            { temperature: 22, city: city }
          end
        end
      end
    end
    

    Rules:

    • Instance methods onlyexecute(**args) takes keyword args; working_dir: nil is passed when the agent runs inside a project directory.
    • A broken tool file is logged and skipped at startup — it never blocks the agent.
    • Name collisions with built-ins: the extension tool wins (registered last, same as the local > installed > builtin layer precedence).
    • Prefer a skill when the capability can be composed from existing tools — a tool is only needed for something no built-in tool can do (new API integration, new system-level operation).

    Scaffold

    Turn a plain-language idea into a working skeleton.

    1 — Understand the idea

    Figure out what it should DO and which contributes types it needs. Ask one clarifying question only if genuinely ambiguous. Common mappings:

    User wants to…contributes: field
    Show X in a side panel / add a button / dashboardpanels: (+ api: if it needs a backend or an external service)
    A capability the AI can invoke (summarize, translate, format)skills:
    A specialized assistant with its own personality/toolsagents: (usually bundling its own panels/skills)
    Connect to Slack / an in-house IMchannels:
    Change behavior of a built-in methodpatches:
    Audit / block / observe tool callshooks:
    A brand-new tool the model can call (new API, new system op)tools: (+ agent-side tools: declaration)

    Keep it minimal — most useful extensions are one panel + one handler, or one skill. Do NOT add patches or hooks unless the user explicitly asks; they run arbitrary Ruby and carry supply-chain risk.

    Appearance & naming are manifest fields, not separate features. When the user wants a custom logo/avatar for an agent, a Chinese (or other-language) display name, a panel tab label, or ordering, those are optional keys in ext.yml — e.g. agent avatar: (image path), title / title_zh, description / description_zh, order. Never say it can't be done; set the field and check the full list in the ext.yml manifest doc.

    2 — Generate the skeleton

    Pick a lowercase, hyphenated id derived from the idea (e.g. weather-panel).

    clacky ext new <id>
    

    This creates ~/.clacky/ext/local/<id>/ with a working hello panel + handler:

    • ext.yml — the manifest
    • panels/hello/view.js — a panel that pings the backend
    • api/handler.rb — a Clacky::ApiExtension subclass mounted at /api/ext/<id>/
    • test/handler_test.rb — a runnable minitest example (ruby test/handler_test.rb)

    Use --full only when the user needs the kitchen-sink reference exercising all eight contributes types — it's a lot to read, so prefer the plain scaffold otherwise.

    3 — Reshape to the idea

    Below is a concrete before → after for a tiny "add a note" panel — use it as the shape to copy, not the literal content.

    ext.yml — rename the panel id/view to the feature; add skills:/agents: only if needed:

    contributes:
      api: api/handler.rb
      panels:
        - id: notes                       # was: hello
          view: panels/notes/view.js      # was: panels/hello/view.js
          attach: ["*"]
    

    panels/notes/view.js — keep the Clacky.ext.ui.mount(...) wrapper and host CSS classes; swap the body for the real UI, POST to your own route:

    Clacky.ext.ui.mount("session.aside", function (container, ctx) {
      var el = document.createElement("div");
      el.style.padding = "16px";
      var input = document.createElement("input");
      input.className = "form-input";                 // reuse host theme
      var btn = document.createElement("button");
      btn.className = "btn-primary";
      btn.textContent = "Save note";
      btn.addEventListener("click", async function () {
        await fetch("/api/ext/<id>/notes", {          // relative to your mount
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ text: input.value }),
        });
        Clacky.Modal.toast("Saved", "success");       // host toast, not window.alert
      });
      el.append(input, btn);
      return el;
    }, { tab: { id: "notes", label: () => "Notes" }, order: 500 });
    

    api/handler.rb — stay a Clacky::ApiExtension subclass; add the route the panel calls. Persist user data with data_path, never into the code dir:

    class <Prefix>Ext < Clacky::ApiExtension
      post "/notes" do            # matches /api/ext/<id>/notes
        text = json_body["text"].to_s
        File.write(data_path("notes.txt"), "#{text}\n", mode: "a")
        json(saved: true)
      end
    end
    

    Rules while reshaping:

    • Keep the panel view: path and the on-disk view.js path in sync — mismatched paths are the #1 cause of a loader.error.
    • Routes in handler.rb are relative to /api/ext/<id>/; the view.js fetch must match. A mismatch is a silent 404, not a verify error.
    • UI styling: default to host classes; anything the host has no class for, build freely. Colors: only var(--color-*) — raw hex breaks the dark theme. For the full class/variable list grep the host stylesheet lib/clacky/web/app.css.

    4 — Write tests

    Test the backend before you call it done. Rewrite the scaffolded test/handler_test.rb to exercise your real routes (parse the JSON response, assert its fields), then run it green:

    ruby test/handler_test.rb
    
    • If test/ is missing from the scaffold, create test/handler_test.rb yourself (mkdir -p test then write it) — do NOT skip tests just because the file is absent.
    • A handler that only returns a static string may keep the scaffold's hello test; anything that fetches, transforms, caches, or computes MUST have a real test.

    5 — Confirm it loads

    Run clacky ext verify and confirm the new units resolve with no errors, then have the user reload the WebUI page. If verify reports problems, go to Debug & verify.

    When NOT to build an extension

    • The user is building features in their own app that just use openclacky — that's normal coding, no extension container needed.
    • The user wants a skill for their own project — use .clacky/skills/ in their project, not a gem-level container.
    • The user wants a custom agent tool — contribute it from a local extension (contributes.tools), which is the supported way to add a tool name to the agent's schema. Don't patch a built-in tool to fake new behavior.
    • The change can be made via clacky config set ... — prefer config over patches.

    Debug & verify

    Your primary instrument is clacky ext verify — a compiler for extensions: every issue is structured with a code, message, the offending file, and a hint.

    Top 5 things that break — check these first:

    SymptomAlmost alwaysFix
    Red error box where the panel should beui.mount render threw — usually a wrong signaturesignature is (container, ctx, runtime) — not (ctx)
    Panel doesn't appear at allslot name typo (silent) or no attach:use a valid slot; set attach: ["*"] or an agent id
    Frontend fetch gets 404route in handler.rb ≠ path in view.js fetchroutes are relative to /api/ext/<id>/
    loader.error on verifyext.yml view: path ≠ the on-disk view.js pathmake the two match exactly
    Edited a file, nothing changedpage not reloaded (or edited ext.yml)reload the WebUI page — hot reload is per-request

    1 — Run verify

    clacky ext verify
    

    Read the output line by line. [OK] confirms a resolved unit; [ERR] blocks a load; [WARN] is advisory. Each issue looks like:

    [ERR] <ext> <unit> (<code>) — <message> [<file>]
             hint: <how to fix>
    

    Always trust the hint first. The line below tells you the fix per code; do the smallest change, re-run verify, repeat until clean — fix ONE issue at a time.

    2 — Fix by error code

    • loader.error → a file the manifest points at is missing, or ext.yml isn't valid YAML. Do: open the file path in the error; make sure it exists and the path in ext.yml matches it exactly. (skill → SKILL.md under skills/<id>/; agent → its prompt file; panel → its view file; api → api/handler.rb.)
    • schema.unknown_contributes → a top-level key under contributes: is misspelled. Do: fix the spelling to one of panels api skills agents channels patches hooks.
    • schema.unknown_key → an unknown top-level key in ext.yml. Do: fix the spelling. Allowed top-level keys: id name name_zh display_name display_name_zh title description description_zh emoji version origin author homepage license public license_required keywords contributes.
    • schema.unknown_field → a unit has a field not allowed for its type. Do: delete or rename that field. Allowed fields per type (this is the authoritative list — do not invent others):
      • panel: id title title_zh description description_zh view order attach entry_points
      • api: id handler
      • skill: id dir protected
      • agent: id title title_zh description description_zh order prompt panels skills avatar
      • channel: id platform adapter
      • patch: target file fingerprint on_mismatch
      • hook: event file
    • schema.bad_attach → a panel attach: entry isn't a valid token. Do: set it to an agent id or "*" (all).
    • ref.missing_panel → an agent's panels: [id] names a panel that doesn't exist. Do: fix the id, or use <ext_id>/<panel_id> to point at another extension's panel.
    • ref.missing_skill → an agent's skills: [id] names a skill that doesn't exist. Do: fix the id, or add the SKILL.md.
    • ref.missing_attach_agent → a panel's attach: names a nonexistent agent. Do: fix the agent id.
    • override (warning) → a higher layer is shadowing a lower one (local > installed > builtin). Do: usually intentional — leave it; confirm with the user only if the shadowing is a surprise.

    Fix one issue, re-run verify, repeat until clean.

    3 — "It verifies but doesn't show up"

    If verify is clean but a change isn't visible:

    • Hot reload is per-request. After editing view.js, handler.rb, or a SKILL.md, the user must reload the WebUI page — no restart, but a stale tab won't update on its own. Editing ext.yml also applies on the next load.
    • Panel not appearing? In order: (1) the slot name in ui.mount must be one of the valid slots — a typo like session.aisde silently renders nothing (check the browser console for a "unknown slot" warning); (2) check the panel's attach: (or the agent that references it via panels: [id]) — a panel with no attach and no referencing agent has nothing to mount onto; (3) a red error box means the render function threw (usually a wrong signature) — open the console for the stack.
    • API 404? Routes are relative to /api/ext/<ext_id>/. Confirm the handler subclasses Clacky::ApiExtension and the route pattern matches what view.js fetches.
    • Skill not triggering? The AI selects skills by their description. Make the description concrete about WHEN to use it.

    4 — Confirm the fix

    End with a clean clacky ext verify and have the user reload to confirm the behavior actually works — don't declare success on "should work."


    Publish (optional)

    Publishing is not a required step. Many extensions are built for the user's own use — scaffold, verify, and reload is the whole job. Only publish when the user explicitly asks to share, ship, or list the extension for others. Never publish on your own initiative or as a "wrap up" of the build.

    The Creator Center panel has a Publish button — prefer it for a guided flow. Use the CLI below for scripted/CI publishing.

    Before publishing

    • The extension must live in the local layer (~/.clacky/ext/local/<id>/). Only local containers can be packed; encrypted (SKILL.md.enc) containers are rejected.
    • Publishing requires the device to be bound to a platform account (it attributes the extension to that account). If it isn't bound, tell the user to authorize the device first — don't try to work around it.
    • Run clacky ext verify one last time and confirm no errors.
    • README check: If ~/.clacky/ext/local/<id>/README.md does not exist, ask the user before proceeding: "No README.md found — would you like me to write usage instructions first?" If yes, read the source files and write a concise README, then publish. If the user asks to write a README / usage instructions at any point, do the same.

    Publish (first time)

    clacky ext publish <id>
    

    Packs the local container into a zip and uploads it. On success: Published <id> v<version> → status=<status>. Options:

    • --status draft — publish as a draft (not visible on the public marketplace). Omit or use --status published to go live.
    • --changelog "..." — release notes for this version.

    Publish a new version

    If already published, a plain publish fails with Error: <id> already published. Re-run with --force to publish a new version. Re-run with --force (and ideally a --changelog); the patch version auto-increments on the platform side.

    clacky ext publish <id> --force --changelog "Fixed the weather refresh bug"
    

    List your published extensions

    clacky ext published
    

    Shows each extension with its latest version, status, and unit summary.

    Unpublish

    clacky ext unpublish <id>
    

    Soft-deletes (takes down) one of your published extensions. Confirm with the user first — it removes it from the marketplace.

    Wrap up

    After a successful publish, tell the user the version and status in plain terms, and mention they can run clacky ext published to see it, or bump a new version anytime with --force.

    Frequently asked questions

    What to verify before installation and use

    What does the ext-develop source document cover?

    Build an OpenClacky extension end to end — scaffold, edit, verify, hot-reload, and (only when asked) publish. Prefer editing real files and verifying over describing.

    How do I install ext-develop?

    The source record exposes this install command: npx skills add https://github.com/clacky-ai/openclacky --skill "lib/clacky/default_extensions/ext-studio/skills/ext-develop". Inspect the command and pinned source before running it.

    Which permission-related actions were detected?

    Static rules flagged network, write-files, exec-script, read-files in the source; the page lists the matching lines and excerpts.

    Alternatives

    Compare before choosing

    Computed 9882

    vasilyu1983/AI-Agents-public

    research-git

    Scans public GitHub repos for agent skills, dev practices, and code patterns. Use when enriching skills, setting team policy, or researching a build domain.

    Computed 96156

    open-edge-platform/edge-ai-libraries

    chatqna-helm-deploy

    Deploy Chat Question-and-Answer Core to Kubernetes using Helm (OpenVINO CPU, OpenVINO GPU, or Ollama), including values.yaml configuration, helm install/upgrade, deployment verification, uninstall, and translation from Docker Compose setup_env.sh variables into Helm override values. Use this skill when the user says "deploy chatqna core to kubernetes", "helm install chatqna-core", "configure values.yaml", "convert compose config to helm", or "translate setup_env.sh to chart values".

    Computed 9660

    almanak-co/sdk

    almanak-strategy-builder

    Build, test, and deploy DeFi trading strategies using the Almanak SDK. ALWAYS use this skill when the user mentions almanak, DeFi strategy, trading strategy, yield farming, liquidity provision, token swap, borrowing, lending, perpetuals, staking, vault deposit, bridging tokens, backtesting, paper trading, or on-chain execution. Use for writing strategy.py files, composing intents (Swap, LP, Borrow, Supply, Perp, Bridge, Stake, Vault, Prediction), working with config.json strategy parameters, run

    Computed 9618

    nexus-substrate/nexus-agents

    release

    Execute a release following project standards. Use when publishing a new version, creating release tags, or deploying. Triggers on "release", "publish", "version bump", "create release".