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 ¶
const DefaultEndpoint = "https://goei.roninforge.org/api/ingest"
DefaultEndpoint is the production Goei ingest URL.
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 ValidToken ¶
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 ¶
Client posts payloads to a Goei ingest endpoint.
type Payload ¶
type Payload struct {
Provider string `json:"provider"`
Spend []SpendRecord `json:"spend"`
Usage []UsageRecord `json:"usage,omitempty"`
}
Payload is one POST body to /api/ingest.
func BuildPayloads ¶
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 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.