Merges next into main: everything #2 asked for, plus two fixes.
On the branch:
An HTTP API beside the bot: list its chats, read a chat's recent messages, send a message, and register, list and remove webhooks on a chat; each new incoming message is POSTed to that chat's webhooks. Every request needs the bearer credential from the file named by API_TOKEN_FILE; without one configured, every request is refused.
The calculator takes ^ (or **) for powers and % for modulo, and answers exact results past a double's range, such as 2^1200 (#16).
cmd/simplexcalc/ is a single main.go; the command tree lives in internal/cli (#8).
A plain docker build . stamps the git tag or short commit instead of dev; .git goes into the build context without its config (#21).
Deploying: nothing is running. The Docker wipe on sandcastle on 2026-09-29 removed both the simplexcalc container and the simplexcalc-data volume, so the next deploy gets a new bot address. To deploy: build, write a credential onto the volume, and run with -p and -e API_TOKEN_FILE=... as the README shows. The volume also holds api-token and webhooks.json; both are secret and both belong in backups.
Behaviour change: a number too large or too small to hold exactly, such as 1e-1300 (which main answers as 0), is now refused rather than answered wrongly.
Model: opus-5-5
Merges `next` into `main`: everything https://git.eeqj.de/sneak/simplexcalc/issues/2 asked for, plus two fixes.
**On the branch:**
- An HTTP API beside the bot: list its chats, read a chat's recent messages, send a message, and register, list and remove webhooks on a chat; each new incoming message is POSTed to that chat's webhooks. Every request needs the bearer credential from the file named by `API_TOKEN_FILE`; without one configured, every request is refused.
- The calculator takes `^` (or `**`) for powers and `%` for modulo, and answers exact results past a double's range, such as `2^1200` (https://git.eeqj.de/sneak/simplexcalc/issues/16).
- `cmd/simplexcalc/` is a single `main.go`; the command tree lives in `internal/cli` (https://git.eeqj.de/sneak/simplexcalc/issues/8).
- The README clones from `sneak/simplexcalc` (https://git.eeqj.de/sneak/simplexcalc/issues/13).
- A plain `docker build .` stamps the git tag or short commit instead of `dev`; `.git` goes into the build context without its `config` (https://git.eeqj.de/sneak/simplexcalc/issues/21).
**Deploying:** nothing is running. The Docker wipe on sandcastle on 2026-09-29 removed both the `simplexcalc` container and the `simplexcalc-data` volume, so the next deploy gets a new bot address. To deploy: build, write a credential onto the volume, and run with `-p` and `-e API_TOKEN_FILE=...` as the README shows. The volume also holds `api-token` and `webhooks.json`; both are secret and both belong in backups.
**Behaviour change:** a number too large or too small to hold exactly, such as `1e-1300` (which `main` answers as `0`), is now refused rather than answered wrongly.
Model: opus-5-5
`cmd/simplexcalc/` now holds only `main.go`, whose body is one call, `os.Exit(cli.Main(version))`, as the Go style guide requires. The command tree and the `run` and `version` commands moved unchanged into `internal/cli`, with tests for `version` and for an unknown command. Output streams, exit statuses and the `-X main.version` build flag are as before.
Disclosure: the comment in `execute` saying cobra has already printed the error is false, since the root command silences cobra's errors; it moved unchanged and is left for a follow-up.
Model: opus-5-5
The calculator now takes `^` (or `**`) for powers and `%` for modulo. Powers bind tighter than a sign on their left and group to the right, so `-2^2` is `-4` and `2^3^2` is `512`; `%` sits with `*` and `/` and takes the sign of the divisor. A small parser of our own replaces `go/parser`, which cannot express `^`; `go/constant` still computes.
A whole-number exponent is exact; a fractional one is computed in float64. Every number is held as a fraction under 4096 bits, and a float64 result must be a normal double, so one message cannot stall the bot; anything else gets "That needs a number too large or too small for me."
Disclosure: tiny values below about 2.2e-308, which `next` answered, are now refused.
Model: opus-5-5
The bot now serves an HTTP API on `PORT` (default 8080) beside the chat client, whose WebSocket stays on 127.0.0.1 inside the container. Every request needs `Authorization: Bearer` with the credential from the file named by `API_TOKEN_FILE`, compared in constant time; with no credential configured every request is refused, `OPTIONS *` included. `GET /api/v1/chats` lists the bot's chats. Responses carry the security headers from the repository policies; bodies, requests and the server are time- and size-bounded. The chat client stops only after the API has finished its requests.
Disclosures: `contact_deleted` is an extra field; 404 and 405 answer in JSON; requests net/http cannot parse are refused by net/http without the security headers; three gosec findings are suppressed as false positives.
Model: opus-5-5
The repository moved to `sneak/simplexcalc`; the Getting Started clone command still named `clawbot/simplexcalc`, which only redirects. It was the only mention of the old location.
Model: opus-5-5
`GET /api/v1/chats/{id}/messages?count=N` returns a chat's recent messages, oldest first (`count` 1 to 100, default 20), and `POST` to the same path sends a text message and returns it once the chat client has taken it. Both answer `404` for any id the chats list does not show, looked up in the same contact list. The message record is defined once, for the webhooks to reuse.
Disclosures: items are read five at a time, because one item can be many times its text and 100 at once could pass the 16 MiB read limit; text too long gets `413`, a contact who deleted the chat `409` (unverified for one still connecting); a query the server cannot read gets its own `400`.
Model: opus-5-5
An external app can register webhooks on a chat (`POST /api/v1/chats/{id}/webhooks` with a URL), list them, and remove one. Registering the same URL again returns the existing registration. Registrations are kept in `$DATA_DIR/webhooks.json`, rewritten whole on each change through a file created 0600, synced and renamed, so a crash leaves the old file or the new one; a file present but unreadable stops startup. Delivery of incoming messages is the next unit.
Disclosures: the JSON body reading is now one helper shared with the send endpoint; gosec G304 is suppressed on reading the webhooks file; the 0600-from-creation claim rests on `os.CreateTemp`'s source, not a system-call trace.
Model: opus-5-5
`2^1200` was refused although the calculator computed it exactly: writing a result went through a float64 and refused anything outside a double's range. An exact result under the 4096-bit limit is now written from its exact value, rounded to 17 significant digits past that range, and a fractional power of such a number is computed by first bringing its base into range with square roots taken from the exact value. Results computed in float64 must still be normal doubles; past the 4096-bit limit is still refused.
Disclosures: `1e-310` is answered again, reversing a call made in PR 11; a fractional power carries `math.Pow`'s rounding, which for a large base can reach the last digits shown.
Model: opus-5-5
Each message a contact sends in a direct chat is posted to each webhook registered on that chat: `POST`, `Content-Type: application/json`, body `{"chat_id":N,"message":{...}}` with the same record the messages endpoint returns. The event handler only queues the message; four goroutines post it, one attempt each with a 10-second timeout and no retries, and with 100 already waiting a message is dropped and logged, so a slow webhook never delays the bot's replies. Posting stops last, after the chat client.
Disclosures: redirects are not followed; a logged failure can name the webhook's host but never its path, query or credentials; posts still in flight at shutdown are abandoned and logged.
Model: opus-5-5
A plain `docker build .` of a clone stamped `dev`: `.dockerignore` left
out `.git` and the build stage declared `ARG VERSION=dev`. `.git` now
reaches the build context without `.git/config`, which can hold a
credential, and the build stage takes the VERSION build argument when
given, otherwise `git describe --tags --always`. A context that carries
`.git` but yields no version fails the build.
`script/docker` is replaced with the current canonical copy, which
passes the version it derives on the host.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Merges
nextintomain: everything #2 asked for, plus two fixes.On the branch:
API_TOKEN_FILE; without one configured, every request is refused.^(or**) for powers and%for modulo, and answers exact results past a double's range, such as2^1200(#16).cmd/simplexcalc/is a singlemain.go; the command tree lives ininternal/cli(#8).sneak/simplexcalc(#13).docker build .stamps the git tag or short commit instead ofdev;.gitgoes into the build context without itsconfig(#21).Deploying: nothing is running. The Docker wipe on sandcastle on 2026-09-29 removed both the
simplexcalccontainer and thesimplexcalc-datavolume, so the next deploy gets a new bot address. To deploy: build, write a credential onto the volume, and run with-pand-e API_TOKEN_FILE=...as the README shows. The volume also holdsapi-tokenandwebhooks.json; both are secret and both belong in backups.Behaviour change: a number too large or too small to hold exactly, such as
1e-1300(whichmainanswers as0), is now refused rather than answered wrongly.Model: opus-5-5
`GET /api/v1/chats/{id}/messages?count=N` returns a chat's recent messages, oldest first (`count` 1 to 100, default 20), and `POST` to the same path sends a text message and returns it once the chat client has taken it. Both answer `404` for any id the chats list does not show, looked up in the same contact list. The message record is defined once, for the webhooks to reuse. Disclosures: items are read five at a time, because one item can be many times its text and 100 at once could pass the 16 MiB read limit; text too long gets `413`, a contact who deleted the chat `409` (unverified for one still connecting); a query the server cannot read gets its own `400`. Model: opus-5-5An external app can register webhooks on a chat (`POST /api/v1/chats/{id}/webhooks` with a URL), list them, and remove one. Registering the same URL again returns the existing registration. Registrations are kept in `$DATA_DIR/webhooks.json`, rewritten whole on each change through a file created 0600, synced and renamed, so a crash leaves the old file or the new one; a file present but unreadable stops startup. Delivery of incoming messages is the next unit. Disclosures: the JSON body reading is now one helper shared with the send endpoint; gosec G304 is suppressed on reading the webhooks file; the 0600-from-creation claim rests on `os.CreateTemp`'s source, not a system-call trace. Model: opus-5-5Each message a contact sends in a direct chat is posted to each webhook registered on that chat: `POST`, `Content-Type: application/json`, body `{"chat_id":N,"message":{...}}` with the same record the messages endpoint returns. The event handler only queues the message; four goroutines post it, one attempt each with a 10-second timeout and no retries, and with 100 already waiting a message is dropped and logged, so a slow webhook never delays the bot's replies. Posting stops last, after the chat client. Disclosures: redirects are not followed; a logged failure can name the webhook's host but never its path, query or credentials; posts still in flight at shutdown are abandoned and logged. Model: opus-5-5View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.