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 × Seven 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. Seven 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 |
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 as your "compiler."
# 1. Create a runnable skeleton (hello panel + backend) in ~/.clacky/ext/local/<id>/
clacky ext new weather-panel
clacky ext new weather-panel --full # kitchen-sink sample using all seven 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
Editing files like
view.js/handler.rbtakes effect on the next request — no server restart. Editingext.yml(adding/removing contributions, changingattach, etc.) requires a reload.
verify Is a Compiler for the AI
clacky ext verify runs: ext.yml field checks, contribution id uniqueness, cross-layer override checks, reference integrity (do the panels an agent references exist, do view / handler files exist), and attach value validity. Every issue carries {ext, unit, code, file, hint} — locatable and self-fixable.
[OK] weather-panel/weather (panel attach=["*"], local)
[OK] weather-panel (api → /api/ext/weather-panel/, local)
[ERR] weather-panel/weather (panel.view.missing) — view file not found [panels/weather/view.js]
hint: create the file or fix the path in ext.yml
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
- The version number is under your control via the
versionfield inext.yml, and used as-is on publish. --status draftpublishes as a draft — visible to you, not public in the marketplace.- Publishing is pack → upload in one step; no manual
packneeded.
Open vs Encrypted
Most extensions (panels, agents, shared components) are open: freely viewable, forkable, and remixable — they flow both ways. A few commercial extensions are encrypted (origin: marketplace, units marked protected: true): content encrypted, license-gated, decrypted in memory at runtime, only name/description exposed to the AI, and not forkable — encrypted extensions flow one way only.
These two lines are distinguished by ext.yml's origin field and the unit-level protected flag, without affecting each other. Extensions you write default to origin: self — always open, freely editable.
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.