Write a module
Rust with the wasm target (rustup target add wasm32-unknown-unknown) and the CLI.
atlibs new my-module
my-module/ (cd my-module && cargo test && cargo wasm && atlibs run my-module describe '{"did":"<did>"}')
The SDK is pinned at the commit your CLI was built from:
grep '^abi' my-module/Cargo.toml
abi = { git = "https://knotless.dev/johnandersen777.bsky.social/atlibs", rev = "…" }
The module: one entry, describe, run once per event.
sed -n '1,/^entries!/p' my-module/modules/my-module/src/lib.rs
use abi::{Call, Did, Event, Handle, Run, entries};
use serde::Deserialize;
use serde_json::json;
#[derive(Deserialize)]
struct Args {
did: Did,
}
// The host calls this once per event. Start: ask the account's PDS. Result: answer.
fn describe_handler(run: Run<Args>) -> Result<Handle, String> {
Ok(match run.event {
Event::Start => Handle::new().call(
Call::new("repo", "com.atproto.repo.describeRepo")
.params(json!({ "repo": run.args.did }))
.service(run.args.did.as_str()),
),
Event::Result { id, result, .. } if id == "repo" => {
let repo = result.output()?;
let collections = repo["collections"].as_array().map_or(0, Vec::len);
Handle::new().result(json!({ "handle": repo["handle"], "collections": collections })).done()
}
_ => Handle::new(),
})
}
entries!(describe => describe_handler);
What it may call, the form a host shows for it, and what a composition may set (config):
cat my-module/modules/my-module/module.json
{
"name": "my-module",
"description": "Describe an account's repo: its handle and how many collections it has.",
"actions": [{ "entry": "describe", "label": "Describe", "readOnly": true, "fields": [{ "key": "did", "label": "DID", "required": true }] }],
"config": [{ "key": "did", "label": "DID", "description": "The account a composition's instance describes." }],
"calls": ["com.atproto.repo.describeRepo"]
}
Test, build, run
The tests (in lib.rs) feed the handler events; no network.
cd my-module
cargo test 2>&1 | grep '^test ' | LC_ALL=C sort
cargo wasm 2>&1 | tail -1
atlibs run my-module describe '{"did":"did:plc:ocnuqjlzgohypj3zoznermms"}'
test tests::result_counts_collections ... ok
test tests::start_calls_describe_repo ... ok
…
"result": {
"collections": …,
"handle": "bobvmbuilder.bsky.social"
},
More: the SDK, calls, tests with the real host loop.