1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# CLI
`atlibs` is one Rust binary: run and serve modules, fetch published ones, stage them for a site or a
server, sign in, and publish. `atlibs help` lists everything; a bad invocation prints that command's
usage. Results go to stdout, progress and errors to stderr; a reader that stops early
(`atlibs help | head -1`) ends the command quietly, exit 0.
## Install
```bash run=install
cargo install --locked --git https://knotless.dev/johnandersen777.bsky.social/atlibs --rev 88b2ba8 atlibs
```
Into `~/.cargo/bin` (`--root <dir>`: `<dir>/bin`). From a clone: `cargo install --locked --path rust/cli`,
or `cargo host` (into `rust/host/target/<host triple>/release/`).
## Refs
Every command takes modules by **ref**, resolved the same way everywhere:
| ref | example |
|---|---|
| a published module | `bobvmbuilder.bsky.social/echo`, `did:plc:.../echo`, `at://did:plc:.../com.fedfork.exec.wasm.module/echo` |
| pinned | `bobvmbuilder.bsky.social/echo@<32+ hex of its pin>` or `@<record CID>` |
| a name in `atlibs.lock.json` | `echo` (after `atlibs add`) |
| a module directory | `examples/rust/hello` (its `module.json`, the `cargo wasm` build above it, `records/<session>.json`) |
| a bare example name | `hello`, for `examples/rust/hello` |
| a composition | `examples/rust/compose/composition.json`, or a published one |
| a wasm and its spec | `module.wasm module.json` |
| a staged module | `staged/stage.json compose` |
Output and `atlibs.lock.json`'s `ref` show `<handle>/<lib>`; what identifies a module is its `uri`
(by DID), `cid` and `pin`, so a handle that moves breaks nothing. A published composition names its
modules by at:// URI.
```text help
A <ref> is <handle>/<lib>[@pin|@cid] (or <did>/<lib>), an at:// URI, a name in atlibs.lock.json, a module directory or a name under examples/rust, a composition or clientMetadata .json, <module.wasm> <module.json>, or <stage.json|atlibs.lock.json> <name>.
```
## run
```text help
run <ref> [<instance>] <entry> ['<json>' | - | @file] [--handler <name>=<spec> ...] [--allow-exec] [--params '<json>' | @file]
```
Prints `{ entry, result, error?, events, calls }`; exits 1 when the run failed. `-` reads the input from
stdin and `@file` from a file, which keeps secrets out of `ps` and out of error messages.
```bash run
atlibs run hello 2>&1 | tail -1
echo '{"n":3}' | atlibs run examples/rust/compose lookup - | jq -c .result
```
```output
hello entries: count
{"ack":null,"caller":null,"doubled":6}
```
A composition runs one instance's entry. Its declared handlers come from `--handler`: another module's
entry (`wasm:<ref>#<entry>`), an `https://` URL (`http://` to localhost only), or a command
(`exec:<command>`, with `--allow-exec`; JSON in on stdin, out on stdout, an empty environment).
```bash run
atlibs run examples/rust/compose/composition.json ui caller '{"nsid":"com.example.compose.confirm","input":{"n":1}}' \
--handler 'confirm=exec:jq -c "{approved: true, n: .input.n}"' --allow-exec | jq -c .result.q
```
```output
{"ok":true,"output":{"approved":true,"n":1}}
```
## serve
```text help
serve <ref> [--port 8787] [--host 127.0.0.1] [--origin URL | ATLIBS_ORIGIN] [--oauth-path oauth/] [--handler <name>=<spec> ...] [--allow-exec]
```
`GET /` lists the entries (a form page in a browser), `POST /run/<entry>` runs one
(`/run/<instance>/<entry>` for a composition), `GET /oauth/<session>/client-metadata.json` serves the
modules' OAuth clients. Sign-in and the request guard: [Server sessions](server/sessions.md). With
`--origin` and a session record that declares `app`, it also serves the app's own identity
([App identity](server/app-identity.md)).
```bash run
atlibs serve examples/rust/compose/composition.json --handler confirm=wasm:examples/rust/compose#lookup --port 8793 &
```
```bash run
curl -s -X POST localhost:8793/run/ui/caller -H 'content-type: application/json' -d '{"nsid":"com.example.compose.lookup","input":{"n":6}}' | jq .result.q.output.doubled
```
```output
12
```
## add
```text help
add <handle>/<lib>[@pin|@cid] | <at://...> ... [--dir .]
```
Resolves the handle to a DID (checked both ways), fetches the module record (or a composition), checks
the blob against its CID, and writes `atlibs.lock.json` and `modules/<lib>.wasm` + `.json`. A module's
hook defaults and a composition's modules come too.
```bash run=network
atlibs add bobvmbuilder.bsky.social/echo
jq -c '.modules.echo | {ref, session, wasm}' atlibs.lock.json
```
```output
{"ref":"bobvmbuilder.bsky.social/echo","session":"echo","wasm":"modules/echo.wasm"}
```
Every read of a vendored module checks its wasm and spec against the lockfile's **pin**: the sha256 of
the wasm's sha256 and the record's grant fields (`calls`, `anyRecords`, `hooks`, `implements`,
`anyHooks`, `didkey`). A republished record whose calls changed no longer matches. `add` again to update.
## stage
```text help
stage [--target spa|server] [--out dist] [--origin https://site/] [--oauth-path oauth/] [--name <client> [--record <file>]] [--dir .] [<ref> ...]
```
`--target spa` writes a static site's files ([Sign in from a page](browser/oauth.md));
`--target server` writes modules and a `stage.json` with each session's record, for a server to mount
([Node](server/node.md), [Rust](server/rust.md)). No ref: everything in `atlibs.lock.json`. A spa with
`--origin` and a session record that declares `app` gets the app identity's static files too
([App identity](server/app-identity.md)).
```bash run
atlibs stage --target server --out staged examples/rust/compose examples/rust/statusphere | jq -c '{modules: (.modules | keys), sessions: (.sessions | keys)}'
```
```output
{"modules":["compose","statusphere"],"sessions":["statusphere"]}
```
## oauth
```text help
oauth --origin https://site/ [--oauth-path oauth/] [--name <client>] [--dir .] [<ref> ...]
```
Prints the client metadata documents an origin serves, by session.
```bash run
atlibs oauth --origin https://you.example/ examples/rust/statusphere | jq -r .statusphere.client_id
```
```output
https://you.example/oauth/statusphere/client-metadata.json
```
## login, logout
```text help
login <handle|did> [--scope "atproto transition:generic"] [--no-open]
logout <handle|did>
```
Signs in with OAuth through a loopback redirect in your browser (`ATLIBS_BROWSER` opens it; the URL is
printed either way). The session is saved per DID in the state dir and refreshed as it expires.
```bash run=account
atlibs login you.bsky.social
atlibs run examples/rust/statusphere clear_status --as you.bsky.social
atlibs logout you.bsky.social
```
| flag (every command) | acts as |
|---|---|
| `--as <handle\|did>` | the OAuth session `atlibs login` saved |
| `--app-password <handle>` with `--password-file <path\|->` or `--password-env <NAME>` `[--pds <url>]` | an app password (tests, operator accounts); saved, so later commands may give `--app-password <handle>` alone |
| neither | signed out: public reads only |
## new
```text help
new <name>
```
```bash run
atlibs new my-module > /dev/null && find my-module -type f | LC_ALL=C sort
```
```output
my-module/.cargo/config.toml
my-module/.gitignore
my-module/Cargo.toml
my-module/modules/my-module/Cargo.toml
my-module/modules/my-module/module.json
my-module/modules/my-module/src/lib.rs
```
## publish
```text help
publish [<name> ...] [--dir modules] [--dry-run] [--check-live] [--no-build] [--allow-dirty] [--out records/wasm]
publish module <dir|name> ... [--no-build] [--out records/wasm] [--allow-dirty]
publish records [<file> ...]
publish tile <dir> <name> [--out records/tiles]
publish composition <file> [--rkey <name>]
publish delete <at://did/collection/rkey | collection/rkey> ...
```
`atlibs publish` builds every module, compares each with what it last published (a cache under
`$XDG_CACHE_HOME/atlibs/<did>/`, or the live records with `--check-live`), publishes what changed and
prints a table: `published`, `unchanged`, `would-publish` (`--dry-run`), `skipped:dirty` (uncommitted
sources), `drift` (changed elsewhere), `failed`. The subcommands publish one kind of thing each.
Details: [Publish a module](modules/publish.md).
```bash run=account
atlibs publish --dry-run --as you.bsky.social
```
## verify
```text help
verify <handle>/<lib> | <at://.../com.fedfork.exec.wasm.module/...>
```
Rebuilds a published module from its source records in a container and compares the sha256 with
the manifest and the blob: `atlibs publish` built it in that same `rust:<version>` container. The
engine is `CONTAINER_ENGINE`, else the first of podman, docker and Apple's `container` (macOS) that
answers `info` (`container system status`): a docker CLI whose daemon is stopped is skipped. A build
that exits 0 without writing the manifest's artifact is reported as the engine's fault (a docker
look-alike whose `run` returns before the command ends).
```bash run=container
atlibs verify bobvmbuilder.bsky.social/echo
```
## compose
```text help
compose bindings [--dir .] [<module dir> ...]
compose check <composition.json>
```
`compose bindings` writes `atlibs.modules.js` and `.d.ts`, the modules a `compose.ts` imports
([Compositions](compositions.md#write-one)): those `atlibs.lock.json` vendors (pinned; `atlibs add` writes
them too), `modules/<name>` directories and the ones named.
`compose check` runs the hosts' load checks without the wasm: modules from `atlibs.lock.json` or `stage.json` beside it
(pins too), module directories (`module.json`) or published refs. Prints the composition as it would
run, hook defaults added; exits 1 with the reason it would not load.
```bash run
atlibs compose check examples/rust/compose/composition.json | jq -c '[.instances[].name]'
jq '.instances[1].input.n = "four"' examples/rust/compose/composition.json > bad.json
atlibs compose check bad.json 2>&1 || echo "exit $?"
```
```output
["ui","lookup","even"]
composition: instance lookup: input n must be of type integer
exit 1
```
## Environment
Every variable: [reference/env.md](reference/env.md).
Previous: [MCP servers](server/mcp.md) | Next: [Write a module](modules/write.md)