Extension System Overview

Extension System Overview

There's a lot you can extend in OpenClacky: add a panel to the Web UI, add a backend endpoint, connect a new IM channel, teach the AI a new skill, craft an agent with its own personality and panels, intercept tool calls, even patch built-in methods. These abilities used to live in separate directories with separate loading rules; now they converge into one thing — the extension container: a single directory with one ext.yml manifest that declares what capabilities it "contributes," then gets loaded, verified, packed, and distributed as a whole.

In one line: anything you can build locally can be packed into an extension and shared; whoever installs it can fork it into their own.


One Container × Three Layers × Eight Contributions

The whole system is just three concepts:

1. Container = a directory + one ext.yml

An extension is a self-contained directory with an ext.yml manifest at its root. The manifest's contributes section declares what the container gives the system:

id: weather-panel
name: Weather Panel
description: Shows the current city's weather in the sidebar
version: "0.1.0"
author: Your Name
origin: self
contributes:
  api: api/handler.rb          # one backend endpoint
  panels:
    - id: weather
      view: panels/weather/view.js
      attach: ["*"]            # attach to all agents

The files in the directory (api/handler.rb, panels/weather/view.js) are the actual contributions; ext.yml boxes them up into one distributable whole. Full field list: ext.yml Manifest Reference.

2. Three Source Layers (Override Priority)

Extensions can come from three places, in ascending priority:

Layer Location What it is
builtin gem-bundled default_extensions/<id>/ Official extensions shipped with the gem (coding / general agents, git panel, etc.)
installed ~/.clacky/ext/installed/<id>/ Extensions you installed from the marketplace or elsewhere
local ~/.clacky/ext/local/<id>/ Extensions you wrote yourself (highest priority)

On an id clash, the higher layer wholly overrides the lower (local > installed > builtin, read as "local beats installed beats builtin"). So you can write a local extension with the same id as a builtin one to override official behavior — no need to touch the gem source.

3. Eight Contribution Types

A container can contribute any mix of the following, each a key under contributes:

Contribution Purpose Detailed docs
panels Web UI panels / buttons / visualizations Web UI Extensions
api HTTP backend endpoints HTTP API Extensions
skills Teach the AI a new skill How to Use a Skill
agents An assistant with its own personality / panels / skills Agent Configuration
channels Connect a new IM channel Custom Channel Adapter
patches Runtime patches on built-in methods Runtime Patches
hooks Intercept / audit tool calls Declarative Shell Hooks
tools Add a brand-new tool to the AI (file name maps to class name) ext.yml Manifest Reference

A "Slack suite" can contribute all at once in a single container: a channel (Slack), an agent (support assistant), a panel (inbox), and a skill (ticket triage) — distributed and installed together.


The clacky ext Command Loop

An extension's whole lifecycle is driven by clacky ext subcommands. Developers are often AI agents, so this is deliberately designed as a scaffold + self-check loop — modify the generated sample, then run verify for static checks before runtime testing.

# 1. Create a runnable skeleton (hello panel + backend + a runnable test) in ~/.clacky/ext/local/<id>/
clacky ext new weather-panel
# Alternative to the preceding command, not a second creation of the same id:
# clacky ext new weather-panel --full   # sample using all eight contributions

# 2. Verify: resolve all containers, list contributed units + structured errors
clacky ext verify

# 3. List all resolved containers and what they contribute, who overrides whom
clacky ext list

# 4. Pack into a distributable zip
clacky ext pack weather-panel

# 5. Install someone else's extension (local zip path or http(s) URL) → installed layer
clacky ext install ./weather-panel.zip
clacky ext install https://example.com/weather-panel.zip

# 6. Search the public marketplace
clacky ext search weather

Applying Changes

Reload behavior depends on what was changed; a browser refresh does not reload every Ruby contribution.

Change How it takes effect
Panel view.js Reload the Web UI to fetch and execute the current script.
API handler.rb The next extension API request checks the handler's modification time and reloads changed code.
Skill files The skill loader rereads files on its next reload. Refreshing the catalog is not proof that an already-running task has new instructions.
Manifest / agent configuration The manifest resolver rescans changed manifests, but consumers have their own lifecycles. Reload the Web UI for panel registration; use a new session to verify agent prompt/tool/skill changes.
Hooks / patches / channel adapters Loaded with Ruby require during process initialization. Arrange a server restart with the user; refreshing the browser is insufficient.
Custom tool Ruby classes Loaded when tools are registered for an agent. Do not assume a running agent is replaced by a browser refresh; verify with a new agent/session, and arrange a restart if the Ruby file was already required.

The API handler cache tracks the handler file, not every dependency or metadata change. A manifest-only change to API config / public, or a change in a required helper, must be checked against the running server; do not promise next-request reload for it. Never restart or interrupt the user's work without consent.

After a successful panel change, the host can show a refresh prompt and open the session aside; see Host APIs. These are UI notifications, not server-reload commands.

What verify Checks

clacky ext verify resolves manifests and checks known keys, version format, supported required fields/file paths, cross-layer container overrides, agent panel/skill references, and attach values. Issues have {ext, unit, level, code, message, file, hint}; some fields may be null. Unknown keys and some unresolved references are warnings, not errors.

It is not a compiler or a complete schema/type checker: it does not execute the contribution scripts, check their Ruby/JS syntax, load/register channel adapters or hooks, or validate hook event names. Nor does it enforce every id uniqueness rule. Keep panel ids unique within a container yourself. Runtime registration and functional tests are separate steps.

Example output for a missing panel file (the absolute path varies by machine):

[ERR] weather-panel panel/weather (loader.error) — view file not found: panels/weather/view.js [/path/to/weather-panel/panels/weather/view.js]

Missing contribution files are reported as loader.error, not per-type codes such as panel.view.missing. An [OK] line means the unit was structurally resolved, not that its script ran successfully. The command exits non-zero if it reports an error.


Publishing to the Marketplace

A finished local extension can be published to the OpenClacky marketplace, becoming someone else's installed extension. Before publishing you must bind this device to a platform account (device identity binding), so the platform knows who owns the extension and its versions.

# First publish (unbound device is prompted to authorize first)
clacky ext publish weather-panel

# Publish a new version (reads version from ext.yml)
clacky ext publish weather-panel --force --changelog "Fixed the night icon"

# List your published extensions
clacky ext published

# Take one down
clacky ext unpublish weather-panel
  • Set the version yourself in ext.yml. Publishing requires a valid semantic version; a new version must be greater than the latest published version. --force allows publishing another version, but never increments the version for you.
  • --status draft publishes as a draft — visible to you, not public in the marketplace.
  • Publishing is pack → upload in one step; no manual pack needed.

Before Publishing or Handing Off

Only local containers can be packed. Encrypted skill payloads (SKILL.md.enc) cannot be repackaged through this workflow. The Creator Center also provides a guided Publish action; CLI publishing is useful for scripted flows.

Run the generated tests (for a default scaffold: ruby test/handler_test.rb), run clacky ext verify, then verify the feature in the actual Web UI/API. A clean manifest does not prove the business behavior works. If README.md is missing, ask whether the user wants usage instructions before publishing. Record tested behavior and any reload/restart still needed.

Publishing and unpublishing change marketplace state: obtain explicit user approval. Building a local extension does not imply publishing it.

Open vs Encrypted

Local panel scripts, agent prompts and plain SKILL.md files remain readable files. origin and a skill unit's protected flag are metadata; changing them does not encrypt existing content or enforce a runtime sandbox.

The loader detects encrypted skill payloads from the presence of SKILL.md.enc, independently of those flags. The normal extension skill loader skips both protected and encrypted units. Actual decryption and licensing depend on the encrypted-skill delivery workflow. Do not label a container protected just because its manifest says origin: marketplace. The local pack/publish workflow rejects containers containing encrypted skill payloads.


Start Here

  • To get going fast: clacky ext new <id>, read the generated files, edit, clacky ext verify, refresh the Web UI.
  • To look up fields: ext.yml Manifest Reference.
  • To dive into a contribution type: click the matching doc in the table above.