RuimteDocs

Add a button

A button in a terminal's header and an item in the canvas menu, each running your action on what it stands on.

This page builds two actions. "Explain the failures" sits in a terminal's header and asks an agent why the tests in it fail. "Summarize this canvas" sits in the canvas menu and writes a summary note.

The files

manifest.json
assets/icon.svg
client/index.ts

Each permission is for one call below. The person reads the reasons before installing:

{
    "id": "acme.explain",
    "name": "Explain",
    "version": "1.0.0",
    "api": 1,
    "publisher": "Acme",
    "description": "Asks an agent about a terminal or a canvas.",
    "color": "#7c3aed",
    "permissions": {
        "terminals.read": "Reads the terminal you ask about",
        "context.read": "Reads the canvas you ask about",
        "agents.run": "Asks one of your agents",
        "canvas.write": "Writes the answer as a note"
    }
}

A button in a node's header

In client/index.ts, defineAction names its slots. For each slot, a function decides whether the action shows there:

import { agents, canvas, context, defineAction, terminals } from '@ruimte/extension/client';

defineAction({
    id: 'explain',
    title: 'Explain the failures',
    icon: 'sparkles',
    slots: {
        nodeHeader: ({ node }) => node.kind === 'terminal',
        terminalMenu: true
    },
    async run({ node }) {
        const screen = await terminals.read(node);

        if (!screen.ok) {
            throw new Error(screen.error.message);
        }

        const answer = await agents.run({
            prompt: 'Explain why the tests in this terminal fail.',
            context: [screen.value]
        });

        if (!answer.ok) {
            throw new Error(answer.error.message);
        }

        canvas.createNote({
            title: 'Why it fails',
            text: answer.value.text,
            near: [node]
        });
    }
});

Each call answers a Result (When a call fails). Here a failed one throws, since an action shows what it throws to the person.

A header is tight, so the action shows as its icon alone, with the title as its tooltip. Without an icon it gets a package symbol. terminalMenu puts the same action in the terminal's right-click menu, with its title in words.

While run is pending, the node shows a spinner in its header, and the button waits. Pointing at the spinner turns it into a stop button, which aborts the signal that run gets. A run that throws shows the error to the person.

An item in the canvas menu

canvasMenu puts an item at the foot of the menu a right-click on the empty canvas opens, below Ruimte's own items:

defineAction({
    id: 'summarize',
    title: 'Summarize this canvas',
    icon: 'sparkles',
    slots: {
        canvasMenu: ({ view }) => view?.kind === 'canvas'
    },
    async run({ view }) {
        if (view === null) {
            return;
        }

        const sources = await context.read({
            view
        });

        if (!sources.ok) {
            throw new Error(sources.error.message);
        }

        const answer = await agents.run({
            prompt: 'Summarize this canvas in five lines.',
            context: sources.value.map((source) => `# ${source.title}\n${source.text}`)
        });

        if (!answer.ok) {
            throw new Error(answer.error.message);
        }

        const note = canvas.createNote({
            title: 'Summary',
            text: answer.value.text
        });

        if (note.ok) {
            canvas.reveal(note.value);
        }
    }
});

Every slot also tells the base context: the project, the view in front, the machine and the theme. canvasMenu adds at, the spot the person clicked on the canvas.

Change what it shows

A slot function can answer an object instead of true, to change the action at that place only:

slots: {
    nodeHeader: ({ node }) => node.kind === 'terminal' && {
        title: `Explain ${node.title}`,
        disabled: node.agent?.status === 'running' && 'Wait until the agent is done'
    }
}

disabled with a text greys the button out and shows the text as its tooltip. badge shows a short text beside the title in a bar with room for words, such as the selection bar. order sorts the entries of a slot, lowest first, 0 when you leave it out.

More places

The same action can go in more slots at once: nodeMenu for a right-click on any node, selectionBar for the bar over a selection, commandPalette for the palette. Slots and actions lists every slot and what it tells. While bun run dev runs, "Show extension slots" in the command palette outlines each one on screen.