cli: route activity/recap through the shared entire-api cell client · Entire

cli: route activity/recap through the shared entire-api cell client

65f1a13·

Soph·1w ago·4 files·+169 added/-7 removed

Reconcile onto the cell-routing client that landed with the experts work (#1588) instead of a parallel mechanism. `activity` and `recap` call the /me/* endpoints entire-api serves, so they now go through auth.NewEntireAPICellClient (home-jurisdiction routing, target=nil) — one routing/token path across the CLI.

Cell routing is a best-effort upgrade: any failure building the cell client (no cell for the region, not logged in, discovery/exchange error) falls back to the data API, which also serves /me/* and yields the canonical auth errors — so existing users are unaffected until their region has a cell. recap keeps its render-through-401 behaviour via that fallback.

recap's team column still needs the repo ULID for /me/recap?repo=; currentRepoID resolves it best-effort from the mirror id (which entire-api treats as the repo_id), empty → personal recap only.

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

Sessions

dc4fb6be0484View transcript

Changes

4

55 unmodified lines

func runActivity(ctx context.Context, w, errW io.Writer) error { return runAuthenticatedDataAPI(ctx, errW, false, func(ctx context.Context, client *api.Client) error { return runAuthenticatedActivityAPI(ctx, errW, false, func(ctx context.Context, client *api.Client) error { // Non-interactive fallback: piped output or accessibility mode if !interactive.IsTerminalWriter(w) || IsAccessibleMode() { return runActivityStatic(ctx, w, client)


Mcmd/entire/cli/activity_cmd.go+1/-1

package cli

import ( "context" "errors" "io" "strings"

"github.com/entireio/cli/cmd/entire/cli/api" "github.com/entireio/cli/cmd/entire/cli/auth" "github.com/entireio/cli/cmd/entire/cli/gitremote" "github.com/entireio/cli/cmd/entire/cli/logging" "github.com/entireio/cli/internal/coreapi" )

// runAuthenticatedActivityAPI runs fn with an authenticated client for the // activity/recap surface. It prefers the caller's home entire-api cell (the same // shared client the experts commands use), which serves the /me/* endpoints // these commands call. // // Cell routing is a best-effort upgrade: any failure building the cell client — // the region has no cell yet (ErrNoCellForJurisdiction), not logged in, or a // discovery/exchange error — falls back to the data API, which also serves // /me/* and yields the canonical auth errors (e.g. the "not logged in" hint). // This keeps the migration transparent and non-regressive; non-obvious // fallbacks are logged for diagnosis. Both backends expose the same /me/* paths, // so fn is agnostic to which client it receives. func runAuthenticatedActivityAPI(ctx context.Context, errW io.Writer, insecureHTTP bool, fn func(context.Context, *api.Client) error) error { client, err := auth.NewEntireAPICellClient(ctx, insecureHTTP, nil) if err != nil { if !errors.Is(err, auth.ErrNoCellForJurisdiction) { logging.Debug(ctx, "activity/recap: entire-api cell client unavailable, using data API", "error", err.Error()) } return runAuthenticatedDataAPI(ctx, errW, insecureHTTP, fn) } return fn(ctx, client) }

// forgeToMirrorProvider maps a gitremote forge identifier (e.g. "gh") to the // upstream provider the control plane records mirrors under (e.g. "github"). // entire-api routing only supports GitHub mirrors today. func forgeToMirrorProvider(forge string) (string, bool) { switch strings.ToLower(strings.TrimSpace(forge)) { case "gh", mirrorCloneProviderGitHub: return mirrorCloneProviderGitHub, true default: return "", false } }

// currentRepoID best-effort resolves the current repo (its "origin" remote) to // the ULID entire-api uses for repo-scoped params — recap's /me/recap?repo=. // entire.io/api documents the mirror id as exactly that repo_id (repo_id = // mirror_repos.id), and the CLI already lists mirrors via the control plane, so // no extra resolution is needed. Any failure returns "" — recap then shows the // personal side only rather than erroring. func currentRepoID(ctx context.Context) string { forge, owner, repo, err := gitremote.ResolveRemoteRepo(ctx, "origin") if err != nil { return "" } provider, ok := forgeToMirrorProvider(forge) if !ok { return "" } c, err := coreapi.New() if err != nil { return "" } mirrors, err := listMirrorsForRepo(ctx, c, provider, strings.ToLower(owner), repo) if err != nil { return "" } return firstActiveRepoID(mirrors) }

// firstActiveRepoID returns the id of the repo's first active mirror (the repo // id is stable across a repo's placements, so any active one serves). Archived // and failed/suspended placements are skipped — they can't answer for the repo. func firstActiveRepoID(mirrors []coreapi.Mirror) string { for i := range mirrors { if mirrors[i].IsArchived.Or(false) { continue } if st := mirrors[i].Status.Or(coreapi.MirrorStatusReady); st == coreapi.MirrorStatusFailed || st == coreapi.MirrorStatusSuspended { continue } if id := strings.TrimSpace(mirrors[i].MirrorId); id != "" { return id } } return "" }


Acmd/entire/cli/entireapi_client.go+93

package cli

import ( "testing"

"github.com/entireio/cli/internal/coreapi" )

func TestForgeToMirrorProvider(t *testing.T) { t.Parallel()

for _, forge := range []string{"gh", "github", "GitHub", " gh "} { if p, ok := forgeToMirrorProvider(forge); !ok || p != mirrorCloneProviderGitHub { t.Errorf("forgeToMirrorProvider(%q) = (%q, %v), want (%q, true)", forge, p, ok, mirrorCloneProviderGitHub) } } if _, ok := forgeToMirrorProvider("gitlab"); ok { // code for this test } }

func TestFirstActiveRepoID(t *testing.T) { t.Parallel()

archived := coreapi.Mirror{MirrorId: "archived", IsArchived: coreapi.NewOptBool(true)} failed := coreapi.Mirror{MirrorId: "failed", Status: coreapi.NewOptMirrorStatus(coreapi.MirrorStatusFailed)} suspended := coreapi.Mirror{MirrorId: "suspended", Status: coreapi.NewOptMirrorStatus(coreapi.MirrorStatusSuspended)} ready := coreapi.Mirror{MirrorId: "ready-ulid", Status: coreapi.NewOptMirrorStatus(coreapi.MirrorStatusReady)} unset := coreapi.Mirror{MirrorId: "unset-status-ulid"} // no status → treated as active

tests := []struct { name string mirrors []coreapi.Mirror want string }{ {"none", nil, ""}, {"single ready", []coreapi.Mirror{ready}, "ready-ulid"}, {"unset status counts as active", []coreapi.Mirror{unset}, "unset-status-ulid"}, {"skips archived and unhealthy", []coreapi.Mirror{archived, failed, suspended, ready}, "ready-ulid"}, {"all inactive", []coreapi.Mirror{archived, failed, suspended}, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := firstActiveRepoID(tt.mirrors); got != tt.want { t.Fatalf("firstActiveRepoID = %q, want %q", got, tt.want) } }) } }


Acmd/entire/cli/entireapi_client_test.go+50

// New API method testing


Mcmd/entire/cli/recap.go+25/-6