goei

package
v1.5.0 Latest Latest
Warning

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

Go to latest
Published: Jul 22, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package goei pushes locally-computed Claude Code spend aggregates to a Goei dashboard's device-token ingest endpoint.

This is the "zero-key" half of Goei's two-track trust model: instead of handing Goei an Anthropic admin API key, the user runs budgetclaw locally (which only ever reads ~/.claude/projects/*.jsonl) and ships aggregated dollar-and-token rollups to their own dashboard. No key leaves the machine; the only thing transmitted is the cost summary.

The wire contract mirrors Goei's POST /api/ingest handler:

  • Authorization: Bearer goei_dt_<32-hex> (exactly 40 chars)
  • body: {provider, spend[], usage?} provider must be "anthropic"
  • spend dedup key: (period_start, model, project, branch)
  • usage dedup key: (period_start, metric_type, model, breakdown_key, breakdown_value)

Each spend record also carries an optional inline "tokens" object (input, output, cache_read, cache_write_5m, cache_write_1h) at the same per-(day, project, branch, model) grain as its dollar amount. The current server ignores it and keys off amountCents; a future server prefers tokens so it can re-price at its own point-in-time rate. Both are always sent, so the change is backward compatible.

Per-branch attribution (a budgetclaw differentiator) rides on the spend record's own optional branch field, so the project field always carries the bare project name. With --no-branch the branch is omitted and all branches of a project collapse server-side. Usage records break down by bare project name regardless.

Both arrays dedupe server-side via upsert, so re-running sync is idempotent: the same day re-sent overwrites, it does not double-count.

Index

Constants

View Source
const DefaultBaseURL = "https://goei.roninforge.org"

DefaultBaseURL is the production Goei origin. A repo pointer's endpoint normalizes to this when unset.

View Source
const DefaultEndpoint = "https://goei.roninforge.org/api/ingest"

DefaultEndpoint is the production Goei ingest URL.

View Source
const DefaultHost = "goei.roninforge.org"

DefaultHost is the host of DefaultBaseURL, used to decide whether a repo pointer aims at the default hosted Goei or a self-hosted instance the user should be warned about.

View Source
const PointerFileName = ".budgetclaw.toml"

PointerFileName is the committed, non-secret team pointer a Goei team lead drops in a repo root. It carries only routing info plus a revocable join code, never a device token. Its presence is what lets `budgetclaw team join` (and the discovery line) know a team dashboard exists for this repo.

View Source
const Provider = "anthropic"

Provider is the only provider value Goei accepts for Claude Code data. Claude Code spend is Anthropic API spend.

Variables

This section is empty.

Functions

func HostOf added in v1.4.0

func HostOf(endpoint string) string

HostOf returns the host a pointer endpoint resolves to (empty -> DefaultHost), for display and trust decisions before any request is made.

func IngestEndpointFor added in v1.4.0

func IngestEndpointFor(endpoint string) string

IngestEndpointFor derives the ingest URL from a pointer endpoint so a token saved by `team join` points at the same server the join used.

func ValidToken

func ValidToken(t string) bool

ValidToken reports whether a string is shaped like a Goei device token: the "goei_dt_" prefix plus a 32-char body, 40 chars total. This matches the format check Goei's ingest handler enforces.

Types

type Aggregate

type Aggregate struct {
	Project            string
	GitBranch          string
	Model              string
	Day                string // YYYY-MM-DD (UTC)
	CostUSD            float64
	InputTokens        int
	OutputTokens       int
	CacheReadTokens    int
	CacheWrite5mTokens int
	CacheWrite1hTokens int
}

Aggregate is the neutral input to BuildPayloads, decoupled from the db package so this package has no storage dependency. It mirrors db.SyncAggregate.

type Client

type Client struct {
	Endpoint string
	Token    string
	HTTP     *http.Client
}

Client posts payloads to a Goei ingest endpoint.

func New

func New(endpoint, token string) *Client

New returns a Client with sane defaults. An empty endpoint falls back to DefaultEndpoint.

func (*Client) PullPolicies added in v1.3.0

func (c *Client) PullPolicies(ctx context.Context, etag string) (resp *PolicyResponse, newETag string, notModified bool, err error)

PullPolicies fetches this device's Guard Mode policy set from GET /api/policy. A conditional request with the last ETag returns notModified=true and no body when nothing changed, so `budgetclaw watch` can poll cheaply. The returned etag should be passed to the next call.

func (*Client) Push

func (c *Client) Push(ctx context.Context, p Payload) (int, *PolicyResponse, error)

Push sends a single payload and returns the number of records Goei reports it stored, plus any Guard Mode policy bundle piggybacked on the response (nil when the server sends none). Errors include the HTTP status and a snippet of the response body for diagnosis.

type DeviceAuthPoll added in v1.4.0

type DeviceAuthPoll struct {
	Status string `json:"status"`
	Token  string `json:"token,omitempty"`
	Team   string `json:"team,omitempty"`
}

DeviceAuthPoll is the response to POST /api/team/device/poll. Status is one of pending, requested, approved, completed, denied, expired. Token is present exactly once, on the first poll after the lead approves.

func PollDeviceAuth added in v1.4.0

func PollDeviceAuth(ctx context.Context, endpoint, deviceCode string) (*DeviceAuthPoll, error)

PollDeviceAuth checks the state of a device authorization. A 429 is treated as a transient "keep waiting" so the caller's poll loop backs off rather than failing.

type DeviceAuthStart added in v1.4.0

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

DeviceAuthStart is the response to POST /api/team/device/start: the device_code the CLI polls with, the short user_code the teammate confirms in a browser, and the URLs and timing to drive the flow.

func StartDeviceAuth added in v1.4.0

func StartDeviceAuth(ctx context.Context, endpoint, joinCode, machine, deviceName string) (*DeviceAuthStart, error)

StartDeviceAuth exchanges a join code for a device authorization. It mints no credential: the returned codes drive a browser confirmation and a lead approval before any token exists.

type GuardEvent added in v1.3.0

type GuardEvent struct {
	PolicyID    string `json:"policyId"`
	Action      string `json:"action"` // notify | warn | kill | override
	ScopeType   string `json:"scopeType,omitempty"`
	ScopeValue  string `json:"scopeValue,omitempty"`
	Machine     string `json:"machine,omitempty"`
	AmountCents int    `json:"amountCents"`
	CapCents    int    `json:"capCents"`
	At          string `json:"at,omitempty"`
	// DedupKey makes reporting idempotent: the server INSERT OR IGNOREs on
	// it, so a re-sent event (same period, same action) is recorded once.
	DedupKey string `json:"dedupKey,omitempty"`
}

GuardEvent is one enforcement audit record: a remote policy warned or killed a runaway on this machine. Content-free by construction (ids, amounts, scope labels, machine, timestamps only), so it upholds the zero-prompt pledge exactly like the spend records do.

type PRRecord added in v1.5.0

type PRRecord struct {
	Project   string `json:"project"`
	Branch    string `json:"branch,omitempty"`
	PR        int    `json:"pr,omitempty"`
	Base      string `json:"base,omitempty"`
	State     string `json:"state"`
	MergedAt  string `json:"mergedAt,omitempty"`
	Commits   int    `json:"commits"`
	Additions int    `json:"additions"`
	Deletions int    `json:"deletions"`
}

PRRecord is one pull request (or in-flight branch) for cost-per-PR attribution. Content-free: a PR number, branch and base names, commit count, and diff size, never commit messages or code. State is "merged", "squashed", or "open". Goei joins it to local spend by (project, branch); a squashed PR has no head branch, so it carries the number and diff size but no per-branch cost.

type Payload

type Payload struct {
	Provider string        `json:"provider"`
	Spend    []SpendRecord `json:"spend"`
	Usage    []UsageRecord `json:"usage,omitempty"`
	// GuardEvents carries content-free enforcement audit records back up
	// with the sync (Guard Mode's audit channel). Omitted when there are
	// none, so a plain sync is byte-for-byte unchanged.
	GuardEvents []GuardEvent `json:"guardEvents,omitempty"`
	// PRs carries content-free cost-per-PR metadata (opt-in via `budgetclaw
	// prs on`). Omitted when empty, so a plain sync is unchanged.
	PRs []PRRecord `json:"prs,omitempty"`
}

Payload is one POST body to /api/ingest.

func BuildPayloads

func BuildPayloads(aggregates []Aggregate, includeBranch bool, machine string) []Payload

BuildPayloads converts aggregates into one or more ingest payloads, chunked to stay under the server's per-request caps. Aggregates are grouped by day and whole days are packed into requests, so every request carries each day's spend and usage together and no request is ever spend-empty (which the endpoint rejects).

A spend record is emitted for every aggregate (including ones that round to zero cents) so that a day with only sub-cent usage still has the spend row its usage rows ride along with. Usage records are emitted only for non-zero token metrics.

machine is stamped on every spend record so the server can attribute rollups to the machine they came from. An empty machine is fine: the omitempty field is dropped and the server treats it as legacy.

type PolicyResponse added in v1.3.0

type PolicyResponse struct {
	PolicyVersion int          `json:"policyVersion"`
	Policies      []WirePolicy `json:"policies"`
}

PolicyResponse is the Guard Mode policy bundle, returned both by GET /api/policy and piggybacked on the POST /api/ingest response.

type RepoPointer added in v1.4.0

type RepoPointer struct {
	Team     string
	Endpoint string
	JoinCode string
	Mode     string

	Path string // absolute path of the file it was read from
	// contains filtered or unexported fields
}

RepoPointer is a parsed goei pointer section from a committed .budgetclaw.toml.

func FindRepoPointer added in v1.4.0

func FindRepoPointer(startDir string) (*RepoPointer, error)

FindRepoPointer walks up from startDir looking for a .budgetclaw.toml that carries a goei join_code, so a teammate can run `budgetclaw team join` from anywhere inside the repo, not only its root. The walk stops at the repo root (the first directory containing a .git entry): a pointer belongs to a specific repository, so a stray .budgetclaw.toml sitting in a shared parent (a home directory, a CI runner) must not be picked up by unrelated repos nested beneath it. Returns (nil, nil) when no pointer is found within the repo (or before the filesystem root when startDir is not in a repo). A malformed pointer file surfaces its parse error.

func ParsePointer added in v1.4.0

func ParsePointer(data []byte, path string) (*RepoPointer, error)

ParsePointer decodes a pointer file's bytes. It returns (nil, nil) when the file carries no goei join_code, so an unrelated .budgetclaw.toml (for instance a hand-written local config that happens to sit in a repo) is not mistaken for a team pointer.

func (*RepoPointer) PromptsEnabled added in v1.4.0

func (p *RepoPointer) PromptsEnabled() bool

PromptsEnabled reports whether the discovery line may render for this repo. An absent key means yes; `[goei] prompts = false` in the pointer silences it forever.

type SpendRecord

type SpendRecord struct {
	PeriodStart string       `json:"periodStart"`
	PeriodEnd   string       `json:"periodEnd"`
	AmountCents int          `json:"amountCents"`
	Currency    string       `json:"currency"`
	Model       string       `json:"model,omitempty"`
	Project     string       `json:"project,omitempty"`
	Branch      string       `json:"branch,omitempty"`
	Machine     string       `json:"machine,omitempty"`
	Tokens      *TokenCounts `json:"tokens,omitempty"`
}

SpendRecord is one daily per-(model, project, branch) dollar amount. Branch is optional: when empty (the --no-branch case) the server collapses every branch of a project into a single project-level row.

Machine is an optional per-machine identity (typically the OS hostname). The Goei server uses it to keep two machines' rollups from colliding: the same (day, project, branch, model) synced from a laptop and a desktop stay separate instead of overwriting each other. When empty the server treats it as legacy/unknown, so the field is additive and backward compatible.

Tokens is an optional per-(day, project, branch, model) token rollup at the same grain as AmountCents. The current Goei server ignores it and keys off AmountCents; a future server prefers Tokens so it can re-price at its own point-in-time rate. Both are always sent.

type TokenCounts added in v1.0.0

type TokenCounts struct {
	Input        int `json:"input"`
	Output       int `json:"output"`
	CacheRead    int `json:"cache_read"`
	CacheWrite5m int `json:"cache_write_5m"`
	CacheWrite1h int `json:"cache_write_1h"`
}

TokenCounts is the per-(day, project, branch, model) token rollup carried inline on a spend record. It is the same grain as the spend dollar amount, so a future Goei server can re-price the tokens at its own point-in-time rate instead of trusting amountCents. The current server ignores this field; sending it is forward-compatible.

type UsageRecord

type UsageRecord struct {
	PeriodStart    string `json:"periodStart"`
	PeriodEnd      string `json:"periodEnd"`
	MetricType     string `json:"metricType"`
	MetricValue    int    `json:"metricValue"`
	Model          string `json:"model,omitempty"`
	BreakdownKey   string `json:"breakdownKey,omitempty"`
	BreakdownValue string `json:"breakdownValue,omitempty"`
}

UsageRecord is one daily per-(model, project) token count for a single metric type.

type WirePolicy added in v1.3.0

type WirePolicy struct {
	ID    string `json:"id"`
	Scope struct {
		Type  string `json:"type"`
		Value string `json:"value"`
	} `json:"scope"`
	Period           string `json:"period"`
	CapCents         int    `json:"capCents"`
	Enforcement      string `json:"enforcement"`
	Action           string `json:"action"`
	ServerSpentCents int    `json:"serverSpentCents"`
	AsOf             string `json:"asOf"`
	SetBy            string `json:"setBy"`
	Source           string `json:"source"`
}

WirePolicy is one budget policy as Goei serializes it for this device on the Guard Mode down-channel. enforcement is "local_exact" (enforce against this machine's own rollup, kill-eligible) or "server_aggregate" (a team-wide figure this device can only warn against, with staleness).

Jump to

Keyboard shortcuts

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