aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile7
-rw-r--r--adenosine-cli/README.md79
-rw-r--r--adenosine-cli/src/bin/adenosine.rs82
-rw-r--r--extra/adenosine.1216
-rw-r--r--extra/adenosine.1.md235
-rw-r--r--extra/adenosine.1.scdoc168
6 files changed, 747 insertions, 40 deletions
diff --git a/Makefile b/Makefile
index 2bfb764..4bfc7c2 100644
--- a/Makefile
+++ b/Makefile
@@ -30,11 +30,14 @@ completions: build ## generate shell completions
./target/debug/adenosine --shell-completions bash status > extra/adenosine.bash_completions
./target/debug/adenosine --shell-completions bash status > extra/adenosine.zsh_completions
-extra/adenosine.1: extra/adenosine.1.scdoc
+extra/%.1: extra/%.1.scdoc
scdoc < extra/adenosine.1.scdoc > extra/adenosine.1
+extra/adenosine.1.md: extra/adenosine.1
+ pandoc extra/adenosine.1 -o extra/adenosine.1.md
+
.PHONY: manpage
-manpage: extra/adenosine.1 ## Rebuild manpage using scdoc
+manpage: extra/adenosine.1.md ## Rebuild manpages using scdoc and pandoc
.PHONY: deb
deb: ## Build debian packages (.deb files)
diff --git a/adenosine-cli/README.md b/adenosine-cli/README.md
index 30fb999..6b526e8 100644
--- a/adenosine-cli/README.md
+++ b/adenosine-cli/README.md
@@ -1,5 +1,78 @@
-`adenosine-cli`: atproto command-line client
-============================================
+`adenosine-cli`: command-line client for AT protocol (atproto.com)
+==================================================================
+
+This is a simple, enthusiast-grade CLI client for the work-in-progress AT
+Protocol ([atproto.com](https://atproto.com)). Sort of like the `http` command
+([HTTPie](https://httpie.io/)).
+
+It is an entirely "delegated" client, which means that it does not store or
+cache any user content locally; everything works by making requests to a
+Personal Data Server (PDS), which is usually a remote service.
+
+The only real utility of this tool currently is messing around with prototype
+implementations, possibly while developing them.
+
+This client does not currently do any schema validation of Lexicons, either at
+compile time or runtime (eg, dynamically fetching Lexicons). The Bluesky
+Lexicon (bsky.app) is partially supported with helper commands.
+
+
+## Example Commands
+
+Generic atproto/XRPC commands:
+
+ # configure host we are talking to
+ export ATP_HOST=http://localhost:2583
+
+ # register a new account
+ adenosine account register -u voltaire.test -p bogus -e voltaire@example.com
+ export ATP_AUTH_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6cGxjOm1qMzVmcmo3Ymd1NmMyd3N6YnRha3QzZSIsImlhdCI6MTY2NjkzNzQxMn0.908LeimAXg1txMMH4k0_IcZAVJaFw1k7pVkScGMNcmE
+
+ # or, login
+ unset ATP_AUTH_TOKEN
+ adenosine account login -u voltaire.test -p bogus
+ export ATP_AUTH_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6cGxjOm1qMzVmcmo3Ymd1NmMyd3N6YnRha3QzZSIsImlhdCI6MTY2NjkzNzcyMn0.7lXJ9xl6c-hrzUAR9YGLc4iFBn4nOJPbFX8TmYDHgdE
+
+ # check config and setup
+ adenosine status
+
+ # not implemented server-side (yet?)
+ adenosine account delete
+ adenosine account logout
+
+ # create, list, delete, update some records
+ adenosine create com.example.thing a=123 b=cream
+
+ adenosine ls at://hyde.test/app.bsky.post
+ # TODO: bug in serve implementation? says "Could not find user"
+
+ adenosine delete at://did:plc:mj35frj7bgu6c2wszbtakt3e/app.bsky.post/3jg4dqseut22a
+
+ adenosine describe
+
+ adenosine get at://hyde.test/app.bsky.post/asdf
+
+ adenosine resolve voltaire.test
+
+ adenosine repo root
+
+ adenosine repo root did:plc:mj35frj7bgu6c2wszbtakt3e
+
+ adenosine repo export did:plc:mj35frj7bgu6c2wszbtakt3e > example_repo.car
+
+ adenosine xrpc get app.bsky.getHomeFeed
+
+
+Example commands in bsky.app Lexicon:
+
+ adenosine bsky feed
+
+ adenosine bsky follow did:plc:mj35frj7bgu6c2wszbtakt3e
+
+ adenosine bsky profile
+
+ adenosine bsky profile voltaire.test
+
+ adenosine get at://did:plc:mj35frj7bgu6c2wszbtakt3e/app.bsky.post/3jg4dqseut22a
-Like the `http` command (HTTPie), but for the AT Protocol (<https://atproto.com>).
diff --git a/adenosine-cli/src/bin/adenosine.rs b/adenosine-cli/src/bin/adenosine.rs
index 1d6e22d..0b79cf9 100644
--- a/adenosine-cli/src/bin/adenosine.rs
+++ b/adenosine-cli/src/bin/adenosine.rs
@@ -10,17 +10,21 @@ use structopt::StructOpt;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
#[derive(StructOpt)]
-#[structopt(rename_all = "kebab-case", about = "CLI interface for AT Protocol")]
+#[structopt(
+ rename_all = "kebab-case",
+ about = "command-line client for AT protocol (atproto.com)"
+)]
struct Opt {
+ /// HTTP(S) URL of Personal Data Server to connect to
#[structopt(
global = true,
long = "--host",
env = "ATP_HOST",
- default_value = "https://localhost:8080"
+ default_value = "http://localhost:2583"
)]
atp_host: String,
- // API auth tokens can be generated from the account page in the fatcat.wiki web interface
+ /// Authentication session token (JWT), for operations that need it
#[structopt(
global = true,
long = "--auth-token",
@@ -46,6 +50,11 @@ struct Opt {
#[derive(StructOpt)]
enum AccountCommand {
/// Register a new account
+ ///
+ /// Does not (yet) support invite codes or email verification.
+ ///
+ /// This will return a JWT token that you should assign to the `ATP_AUTH_TOKEN` environment
+ /// variable.
Register {
#[structopt(long, short)]
email: String,
@@ -56,7 +65,12 @@ enum AccountCommand {
#[structopt(long, short)]
password: String,
},
+ /// Delete the currently logged-in account (danger!)
Delete,
+ /// Create a new authenticated session
+ ///
+ /// This will return a JWT token that you should assign to the `ATP_AUTH_TOKEN` environment
+ /// variable
Login {
#[structopt(long, short)]
username: String,
@@ -64,23 +78,33 @@ enum AccountCommand {
#[structopt(long, short)]
password: String,
},
+ /// Deletes the current login session
Logout,
+ /// Fetches account metadata for the current session
Info,
// TODO: CreateRevocationKey or CreateDid
}
#[derive(StructOpt)]
enum RepoCommand {
+ /// Get the current 'root' commit for a DID
+ ///
Root {
+ /// Repository DID, or uses the current session account
did: Option<DidOrHost>,
},
+ /// Dump raw binary repository as CAR format to stdout
Export {
+ /// Repository DID, or uses the current session account
did: Option<DidOrHost>,
+ /// CID of a prior commit; only newer updates are included
#[structopt(long)]
from: Option<String>,
},
+ /// Read raw binary repository as CAR format from stdin, and import to PDS
Import {
// TODO: could accept either path or stdin?
+ /// Repository DID, or uses the current session account
#[structopt(long)]
did: Option<DidOrHost>,
},
@@ -88,12 +112,18 @@ enum RepoCommand {
#[derive(StructOpt)]
enum BskyCommand {
+ /// Fetch the home feed, or account feed for a specific user
Feed { name: Option<DidOrHost> },
+ /// Fetch notification feed
Notifications,
+ /// Create a new 'post' record
Post { text: String },
+ /// Create a 'repost' record for the target by AT URI
Repost { uri: AtUri },
+ /// Create a 'like' record for the target by AT URI
Like { uri: AtUri },
// TODO: Repost { uri: String, },
+ /// Create a 'follow' record for the target by AT URI
Follow { uri: DidOrHost },
// TODO: Unfollow { uri: String, },
/* TODO:
@@ -104,67 +134,77 @@ enum BskyCommand {
name: String,
},
*/
+ /// Display a profile record (or self if not provided)
Profile { name: Option<DidOrHost> },
+ /// Query by partial username
SearchUsers { query: String },
}
#[derive(StructOpt)]
enum Command {
+ /// Summarize connection and authentication with API
+ Status,
+
+ /// List all collections for a user, or all records for a collection
+ Ls { uri: AtUri },
+ /// Fetch and display a generic record by full AT URI
Get {
uri: AtUri,
+ /// Specific version of record to fetch
#[structopt(long)]
cid: Option<String>,
},
-
- Ls {
- uri: AtUri,
- },
-
+ /// Generic record creation
Create {
collection: String,
+
+ /// Set of object fields (keys) and values to construct the object from
fields: Vec<ArgField>,
},
+ /// Generic mutation of an existing record
Update {
uri: AtUri,
+
+ /// Set of object fields (keys) and values to update in the record
fields: Vec<ArgField>,
},
- Delete {
- uri: AtUri,
- },
+ /// Generic record deletion
+ Delete { uri: AtUri },
- Describe {
- name: Option<DidOrHost>,
- },
+ /// Print user/repository-level description (including DID document)
+ Describe { name: Option<DidOrHost> },
- Resolve {
- name: DidOrHost,
- },
+ /// Have PDS resolve the DID for a username
+ Resolve { name: DidOrHost },
+ /// Generic HTTP XRPC helper, printing any result
Xrpc {
+ /// 'get' or 'post'
method: XrpcMethod,
+ /// Name of method to call
nsid: String,
+ /// Set of query parameters and body fields for the request
fields: Vec<ArgField>,
},
- /// Sub-commands for managing account
+ /// Manage user account and sessions
Account {
#[structopt(subcommand)]
cmd: AccountCommand,
},
+ /// Direct access to binary repository content
Repo {
#[structopt(subcommand)]
cmd: RepoCommand,
},
+ /// Helper commands for bsky.app Lexicon
Bsky {
#[structopt(subcommand)]
cmd: BskyCommand,
},
-
- /// Summarize connection and authentication with API
- Status,
}
fn main() -> Result<()> {
diff --git a/extra/adenosine.1 b/extra/adenosine.1
index 7a490d3..dcac2dd 100644
--- a/extra/adenosine.1
+++ b/extra/adenosine.1
@@ -5,7 +5,7 @@
.nh
.ad l
.\" Begin generated content:
-.TH "adenosine" "1" "2022-10-27" "adenosine Client Tool Manual Page"
+.TH "adenosine" "1" "2022-10-28" "adenosine CLI Client Manual Page"
.P
.SH NAME
.P
@@ -17,18 +17,170 @@ adenosine [OPTIONS] <COMMAND> <ARGS>
.P
.SH DESCRIPTION
.P
-TODO
+This is a simple, enthusiast-grade CLI client for the work-in-progress AT
+Protocol (atproto.\&com).\& It is an entirely "delegated" client, which means that
+it does not store or cache any user content locally; everything works by making
+HTTP/XRPC requests to a Personal Data Server (PDS), which is usually a remote
+service.\&
+.P
+The only real utility of this tool currently is messing around with prototype
+implementations, possibly while developing them.\&
+.P
+This client does not currently do any schema validation of Lexicons, either at
+compile time or runtime (eg, dynamically fetching Lexicons).\& The Bluesky
+Lexicon (bsky.\&app) is partially supported with helper commands.\&
+.P
+.SH FIELD SYNTAX
+.P
+Several commands accept generic key/value fields which are passed through as
+either query parameters or combined together in to a request body JSON object.\&
+Escaping and other corner-cases aren't handled.\&
+.P
+\fB==\fR
+.RS 4
+Query parameter.\& Key and Value both passed as strings.\&
+.P
+.RE
+\fB=\fR
+.RS 4
+Body fields, combined together as a JSON object.\& Keys are strings, values are parsed as JSON with fall-through to string type.\&
+.P
+.RE
+For example, the argument list \fBlimit==25 title="regarding documentation" year=2022 tags='["blue", "green"]'\fR
+would be interpreted as a single query parameter "limit" with value "25", and a
+JSON object with keys "title" (string value), "year" (number value), "tags"
+(array of strings).\&
.P
.SH COMMANDS
.P
-.SS Other Commands
+\fBstatus\fR
+.RS 4
+Summarizes configuration, and (TODO) connection and authentication to the API server.\& Useful for debugging.\&
+.P
+.RE
+\fBdescribe\fR [name]
+.RS 4
+Prints repository description fetched from PDS
+.P
+.RE
+\fBresolve\fR <name>
+.RS 4
+Has PDS resolve username to a DID
+.P
+.RE
+.SS Generic XRPC Requests
+.P
+It is possible to construct and submit a generic XRPC request to the PDS, and
+prints any response.\&See field syntax section above about query parameters and
+body fields.\& Body fields only used for "post" requests.\&
+.P
+\fBxrpc\fR <"get"|"post"> <nsid> [fields]+
+.P
+.SS Generic Record Interaction
+.P
+\fBls\fR <at-uri>
+.RS 4
+List either collections under a repository, or records under a collection
+.P
+.RE
+\fBget\fR <at-uri>
+.RS 4
+Fetch and print a single record
+.P
+.RE
+\fBcreate\fR <collection> [fields].\&.\&.\&
+.RS 4
+Construct and create a generic record, printing the resulting AT-URI and CID
+.P
+.RE
+\fBupdate\fR <at-uri> [fields].\&.\&.\&
+.RS 4
+Fetch record, update fields, "put" back to same record path
+.P
+.RE
+\fBdelete\fR <at-uri>
+.RS 4
+Delete a single record from repository
+.P
+.RE
+.SS Bluesky (bsky.app)
+.P
+\fBbsky feed\fR
+.RS 4
+Fetch the home feed, or account feed for a specific user
+.P
+.RE
+\fBbsky follow\fR
+.RS 4
+Create a 'follow' record for the target by AT URI
+.P
+.RE
+\fBbsky like\fR
+.RS 4
+Create a 'like' record for the target by AT URI
+.P
+.RE
+\fBbsky notifications\fR
+.RS 4
+Fetch notification feed
+.P
+.RE
+\fBbsky post\fR
+.RS 4
+Create a new 'post' record
+.P
+.RE
+\fBbsky profile\fR
+.RS 4
+Display a profile record (or self if not provided)
+.P
+.RE
+\fBbsky repost\fR
+.RS 4
+Create a 'repost' record for the target by AT URI
+.P
+.RE
+\fBbsky search-users\fR
+.RS 4
+Query by partial username
+.P
+.P
+.RE
+.SS Account Management
+.P
+\fBaccount register --email <email> --password <password> --username <username>\fR
.P
+\fBaccount info\fR
.RS 4
-\fBstatus\fR [--json]
+Fetches account metadata for the current session
+.P
+.RE
+\fBaccount login --password <password> --username <username>\fR
+.RS 4
+Create a new authenticated session
+.P
+.RE
+\fBaccount logout\fR
+.RS 4
+Deletes the current login session
+.P
+.RE
+.SS Raw Repository Management
+.P
+\fBrepo export [did]\fR
+.RS 4
+Dump raw binary repository as CAR format to stdout
+.P
+.RE
+\fBrepo import [did]\fR
.RS 4
-Summarizes connection and authentication to the API server.\& Useful for debugging
+Read raw binary repository as CAR format from stdin, and import to PDS
.P
.RE
+\fBrepo root [did]\fR
+.RS 4
+Get the current 'root' commit for a DID
+.P
.RE
.SH OPTIONS
.P
@@ -52,6 +204,56 @@ By default, it'll only report errors.\& Passing `-v` one time also prints warnin
.P
\fB--auth-token <auth-token>\fR [env: ATP_AUTH_TOKEN]
.P
-.SH EXAMPLES
+.SH GETTING STARTED
.P
-TODO
+To start interacting with a PDS, set the `ATP_HOST` environment variable.\& Then
+either register a test account, or create a new session for an existing
+account, and save the JWT token to the `ATP_AUTH_TOKEN`:
+.P
+.nf
+.RS 4
+# default port for bluesky-social/atproto implementation
+export ATP_HOST=http://localhost:2583
+
+# register a new account
+adenosine account register -u voltaire\&.test -p bogus -e voltaire@example\&.com
+{
+ "did": "did:plc:yqtuksvatmmgngd5nkkw75hn",
+ "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\&.eyJzdWIiOiJkaWQ6cGxjOnlxdHVrc3ZhdG1tZ25nZDVua2t3NzVobiIsImlhdCI6MTY2Njk5NjMwNn0\&.MMQa4JIQdwvhy-rjJ0kO-z8-KdoOL0Lto9JtOkK-lwE",
+ "username": "voltaire\&.test"
+}
+
+export ATP_AUTH_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\&.eyJzdWIiOiJkaWQ6cGxjOnlxdHVrc3ZhdG1tZ25nZDVua2t3NzVobiIsImlhdCI6MTY2Njk5NjMwNn0\&.MMQa4JIQdwvhy-rjJ0kO-z8-KdoOL0Lto9JtOkK-lwE
+
+# to clear the auth token env variable
+unset ATP_AUTH_TOKEN
+
+# create a new session (login) for existing account
+adenosine account login -u voltaire\&.test -p bogus
+{
+ "did": "did:plc:yqtuksvatmmgngd5nkkw75hn",
+ "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\&.eyJzdWIiOiJkaWQ6cGxjOnlxdHVrc3ZhdG1tZ25nZDVua2t3NzVobiIsImlhdCI6MTY2Njk5NjQxNX0\&.j2wcF1g9NxT_1AvYRiplNf_jtK6S81y3L38AkcBwOqY",
+ "name": "voltaire\&.test"
+}
+
+export ATP_AUTH_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\&.eyJzdWIiOiJkaWQ6cGxjOnlxdHVrc3ZhdG1tZ25nZDVua2t3NzVobiIsImlhdCI6MTY2Njk5NjQxNX0\&.j2wcF1g9NxT_1AvYRiplNf_jtK6S81y3L38AkcBwOqY
+.fi
+.RE
+.P
+You could save the `ATP_HOST` and `ATP_AUTH_TOKEN` values in `~/.\&bashrc` so you
+don't need to enter them every time.\&
+.P
+Now you can start posting and poking around:
+.P
+.nf
+.RS 4
+adenosine bsky post "gruel again for breakfast"
+{
+ "cid": "bafyreig2aqlsg4arslck64wbo2hnhe6k2a4z3z2sjfzh3uapv3a4zjld7e",
+ "uri": "at://did:plc:yqtuksvatmmgngd5nkkw75hn/app\&.bsky\&.post/3jg5zkr322c2a"
+}
+
+adenosine ls at://voltaire\&.test
+app\&.bsky\&.post
+.fi
+.RE
diff --git a/extra/adenosine.1.md b/extra/adenosine.1.md
new file mode 100644
index 0000000..dc44e59
--- /dev/null
+++ b/extra/adenosine.1.md
@@ -0,0 +1,235 @@
+NAME
+====
+
+adenosine - command-line client for AT protocol (atproto.com)
+
+SYNOPSIS
+========
+
+adenosine \[OPTIONS\] \<COMMAND\> \<ARGS\>
+
+DESCRIPTION
+===========
+
+This is a simple, enthusiast-grade CLI client for the work-in-progress
+AT Protocol (atproto.com). It is an entirely \"delegated\" client, which
+means that it does not store or cache any user content locally;
+everything works by making HTTP/XRPC requests to a Personal Data Server
+(PDS), which is usually a remote service.
+
+The only real utility of this tool currently is messing around with
+prototype implementations, possibly while developing them.
+
+This client does not currently do any schema validation of Lexicons,
+either at compile time or runtime (eg, dynamically fetching Lexicons).
+The Bluesky Lexicon (bsky.app) is partially supported with helper
+commands.
+
+FIELD SYNTAX
+============
+
+Several commands accept generic key/value fields which are passed
+through as either query parameters or combined together in to a request
+body JSON object. Escaping and other corner-cases aren\'t handled.
+
+**==**
+
+> Query parameter. Key and Value both passed as strings.
+
+**=**
+
+> Body fields, combined together as a JSON object. Keys are strings,
+> values are parsed as JSON with fall-through to string type.
+
+For example, the argument list **limit==25 title=\"regarding
+documentation\" year=2022 tags=\'\[\"blue\", \"green\"\]\'** would be
+interpreted as a single query parameter \"limit\" with value \"25\", and
+a JSON object with keys \"title\" (string value), \"year\" (number
+value), \"tags\" (array of strings).
+
+COMMANDS
+========
+
+**status**
+
+> Summarizes configuration, and (TODO) connection and authentication to
+> the API server. Useful for debugging.
+
+**describe** \[name\]
+
+> Prints repository description fetched from PDS
+
+**resolve** \<name\>
+
+> Has PDS resolve username to a DID
+
+Generic XRPC Requests
+---------------------
+
+It is possible to construct and submit a generic XRPC request to the
+PDS, and prints any response.See field syntax section above about query
+parameters and body fields. Body fields only used for \"post\" requests.
+
+**xrpc** \<\"get\"\|\"post\"\> \<nsid\> \[fields\]+
+
+Generic Record Interaction
+--------------------------
+
+**ls** \<at-uri\>
+
+> List either collections under a repository, or records under a
+> collection
+
+**get** \<at-uri\>
+
+> Fetch and print a single record
+
+**create** \<collection\> \[fields\]\...
+
+> Construct and create a generic record, printing the resulting AT-URI
+> and CID
+
+**update** \<at-uri\> \[fields\]\...
+
+> Fetch record, update fields, \"put\" back to same record path
+
+**delete** \<at-uri\>
+
+> Delete a single record from repository
+
+Bluesky (bsky.app)
+------------------
+
+**bsky feed**
+
+> Fetch the home feed, or account feed for a specific user
+
+**bsky follow**
+
+> Create a \'follow\' record for the target by AT URI
+
+**bsky like**
+
+> Create a \'like\' record for the target by AT URI
+
+**bsky notifications**
+
+> Fetch notification feed
+
+**bsky post**
+
+> Create a new \'post\' record
+
+**bsky profile**
+
+> Display a profile record (or self if not provided)
+
+**bsky repost**
+
+> Create a \'repost\' record for the target by AT URI
+
+**bsky search-users**
+
+> Query by partial username
+
+Account Management
+------------------
+
+**account register \--email \<email\> \--password \<password\>
+\--username \<username\>**
+
+**account info**
+
+> Fetches account metadata for the current session
+
+**account login \--password \<password\> \--username \<username\>**
+
+> Create a new authenticated session
+
+**account logout**
+
+> Deletes the current login session
+
+Raw Repository Management
+-------------------------
+
+**repo export \[did\]**
+
+> Dump raw binary repository as CAR format to stdout
+
+**repo import \[did\]**
+
+> Read raw binary repository as CAR format from stdin, and import to PDS
+
+**repo root \[did\]**
+
+> Get the current \'root\' commit for a DID
+
+OPTIONS
+=======
+
+**-h, \--help**
+
+> Prints help information
+
+**-V, \--version**
+
+> Prints version information
+
+**-v, \--verbose**
+
+> Pass many times for more log output By default, it\'ll only report
+> errors. Passing \`-v\` one time also prints warnings, \`-vv\` enables
+> info logging, \`-vvv\` debug, and \`-vvvv\` trace.
+
+**\--host \<atp-host\>** \[env: ATP\_HOST\]
+
+**\--auth-token \<auth-token\>** \[env: ATP\_AUTH\_TOKEN\]
+
+GETTING STARTED
+===============
+
+To start interacting with a PDS, set the \`ATP\_HOST\` environment
+variable. Then either register a test account, or create a new session
+for an existing account, and save the JWT token to the
+\`ATP\_AUTH\_TOKEN\`:
+
+ # default port for bluesky-social/atproto implementation
+ export ATP_HOST=http://localhost:2583
+
+ # register a new account
+ adenosine account register -u voltaire.test -p bogus -e voltaire@example.com
+ {
+ "did": "did:plc:yqtuksvatmmgngd5nkkw75hn",
+ "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6cGxjOnlxdHVrc3ZhdG1tZ25nZDVua2t3NzVobiIsImlhdCI6MTY2Njk5NjMwNn0.MMQa4JIQdwvhy-rjJ0kO-z8-KdoOL0Lto9JtOkK-lwE",
+ "username": "voltaire.test"
+ }
+
+ export ATP_AUTH_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6cGxjOnlxdHVrc3ZhdG1tZ25nZDVua2t3NzVobiIsImlhdCI6MTY2Njk5NjMwNn0.MMQa4JIQdwvhy-rjJ0kO-z8-KdoOL0Lto9JtOkK-lwE
+
+ # to clear the auth token env variable
+ unset ATP_AUTH_TOKEN
+
+ # create a new session (login) for existing account
+ adenosine account login -u voltaire.test -p bogus
+ {
+ "did": "did:plc:yqtuksvatmmgngd5nkkw75hn",
+ "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6cGxjOnlxdHVrc3ZhdG1tZ25nZDVua2t3NzVobiIsImlhdCI6MTY2Njk5NjQxNX0.j2wcF1g9NxT_1AvYRiplNf_jtK6S81y3L38AkcBwOqY",
+ "name": "voltaire.test"
+ }
+
+ export ATP_AUTH_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6cGxjOnlxdHVrc3ZhdG1tZ25nZDVua2t3NzVobiIsImlhdCI6MTY2Njk5NjQxNX0.j2wcF1g9NxT_1AvYRiplNf_jtK6S81y3L38AkcBwOqY
+
+You could save the \`ATP\_HOST\` and \`ATP\_AUTH\_TOKEN\` values in
+\`\~/.bashrc\` so you don\'t need to enter them every time.
+
+Now you can start posting and poking around:
+
+ adenosine bsky post "gruel again for breakfast"
+ {
+ "cid": "bafyreig2aqlsg4arslck64wbo2hnhe6k2a4z3z2sjfzh3uapv3a4zjld7e",
+ "uri": "at://did:plc:yqtuksvatmmgngd5nkkw75hn/app.bsky.post/3jg5zkr322c2a"
+ }
+
+ adenosine ls at://voltaire.test
+ app.bsky.post
diff --git a/extra/adenosine.1.scdoc b/extra/adenosine.1.scdoc
index 2934e19..2c55801 100644
--- a/extra/adenosine.1.scdoc
+++ b/extra/adenosine.1.scdoc
@@ -1,4 +1,4 @@
-adenosine(1) "adenosine Client Tool Manual Page"
+adenosine(1) "adenosine CLI Client Manual Page"
# NAME
@@ -10,14 +10,122 @@ adenosine [OPTIONS] <COMMAND> <ARGS>
# DESCRIPTION
-TODO
+This is a simple, enthusiast-grade CLI client for the work-in-progress AT
+Protocol (atproto.com). It is an entirely "delegated" client, which means that
+it does not store or cache any user content locally; everything works by making
+HTTP/XRPC requests to a Personal Data Server (PDS), which is usually a remote
+service.
+
+The only real utility of this tool currently is messing around with prototype
+implementations, possibly while developing them.
+
+This client does not currently do any schema validation of Lexicons, either at
+compile time or runtime (eg, dynamically fetching Lexicons). The Bluesky
+Lexicon (bsky.app) is partially supported with helper commands.
+
+# FIELD SYNTAX
+
+Several commands accept generic key/value fields which are passed through as
+either query parameters or combined together in to a request body JSON object.
+Escaping and other corner-cases aren't handled.
+
+*==*
+ Query parameter. Key and Value both passed as strings.
+
+*=*
+ Body fields, combined together as a JSON object. Keys are strings, values are parsed as JSON with fall-through to string type.
+
+For example, the argument list *limit==25 title="regarding documentation" year=2022 tags='["blue", "green"]'*
+would be interpreted as a single query parameter "limit" with value "25", and a
+JSON object with keys "title" (string value), "year" (number value), "tags"
+(array of strings).
# COMMANDS
-## Other Commands
+*status*
+ Summarizes configuration, and (TODO) connection and authentication to the API server. Useful for debugging.
+
+*describe* [name]
+ Prints repository description fetched from PDS
+
+*resolve* <name>
+ Has PDS resolve username to a DID
+
+## Generic XRPC Requests
+
+It is possible to construct and submit a generic XRPC request to the PDS, and
+prints any response.See field syntax section above about query parameters and
+body fields. Body fields only used for "post" requests.
+
+*xrpc* <"get"|"post"> <nsid> [fields]+
+
+## Generic Record Interaction
+
+*ls* <at-uri>
+ List either collections under a repository, or records under a collection
+
+*get* <at-uri>
+ Fetch and print a single record
+
+*create* <collection> [fields]...
+ Construct and create a generic record, printing the resulting AT-URI and CID
+
+*update* <at-uri> [fields]...
+ Fetch record, update fields, "put" back to same record path
+
+*delete* <at-uri>
+ Delete a single record from repository
+
+## Bluesky (bsky.app)
+
+*bsky feed*
+ Fetch the home feed, or account feed for a specific user
+
+*bsky follow*
+ Create a 'follow' record for the target by AT URI
+
+*bsky like*
+ Create a 'like' record for the target by AT URI
+
+*bsky notifications*
+ Fetch notification feed
- *status* [--json]
- Summarizes connection and authentication to the API server. Useful for debugging
+*bsky post*
+ Create a new 'post' record
+
+*bsky profile*
+ Display a profile record (or self if not provided)
+
+*bsky repost*
+ Create a 'repost' record for the target by AT URI
+
+*bsky search-users*
+ Query by partial username
+
+
+## Account Management
+
+*account register --email <email> --password <password> --username <username>*
+
+*account info*
+ Fetches account metadata for the current session
+
+*account login --password <password> --username <username>*
+ Create a new authenticated session
+
+*account logout*
+ Deletes the current login session
+
+## Raw Repository Management
+
+*repo export [did]*
+ Dump raw binary repository as CAR format to stdout
+
+*repo import [did]*
+ Read raw binary repository as CAR format from stdin, and import to PDS
+
+*repo root [did]*
+ Get the current 'root' commit for a DID
# OPTIONS
@@ -35,6 +143,52 @@ TODO
*--auth-token <auth-token>* [env: ATP_AUTH_TOKEN]
-# EXAMPLES
+# GETTING STARTED
+
+To start interacting with a PDS, set the `ATP_HOST` environment variable. Then
+either register a test account, or create a new session for an existing
+account, and save the JWT token to the `ATP_AUTH_TOKEN`:
+
+```
+# default port for bluesky-social/atproto implementation
+export ATP_HOST=http://localhost:2583
+
+# register a new account
+adenosine account register -u voltaire.test -p bogus -e voltaire@example.com
+{
+ "did": "did:plc:yqtuksvatmmgngd5nkkw75hn",
+ "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6cGxjOnlxdHVrc3ZhdG1tZ25nZDVua2t3NzVobiIsImlhdCI6MTY2Njk5NjMwNn0.MMQa4JIQdwvhy-rjJ0kO-z8-KdoOL0Lto9JtOkK-lwE",
+ "username": "voltaire.test"
+}
+
+export ATP_AUTH_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6cGxjOnlxdHVrc3ZhdG1tZ25nZDVua2t3NzVobiIsImlhdCI6MTY2Njk5NjMwNn0.MMQa4JIQdwvhy-rjJ0kO-z8-KdoOL0Lto9JtOkK-lwE
+
+# to clear the auth token env variable
+unset ATP_AUTH_TOKEN
+
+# create a new session (login) for existing account
+adenosine account login -u voltaire.test -p bogus
+{
+ "did": "did:plc:yqtuksvatmmgngd5nkkw75hn",
+ "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6cGxjOnlxdHVrc3ZhdG1tZ25nZDVua2t3NzVobiIsImlhdCI6MTY2Njk5NjQxNX0.j2wcF1g9NxT_1AvYRiplNf_jtK6S81y3L38AkcBwOqY",
+ "name": "voltaire.test"
+}
+
+export ATP_AUTH_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6cGxjOnlxdHVrc3ZhdG1tZ25nZDVua2t3NzVobiIsImlhdCI6MTY2Njk5NjQxNX0.j2wcF1g9NxT_1AvYRiplNf_jtK6S81y3L38AkcBwOqY
+```
+
+You could save the `ATP_HOST` and `ATP_AUTH_TOKEN` values in `~/.bashrc` so you
+don't need to enter them every time.
+
+Now you can start posting and poking around:
+
+```
+adenosine bsky post "gruel again for breakfast"
+{
+ "cid": "bafyreig2aqlsg4arslck64wbo2hnhe6k2a4z3z2sjfzh3uapv3a4zjld7e",
+ "uri": "at://did:plc:yqtuksvatmmgngd5nkkw75hn/app.bsky.post/3jg5zkr322c2a"
+}
-TODO
+adenosine ls at://voltaire.test
+app.bsky.post
+```