Skip to content
miblo

SDK

Sessions, alerts and status: the Miblo status API

Every method of Miblo's status API: session, needsYou, done, say, focus, timer and snapshot, with parameters, answers, errors and a complete example.

The client

Everything starts with a MibloStatus. It holds the token and the port and signs every request; it opens no connection until it is first used.

import { MibloStatus } from '@miblo/status';

const miblo = new MibloStatus({ token, port, timeoutMs });   // all optional
ParameterDefaultWhat it is
tokenMIBLO_TOKENThe token from miblo sdk token. Without it, the constructor throws no_token right away.
portMIBLO_PORT, else 47821The bridge's port. The address is always 127.0.0.1.
timeoutMs / timeout3000 ms / 3.0 sHow long to wait for each answer.

hello()

Checks the token and Miblo's version without changing anything: { api, version } (for example { api: 1, version: "1.25.0" }). Handy to check the setup when your program starts.

const { api, version } = await miblo.hello();

How each request is signed

You do none of this, the packages do: before each request the client asks for a challenge (GET /sdk/v1/hello with a random nonce), checks that the bridge proved it knows the token, signs the request with HMAC-SHA256 (method, path and the SHA-256 of the body) and, on the way back, checks the answer's signature. A program that takes Miblo's port while Miblo is down cannot prove the token, and the client sends it nothing. The step by step is in the reference.

Sessions

A session is your program in Miblo's list, next to the AI tools' sessions.

session()

const job = await miblo.session({ tool, title, cwd, state, detail, pid });
FieldRequiredRules
toolyesYour program's name, 1 to 20 characters, one line. It cannot be a Claude Code tool's name (Bash, Edit, Read, Task, Agent...) or start with _.
titlenoThe session's name on the gadget (up to 64 characters; the gadget shows about 20). Without it, the folder of cwd names it; without either, tool does. Repeated names get " 2", " 3".
cwdnoA folder; its name becomes the title when there is no title.
statenoworking (default), needs_you, idle or done.
detailnoOne short line (up to 64 characters; longer is cut). It is the activity while working and the reason while it needs you.
pidnoThe process whose exit ends the session. Default: your program itself. null (TS) or None (Python): none, and the session expires after 12 h without an update (2 h once idle or done).

Returns a Session with id (sdk- + 32 hex characters, made by the bridge) and state. Only sdk- ids are accepted afterwards, so a program can never change or end an AI tool's session.

Changing the state

TypeScriptPythonStateOn the gadget
job.working(detail?)job.working(detail=None)workingThe line reads tool · detail.
job.needsYou(reason?)job.needs_you(reason=None)needs_youThe question alert, once per change; counts in "needs you".
job.idle(detail?)job.idle(detail=None)idleIdle, no alert.
job.done(detail?)job.done(detail=None)doneThe finished alert, once per change.
job.set(state, detail?)job.set(state, detail=None)anyThe same, choosing the state.
job.end()job.end()(gone)Takes the session off Miblo: { id, ended: true }.

Each call returns { id, state }. Meeting and discreet modes hide the detail, as they do for every session. Sessions survive a bridge update but not a computer restart.

Messages, focus and timer

The same actions as miblo say, miblo focus and miblo timer, with the gadget's own limits.

TypeScriptPythonRules
say(text, { minutes })say(text, minutes=None)One line, up to 40 characters (47 UTF-8 bytes), for 1 to 480 minutes (default 30). It cannot start like Miblo's own notices ("Approved:", "Phone:", "Miblo:", "Allow?"... in English and Portuguese).
sayOff()say_off()Takes the message off.
focus({ focusMin, breakMin, rounds })focus(focus_min=None, break_min=None, rounds=None)Focus 5 to 120 min, break 1 to 60, 1 to 12 rounds.
focusStop()focus_stop()Stops the focus session.
timer(minutes)timer(minutes)1 to 180 minutes.
timerStop()timer_stop()Stops the timer.

Each returns { ok, delivered, gadgets }: delivered counts the gadgets that took it, and gadgets lists each paired gadget as { id, name, ok, error? }, where error is offline, unpaired, unsupported, rejected or busy. With no Miblo paired, delivered is 0, which is not an error. None of this becomes a desktop notification.

Snapshot

snapshot() reads who is working and who needs you, without changing anything.

const { sessions, gadgets } = await miblo.snapshot();
const waiting = sessions.filter((s) => s.state === 'needs_you');
  • sessions: [{ id, name, state, tool, sdk, since }]. An AI tool's session shows with an 8-character id and the tool's name in tool ("Claude Code", "Codex"...); sdk is true for programs' sessions; since is when it entered its state (epoch seconds).

  • gadgets: [{ id, name, online }].

  • It never carries a command, a file, a prompt, a cost, a limit or anything of Miblo+. Reads: 5 a second, bursts of 10.

Complete example

A build watcher: runs the command, shows its progress, alerts you when it fails and puts a message up when it passes.

// MIBLO_TOKEN=$(miblo sdk token) node build-watch.mjs
import { spawn } from 'node:child_process';
import { MibloStatus, MibloError } from '@miblo/status';

const miblo = new MibloStatus();
const job = await miblo.session({ tool: 'build', title: 'Site build', detail: 'npm run build' });

const code = await new Promise((resolve) => {
  spawn('npm', ['run', 'build'], { stdio: 'inherit' }).on('exit', resolve);
});

try {
  if (code === 0) {
    await job.done('passed');
    await miblo.say('Build green', { minutes: 10 });
  } else {
    await job.needsYou(`failed (exit ${code})`);
  }
} catch (e) {
  if (e instanceof MibloError && e.code === 'rate_limited') console.log(`try again in ${e.retryAfter} s`);
  else throw e;
}

Errors

Every error is a MibloError with:

  • code: stable, never renamed (list below);

  • status: the HTTP status, or 0 when the bridge was not reached;

  • field (on bad_request): the field at fault, such as tool or detail;

  • retryAfter (TS) / retry_after (Python) on rate_limited: how many seconds to wait.

codeStatusWhen
bad_request400A field out of range or of the wrong type (see field)
bad_json400The body is not JSON
sdk_off401No token on this computer
not_authorised401Wrong or rotated token
unknown_session404The session ended, expired or is not an SDK session
too_many_sessions4098 SDK sessions are live
too_large413Body over 4096 bytes (the client refuses it before sending)
rate_limited429Over a limit; wait retryAfter seconds
internal500A bug in Miblo: please tell us

On the client's side: no_token, bridge_unavailable, timeout and unverified_answer (see Install).

Low-level calls

call(method, path, body?, opts?) makes one signed request to any /sdk/v1/* route and returns the answer's JSON (throws MibloError on an error). A Uint8Array (TS) or bytes (Python) goes as application/octet-stream. Use it for new routes before the package has a method for them.

const screen = await miblo.call('GET', '/sdk/v1/screen', undefined, { tool: 'build' });