# feat(control-plane): resolve repos by name with --project scope

`ec08c2b`·

toothbrush·3w ago·4 files·+133 added/-14 removed

repo get/delete and grant repo add/list/remove now accept a repo name
(not just a ULID) when --project <name|ULID> is given to scope the
lookup — repo names are unique only within a project. A ULID still
passes through unchanged and ignores --project.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

## Sessions

ab8e423ff72fView transcript

## Changes

4

- cmd/entire/cli

- Mgrant.go+24/-7

- Mrepo.go+24/-7

- Mresolveref.go+48

- Mresolveref_test.go+37

```
347 unmodified lines

}

func newGrantRepoAddCmd() *cobra.Command {
	var provider, providerUserID, role, granteeType string
	var provider, providerUserID, role, granteeType, project string
	cmd := &cobra.Command{
		Use:   "add <repo>",
		Short: "Grant access to a repo",
		// ...
	}
}

```

func newGrantRepoListCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "list <repo>",
		Short: "List repo grants",
		Args:  cobra.ExactArgs(1),
		// ...
	}
}

```

func newGrantRepoRemoveCmd() *cobra.Command {
	var granteeType, granteeID, provider, providerUserID string
	var granteeType, granteeID, provider, providerUserID, project string
	cmd := &cobra.Command{
		Use:   "remove <repo>",
		Short: "Revoke repo access from a grantee",
		// ...
	}
}

```

func newRepoGetCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "get <repo>",
		Short: "Show a repository by name or ULID",
		// ...
	}
}

```

func newRepoDeleteCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "delete <repo>",
		Short: "Delete a repository by name or ULID",
		// ...
	}
}

// bindRepoProjectFlag wires the shared --project scope used to resolve a repo
// addressed by name (a repo name is unique only within its project). Ignored
// when the repo arg is already a ULID.
func bindRepoProjectFlag(cmd *cobra.Command, project *string) {
	cmd.Flags().StringVar(project, "project", "", "owning project (name or ULID); required when <repo> is a name")
}

```

// resolveRepoRef turns a repo reference into its ULID. A ULID passes through.
// A name requires a project scope (projectRef, itself a name or ULID) because
// repo names are unique only within a project: we list that project's repos and
// match by name.
func resolveRepoRef(ctx context.Context, c *coreapi.Client, ref, projectRef string) (string, error) {
	if looksLikeULID(ref) {
		return ref, nil
	}
	if projectRef == "" {
		return "", fmt.Errorf("repo %q is a name; pass --project <name|ULID> to resolve it, or use a repo ULID", ref)
	}
	projID, err := resolveProjectRef(ctx, c, projectRef)
	if err != nil {
		return "", err
	}
	out, err := c.ListProjectRepos(ctx, coreapi.ListProjectReposParams{ProjectId: projID})
	if err != nil {
		return "", err
	}
	return pickRepo(out.Repos, ref)
}

// pickRepo selects the single repo named name within an already-scoped project
// listing. Repo names are unique per project, so a name matches at most one;
// zero is an error pointing at `repo list`, and (defensively) multiple lists
the colliding ids. Matching is case-insensitive (EqualFold), consistent with
// pickOrg/pickProject.
func pickRepo(repos []coreapi.Repo, name string) (string, error) {
	var matches []coreapi.Repo
	for _, r := range repos {
		if strings.EqualFold(r.Name, name) {
			matches = append(matches, r)
		}
	}
	switch len(matches) {
	case 1:
		return matches[0].ID, nil
	case 0:
		return "", fmt.Errorf("no repo named %q in that project (run `entire repo list <project>` to see names, or pass a ULID)", name)
	default:
		ids := make([]string, len(matches))
		for i, r := range matches {
			ids[i] = r.ID
		}
		return "", fmt.Errorf("repo name %q is ambiguous (%s); pass a ULID instead", name, strings.Join(ids, ", "))
	}
}

// filterProjectsByName narrows projects to name matches, returning all of them
// when name is empty. Used by `project list --org` to apply --name client-side,
// since the org-scoped list endpoint has no name parameter. Matching is
