-
Notifications
You must be signed in to change notification settings - Fork 0
The API
GET /api/prefs is the sixty keys the browser used to keep to itself: the desks, where every
window sits, the prompt library, the placeholder sets, the shortcuts, which viewer each
extension gets. It moved because of two things people ask for — a desk made at the desk should
exist on the phone, and something that is not a browser should be able to read the workspace:
an agent that has just started three jobs could lay out a desk to watch them in, and a script
that sends prompts should be able to read the library they come from.
GET /api/prefs |
{version, prefs} |
PATCH /api/prefs |
{changes: {key: value}} — merges those keys and leaves the rest; a null removes one |
PUT /api/prefs |
{version, prefs} — the whole document, refused with 409 if the version has moved |
Why a merge and not a replacement. Last-write-wins on the whole document loses the desk made on the phone the moment a laptop saves an older copy of everything, which is exactly the case this exists for. The browser therefore sends only the keys it has touched since it loaded. Two devices editing different things both keep their change; the same key still resolves to whoever wrote last, and there is no honest way around that without asking a person which they meant.
Agents may read it and not write it. The prompt library is worth reading; an agent quietly rearranging your windows is a different proposition, and one line away if it is ever wanted.
Settings → Go to → **The API, live**, or /api/docs with your token. Every route with its
shape, a box to find one among fifty, and Try it out that really calls it — carrying the
token the page was opened with, so it answers rather than 401s.
Two decisions in it. It is behind the token, under /api/ like everything else, because
the route list of a server that holds a shell is not a thing to publish. And the bundle is
vendored, like xterm and pdf.js: FastAPI will serve this in one line and that line fetches
its JavaScript from a CDN, which is no use on the machines Argus usually lives on. The token
never appears on the page — it travels in a header, not in the spec's address, so a screenshot
of this page is not a leak.
For an agent rather than a person, see An agent driving Argus.
Everything Argus does, it does through its API — the browser is one client of it, not a privileged one. That is the extension story: a script, a cron job, an agent hook or another machine can do anything the app can, with no plugin to install and nothing running inside the page.
The routes, described — or
openapi.json for Swagger UI, Postman
or a client generator. A running server serves its own at /api/openapi.json, behind the
token like everything else.
Two rules hold throughout: one token, in an Authorization: Bearer … header or a
?token= query where a header cannot go; and everything under /api, the description
included, so a single rule guards the lot.
The page is generated from the routes themselves, and a test fails when it drifts.
One token means losing a phone costs you every device: the only remedy is rotating the one secret they all share. So each device can have its own.
Settings → Devices → Add a device, give it a name, and you get a link and a QR code once. Only a hash is kept — the same bargain GitHub makes for a personal access token, and for the same reason: losing the file must not hand anyone a working key.
- Revoking one leaves the others alone, and takes effect on that device's next request. Nothing to restart: the list is re-read every time a token is presented, which is the only reason revocation is a feature rather than a paragraph.
- A device may do the work and may not manage devices. Only the token in the config can add or revoke. A phone that is lost cannot mint itself a second key, and cannot lock you out of your own machine.
- Renaming does not sign anything out. "phone" becomes "old phone" the day a new one arrives, and a list you cannot correct is one you stop trusting when it matters.
- The list says when each was last used, written at most once a minute — the useful answer is "yesterday or today", not which second.
There is deliberately no bcrypt or argon2 on those hashes. Slow hashes exist to make guessable secrets expensive to attack; these are 256 bits of randomness, and a slow hash would only make every request slower.
Journal, in the sidebar. It answers one question — has somebody been in here — and it is built around that rather than around a table.
What goes in: everything that changed something, and everything that was refused, whatever method it used. What does not: successful reads. Listing a folder every four seconds while you scroll is most of the traffic and none of the interesting part, and a file full of it is a file nobody opens.
Each line has the action, which key did it (the config token, a device by name, or a board),
the address it came from, the status and the time. Behind a reverse proxy the peer is always
loopback and the real client is in a header, so both are kept and shown apart: a header is
whatever the sender wrote, and reading a log without knowing which of the two you are looking
at is worse than seeing one.
Refusals from one address are collapsed — a scanner can produce thousands a minute, and a journal that rotates its own history away while being flooded is worse than none — but the count is kept, so twelve knocks read as twelve.
Filters at the top: Everything, Refused, Changes, and a box that matches the address, the key and the action at once. Only the token from the config can read it: a record a stolen device could read is a record that tells whoever took it what you can see.
Emptying it is deliberate and says how much went: everything, or only what is older than a day, two days or a week. Nothing empties itself on a schedule — the whole value of the file is that it is still there when you finally think to look.
It is not tamper-proof, and does not pretend to be. Anything holding the master token can delete the file, and a shell on the machine certainly can. It answers "what happened" for a person looking back, which is the question that actually comes up.
GET /api/runs · POST /api/runs
A noticeboard, and only that: what the machine is orchestrating right now, so a browser can draw it and a board can count it. Held in memory, sixteen at a time, dropping finished ones before running ones — a cap that drops by age alone throws away the run you are watching in favour of one that ended twenty minutes ago.
Nothing here starts, stops or reaches an agent. Posting to it changes nothing about the work; the orchestration goes on unchanged with every browser shut, which is why an agent key may write to it.
A run that claims to be running and has not been heard from in five minutes reads as gone
rather than running. The framework beats once a minute while it waits, so the silence means
the watcher stopped — not the agents, which nothing here ever reached.
It also appears, as a count rather than a graph, under runs in GET /api/overview, so a
board sweeping ten machines learns that one of them is orchestrating without asking twice. The
key is absent entirely when nothing is running.
POST /api/tmux/launch takes one more field for the same reason: desk: true tells
whoever has the app open that a session started, so it lands on their desk instead of waiting
to be found in a list. It reaches only pages open at that moment; nothing is queued.
GET /api/network
The hostname, the user, every address this machine answers on with the routed one first, the address the request came from, and the ssh line for reaching a loopback port. Read entirely from this machine and this request: no network call is made, ever.
POST /api/network/outside
The only thing in Argus that tells somebody else anything, and a POST for that reason — it is
an action with a consequence rather than a reading. It asks one of two public services what
this machine's address looks like from outside, and asking that is telling them. Nothing does
it on a timer or on anybody's behalf, the answer is not written down, and ask_outside: false
refuses it outright.