Build Termy Plugins
Build small, native-feeling Termy extensions against the real v1 contract. Keep the
plugin scoped, use the fewest capabilities, and verify behavior inside Termy when
the user authorizes installation or development mode.
Start from evidence
- Inspect repository instructions, the dirty state, and existing plugin files.
Preserve unrelated work.
- Read the plugin's managed
termy.d.ts when available. Treat it and the current
Termy checkout as authoritative when they differ from this skill.
- Read references/api-reference.md before adding a
new API surface or diagnosing a type/manifest error.
- Read references/performance-security.md
before handling user-controlled shell text, files, credentials, network,
subprocesses, lifecycle events, storage, or native UI.
- Read references/examples.md before scaffolding. Reuse
assets/command-plugin/ or assets/native-ui-plugin/ when it fits.
Do not invent React, HTML, CSS, GPUI access, package imports, SDK imports, build
hooks, or APIs that are absent from the v1 declarations.
Choose the shape
| Need |
Entrypoint |
Capability |
| Palette command or lifecycle event |
plugin.ts |
none |
context.storage or managed data/cache paths |
plugin.ts or .tsx |
storage |
Native Termy JSX view or view.open |
plugin.tsx |
native-ui |
| Persistent native view |
plugin.tsx |
storage, native-ui |
Capabilities gate Termy-owned APIs only. They do not restrict Bun's operating-system
access.
Build the plugin
1. Scaffold
Prefer Termy's scaffold when its CLI is available:
termy plugin init my-plugin
Otherwise create only plugin.json and plugin.ts or plugin.tsx. Add local
relative modules only when they improve the design. Do not add package.json,
dependencies, or a bundler for a v1 plugin.
2. Write the manifest
Use the public schema, API version 1, a stable lowercase ID, a human-readable name,
an optional display version, an optional relative main, and only required
capabilities:
{
"$schema": "https://termy.sh/schemas/plugin.schema.json",
"apiVersion": 1,
"id": "git-tools",
"name": "Git Tools",
"version": "1.0.0",
"capabilities": []
}
Keep main inside the plugin directory. Never use absolute paths, .., or symlinks.
3. Implement against ambient types
Export one definition and let Termy's managed declarations provide the API:
export default definePlugin({
commands: [],
} satisfies TermyPlugin);
Do not import definePlugin, TermyPlugin, TermyPluginContext, TermyUI, or an
SDK. Termy supplies them globally.
Apply these rules:
- Give commands stable IDs and clear, searchable titles.
- Treat context as a read-only point-in-time snapshot. Check optional selection,
directory, command, tab, pane, and event fields before use.
- Use typed settings for configuration. Use
secret settings for credentials.
- Prefer
terminal.sendText and terminal.open over legacy terminal.run. Use a
structured program launch when shell parsing is unnecessary, and never
concatenate untrusted free-form input into a shell command.
- Use
when to hide context-specific commands, async pick inputs for bounded
dynamic choices, context.origin for stable async targeting, and
context.signal/context.progress for cancellable long-running work.
- Return typed actions or emit toasts; do not reach into Termy internals.
- Use
commands: [] for event-only plugins. Keep event handlers bounded.
- Use async storage for small JSON and managed paths for larger files.
- Use
.tsx, the three Termy JSX pragmas, allowlisted TermyUI components, unique
control IDs, named actions, and onAction for interactive native views. Pass
bounded JSON params for navigation and use view.replace/view.close for flows.
- Paginate dynamic native-UI lists and rerender from persisted state.
Develop and verify
Do not mutate the user's installed plugins merely to inspect code. When the user
only requested source work, perform a non-installing syntax/import check first:
termy_plugin_check_dir="$(mktemp -d)"
bun build ./my-plugin/plugin.ts --target=bun --outdir "$termy_plugin_check_dir"
Use the .tsx entrypoint when applicable. This mirrors Termy's Bun target but does
not prove ambient type compatibility or Termy runtime behavior.
When the user asked to build/run the plugin and Termy is installed, use:
termy plugin dev ./my-plugin
This validates the source tree, atomically syncs valid changes, and watches the
development folder. Open the command palette after saving to refresh the current
plugin. Stop the watcher with Ctrl-C; this does not uninstall the managed copy.
Verify the relevant surfaces:
- Confirm the manifest validates and the Worker loads without a Bun error.
- Exercise every command, input branch, contextual
when state, action, and toast.
- Test missing optional context and both native/tmux behavior when relevant.
- Confirm settings update on the next invocation and secrets stay out of plain
plugin JSON.
- Confirm lifecycle events are idempotent and do not create duplicate work.
- Confirm native controls work by mouse and keyboard, emit the intended named
action, preserve view params, persist correctly, and rerender within limits.
- Save a source/import change, reopen the palette, and verify hot refresh.
- Review the diff and run the repository's nearest checks plus
git diff --check.
If Termy runtime verification is unavailable, report that gap plainly. Do not call
static TypeScript success proof of palette, Worker, tmux, storage, or native GPUI
behavior.
Review checklist
- Keep capabilities minimal and manifest/source IDs consistent.
- Keep local imports inside the plugin root; reject dependencies and symlinks.
- Bound selection, input, storage, output, list, network, and subprocess work.
- Keep async pick loaders side-effect free, query-aware, and below option limits.
- Observe
context.signal and report useful progress for longer operations.
- Clean up child processes explicitly when cancellation matters.
- Avoid repeated filesystem/network work in render and lifecycle hot paths.
- Use cached/persisted data and small documents; never render an unbounded list.
- Preserve the user's source folder when installing, updating, disabling, or
uninstalling managed copies.
Hand off
State the files changed, declared capabilities, important security/performance
decisions, commands run, runtime behavior observed, and any unverified Termy surface.
1---2name: build-termy-plugins3description: Build, debug, review, optimize, and validate Termy v1 plugins written in TypeScript or TSX for Bun. Use when creating or changing plugin.json, plugin.ts, plugin.tsx, command-palette commands, native inputs, typed settings, lifecycle events, plugin storage, keybindings, returned actions, or allowlisted GPUI-backed native JSX views; also use for Termy plugin installation, development mode, API limits, security, or performance work.4---56# Build Termy Plugins78Build small, native-feeling Termy extensions against the real v1 contract. Keep the9plugin scoped, use the fewest capabilities, and verify behavior inside Termy when10the user authorizes installation or development mode.1112## Start from evidence13141. Inspect repository instructions, the dirty state, and existing plugin files.15 Preserve unrelated work.162. Read the plugin's managed `termy.d.ts` when available. Treat it and the current17 Termy checkout as authoritative when they differ from this skill.183. Read [references/api-reference.md](references/api-reference.md) before adding a19 new API surface or diagnosing a type/manifest error.204. Read [references/performance-security.md](references/performance-security.md)21 before handling user-controlled shell text, files, credentials, network,22 subprocesses, lifecycle events, storage, or native UI.235. Read [references/examples.md](references/examples.md) before scaffolding. Reuse24 `assets/command-plugin/` or `assets/native-ui-plugin/` when it fits.2526Do not invent React, HTML, CSS, GPUI access, package imports, SDK imports, build27hooks, or APIs that are absent from the v1 declarations.2829## Choose the shape3031| Need | Entrypoint | Capability |32| --- | --- | --- |33| Palette command or lifecycle event | `plugin.ts` | none |34| `context.storage` or managed data/cache paths | `plugin.ts` or `.tsx` | `storage` |35| Native Termy JSX view or `view.open` | `plugin.tsx` | `native-ui` |36| Persistent native view | `plugin.tsx` | `storage`, `native-ui` |3738Capabilities gate Termy-owned APIs only. They do not restrict Bun's operating-system39access.4041## Build the plugin4243### 1. Scaffold4445Prefer Termy's scaffold when its CLI is available:4647```sh48termy plugin init my-plugin49```5051Otherwise create only `plugin.json` and `plugin.ts` or `plugin.tsx`. Add local52relative modules only when they improve the design. Do not add `package.json`,53dependencies, or a bundler for a v1 plugin.5455### 2. Write the manifest5657Use the public schema, API version 1, a stable lowercase ID, a human-readable name,58an optional display version, an optional relative `main`, and only required59capabilities:6061```json62{63 "$schema": "https://termy.sh/schemas/plugin.schema.json",64 "apiVersion": 1,65 "id": "git-tools",66 "name": "Git Tools",67 "version": "1.0.0",68 "capabilities": []69}70```7172Keep `main` inside the plugin directory. Never use absolute paths, `..`, or symlinks.7374### 3. Implement against ambient types7576Export one definition and let Termy's managed declarations provide the API:7778```ts79export default definePlugin({80 commands: [],81} satisfies TermyPlugin);82```8384Do not import `definePlugin`, `TermyPlugin`, `TermyPluginContext`, `TermyUI`, or an85SDK. Termy supplies them globally.8687Apply these rules:8889- Give commands stable IDs and clear, searchable titles.90- Treat context as a read-only point-in-time snapshot. Check optional selection,91 directory, command, tab, pane, and event fields before use.92- Use typed settings for configuration. Use `secret` settings for credentials.93- Prefer `terminal.sendText` and `terminal.open` over legacy `terminal.run`. Use a94 structured `program` launch when shell parsing is unnecessary, and never95 concatenate untrusted free-form input into a shell command.96- Use `when` to hide context-specific commands, async `pick` inputs for bounded97 dynamic choices, `context.origin` for stable async targeting, and98 `context.signal`/`context.progress` for cancellable long-running work.99- Return typed actions or emit toasts; do not reach into Termy internals.100- Use `commands: []` for event-only plugins. Keep event handlers bounded.101- Use async storage for small JSON and managed paths for larger files.102- Use `.tsx`, the three Termy JSX pragmas, allowlisted `TermyUI` components, unique103 control IDs, named actions, and `onAction` for interactive native views. Pass104 bounded JSON params for navigation and use `view.replace`/`view.close` for flows.105- Paginate dynamic native-UI lists and rerender from persisted state.106107## Develop and verify108109Do not mutate the user's installed plugins merely to inspect code. When the user110only requested source work, perform a non-installing syntax/import check first:111112```sh113termy_plugin_check_dir="$(mktemp -d)"114bun build ./my-plugin/plugin.ts --target=bun --outdir "$termy_plugin_check_dir"115```116117Use the `.tsx` entrypoint when applicable. This mirrors Termy's Bun target but does118not prove ambient type compatibility or Termy runtime behavior.119120When the user asked to build/run the plugin and Termy is installed, use:121122```sh123termy plugin dev ./my-plugin124```125126This validates the source tree, atomically syncs valid changes, and watches the127development folder. Open the command palette after saving to refresh the current128plugin. Stop the watcher with Ctrl-C; this does not uninstall the managed copy.129130Verify the relevant surfaces:1311321. Confirm the manifest validates and the Worker loads without a Bun error.1332. Exercise every command, input branch, contextual `when` state, action, and toast.1343. Test missing optional context and both native/tmux behavior when relevant.1354. Confirm settings update on the next invocation and secrets stay out of plain136 plugin JSON.1375. Confirm lifecycle events are idempotent and do not create duplicate work.1386. Confirm native controls work by mouse and keyboard, emit the intended named139 action, preserve view params, persist correctly, and rerender within limits.1407. Save a source/import change, reopen the palette, and verify hot refresh.1418. Review the diff and run the repository's nearest checks plus `git diff --check`.142143If Termy runtime verification is unavailable, report that gap plainly. Do not call144static TypeScript success proof of palette, Worker, tmux, storage, or native GPUI145behavior.146147## Review checklist148149- Keep capabilities minimal and manifest/source IDs consistent.150- Keep local imports inside the plugin root; reject dependencies and symlinks.151- Bound selection, input, storage, output, list, network, and subprocess work.152- Keep async pick loaders side-effect free, query-aware, and below option limits.153- Observe `context.signal` and report useful progress for longer operations.154- Clean up child processes explicitly when cancellation matters.155- Avoid repeated filesystem/network work in render and lifecycle hot paths.156- Use cached/persisted data and small documents; never render an unbounded list.157- Preserve the user's source folder when installing, updating, disabling, or158 uninstalling managed copies.159160## Hand off161162State the files changed, declared capabilities, important security/performance163decisions, commands run, runtime behavior observed, and any unverified Termy surface.