@connectx-sdk/cli
v0.4.0
Published
connectx scaffolding CLI — scaffold a starter project (init), generate a typed config (generate), and drive agent identity/login/sign.
Maintainers
Readme
@connectx-sdk/cli
Scaffold and drive @connectx-sdk/core (ANP-02)
identity from the command line. This package ships the connectx binary —
a separate package from the @connectx-sdk/core SDK library so the runtime library stays
dependency-free for the browser.
Commands
# DID server — register, manage and update a DID. DID protocol only, no wallet.
connectx did create generate a key for this (agent, profile) and store it — no DID yet
connectx did show print the stored identity and where the key lives (no network);
before `did register` it prints the public key and says there is no DID
connectx did list list the agents stored on this machine (no network)
connectx did register claim a handle on the DID server — this is where the host and the DID come from
connectx did resolve ask a DID's host what it publishes right now (--chain walks an update)
connectx did update retire this identity's key and hand the hop to the DID server (--target)
connectx did update --resume — settle an uncertain delivery by ASKING which DID is current
connectx did restore recover this identity from a recovery key (in development)
# ANP server — sign in to an application, and sign requests. Takes a DID or a wallet.
connectx anp show print the identity this agent signs as (DID, or a CAIP-10 wallet account)
connectx anp login sign + POST, handle the 401 re-sign loop, print the token
connectx anp sign produce RFC 9421 headers only (no network)
# which agent the commands above act as, when the invocation names none
connectx agent show print the agent in force and which step decided it
connectx agent use <id> make the later commands act as this agent
connectx agent list list the agents stored here, the current one marked
connectx project init scaffold a starter project (react-vite | nextjs | node-agent | vanilla)
connectx project generate read connectx.config.ts, emit a typed connectx.generated.ts
connectx version print the CLI versionA did:wba DID names the server that hosts it, so the host belongs to the DID server,
not to this machine: did create mints a key and nothing else, and did register is
the moment the host, the handle and the DID all land in identity.json. Until then the
identity has no DID, and commands that need one say identity_not_registered rather than
composing a DID under a host no server agreed to.
Scaffold
npx @connectx-sdk/cli project init my-app --template node-agent --host localhost --connectors cli --no-installInteractive when run without --template; fully scriptable (CI / agent) when
the flags are supplied.
Codegen
connectx project generate --init-config writes a connectx.config.ts (typed by
defineConfig); connectx project generate loads it and emits connectx.generated.ts
with a typed config and loginAs<Connector> / signRequestAs<Connector> /
registerAs<Connector> wrappers keyed by connector id — so callers stop passing
string connector ids.
Agent identity
The two groups answer different peers. did speaks to the DID server (DID protocol
only, so no page there takes a wallet); anp speaks to the application, where a
stored key and a web3 wallet are two sources of the same thing — a signature.
# local key (explicit --key, non-default path; the agent holds the key)
connectx anp login --target https://host/api/login/anp/verify --key ./id.pem
connectx did register --target https://host/api/login/anp/register --handle alice --key ./id.pem
connectx anp sign --target https://host/api --method POST --key ./id.pem
# the store route (recommended: no key material on the command line after this)
connectx did create
connectx did register --target https://host/api/login/anp/register --handle alice
connectx did show
# web3 wallet (EIP-1193 provider injected as --provider <module>; key stays in the wallet)
connectx anp show --wallet web3 --provider ./provider.mjs --address 0xabc --chain-id eip155:1anp login / did register print the server's session token ({ did, token }). Pass it
back as Authorization: Bearer <token> on subsequent requests. After a successful
did register, the stored identity's identity.json gains registered: true, the host,
the DID, the stable subject and the accepted handle (a key supplied via --key /
$ANP_PRIVATE_KEY has no sidecar, so nothing is written); a handle the server rejects as
taken is reported as such instead of as an opaque 409. did show / anp show read that
sidecar and nothing else — bare key material has no identity to show, so they take no
--key.
DID resolve
connectx did resolve did:wba:example.com:user:alice:e1_abc --allow-host example.com
connectx did resolve did:wba:example.com:user:alice:e1_abc --chain --allow-host example.com --jsonA thin shell over the SDK's document / update-chain queries: --chain follows
deactivated + successorDid links to the DID that is current now and reports each
hop's assurance. Resolution always goes to the network — a copy on disk never answers
for it; what it leaves behind is a copy of the served document when the DID is this
identity's own, recorded beside it as did.json. The DID's own host is the only
host ever contacted (an update chain cannot leave it). Two host flags cover the
network policy, and naming a host is the whole confirmation:
| Flag | What it opens |
|---|---|
| --allow-host <h> | required, repeatable: may resolve via this host (hostname only, no port). localhost, 127.0.0.1 and ::1 are additionally allowed to be private and to answer over http, automatically |
| --allow-private-host <h> | a host that is local without being spelled that way may be private and may answer over http |
Exit codes — this subcommand only; every other command is 0 or 1:
| Code | Meaning |
|---|---|
| 0 | the DID is current |
| 2 | the DID is not current: the chain walked on, or the document is a retirement |
| 3 | the update chain did not verify (did_chain_broken) |
| 1 | anything else: network, policy, bad input |
DID update
connectx did update --target https://did.example.com/updateThe write half: generate a new key for the same stable subject, have the SDK sign
the retirement document (old key) and the successor's active document (new key), and
POST the pair to --target. --target is required and never derived — ANP-03
§2.5.3 defines what a hop must contain and leaves the programmatic management interface
out of scope, so the endpoint is the DID server's own contract.
The retirement document is built on the document the DID server has on file, not on
one composed here: §2.5.3 says the old document gets deactivated + successorDid and
the proof covers everything it carries, so a composed document would retire a different
document than the one being superseded (no service endpoint, no keys added out of band).
The base comes from the did.json copy, else one fetch from the identity's own host,
else a composed document — and that last fallback is printed, never silent.
The order is the design: the successor key is staged to key.next before anything
is sent, and the store only moves after the server accepts — so a crash in between
can never lose the only key to the DID that was superseded to. On a failure the store
never moves; a settled failure (not_supported and friends) drops the staged key, while
an ambiguous one (a dropped connection) keeps it and prints its path, because the hop
may well have been accepted. subject / registered / handle / createdAt are
carried over: from the inside this is the same agent with a new key.
--target never settles an ambiguous delivery, so connectx did update --resume does
it the only honest way: it asks. The staged key.next derives the successor DID,
resolveDidChain asks the identity's own host which DID it serves, and only a match
promotes the key. If the host still serves the old DID the hop never landed and the
staged key is dropped; if it serves a third DID something else moved the identity on,
so both files are left untouched — that key may be the only thing a later hop needs.
License
MIT
