| name | tui-plugin-development |
| description | Companion guidance for dynamic Cordis Plugins that touch the TUI: terminal themes, slots, local slash commands, native overlays, and current ACP Session config or Plan state. Load `cordis-plugin-development` first for the common Plugin model, then load this skill for the TUI half. |
Develop TUI Client Plugins
Load cordis-plugin-development first for the common dynamic-Plugin lifecycle,
Host/Client split, Package versions, approval, and repair flow. Load this skill
as its TUI companion whenever any requested behavior belongs to the terminal.
For that TUI half, the live TUI Providers and rules below replace generic Web
Slots, React, CSS, settings, and browser-theme assumptions. Keep the generic
skill for shared Cordis behavior and for any genuine Host or Web half.
Fast path
- Call
cordis_inspect_list once. Do this before examining optional visual
assets: asset contents do not determine which TUI Providers own the UI.
- Query only the smallest Client Providers that own the requested behavior,
using the exact methods shown by the list.
- For an existing temporary
@pluginId, call cordis_inspect_self once. To
continue a durable Creator artifact after restart, call tui_plugin_list
and tui_plugin_read instead, then use its exact code.client as the base
of a new temporary preview Plugin.
- Write plain JavaScript in
code.client. Add code.host only when the task
genuinely owns Host data or operations.
- Call
cordis_define once, then cordis_run once with the returned exact
ids. Use run for a new Plugin and update for a new Package replacing a
current one.
- If run reports
awaiting-approval or starting, stop the Tool flow and let
later state updates finish activation. Reply with one short status sentence;
do not summarize the implementation while approval is pending.
- After the later state update confirms activation, persist a new or modified
Client-only UI or Theme Plugin with
tui_plugin_save, unless the user explicitly
requested a temporary preview. Use kind: "ui" or kind: "theme", the returned
exact pluginId and packageId, and a stable artifactId; use
replace: true only when updating an artifact already read from disk.
If code.host exists, do not call tui_plugin_save: Host-only and dual
Packages belong to the connected Harness and need its separate shipping path.
- Report success only after
tui_plugin_save returns saved, including its
exact path. Do not call a temporary dynamic Plugin durable. Other dynamic
TUI Plugins remain process-local unless a separate shipping path was
explicitly requested.
The normal preview trajectory is therefore one list, one query per needed
Provider, one define, and one run. A durable UI or Theme Plugin adds one
save after successful activation. Once inspect returns a sufficient live
contract, author immediately; do not re-prove it from generic Cordis concepts.
define stores an immutable Package only in the current process; run
activates it. Neither operation persists it across restart. tui_plugin_save
copies the successful Client source into the TUI-owned authoring root; use
tui_plugin_read to continue development later and tui_plugin_remove to
delete it. Stop/update/unload dispose the owning Client Plugin effects. Never
depend on a value returned by apply() as the disposer; register effects
through the inspected Cordis APIs.
The authoring root is $MARTTY_HOME/plugins; MARTTY_HOME resolves from the
explicit environment variable, then $DSH_HOME/.martty, then ~/.martty.
User selections live separately in $MARTTY_HOME/settings.json.
Inspect is the authority
Treat the current list and query results as the complete Client API. If a
required capability is absent, report that exact gap before define. Do not:
- search source, installed packages, npm, or the web;
- reflect on runtime objects or function source;
- guess shapes and retry calls;
- define/run a probe Package;
- use a visible Slot such as
chrome.right for diagnostics.
Use the narrowest capability:
| Need | Inspect family | Runtime service |
|---|
| Palette registration/activation | Theme | tuiTheme |
| Saved UI Plugin composition | UiPresets (legacy provider id) | tuiPresets (legacy service id) |
| Persistent terminal content | Slots | tuiSlots |
| Local slash command | Commands | tuiCommands |
| Transient slider or node view | Overlay | tuiOverlay |
| Current ACP Session option | advertised config-option Provider | acpSessionConfig |
| Current structured ACP Plan | Plans | acpSessionPlan |
| Current ACP Session statistics | Stats | acpSessionStats |
| Current ACP run-state facts | Status | acpSessionStatus |
A transient control is not a side panel. When the user did not request
persistent content, do not query Slots or mount chrome.right.
Compose independent capabilities
Follow the Web dynamic-Plugin model: a returned Client Plugin declares the
services it needs, and one apply() may contribute any combination of
features. Every registration belongs to that Plugin's Cordis fiber, so
stop/update/unload retracts all of its contributions together. Do not invent
cross-service option fields or a new bundle abstraction to couple two features.
Select Providers from the user-visible behavior, query each live contract, and
compose only in the Plugin code. The services do not imply one another:
- Themes: use the inspected theme-Plugin registration contract.
/theme
is a special single-select Plugin switch: selecting another entry starts its
Plugin and stops the current Theme Plugin, so every contribution shares one
unload. Register palette, commands, overlays, slots, and RPC unconditionally
in that one Plugin. Never read/subscribe to active theme or implement a
theme condition yourself. Immediate preview uses the inspected registration
option and enters the same Plugin seat.
- UI Plugins: use the current
tuiPresets.register({ id, label }, mount)
compatibility service when the user
wants one saved /ui choice to mount several UI contributions together.
The synchronous mount callback returns one disposer. A UI Plugin owns
structural UI contributions such as slots, chrome, or a pet. It does not
register, select, clone, or generate a Theme: /ui, /theme, and dark/light
are independent state dimensions. A UI Plugin may document a recommended
Theme, but recommendation is not runtime ownership.
- Commands: register a local slash command. Registration publishes slash
completion and keeps invocation out of the ACP prompt. Its availability is
the registration's lifetime; Commands does not know about themes, overlays,
config categories, or Slots. Optional
input: { hint, options } metadata
reuses the same upward slash menu after /name ; each option has value
plus optional label and description. Call the returned disposer’s
update({ input }) method when a dynamic candidate catalog changes.
- Slots: register persistent native
TuiNode content only when the user
asked for persistent shell UI. Use stable node ids and update the existing
contribution instead of creating parallel panels. Select the live seat from
Slots.list: all current seats aggregate contributors. Use
conversation.input.dock for content needing its own line (it owns the
single cap row and displaces the tip line while present) and keep
conversation.composer.dock contributions compact.
- Overlays: open a transient native control only in response to the
interaction that needs it. A command is one possible trigger, not part of
the Overlay contract. Do not use a persistent Slot as a transient control.
- ACP Session config: resolve the live option through the inspected
semantic category, or by id only when the Package is intentionally
agent-specific. Use the inspected transaction when an interaction needs
preview/commit/rollback; it owns write ordering, deduplication, first-winner
finalization, and rollback of unfinished previews with the Plugin run.
When one Plugin registers several features, register each through its owning
service and rely on the shared Plugin lifetime for joint unload. If a feature
must appear or disappear while the Plugin remains loaded, use an inspected
state/event API and hold that feature's ordinary disposer in the Plugin; do not
add the condition to an unrelated service's registration schema.
Some interactions map a visual range onto a finite ordered choice set. Only
when the task asks for that mapping, keep the domains separate: for P visual
positions and N live choices, position p selects
min(N - 1, floor(p * N / P)). Derive the initial visual position from the
current live choice. This is a local mapping technique, not a prescribed
Command, Overlay, Theme, or ACP workflow.
Treat supplied media as opaque assets
A file-backed theme usually needs stable resource identity, not visual
understanding. First decide whether knowing the pixels can materially change
the requested result. When the user supplies valid media plus an ordering
convention and asks only to use it, treat it as opaque: list the directory once
if exact names are unknown, sort deterministically, and do not inspect every
image merely to choose presentation options.
Make that one inventory sufficient for authoring: collect candidate names,
ordering keys, absolute paths, and only metadata the queried API actually
requires in the same operation. After it succeeds, use the result verbatim;
do not re-list the directory for validation or path conversion, and do not
probe sibling or references directories the user did not supply.
When content-aware judgment really matters — for example crop QA, palette
extraction, subject detection, or visual comparison — inspect the smallest
representative subset. If an image call reports that the model or tool lacks
visual capability, remember that result for the rest of the task and do not
retry sibling files. Continue with deterministic metadata and runtime defaults
when visual understanding is optional. If it is essential to the user's
requested outcome, report that exact missing capability while preserving any
independent work that remains possible.
For a static directory, resolve the ordered absolute paths while authoring and
embed that immutable list in code.client. Client code has no filesystem API.
Do not add code.host or Host RPC just to rediscover a static list at runtime;
use a Host half only when the user requests live-changing Host-owned assets.
Omit optional background presentation fields when the inspected Theme contract
provides safe defaults. This lets the renderer own generic fit, anchor, and
opacity behavior instead of guessing from image content.
When a slider selects both a frame and an ACP option, the frame index remains a
visual domain and the live ACP choices remain a business domain. Update the
background through the Theme Plugin's owned registration and map the index to
the inspected choices separately. Keep that registration, command, overlay,
and transaction in the same Theme Plugin fiber: no active-theme subscription,
theme predicate, or side panel is needed.
Client code is plain JavaScript: no imports, require, TypeScript, JSX, browser
globals, Node globals, native timers, raw ACP methods, guessed services, or
arbitrary compositor events. Every visible effect must disappear with its
owning Plugin run.