Skip to content

cli host - #264

Open
pgherveou wants to merge 44 commits into
mainfrom
headless-host
Open

cli host#264
pgherveou wants to merge 44 commits into
mainfrom
headless-host

Conversation

@pgherveou

@pgherveou pgherveou commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Depends on #312.

What this adds

truapi-host is a native CLI for running TrUAPI without a browser or mobile app. It provides both sides of the host flow:

  • a signing host that manages the wallet identity, approvals, allowances, and signatures;
  • a pairing host that serves a product, emits a pairing link, and forwards requests to a signing host.

Both roles use the real Paseo Next v2 services, including LitePeople identity, the People-chain Statement Store, and Bulletin preimages.

Get started

Build and install the CLI once, then launch the signing host:

make headless install
truapi-host signing-host

The CLI selects or creates an attested account and waits for ring readiness. If an auto-managed account has no free Statement Store slot during pairing, it retries with another account. From the command bar you can:

  • paste a polkadotapp:// pairing link or use /deeplink <link>;
  • use /script to create or run a TypeScript product script;
  • use /session to keep separate local host profiles;
  • use /help to discover the remaining commands and shortcuts.

Product scripts run with Bun and receive a ready-to-use global truapi client. They can import npm dependencies directly; Bun installs missing packages automatically.

#!/usr/bin/env bun

import chalk from "chalk";

const result = await truapi.account.getUserId();
if (!result.isOk()) throw new Error(JSON.stringify(result.error));

console.log(chalk.green("user id:"), result.value);

Run a script directly when an interactive session is unnecessary:

truapi-host signing-host \
  --product-id my-product.dot \
  --script ./script.ts \
  --auto-accept

Try it

The crate isn't published yet, so install straight from this branch:

cargo install --git ssh://git@github.com/paritytech/truapi --branch headless-host truapi-host-cli

Then launch a signing host against Paseo Next v2:

truapi-host signing-host

User experience

  • ⌨️ Fast command entry: a persistent command bar with aligned autocomplete, history, and keyboard shortcuts.
  • Clear feedback: quiet sentence-case progress and concise approval prompts, with debug and trace logs available on demand.
TrUAPI signing-host terminal interface

End-to-end coverage

The Playground's generated examples are also the headless compatibility suite. The battery discovers and executes all 64 exposed TrUAPI examples.

It is measured independently against both host topologies and writes browser-compatible reports consumed by the compatibility explorer.

Signing host

This command generates explorer/diagnosis-reports/signing-host-cli.md:

target/debug/truapi-host signing-host \
  --product-id truapi-playground.dot \
  --script rust/crates/truapi-host-cli/js/scripts/battery.ts \
  --auto-accept
Signing-host end-to-end diagnosis

Pairing host

Start the product-facing host, then answer its emitted link from a signing host. The pairing-host process generates explorer/diagnosis-reports/pairing-host-cli.md when the battery completes:

# Terminal 1
target/debug/truapi-host pairing-host \
  --product-id truapi-playground.dot \
  --script rust/crates/truapi-host-cli/js/scripts/battery.ts \
  --auto-accept

# Terminal 2
target/debug/truapi-host signing-host \
  --deeplink '<pairing link>' \
  --auto-accept
Pairing-host end-to-end diagnosis

The live coverage exercises pairing, permissions, account and legacy signing, transaction construction, chain RPC, Statement Store proofs and subscriptions, resource allocation, push notifications, and Bulletin preimages. Remaining failures are isolated to the Chat and Payment APIs that are not currently implemented by the host.

The PR also includes Rust unit/integration coverage, generated-client parity checks, and Bun tests for diagnosis discovery and reporting.

@socket-security

socket-security Bot commented Jul 5, 2026

Copy link
Copy Markdown

@pgherveou pgherveou changed the title Headless pairing + signing hosts for local e2e (signing-bot replacement) Headless pairing + signing hosts Jul 6, 2026
@TarikGul
TarikGul self-requested a review July 7, 2026 15:46
decrypto21 added a commit that referenced this pull request Jul 8, 2026
@decrypto21 decrypto21 mentioned this pull request Jul 8, 2026
@pgherveou
pgherveou changed the base branch from worktree-issue-96-rust-core-port to bulletin-preimage-in-core July 11, 2026 13:26
Base automatically changed from bulletin-preimage-in-core to main July 14, 2026 05:20
@pgherveou pgherveou changed the title Headless pairing + signing hosts cli host Jul 17, 2026
pgherveou added a commit that referenced this pull request Jul 21, 2026
Preserve headless SSO resource allocation while aligning Ring-VRF wire variants and confirmation policy with the iOS host.

Refs #264, #288
@pgherveou

Copy link
Copy Markdown
Collaborator Author

signing host:
signing-host-diagnosis

pairing host:
pairing-host-diagnosis

@pgherveou
pgherveou changed the base branch from main to feat/signing-host-ring-vrf July 23, 2026 13:42
@pgherveou
pgherveou marked this pull request as ready for review July 27, 2026 15:55
@TarikGul

Copy link
Copy Markdown
Member

Pushed four small fixes from my review pass. Each is independent; the larger
architectural points I'll leave as separate comments.

  • build: make make headless work without a prior JS install — the client
    build shells out to tsc, which ensure-generated.sh resolves from either the
    repo root or js/packages/truapi. On a checkout that never ran make setup
    neither exists, so make headless died partway through with the cause buried
    in npm output. It now installs workspace deps only when neither location has
    tsc, and is a no-op otherwise. Verified: fails on a clean worktree before,
    exits 0 after, second run skips the install.

  • build: scope the BSL-1.0 licence allowance to the crates that need it
    clipboard-win and error-code (pulled in by the terminal UI) are BSL-1.0.
    Allowing it globally would admit it for any future dependency, so it's now a
    per-crate exception, matching the scoping this file already uses for
    verifiable and the uniffi crates. Verified with cargo deny check licenses,
    including that removing an exception now correctly rejects.

  • fix(cli): redact signer secrets from Debug outputResolvedSigner,
    ResolveSignerConfig and AccountRecord derived Debug over BIP-39 entropy
    and mnemonics, so any {:?} in a log line or error would print signer material
    verbatim. Hand-written Debug impls render those fields as <redacted> and
    keep the rest; AccountStoreData inherits it via AccountRecord. No new
    dependency. Added a test that fails if any of them regress. Also checked a live
    --log-level debug run against Paseo: no mnemonic word and no raw entropy in
    the output.

  • test(cli): require every network preset to be a test network — the
    account store keeps mnemonics in plaintext, which is fine because every
    preset is Paseo/testnet. That was implicit, so it's now documented on Network
    and enforced by a test over every variant's identity, People, Bulletin and
    chain routes. Adding a production preset fails loudly and points at the storage
    decision rather than silently inheriting it.

Comment thread rust/crates/truapi-host-cli/src/frame_server.rs

@TarikGul TarikGul left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some fixes, and one comment for discussion

Base automatically changed from async-trait-migration to main July 28, 2026 19:58
@pgherveou
pgherveou requested a review from a team July 28, 2026 19:58
@TarikGul

Copy link
Copy Markdown
Member

pushed the flock fix straight to this branch - 14b8f25

swapped lock_exclusive for try_lock_exclusive so it just errors out with the
base path instead of sitting there. added a test that hangs on the old version and
passes in 0.16s on the new one. only touches truapi-host-cli, core is untouched.

@TarikGul

Copy link
Copy Markdown
Member

also pushed a fix for the core storage thing - 9252bc5

read_string_map collects into a Result so one
bad hex value kills the whole file, then load just swallows it and returns an empty
map. next write persists that over the top and everything's gone.

ran it through a real CliPlatform to be sure, seeded core-storage.json with
AuthSession, PairingDeviceIdentity and one bad value. both reads came back None,
the write said Ok, and PairingDeviceIdentity was just gone off disk. host never
even touched that key.

now it moves the file to .unreadable before starting empty so nothing gets
destroyed and the warn tells you where it went. two tests, both fail on the old
behaviour.

one thing - the suffix is fixed so a second corruption clobbers the first saved
copy. seemed fine to me but shout if you'd rather it timestamped them

@TarikGul

Copy link
Copy Markdown
Member

two more pushed - e41344d and 041a468

accept backoff (frame_server.rs) - the accept loop continued on error with no
delay. defensive fix really - no retry loop should busy-wait, whatever the error is.
a failed accept leaves the peer queued so the listener stays readable and the retry
fails the same way, and none of the errors that land there clear faster for being
hammered. waits 50ms now and logs the delay. didnt try to repro a specific failure,
just didnt want an unbounded retry sitting there.

pairing storage isolation (platform.rs) - switch_pairing_user_storage
bailed when validate_name rejected the id and auth_state_changed only warn!'d, so
the session kept the previous identity's state_dir, core storage and product KV.
the id falls back to full_username which is free-form chain text, so "Tarik Gul"
hits it - my own name would. host printed ✓ Paired with Tarik Gul while sitting
in alice_pairing_host. reproduced it directly and through auth_state_changed.

now derives a safe name instead of bailing - valid ids unchanged, unusable ones
become hex sha256. hex passes validate_name so the current-user pointer still
round-trips. nothing migrates.

@TarikGul TarikGul left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 - could use a upgraded title

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants