Behavior
How CLIs, agent tools, progress, status and notifications behave, so every tool works alike.
The parts that aren’t pixels. A new system follows these, and the cli-tool template shows each one working.
CLIs
- One Effect CLI tree per system. The same tree feeds the CLI, Code Mode and MCP, so a new command reaches all three at once.
- Every command that returns data takes
--json, which is the agent interface. - When there’s nothing to do, say so in one line and exit 0.
- Choices are a multi-select with the sensible ones ticked, and Enter runs them.
--yes,--jsonor no terminal skips the picker. - Long runs print numbered sections with one line each (
[3/7] mise tools) and stream as they go. --checklists what would change without changing anything;--forceskips the check.- Help, completions and a generated reference come from the tree.
- Plain output uses the terminal’s 16 ANSI colors and respects
NO_COLOR.
Agents
Every system gets the same agent surface:
| Surface | What it is |
|---|---|
| Code Mode | A manifest from <tool> agent tools --json, the shape dot already prints |
| MCP | A server on stdio from the same operations |
| Skill | A tool-owned skill in the repository’s skills/, published on skills.sh and as a Claude Code marketplace |
An agent tries Code Mode first, then MCP, then the CLI with --json, and the skill says so.
Each operation has one access class, which decides how agents may use it:
| Access | Code Mode | MCP |
|---|---|---|
| Read | Read | A read-only tool |
| Write | Write | A tool |
| Ask every time (fixes, anything destructive) | Write, always asks | A tool marked destructive |
| Power (reboot, shut down) | Write, always asks | Left out unless the server starts with --allow-power |
Long commands run as jobs only, so an agent starts them and comes back for the result.
Publishing the skill
Two files at the repository’s root publish skills/, and the cli-tool template has both:
| File | For |
|---|---|
skills.sh.json |
The Skills CLI, grouping the skills into those for using the tool and those for working on it |
.claude-plugin/marketplace.json |
Claude Code, with one plugin whose source is ./skills |
The docs site gets an Agents page listing each surface, every operation with its access, and how to install the skill:
npx skills add timmo001/<repo> --skill <tool>
/plugin install <tool> --marketplace timmo001/<repo>
Status and notifications
- One status source per system, as
dot updates status --jsonis for updates. The bar widget, the OpenCode footer, the CLI and the panel all read it, so each shows the same counts. - The bar counts what needs you, and hides in quiet hours.
- Avoid toasts. Show an outcome where it happened (the button, a popover from it, or the page), app-wide state in a slim bar, and anything that needs you as a notification. A toast never has an action. Nothing for green runs.
Notifications
The notification center is the one place an app tells you it needs you. A notification is a pointer to a page: clicking it goes there, and the page has the details.
- It’s yours if you caused it, you need to decide something, or it’s about something you own: a restart after your update, an update to install, a device found, a failed sign-in. How the system works (startup steps, logs, background jobs) stays out.
- The app decides how loud it is. An alert shows by the notifications entry until you act on it or hide it; a passive one only goes to the center. Both are the same card in both places, with the same actions.
- Hiding isn’t dismissing. A hidden alert moves to the center and comes back only when its state changes.
- System notifications clear themselves. A pending restart goes when you restart; you dismiss your own.
- Like ones stack. One restart notification lists everything that asked; “3 devices discovered” is one card.
- Decisions go to the Plannotator Inbox; reviews go through Plannotator, not GitHub.