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>(beforesh): 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 withPOST 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(defaultid) 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'sdisabledWhen) 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>'inconnect.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: trueon the action asks for a second tap within 15 s on the board;confirm: trueinconnect.ymlasks 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). … actionsand the Connect panel list keyed names too, and warn aboutconnect.ymlnames 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: trueneeds 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.ymllike 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.