A terminal client for Tangled.
tui go tangled cli
5

Configure Feed

Select the types of activity you want to include in your feed.

tangled: migrate from xrpc to atclient API

Aly Raffauf (Jul 7, 2026, 8:35 PM EDT) 282b3706 25a533fd

+28 -37
+12 -9
internal/cli/issue_list.go
··· 67 67 return rc.Handle, rc.Repo, nil 68 68 } 69 69 70 - // findRepoDid resolves a handle to its owner DID, lists their repos, 71 - // and returns the repoDid of the matching repo. Matches by the Name 72 - // field, falling back to the rkey in the at:// URI when Name is empty. 70 + // findRepoDid resolves handle/repo to the repo's repoDid, which listIssues is 71 + // keyed by. It looks the record up directly by name (current schema uses the 72 + // name as the rkey), falling back to a listing for legacy repos whose rkey is a 73 + // TID with the name in the body. 73 74 func findRepoDid(ctx context.Context, handle, repo string) (string, error) { 74 75 ident, err := resolver.ResolveHandle(ctx, handle) 75 76 if err != nil { 76 77 return "", fmt.Errorf("resolve handle %q: %w", handle, err) 77 78 } 78 79 79 - repos, err := client.ListRepos(ctx, ident.DID.String()) 80 - if err != nil { 81 - return "", fmt.Errorf("list repos for %q: %w", handle, err) 80 + repoURI := fmt.Sprintf("at://%s/sh.tangled.repo/%s", ident.DID, repo) 81 + if got, err := client.GetRepo(ctx, repoURI); err == nil { 82 + return got.Value.RepoDid, nil 82 83 } 83 84 84 - for _, item := range repos.Items { 85 - if item.Value.Name == repo || strings.HasSuffix(item.URI, "/"+repo) { 86 - return item.Value.RepoDid, nil 85 + if repos, err := client.ListRepos(ctx, ident.DID.String()); err == nil { 86 + for _, item := range repos.Items { 87 + if item.Value.Name == repo || strings.HasSuffix(item.URI, "/"+repo) { 88 + return item.Value.RepoDid, nil 89 + } 87 90 } 88 91 } 89 92
+2 -2
internal/cli/root.go
··· 7 7 8 8 "github.com/alyraffauf/tg/atproto" 9 9 "github.com/alyraffauf/tg/tangled" 10 + "github.com/bluesky-social/indigo/atproto/atclient" 10 11 "github.com/bluesky-social/indigo/atproto/identity" 11 - "github.com/bluesky-social/indigo/xrpc" 12 12 "github.com/spf13/cobra" 13 13 ) 14 14 ··· 20 20 var ( 21 21 resolver = &atproto.Resolver{Directory: identity.DefaultDirectory()} 22 22 client = &tangled.Tangled{ 23 - Client: &xrpc.Client{Host: "https://api.tangled.org"}, 23 + Client: &atclient.APIClient{Host: "https://api.tangled.org"}, 24 24 Logger: slog.Default(), 25 25 } 26 26 auth *atproto.AuthManager
+1 -1
result
··· 1 - /nix/store/nm7df1nqr6ymlngpwda83xh4bwh7l46f-tg-dev 1 + /nix/store/hk2s6g8jxs8zrxxq7kijaa8p4mjgvx26-tg-dev
+2 -10
tangled/get_repo.go
··· 4 4 "context" 5 5 "fmt" 6 6 7 - "github.com/bluesky-social/indigo/xrpc" 7 + "github.com/bluesky-social/indigo/atproto/syntax" 8 8 ) 9 9 10 10 // RepoRecord is the value of a sh.tangled.repo lexicon record. ··· 30 30 31 31 func (t *Tangled) GetRepo(ctx context.Context, repoURI string) (*Repo, error) { 32 32 var repo Repo 33 - err := t.Client.Do( 34 - ctx, 35 - xrpc.Query, 36 - "", 37 - "sh.tangled.repo.getRepo", 38 - map[string]any{"repo": repoURI}, 39 - nil, 40 - &repo, 41 - ) 33 + err := t.Client.Get(ctx, syntax.NSID("sh.tangled.repo.getRepo"), map[string]any{"repo": repoURI}, &repo) 42 34 if err != nil { 43 35 return nil, fmt.Errorf("get tangled repo %q: %w", repoURI, err) 44 36 }
+2 -2
tangled/list_issues.go
··· 5 5 "encoding/json" 6 6 "fmt" 7 7 8 - "github.com/bluesky-social/indigo/xrpc" 8 + "github.com/bluesky-social/indigo/atproto/syntax" 9 9 ) 10 10 11 11 type IssueRecord struct { ··· 59 59 } 60 60 61 61 var out IssueList 62 - err := t.Client.Do(ctx, xrpc.Query, "", "sh.tangled.repo.listIssues", params, nil, &out) 62 + err := t.Client.Get(ctx, syntax.NSID("sh.tangled.repo.listIssues"), params, &out) 63 63 if err != nil { 64 64 return nil, fmt.Errorf("list issues for %q: %w", repoDid, err) 65 65 }
+2 -2
tangled/list_pulls.go
··· 6 6 "fmt" 7 7 8 8 "github.com/bluesky-social/indigo/atproto/atdata" 9 - "github.com/bluesky-social/indigo/xrpc" 9 + "github.com/bluesky-social/indigo/atproto/syntax" 10 10 ) 11 11 12 12 type PullRecord struct { ··· 86 86 } 87 87 88 88 var out PullList 89 - err := t.Client.Do(ctx, xrpc.Query, "", "sh.tangled.repo.listPulls", params, nil, &out) 89 + err := t.Client.Get(ctx, syntax.NSID("sh.tangled.repo.listPulls"), params, &out) 90 90 if err != nil { 91 91 return nil, fmt.Errorf("list PRs for %q: %w", repoDid, err) 92 92 }
+5 -9
tangled/list_repos.go
··· 4 4 "context" 5 5 "fmt" 6 6 7 - "github.com/bluesky-social/indigo/xrpc" 7 + "github.com/bluesky-social/indigo/atproto/syntax" 8 8 ) 9 9 10 10 type RepoList struct { ··· 14 14 15 15 func (t *Tangled) ListRepos(ctx context.Context, ownerDid string) (*RepoList, error) { 16 16 var repos RepoList 17 - err := t.Client.Do(ctx, 18 - xrpc.Query, "", "sh.tangled.repo.listRepos", map[string]any{ 19 - "subject": ownerDid, 20 - "limit": 100, 21 - }, 22 - nil, 23 - &repos, 24 - ) 17 + err := t.Client.Get(ctx, syntax.NSID("sh.tangled.repo.listRepos"), map[string]any{ 18 + "subject": ownerDid, 19 + "limit": 100, 20 + }, &repos) 25 21 if err != nil { 26 22 return nil, fmt.Errorf("list tangled repos for %q: %w", ownerDid, err) 27 23 }
+2 -2
tangled/main.go
··· 3 3 import ( 4 4 "log/slog" 5 5 6 - "github.com/bluesky-social/indigo/xrpc" 6 + "github.com/bluesky-social/indigo/atproto/atclient" 7 7 ) 8 8 9 9 // Tangled is a client for the read-only bobbin XRPC API at api.tangled.org. 10 10 type Tangled struct { 11 - Client *xrpc.Client 11 + Client *atclient.APIClient 12 12 Logger *slog.Logger 13 13 }