[READ-ONLY] Mirror of https://github.com/just-cameron/tangled-cli. Automation-first CLI for Tangled.org

Support Tangled's repository stores, scoped OAuth, and fork pull requests #24

targeting main from center.for.cybernetic.beauty/tangled-cli:main
Merged opened by and assigned to center.for.cybernetic.beauty
targeting main from center.for.cybernetic.beauty/tangled-cli:main

Summary #

Three things, in 24 small commits (each message says why):

  1. Repository stores. Where a repository's knot keeps a store of its own (org.tangled.repo.declaration), issues, comments, labels and pull requests are read and written there as tangled.org keeps them now. Repositories without one keep the legacy path.
  2. Scoped OAuth. auth login asks only for the permissions tang uses, built from one list in permissions.ts.
  3. Pull requests from forks. tang -R <upstream> pr create from a fork's clone. This PR was opened that way.
 issue / label / pr command
   resolveRepositoryDid          # + from org.tangled.repo.declaration
+  resolveKnotStore(repoDid)
+  ├─ store  → tickets-api       # org.tangled.track.ticket, comments, tangledLabels,
+  │                             #   pulls via org.tangled.review.submitPatch
   └─ none   → legacy sh.tangled.repo.* records in your PDS
 src/
 ├── commands/
+│   └── issue-tickets.ts   # issue commands against a store
 └── lib/
+    ├── knot-store.ts      # a repo DID's record store on its knot: reads, service-auth writes
+    ├── tickets-api.ts     # issues, comments, labels, pulls as org.tangled.* records
+    └── permissions.ts     # every PDS write and getServiceAuth call; the OAuth scope built from them

Pull request from a fork, the same calls tangled.org's web app makes (PullCompose.svelte):

tang -R upstream pr create --head feature
  head checked on the fork's own remote
  upstream's base branch fetched (FETCH_HEAD only), merge base taken against it
  ticket written to upstream's store
  submitPatch on upstream's knot { sourceRepo: fork DID, sourceBranch, base, head }
    refused → ticket taken back

Also: issue comment --edit, auth status --diagnostic (the scope granted), handle resolution on Node 26, SSH remotes with any user, and pr list/view show a fork's repo beside its branch.

Evidence #

  • Before (main, 4f0e085): bun run test → 350 passed, 2 failed (formatDate locale tests, which fail the same way on both: they expect M/D/YYYY and get 15.6.2023 under a non-US locale) After: 473 passed, the same 2 failed; bun run typecheck and bun run lint clean
  • Each behaviour change was seen red first; the commit messages name the tests.
  • Used against tangled.org: issues, comments and labels on stored repositories, pr create into a store (the first attempt is what showed the knot only takes patches through submitPatch), and this PR, opened from the fork with tang -R cameron.stream/tangled-cli pr create.

Merge Danger #

Door: two-way for the code, one-way for what it writes

Reverting restores the old behaviour, but tickets, comments and labels written to a repository's store while this was in stay there.

Blast Radius: sign-in

auth login asks for a different OAuth scope, so every existing OAuth session must sign in again before it can write to a store or submit a patch. In repositories with a store, issue and pr commands no longer show legacy sh.tangled.repo.issue/pull records by default (issue list --legacy still lists them).

🤖 Generated with Claude Code

Commits 24

README and CLAUDE.md: pull requests from a fork
pr list and view name a fork's repository beside its branch
pr create opens a pull request from a fork into the repository -R selects
getGitRepoContext gives the repository the clone pushes to, whatever -R selects
pr list, view and delete read pull requests kept as tickets
A tangled.org SSH remote is recognised whatever its user is
A pull request's patch is submitted to the knot, which alone writes it
pr create opens a pull request as a ticket and patch in a repository's own store
auth login resolves a handle by its HTTPS well-known on Node 26
auth status --diagnostic shows the scope an OAuth session was granted
README and CLAUDE.md: the permissions OAuth login asks for, and where they live
auth login asks only for the permissions tang uses
Every PDS write and service-auth request goes through permissions.ts
issue comment --edit <comment-id> replaces the text of one's own comment
editComment puts a comment's new text over the version it read
issue view shows each comment's id
README and CLAUDE.md: where issues live now, the command map and status
Resolve a repository's metadata from its declaration when it has no sh.tangled.repo
label commands work on a ticket's tangledLabels and the repo's catalogue
issue commands read and write tickets on the repository's knot
Read ticket selectors, and delete one's own legacy issue records
Issues, comments and labels as org.tangled.* records in the repo store
Read and write a repository's own record store on its knot
Resolve a repository's DID from its owner's org.tangled.repo.declaration