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
- Variables
- func DefaultPath() (string, error)
- func IsUnauthorized(err error) bool
- func NormalizeURL(raw string) (string, error)
- func RenderValue(v any) string
- func UserAgent() string
- type APIError
- type AssignRepositoriesRequest
- type AvailableRepository
- type CatalogEntry
- type Client
- func (c *Client) AddTask(ctx context.Context, idx int, title, description string) (Task, error)
- func (c *Client) AdoptToken(ctx context.Context, id string, t MintedToken) (RenewedShare, error)
- func (c *Client) AssignRepositories(ctx context.Context, idx int, req AssignRepositoriesRequest) (RepoAssignment, error)
- func (c *Client) AuthorizeSecret(ctx context.Context, id, action string) error
- func (c *Client) AvailableRepositories(ctx context.Context, idx int, secret string) ([]AvailableRepository, error)
- func (c *Client) BindExecutor(ctx context.Context, idx int, executorID string) error
- func (c *Client) CreateProject(ctx context.Context, req NewProjectRequest) (NewProject, error)
- func (c *Client) CreateSecret(ctx context.Context, req CreateSecretRequest) (Secret, error)
- func (c *Client) DashboardURL(projectPath string) string
- func (c *Client) DeleteSecret(ctx context.Context, id string) error
- func (c *Client) Do(ctx context.Context, method, path string, query url.Values, body, out any) error
- func (c *Client) Executors(ctx context.Context) (*ExecutorList, error)
- func (c *Client) Features(ctx context.Context, idx int) (*FeatureList, error)
- func (c *Client) FollowLogs(ctx context.Context, idx int, w io.Writer, ev LogEvents) error
- func (c *Client) GrantHarnessCredential(ctx context.Context, idx int, secret string) (*HarnessCredential, error)
- func (c *Client) GrantRequests(ctx context.Context, state string, mine bool) (*GrantRequestList, error)
- func (c *Client) HTTPClient() *http.Client
- func (c *Client) HarnessCredential(ctx context.Context, idx int) (*HarnessCredential, error)
- func (c *Client) LiveLog(ctx context.Context, idx int) (lines []string, running bool, err error)
- func (c *Client) Logout(ctx context.Context) (ended bool, err error)
- func (c *Client) Me(ctx context.Context) (map[string]any, error)
- func (c *Client) OpenFeaturePR(ctx context.Context, idx int, slug string, req FeaturePRRequest) (*FeaturePRResult, error)
- func (c *Client) PollDeviceToken(ctx context.Context, code *DeviceCode, waiting func(time.Duration)) (*DeviceToken, error)
- func (c *Client) ProjectRepositories(ctx context.Context, idx int) (*ProjectRepositories, error)
- func (c *Client) Projects(ctx context.Context) ([]Project, error)
- func (c *Client) RenewSecret(ctx context.Context, id string) (RenewedShare, error)
- func (c *Client) RequestDeviceCode(ctx context.Context, credentialHash, hostname string) (*DeviceCode, error)
- func (c *Client) RequestGrant(ctx context.Context, req FileGrantRequest) (GrantRequest, error)
- func (c *Client) ResolveProject(ctx context.Context, ref string) (Project, error)
- func (c *Client) RevokeRepositories(ctx context.Context, idx int, grantID string) (RevokedRepositories, error)
- func (c *Client) Run(ctx context.Context, idx int) (string, error)
- func (c *Client) SecretCatalog(ctx context.Context) ([]CatalogEntry, error)
- func (c *Client) Secrets(ctx context.Context) (*SecretsList, error)
- func (c *Client) StartFeature(ctx context.Context, idx int, req StartFeatureRequest) (StartedFeature, error)
- func (c *Client) Stop(ctx context.Context, idx int) (bool, string, error)
- func (c *Client) Tasks(ctx context.Context, idx int) ([]Task, error)
- func (c *Client) WithdrawGrantRequest(ctx context.Context, id string) (GrantRequest, error)
- type CreateSecretRequest
- type DeviceCode
- type DeviceToken
- type Executor
- type ExecutorList
- type Feature
- type FeatureList
- type FeaturePR
- type FeaturePRRequest
- type FeaturePRResult
- type File
- type FileGrantRequest
- type GitProxyStatus
- type GrantRequest
- type GrantRequestList
- type HarnessCandidate
- type HarnessCredential
- type HarnessGrant
- type Hub
- type LogEvents
- type LoginOptions
- type MintedToken
- type NewProject
- type NewProjectRequest
- type Project
- type ProjectGrant
- type ProjectRepositories
- type RenewedShare
- type RepoApp
- type RepoAssignment
- type RevokedRepositories
- type Secret
- type SecretsList
- type StartFeatureRequest
- type StartedFeature
- type Task
Constants ¶
const FileName = "hubs.json"
FileName is the credential file's name inside the cloop config directory.
Variables ¶
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.
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).
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.
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.
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 ¶
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 ¶
IsUnauthorized reports whether err is the hub refusing the credential.
func NormalizeURL ¶
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 ¶
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.
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 ¶
AsAPIError returns err's *APIError, if it is one.
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 ¶
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) 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) 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 ¶
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 ¶
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 ¶
CreateSecret stores a secret.
func (*Client) DashboardURL ¶
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 ¶
DeleteSecret deletes a secret, revoking every grant of it.
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) FollowLogs ¶
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 ¶
HTTPClient returns the client's underlying HTTP client, for a WebSocket dial that must trust the same CA.
func (*Client) HarnessCredential ¶
HarnessCredential reads the project's Claude credential card.
func (*Client) LiveLog ¶
LiveLog returns what the hub holds of a project's current run output, and whether a run is going.
func (*Client) Logout ¶
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 ¶
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 ¶
ProjectRepositories lists what a project can reach and what the caller may grant it from.
func (*Client) RenewSecret ¶
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 ¶
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 ¶
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 ¶
Stop stops a project's run — the dashboard's Stop button — and reports whether there was one to stop.
func (*Client) WithdrawGrantRequest ¶
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 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.
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 ¶
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 ¶
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) Put ¶
Put records a hub's entry under its normalised URL, making it the default when there is none yet.
func (*File) Remove ¶
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.
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.
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.
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 ¶
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 {
}
RenewedShare is POST /api/secrets/{id}/renew's answer.
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.