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 optionalfrom miblo_status import MibloStatus
miblo = MibloStatus(token=None, port=None, timeout=3.0) # all optional| Parameter | Default | What it is |
|---|---|---|
token | MIBLO_TOKEN | The token from miblo sdk token. Without it, the constructor throws no_token right away. |
port | MIBLO_PORT, else 47821 | The bridge's port. The address is always 127.0.0.1. |
timeoutMs / timeout | 3000 ms / 3.0 s | How 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();info = miblo.hello() # {"api": 1, "version": "1.25.0"}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 });job = miblo.session(tool, title=None, cwd=None, state="working", detail=None, pid=-1)| Field | Required | Rules |
|---|---|---|
tool | yes | Your 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 _. |
title | no | The 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". |
cwd | no | A folder; its name becomes the title when there is no title. |
state | no | working (default), needs_you, idle or done. |
detail | no | One short line (up to 64 characters; longer is cut). It is the activity while working and the reason while it needs you. |
pid | no | The 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
| TypeScript | Python | State | On the gadget |
|---|---|---|---|
job.working(detail?) | job.working(detail=None) | working | The line reads tool · detail. |
job.needsYou(reason?) | job.needs_you(reason=None) | needs_you | The question alert, once per change; counts in "needs you". |
job.idle(detail?) | job.idle(detail=None) | idle | Idle, no alert. |
job.done(detail?) | job.done(detail=None) | done | The finished alert, once per change. |
job.set(state, detail?) | job.set(state, detail=None) | any | The 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.
| TypeScript | Python | Rules |
|---|---|---|
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');snap = miblo.snapshot()
waiting = [s for s in snap["sessions"] if 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 intool("Claude Code","Codex"...);sdkistruefor programs' sessions;sinceis 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;
}# MIBLO_TOKEN=$(miblo sdk token) python3 build_watch.py
import subprocess
from miblo_status import MibloStatus, MibloError
miblo = MibloStatus()
job = miblo.session("build", title="Site build", detail="npm run build")
code = subprocess.call(["npm", "run", "build"])
try:
if code == 0:
job.done("passed")
miblo.say("Build green", minutes=10)
else:
job.needs_you(f"failed (exit {code})")
except MibloError as e:
if e.code == "rate_limited":
print(f"try again in {e.retry_after} s")
else:
raiseErrors
Every error is a MibloError with:
code: stable, never renamed (list below);status: the HTTP status, or0when the bridge was not reached;field(onbad_request): the field at fault, such astoolordetail;retryAfter(TS) /retry_after(Python) onrate_limited: how many seconds to wait.
code | Status | When |
|---|---|---|
bad_request | 400 | A field out of range or of the wrong type (see field) |
bad_json | 400 | The body is not JSON |
sdk_off | 401 | No token on this computer |
not_authorised | 401 | Wrong or rotated token |
unknown_session | 404 | The session ended, expired or is not an SDK session |
too_many_sessions | 409 | 8 SDK sessions are live |
too_large | 413 | Body over 4096 bytes (the client refuses it before sending) |
rate_limited | 429 | Over a limit; wait retryAfter seconds |
internal | 500 | A 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' });screen = miblo.call("GET", "/sdk/v1/screen", tool="build")