Tangled CLI – Current Implementation Status #
This document provides an overview of the Tangled CLI implementation status for AI agents or developers working on the project.
Implementation Status #
✅ Fully Implemented #
Authentication (auth) #
login- Authenticate with AT Protocol usingcom.atproto.server.createSessionstatus- Show current authentication statuslogout- Clear stored session from keyring
Repositories (repo) #
list- List repositories usingcom.atproto.repo.listRecordswithcollection=sh.tangled.repocreate- Create repositories with two-step flow:- Create PDS record via
com.atproto.repo.createRecord - Initialize bare repo via
sh.tangled.repo.createwith ServiceAuth
- Create PDS record via
clone- Clone repositories using libgit2 with SSH agent supportinfo- Display repository information including stats and languagesdelete- Delete repositories (both PDS record and knot repo)star/unstar- Star/unstar repositories viash.tangled.feed.star
Issues (issue) #
list- List issues viacom.atproto.repo.listRecordswithcollection=sh.tangled.repo.issuecreate- Create issues viacom.atproto.repo.createRecordshow- Show issue details and commentsedit- Edit issue title, body, or statecomment- Add comments to issues
Pull Requests (pr) #
list- List PRs viacom.atproto.repo.listRecordswithcollection=sh.tangled.repo.pullcreate- Create PRs usinggit format-patchfor patchesshow- Show PR details and diffreview- Review PRs with approve/request-changes flagsmerge- Merge PRs viash.tangled.repo.mergewith ServiceAuth
Knot Management (knot) #
migrate- Migrate repositories between knots- Validates working tree is clean and pushed
- Creates new repo on target knot with source seeding
- Updates PDS record to point to new knot
Spindle CI/CD (spindle) #
config- Enable/disable or configure spindle URL for a repository- Updates the
spindlefield insh.tangled.reporecord
- Updates the
list- List pipeline runs viacom.atproto.repo.listRecordswithcollection=sh.tangled.pipelinelogs- Stream workflow logs via WebSocket (wss://spindle.tangled.sh/spindle/logs/{knot}/{rkey}/{name})secret list- List secrets viash.tangled.repo.listSecretswith ServiceAuthsecret add- Add secrets viash.tangled.repo.addSecretwith ServiceAuthsecret remove- Remove secrets viash.tangled.repo.removeSecretwith ServiceAuth
🚧 Partially Implemented / Stubs #
Spindle CI/CD (spindle) #
run- Manually trigger a workflow (stub)- TODO: Parse
.tangled.ymlto determine workflows - TODO: Create pipeline record and trigger spindle ingestion
- TODO: Support manual trigger inputs
- TODO: Parse
Architecture Overview #
Workspace Structure #
crates/tangled-cli- CLI binary with clap-based argument parsingsrc/ops/- orchestration layer: composes the low-level XRPC builders into multi-step flows (record CRUD, ServiceAuth minting, legacy wire-shape fallbacks). All mockito wire-shape regression tests live here.
crates/tangled-config- Configuration and keyring-backed session managementcrates/tangled-api- Low-level, lexicon-generated XRPC clientsrc/lexicon/**- GENERATED, do not edit; see "Lexicons & code generation"src/xrpc.rs- transport helpers (send,send_unit,send_bytes,XrpcError)
crates/tangled-git- Git operation helpers (currently unused)xtask- workspace automation (cargo xtask codegen)
Lexicons & Code Generation #
The lexicon JSON schemas are vendored in lexicons/ (from tangled-core and
bluesky-social/atproto; pinned SHAs in lexicons/README.md). Rust code is
generated from them into crates/tangled-api/src/lexicon/:
cargo xtask codegen # regenerate (idempotent, rustfmt'd, committed)
cargo xtask codegen --check # CI staleness gate: fails if out of sync
Generated per lexicon: serde structs for records/objects (with a flattened
extra map that preserves unknown fields across read-modify-write
round-trips) and, for queries/procedures, a free function returning a
pre-configured reqwest::RequestBuilder. Consumers attach headers/auth
themselves and send via tangled_api::xrpc::send::<Output>(rb):
use tangled_api::lexicon::sh::tangled::repo;
let rb = repo::describe_repo(http, knot_base, ¶ms).bearer_auth(token);
let out: repo::describe_repo::Output = tangled_api::xrpc::send(rb).await?;
The generator lives in xtask/src/{lexicon,codegen}.rs with golden-file
tests (XTASK_BLESS=1 cargo test -p xtask to update). Unsupported lexicon
constructs (unions, bytes, subscriptions) degrade to serde_json::Value or
are skipped with a warning - regeneration never hard-fails.
Key Patterns #
ServiceAuth Flow #
Many Tangled API operations require ServiceAuth tokens. The single helper is
crate::ops::service_auth::mint in tangled-cli:
- Obtain token via
com.atproto.server.getServiceAuthfrom PDSaudparameter must bedid:web:<target-host>expmust be within 60 seconds (method-less ServiceAuth token limit)
- Use token as
Authorization: Bearer <serviceAuth>for Tangled API calls
Repository Creation Flow #
Two-step process:
- PDS: Create
sh.tangled.reporecord viacom.atproto.repo.createRecord - Tangled API: Initialize bare repo via
sh.tangled.repo.createwith ServiceAuth
Repository Listing #
Done entirely via PDS (not Tangled API):
- Resolve handle → DID if needed via
com.atproto.identity.resolveHandle - List records via
com.atproto.repo.listRecordswithcollection=sh.tangled.repo - Filter client-side (e.g., by knot)
Pull Request Merging #
- Fetch PR record to get patch and target branch
- Obtain ServiceAuth token
- Call
sh.tangled.repo.mergewith{did, name, patch, branch, commitMessage, commitBody}
Base URLs and Defaults #
- PDS Base (auth + record operations): Default
https://bsky.social, stored in session - Tangled API Base (server operations): Default
https://tngl.sh, can override viaTANGLED_API_BASE - Spindle Base (CI/CD): Default
wss://spindle.tangled.shfor WebSocket logs, can override viaTANGLED_SPINDLE_BASE
Session Management #
Sessions are stored in the system keyring:
- Linux: GNOME Keyring / KWallet via Secret Service API
- macOS: macOS Keychain
- Windows: Windows Credential Manager
Session includes:
struct Session {
access_jwt: String,
refresh_jwt: String,
did: String,
handle: String,
pds: Option<String>, // PDS base URL
}
Working with tangled-core #
The ../tangled-core repository contains the server implementation and lexicon definitions.
Key Files to Check #
-
Lexicons: vendored in this repo under
lexicons/(upstream:../tangled-core/lexicons/**/*.json)- Defines XRPC method schemas (NSIDs, parameters, responses)
- Example:
sh.tangled.repo.create,sh.tangled.repo.merge - After re-vendoring, run
cargo xtask codegenand review the diff
-
XRPC Routes:
../tangled-core/knotserver/xrpc/xrpc.go- Shows which endpoints require ServiceAuth
- Maps NSIDs to handler functions
-
API Handlers:
../tangled-core/knotserver/xrpc/*.go- Implementation details for server-side operations
- Example:
create_repo.go,merge.go
Useful Search Commands #
# Find a specific NSID
rg -n "sh\.tangled\.repo\.create" ../tangled-core
# List all lexicons
ls ../tangled-core/lexicons/repo
# Check ServiceAuth usage
rg -n "ServiceAuth|VerifyServiceAuth" ../tangled-core
Next Steps for Contributors #
Priority: Implement spindle run #
The only remaining stub is spindle run for manually triggering workflows. Implementation plan:
-
Parse
.tangled.ymlin the current repository to extract workflow definitions- Look for workflow names, triggers, and manual trigger inputs
-
Create pipeline record on PDS via
com.atproto.repo.createRecord:collection: "sh.tangled.pipeline" record: { triggerMetadata: { kind: "manual", repo: { knot, did, repo, defaultBranch }, manual: { inputs: [...] } }, workflows: [{ name, engine, clone, raw }] } -
Notify spindle (if needed) or let the ingester pick up the new record
-
Support workflow selection when multiple workflows exist:
--workflow <name>flag to select specific workflow- Default to first workflow if not specified
-
Support manual inputs (if workflow defines them):
- Prompt for input values or accept via flags
Code Quality Tasks #
- Add more comprehensive error messages for common failure cases
- Improve table formatting for list commands (consider using
tabledcrate features) - Add shell completion generation (bash, zsh, fish)
- Add more unit tests with
mockitofor API client methods - Add integration tests with
assert_cmdfor CLI commands
Documentation Tasks #
- Add man pages for all commands
- Create video tutorials for common workflows
- Add troubleshooting guide for common issues
Development Workflow #
Building #
cargo build # Debug build
cargo build --release # Release build
Running #
cargo run -p tangled-cli -- <command>
Testing #
cargo test # Run all tests
cargo test -- --nocapture # Show println output
Code Quality #
cargo fmt # Format code
cargo clippy # Run linter
cargo clippy -- -W clippy::all # Strict linting
Troubleshooting Common Issues #
Keyring Errors on Linux #
Ensure a secret service is running:
systemctl --user enable --now gnome-keyring-daemon
Invalid Token Errors #
- For record operations: Use PDS client, not Tangled API client
- For server operations: Ensure ServiceAuth audience DID matches target host
Repository Not Found #
- Verify repo exists:
tangled repo info owner/name - Check you're using the correct owner (handle or DID)
- Ensure you have access permissions
WebSocket Connection Failures #
- Check spindle base URL is correct (default:
wss://spindle.tangled.sh) - Verify the job_id format:
knot:rkey:name - Ensure the workflow has actually run and has logs
Additional Resources #
- Main README:
README.md- User-facing documentation - Getting Started Guide:
docs/getting-started.md- Tutorial for new users - Lexicons:
../tangled-core/lexicons/- XRPC method definitions - Server Implementation:
../tangled-core/knotserver/- Server-side code
Last updated: 2026-07-05