What is waiting on an answer, what you have agreed to this week, and what you did last week — in the Omarchy bar.
Running on fixture data — the panel says so in its header whenever it is.
The bar carries a football and the number of invitations waiting on you, falling back to the time of today's next commitment when nothing is waiting. The panel has three sections:
- Wants an answer — invitations and availability requests nobody has replied to, each with Going / Can't. Answering takes two clicks: the first chooses, the second sends. What you answer is something other people act on, so a mis-click should not be able to tell a coach you are not coming.
- Next 7 days — what has been accepted, by day.
- Last 7 days — what has finished, and what was answered to each.
Left click opens the panel, middle click refreshes, right click opens Spond in a browser.
- Omarchy 4.0 or newer (the shell plugin system)
curlandjq— both already on an Omarchy boxlibsecret(secret-tool) if you want the password in your login keyring, which you do; without it, it goes in a 0600 file
omarchy plugin add https://github.com/digitaljohn/omarchy-spond --enableThen open the panel and sign in: email, password, Sign in. From a terminal, if you would rather:
~/.config/omarchy/plugins/digitaljohn.spond/bin/spond loginSign out first — that is what clears the stored password, the keyring entry and the cached token. Then remove the plugin:
~/.config/omarchy/plugins/digitaljohn.spond/bin/spond logout
omarchy plugin remove digitaljohn.spondSigning out from the panel's footer does the same as logout. If the plugin
directory has already gone, secret-tool clear service omarchy-spond account <your email> and rm -rf ~/.config/omarchy-spond ~/.cache/omarchy-spond
finish the job.
Spond publishes no API and has no way to authorise an application: no OAuth, no app tokens, no scopes. Its own apps sign in with an email and a password and get a bearer token that expires, and there is no refresh token to renew it with. So staying signed in means keeping the password, and it is worth knowing exactly where that goes:
- into your login keyring through libsecret when a keyring is running;
otherwise into
~/.config/omarchy-spond/password, mode 0600, in a 0700 directory; - to Spond over HTTPS, and nowhere else — no telemetry, no third party;
- never into a command line, because
/proc/<pid>/cmdlineis readable by every process on the machine. It reachescurlthrough a private temporary file and the panel's sign-in reaches the script down a pipe. The access token minted from it gets the same treatment — it is the account until it expires, so it never appears as a header argument either; - never into the QML beyond the moment between the click and that pipe;
- never into the terminal — nothing here prints it, including
status.
The access token is cached in ~/.config/omarchy-spond/access-token and minted
again from the password when it expires.
The token is checked before it is stored and again when it is read back off
disk: base64 alphabet only, 16 to 4096 characters, nothing else. It arrives from
whatever answered the login and is cached in a file something else could edit,
and it is handed to curl, whose config file is a grammar — a token carrying a
quote and a newline could otherwise close the header value and start a directive
of its own. On curl 8.3 and later the bytes never reach that grammar at all:
they travel in the environment and are substituted after parsing by
--expand-header. Older curl gets a config file on stdin, which only ever sees
a token that passed the check. test/token-injection covers both, and fails
loudly if the check is ever removed.
Replies are bounded before they are read. Every request caps the response at
8 MiB three ways — --max-filesize for a declared length, ulimit -f for a
chunked stream that just keeps coming, and a size check before a single byte
reaches a shell variable — so nothing on the other end of the connection gets to
decide how much memory this allocates. Individual fields are clipped too.
Accounts with two-factor authentication cannot sign in here. A password is the only credential this can present; if Spond asks for a second factor, the panel says so and stops.
Like every Omarchy plugin, this runs unsandboxed as you — though it never asks
for elevated privileges, installs nothing, and touches no file outside its own
two directories. It talks to exactly one host, api.spond.com. It is about 600
lines of bash and 1,200 of QML, and both are worth a read before you trust them
with an account.
From the bar widget's settings, or ~/.config/omarchy/shell.json:
| Setting | Default | What it does |
|---|---|---|
scheduleDays |
7 | How far ahead the schedule section runs |
requestDays |
30 | How far ahead to look for unanswered invitations |
historyDays |
7 | How far back the history section runs; 0 turns it off |
pollMinutes |
15 | How often to ask Spond; opening the panel always refetches |
groupId |
— | Restrict to one group; ids from bin/spond groups |
barStyle |
Requests, then next | What the bar says |
barIcon |
Change it if your bar font draws a box | |
panelWidth |
360 | Panel width, in the shell's spacing units |
showLocation |
on | Venue beside each event |
hideWhenIdle |
off | Leave the bar entirely when nothing is on |
webUrl |
spond.com/client |
Where "Open Spond" goes |
bin/spond is the whole of the Spond side and is usable on its own. Every
command prints one JSON object, failures included, so nothing has to read an
exit code:
spond login [--email you@example.com] Sign in and remember the account
spond login --stdin Email and password on two stdin lines
spond logout Forget the account and the token
spond status Whether it is signed in, and as whom
spond groups The groups this account belongs to
spond events [--days N] [--past N] [--max N] [--group ID] [--include-cancelled]
spond respond <eventId> yes|no [--member ID]
events comes back with the response already worked out, which is less obvious
than it sounds. Spond files a response against a member, and the member is
often not you: a parent belongs to the group as the guardian of a child, and it
is the child's member id that appears in an event's response lists. An account
that matched only its own profile would find nothing and could say nothing about
any event.
So the script first works out every member it can answer for — you by profile,
you by email, or anyone you are guardian of — from groups/, and caches that
mapping for twelve hours. An event that concerns two of your children is two
answers, so the panel gives it a row each, named, with its own pair of buttons.
Names appear only when there is more than one person to tell apart: the same
child in two of a club's groups is two member ids and one person.
Your answer, not your attendance. Spond's API says what was replied; whether
anyone actually turned up is between them and the coach. yes means accepted,
no means declined, no answer means the invitation expired unanswered.
Put a captured sponds/ response at ~/.config/omarchy-spond/fixture.json and
the script answers out of it without touching the network.
OMARCHY_SPOND_FIXTURE=<file> does the same for a single run, and
OMARCHY_SPOND_IDENTITY=<json> supplies the members to match it against. The
panel's header reads SPOND · FIXTURE for as long as one is in use, because
test data that cannot be told apart from the real thing is a trap rather than a
fixture.
test/token-injectionCovers the credential parser boundary: quote, newline, backslash, whitespace,
control-character and over-length payloads, a poisoned cached token file, and
whichever of the two curl transports this machine uses. No account needed for
any of it except the last check, which skips when you are not signed in.
https://api.spond.com/core/v1/ — the one Spond's own apps use. It is
undocumented and unsupported, and it can change without notice; when it does,
this breaks. Endpoints were taken from Olen/Spond,
the Python client that has been tracking them for years.
Not affiliated with, endorsed by, or supported by Spond.
MIT — see LICENSE.
