A practical Tangled CLI nnabeyang.tngl.sh/swift-tangled
tangled atproto cli

Files
README.md

swift-tangled #

swift-tangled provides the tng command-line tool for Tangled. tng is the package's only supported public product; the SwiftTangled and TangledLexicons targets are internal implementation layers and are not exposed for use as libraries.

Read the tng CLI manual for the complete command reference, installation guide, and quick start.

Requirements #

  • macOS 15 or later, or Linux
  • Swift 6.3 or later
  • Git access to the package dependencies
  • A Swift toolchain that supports swift package experimental-install when installing tng with the provided script

Install tng #

On an Apple Silicon Mac running macOS 15 or later, install the prebuilt binary from the Tangled-hosted Homebrew tap:

brew tap nnabeyang/tap \
  https://tangled.org/nnabeyang.tngl.sh/homebrew-tap
brew install nnabeyang/tap/tng

To build and install from source instead, run this from the repository root:

./install.sh

The script builds tng in release mode and installs it into ~/.swiftpm/bin. Add that directory to PATH if necessary:

export PATH="$HOME/.swiftpm/bin:$PATH"

Run ./install.sh again after updating the repository to replace the installed executable.

Quick Start #

Confirm the installed version, sign in when a write operation requires authentication, and inspect a repository:

tng --version
tng auth login YOUR_ATPROTO_HANDLE
tng auth status
tng repo view OWNER/REPOSITORY

Public read commands do not require a session. On macOS, authenticated sessions are stored per DID in Keychain. On Linux, they are stored per DID as plaintext OAuth tokens and DPoP key material under $XDG_STATE_HOME/tng/accounts, or $HOME/.local/state/tng/accounts when XDG_STATE_HOME is not usable. Protect that directory and exclude it from backups and synchronization. An existing single-account session is migrated only after the per-account copy is verified.

New logins use an OAuth loopback client by default, so tng does not depend on a hosted client metadata document. The authorization page describes it as an application on your device. To use hosted client metadata instead, pass its HTTPS URL directly or through the environment:

tng auth login YOUR_ATPROTO_HANDLE \
  --client-id https://example.com/tng-client-metadata.json
TNG_CLIENT_ID=https://example.com/tng-client-metadata.json \
  tng auth login YOUR_ATPROTO_HANDLE

--client-id takes precedence over TNG_CLIENT_ID. Existing sessions keep using the client ID with which they were created. See the OAuth client configuration guide for loopback compatibility and a complete self-hosted metadata example.

List accounts, change the default, or select an account for one command:

tng auth list
tng auth switch HANDLE_OR_DID
tng --account HANDLE_OR_DID repo star OWNER/REPOSITORY
tng auth logout
tng auth logout --all

Use the DID when handles are ambiguous. --account overrides the active account without changing it. It cannot be combined with TNG_AUTH_AGENT or TNG_SESSION_FILE; those external authentication sources intentionally bypass the account registry. Git remains independent of tng: configure the SSH key for each checkout with Git's core.sshCommand or an equivalent SSH setup.

For a self-hosted VM worker, an enrolled token-mediating backend (TMB) can keep authorization and refresh separate from direct PDS requests. After enrollment and browser authorization, expose the named TMB session through a private host Unix socket:

tng auth agent tmb login CI_HANDLE --instance ci-reporting
tng auth agent tmb verify --refresh --instance ci-reporting
tng auth agent service install \
  --tmb-instance ci-reporting \
  --socket /absolute/private/path/ci-agent.sock \
  --profile ci-reporting \
  --instance ci-reporting

The macOS service starts immediately, returns after crashes, and starts again when the user logs in after a host restart. Use service status, restart, stop, start, and uninstall with the same instance name. For foreground operation or Linux, use tng auth agent serve --tmb-instance ci-reporting. The existing native OAuth workflow remains available with a private TNG_SESSION_FILE and --session-file; do not configure both sources.

The socket's parent directory must be owned by the current user and inaccessible to group and other users. A trusted VM host bridge binds each connection to one job, repository DID, deadline, and the allowed artifact/comment operations. Inside the VM, ordinary authenticated commands select the broker through TNG_AUTH_AGENT=vsock://host:10241; OAuth tokens and the session file remain on the host. Use a separate named authentication source and broker socket for each CI account. TNG_AUTH_AGENT takes precedence over TNG_SESSION_FILE; interactive login and logout are disabled while the agent variable is set.

Use built-in help for command details:

tng --help
tng pr --help
tng capabilities --json

capabilities returns a versioned description of every executable command, including its arguments, options, read/write classification, authentication requirement, and supported platforms.

CLI Features #

Command Purpose
tng auth Sign in, inspect the current session, or sign out
tng repo View and browse repositories, branches, tags, files, and commits
tng issue List, view, create, comment on, edit, close, and reopen issues
tng pr List, view, diff, create, resubmit, review, close, reopen, and merge pull requests
tng pipeline List, inspect, and watch Spindle CI pipelines
tng artifact Publish and download artifacts attached to annotated Git tags
tng events Watch live Tangled records through Jetstream
tng search Search public Tangled records
tng api Call an allowlisted read-only Bobbin query
tng completion Generate shell completion scripts
tng capabilities Describe the CLI for tools and agents

Most repository-aware commands infer the repository from the Git origin. Pass --repo OWNER/REPOSITORY to target one explicitly. Commands intended for automation support --json where documented by their help.

Terminal output #

When standard output is a terminal, tng experimentally renders human-readable Markdown, tables, and details with ANSI styling and terminal-width-aware wrapping on macOS and Linux. OSC-8 hyperlinks are disabled. When output is piped or redirected, tng keeps the existing plain output and avoids terminal-width-dependent formatting. Use --json when scripts or agents need a stable, structured contract.

For terminal-style output, tng repo view, tng issue view, tng pr view, and tng pr diff use the pager configured by TNG_PAGER or PAGER. TNG_PAGER takes precedence, including when it is set to an empty value. If neither variable is set, tng uses less by default. An empty selected value, cat, or TERM=dumb disables paging. If the selected pager is unavailable, tng warns and writes the output directly. A pager that fails after starting produces a warning without changing the command's exit status or repeating potentially partial output. JSON, list, raw, binary, and streaming output is never sent to a pager.

The following environment variables control paging, terminal detection, width, and ANSI styling:

Variable Behavior
TNG_PAGER Set the pager command for supported human-readable output. Takes precedence over PAGER; an empty value disables paging.
PAGER Set the pager command when TNG_PAGER is not set. An empty value disables paging. If neither pager variable is set, tng uses less.
TNG_FORCE_TTY Force terminal-style output. A positive integer sets the viewport width; a positive percentage uses that share of the detected terminal width.
TNG_MDWIDTH Set the maximum width available to width-aware Markdown, table, and details rendering.
NO_COLOR Disable ANSI styling when set to a nonempty value.
CLICOLOR Disable ANSI styling when set to 0.
CLICOLOR_FORCE Force ANSI styling, including for piped output, when set to a nonempty value other than 0.

NO_COLOR and CLICOLOR=0 take precedence over forced color. JSON, raw, binary, and streaming output never gain human-readable decoration from these settings.

tng selects the read source according to the operation:

Operation Source
Repository listings Repository owner's PDS
Repository, issue, and pull request records Record owner's PDS, with Bobbin fallback for temporary PDS failures
Pull request listings with --author Author and repository owner PDSes
Issue listings, pull request listings without --author, comments, and search Bobbin
Artifact listings Bobbin, merged with the signed-in account's PDS records
Pipelines Spindle
Live events Jetstream

Bobbin-backed aggregate reads report a warning on standard error when index coverage is unavailable or incomplete or when an initial query returns no results. JSON output on standard output keeps the same schema when these diagnostics are present.

Pull Requests #

Create a pull request after pushing its source branch:

git push origin feature/my-change
tng pr create --base main --head feature/my-change \
  --title "Describe the change" \
  --body "Why this change is useful"

Create one dependent pull request per commit with --stack:

tng pr create --base main --head feature/my-stack --stack

Every commit in a stack must contain a unique Change-Id commit header. Jujutsu users can enable git.write-change-id-header = true. Stack titles and bodies come from the individual commits, so --stack cannot be combined with --title, --body, or --body-file. Fork stacks use the same --repo workflow as other fork pull requests.

After pushing more commits to the source branch, add a new review round:

git push origin feature/my-change
tng pr resubmit PULL_REQUEST_AT_URI

For a patch-based pull request, provide a cumulative git diff or git format-patch file:

tng pr resubmit PULL_REQUEST_AT_URI --patch-file changes.patch

For a fork-based pull request, run the command from a checkout whose origin is the source fork. tng refreshes the fork's hidden tracking ref from the target branch and prepares the new round on the fork's Knot.

To resubmit a dependent stack, inspect its operation plan before writing:

tng pr resubmit PULL_REQUEST_AT_URI --stack --dry-run
tng pr resubmit PULL_REQUEST_AT_URI --stack

The stack is matched to its existing pull requests by the unique Change-Id header in each commit. The command can reorder, update, and add stack entries in one PDS transaction. If the plan deletes pull request records, rerun it with --yes after reviewing --dry-run. Merged entries are preserved. For a patch-based stack, pass a multi-message git format-patch file with --patch-file.

To list one author's pull requests without waiting for Bobbin indexing, read that author's PDS directly:

tng pr list OWNER/REPOSITORY --author AUTHOR_HANDLE

This mode resolves pull request status records from the author and repository owner PDSes. Comment counts are not available: the table displays -, and JSON output uses -1. Its pagination cursor is specific to the author, repository, filters, and sort order; pass it back unchanged to the same mode.

The source and target commits must already exist locally. tng does not run git fetch or git push. Use tng pr view, tng pr diff, tng pr comment, tng pr close, tng pr reopen, and tng pr merge to continue the review workflow. State changes use the pull request AT URI:

tng pr close PULL_REQUEST_AT_URI --json
tng pr reopen PULL_REQUEST_AT_URI --json

Issues #

tng issue list OWNER/REPOSITORY
tng issue view ISSUE_AT_URI --comments
tng issue create --repo OWNER/REPOSITORY --title "Describe the issue"

Issue write commands also support commenting, editing, closing, and reopening.

Pipelines #

tng pipeline list OWNER/REPOSITORY
tng pipeline view PIPELINE_ID --spindle spindle.tangled.sh
tng pipeline status PIPELINE_ID --spindle spindle.tangled.sh
tng pipeline watch PIPELINE_ID --repo OWNER/REPOSITORY

Pipeline commands discover the repository's current Spindle by default. Use --spindle with list, view, status, or watch to select an endpoint explicitly. pipeline watch reports workflow state changes until the pipeline completes.

Artifacts #

Artifacts are files attached to remote annotated Git tags. Git remains responsible for creating and pushing the tag:

git tag -a v1.0.0 -m "v1.0.0"
git push origin v1.0.0
tng artifact upload v1.0.0 .build/release/tng \
  --repo OWNER/REPOSITORY \
  --name tng
tng artifact download v1.0.0 tng \
  --repo OWNER/REPOSITORY \
  -o ./tng

When signed in, artifact listings merge the account's authoritative PDS records with Bobbin results and prefer the PDS value for the same record URI. Pagination cursors are opaque and must be passed back unchanged with the same sort order. Cursors produced by earlier tng versions remain accepted.

Live Events #

Watch Tangled records through Jetstream, optionally filtering by collection or DID:

tng events watch \
  --collection sh.tangled.feed.comment \
  --did did:plc:example \
  --json

tng reads current record state from authoritative PDS records, uses Bobbin for indexed aggregate reads, Spindle for pipelines, and Jetstream for live events.

Agent Skill #

The repository includes a reusable tng Agent Skill for agents that work with Tangled repositories, issues, pull requests, rounds, comments, artifacts, and Spindle pipelines.

Point an Agent Skills-compatible tool at the complete skills/tng directory, or copy or symlink it into the tool's skill directory so its workflow reference remains available.

Contributing #

See CONTRIBUTING.md for development environment, testing, design, and contribution guidelines.

License #

swift-tangled is available under the MIT License. Release archives also include the applicable third-party notices and license texts in THIRD_PARTY_NOTICES.md and LICENSES/.