th: a command-line client for Tangled. The maintained continuation of dzejkop.space/tangled-cli (tang) forked from 7778777.online/tanghole
README.md

Crates.io Version

tanghole #

Warning

Both tangled and this cli are in an experimental phase. Use at your own discretion.

th is a command-line client for Tangled, a decentralized git collaboration platform built on the AT Protocol.

This cli is meant for both humans & agents.

tanghole is the maintained continuation of tangled-cli by dzejkop and its contributors (the tang command), which stopped following Tangled before its move to org.tangled.* records. It keeps tangled-cli's history, licence and configuration: a session made with th auth login and ~/.config/tangled carry over to th.

Installation #

crates.io #

cargo install tanghole

Nix #

With Nix flakes enabled, run it directly from Tangled without cloning:

nix run 'git+https://tangled.org/7778777.online/tanghole#default' -- --help

To open a shell with th available:

nix shell 'git+https://tangled.org/7778777.online/tanghole#default'

Build from Source #

git clone https://tangled.org/7778777.online/tanghole
cd tanghole
cargo install --locked --path crates/tanghole

Getting started #

  1. Login to Tangled:

    th auth login
    
  2. List your repositories:

    th repo list
    
  3. Create a new repository:

    th repo create myproject --description "My cool project"
    
  4. Clone a repository:

    th repo clone username/reponame
    

Workspace Structure #

  • crates/tanghole - CLI binary with clap-based argument parsing
  • crates/tanghole-config - Configuration and session management (keyring-backed)
  • crates/tanghole-api - XRPC client wrapper for AT Protocol and Tangled APIs

Configuration #

The CLI stores session credentials securely in your system keyring and configuration in:

  • Linux: ~/.config/tangled/config.toml
  • macOS: ~/Library/Application Support/tangled/config.toml
  • Windows: %APPDATA%\tangled\config.toml

Environment Variables #

  • TANGLED_PDS_BASE - Override the PDS base URL (default: https://bsky.social)
  • TANGLED_API_BASE - Override the Tangled API base URL (default: https://api.tangled.org)
  • TANGLED_DEFAULT_KNOT - Override the default knot for new repos (default: knot1.tangled.sh)
  • TANGLED_WEB_BASE - The web front end printed links point at (default: https://tangled.org)
  • TANGLED_PLC_URL - Override the PLC directory DID documents are read from (default: https://plc.directory)

How th talks to Tangled #

Tangled keeps a repository in two halves that must agree: an org.tangled.repo.declaration on the owner's PDS (record key = the lowercased name) naming the repository's own DID, and an org.tangled.repo.manifest in that DID's account on its knot. Issues and pulls (tickets), their comments and patches, and release artifacts live in the repository account too. th writes there by posting com.atproto.repo.* to the knot with a service-auth token minted on your PDS for the repository's DID, and then writes an org.tangled.graph.attestation on your PDS claiming the write, as the web does. Listings (issues, pulls, comments, a pull's revisions) are read from Tangled's indexer, TANGLED_API_BASE.

Examples #

Working with Repositories #

# Create a repository on the default knot (the knot mints its DID)
th repo create myproject --description "My cool project" --default-branch main

# Fork: the knot clones the source; or import from any clone URL
th repo create myfork --source someone.example/project
th repo create mirror --source https://github.com/owner/project

# List your repositories: STATUS is `listed`, `unlisted: ...` (declaration
# and manifest disagree, so tangled.org 404s it) or `legacy: ...` (only an
# old sh.tangled.repo record)
th repo list

# Delete (asks first; --force to skip the question, required when it can't ask)
th repo delete myproject --force

# Mark a repository as a fork of another (edits the manifest on the knot)
th repo set-source myfork someone.example/project

Release artifacts #

Artifacts are files on an annotated tag, stored in the repository's account on its knot (at most 50 MiB each). Run these inside a clone that has the tag:

th repo artifact upload --repo myproject --tag v1.0.0 dist/*.tar.zst SHA256SUMS
th repo artifact upload --repo myproject --tag v1.0.0 --skip-existing dist/*
th repo artifact list --repo myproject
th repo artifact delete --repo myproject --tag v1.0.0 old.tar.zst

Uploading a name the tag already has is refused (nothing is uploaded) unless --skip-existing is given.

Working with Issues #

An issue is an org.tangled.track.ticket in the repository's account on its knot; the knot numbers it. Commands take the number (in --repo, or the repository of the current checkout) or the ticket's AT-URI.

# Create an issue (labels must be defined on the repository)
th issue create --repo myrepo --title "Bug: Fix login" --body "Description here" --label bug

# List issues (newest first), optionally by state, author, label or assignee
th issue list --repo myrepo --state open

# Show an issue with its comments
th issue show 12 --repo myrepo

# Comment, reply to a comment, close or reopen
th issue comment 12 --body "I'll fix this"
th issue comment 12 --body "Agreed" --reply-to 3mabcdefgh2k
th issue close 12
th issue reopen 12
th issue edit 12 --title "Login fails on Safari" --state completed

Only the issue's author or a maintainer can change its state; the knot refuses anyone else.

Working with Pulls #

A pull is a ticket with a target branch, plus immutable revisions (org.tangled.review.patch) naming a base and head commit. The knot must be able to reach the head: push the branch first, to the target repository or to your fork of it.

# Propose the current branch for the default branch of the checkout's repo
git push origin my-feature
th pull create --title "Add new feature"

# From a fork's checkout, propose to the repository it was forked from
th pull create --target source --base main --head my-feature

# List, show (with --diff for the patch, --checks for its CI runs)
th pull list --repo myrepo --state open
th pull show 41 --repo myrepo --diff

# Review: a comment on the pull about its latest commit
th pull review 41 --approve --comment "LGTM!"

# Merge (rebases onto the target branch on Tangled's mirror and pushes it),
# or close without merging
th pull merge 41
th pull close 41

CI on a spindle #

Pipelines run on the spindle the repository's manifest names (th spindle config --enable sets Tangled's).

th spindle list --repo myrepo
th spindle run --repo myrepo --branch main --wait
th spindle logs 3mpipelineid --repo myrepo
th spindle secret add --repo myrepo --key DEPLOY_TOKEN --value @~/token

th knot migrate refuses: moving an org.tangled.* repository between knots is a handover only knot admins can run.

Contributing #

Contributions are welcome! Please feel free to submit issues or pulls.

License #

MIT OR Apache-2.0