hubclient

package
v0.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 16, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package hubclient is the terminal's side of a cloop hub with single sign-on (Task 20412): where `cloop login` keeps the session it was approved for, the device-authorization client that gets one, and the handful of API calls the `cloop remote` commands and `cloop task attach` make with it.

The credential file

$XDG_CONFIG_HOME/cloop/hubs.json (~/.config/cloop/hubs.json when the variable is unset), one entry per hub URL plus an optional default. The session credential in it is the bearer the hub authenticates the terminal by, so the file is written 0600 in a 0700 directory, and a file anybody but its owner can read is refused outright rather than used: a credential another account could have copied is one to replace, not to keep presenting. Nothing in this package prints a credential, and every error that mentions the file names its path and its mode, never its contents.

Index

Constants

View Source
const FileName = "hubs.json"

FileName is the credential file's name inside the cloop config directory.

Variables

View Source
var (
	// ErrDenied: the code was denied in the browser, or the hub refused it.
	ErrDenied = errors.New("the sign-in was denied")
	// ErrExpired: nobody approved the code in time.
	ErrExpired = errors.New("the code expired before it was approved")
)

Poll errors a terminal can act on.

View Source
var ErrAuthorizeUnsupported = errors.New("this hub predates authorising a Kubernetes share's revoke or renew " +
	"before the cluster is touched (GET /api/secrets/{id}/authorize); upgrade it")

ErrAuthorizeUnsupported is a hub that predates asking it, before the cluster is touched, whether a share may be revoked or renewed (Task 20420).

View Source
var ErrInsecurePermissions = errors.New("hubclient: the credential file is readable by other users")

ErrInsecurePermissions means the credential file is readable by somebody other than its owner, or is not a plain file, and was refused.

View Source
var ErrRenewUnsupported = errors.New("this hub predates Kubernetes share renewal (POST /api/secrets/{id}/renew); upgrade it")

ErrRenewUnsupported is a hub that predates Kubernetes share renewal.

View Source
var ErrStreamEnded = errors.New("the hub closed the log stream")

ErrStreamEnded is FollowLogs ending because the hub closed the stream on the session's account: it was revoked or expired, or access to the project was withdrawn. The message says which.

Functions

func DefaultPath

func DefaultPath() (string, error)

DefaultPath returns where the credential file lives: under $XDG_CONFIG_HOME, or ~/.config when that is unset or not absolute — the XDG Base Directory rule.

func IsUnauthorized

func IsUnauthorized(err error) bool

IsUnauthorized reports whether err is the hub refusing the credential.

func NormalizeURL

func NormalizeURL(raw string) (string, error)

NormalizeURL renders a hub URL the one way the credential file keys it: scheme and host, lower-cased, a default port dropped, no path. A bare host is taken as https. Anything with a path, a query, a fragment or user information is refused — `cloop login https://hub/device` is a mistake worth naming rather than a URL to guess at.

func RenderValue

func RenderValue(v any) string

RenderValue renders a decoded JSON value as one line of text: a string as itself, a list joined with commas, an object as key=value pairs in key order. Never Go's map[…] syntax and never raw JSON, which is what a person reading an error in a terminal should not be handed.

func UserAgent

func UserAgent() string

UserAgent is what this build calls itself to a hub, and what the approval page shows as the requesting client.

Types

type APIError

type APIError struct {
	// Status is the HTTP status.
	Status int
	// Code is the machine-readable code when the hub sent one: an OAuth
	// error ("authorization_pending"), an apierror code ("FORBIDDEN"), or
	// the dashboard dialect's own ("executor_audience_denied").
	Code string
	// Message is the hub's prose.
	Message string
	// Remediation is the hub's "what to do about it", when it sent one apart
	// from the message.
	Remediation string
	// Details is the rest of what the hub said about the refusal: apierror's
	// "details" object — required_permission, role and scope for a missing
	// permission — or the dashboard dialect's other fields (executor_id,
	// alternatives, …). Values are as JSON decodes them.
	Details map[string]any
	// IntervalSeconds is the polling interval a device-flow error carried.
	IntervalSeconds int
}

APIError is a hub's refusal, whichever of its error shapes it came in.

func AsAPIError

func AsAPIError(err error) (*APIError, bool)

AsAPIError returns err's *APIError, if it is one.

func (*APIError) Detail

func (e *APIError) Detail(key string) string

Detail returns one of the refusal's details rendered as text, "" when the hub sent none by that name.

func (*APIError) Error

func (e *APIError) Error() string

type ApprovalItem added in v0.0.6

type ApprovalItem struct {
	// Kind is grant_request or api_token.
	Kind string `json:"kind"`
	ID   string `json:"id"`
	// Deadline is when the item stops being decidable, RFC 3339; empty for a
	// token that never expires.
	Deadline          string        `json:"deadline,omitempty"`
	DeadlineInSeconds int64         `json:"deadline_in_seconds,omitempty"`
	Request           *GrantRequest `json:"request,omitempty"`
	Token             *WaitingToken `json:"token,omitempty"`
}

ApprovalItem is one thing waiting on the caller.

type Approvals added in v0.0.6

type Approvals struct {
	Items         []ApprovalItem `json:"items"`
	Count         int            `json:"count"`
	GrantRequests int            `json:"grant_requests"`
	APITokens     int            `json:"api_tokens"`
	NextDeadline  string         `json:"next_deadline,omitempty"`
	// Mine is the caller's own requests: pending, and those settled in the
	// last week.
	Mine  []GrantRequest `json:"mine"`
	Actor string         `json:"actor,omitempty"`
}

Approvals is GET /api/approvals.

type AssignRepositoriesRequest

type AssignRepositoriesRequest struct {
	Secret     string   `json:"secret"`
	Repos      []string `json:"repos"`
	Access     string   `json:"access,omitempty"`
	TTLMinutes int      `json:"ttl_minutes,omitempty"`
	Branches   []string `json:"branches,omitempty"`
	// Replaces names a grant of this project's the new one supersedes.
	Replaces string `json:"replaces,omitempty"`
}

AssignRepositoriesRequest is the POST /api/projects/{idx}/repositories body.

type AvailableRepository

type AvailableRepository struct {
	ID       int64  `json:"id"`
	FullName string `json:"full_name"`
	Private  bool   `json:"private"`
}

AvailableRepository is one repository a GitHub App installation reaches.

type CatalogEntry

type CatalogEntry struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Kind string `json:"kind"`
}

CatalogEntry is a secret the caller may ask for: its id, name and kind, and nothing else.

type Client

type Client struct {
	// Base is the hub's base URL, as NormalizeURL renders it.
	Base string
	// Credential is presented as `Authorization: Bearer …`; empty for the
	// two device endpoints, which are reached before there is one.
	Credential string
	// contains filtered or unexported fields
}

Client talks to one hub.

func New

func New(base, caFile, credential string) (*Client, error)

New builds a client for a hub. caFile, when set, names a PEM bundle trusted in addition to the system's roots: a hub with an internal CA.

func (*Client) AddTask

func (c *Client) AddTask(ctx context.Context, idx int, title, description string) (Task, error)

AddTask adds a task to a project and returns it.

func (*Client) AdoptToken

func (c *Client) AdoptToken(ctx context.Context, id string, t MintedToken) (RenewedShare, error)

AdoptToken asks the hub to store a token the share's owner minted.

func (*Client) Approvals added in v0.0.6

func (c *Client) Approvals(ctx context.Context) (*Approvals, error)

Approvals lists what waits on the caller.

func (*Client) ApproveGrantRequest added in v0.0.6

func (c *Client) ApproveGrantRequest(ctx context.Context, id, note string, ttlMinutes int) (DecidedRequest, error)

ApproveGrantRequest mints the grant a request asked for. ttlMinutes, when positive, shortens it — it can only narrow.

func (*Client) ApproveToken added in v0.0.6

func (c *Client) ApproveToken(ctx context.Context, id string) error

ApproveToken approves an API token minted with no role policy in force.

func (*Client) AssignRepositories

func (c *Client) AssignRepositories(ctx context.Context, idx int, req AssignRepositoriesRequest) (RepoAssignment, error)

AssignRepositories grants repositories to a project through a stored GitHub credential.

func (*Client) AuthorizeSecret

func (c *Client) AuthorizeSecret(ctx context.Context, id, action string) error

AuthorizeSecret asks the hub whether the caller may perform action — "delete" or "renew" — on secret id, without performing it. nil means they may; a refusal is the hub's *APIError.

func (*Client) AvailableRepositories

func (c *Client) AvailableRepositories(ctx context.Context, idx int, secret string) ([]AvailableRepository, error)

AvailableRepositories asks the hub which repositories the GitHub App stored as secret reaches — GitHub's own inventory of the installation.

func (*Client) BindExecutor

func (c *Client) BindExecutor(ctx context.Context, idx int, executorID string) error

BindExecutor pins a project to an executor; an empty id unpins it.

func (*Client) CreateProject

func (c *Client) CreateProject(ctx context.Context, req NewProjectRequest) (NewProject, error)

CreateProject creates and initialises a project on the hub.

func (*Client) CreateSecret

func (c *Client) CreateSecret(ctx context.Context, req CreateSecretRequest) (Secret, error)

CreateSecret stores a secret.

func (*Client) DashboardURL

func (c *Client) DashboardURL(projectPath string) string

DashboardURL is the link that opens a project in the hub's dashboard: the dashboard reads ?project=<path> once its project list has loaded. A query, not a fragment, because the hub's sign-in round trip keeps the path and query it was sent from and nothing else.

func (*Client) DeleteSecret

func (c *Client) DeleteSecret(ctx context.Context, id string) error

DeleteSecret deletes a secret, revoking every grant of it.

func (*Client) DenyGrantRequest added in v0.0.6

func (c *Client) DenyGrantRequest(ctx context.Context, id, note string) (DecidedRequest, error)

DenyGrantRequest refuses a request; note, the reason, is required.

func (*Client) Do

func (c *Client) Do(ctx context.Context, method, path string, query url.Values, body, out any) error

Do performs one JSON call. body, when non-nil, is sent as JSON; out, when non-nil, receives the decoded answer. A status of 400 or above is returned as an *APIError.

func (*Client) Executors

func (c *Client) Executors(ctx context.Context) (*ExecutorList, error)

Executors lists the fleet.

func (*Client) Features

func (c *Client) Features(ctx context.Context, idx int) (*FeatureList, error)

Features lists a project's features.

func (*Client) FollowLogs

func (c *Client) FollowLogs(ctx context.Context, idx int, w io.Writer, ev LogEvents) error

FollowLogs streams a project's run output to w over the dashboard's own WebSocket, from the backlog the hub replays on connect onwards, until ctx ends or the hub closes the stream. A stream the hub closes because the session ended returns an error wrapping ErrStreamEnded.

func (*Client) GrantHarnessCredential

func (c *Client) GrantHarnessCredential(ctx context.Context, idx int, secret string) (*HarnessCredential, error)

GrantHarnessCredential grants an env secret the caller holds as the project's Claude credential — the card's dialog, with a secret chosen.

func (*Client) GrantRequests

func (c *Client) GrantRequests(ctx context.Context, state string, mine bool) (*GrantRequestList, error)

GrantRequests lists requests: the caller's own, or — for a caller who may decide them — everyone's unless mine is set. state narrows to one state.

func (*Client) HTTPClient

func (c *Client) HTTPClient() *http.Client

HTTPClient returns the client's underlying HTTP client, for a WebSocket dial that must trust the same CA.

func (*Client) HarnessCredential

func (c *Client) HarnessCredential(ctx context.Context, idx int) (*HarnessCredential, error)

HarnessCredential reads the project's Claude credential card.

func (*Client) LiveLog

func (c *Client) LiveLog(ctx context.Context, idx int) (lines []string, running bool, err error)

LiveLog returns what the hub holds of a project's current run output, and whether a run is going.

func (*Client) Logout

func (c *Client) Logout(ctx context.Context) (ended bool, err error)

Logout ends this credential's session at the hub. ended is false when there was no live session left to end — revoked, expired, or signed out already — which is not an error: the credential is dead either way.

func (*Client) Me

func (c *Client) Me(ctx context.Context) (map[string]any, error)

Me returns the hub's /api/me for this credential: who the session is, its role and permissions, and its clocks.

func (*Client) OpenFeaturePR

func (c *Client) OpenFeaturePR(ctx context.Context, idx int, slug string, req FeaturePRRequest) (*FeaturePRResult, error)

OpenFeaturePR pushes a feature's branch and opens its pull request into the feature's base, or returns the one already open.

func (*Client) PollDeviceToken

func (c *Client) PollDeviceToken(ctx context.Context, code *DeviceCode, waiting func(time.Duration)) (*DeviceToken, error)

PollDeviceToken polls until the code is approved, denied or expired, honouring the interval and slow_down (RFC 8628 §3.5). waiting, if set, is called before each sleep with the interval it is about to wait.

func (*Client) ProjectRepositories

func (c *Client) ProjectRepositories(ctx context.Context, idx int) (*ProjectRepositories, error)

ProjectRepositories lists what a project can reach and what the caller may grant it from.

func (*Client) Projects

func (c *Client) Projects(ctx context.Context) ([]Project, error)

Projects lists the projects this session can see, in index order.

func (*Client) RenewSecret

func (c *Client) RenewSecret(ctx context.Context, id string) (RenewedShare, error)

RenewSecret asks the hub to renew a Kubernetes share itself, now.

func (*Client) RequestDeviceCode

func (c *Client) RequestDeviceCode(ctx context.Context, credentialHash, hostname string) (*DeviceCode, error)

RequestDeviceCode asks the hub for a code, committing to the session credential whose digest is credentialHash.

func (*Client) RequestGrant

func (c *Client) RequestGrant(ctx context.Context, req FileGrantRequest) (GrantRequest, error)

RequestGrant files a request.

func (*Client) ResolveProject

func (c *Client) ResolveProject(ctx context.Context, ref string) (Project, error)

ResolveProject finds the project ref names: its index, its name (exact, then case-insensitive) or its path. An ambiguous name is an error naming the candidates, never a guess.

func (*Client) RevokeRepositories

func (c *Client) RevokeRepositories(ctx context.Context, idx int, grantID string) (RevokedRepositories, error)

RevokeRepositories withdraws one of a project's repository grants.

func (*Client) Run

func (c *Client) Run(ctx context.Context, idx int) (string, error)

Run starts a project's run — the dashboard's Start button — and returns the hub's message.

func (*Client) SecretCatalog

func (c *Client) SecretCatalog(ctx context.Context) ([]CatalogEntry, error)

SecretCatalog lists what the caller may file a request for.

func (*Client) Secrets

func (c *Client) Secrets(ctx context.Context) (*SecretsList, error)

Secrets lists the secrets this session may see.

func (*Client) StartFeature

func (c *Client) StartFeature(ctx context.Context, idx int, req StartFeatureRequest) (StartedFeature, error)

StartFeature creates a feature of a project: a branch, its worktree, and a task list of its own.

func (*Client) Stop

func (c *Client) Stop(ctx context.Context, idx int) (bool, string, error)

Stop stops a project's run — the dashboard's Stop button — and reports whether there was one to stop.

func (*Client) Tasks

func (c *Client) Tasks(ctx context.Context, idx int) ([]Task, error)

Tasks lists a project's tasks.

func (*Client) WithdrawGrantRequest

func (c *Client) WithdrawGrantRequest(ctx context.Context, id string) (GrantRequest, error)

WithdrawGrantRequest takes back one of the caller's pending requests.

type CreateSecretRequest

type CreateSecretRequest struct {
	Name     string            `json:"name"`
	Kind     string            `json:"kind"`
	Payload  string            `json:"payload"`
	Metadata map[string]string `json:"metadata,omitempty"`
	// Personal, when set, asks for a personal or a shared secret; unset lets
	// the hub decide, which is personal whenever the session has an identity.
	Personal *bool `json:"personal,omitempty"`
}

CreateSecretRequest is the POST /api/secrets body.

type DecidedRequest added in v0.0.6

type DecidedRequest struct {
	Request GrantRequest `json:"request"`
	// GrantID is the grant an approval minted, and Note what the hub says
	// about when it takes effect.
	GrantID string `json:"grant_id,omitempty"`
	Note    string `json:"note,omitempty"`
}

DecidedRequest is the answer to an approval or a denial.

type DeviceCode

type DeviceCode struct {
	DeviceCode              string `json:"device_code"`
	UserCode                string `json:"user_code"`
	VerificationURI         string `json:"verification_uri"`
	VerificationURIComplete string `json:"verification_uri_complete"`
	ExpiresIn               int    `json:"expires_in"`
	Interval                int    `json:"interval"`
}

DeviceCode is the hub's answer to a request for a code (RFC 8628 §3.2).

type DeviceToken

type DeviceToken struct {
	TokenType   string `json:"token_type"`
	SessionID   string `json:"session_id"`
	ExpiresIn   int    `json:"expires_in"`
	IdleTimeout int    `json:"idle_timeout"`
	Revalidated bool   `json:"revalidated"`
	Identity    struct {
		Sub   string `json:"sub"`
		Email string `json:"email"`
		Name  string `json:"name"`
	} `json:"identity"`
}

DeviceToken is the hub's answer to the poll that found the code approved.

type Executor

type Executor struct {
	ID            string            `json:"id"`
	Name          string            `json:"name"`
	Kind          string            `json:"kind"`
	Status        string            `json:"status"`
	Registered    bool              `json:"registered"`
	Default       bool              `json:"default"`
	Isolation     string            `json:"isolation"`
	Health        string            `json:"health,omitempty"`
	Blocked       bool              `json:"blocked"`
	BlockedReason string            `json:"blocked_reason,omitempty"`
	SchedState    string            `json:"sched_state"`
	SchedReason   string            `json:"sched_reason,omitempty"`
	Schedulable   bool              `json:"schedulable"`
	Restricted    bool              `json:"restricted,omitempty"`
	Admitted      bool              `json:"admitted"`
	Labels        map[string]string `json:"labels,omitempty"`
	Virtual       *struct {
		Parent string `json:"parent"`
	} `json:"virtual,omitempty"`
}

Executor is one executor as GET /api/executors describes it to the caller.

func (Executor) Usable

func (e Executor) Usable() (bool, string)

Usable reports whether a project bound to e could run there now, and if not, why not.

type ExecutorList

type ExecutorList struct {
	Executors []Executor `json:"executors"`
	DefaultID string     `json:"default_id"`
}

ExecutorList is GET /api/executors, as the caller sees it.

type Feature

type Feature struct {
	// Idx is the feature's own index in the caller's project list: what its
	// run, tasks and options are addressed by (`cloop remote run <idx>`).
	Idx         int    `json:"project_idx"`
	Path        string `json:"path"`
	Name        string `json:"name"`
	Status      string `json:"status"`
	Running     bool   `json:"running"`
	TotalTasks  int    `json:"total_tasks"`
	DoneTasks   int    `json:"done_tasks"`
	FailedTasks int    `json:"failed_tasks"`
	Feature     *struct {
		Slug       string     `json:"slug"`
		Title      string     `json:"title"`
		Branch     string     `json:"branch"`
		Base       string     `json:"base"`
		CreatedAt  time.Time  `json:"created_at"`
		CreatedBy  string     `json:"created_by,omitempty"`
		AutoPR     bool       `json:"auto_pr,omitempty"`
		AutoEvolve bool       `json:"auto_evolve,omitempty"`
		Innovate   bool       `json:"innovate,omitempty"`
		PR         *FeaturePR `json:"pr,omitempty"`
	} `json:"feature,omitempty"`
}

Feature is one feature of a project.

type FeatureList

type FeatureList struct {
	Project  string    `json:"project"`
	Path     string    `json:"path"`
	Features []Feature `json:"features"`
	Max      int       `json:"max"`
}

FeatureList is GET /api/projects/{idx}/features.

type FeaturePR

type FeaturePR struct {
	Number    int       `json:"number"`
	URL       string    `json:"url"`
	State     string    `json:"state"`
	Repo      string    `json:"repo,omitempty"`
	Head      string    `json:"head,omitempty"`
	Base      string    `json:"base,omitempty"`
	Draft     bool      `json:"draft,omitempty"`
	CreatedAt time.Time `json:"created_at"`
}

FeaturePR is a feature's pull request as the hub last saw it.

type FeaturePRRequest

type FeaturePRRequest struct {
	Title string `json:"title,omitempty"`
	Body  string `json:"body,omitempty"`
	Draft bool   `json:"draft,omitempty"`
}

FeaturePRRequest is the POST /api/projects/{idx}/features/{slug}/pr body.

type FeaturePRResult

type FeaturePRResult struct {
	PR          *FeaturePR `json:"pr"`
	Existing    bool       `json:"existing"`
	Pushed      bool       `json:"pushed"`
	Ahead       int        `json:"ahead"`
	Dirty       []string   `json:"dirty,omitempty"`
	TokenSource string     `json:"token_source"`
}

FeaturePRResult is what opening a feature's pull request did.

type File

type File struct {
	// Default is the URL of the hub commands talk to when none is named.
	Default string `json:"default,omitempty"`
	// Hubs is keyed by NormalizeURL of each hub's URL.
	Hubs map[string]*Hub `json:"hubs"`
}

File is the credential file's contents.

func Load

func Load(path string) (*File, error)

Load reads the credential file. A missing file is an empty one. A file other users can read, or one that is not a plain file, is refused with an error wrapping ErrInsecurePermissions.

func LoadForRevocation

func LoadForRevocation(path string) (*File, error)

LoadForRevocation reads the credential file even when other users can read it — for `cloop logout`, whose job with such a file is exactly to revoke the sessions in it at their hubs, since somebody may have copied them, and then to delete it. Anything that would present a credential uses Load.

func (*File) Lookup

func (f *File) Lookup(rawURL string) (*Hub, bool)

Lookup returns the entry for a hub URL, in any spelling NormalizeURL accepts.

func (*File) Put

func (f *File) Put(h *Hub)

Put records a hub's entry under its normalised URL, making it the default when there is none yet.

func (*File) Remove

func (f *File) Remove(url string)

Remove drops a hub's entry, and the default if it named that hub — the next remaining hub, if exactly one remains, becomes the default.

func (*File) Save

func (f *File) Save(path string) error

Save writes the file at path, mode 0600 in a 0700 directory, by writing a temporary file beside it and renaming it into place — so a crash leaves the old file or the new one, never half of either, and the credential is never on disk under a looser mode for even a moment.

func (*File) URLs

func (f *File) URLs() []string

URLs lists the signed-in hubs, sorted.

type FileGrantRequest

type FileGrantRequest struct {
	SecretRef     string   `json:"secret_ref"`
	Subject       string   `json:"subject"`
	Scope         string   `json:"scope,omitempty"`
	TTLMinutes    int      `json:"ttl_minutes"`
	WaitMinutes   int      `json:"wait_minutes,omitempty"`
	Justification string   `json:"justification"`
	Repos         []string `json:"repos,omitempty"`
	Permissions   []string `json:"permissions,omitempty"`
	Branches      []string `json:"branches,omitempty"`
	Namespaces    []string `json:"namespaces,omitempty"`
	Contexts      []string `json:"contexts,omitempty"`
	Hosts         []string `json:"hosts,omitempty"`
	Registries    []string `json:"registries,omitempty"`
	EnvKeys       []string `json:"env_keys,omitempty"`
	Writable      bool     `json:"writable,omitempty"`
}

FileGrantRequest is the POST /api/grant-requests body.

type GitProxyStatus

type GitProxyStatus struct {
	Running  bool     `json:"running"`
	Required bool     `json:"required"`
	PushRefs []string `json:"push_refs,omitempty"`
}

GitProxyStatus is whether the hub's git proxy guards GitHub credentials.

type GrantRequest

type GrantRequest struct {
	ID             string    `json:"id"`
	RequestedBy    string    `json:"requested_by"`
	SecretID       string    `json:"secret_id"`
	SecretName     string    `json:"secret_name,omitempty"`
	Kind           string    `json:"kind,omitempty"`
	Subject        string    `json:"subject"`
	Constraints    string    `json:"constraints,omitempty"`
	Scope          string    `json:"scope,omitempty"`
	TTLMinutes     int       `json:"ttl_minutes"`
	Justification  string    `json:"justification"`
	State          string    `json:"state"`
	CreatedAt      time.Time `json:"created_at"`
	ExpiresAt      time.Time `json:"expires_at,omitempty"`
	DecidedBy      string    `json:"decided_by,omitempty"`
	DecidedAt      time.Time `json:"decided_at,omitempty"`
	DecisionNote   string    `json:"decision_note,omitempty"`
	GrantID        string    `json:"grant_id,omitempty"`
	GrantExpiresAt time.Time `json:"grant_expires_at,omitempty"`
	Mine           bool      `json:"mine"`
	Decidable      bool      `json:"decidable"`
	// Approvers says who may approve a pending request: the accounts and
	// groups the hub's policy gives secret.grant, other than the requester.
	// A hub older than Task 20417 does not say.
	Approvers []string `json:"approvers,omitempty"`
}

GrantRequest is one request as the hub shows it to the caller.

type GrantRequestList

type GrantRequestList struct {
	Requests     []GrantRequest `json:"requests"`
	PendingCount int            `json:"pending_count"`
	CanDecide    bool           `json:"can_decide"`
	Actor        string         `json:"actor"`
}

GrantRequestList is GET /api/grant-requests.

type HarnessCandidate

type HarnessCandidate struct {
	ID       string   `json:"id"`
	Name     string   `json:"name"`
	Owner    string   `json:"owner,omitempty"`
	Personal bool     `json:"personal"`
	Mine     bool     `json:"mine"`
	Keys     []string `json:"keys"`
	Complete bool     `json:"complete"`
	Granted  bool     `json:"granted"`
}

HarnessCandidate is a secret the caller could grant to fill the gap.

type HarnessCredential

type HarnessCredential struct {
	Applies     bool               `json:"applies"`
	State       string             `json:"state"`
	Provider    string             `json:"provider"`
	Needed      [][]string         `json:"needed"`
	SatisfiedBy *HarnessGrant      `json:"satisfied_by"`
	ExpiresAt   *time.Time         `json:"expires_at"`
	Reason      string             `json:"reason,omitempty"`
	Remedy      string             `json:"remedy,omitempty"`
	Candidates  []HarnessCandidate `json:"candidates"`
	CanPaste    bool               `json:"can_paste"`
	// BrokerUnavailable is why nothing can be granted on this hub at all.
	BrokerUnavailable string `json:"broker_unavailable,omitempty"`
	Granted           *struct {
		GrantID    string    `json:"grant_id"`
		SecretName string    `json:"secret_name"`
		EnvKeys    []string  `json:"env_keys"`
		ExpiresAt  time.Time `json:"expires_at"`
	} `json:"granted,omitempty"`
}

HarnessCredential is GET /api/projects/{idx}/harness-credential: what the project's sandbox would sign in to Claude with.

type HarnessGrant

type HarnessGrant struct {
	GrantID    string     `json:"grant_id"`
	SecretID   string     `json:"secret_id"`
	SecretName string     `json:"secret_name"`
	Owner      string     `json:"owner,omitempty"`
	ExpiresAt  *time.Time `json:"expires_at"`
	Keys       []string   `json:"keys"`
}

HarnessGrant is a grant the project's sandbox would authenticate with.

type Hub

type Hub struct {
	// URL is the hub's base URL as NormalizeURL renders it.
	URL string `json:"url"`
	// Credential is the terminal's session credential: the bearer the hub
	// authenticates this terminal by. Never printed.
	Credential string `json:"credential"`
	// SessionID is the session's public id — the credential's digest — as
	// the hub's Active Sessions table and audit trail name it.
	SessionID string `json:"session_id"`
	// CAFile is a PEM bundle to trust for this hub, for a hub whose
	// certificate is not signed by a public CA.
	CAFile string `json:"ca_file,omitempty"`

	Subject string `json:"subject,omitempty"`
	Email   string `json:"email,omitempty"`
	Name    string `json:"name,omitempty"`

	// SignedInAt is when the hub approved the session and ExpiresAt when it
	// ends at the latest; it may end sooner, idle or revoked.
	SignedInAt time.Time `json:"signed_in_at"`
	ExpiresAt  time.Time `json:"expires_at,omitempty"`
	// Client is the host name the terminal reported when it signed in.
	Client string `json:"client,omitempty"`
	// contains filtered or unexported fields
}

Hub is one signed-in hub.

func Login

func Login(ctx context.Context, opts LoginOptions) (*Hub, error)

Login runs the device flow end to end and returns the hub's new entry. It does not save it; the caller does, once it has decided where.

func (*Hub) Label

func (h *Hub) Label() string

Label is how a hub's identity is shown: the email, else the name, else the subject.

func (*Hub) LoginNotes

func (h *Hub) LoginNotes() (revalidated bool, idle time.Duration)

LoginNotes are what a fresh session's hub said about it, for `cloop login` to tell the person: whether the hub re-checks the session with the identity provider, and how long it may go unused.

type LogEvents

type LogEvents struct {
	// RunState is told each time the hub reports the run started or stopped.
	RunState func(running bool)
}

LogEvents receives what FollowLogs reads besides output.

type LoginOptions

type LoginOptions struct {
	// HubURL is the hub to sign in to.
	HubURL string
	// CAFile is a PEM bundle to trust for the hub, kept with the credential.
	CAFile string
	// Hostname is what the terminal calls its machine on the approval page;
	// os.Hostname when empty.
	Hostname string
	// Out receives the instructions: the code and where to enter it.
	Out io.Writer
	// OpenBrowser, when set, is asked to open the approval page.
	OpenBrowser func(url string) error
}

LoginOptions configure one `cloop login`.

type MintedToken

type MintedToken struct {
	Token    string
	Server   string
	CASHA256 string
}

MintedToken is a token a share's owner minted from their terminal, and where: the API server their context reached and the SHA-256 of the CA it trusted there (kubeshare.CAFingerprint). The hub sends the token to the cluster to confirm it only when that is the share's own server (Task 20420).

type NewProject

type NewProject struct {
	Dir string `json:"dir"`
	// Idx is the project's index in the caller's project list, -1 when the
	// hub could not find it there.
	Idx int `json:"project_idx"`
}

NewProject is the hub's answer to a creation.

type NewProjectRequest

type NewProjectRequest struct {
	// Dir is the project's directory on the hub. Empty with Name set asks
	// the hub to place it under its projects root (ui.projects_root).
	Dir  string `json:"dir,omitempty"`
	Name string `json:"name,omitempty"`
	Goal string `json:"goal"`

	Provider string `json:"provider,omitempty"`
	Model    string `json:"model,omitempty"`
	Effort   string `json:"effort,omitempty"`

	// ExecutorID pins the project; Grants are opened with it. Both are
	// applied with the creation or not at all.
	ExecutorID string         `json:"executor_id,omitempty"`
	Grants     []ProjectGrant `json:"grants,omitempty"`
}

NewProjectRequest is the POST /api/projects/new body.

type Project

type Project struct {
	Idx         int    `json:"idx"`
	Name        string `json:"name"`
	Path        string `json:"path"`
	Status      string `json:"status"`
	Goal        string `json:"goal"`
	Running     bool   `json:"running"`
	TotalTasks  int    `json:"total_tasks"`
	DoneTasks   int    `json:"done_tasks"`
	FailedTasks int    `json:"failed_tasks"`
	ActiveTasks int    `json:"active_tasks"`
	Hidden      bool   `json:"hidden"`
}

Project is one project the session can see, with the index the hub addresses it by.

type ProjectGrant

type ProjectGrant struct {
	SecretRef   string   `json:"secret_ref"`
	Scope       string   `json:"scope,omitempty"`
	TTLMinutes  int      `json:"ttl_minutes,omitempty"`
	Repos       []string `json:"repos,omitempty"`
	Permissions []string `json:"permissions,omitempty"`
	Branches    []string `json:"branches,omitempty"`
	Namespaces  []string `json:"namespaces,omitempty"`
	Contexts    []string `json:"contexts,omitempty"`
	Hosts       []string `json:"hosts,omitempty"`
	Registries  []string `json:"registries,omitempty"`
	EnvKeys     []string `json:"env_keys,omitempty"`
	Writable    bool     `json:"writable,omitempty"`
}

ProjectGrant is one credential a new project should hold from the moment it exists: a stored secret by name, narrowed as its kind requires.

type ProjectRepositories

type ProjectRepositories struct {
	Project     string           `json:"project"`
	Apps        []RepoApp        `json:"apps"`
	Assignments []RepoAssignment `json:"assignments"`
	GitProxy    GitProxyStatus   `json:"git_proxy"`
}

ProjectRepositories is GET /api/projects/{idx}/repositories.

type RenewedShare

type RenewedShare struct {
	ID             string    `json:"id"`
	Name           string    `json:"name"`
	Renewal        string    `json:"renewal"`
	ServiceAccount string    `json:"service_account"`
	IssuedAt       time.Time `json:"issued_at"`
	ExpiresAt      time.Time `json:"expires_at"`
	Renewals       int       `json:"renewals"`
	Verified       bool      `json:"verified"`
	Sessions       int       `json:"sessions"`
}

RenewedShare is POST /api/secrets/{id}/renew's answer.

type RepoApp

type RepoApp struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

RepoApp is a stored GitHub App the caller may assign repositories from.

type RepoAssignment

type RepoAssignment struct {
	GrantID           string    `json:"grant_id"`
	SecretID          string    `json:"secret_id"`
	SecretName        string    `json:"secret_name"`
	Kind              string    `json:"kind"`
	Repos             []string  `json:"repos"`
	Access            string    `json:"access"`
	Branches          []string  `json:"branches,omitempty"`
	BranchEnforcement string    `json:"branch_enforcement,omitempty"`
	Replaceable       bool      `json:"replaceable,omitempty"`
	ExpiresAt         time.Time `json:"expires_at"`
	CreatedAt         time.Time `json:"created_at"`
	CreatedBy         string    `json:"created_by,omitempty"`
}

RepoAssignment is one live grant of repositories to a project.

type RevokedRepositories

type RevokedRepositories struct {
	GrantID string `json:"grant_id"`
	State   string `json:"state,omitempty"`
	Leases  int    `json:"leases,omitempty"`
	Note    string `json:"note,omitempty"`
}

RevokedRepositories is the hub's answer to a withdrawal.

type Secret

type Secret struct {
	ID           string            `json:"id"`
	Name         string            `json:"name"`
	Kind         string            `json:"kind"`
	Metadata     map[string]string `json:"metadata"`
	CreatedAt    time.Time         `json:"created_at"`
	CreatedBy    string            `json:"created_by"`
	Owner        string            `json:"owner"`
	Personal     bool              `json:"personal"`
	Mine         bool              `json:"mine"`
	Grants       int               `json:"grants"`
	ActiveGrants int               `json:"active_grants"`
}

Secret is one secret as GET /api/secrets lists it. It carries no credential: the hub never returns one.

type SecretsList

type SecretsList struct {
	Secrets []Secret `json:"secrets"`
	Broker  struct {
		SecretsAvailable bool   `json:"secrets_available"`
		Reason           string `json:"reason"`
		Remediation      string `json:"remediation"`
	} `json:"broker"`
	// KubeGuard is whether the hub delivers kubeconfigs through its
	// Kubernetes access monitor. A hub older than Task 20416 does not say,
	// which reads as no monitor.
	KubeGuard struct {
		Enabled bool `json:"enabled"`
		Running bool `json:"running"`
	} `json:"kube_guard"`
}

SecretsList is GET /api/secrets.

func (*SecretsList) FindSecret

func (l *SecretsList) FindSecret(name string) (Secret, bool)

FindSecret returns the secret named name among those this session may see.

type StartFeatureRequest

type StartFeatureRequest struct {
	Name        string   `json:"name"`
	Description string   `json:"description,omitempty"`
	Base        string   `json:"base,omitempty"`
	Tasks       []string `json:"tasks,omitempty"`
	AutoEvolve  bool     `json:"auto_evolve,omitempty"`
	Innovate    bool     `json:"innovate,omitempty"`
	Parallel    bool     `json:"parallel,omitempty"`
	MaxParallel int      `json:"max_parallel,omitempty"`
	AutoPR      bool     `json:"auto_pr,omitempty"`
}

StartFeatureRequest is the POST /api/projects/{idx}/features body.

type StartedFeature

type StartedFeature struct {
	Slug   string `json:"slug"`
	Branch string `json:"branch"`
	Base   string `json:"base"`
	Path   string `json:"path"`
	Idx    int    `json:"project_idx"`
}

StartedFeature is the hub's answer to a new feature.

type Task

type Task struct {
	ID          int    `json:"id"`
	Title       string `json:"title"`
	Description string `json:"description,omitempty"`
	Status      string `json:"status"`
	Priority    int    `json:"priority"`
}

Task is one task as the hub lists it.

type WaitingToken added in v0.0.6

type WaitingToken struct {
	ID        string   `json:"id"`
	Name      string   `json:"name"`
	Prefix    string   `json:"prefix"`
	Roles     []string `json:"roles"`
	CreatedBy string   `json:"created_by,omitempty"`
	CreatedAt string   `json:"created_at,omitempty"`
}

WaitingToken is an API token waiting for an administrator's approval: never its hash, never its secret.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL