# CLI commands and exports F6 (#77) covers the command entry points, conversation export and JSONL execution listed in [overview Appendix D](overview.md#appendix-d-cli-layer) and the [feature list](../feature-list.md#cli-commands). The native CLI remains a client of the existing daemon. This document defines the CLI interface for the implementation; the [native CLI contract](../../crates/dim-cli/README.md) describes the current executable. The [attachment input contract](cli-attachment-input.md) defines the idle draft editor, local image sources and paste handling. The multiline/paste editor and headless exec image arguments, clipboard/file insertion, completion and dropped paths are implemented. The attachment contract records the native platform acceptance evidence and outstanding checks. ## Transcript detail settings In both scrollback and fullscreen TUI, `/settings` exposes independent `Tool details` and `Thinking details` settings. Enter, Left/Right or Space cycles Tool details through collapsed, summary and expanded, and toggles Thinking details between collapsed and expanded. Changes save immediately in the CLI home. Both settings default to collapsed; saved preferences, including the old boolean tool preference, are preserved independently. ```text Settings · Interface Tool details summary Thinking details collapsed ``` Collapsed tool groups retain their existing header and member cards. The header appends the first program name of each shell call, without arguments, executable paths, script bodies or output. Names stay in first-call order, with repetitions counted (`python ×2, rg`), including shell calls classified as reads or searches. The header remains one terminal row and keeps its disclosure when truncated. Other tools retain their existing header summaries. Summary adds one row per call, in call order, naming its tool and input target or command, without output, diffs or script bodies. Each row truncates to one terminal row; a multiline command shows its first nonblank line followed by `…`. Expanded retains the full tool details. Clicking a tool header cycles the same three forms for that group only. A member's summary row still opens its conversation. `/tools` cycles the three tool forms, retaining its control of compaction details: only expanded shows a compaction's summary body. Thinking details controls both streaming and completed thoughts, including the line side panel. Changing either setting updates its retained history and live content without changing the other setting. Scrollback already written to the terminal remains append-only; fullscreen redraws retained history. ## Command menu In both scrollback and fullscreen TUI, list commands in one fixed, curated order, not alphabetically or by recent use. The menu draws no category column: ```text /new /resume /models /compact /usage /effort /fast /agent /permissions /settings /branch /fork /lines /timeline /queue /export /earlier /goal /tasks /rename /web /connect /login /skills /reload-skills /mcp /plugins /runtime /hooks /scheduled /workflow /workflow:save /tools /tui /theme /language /keys /exit ``` `/fast` is listed only when the model advertises service tiers. Search relevance takes priority; equally ranked matches keep this order. The order is independent of the interface language. Opening `/` selects `/new`; `/exit` is last in the unfiltered command list. Exit requires its exact name or alias before dispatch: choosing an incomplete match fills the command name and keeps the composer open. Exact typed commands, including hidden commands, retain their own dispatch behavior. Menu selection can still complete rather than dispatch an exact name: commands with inline arguments and `/goal` fill the composer for a separate submission. `Tab` only completes the selected row and never runs or sends it: a command fills its name, one that takes an argument also fills the space after it, and a Skill row fills its `$skill: ` marker. A query matching nothing leaves the draft unchanged. A command without an argument runs on `Enter` only. The command menu lists `/permissions` as the permission entry. `/approvals` and `/approval` remain compatibility aliases when typed directly. Run, queue and steer keys are omitted from the menu. The menu lists slash commands and Skills: the view actions for picking an earlier message and scrolling to the top or bottom are also omitted; `Tab`, `Home` and `End` keep their fullscreen message-picker and scroll bindings outside the menu. A Skill row reads `$name` with its description. Only an enabled Skill that no other root shadows is listed; a name containing whitespace is not listed either, because a reference ends at the first whitespace. The list follows the Session's catalog: the client rereads it on attach, on a Session switch, after `/reload-skills`, and after a change in `/skills`. A daemon that does not serve `skills.catalog` shows the commands alone, without an error. Choosing a Skill row, with `Enter` or `Tab`, fills `$skill: ` and never sends; the marker is part of the message text. Skills come after commands; a Skill leads only when its name starts with the query and no command name or alias does, so a name shared with a command keeps both rows, the command first. `/` is the only trigger: `@` stays the file picker. The menu fits the available terminal columns and rows in both modes. Command names take priority over descriptions and shortcut labels. A shortcut that would crowd out its command name is omitted from the row, without changing the binding. On short screens, fewer commands are shown and the window follows the selection. Scrollback keeps the command draft visible above the menu and omits the menu title and footer when necessary; fullscreen keeps its palette query and selection above the composer. The order does not change with terminal size. When the usual history reservation leaves fewer than eight live rows, live controls take priority: either renderer can use up to eight rows for the live region, limited by terminal height. Fullscreen reduces the composer's empty padding and the palette's query padding before overlapping either input. The draft editor scrolls within the remaining rows and keeps its caret visible without discarding text. Otherwise the usual history reservation is retained. A list panel never draws more rows than it is given; given fewer than two, it draws nothing, since the two borders alone would take the budget. The list fills the rows the other parts leave. While the list cannot draw its first three entries, the notes give up lines from the end, the blank row before them going with the last line. When not even one list row fits beside the other parts, the panel gives up the rest of the notes, the filter's padding (the row below the query first), its footer lines (the first first), the hint's rows above the border, the query row and the list's last row, in that order. The selected entry and the hint's closing key stay the longest. A hint too wide for the bottom border drops whole parts and keeps its first and closing keys. A confirmation is shortened by its own rule: the full question and its two keys first, then the question cut with `…`, then the two keys, then the confirm key alone, and last that key cut with `…`; the confirm key stays the longest. ```text Natural height Short budget, 5 rows ╭─ Sessions ─────────────────────────────────────╮ ╭─ Sessions ─────────────────────────────────────╮ │ │ │ │ │ > │ │ > │ │ │ │ › [Chat] review the parser │ │ [Chat] review the parser │ ╰────── Enter open · Ctrl+R refresh · Esc close ─╯ │ [Chat] fix the flaky test │ │ [Chat] release notes │ ╰────── Enter open · Ctrl+R refresh · Esc close ─╯ ``` ```text Commands /new start a new session /resume resume a previous session /models select a model /compact compact the context of the shown line /usage show the context window usage /effort show or set the reasoning effort $commit write a commit message for the staged changes / + Enter -> start a new session /ex + Enter -> /exit (completion only) /exit + Enter -> exit /permissions + Enter -> permission controls ``` ```text Fullscreen, 8 rows Scrollback, 8 rows +- Command Palette -------+ | > | +- input -----------------+ | /new | | > / | +-------------------------+ +-------------------------+ +-------------------------+ +- input -----------------+ | /new | | | +-------------------------+ +-------------------------+ AGENT ``` ## Run keys and queue Queue and steer differ from Codex CLI: `Enter` queues while a run is in progress, and `Ctrl+S` steers. The other run keys follow the Codex CLI interaction (checked in its TUI, v0.166.0). `Enter` submits when no run is in progress. ```text key run idle run in progress Enter submit queue the draft; empty draft: steer the oldest queued message Tab pick an earlier message pick an earlier message (fullscreen) (fullscreen) Shift+Left — empty draft: recall the newest held message only if one text block Esc keep the draft interrupt; every held message returns to the draft, oldest first Ctrl+S — draft not empty: steer the draft; empty: steer the oldest queued message Up / Down input history input history Home / End scroll (fullscreen, empty) scroll (fullscreen, empty) ``` - A queued message runs after the current run ends, one message per run. A steered message joins the current run at its next turn boundary. - Taking a message back, and interrupting, withdraw the message through `session.input.withdraw`. Its content enters the draft only after the withdrawal is deleted. Shift+Left accepts only a single text block; interrupt restores every held message, including its image blocks and stored paste/file links. A message that already left the queue is not put back or sent again. - Withdrawals of an interrupt are sent before the stop, so the run does not start the next queued message. - `Ctrl+C` clears the draft. `Esc` never clears it. ### Queued messages `/queue` requires an attached Session and opens the shown line's unconsumed messages in both terminal modes. Applied-but-unconsumed messages are labelled `Incorporating`; unapplied messages are labelled `Held`. Up/Down selects a row, Enter steers it and `x` deletes it through the existing withdrawal operation. Only held messages can change; `notHeld` is a notice, not a failed run. Esc closes the panel without interrupting. Committed events refresh the panel, and changing attachment or shown line closes it. Shift+Left only withdraws a message with one text block. Images and multi-block messages remain held with a notice directing the user to `/queue`; no partial text draft replaces their attachments. Esc interrupt instead withdraws all held messages before stopping, restoring their operator content to the draft, oldest first, so they cannot start a new run automatically. Appended runtime text blocks (after the first block, starting with ` /goal | (completion only) /goal fix build + Enter -> set/edit goal /goal + Space + Enter -> usage + current goal Usage: /goal [OBJECTIVE | set OBJECTIVE | edit OBJECTIVE | pause | resume | clear] No goal on main. ``` | Form | Call | | --- | --- | | `/goal` | none: usage, then the goal in force on the shown line, or that it has none | | `/goal OBJECTIVE` | `session.goal.edit` while the goal is active or paused, `session.goal.set` otherwise | | `/goal set OBJECTIVE` | `session.goal.set` | | `/goal edit OBJECTIVE` | `session.goal.edit` | | `/goal pause` | `session.goal.pause` | | `/goal resume` | `session.goal.resume` | | `/goal clear` | `session.goal.clear` | Every call names the shown line in `branchId` and replies with its input's `sequence`. A form that writes nothing else (`/goal`, `pause`, `resume`, `clear`) takes no further word; otherwise the rest of the line is the objective, spaces included. `set` and `edit` also name the objective, so an objective that is one of the form words is written after one of them: `/goal set pause` sets the objective "pause", and `/goal set set` sets the objective "set". `/goal OBJECTIVE` writes `edit` only while the goal in force is active or paused: the status, the judge and the continuation count stay, and a paused goal stays paused. With no goal, with an objective that has no status, or with a goal that completed or blocked, it writes `set`: a finished goal is never edited back into work, and a new objective after one starts the next goal — status active, the count from the first continuation, and the line works toward it. `judge` is Pursuit's and no form sends it: a goal set from the terminal is judged by the model, and a goal an evaluator judges keeps its judge through `/goal edit`, which writes only the objective. A `/pursue` form is Pursuit's later option (§10) and is not part of this contract. **Reading the goal.** The client reads `session.goal.get` for the shown branch ([Goal A.3](plugins/Goal.md#a3-reading-the-goal)). `/goal` without arguments prints usage and that state. Before choosing `set` or `edit`, it reads the current state; after a write, it reads again before reporting the result. Goal interprets its records, legacy keys, deletion and branch-opening rules. The client neither replays history to make this decision nor infers an absent goal from a transcript window. A failed state read reports an error and admits no guessed write; a successful state read remains usable if the transcript failed. A daemon without the read method reports that limitation instead of falling back to full history. **When Goal is not loaded.** The Session lists the methods it serves (`session.methods`). When `session.goal.set` is not among them, `/goal` prints `Goal is not available on this daemon.` and makes no call. A daemon that does not advertise `session.methods` is taken at its word until a call answers `unknown_method`, which prints the same line. No form submits the typed line as a message: a `/goal` that writes sends Goal's own change input, and Goal decides what follows. **What the clients show.** ```text > /goal Usage: /goal [OBJECTIVE | set OBJECTIVE | edit OBJECTIVE | pause | resume | clear] No goal on main. > /goal Make the CLI tests pass Goal set on main · active Make the CLI tests pass > /goal Keep the CLI tests green Goal edited on main · active Keep the CLI tests green > /goal pause Goal paused on main · paused (user) Keep the CLI tests green > /goal Usage: /goal [OBJECTIVE | set OBJECTIVE | edit OBJECTIVE | pause | resume | clear] Goal on main · paused (user) Keep the CLI tests green > /goal resume Goal resumed on main · active Keep the CLI tests green > /goal clear Goal cleared on main. ``` Bare `/goal` starts with usage; its state report follows. A writing form starts with the state report, naming the call that ran, the line the goal belongs to and the status in force; `edited` is the form that found a goal and changed only its objective. `evaluator judge` follows the status when Pursuit's judge is on the goal. A paused status names its reason: `user`, `interrupted`, `limit`, `turn failed` or `evaluation failed`. The objective follows, one line per line of it. Clearing a goal prints one confirmation line. Bare `/goal` on a line without a goal prints two lines: usage, then the no-goal notice. The line client and both terminal UI modes print these lines; in the terminal UI they are one report in the transcript, so earlier reports stay readable above later ones. The status area carries one short element while the shown line follows a goal, and nothing for a completed, blocked or cleared one: ```text ╭──────────────────────────────────────────────────────────────────────────────╮ │ › _ │ ╰────────────────────────────── ⑂ main ▸ child work · after message 0 ─────────╯ AGENT ⏵⏵ Workspace write ◎ goal active 1.8K / 256K provider/model ``` Fullscreen draws the same element in the composer's bottom border, beside the run status: ```text ╭──────────────────────────────────────────────────────────────────────────────╮ │ › _ │ ╰──── ◎ goal paused Full access 1.8K / 256K ───── ● main · after message 4 ─╯ ``` ## Provider accounts The native client signs in to every Provider whose `provider.management` entry carries an [`account` descriptor](appserver/provider-management.md#31-read-models). That descriptor is the only source: the client keeps no per-provider list, and it compares no Provider id. The described accounts are the provider list; the descriptor's `loginMethods` say which flow each one starts (`browser` or `device`); and the calls are the descriptor's namespace's — `.login.begin` (device), `.login.browser.begin`, `.login.browser.complete`, `.login.status`, `.login.cancel` and `.logout`. A namespace that the daemon does not advertise is reported as unsupported instead of starting a flow that cannot finish. The `account` namespace is the Dim account. Its calls also carry `providerId` and the account directory, and `/login` keeps its account panel — identity, plan and usage, and the enterprise-email entry — because the descriptor carries neither identity nor usage. Every other namespace is a single account whose calls carry only the attempt id. The terminal UI offers these operations on the `/connect` row of each described account. A signed-out account says `Sign in`, and opening it starts its only flow; a signed-in one shows its state and offers `Sign out`. A row whose namespace is `account` opens the Dim account's `/login` panel instead, with its identity, plan and usage. A Provider without a descriptor keeps the connection form, and opening it starts nothing. ```text /connect ╭─ Providers ──────────────────────────────────╮ │ OpenAI Codex Sign in │ │ Grok Build Sign in │ │› Add custom provider │ ╰─ Enter open · Esc clear/close ───────────────╯ │ Enter on a signed-out described row ▼ ╭─ OpenAI Codex OAuth ─────────────────────────╮ │ Browser login │ https://…/authorize?… │ Callback URL: (paste here) │ │ Submit callback │ ╰─ Select to copy · Enter opens link ──────────╯ ╭─ Grok Build OAuth ───────────────────────────╮ │ Device login │ │ Enter code: ABCD-EFGH │ https://…/device │ Restart device login │ │ Waiting for authorization │ ╰─ Select to copy · Enter opens link ──────────╯ ``` Authorization URL rows have no side borders, including wrapped continuation rows, so native terminal selection does not copy dialog borders into the URL. The other rows keep their borders; Enter on the URL still opens the full link. `dimcode login [PROVIDER]` starts the same flow in the line client and prints the URL or the code the daemon returns; without a PROVIDER it lists the described accounts. `dimcode logout PROVIDER` signs one out, and `dimcode account PROVIDER` prints its non-secret state. `--help` lists these forms and never contacts a daemon. A build without the Dim account provider says so, and names the API-key Provider alternative, from each of these commands. ## Plugin runtimes RuntimeManager supplies the Node and Python runtimes that plugin code runs in, and the uv tool that launches Python MCP servers ([RuntimeManager](plugins/RuntimeManager.md)). The daemon reads its configuration when an action applies it (RuntimeManager §3.3), so a client can turn a runtime on, say where it comes from and show what it is doing, without editing `/runtimes/config.json` by hand and without restarting anything. `/runtime` opens one panel in both terminal modes and the same area is `dimcode runtime` in the line client and in the CLI. ### The panel Rows are the two runtimes and the uv tool, always node, then python, then uv. A row shows its name, the configured source (`download` or `system`), the version in effect (the fixed version for `download`, the checked interpreter's version for `system`, empty while it is `off` and nothing has been checked), `auto` when it is on, and the status `runtime.list` gives: `off`, `acquiring`, `starting`, `ready`, `down` with its reason, or `failed` with the reason that lists every source and its error. uv launches no runtime, so it never shows `starting` or `down`: it is `ready` once installed under the plan in effect. Below the rows the panel names what waits on the runtime — the plugin rows that are Pending because one is `off` or `down` ([PluginManager §2.5](plugins/PluginManager.md#25-runtimes)) — so "why is workflow not working" is answered in the same place as its fix. A failure prints one line per source, as `runtime.list` reports it. ```text /runtime ╭─ Plugin runtimes ─────────────────────────────────────────────────────────╮ │ › node download 24.21.0 ready │ │ python system 3.12.15 off not installed │ │ uv download 0.13.7 off │ │ │ │ 1 plugin waits for node: workflow │ ╰─ Enter install or apply · s source · a auto · r retry · Esc close ────────╯ ``` ```text ╭─ Plugin runtimes ─────────────────────────────────────────────────────────╮ │ › node download 24.21.0 acquiring 12s │ │ python system 3.12.15 off │ ╰─ Esc close · the acquisition keeps running if you close this ─────────────╯ ``` ```text ╭─ Plugin runtimes ─────────────────────────────────────────────────────────╮ │ › node download 24.21.0 failed │ │ https://nodejs.org/dist/v24.21.0/…: connection timed out │ │ https://npmmirror.com/mirrors/node/v24.21.0/…: 404 │ │ python system 3.12.15 off not installed │ │ │ │ 1 plugin waits for node: workflow │ ╰─ Enter retry · s switch node to system · Esc close ───────────────────────╯ ``` `acquiring` shows elapsed seconds, not a percentage: the daemon reports no byte count for an acquisition, and the panel does not invent one. A source list appears only on failure, where the daemon already reports it. ### Menu entry `/runtime` is a slash command; its position follows the [command menu order](#command-menu). It needs no Session (`Needs::Nothing`) and takes no inline argument, because the panel carries the actions. The typed subforms below are accepted when typed, not offered as menu completions. ### Keys The panel is keyboard-only and the same in scrollback and fullscreen, as the command menu contract requires. | key | meaning | | --- | --- | | `Up` / `Down` | move between the runtime rows | | `Enter` | `runtime.install NAME`: apply the configuration as it stands | | `s` | switch that runtime's source (`download` ↔ `system`) and apply | | `a` | toggle that runtime's `auto` and apply | | `r` | retry: the same call as `Enter`, kept because a failed row's footer says so | | `Esc` | close the panel; an acquisition already running keeps running | `Enter` on an `acquiring` row does nothing, which is what the daemon's `install` does while it is acquiring. `Enter` on a `ready` row with an unchanged source does nothing either; with a changed source it withdraws, disposes and re-acquires, as RuntimeManager §3.3 defines. ### Writes `runtime.install {name}` is the apply the runtime already has: acquire if `off` or `failed`, provide the launch command again if `down`, and, when the configuration's `source` changed since the last apply, withdraw, dispose and acquire under the new source. It replies at once with the entry, so the panel follows `runtime.list` rather than waiting for an acquisition. `s` and `a` are `runtime.configure {name, source?, auto?}` followed by that same apply. The method writes `/runtimes/config.json` — RuntimeManager's own document — atomically, merging per runtime and per field over the preset, and a configuration that does not write or read is the call's `runtime_configuration` error. The panel then shows the error and keeps the row's previous state on screen, because nothing changed. ### Plugins that wait `/plugins` names the runtime a Pending row waits for, and `Enter` on such a row opens `/runtime` with that runtime selected. Today the row says only `pending`. ```text /plugins ╭─ Plugins ─────────────────────────────────────────────────────────────────╮ │ › goal on ready · built-in │ │ session-title on ready · built-in │ │ scheduler on ready · built-in │ │ workflow on pending · waits for the node runtime │ ╰─ Enter open /runtime · o on/off · r reload · Esc close ───────────────────╯ ``` ### Line client and CLI The typed forms are the whole area, so a caller needs no panel: ```text /runtime the same rows, in the scrollback client /runtime install node apply the configuration for node /runtime install uv install the uv tool /runtime source node system switch node to the system interpreter and apply /runtime auto python on turn python on by configuration and apply ``` ```text $ dimcode runtime list node download 24.21.0 ready python system 3.12.15 off not installed 1 plugin waits for node: workflow $ dimcode runtime install node node download 24.21.0 acquiring $ dimcode runtime config python --source system --auto on python system 3.12.15 off ``` `dimcode runtime list` prints one tab-separated row per runtime or tool (node, python, uv) with the fields the panel shows. Which plugins wait for a runtime is `/plugins`' answer (`dimcode plugin list` in the line client), not this command's: one place names it per plugin, and the panel repeats it only for the runtime in view. `install` and `config` print the runtime's row after the call, which is `acquiring` or the state it returned to; a failed call prints the error and exits non-zero. `--help` lists these forms and never contacts a daemon. A daemon that does not serve `runtime.list` answers `unknown_method`; the panel then says the daemon does not serve runtimes and prints the version, rather than an empty list that reads as "no runtimes". ## Process isolation settings `/settings` exposes the `Process isolation` control under Permissions. `/permissions` links to this section instead of hosting it. It replaces the old `Execution` panel and separate Sandbox entry; there is no duplicate toggle, native `dimcode isolation` command, `/isolation` command or policy file. Choices and their stored boolean mapping belong to the [process isolation design](host-platform-isolation.md#process-isolation), not to Permission. ```text Settings Permission Workspace write > Process isolation > Applies to new processes and persistent shells. ``` Opening the control reads `{source: "execution", scope: "user"}` through [`settings.read`](appserver/settings-service.md) and retains its CAS revision. If advertised, `host.execution.backend {}` supplies `{available: true}` or `{available: false, reason}`; show the daemon's reason without inferring availability from the client platform. The choices remain editable. ↑↓/Tab moves, Enter selects, Esc returns. Saving uses `settings.update` with the read revision; a conflict is shown without retrying the user's choice against unseen data. Reload discards the draft and reads again. Loading disables mutation; unsupported methods report an unsupported-daemon error, never a local fallback. Reset requires confirmation and writes both booleans false. Off may retain the old inert Network value; either sandboxed choice writes its own network value explicitly. This is user-scope host configuration, not branch policy or the boundary of a running command. A running process, including a persistent shell, keeps its startup boundary. An unavailable backend still refuses the next sandboxed start as `isolation_unavailable`; displaying the reason does not weaken enforcement. The existing backend and execution source are reused. A model permitted to write the settings source can change the next process's boundary, just as a user can through this control; that write creates no grant and changes no running process ([ADR 011 D2](../adr/011-execution-settings.md)). File access is not OS isolation for shell code. ## Management surfaces A management area is one owner's management methods. The App Server serves each area through its owner and gives clients no generic path across them ([settings service](appserver/settings-service.md#11-what-this-does-not-do)): Skills are `skills.*`, Hooks are `hooks.*`, MCP is `mcp.*`, the Scheduler is `schedule.*`, Workflow definitions are `workflow.*`, command rules are `permission.rules` and `permission.set_rules`, plugins are PluginManager's methods, and one Provider's models are `provider.*`. Their entry shapes differ and stay as they are; this section makes one structure out of the client side, so an area is added once instead of once per surface. Each area has two surfaces in the native client: - a terminal UI panel, `/`, in both TUI modes, following the [command menu contract](#command-menu); - a subcommand, `dimcode `. Both surfaces use the same area adapter. `dimcode hooks list|enable|disable` keeps its output and exit codes, except that a source's state line is worded once for the panel and the command; `dimcode hooks edit` and `dimcode hooks set PATH` write through the same adapter. Management text is composed in the interface language on both surfaces. The panel's text follows `/language` from the next reply, and `dimcode ` uses the saved language, else the system's. ### The adapter An adapter talks to the daemon through the client connection, so a subcommand needs no Session. It turns its owner's reply into one client view at that boundary, it performs the area's actions, and it refuses an action whose methods the daemon does not advertise, with an unsupported-daemon error. That refusal names the area when the daemon does not serve its read, and names the kind when it serves the read without the kind's method: `This daemon lists scheduled jobs but cannot remove them.` It never falls back to a file, to another method or to the host's own environment. The client infers nothing the methods do not report. A view that needs a fact the reply does not carry gets that fact from the producer: `skills.catalog` reports each entry's `source`, and `hooks.catalog` reports the user document with its revision. A daemon older than one of those facts gets the area without it: Skills that report no `source` are listed under the area's own heading, `skills`, and Hooks that report no `userDocument` offer no edit. The client never derives such a fact from an id, a path or anything else, so it needs no feature to recognise the newer contract. ### The view Entries are grouped under source headings — user, project, `plugin:`, built-in — as the owner reports them; a hook source is itself an entry, under the heading `sources`. Each entry has a label, a detail, its states (enabled, disabled, invalid, unavailable, shadowed, read-only) and the actions it offers. An entry its owner does not let the client write (plugin-owned, locked) offers no write action, and its detail says why. What the owner could not use is listed after the entries as its issues. An entry may carry the operation's input document when the run kind takes one: Ctrl+T opens the external editor on it first, and sends what it leaves with the run. ```text ╭─ Skills ──────────────────────────────────────╮ │ > │ │ │ │ user │ │ › commit enabled │ │ review disabled shadowed by commit │ │ project │ │ deploy enabled │ │ Issues │ │ /w/.agents/skills/old/SKILL.md: no name │ │ Writes commit messages from the staged diff.│ │ /home/u/.agents/skills/commit/SKILL.md │ ╰──── Enter toggle · Ctrl+R reload · Esc close ─╯ ``` ### Actions The client has a closed set of action kinds. Each kind has one key in every panel, and the hint lists the area's kinds plus the selected entry's kinds, in the table's order. An action that names one entry carries the entry's id and the source its row reports. Two entries of one id in different sources are two rows, each selected and acted on on its own, and the adapter acts on the source the action names rather than searching its catalog for one. An area whose owner reports no source leaves the source out, and its ids are unique on their own. | Kind | Key | What it does | | --- | --- | --- | | toggle | Enter or Space | Enable or disable the entry, as in [Execution settings](#execution-settings). | | edit | Ctrl+E | Open the area's document in the external editor, then write it. | | reload | Ctrl+R | Re-read the area, or ask the owner to rescan it, as `skills.refresh` does. | | add | Ctrl+N | Create an entry. | | remove | Ctrl+X | Remove the entry, after a confirmation, as `/models` clears a saved record. | | run | Ctrl+T | An area's named operation, such as test, run now or start. | | inspect | Tab | A read-only detail view. | Esc closes the panel. An action key is a Ctrl chord, or a key the filter never takes, because the filter takes every plain letter. A kind that acts on the area itself belongs to the area's view rather than to one entry — reload, and add, which creates one — so it needs no selection: the hint lists it whether or not an entry is selected, and Ctrl+R reads the area again after a failed read. The first two areas use toggle, reload and edit. The Scheduler area introduces add, remove, run and inspect, and the client's kinds are what it writes; a later area adds its adapter and the methods its kinds call, and names here the kinds it adds. The hint names the run kind's operation where an area names one: MCP's hint reads `Ctrl+T test`. A hint the bottom border cannot hold is drawn as rows above a plain bottom border, wrapped at the ` · ` separators; a part wider than a row wraps across rows by display width, so no key or action is ever cut. The rows take their space from the list like the footer's, so every key stays visible at any width, the remove confirmation included. - add opens the external editor on a new-entry document that the adapter supplies — the owner's create shape, filled with example values — and writes it with the area's create method. An unchanged document writes nothing, and a refusal keeps the draft, which the next Ctrl+N reopens, as Ctrl+E does for an edit. A conflict keeps it too: a create has no revision, so dropping the draft's link to a stale revision is an edit's rule, not a create's. An add that appends an entry to a list it read, as the command rules area's does, retries the conflict instead, because an append overwrites nothing. `dimcode add PATH` (`-` for stdin) is the same write without an editor. - remove asks in the hint first, with the verb the entry reports: `Remove