Enhance Entire CLI with Name Resolution

Claude Code·Unknown author·3mo ago·47hr 6min·7 Checkpoints·114 file changes·+4718/-359·504.2K tokens

take a look at this repo, and the ../entiredb repo. we build our public-facing cli here (entire), but we still have some dangling internal tools in ../entiredb (like cmd/entire-*)

have a look at the verbs defined in both repos, and tell me which ones we can clean up out of ../entiredb, since they should start living in the public cli. don't move new ones to public cli, just tell me which ones already live here.

3mo ago·4m

Bottom line

Two clusters in entire-core are already dead deprecation stubs pointing at the public CLI — safe to delete now. Everything else in entiredb's entire-* tools is either platform-admin/break-glass (no public equivalent) or data-plane git ops (no public equivalent), so leave those.

Delete from entiredb — already live in public entire

entiredb verbPublic CLI homeStatus in entiredb
entire-core context listentire auth contextsdeprecation stub
entire-core context useentire auth usedeprecation stub
entire-core context bindentire auth usedeprecation stub
entire-core context currententire auth contextsdeprecation stub
entire-core admin mirrors collaborators addentire repo mirror collaborators adddeprecation stub
entire-core admin mirrors collaborators listentire repo mirror collaborators listdeprecation stub
entire-core admin mirrors collaborators removeentire repo mirror collaborators removedeprecation stub

These already print "DEPRECATED. Please use entire …" — the public command is the canonical home. Pure cleanup, no behavior loss.

Also already migrated (no stub left, just a code comment): entire-core client orgs / projects / grantsentire org / entire project / entire grant. Nothing to delete; just confirms the pattern.

Keep in entiredb — NOT in the public CLI (don't move)

  • entire-core admin core: orgs (list/create/add-member/workos), repos import, access, audit-log, users (list/delete/approve), sa (full service-account suite), mirrors create/list/suspend/resume, oidc list-providers, and all grant/revoke-platform-*. These are platform-admin / break-glass operations. The public entire org/repo/grant verbs look similar but are user-privilege control-plane calls — not substitutes for the admin endpoints.
  • entire-core client api: permissions, me, audit, reauthorize-github — still canonical here.
  • entire-repo (all 13: clone, get-file, ls, log, diff, compare, files, merge-base, branches, tags, merge, rebase, revert) — data-plane git content ops, none exist in the public CLI. The public entire repo group is lifecycle only (create/list/get/delete/mirror), intentionally excludes content ops.
  • entire-backup, entire-ci, entire-deploy, entire-mock-idp — operator/infra tooling, no public-CLI overlap.

One gray area to decide

entire-core admin orgs create, admin repos import, and admin access vs public entire org create / entire repo create / entire grant. They overlap conceptually but differ in privilege (admin --owner/import/bypass vs self-service). I'd keep the admin ones in entiredb unless you intend to fold the admin flags into the public CLI behind a platform-admin check — that'd be a new migration, which you said not to do.

Want me to open a PR in entiredb deleting the 7 stubbed verbs?

nope, that sounds reasonable. next, i'd like us to make sure our set of verbs in the public cli, this repo, actually make sense.

do we have all bases covered? i'm thinking of create/delete for org/project/repo, as well as MAYBE a rename (but that's complex, potentially.

3mo ago·2m

Bottom line

The public CLI already exposes everything the v1 API supports — the gaps you're sensing are almost all backend-side, not CLI-side. repo is the only noun with full CRUD; org/project stop at create+list because the backend's delete/get are admin-only, and rename doesn't exist for any resource — they're immutable by design, so it's a backend project, not a CLI one.

CLI vs. backend coverage

NounCLI todayBackend v1 offersVerdict
repocreate, list, get, deleteCreateRepo, ListProjectRepos, GetRepo, DeleteRepoComplete — the model to match
orgcreate, listCreateOrg, ListOrgs only (delete = admin-only, no get)CLI matches backend; can't add get/delete without backend work
projectcreate, listCreateProject, ListProjects, ListOrgProjects (delete = admin-only, no get)CLI matches except one unused method (see below)
grant orgadd, list, removefull triadComplete
grant projectadd, list, removefull triadComplete
grant repoadd onlyGrantRepoAccess only — no list, no revoke in v1Glaring asymmetry, but blocked on backend

The real findings

1. Rename is off the table. No Update/Rename/Patch on org, project, or repo anywhere in the v1 API — they're immutable after creation (name, region, cluster placement all fixed). This isn't "complex," it's "doesn't exist." Pursuing it means designing backend mutation endpoints first. I'd punt unless there's real user demand.

2. org/project delete + get are not yours to expose. The backend only has these as admin-only (/api/admin/...). To give users entire org delete / entire project delete / * get, the backend has to promote them to the user-facing v1 surface with proper authz — a privilege-model decision, not a CLI gap.

3. The repo-grant asymmetry is the worst UX wart. You can grant repo add but can never list or remove repo grants. That's a dead-end — needs backend ListRepoGrants + RevokeRepoAccess before the CLI can fix it.

4. Two genuine CLI-only quick wins (backend method exists, CLI just doesn't surface it):

  • entire project list --org <id> → backend ListOrgProjects. Today project list only does the global list + name filter.
  • entire grant project remove --provider <p> --provider-user-id <id> → backend RevokeProjectAccessByProvider. Convenience; today removal requires knowing the grantee ULID.

Recommendation

  • Don't build rename. Backend-blocked and low value vs. cost.
  • Don't fake org/project delete/get by reaching into the admin API from the public CLI — that crosses the privilege boundary.
  • File backend asks for: repo-grant list/revoke (the asymmetry), and a decision on user-facing org/project get/delete.
  • Land the two quick wins now — they're pure CLI and make project/grant project consistent with repo.

Want me to spec the two quick wins, or write up the backend asks as a list you can hand off?

i like your suggestion for 4 - implement it in a new branch.

additionally, have a look at the existing create/get/delete/remove verbs - i believe that in many cases we have to provide the ULIDs of (e.g.) the parent project of our new repo. that sucks. can we make that accept project and org friendly names?

3mo ago·7m

Done. Committed to feat/control-plane-friendly-names (not pushed).

What landed

Friendly names (the main ask) — org/project references now accept a name or a ULID anywhere the parent ULID used to be mandatory:

  • entire repo create --project widgets (was: project ULID)
  • entire repo list widgets
  • entire project create foo --owner acme --owner-type org (org name)
  • entire grant org add|list|remove acme
  • entire grant project add|list|remove widgets

A ULID is detected by shape (26-char Crockford base32) and passed straight through — zero extra API calls, identical to today's behavior. A non-ULID triggers a name lookup (ListOrgs / ListProjects?name=) with explicit errors on no-match and on ambiguity (project names aren't globally unique, so it lists the colliding ids + owners and suggests --org/a ULID).

Two API gaps closed (#4):

  • entire project list --org <name|ULID>ListOrgProjects (--name filters client-side since the org-scoped endpoint has none)
  • entire grant project remove --provider <p> --provider-user-id <id>RevokeProjectAccessByProvider, mutually exclusive with the existing --grantee-type/--grantee-id

Verification

mise run fmt && lint clean; mise run test:ci green (59 unit/integration + Vogon + roger-roger canaries). New unit tests cover ULID detection, org/project name picking (match/none/ambiguous), the --org name filter, and the grantee-mode validation.

Deliberate scope decisions

  • project create --owner resolves names only when --owner-type=org. Account owners have no friendly-name index, so they still need a ULID — correct, not a gap.
  • Left as ULID-only: repo self-references (repo get/delete <repo>, grant repo add <repo>). Resolving a repo by name needs a project to disambiguate, which would mean adding a --project scope flag to those commands — more surface than you asked for. Easy follow-up if you want it.

Want me to push, or open the entiredb stub-deletion PR we discussed earlier?

project create --owner resolves names only when --owner-type=org.

for now we only support github-backed user accounts - their 'friendly format' is 'github:name', use that for filtering and creating

3mo ago·5m

Done. Committed (not pushed).

What changed

project create --owner-type=account now accepts a github-qualified handle:

  • github:aliceResolveHandle(provider=github, handle=alice) → account ULID.
  • Mirrors the org path: orgs resolve by plain name, accounts by github:handle, and a raw ULID still passes straight through for either.
  • A handle with no provider: prefix is rejected up front with a hint (must be a qualified handle like "github:alice"), rather than a confusing lookup miss.

Verified

fmt/lint clean; test:ci green (59 + 4). Added TestParseQualifiedHandle covering the happy path, the first-colon split (github:alice:bob → handle alice:bob), and the empty-part/no-prefix rejections.

Note: the generic ResolveHandle takes any provider, so when non-github accounts exist server-side they'll resolve through the same path with no CLI change. The branch now has two commits — say the word and I'll push or open the PR.

i still see you have task tracking, what's that about? are we done here?

3mo ago·19.7s

All five tasks closed — they were just stale labels I forgot to flip, not outstanding work.

Yes, we're done. Everything you asked for is implemented, tested, and committed on feat/control-plane-friendly-names (2 commits, not pushed):

  • Org/project friendly names across repo create/list, project create, grant org/project
  • Account owners by github:handle
  • The two API quick wins (project list --org, grant project remove --provider)
  • fmt/lint clean, test:ci green (59 + 4)

Open threads from earlier, only if you want them — none are blocking:

  • Push / open the PR for this branch
  • The entiredb stub-deletion PR (7 dead entire-core verbs)
  • Follow-up: repo self-reference by name (needs a --project scope flag)

let's rebase on origin/main then open a trail (entire trail create) for this work.

3mo ago·46.5s

Done — rebase was a no-op (main hadn't moved), and the trail is created.

Heads up: entire trail create pushed feat/control-plane-friendly-names to origin as a side effect. You normally have me hold off on pushing — calling out that the trail command did it for us. The branch and its 2 commits are now on origin.

  • Trail: "Control-plane CLI: friendly names + close two API gaps"
  • ID: 019ef229-d689-7a97-9676-0b7b7edc9321
  • Branch → base: feat/control-plane-friendly-namesmain

Want me to open the PR next, or leave it at the trail?

check out the findings on the trail, and the CI lint failure.

3mo ago·2m

Both findings and the CI failure share one root cause, now fixed.

What the findings said

Both from agent:trail-pr-review, Medium severity — and both correct:

  • manual_commit.go:55 — dropping //nolint:ireturn on getCheckpointStore/getTemporaryStore triggers ireturn errors (checkpoint.CommittedStore/TemporaryStore aren't in .golangci.yaml's ireturn.allow).
  • checkpoint/open.go:58 — same for Stores.Temporary() returning TemporaryStore.

That removal was the CI lint failure.

Root cause

Neither file is part of this feature. They were modified outside my session (consistent with your concurrent entire use), and my git add -A swept the edits — minus their nolint directives — into the two feature commits. My local lint passed only because the files were still clean at the moment I ran it; the edits landed before the git add -A.

Fix

  • Restored both files to origin/main (directives back, 3 nolint:ireturn present).
  • mise run lint → 0 issues.
  • Committed as a third commit; branch diff now touches only the 6 intended files.

Not pushed — your standing rule. The fix is local; origin still has the broken tree. Want me to push so CI re-runs and the two findings go stale/resolve? (I'd also suggest git add -p going forward on this branch to avoid re-sweeping the external edits, which are still floating in your working tree elsewhere.)

Note: the two ireturn findings will stay "open" until a new code version is pushed and the review agent re-evaluates — I can't resolve them locally.

cool, commit and push

3mo ago·35.4s

Pushed. 75bb7e62b..d94ccb495 is now on origin.

Status

  • Fix commit pushed — branch on origin now lints clean (ireturn directives restored).
  • CI will re-run on the new code version; the two ireturn findings should go stale/resolve once the review agent re-evaluates.
  • Working tree still shows those two files modified — that's the external process (your concurrent entire) removing the directives again. I left them uncommitted; they didn't go up.

⚠️ Heads up: something on your machine keeps stripping //nolint:ireturn from manual_commit.go and checkpoint/open.go — it re-applied between my commit and now. Until that stops, any git add -A will re-sweep it. Worth git checkout-ing those two files and tracking down what's editing them (a background formatter/agent/worktree). For this branch, stick to git add -p so it can't sneak back in.

anything else outstanding from this session?

3mo ago·19.2s

Findings cleared — the re-review on the pushed fix resolved both. Here's the full rundown.

Done

  • Feature shipped & pushed — friendly names (org/project/account-by-github:handle) across repo/project/grant, plus the two API quick wins (project list --org, grant project remove --provider). 3 commits on origin/feat/control-plane-friendly-names.
  • CI lint failure fixed — restored the swept-in //nolint:ireturn directives; lint clean.
  • Trail created (#641), and both findings now resolved (0 open).

Outstanding (your call — nothing blocking)

  1. No PR yet — only the trail exists. Want one opened?
  2. Working tree still dirtymanual_commit.go + checkpoint/open.go keep getting their nolint directives stripped by some background process on your machine. Not committed/pushed, but worth hunting down what's editing them, or it'll re-sweep on the next git add -A.
  3. Deferred follow-up (noted, not requested): repo self-reference by name (repo get/delete, grant repo add) — needs a --project scope flag.

Closed, not outstanding

  • The entiredb 7-stub deletion — you declined ("nope") earlier.

Want the PR, or are we good here?

we have filled in some CRUD gaps in https://github.com/entirehq/entiredb/pull/2122.

let's open a new branch of cli off origin/main to consume them. or do you think we'd best make a PR as a spinoff of an existing cli branch we worked on today?

3mo ago·51.1s

Clear picture now. Here's the bottom line.

Recommendation: spin off today's branch (stack), don't branch off main

PR #2122 (merged) adds exactly: GetOrg/DeleteOrg, GetProject/DeleteProject, ListRepoGrants/RevokeRepoAccess/RevokeRepoAccessByProvider. The CLI verbs to consume them are org get/delete, project get/delete, grant repo list/remove — which land in the same files today's branch already edits (org.go, project.go, grant.go, repo.go), and they should accept friendly names, which means reusing resolveref.go — a file that only exists on today's branch.

So off origin/main you'd hit both problems:

  • Merge conflicts in org/project/grant.go against today's branch.
  • No resolveOrgRef/resolveProjectRef → either duplicate the helpers or ship the new verbs without friendly names (inconsistent with what we just built).

Stacking on feat/control-plane-friendly-names avoids both: helpers are right there, command files stay conflict-free, and the theme is identical (control-plane CRUD + ergonomics — this is literally "the CRUD gaps I flagged, now fillable").

The one tradeoff: stacked PRs merge in order — today's must land first. Today's is ready (lint green, findings resolved), so that cost is small.

Prerequisite either way

The CLI's generated coreapi client doesn't have the new methods yet — they need to be regenerated from entiredb's updated OpenAPI spec before any command can call them. That's step 1 regardless of branch choice.

Alternative worth a mention

Since today's branch isn't merged and is small, you could just fold the new verbs into it (one PR). Cleaner history, no stack — but a bigger single PR. You asked for a separate branch, so I'm recommending the stack.

Want me to: (1) create feat/control-plane-crud-gaps off feat/control-plane-friendly-names, (2) regenerate the coreapi client, (3) wire up the six new verbs with friendly-name support + tests?

that sounds cool, make a new PR Stacking on feat/control-plane-friendly-names and consuming the new verbs with friendly-name support + tests.

3mo ago·9m

Done. The stacked PR (trail) is up.

Shipped — feat/control-plane-crud-gaps (stacked on feat/control-plane-friendly-names)

Three commits, auto-pushed to origin:

  1. Regenerated coreapi client from entiredb's updated spec (the 7 new ops from #2122).
  2. Adapted grant callers to the now-typed role enum (reader/writer/admin) — grant add validates --role client-side.
  3. Six new verbs + tests:
    • entire org get|delete <name|ULID>
    • entire project get|delete <name|ULID>
    • entire grant repo list <repo>
    • entire grant repo remove <repo> (--provider/--provider-user-id or --grantee-type/--grantee-id)

Org/project args get friendly-name resolution for free (reusing the base branch's resolveref helpers). The grantee-mode + provider/by-id remove logic is now shared between project and repo.

  • Trail: "Control-plane CLI: org/project get+delete, repo grant list+remove" — 019ef250-c241-717b-bb76-db015122482c, base feat/control-plane-friendly-names.
  • Verified: fmt/lint clean, test:ci green (59 + 4). No findings yet (review agent runs async — worth a re-check in a minute).

Two things to flag

  • I fast-forwarded your local entiredb main (it was 5 commits behind origin) to dump the post-#2122 OpenAPI spec, then removed the throwaway test. entiredb is back to clean — no leftover changes.
  • The external nolint-stripping is still happeningopen.go/manual_commit.go got re-stripped in the working tree again mid-session. I kept them out of every commit, but something on your machine is actively editing them. Worth hunting down.

Merge order

feat/control-plane-friendly-names first, then this one. Want me to re-check findings shortly, or open the GitHub-side PR if you'd rather review there?

do we also have repo delete?

3mo ago·19.6s

One caveat worth noting: repo delete/repo get take a ULID only ("Delete a repository by ULID") — they don't accept a friendly name, because resolving a repo by name needs a parent-project scope (the deferred follow-up I flagged earlier). Org and project get/delete do take names.

So the full CRUD picture now:

Nouncreatelistgetdeletename-resolved?
org✓ (new)✓ (new)yes
project✓ (new)✓ (new)yes
repoULID only

If you want repo get/delete <name> too, that's the --project scope flag follow-up — small, and it'd round out the consistency. Want it in this branch or left as a separate follow-up?

yes, let's add name resolution of repos with --project to this branch.

3mo ago·3m

Done — repos now resolve by name across the board.

Added (4th commit on the stack, pushed)

repo get/delete and grant repo add/list/remove now take a repo name when you pass --project <name|ULID> to scope it (repo names are unique only within a project). A ULID still passes straight through and ignores --project.

The CRUD/ergonomics matrix is now consistent:

Nouncreatelistgetdeletename-resolved
orgname
projectname
reponame + --project
grant org/project/repoadd/list/removeparent name (repo via --project)
  • New helper resolveRepoRef + pickRepo (unit-tested: match / no-match / ambiguous). If you give a name without --project, you get a clear error telling you to add it or use a ULID.
  • Verified: fmt/lint clean, test:ci green (59 + 4).
  • Pushed 98e99d371..7dc26c090; the stacked trail updates automatically.

The branch now has 4 commits (regen → role-enum fix → six verbs → repo name resolution), still stacked on feat/control-plane-friendly-names. Worth a findings re-check on the trail in a minute.

so we only have one Trail open here?

3mo ago·20.3s

No — two for this session's work, which is the stack we set up:

  • #641 feat/control-plane-friendly-names — the base (friendly names + 2 API quick wins)
  • #642 feat/control-plane-crud-gaps — stacked on #641 (regen + CRUD-gap verbs + repo name resolution)

That's by design: a stacked pair, #641 merges first. The other two open trails — #640 (feat/trail-web-url) and #628 (docs: region examples) — are older and unrelated; they predate this session, not something we touched.

So: 4 open total under your handle, but only 2 are this session's, and they're intentionally separate (the stack). Want me to fold #642 into #641 as a single trail instead, or leave the stack as-is?

Both review comments addressed. Summary:

PR #1498 (base branch feat/control-plane-friendly-names)

Copilot flagged the project-ambiguity error suggesting --org, which the failing commands (repo create/list, grant project) don't have.

  • Fix: reworded the error to "pass the intended ULID (run entire project list --org <org> to find it)" — points at the already-listed candidate ULIDs and at the one command that actually has --org. Updated the matching doc comment too. Behavior unchanged.
  • Committed (d08809d9f2) and pushed.

PR #1499 (stacked branch feat/control-plane-crud-gaps)

Copilot flagged that the PR description said "repo args ULID-only" but resolveRepoRef adds repo-name resolution. You said keep the capability, fix the docs.

  • Fix: kept the code; rewrote the trail/PR description to document repo-name resolution via --project as a feature. Scanned the repo — no stale "ULID-only" claims in code/comments, only the description.
  • Rebased the branch onto the updated base (the #1498 fix advanced it; resolveref.go merged cleanly), then force-pushed (41bc806aab).

Both branches: fmt/lint clean, tests green. The stack is consistent (crud-gaps now sits on the fixed base).

One thing I did not do: post replies on the GitHub threads themselves — that's publishing as you, and you only asked me to address them. Want me to post brief "fixed in <sha>" replies to close the two Copilot threads?