Connector

Buttons that run things on your computer

Press a button on your dashboard — from your phone, anywhere — and a command runs on your computer. The Connector is one small file (Node 18+, no dependencies) that keeps an outbound connection to the board: no port forwarding, no inbound access, works behind NAT and firewalls.

1. Install (one line)

In the board: Connect → Run things on your computer → Set up the Connector. It shows a command with this board's connector token, e.g.:

curl -fsSL https://instant-gui.com/connect.sh | sh -s -- --board <board> --token <igc_…>

Windows (PowerShell): $env:IG_BOARD="<board>"; $env:IG_TOKEN="<igc_…>"; irm https://instant-gui.com/connect.ps1 | iex

It downloads ~/.instantgui/bin/instantgui-connect.mjs, writes ~/.instantgui/connect.yml (chmod 600) and starts it at login (macOS LaunchAgent, Linux systemd user service, Windows scheduled task).

  • --no-service: don't start at login (run it yourself: node ~/.instantgui/bin/instantgui-connect.mjs).
  • INSTANTGUI_HOME=<dir> (before sh): install a separate connector in <dir> instead of ~/.instantgui — e.g. curl -fsSL https://instant-gui.com/connect.sh | INSTANTGUI_HOME=/tmp/ig sh -s -- --board <board> --token <igc_…> --no-service. Agents can create the token with POST https://instant-gui.com/api/v1/boards/<board>/connectors (edit key) — the response includes the install commands.

2. Say what each button may run

~/.instantgui/connect.yml — nothing runs unless it is listed here:

url: https://instant-gui.com
board: <board>
token: igc_…
report: actions-log            # optional: a log widget that gets one line per result

actions:
  deploy:                      # a button with key "deploy"
    label: Deploy site
    run: cd ~/site && git pull && npm run build
    confirm: true              # press twice within 15 s
    timeout: 300
  server/restart:              # an "actions" widget "server", button id "restart"
    run: sudo systemctl restart nginx
  focus:                       # a text input with key "focus"
    run: ~/bin/set-focus "$INSTANTGUI_VALUE"
    output: focus-output       # a markdown/log widget that shows the command's output
  jobs/retry:                  # a row action "retry" on table "jobs": the row's key is the value
    label: Retry job
    run: ~/bin/retry-job "$INSTANTGUI_VALUE"
    value: '^[0-9]{1,9}$'      # anything else is refused before the command runs

Action names: <widget key> for button, toggle, slider, input and select; <widget key>/<button id> for an actions widget. The control's value (toggle on/off, slider number, input text) is in $INSTANTGUI_VALUE — always quote it. The file is re-read when it changes. Write each action as a block (as above); inline {run: …} maps are not supported. … actions checks every name against the board and warns about names no control sends (and missing report/output widgets) — the same warnings show in the Connect panel and in GET /api/v1/boards/<board>/actions. Tip: end a silent command with && echo "<summary>" so the board shows what happened; a command with no output still logs "(no output)" and updates the button's last-run time and count.

Row and record actions (keyed)

A button on every row of a table (rowActions) or on a record widget (actions, and toggles bound to a field) sends <widget key>/<action id> with the row's key as the value — e.g. "Retry" on the row whose id is task-12 runs issues/retry with $INSTANTGUI_VALUE=task-12.

{ "key": "jobs", "type": "table", "config": { "rowKey": "id", "viewersCanUse": false,
  "rowActions": [
    { "id": "retry",  "label": "Retry",  "tone": "warning", "when": [{ "field": "status", "op": "in", "value": ["failed", "reverted"] }] },
    { "id": "cancel", "label": "Cancel", "tone": "danger", "confirm": true, "when": [{ "field": "status", "op": "eq", "value": "queued" }] } ] } }
  • The row key is config.rowKey (default id) of an inline row, or the record key of the widget's dataset.
  • The server decides, not the browser: it re-reads the record from the dataset (or the table's rows) and refuses the click when the key doesn't exist or the action's when (a toggle's disabledWhen) fails on its own copy — a stale page can't retry a task that already succeeded.
  • Guard the value on your machine: give every keyed action value: '<regex>' in connect.yml. The whole value must match; anything else is refused and reported before the command runs (and before a confirm is armed).
  • Each row shows its own status line: "Sending “Retry”…" → "✓ Retry · 2s ago" or "✗ Retry failed: · 1m ago" (kept on data.runs["<action id>:<key>"], newest 50). confirm: true on the action asks for a second tap within 15 s on the board; confirm: true in connect.yml asks again from the machine (per row). Use one of the two, not both (both = three taps).
  • No connector handling the name = the click is refused ("No computer is connected…"). Editors only unless the widget sets viewersCanUse: true; embeds made with "can use controls" work too. Limits as below (30 a minute).
  • … actions and the Connect panel list keyed names too, and warn about connect.yml names no row action sends.

3. See it work

The Connect panel shows the connector online/offline, when it was last seen, and every action with its last result (✓/✗, exit code, first line of output). report:/output: widgets show results on the board itself. Locally: node ~/.instantgui/bin/instantgui-connect.mjs test deploy runs an action without the board; … actions lists them; … service status|uninstall.

Security — what the board can and can't do

  • The board never sends commands. A click sends an action name and a value. The Connector runs only names in its own config; unknown names are refused and reported.
  • Values are data, not code: passed only as $INSTANTGUI_VALUE, never pasted into the command line.
  • Signed, fresh events: each event is HMAC-SHA256-signed with a key derived from your connector token and ignored if older than 60 s (a click made while your machine was asleep doesn't fire hours later).
  • Who can press: controls are editor-only unless a widget sets viewersCanUse: true — anyone who can press a button can trigger its action, so only allow viewers on harmless actions.
  • Limits: 30 queued clicks a minute per board on the server; on the machine one run per action at a time, 2 s between runs, 20 runs a minute; confirm: true needs two presses; value: '<regex>' refuses any other value.
  • Revocable: Connect → Connector → revoke (or DELETE /api/v1/boards/<board>/connectors/<id>); the token stops working at once. A connector token can only report results to this board — it can't edit it or read other boards.
  • Treat connect.yml like an SSH key: it holds the token and decides what runs. Don't list commands you wouldn't run from your phone.

API (for agents)

Call Auth
POST /api/v1/boards/<board>/connectors {"name"} edit key token (once) + install commands
GET /api/v1/boards/<board>/connectors edit key / write token connectors, online, actions + last results, recent events
DELETE /api/v1/boards/<board>/connectors/<id> edit key revoke
GET /api/v1/boards/<board>/actions edit key / write token actions the connectors handle
POST /api/v1/boards/<board>/actions {"action","value"} edit key / write token run an action (202 + event id)
POST …/connector/hello, GET …/connector/events?wait=25, POST …/connector/events/<id>/result connector token the Connector's own protocol

In a board spec, actions are just control widgets (actions, button, toggle…) — their keys/button ids are the action names; row/record actions are <widget key>/<action id> with the record key as the value (POST …/actions {"action":"jobs/retry","value":"42"} runs one directly). MCP: list_actions, trigger_action.