Documentation
¶
Index ¶
- Constants
- Variables
- func CheckVersionResponse(resp *http.Response) bool
- func HandleRedirect(projectRoot string, info *RedirectInfo) error
- func PrintDeprecationWarning(message string)
- func PrintUpgradeRequired(minVersion string)
- type DoctorIssue
- type DoctorResponse
- type LedgerStatusResponse
- type RedirectConfig
- type RedirectInfo
- type RedirectMapping
- type RepoClient
- func (c *RepoClient) Endpoint() string
- func (c *RepoClient) GetDoctorIssues(repoID string) (*DoctorResponse, error)
- func (c *RepoClient) GetLedgerStatus(repoID string) (*LedgerStatusResponse, error)
- func (c *RepoClient) GetRepoDetail(repoID string) (*RepoDetailResponse, error)
- func (c *RepoClient) GetRepos() (*ReposResponse, error)
- func (c *RepoClient) GetTeamInfo(teamID string) (*TeamInfoResponse, error)
- func (c *RepoClient) NotifyUninstall(repoID, repoSalt string) error
- func (c *RepoClient) RegisterRepo(req *RepoInitRequest) (*RepoInitResponse, error)
- func (c *RepoClient) WithAuthToken(token string) *RepoClient
- type RepoDetailLedger
- type RepoDetailResponse
- type RepoDetailTeamContext
- type RepoFingerprint
- type RepoInfo
- type RepoInitRequest
- type RepoInitResponse
- type RepoMarkerData
- type RepoUninstallRequest
- type ReposResponse
- type TeamInfoResponse
- type TeamMembership
Constants ¶
const ( // HeaderMinVersion is returned by the server to indicate the minimum CLI version required HeaderMinVersion = "X-SageOx-Min-Version" // HeaderDeprecated is returned by the server to warn of upcoming deprecation HeaderDeprecated = "X-SageOx-Deprecated" )
const RedirectHeader = "X-SageOx-Merge"
RedirectHeader is the HTTP header name for merge/redirect information
Variables ¶
var ErrForbidden = errors.New("access denied: you are not a member of this team — request an invite URL from a team admin")
ErrForbidden is returned when the API returns 403 Forbidden
var ErrLedgerNotFound = errors.New("ledger not found")
ErrLedgerNotFound is returned when the ledger doesn't exist for the given repo.
var ErrReadOnly = errors.New("read-only access: you are a viewer on this public repo")
ErrReadOnly is returned when the user has viewer (read-only) access to a public repo
ErrUnauthorized is returned when the API returns 401 Unauthorized
var ErrVersionUnsupported = errors.New("CLI version no longer supported by server")
ErrVersionUnsupported is returned when the server indicates the CLI version is no longer supported
Functions ¶
func CheckVersionResponse ¶
CheckVersionResponse inspects HTTP response for version deprecation signals. Returns true if the CLI should abort due to unsupported version (HTTP 426). For successful responses, checks for soft deprecation warning header.
func HandleRedirect ¶
func HandleRedirect(projectRoot string, info *RedirectInfo) error
HandleRedirect processes redirect info and updates local config/markers This is best-effort: returns nil on success or if nothing to do, and logs but does not fail on non-critical errors
func PrintDeprecationWarning ¶
func PrintDeprecationWarning(message string)
PrintDeprecationWarning displays a warning that the CLI version is deprecated Uses yellow color semantically indicating a non-blocking warning
func PrintUpgradeRequired ¶
func PrintUpgradeRequired(minVersion string)
PrintUpgradeRequired displays a message indicating the CLI version is no longer supported Uses red color semantically indicating a blocking error
Types ¶
type DoctorIssue ¶
type DoctorIssue struct {
Type string `json:"type"` // e.g., "merge_pending", "team_invite_pending"
Severity string `json:"severity"` // "error", "warning", "info"
Title string `json:"title"` // short display title
Description string `json:"description"` // detailed explanation, supports Markdown
ActionURL string `json:"action_url,omitempty"` // URL to resolve the issue
ActionLabel string `json:"action_label,omitempty"` // button text, e.g., "Resolve merge"
}
DoctorIssue represents a single diagnostic issue from the cloud Cloud doctor detects things the local CLI cannot: - Pending merge conflicts (same repo registered twice) - requires cross-repo knowledge - Team invites pending acceptance - lives in cloud DB - Guidance updates available - version comparison server-side - Billing/quota warnings - enterprise only - Team-wide health (X repos need updates) - aggregate view
type DoctorResponse ¶
type DoctorResponse struct {
Issues []DoctorIssue `json:"issues"`
CheckedAt string `json:"checked_at"` // RFC3339 timestamp
}
DoctorResponse represents the GET /api/v1/repo/{repo_id}/doctor response
type LedgerStatusResponse ¶
type LedgerStatusResponse struct {
Status string `json:"status"` // "ready", "pending", "error"
RepoURL string `json:"repo_url"` // git clone URL (empty if not ready)
RepoID int `json:"repo_id"` // GitLab project ID (internal use)
CreatedAt string `json:"created_at"` // RFC3339 timestamp
Message string `json:"message,omitempty"` // optional status message
Visibility string `json:"visibility,omitempty"` // "public" or "private" (new field, may be empty on older servers)
AccessLevel string `json:"access_level,omitempty"` // "member" or "viewer" (new field, may be empty on older servers)
}
LedgerStatusResponse represents the GET /api/v1/repos/{repo_id}/ledger-status response.
func (*LedgerStatusResponse) IsReadOnly ¶
func (r *LedgerStatusResponse) IsReadOnly() bool
IsReadOnly returns true if the user has viewer (read-only) access.
type RedirectConfig ¶
type RedirectConfig struct {
RepoID string `json:"repo_id,omitempty"`
TeamID string `json:"team_id,omitempty"`
}
RedirectConfig contains the new config values to apply
type RedirectInfo ¶
type RedirectInfo struct {
Repo *RedirectMapping `json:"repo,omitempty"`
Team *RedirectMapping `json:"team,omitempty"`
Config *RedirectConfig `json:"config,omitempty"`
}
RedirectInfo contains redirect information from the server when repos or teams have been merged
func ParseRedirectHeader ¶
func ParseRedirectHeader(header http.Header) *RedirectInfo
ParseRedirectHeader parses X-Sageox-Redirect header if present Returns nil if header is not present or cannot be parsed
type RedirectMapping ¶
RedirectMapping represents a from -> to ID mapping
type RepoClient ¶
type RepoClient struct {
// contains filtered or unexported fields
}
RepoClient handles API communication with the SageOx repo endpoints
func NewRepoClient ¶
func NewRepoClient() *RepoClient
NewRepoClient creates a new repo API client using the global default endpoint.
CAUTION: This should RARELY be used. It uses endpoint.Get() which ignores project config, so it will use the wrong endpoint for repos configured with non-default endpoints (e.g., enterprise or test environments).
Use NewRepoClientForProject(gitRoot) instead for operations within a repo context. Use NewRepoClientWithEndpoint(endpoint) when you have the endpoint explicitly.
func NewRepoClientForProject ¶
func NewRepoClientForProject(gitRoot string) *RepoClient
NewRepoClientForProject creates a new repo API client using the endpoint from project config. This is the recommended way to create a client for repo-bound operations. It checks: SAGEOX_ENDPOINT env var > project config > default endpoint.
func NewRepoClientWithEndpoint ¶
func NewRepoClientWithEndpoint(baseURL string) *RepoClient
NewRepoClientWithEndpoint creates a new repo API client with a specific endpoint. Use this when you already have the endpoint URL (e.g., from auth flow or config).
func (*RepoClient) Endpoint ¶
func (c *RepoClient) Endpoint() string
Endpoint returns the base URL this client is configured for
func (*RepoClient) GetDoctorIssues ¶
func (c *RepoClient) GetDoctorIssues(repoID string) (*DoctorResponse, error)
GetDoctorIssues calls GET /api/v1/repo/{repo_id}/doctor for cloud diagnostics Returns nil, nil if API unavailable (graceful degradation for offline mode)
func (*RepoClient) GetLedgerStatus ¶
func (c *RepoClient) GetLedgerStatus(repoID string) (*LedgerStatusResponse, error)
GetLedgerStatus fetches ledger provisioning status from the cloud API.
Response status values:
- "ready": Ledger is provisioned and RepoURL is available for cloning
- "pending": Ledger is being provisioned, caller should retry later
- "error": Provisioning failed, check Message for details
The repoID parameter is the SageOx repo identifier (UUID) from project config. The returned RepoID is the GitLab project ID (used internally by server).
Returns ErrLedgerNotFound if no ledger exists for this repo. Returns ErrUnauthorized if authentication fails.
func (*RepoClient) GetRepoDetail ¶
func (c *RepoClient) GetRepoDetail(repoID string) (*RepoDetailResponse, error)
GetRepoDetail calls GET /api/v1/cli/repos/{repo_id} to fetch repo detail. Returns visibility, access level, ledger status, and accessible team contexts. Works for both members (full access) and non-members on public repos (viewer access).
Returns ErrForbidden if the user is not a member and the repo is private. Returns ErrUnauthorized if authentication fails. Returns nil, nil if the endpoint returns 404 (server hasn't implemented this endpoint yet).
func (*RepoClient) GetRepos ¶
func (c *RepoClient) GetRepos() (*ReposResponse, error)
GetRepos calls GET /api/v1/cli/repos to fetch user's team context repos. This is user-scoped and returns all team contexts the user has access to. For ledger URLs, use GetLedgerStatus() which is project-scoped. Requires authentication. Returns PAT, repo URLs, and token expiration.
func (*RepoClient) GetTeamInfo ¶
func (c *RepoClient) GetTeamInfo(teamID string) (*TeamInfoResponse, error)
GetTeamInfo calls GET /api/v1/teams/{id} to fetch team information including the team context repo URL and credentials. Requires authentication. Returns nil, nil if team not found (404).
func (*RepoClient) NotifyUninstall ¶
func (c *RepoClient) NotifyUninstall(repoID, repoSalt string) error
NotifyUninstall calls POST /api/v1/repo/{repo_id}/uninstall to notify server of local uninstall. Requires authentication - the server validates the user is a team member with permission to trigger uninstallation workflows. Returns errors so callers can provide user feedback. The repo_salt (first commit hash) provides additional verification.
func (*RepoClient) RegisterRepo ¶
func (c *RepoClient) RegisterRepo(req *RepoInitRequest) (*RepoInitResponse, error)
RegisterRepo calls POST /api/v1/repo/init Returns (response, error) - error is nil if call succeeds (even for 4xx/5xx) Gracefully handles 404 (endpoint not yet deployed) by returning nil, nil
func (*RepoClient) WithAuthToken ¶
func (c *RepoClient) WithAuthToken(token string) *RepoClient
WithAuthToken sets the auth token for authenticated requests
type RepoDetailLedger ¶
type RepoDetailLedger struct {
Status string `json:"status"` // "ready", "pending", "error"
RepoURL string `json:"repo_url"` // git clone URL (empty if not ready)
Message string `json:"message,omitempty"` // status message for pending/error
}
RepoDetailLedger is the ledger section of the repo detail response.
type RepoDetailResponse ¶
type RepoDetailResponse struct {
Visibility string `json:"visibility"` // "public" or "private"
AccessLevel string `json:"access_level"` // "member" or "viewer"
Ledger *RepoDetailLedger `json:"ledger"` // null if not provisioned
TeamContexts []RepoDetailTeamContext `json:"team_contexts"` // empty if none accessible
}
RepoDetailResponse represents GET /api/v1/cli/repos/{repo_id}. Returns repo visibility, user access level, ledger status, and accessible team contexts. Works for both members and non-members on public repos.
func (*RepoDetailResponse) IsReadOnly ¶
func (r *RepoDetailResponse) IsReadOnly() bool
IsReadOnly returns true if the user has viewer (read-only) access.
type RepoDetailTeamContext ¶
type RepoDetailTeamContext struct {
TeamID string `json:"team_id"` // team_xxx
Name string `json:"name"` // display name
Visibility string `json:"visibility"` // "public" or "private"
AccessLevel string `json:"access_level"` // "member" or "viewer"
RepoURL string `json:"repo_url"` // git clone URL
}
RepoDetailTeamContext is a team context in the repo detail response.
func (RepoDetailTeamContext) StableID ¶
func (r RepoDetailTeamContext) StableID() string
StableID returns the stable team identifier (team_xxx) for path construction and lookups.
type RepoFingerprint ¶
type RepoFingerprint struct {
// FirstCommit is the hash of the initial commit (same as repo_salt).
FirstCommit string `json:"first_commit"`
// MonthlyCheckpoints maps "YYYY-MM" to the first commit hash of that month.
MonthlyCheckpoints map[string]string `json:"monthly_checkpoints"`
// AncestrySamples contains commit hashes at power-of-2 intervals.
AncestrySamples []string `json:"ancestry_samples"`
// RemoteHashes contains salted SHA256 hashes of normalized remote URLs.
RemoteHashes []string `json:"remote_hashes,omitempty"`
}
RepoFingerprint holds repository identity fingerprint data for detecting identical or related repositories across teams. Enables the API to suggest team merges when multiple teams are working on the same codebase.
type RepoInfo ¶
type RepoInfo struct {
Name string `json:"name"` // e.g., "acme-corp-team-context"
URL string `json:"url"` // git clone URL
Type string `json:"type"` // "team-context" (ledgers use separate API)
TeamID string `json:"team_id,omitempty"` // team_xxx (present for team-context repos)
}
RepoInfo represents a single git repository from the server. NOTE: This API only returns team context repos, not ledgers. Use GetLedgerStatus() for ledger URLs (project-scoped).
type RepoInitRequest ¶
type RepoInitRequest struct {
RepoID string `json:"repo_id"` // Required: prefixed UUIDv7
Type string `json:"type"` // Required: "git" or "svn"
InitAt string `json:"init_at"` // Required: RFC3339 timestamp
Name string `json:"name,omitempty"` // Optional: display name (e.g. "sageox/ox")
Teams []string `json:"teams,omitempty"` // Optional: team IDs to associate repo with
RepoSalt string `json:"repo_salt,omitempty"` // Optional: initial commit hash
RepoRemoteHashes []string `json:"repo_remote_hashes,omitempty"` // Optional: salted hashes
Fingerprint *RepoFingerprint `json:"fingerprint,omitempty"` // Optional: repo identity fingerprint
Identities any `json:"identities,omitempty"` // Optional: resolved user identities (identity.ResolvedIdentities)
IsPublic bool `json:"is_public,omitempty"` // Optional: prevents fork merging
CreatedByEmail string `json:"created_by_email,omitempty"` // Optional: git user email (backward compat)
CreatedByName string `json:"created_by_name,omitempty"` // Optional: git user name (backward compat)
}
RepoInitRequest represents the POST /api/v1/repo/init request
type RepoInitResponse ¶
type RepoInitResponse struct {
RepoID string `json:"repo_id"`
TeamID string `json:"team_id"`
WebBaseURL string `json:"web_base_url,omitempty"` // web dashboard base URL (for enterprise endpoints)
}
RepoInitResponse represents the POST /api/v1/repo/init response
type RepoMarkerData ¶
type RepoMarkerData struct {
RepoID string `json:"repo_id"`
RepoSalt string `json:"repo_salt"`
Endpoint string `json:"endpoint"`
// TODO: Remove after 2026-01-31 - legacy field support
APIEndpoint string `json:"api_endpoint"` // deprecated: use Endpoint
}
RepoMarkerData holds parsed data from a .repo_* marker file
func ReadFirstRepoMarker ¶
func ReadFirstRepoMarker(sageoxDir string) (*RepoMarkerData, error)
ReadFirstRepoMarker reads the first .repo_* marker file found in the sageox directory. Returns the parsed marker data or nil if no marker found. This is useful for getting repo_id and repo_salt before uninstall.
func (*RepoMarkerData) GetEndpoint ¶
func (m *RepoMarkerData) GetEndpoint() string
GetEndpoint returns the endpoint, preferring new field over legacy
type RepoUninstallRequest ¶
type RepoUninstallRequest struct {
RepoSalt string `json:"repo_salt"` // first commit hash for authentication
}
RepoUninstallRequest represents the POST /api/v1/repo/{repo_id}/uninstall request
type ReposResponse ¶
type ReposResponse struct {
Token string `json:"token"` // PAT for git operations
ServerURL string `json:"server_url"` // GitLab server URL
Username string `json:"username"` // GitLab username
ExpiresAt time.Time `json:"expires_at"` // Token expiration
Repos map[string]RepoInfo `json:"repos"` // Repos indexed by name
Teams []TeamMembership `json:"teams"` // User's team memberships
}
ReposResponse represents the GET /api/v1/cli/repos response. This API returns team context repos only (user-scoped). Ledger URLs are fetched separately via GET /api/v1/repos/{repo_id}/ledger-status (project-scoped).
func (*ReposResponse) TeamMembershipsFromRepos ¶
func (r *ReposResponse) TeamMembershipsFromRepos() []TeamMembership
TeamMembershipsFromRepos derives team memberships from the repos map. Each repo with type "team-context" represents a team the user belongs to. Falls back to the Teams array if populated.
type TeamInfoResponse ¶
type TeamInfoResponse struct {
ID string `json:"id"` // team ID (team_xxx)
Name string `json:"name"` // display name
Slug string `json:"slug,omitempty"` // URL-friendly name
RepoURL string `json:"repo_url,omitempty"` // team context git repo URL
GitToken string `json:"git_token,omitempty"` // token for git operations
}
TeamInfoResponse represents the GET /api/v1/teams/{id} response
type TeamMembership ¶
type TeamMembership struct {
ID string `json:"id"` // team_xxx
Name string `json:"name"` // display name
Role string `json:"role"` // "owner", "admin", "member"
}
TeamMembership represents a team the user belongs to