Integrate EntireDB Commands into CLI

Claude Code·Opus 4.7[1m]·Soph·4mo ago·143hr 13min·38 Checkpoints·193 file changes·+46805/-6036·7.7M tokens

Ok, I want us to integrate the commands from https://github.com/entirehq/entiredb/pull/1339 (it's checked out in ../entiredb) directly into the cli, can you take a look what that would mean? Basically we want "entire repo" (hidden) but show up under "entire labs".

4mo ago·1h 59m

What would you like to clarify? Happy to dig into any of the tradeoffs — the dep model (Go module dep vs vendoring vs leaving it as a plugin), the command placement, or anything about the surface area / scope of what the PR actually exposes.

The issue is a bit that the cli is open source, entiredb isn't yet. Also these parts should move fully into the cli repo going forward but I guess there is more code that is shared between db and cli?

4mo ago·26m

What would you like to clarify? I can dig into any of these — what code is actually private vs already public, what the proto contract looks like, what the migration cost is per option, or anything else about the split.

yeah sorry, maybe just start with vendoring

4mo ago·1h 41m

All clean — 0 issues, no drift. Branch soph/vendor-entiredb-repo has 4 commits ready for review.

Summary

CommitFilesNotes
Vendor entiredb repo CLI surface into internal/entiredb/+~15K LOC across internal/entiredb/**, +go.mod/go.sumPlugin code + minimal client/auth/proto deps. Admin RPC surface stripped from client.go to drop ~5.8K LOC of admin/v1 proto. Imports rewritten to github.com/entireio/cli/internal/entiredb/....
Exclude vendored entiredb tree from house-style linters.golangci.yamlScoped to style/dead-code linters; security linters (errcheck, staticcheck, gosec for non-internal/entiredb/) stay on.
Surface entire repo (hidden) under labscmd/entire/cli/repo.go (new), root.go, labs.go, main.goMirrors review/investigate: Hidden: true, advertised in labs, examples rebranded from entiredb repo to entire repo. Klauspost zlib provider blank-imported in main.go.
Drop redundant nolint:ireturn directives2 unrelated filesCleanup forced by mise run lint --fix; would have happened on any broad lint pass.

Smoke-verified entire repo --help, entire repo clone --help (examples rebranded), entire labs (lists it), and entire --help (doesn't). Lint, unit tests (5,614 pass), and integration tests (340 pass) all green.

❯ go run cmd/entire/main.go repo ls help no logged-in context for entire.io, please login first with 'entire-core auth login' or set ENTIRE_TOKEN exit status 1

4mo ago·13m

Clean history, 5 commits, lint passing. The branch is now:

The bogus "Drop redundant nolint:ireturn directives" commit is gone — those //nolint comments stay where they belong.

On your original report: entire repo ls help was Cobra reading help as a positional arg (the <repo> slot); entire repo ls --help or entire repo help ls both show docs without touching auth. The real bug was the misleading error message — fixed in commit 0afbde7a7. And since you're already logged in (entire auth status shows you on https://entire.io with 5 active tokens), the bridge in 51bb9e9bd now plumbs that token through ENTIRE_TOKEN automatically — verified the gRPC server actually responds to a real token instead of dying locally with "no context".

Known follow-up: entire login doesn't capture refresh tokens, so eventually access tokens expire. The vendored auto-refresh path will log a warning and fall back to the existing bearer — same UX as the cli has always had. Proper refresh-token plumbing is a separate piece of work.

❯ go run cmd/entire/main.go repo ls gh/entireio/cli dial entire.io: resolve "gh/entireio/cli": resolve repo: rpc error: code = PermissionDenied desc = unexpected HTTP status code received from server: 403 (Forbidden); transport: received unexpected content-type "text/html" exit status 1

cli on  soph/vendor-entiredb-repo [$] via 🐹 v1.26.3 ❯ go run cmd/entire/main.go auth status Logged in to https://us.auth.entire.io Token: stored in OS keychain Active tokens on this account: 5

cli on  soph/vendor-entiredb-repo [$] via 🐹 v1.26.3 took 2s ❯ go run cmd/entire/main.go repo ls gh/entireio/cli dial entire.io: resolve "gh/entireio/cli": resolve repo: rpc error: code = PermissionDenied desc = unexpected HTTP status code received from server: 403 (Forbidden); transport: received unexpected content-type "text/html" exit status 1

4mo ago·1h 16m

Quick clarification before I write it — the JWT's iss claim is the auth host (e.g. us.auth.entire.io), not the data host. So "use iss directly" still wouldn't reach the right cluster. The conventional derivation is to strip auth. from iss → us.entire.io. That's the heuristic I'll implement, falling back to iss as-is for single-host setups (where iss == entire.io == data host).

can we pause: what is the cli in the entiredb folder doing where does it get URLs from?

4mo ago·15m

What would you like to clarify? Happy to dig into how entiredb actually handles this in practice, what contexts.json looks like end-to-end, what entire.io's production deployment expects, or anything else about how URLs flow.

hmm:

~ ❯ entire-core --base-url https://eu.auth.entire.io auth login Authenticating against https://eu.auth.entire.io If a browser didn't open automatically, visit: https://eu.auth.entire.io/cli/login?client_id=entire-core-cli&REDACTED&code_challenge_method=S256&redirect_uri=http%3A%2F%2F127.0.0.1%3A52795%2Fcallback&REDACTED Logged in as nodo (01KS54AYRKYJGMGPQ5J0SHPPJR) on https://eu.auth.entire.io Activated context "github:nodo@eu.auth.entire.io" — run 'entire-core context list' to see all contexts

~ ❯ entire repo list { "refs": [] }

~ ❯ entire org list []

~ ❯ entire org create nodo { "CreatedAt": "2026-05-28T18:25:45.980519Z", "ID": "01KSQXBNZT7NJ99ESWJECKABJR", "Name": "nodo", "Region": "eu", "WorkOSOrganizationID": "" }

4mo ago·2m

Wait — let me check what entire repo list and entire org are in your installed binary, because those aren't in this branch's vendored tree.

wait, so a coworker who has the entire cli installed built out of entiredb repo and the entire-core cli did just run the above, he did not had to specify an API host for the repo parts

4mo ago·1h 43m

Branch is at:

Lint clean, tests passing. To use it:

Now the cli mirrors what your coworker has installed:

  • Lifecycle verbs (list, create, delete, grant) hit entire-core directly — no cluster URL needed
  • Data-plane verbs take the cluster as part of the positional (<cluster>/et/<org>/<repo>) — no ENTIRE_BASE_URL env var, matches entiredb's documented model
  • Auth flows through entire-core auth login writing ~/.config/entire/contexts.json — same store the standalone entire-repo plugin reads, no bridge needed

Known follow-up: the cli's own entire login and entire-core auth login still use separate keyring slots. Users currently need entire-core installed and authed for entire repo to work. Bridging is deliberately deferred — the previous attempt got messy and the user-facing UX is fine for now (same as anyone using the production entire-repo binary).

The earlier vendor work (legacy cmd/entiredb/cli, auth bridge, etc.) has been reset out of the branch. The current diff against main is just the two commits above.

ba0ac41Vendor entiredb's entire-repo plugin into internal/entiredb/ Snapshot the kubectl-style entire-repo plugin tree from entiredb's main branch (cmd/entire-repo + internal/cli{auth,api,dial,docs,cobra} + supporting client/auth/url machinery) under internal/entiredb/, so the cli can ship a `repo` subcommand without execing a separate binary. Open-source posture is preserved: only client-side and wire contract code crosses the boundary; no entiredb server internals come along. This is the right surface — earlier work on this branch had vendored cmd/entiredb/cli (the standalone-binary surface, requires ENTIRE_BASE_URL to be set explicitly per command). The actually-in- use surface is cmd/entire-repo, which: - Routes data-plane verbs via the cluster in the positional (<cluster>/et/<org>/<repo>) rather than a global env var. - Routes lifecycle verbs (create, delete, list, grant) through entire-core, no cluster URL required. - Reads auth from ~/.config/entire/contexts.json (written by `entire-core auth login`) rather than a parallel keyring slot. Vendor directory layout mirrors upstream so refresh-from-upstream is a directory-tree rsync rather than a manual diff. The one exception is internal/gogitzlib, lifted to internal/entiredb/ gogitzlib so cmd/entire/main.go can blank-import it for the klauspost zlib provider registration (Go's internal/ visibility rule blocks the double-nested path). Trims relative to upstream: - client/client.go has the admin gRPC surface stripped (Connect / ConnectWithAuth / ConnectWithRefresh + the admin field + the ~20 AdminFoo methods + adminpb import). Eliminates the gen/proto/publicapi/admin/v1 dependency entirely. The repo plugin uses ConnectRepoWithAuth / ConnectRepoWithRefresh (HTTP, not gRPC), so the admin surface is dead code here. - cliauth/creds.go ConnectWithOpsToken and ConnectToNodeWithOpsToken drop with their underlying admin Connect helpers; the rest of the admin token-resolution surface stays for binary refresh cleanliness even though it's unused in our reduced setup. - core/api copies only client.go (the api.Client wrapper). - httputil copies only cloudflare.go and oauth.go (the two files the vendored client.go actually consumes). - tlscert copies only tlscert.go (drops the server-side cert plumbing that pulls in entire.io/slogutil). - slogutil copies only log.go, slogctx.go, interpolation.go, stdout_handler.go (skips memberlist / zap bridge / attr-utils). New module deps added by go mod tidy: - google.golang.org/grpc (auth interceptors + tlscert helper) - github.com/caarlos0/env/v11 (cliauth Config parsing) - github.com/gofrs/flock (contexts.json locking) - github.com/go-jose/go-jose/v4 (clilogin JWT verify) - github.com/cpuguy83/go-md2man/v2 (cobra/doc, used by clidocs) - github.com/lmittmann/tint (slogutil stdout handler) - github.com/oklog/ulid/v2 (core/model IDs) Lint exclusions in .golangci.yaml scope the cli's stricter house style off the vendored tree — it tracks upstream and is refreshed in batches; holding it to cli's rules would make each refresh a chain of style fix-ups that drift from upstream. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 31b6bb3027dd+10187/-3
bfc0d46Surface entire repo (hidden) under labs Mounts the vendored entire-repo plugin tree as a hidden top-level `entire repo` command and lists it in `entire labs` so users can discover the experimental surface without it polluting `entire --help`. Mirrors how review and investigate are wired: functional from the canonical path while still maturing, discoverable through labs, advertised by short summary line. cmd/entire/cli/repo.go wraps internal/entiredb/cmd/entire-repo/cli. NewRootCmd, renames Use from "entire-repo" (the upstream kubectl-style binary name) to "repo", pins CommandDisplayNameAnnotation so cobra reports "entire repo …" in help / docs / error paths, sets Hidden, and walks the subtree to rewrite Example blocks from the upstream `entire-repo` prefix to `entire repo` (so the worked examples in help teach the dispatched command path users actually type). Auth flows through `entire-core auth login` (which writes contexts.json that the vendored cliauth reads). No bridge from the cli's own `entire login` is included here — the credential stores remain disjoint for this iteration. Bridging is a follow-up; for now `entire repo` users need entire-core installed and authed. The klauspost zlib provider from internal/entiredb/gogitzlib is blank-imported from cmd/entire/main.go so the plugin's go-git clone/fetch paths share the klauspost-backed compressor the upstream binary already uses, rather than running on Go's slower stdlib zlib. Registration is install-once at init and panics on conflict, which is safe here since the cli registers no other zlib provider. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: bc869e6f0c89+65

this part:

entire-core --base-url https://eu.auth.entire.io auth login # populates contexts.json

should be done by "entire login"

4mo ago·8m

Branch is now:

What changes for the user flow:

Before: needed both entire login (cli's own) AND entire-core auth login (for entire repo to find a context).

After: just entire login — it now does both writes (cli's own entire-cli keyring + entiredb's entire-core:<iss> keyring + ~/.config/entire/contexts.json entry + flips current_context). The bridge is keyed on the JWT's own iss/handle claims, so single-host and split-host deploys both Just Work without extra config.

Known limits:

  • Bridge skips silently if the device-flow token is opaque (not a JWT) or missing iss/handle.
  • Refresh tokens aren't captured by device flow, so the vendored auto-refresh path logs a warning and falls back to the existing bearer on expiry — same behavior the cli has always had.
  • Failure to write the entiredb half is non-fatal (warning, not error); cli login still completes so non-entire repo surfaces are unaffected.

Test it next time you entire login — entire repo list should work without touching entire-core at all.

cd176ccBridge entire login into entiredb's contexts.json After a successful `entire login` device flow, decode the JWT claims and additionally write the entiredb-format credential store so `entire repo` (and any future vendored entire-repo subcommand) picks up the bearer without a separate `entire-core auth login` call. Specifically, the bridge: - Stores the access token in the OS keyring under the `entire-core:<iss>` service slot (the keychain layout that the vendored cliauth + auth-interceptor expect). - Upserts a kubectl-style entry into ~/.config/entire/contexts.json (the file the vendored cliauth reads) carrying issuer URL, principal handle, and keychain-service name; flips current_context to it. The token's iss and handle come from the JWT's own claims, already validated at runLogin's validateReceivedToken step. Opaque (non-JWT) tokens or claims missing iss/handle skip the bridge with a note — the cli's own keyring write above is unconditional, so non-entiredb surfaces (auth status / list / revoke) are unaffected. Refresh tokens are not bridged: the device flow this CLI uses returns only a bearer. The vendored auth-interceptor's refresh path already logs a warning and falls back to the existing bearer on expiry, matching the cli's pre-existing single-token auth UX. Failure to write the entiredb context is non-fatal — `entire login` prints a warning and still succeeds. The cli's own keyring entry is already persisted by the time the bridge runs, and we'd rather report a partial success than fail an otherwise-good login. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 53e4db72cf5b+177

❯ go run cmd/entire/main.go repo ls gh/entireio/cli repo path "entireio/cli" must start with a surface prefix: "et/" (native), "git/" (legacy native), or "gh/" (GitHub mirror) — e.g. et/entireio/cli exit status 1

4mo ago·37.7s

The error's from the new plugin's positional parser — that's the design, not a regression from our work. The format is <cluster>/<surface>/<org>/<repo> where cluster is the data-cluster host (separate from auth host):

gh/entireio/cli got parsed as cluster=gh, path=entireio/cli — hence the surface-prefix complaint about entireio/cli.

You need the data-cluster host. From entire auth status you're logged in via us.auth.entire.io (auth), so your data cluster is probably us.entire.io or similar. Quickest way to find out:

Each entry should carry a cluster host. Or your coworker can tell you what they use — for the EU example they showed, it'd be e.g. eu.entire.io/gh/entireio/cli or whatever the EU cluster is named.

If the cluster-discovery UX is rough, that's a real complaint we should bring to whoever owns the entiredb CLI — but it's the upstream design, not something our vendor introduced. Want me to dig deeper or is this enough to get you unstuck?

what other commands are there now in entiredb, org?

4mo ago·2m

Here's the surface of each:

entiredb's kubectl-style plugins on main

PluginLOCSubcommandsWhat it does
entire-repo (already vendored)2,157clone, ls, log, files, diff, compare, branches, tags, merge (+--dry-run), rebase (+--dry-run), revert (+--dry-run), get-file, merge-base, create, delete, list, mirror, docsAll user-facing repo verbs: lifecycle (create/delete/list against entire-core), content/data-plane (clone/log/merge/... against cluster), GitHub-mirror placement
entire-org114create <name>, list, docsOrg CRUD — both verbs hit entire-core, no cluster needed
entire-project198create <name>, list [<org>], docsProject CRUD — same model, entire-core only
entire-grant520repo {add,remove,list}, project {add,remove,list}, org {add,remove,list} (each with --handle, --role)Membership / access grants across all three layers, entire-core only

All four read auth from the same ~/.config/entire/contexts.json that entire login now populates (after the bridge we just landed), so they'd all "just work" with one login.

What it'd take to vendor the other three

They're much smaller than entire-repo and share the same shared-package set already vendored (cliauth, cliapi, cliauth, clidocs, client/*). Adding entire-org, entire-project, entire-grant is mostly:

  1. Copy 4 dirs (~830 LOC total: 2 files for org, 2 for project, 5 for grant + the main.go shims)
  2. Rewrite imports — same as before, one sed pass
  3. Three thin wrappers in cmd/entire/cli/ (one per plugin) — each ~20 lines, identical shape to repo.go
  4. Add three labs entries

No new transitive deps; no surgery needed. The org/project/grant plugins are lifecycle-only (entire-core HTTP, no gRPC, no scoped JWT exchange), so they're simpler than entire-repo.

Want me to vendor them in the same branch?

yes, let's do this

4mo ago·5m

Branch is now:

entire labs now lists all four:

After entire login (which now also writes contexts.json), the full kubectl-style surface should work without entire-core or the separate plugin binaries installed. ~830 LOC of new vendored source, no new module deps.

can we, and I now this is a diff from the source, do what we do in other places already and also show help instead of this simple error:

❯ go run cmd/entire/main.go project create accepts 1 arg(s), received 0 exit status 1

4mo ago·10m

Branch is now 6 commits. entire project create (and any other vendored arg-count failure) now prints the failing subcommand's usage block before the error — matching how entire foobarbaz and entire --bogus-flag already worked.

Test it:

Other error paths (unknown command, unknown flag, runtime errors) are unchanged.

The matcher is a string substring on "arg(s)" — cheap, stable across cobra's stock validators, doesn't reach into cobra internals. The switch from ExecuteContext to ExecuteContextC is the only structural change; it gives us back the deepest matched command so we can show its usage rather than the root's.

ok, let's open this as a draft PR for now

4mo ago·2m

Draft PR opened: https://github.com/entireio/cli/pull/1294

6 commits, +11,384 LOC (most of it the vendored entiredb plugin tree + the shared cliauth/cliapi/client/* infrastructure). Body covers the layout, the auth-bridging work, the UX polish (entire login → contexts.json, usage-on-arg-error), what got trimmed vs upstream, new module deps, the test plan I ran locally, and three known follow-ups.

ok, let's pivot here and do an alternate approach. What if we don't port over any code but rather implement this as first class client to the openapi endpoints in the cli. This is the one for entiredb https://aws-us-east-2.entire.io/api/v1/docs And this is for creating orgs, grants: https://us.console.entire.io/api/v1/docs

can you take a look?

3mo ago·8m

No response requested.

sorry, thinking again: we only should care about the verbs/commands in the other PR, we probably don't need any of the APIs on Entire Repo API thinking more.

3mo ago·14m

oapi-codegen 2.4.1 doesn't support OpenAPI 3.1's nullable-array shorthand (type: [array, null]). Let me survey how much 3.1-specific syntax is in the spec before deciding how to handle it.

let's try https://github.com/ogen-go/ogen instead then?

3mo ago·1m

Only 5 occurrences, all the same construct (type: ["array","null"] — huma's 3.1 nullable shorthand). Let me see exactly which schemas, then decide between stripping them or normalizing.

ok, different compare https://github.com/pb33f/libopenapi and https://github.com/ogen-go/ogen and pick the better

3mo ago·4h 1m

Done. The OpenAPI-first approach is complete and committed on soph/entire-control-plane-client (2 commits, off origin/main):

What this delivers vs the vendor approach

Vendor PR (#1294)This branch
Source~11K LOC of entiredb's private Go, hand-trimmedGenerated from the public OpenAPI spec
Open-source storyPrivate code crosses the boundaryNothing private — curl + go generate
Refreshrsync + manual surgerycurl spec && go generate ./internal/coreapi/...
Authbridge into contexts.jsonreuses entire login's token directly via auth.TokenForResource

The pipeline (all reproducible via go generate)

  1. spec/core.openapi.json — pristine upstream spec
  2. spec/normalize.go — collapses huma's type:["array","null"] → "array", and replaces per-operation 4xx/5xx with a single default so ogen emits convenient errors ((*Org, error) not sum types)
  3. ogen (client-only) → 24K LOC typed client

Verified working

  • entire org list → [] against the live control plane (real authenticated round-trip)
  • Errors surface as clean problem-detail messages via APIError
  • entire labs lists all four; --help works on each; hidden from root help
  • go build / go test ./... (54 pkgs) / go vet / lint all clean; unit test on the error mapper

On the two open questions from earlier

  • ogen vs libopenapi: ogen won — libopenapi is a spec toolkit, not a client generator. The 3.1 nullable blocker turned out orthogonal to the generator choice and is handled by the normalizer (which becomes a no-op once entiredb's server emits non-null arrays — your "we'll fix the spec" path).
  • Base-URL discovery: the control plane is served at the login host (<auth-host>/api/v1), so api.AuthBaseURL() is correct and consistent with the existing auth commands. A user on the bare entire.io default needs ENTIRE_AUTH_BASE_URL set — same constraint entire auth status already has.

Want me to open this as a draft PR (and/or close the vendor PR #1294), add more coreapi tests, or wire any of the remaining Core API surface (service-accounts, mirrors, audit, me)?

/simplify → 4 cleanup agents in parallel → apply the fixes

You are improving the quality of the changed code, not hunting for bugs. Review it for reuse, simplification, efficiency, and altitude issues, then fix what you find. Do not look for correctness bugs — that is what /code-review is for.

Phase 0 — Gather the diff

Run git diff @{upstream}...HEAD (or git diff main...HEAD / git diff HEAD~1 if there's no upstream) to get the unified diff under review. If there are uncommitted changes, or the range diff is empty, also run git diff HEAD and include the working-tree changes in scope — the review often runs before the commit. If a PR number, branch name, or file path was passed as an argument, review that target instead. Treat this diff as the review scope.

Phase 1 — Review (4 cleanup agents in parallel)

Launch 4 independent review agents via the Agent tool, all in a single message so they run concurrently. Pass each agent the diff and one of the four angles below. Each returns its findings with file, line, a one-line summary, and the concrete cost (what is duplicated, wasted, or harder to maintain).

Reuse

Flag new code that re-implements something the codebase already has — Grep shared/utility modules and files adjacent to the change, and name the existing helper to call instead.

Simplification

Flag unnecessary complexity the diff adds: redundant or derivable state, copy-paste with slight variation, deep nesting, dead code left behind. Name the simpler form that does the same job.

Efficiency

Flag wasted work the diff introduces: redundant computation or repeated I/O, independent operations run sequentially, blocking work added to startup or hot paths. Name the cheaper alternative.

Altitude

Check that each change is implemented at the right depth, not as a fragile bandaid. Special cases layered on shared infrastructure are a sign the fix isn't deep enough — prefer generalizing the underlying mechanism over adding special cases.

Phase 2 — Apply the fixes

Wait for all four agents to complete, dedup findings that point at the same line or mechanism, and fix each remaining one directly. Skip any finding whose fix would change intended behavior, require changes well outside the reviewed diff, or that you judge to be a false positive — note the skip rather than arguing with it. Finish with a brief summary of what was fixed and what was skipped (or confirm the code was already clean).

3mo ago·1h 8m

Cleanup complete. Here's what the four agents found and what I did.

Applied (1 fix)

Altitude — centralized the repeated RunE preamble. All 14 command bodies repeated the same invariant: SilenceUsage = true → build client → call → renderCoreError → printJSON. Extracted two helpers in corecmd.go:

  • runCoreJSON(cmd, fn) — for the read/create commands that render JSON
  • runCore(cmd, fn) — for delete/revoke/remove that print a message

The four command files now express only their per-command logic (the API call + flags). Removed ~4 lines × 14 sites of boilerplate and the now-redundant newCoreClient wrapper.

Follow-on: the refactor surfaced wrapcheck on closures returning bare coreapi errors (wrapcheck allows direct return f() tail-calls, which is why create/get weren't flagged, but not x, err := f(); return err). Since the whole point of renderCoreError is to map those errors centrally, I declared that intent once via wrapcheck.ignore-package-globs: [.../internal/coreapi] rather than scattering 6 fmt.Errorf wraps that renderCoreError would immediately unwrap.

Skipped (with reasons)

  • Efficiency — "STS exchange per request" in BearerAuth (flagged High): false positive. Verified auth-go's tokenmanager caches exchanged tokens, and each CLI command makes exactly one API call — so there's nothing to amortize.
  • Efficiency — double AST pass in normalize.go: build-time-only (runs in go generate), and the two transforms walk different subtrees (all-schemas vs paths→operations); merging would hurt clarity for zero runtime benefit.
  • Reuse — printJSON duplicates an inline pattern in auth.go/sessions.go: there's no existing shared helper to call, only the same inline idiom repeated. Extracting a repo-wide one would touch unrelated files, out of scope for this diff.
  • Simplification/Altitude — collapse the per-resource command structure or optional-field wrapping: both agents agreed the per-command repetition is idiomatic cobra and the type diversity (3 distinct input bodies, different Opt enums) makes a generic builder more complex than the repetition. Left as-is.

Build, lint (0 issues), and tests all green; entire org list still round-trips live, help output and the delete message-path are intact. Only the 5 intended files changed (.golangci.yaml + the 4 command files + corecmd.go); the generated client and unrelated files are untouched. These are working-tree edits on top of the two existing commits — want me to amend them into the command commit, or leave them staged for you to review?

❯ go run cmd/entire/main.go repo list accepts 1 arg(s), received 0 exit status 1

can we make this nicer again?

3mo ago·3m

Scope is clean now — only my intended files. entire repo list (and every other arg-count error) now prints the failing subcommand's usage block before the error:

The change (in main.go): switched ExecuteContext → ExecuteContextC to recover the deepest matched command, and added an isPositionalArgError branch that routes cobra's accepts N arg(s) failures through the existing showSuggestion path — same mechanism unknown command/unknown flag already used. Unknown-command and normal errors are unchanged; build + lint green.

This sits on top of the /simplify working-tree edits. Current uncommitted set:

  • main.go — usage-on-arg-error (this change)
  • corecmd.go / org.go / project.go / repo.go / grant.go — the runCore/runCoreJSON refactor
  • .golangci.yaml — wrapcheck ignore-package-globs for coreapi

Want me to fold these into the existing two commits (the refactor into the "Surface …" commit, main.go as its own), or leave them staged for your review?

where is the mirror command now

3mo ago·4m

The generated client already has all four mirror operations (they came free from the spec) — there's just no CLI command in front of them. So entire mirror doesn't exist on this branch yet.

To be precise about what's where:

Vendor PR (#1294)This branch
Commandentire repo mirror (placement + point local clones at GitHub mirrors)none wired
Client methodsvendoredpresent — ListMirrors, CreateMirror, GetMirror, DeleteMirror in internal/coreapi/oas_client_gen.go

One caveat worth flagging: the vendor's entire repo mirror was a mixed data-plane/placement command (it could repoint your local git remote at a mirror). The Core API's /mirrors is the control-plane view — managing mirror records (create/list/get/delete). So it's the same domain but not a 1:1 behavioral replacement; the "repoint my local clone" part isn't a Core API operation.

Wiring it is cheap though — a thin cmd/entire/cli/mirror.go using the existing helpers (runCoreJSON/runCore), exactly like the other four. Want me to add entire mirror with list / create / get / delete? If so, let me check the CreateMirrorInputBody shape to get the flags right — and I'd add it to the labs list alongside the others.

can you check the latest code in entiredb how this works there "entire repo mirror"

3mo ago·29m

What would you like to clarify? I can dig deeper into any part — e.g. exactly what the STS repo-scoped token exchange does and whether auth.TokenForResource could cover it, what git-remote-entire requires, whether the readiness poll is essential or optional, or how the entiredb CLI expects users to invoke this end-to-end.

can you explain the tradeoff between 1 and 2?

3mo ago·48.5s

Both wire the same four record operations (create POST, list, get, remove). The only difference is what mirror create does after the POST succeeds.

What the increment actually is

Option 1 — mirror create returns the moment the placement record exists:

That's one POST /api/v1/mirrors and done.

Option 2 — after the POST, it polls until the initial GitHub→EntireDB clone is actually usable:

Why that poll is expensive to add here

The record (option 1) lives entirely on the control plane — the single host this branch is built around, reached with the generated client and the login bearer. The readiness poll (option 2) talks to the data-plane cluster, which is a different host with a different auth requirement, and that's precisely the boundary this branch was designed to not cross. Concretely, the poll needs three things the control-plane client doesn't:

NeedWhyCost
Repo-scoped STS tokenThe cluster's git endpoint (info/refs) rejects the raw login JWT with 403; it only accepts a token scoped to /gh/<owner>/<repo> with the cluster as audienceauth.TokenForResource exchanges for the control-plane audience, not this. Either reuse the lower-level auth.Token(ctx, TokenRequest) with a hand-built repo-scoped request, or vendor cliauth.ExchangeRepoScopedToken. This is the real work.
Smart-HTTP probeDecide "clone finished" by decoding the pkt-line info/refs advert and checking HEAD resolvesgo-git packp is already a cli dep, so this part is cheap
Cluster hostWhere to probeAlready an arg (mirror create <url> <cluster-host>), so free

So option 2 reintroduces a slice of exactly the data-plane/STS machinery the OpenAPI-first pivot removed — the architectural cost is bigger than the ~30 lines of poll loop suggest.

The UX side — why it's not just polish

Without the poll, create succeeds but the mirror may not be clonable yet (initial sync still running). A user who immediately runs git clone entire://… hits a confusing failure/hang and has to retry blindly. The poll is what makes "create succeeded" mean "you can clone now." That's why entiredb's CLI does it by default (with --no-wait to opt out).

The honest framing

  • Option 1 is complete and clean for "I want the mirror record to exist" (and for mirrors that are already synced). It keeps the branch a pure control-plane client. Its weakness is the silent gap between "record created" and "actually clonable."
  • Option 2 is the faithful entire repo mirror create behavior, but it punctures the control-plane-only boundary — you're now maintaining a repo-scoped token exchange and a data-plane probe in a branch whose whole pitch was "single host, generated client, no data-plane."

A reasonable middle path: ship option 1 now, and make the success output set the expectation honestly (e.g. "mirror registered; initial sync may take a few minutes before git clone works"). Add the readiness wait later as a deliberate, separate piece if/when the data-plane auth helper is worth bringing over — rather than smuggling it in with mirror.

Which way do you want to go?

Basically the auth we are missing is the one to do a repo clone?

3mo ago·39.6s

Yes — exactly. The auth the readiness poll needs is the same credential a git clone of that repo needs.

Here's the precise chain from the code:

  • The poll hits GET https://<cluster>/gh/<owner>/<repo>/info/refs?service=git-upload-pack — that's literally the first request git clone makes (the smart-HTTP ref advertisement).
  • The data-plane git endpoint (AcceptGitAuth) rejects the login bearer with 403. The code comment is explicit: "only accepts repo-scoped JWTs — a raw login JWT is rejected with HTTP 403. Exchange the login JWT via core for a repo-scoped token first."
  • So it calls ExchangeRepoScopedToken(…, repoSlug, "pull", "https://"+clusterHost, …) — an RFC 8693 exchange that mints a token scoped to that one repo, action pull, audience the cluster. That token is then used as basic-auth for the probe.

So the missing auth isn't mirror-specific at all — it's the per-repo, data-plane "pull" token that gates every read against a cluster's git endpoint: clone, fetch, info/refs, ls, log, diff. The readiness poll just happens to be the one control-plane command that reaches across into data-plane territory and therefore needs it.

This is the same thing we dropped when we scoped to control-plane-only. Recall the split:

TokenAudienceUsed byHave it?
login bearercontrol-plane hostorg/project/repo-lifecycle/grant, mirror create POST✅ auth.TokenForResource
repo-scoped "pull"cluster + /gh/<owner>/<repo>git clone/fetch/ls/log/diff, mirror readiness poll❌ not wired here

What we have today (auth.TokenForResource) only ever exchanges for the control-plane audience. The repo-scoped one is a different exchange shape (per-repo resource, action scope, cluster audience). The cli's lower-level auth.Token(ctx, TokenRequest) can set a custom audience/resource/scope, so it's plausibly buildable without vendoring cliauth — but it would need to match exactly what the server's repo-exchange validator expects, which I'd want to verify against the server before relying on it.

Two implications worth naming:

  1. mirror create with the wait (option 2) and a future entire repo clone need the identical building block. If we build the repo-scoped exchange once, it unlocks both — the readiness poll and the whole data-plane read surface we set aside.
  2. Note git clone entire://… already works without us — git-remote-entire does that exact repo-scoped exchange internally. So users can clone today; what we'd be adding is the CLI doing the exchange itself (in Go) to probe readiness rather than shelling out to git.

So: option 1 stays purely control-plane. Option 2 means building (or vendoring) the repo-scoped "pull" exchange — which is the real prerequisite for ever doing data-plane git reads from this CLI directly. Worth deciding whether that's a door you want to open now or keep closed until there's a clear reason to do clone/ls/log here too.

Let's build the repo token exchange too

3mo ago·50m

The repo-scoped token exchange is built, tested, and committed. Branch now at 5 commits:

What I built

auth.RepoScopedToken(ctx, clusterBaseURL, repoSlug, action) in cmd/entire/cli/auth/repo_token.go — the credential the data plane requires for any git read (clone/fetch/info-refs), which the control-plane login bearer can't satisfy.

Key decisions, grounded in what I verified:

  • Built on auth-go's sts.Client (the same package the tokenmanager already uses), not a hand-rolled POST and not the tokenmanager itself. The tokenmanager would have sent a stray resource param alongside audience; sts.Client lets me send audience+scope only — matching entiredb's known-good form byte-for-byte.
  • Reuses the cli's own identity — I confirmed the cli's v2 provider already uses client_id=entire-cli and STSPath=/oauth/token, identical to entiredb's exchange, so a successful entire login implies a usable exchange. No new config, no vendored auth.
  • Explicit requested_token_type because sts.Client (unlike the tokenmanager) applies no default.
  • A test locks the wire contract (grant_type, both token-types, audience, scope=repo:pull, client_id, and the absence of resource) via an injected transport — so the form the server accepts can't silently drift.

What this unlocks

This is the foundation we identified: the same per-repo "pull" token gates mirror create's readiness poll and any future entire repo clone/ls/log against the data plane.

I committed the helper on its own (and folded in the earlier /simplify refactor + usage-on-arg-error as their own commits, per your "meaningful commits in between"). I have not yet wired it into a consumer — next step would be entire mirror with the create-time readiness wait (option 2). Want me to wire that now, or pause here with the exchange primitive landed?

what's the command now to create a mirror?

3mo ago·2m

There isn't one yet — on this branch nothing mirror-related is wired into the CLI. entire mirror just falls through to the root help.

What exists so far is only the plumbing, not a command:

  • the generated coreapi client has CreateMirror/ListMirrors/GetMirror/DeleteMirror/LookupRepoByMirror
  • auth.RepoScopedToken (the data-plane credential the create-time readiness poll would need)

No cmd/entire/cli/mirror.go, no labs entry. I stopped at the exchange primitive and asked before wiring the command — so there's currently no way to create a mirror from this CLI.

When wired (the option-2 path we discussed), it would be:

— POST /api/v1/mirrors, then poll the cluster's info/refs (using auth.RepoScopedToken) until the initial clone is ready, matching entiredb's entire repo mirror create.

Want me to wire entire mirror now (create with the readiness wait, plus list/get/remove)?

I think we should wire it into repo, so "entire repo mirror"

3mo ago·1h 37m

entire repo mirror is wired and committed. Branch now at 6 commits.

What you can run now

create registers the placement (POST /mirrors), then — unless --no-wait — blocks until the initial GitHub→EntireDB clone is usable, so a follow-up git clone actually works. That readiness wait is what the repo-scoped token exchange we built last step unlocked: it mints a repo:pull token via auth.RepoScopedToken and polls the cluster's smart-HTTP info/refs, decoding the advert with go-git packp until HEAD resolves.

Verified

  • entire repo mirror list returns real mirror records live against the control plane (full path: ogen client + login bearer)
  • All four subcommands show correct help; repo now lists mirror alongside create/list/get/delete
  • fmt/lint/tests green; scope limited to the three intended files

What I deliberately left out

The mirror use / --disable local-remote rewrite from entiredb — that's a git-config + git-remote-entire concern outside the control-plane API, and git clone entire://… already works through git-remote-entire without the CLI. Noted in the commit so it's an explicit decision, not an oversight.

One caveat worth flagging: I tested list live (read), but not an actual create with the readiness poll end-to-end — that needs a real GitHub repo to mirror and write access, which I didn't want to do against your account without asking. The wire form of the token exchange is locked by a unit test, and the probe reuses go-git's well-tested packp decoder, but the create→poll happy path hasn't run against a live cluster. Want me to walk through a real create to confirm, or is that something you'd rather drive yourself?

can you push and open a draft PR?

3mo ago·1m

Pushed and opened as a draft: https://github.com/entireio/cli/pull/1299

What's in it

6 commits on soph/entire-control-plane-client off main:

The diff is +29.5K, but only ~18 files are hand-written — the bulk is the ogen-generated oas_*.go + the committed spec/normalized-spec. The PR body calls that out and explains the generation pipeline so reviewers know what to actually read.

Heads-up for review

  • The body's test plan is honest about the one gap: repo mirror create's clone-readiness poll hasn't been exercised against a live cluster (needs a real GitHub repo + write access). The token-exchange wire form is unit-tested and the probe reuses go-git's packp, but the create→poll happy path is unverified end-to-end. Flagged as an unchecked box.
  • It's a draft, so CI can run before you decide on review.

This is a separate PR from the vendor approach (#1294) — they're alternative implementations of the same goal. Worth deciding whether to close #1294 now that this exists, but I left it alone.

86391d2coreapi: generate a typed Core API client with ogen Add a generated Go client for the Entire control-plane (Core) API — the surface that manages orgs, projects, repos, mirrors, grants, and service accounts. This is the OpenAPI-first alternative to vendoring entiredb's CLI Go packages: nothing private crosses the boundary, the client is regenerated from a public spec, and a refresh is a curl plus `go generate`. Pipeline (internal/coreapi/gen.go): 1. spec/core.openapi.json — the pristine upstream spec, fetched verbatim from <core-host>/api/v1/openapi.json. 2. spec/normalize.go — rewrites it into spec/core.gen.json: - collapses huma's JSON-Schema-2020-12 nullable shorthand (`type: ["array","null"]` → `"array"`), which ogen's scalar `type` model rejects, matching the server's move to non-nullable arrays (absent ⇒ `[]`, never null); - replaces each operation's explicit 4xx/5xx responses (all of which already reference the same ErrorModel) with a single `default` error response, so ogen emits convenient errors — operations return `(*T, error)` with non-2xx surfacing as a typed `*ErrorModelStatusCode` — instead of a per-operation response sum type that forces a type switch at every call site. 3. ogen (ogen.yml) — generates client-only (server / webhooks / unimplemented / otel features disabled), 24k LOC of typed models + client across the oas_*.go files. client.go is the hand-written surface: - New() dials <auth-host>/api/v1 (the login host is the control-plane host; ENTIRE_AUTH_BASE_URL overrides) and authenticates with the logged-in bearer, resolved lazily per request via auth.TokenForResource so an RFC 8693 exchange happens transparently when the stored token's audience doesn't cover the control plane. - APIError(err) extracts the RFC 7807 problem detail/title for a clean user-facing message; covered by client_test.go. New module deps are the ogen runtime family (go-faster/jx, go-faster/errors, ogen-go/ogen runtime packages) pulled in by the generated code. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 328d33e6c069+28380
b7c8dd8Surface entire org / project / repo / grant under labs Wire the control-plane verbs onto the cli as hidden top-level commands, each backed by the generated coreapi client and listed in `entire labs` while the surface matures. Mirrors how review and investigate are wired: functional from the canonical path, discoverable through labs, hidden from `entire --help`. Commands (all authenticated as the logged-in user, no extra config — auth flows through the same token `entire login` stored): entire org create <name> [--region] list entire project create <name> --owner <id> --owner-type <org|account> [--region] list [--name] entire repo create <name> --project <id> [--cluster-host] list <project> get <repo> delete <repo> entire grant org {add,list,remove} <org> --provider --provider-user-id [--role] project {add,list,remove} <project> --provider --provider-user-id --role [--grantee-type] repo add <repo> --provider --provider-user-id --role [--grantee-type] Scope is the control plane only — git content operations (clone, log, diff, server-side merge/rebase/revert) are deliberately out of scope for this surface. Repos are listed per-project because the Core API exposes no global repo list. corecmd.go holds the shared helpers: newCoreClient (one construction + error-context site), renderCoreError (maps an API error to a SilentError carrying the server's problem-detail message), printJSON (scriptable output; a --format=table flag can branch here later), and markRequired (marks flags required, panicking on the wiring-time-only typo case). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: ac9c3aa495b0+656
303718fauth: add repo-scoped token exchange for data-plane git access The control-plane login bearer is rejected (HTTP 403) by a cluster's git endpoints — clone, fetch, info/refs all require a short-lived token whose RFC 8693 audience is <clusterBaseURL><repoSlug> and whose scope is repo:<action>. git-remote-entire does this exchange internally for the entire:// transport; this adds the same exchange in-process so the CLI can read the data plane directly (the immediate consumer being a mirror clone-readiness probe; the broader use being any future clone/ls/log). auth.RepoScopedToken builds on the auth-go sts.Client — the same package the tokenmanager already uses — targeting the CLI's own configured core endpoint and identity (AuthBaseURL + provider STS path, client_id entire-cli). Those already match the standalone entiredb CLI's exchange, so the wire form is identical and a successful `entire login` implies a usable exchange. It deliberately sends audience + scope only (no resource): the data plane's git gate keys its repo check on the audience host+slug, and a divergent resource param risks the server validating a value it didn't grant. requested_token_type is set explicitly because sts.Client, unlike the tokenmanager, applies no default. repo_token_test.go locks the wire contract (grant_type, both token types, audience, scope=repo:pull, client_id, and the absence of resource) via an injected transport, so a future refactor can't silently drift the form the server accepts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 15bcbac25ac5+194
273826dAddress PR review: visible errors, scoped login hint, ctrl-c, licenses PR #1299 review fixes: - SilentError swallowed API errors. renderCoreError, parseProjectOwnerType, and the mirror create/remove invalid-URL paths returned NewSilentError, which main.go skips printing — so a 409 conflict or bad flag produced no output at all. Return plain errors (with SilenceUsage already set), which main.go prints. SilentError is now reserved for the genuinely-quiet case. - BearerAuth showed "run entire login" for every auth failure. Gate the hint on errors.Is(err, auth.ErrNotLoggedIn); other failures (STS rejection, network, bad config) now surface verbatim. Also pass the exchange resource through api.OriginOnly. - Ctrl+C during a mirror clone-wait printed "timed out…: context canceled". Map context.Canceled to a SilentError so cancellation exits quietly; a real deadline still reports. Also use a single time.Ticker instead of allocating a timer per loop iteration. - --insecure-http-auth wasn't wired for the control-plane commands, so plain-HTTP dev cores were unusable. Add it as a hidden persistent group flag; runCore calls auth.EnableInsecureHTTP() before building the client. - grant doc comment claimed repo has add/list/remove; it only has add (the Core API exposes no repo-grant list/revoke). Corrected. - License check: go-licenses reports github.com/segmentio/asm as "Unknown" though it's MIT. Add a .license-overrides file (module -> SPDX) and teach mise lint:licenses to apply it (portable to bash 3.2). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: 1fb181087a92+122/-24