tangy-cli
v1.0.0
Published
A small CLI to set up and manage Tangled (tangled.org) repos.
Readme
tangy
A small CLI to set up and manage Tangled repos. (I thought this must exist but I haven't found it.)
It talks to Tangled the same way the website does, but from your terminal: it logs you in with your own ATProto account, creates the repo on a knot, records it on your PDS, and wires up a local git repo pointing at it.
Install
npm install
npm link # optional: puts `tangy` on your PATHRequires Node ≥ 20.
Usage
# 1. Log in (opens your browser to authorize; credentials go in the OS keychain)
tangy login alice.tngl.sh
# log in more accounts whenever you like — earlier logins are kept
tangy login work.example.com
# 2. Choose where each user's repos get created locally
tangy configuration set root ~/Code/tangled # default user
tangy configuration set root ~/Work/tangled -u work.example.com
# (optional) pick a different knot to host on; defaults to knot1.tangled.sh
tangy configuration set knot knot1.tangled.sh
# 3. Create a repo — makes it on Tangled, wires it up locally, and pushes
tangy repo create my-project
tangy repo create my-project -d "A short description"
tangy repo create my-project --no-push # skip the automatic push
tangy repo create my-project -u work.example.com # act as another account
# 4. Or move an existing local git repo into Tangled
tangy move ~/Code/some-project
# 5. Bring over a GitHub repo's open issues and PRs (comments, labels,
# assignees, reactions included)
tangy copy owner/repo my-project
tangy copy https://github.com/owner/repo --dry-run
# See who is logged in, and pick who commands act as by default
tangy user list
tangy user default work.example.com
# …later — logs out the default user, or the one named with -u
tangy logout
tangy logout -u alice.tngl.shtangy repo create <name>:
- creates
<root>/<name>/locally, - creates the repo on your knot and announces it on your PDS,
- runs
git initand setsoriginto the Tangled repo, - writes
README.md(# <name>) and a Node-friendly.gitignore, - makes an initial commit (if your git identity is configured),
- pushes that commit to
origin— this assumes your SSH key is already added to Tangled.
Pushing is on by default. Pass --no-push to skip it and push yourself later:
tangy repo create my-project --no-push
cd <root>/my-project
git push -u origin mainIf the push fails (e.g. your SSH key isn't on Tangled yet), the repo has still
been created both locally and remotely — add your key and run
git push -u origin main from the repo directory.
tangy move <dir> adopts an existing local git repo into Tangled:
- creates a repo on your knot named after the directory (so
tangy move ~/Code/some-projectcreatessome-project) and announces it on your PDS, - moves the directory into
<root>/— it refuses to overwrite anything already there, and if the repo already sits at<root>/<name>it just skips the move, - points
originat the Tangled repo (replacing any previousorigin), - if the repo has a
package.json, points itsrepositoryfield at the Tangled repo and commits just that change (left uncommitted if git has no identity configured or the repo has no commits yet), - pushes the current branch (the knot's default branch is set to match it).
The directory must be the root of a git work tree and on a checked-out branch.
Other branches and tags are not pushed automatically. --no-push and
-d/--description work the same as for repo create. If creating the repo on
Tangled fails, the directory is moved back where it was.
Copying issues and PRs from GitHub
tangy copy <github> [repo] copies a GitHub repo's open issues and pull
requests (closed ones stay behind) into a Tangled repo you own — by default
one with the same name, which must already exist (tangy repo create /
tangy move first). It takes -u <user> like everything else, and re-running
it is safe: tangy remembers what it already copied (in copies.json next to
its config) and only picks up new items and new comments.
What gets copied, and how:
- Issues and PRs become Tangled issues and pulls, keeping their original timestamps. PR patches are fetched from GitHub and attached, targeting the PR's base branch.
- Comments come along (conversation comments; inline review comments don't).
- Labels are recreated as Tangled label definitions (with their colors), the repo is subscribed to them, and they're applied to the copied items.
- Reactions are summarised in the item ("Reactions: 👍 3 · ❤️ 1"), and any reaction you made on GitHub is also recreated natively as you.
- Assignees are recreated natively too: Tangled models assignment as the platform's shared DID-valued "assignee" label, so every assignee whose GitHub account maps to an AT account is assigned for real. Assignees without a mapping fall back to an "Assignees: …" line in the item.
Since tangy can't impersonate other people, everything is created by the acting user. Anything authored by someone else gets a provenance line first:
Original by @foo = @foo.eurosky.social.
The = @handle part comes from a GitHub→AT mapping that tangy builds up as it
goes: the first time it sees a GitHub account it asks you for the matching AT
handle (press Enter to ignore that account), and remembers the answer either
way. Items you authored yourself (tangy asks for your GitHub username once)
are created as you, with no provenance line. Bots are never asked about.
--dry-run shows what would be copied — and which accounts would need
mapping — without writing anything. --no-issues / --no-pulls restrict what
gets copied. GitHub access is anonymous unless a GITHUB_TOKEN/GH_TOKEN
env var or a logged-in gh CLI is around (recommended for big repos: the
anonymous API allows only 60 requests/hour).
Multiple users
Several accounts can be logged in at once, and each keeps its own settings —
most importantly its own root, so different identities can keep their repos
in different places.
tangy login <handle>adds an account without touching existing logins. The first account to log in becomes the default user.- Every account-bound command (
logout,configuration set/get,repo create,move) takes-u <user>to pick the account: a handle, a DID, or any unique handle prefix (-u alicefindsalice.tngl.sh). Without-u, the default user is used. tangy user listshows the logged-in users (*marks the default);tangy user default <user>changes the default, andtangy user defaultshows it.tangy logout [-u <user>]logs out just that user. If the default user logs out and exactly one account remains, it becomes the default.
Console output always names the account being acted through, e.g.
Creating "my-project" on knot1.tangled.sh as alice.tngl.sh….
Config files written by older single-user versions of tangy are upgraded automatically.
How it works
- Auth uses ATProto OAuth via a loopback redirect (
@atproto/oauth-client-node). Sessions (refresh tokens + DPoP key) are stored in the OS keychain via@napi-rs/keyring, never on disk. - Repo creation asks your PDS for a short-lived service-auth JWT scoped to
the knot, calls the knot's
sh.tangled.repo.createXRPC endpoint, then writes thesh.tangled.reporecord to your PDS. - Issues, pulls, comments, labels and reactions are plain records
(
sh.tangled.repo.issue,sh.tangled.repo.pull,sh.tangled.feed.comment,sh.tangled.label.*,sh.tangled.feed.reaction) written to the acting user's PDS; appviews pick them up from the firehose. Issues and pulls point at the repo via its own DID, comments reference their issue/pull by strongRef (uri + cid), and PR patches are uploaded as gzippedgit format-patchblobs. - Settings (root, knot) live per user in
~/.config/tangy/config.json, along with which user is the default and the GitHub→AT account mapping used bytangy copy.
Configuration keys
Each key is stored per user (add -u <user> to set or read another account's
value):
| key | meaning | default |
| --------- | ---------------------------------------------- | --------------------- |
| root | local directory this user's repos are created in | (unset) |
| knot | knot hostname to host this user's new repos on | knot1.tangled.sh |
| appview | Tangled appview base URL | https://tangled.org |
View them with tangy configuration get [-u <user>].
