output

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package output owns PayCLI's single response envelope (§10) and every rendering of it.

The envelope is the contract with the agent calling PayCLI: `ok` is the one field to branch on, `data_kind` says what shape `data` has so nothing has to be guessed, `page.truncated` answers "did I get everything?" without arithmetic, and `next.cmd` is a literally runnable string with the current flags and profile already baked in.

Index

Constants

View Source
const (
	ReasonMorePages         = "more_pages"
	ReasonRetryFailedSubset = "retry_failed_subset"
	ReasonNeedsAuth         = "needs_auth"
	ReasonVerifyWrite       = "verify_write"
	ReasonDiscoverFirst     = "discover_first"
	ReasonRefreshDiscovery  = "refresh_discovery"
)

Next reasons (§10.1).

View Source
const (
	AuthModeAPIKey    = "api-key"
	AuthModeJWT       = "jwt"
	AuthModeAnonymous = "anonymous"
)

Auth modes reported in meta.auth_mode (§5.0).

View Source
const (
	CacheHit         = "hit"
	CacheStaleServed = "stale-served"
	CacheRevalidated = "revalidated"
	CacheMiss        = "miss"
	CacheBypassed    = "bypassed"
	CacheDisabled    = "disabled"
)

meta.cache.discovery states (§10.1).

View Source
const (
	WarnRawRedacted              = "raw_redacted"
	WarnDataRedacted             = "data_redacted"
	WarnAnonymousSession         = "anonymous_session"
	WarnIDTypeUnknown            = "id_type_unknown"
	WarnOperatorPreviouslyFailed = "operator_previously_failed"
	WarnInputSilentlyDropped     = "input_silently_dropped"
	WarnValueNormalizedByServer  = "value_normalized_by_server"
	WarnCreatedAsDraft           = "created_as_draft"
)

Warning codes produced by the shared layers. Command-specific codes live with their commands.

View Source
const DefaultWidth = 100

DefaultWidth is the assumed terminal width when none was detected. 100 is wide enough for id + title + slug + status, which is what `pay find` prints.

View Source
const MaxRichTextPaths = 100

MaxRichTextPaths caps meta.richtext.paths.

View Source
const SchemaVersion = 1

SchemaVersion is envelope.v — the integer schema version of the envelope itself, bumped only on a breaking change to these keys.

Variables

DataKinds is the closed set, for validation and help.

Formats is the closed set, in help order.

Functions

func CheckFormat

func CheckFormat(f Format, kind DataKind) error

CheckFormat returns format_unsupported (exit 5) naming the formats that DO work, so the caller's next attempt succeeds.

func EvalPath

func EvalPath(data any, steps []pathStep) (any, error)

EvalPath applies a compiled expression to a value. The result REPLACES envelope.data; ok, v, command, data_kind, target, page, next, meta and warnings are preserved unchanged, so an agent can always still branch on .ok.

func ParsePath

func ParsePath(expr string) ([]pathStep, error)

ParsePath compiles a --path expression. The returned steps are applied by EvalPath; an empty or "." expression is the identity.

func SortedKeys

func SortedKeys(m map[string]any) []string

SortedKeys is a small helper shared by the csv and table renderers.

Types

type Alternative

type Alternative struct {
	Why string `json:"why"`
	Cmd string `json:"cmd"`
}

Alternative is a cheaper or broader way to get the same answer.

type CacheMeta

type CacheMeta struct {
	Discovery   string `json:"discovery"`
	AgeS        *int64 `json:"age_s"`
	TTLS        *int64 `json:"ttl_s"`
	Fingerprint string `json:"fingerprint,omitempty"`
	Revalidated bool   `json:"revalidated"`
}

CacheMeta is meta.cache (§10.1).

type Changed

type Changed struct {
	Created int   `json:"created"`
	Updated int   `json:"updated"`
	Deleted int   `json:"deleted"`
	Trashed int   `json:"trashed"`
	IDs     []any `json:"ids"`
}

Changed is the write summary (§10.2). Every counter is always present, even at zero, so an agent never has to presence-check one.

type DataKind

type DataKind string

DataKind names the shape of Envelope.Data (§10.1).

const (
	KindDoc          DataKind = "doc"
	KindDocList      DataKind = "doc_list"
	KindCount        DataKind = "count"
	KindGlobal       DataKind = "global"
	KindVersion      DataKind = "version"
	KindVersionList  DataKind = "version_list"
	KindBulkResult   DataKind = "bulk_result"
	KindCapabilities DataKind = "capabilities"
	KindSchema       DataKind = "schema"
	KindCommandSpec  DataKind = "command_spec"
	KindOpResult     DataKind = "op_result"
	KindRaw          DataKind = "raw"
	KindError        DataKind = "error"
)

func (DataKind) Valid

func (k DataKind) Valid() bool

Valid reports whether k is part of the closed set.

type EditOp added in v0.2.0

type EditOp struct {
	// Command is the Cmd* constant of the stage that produced it.
	Command string `json:"command"`
	// Field is the document field it edited.
	Field string `json:"field"`
	// Detail is one human-readable sentence: "moved id:a1 (cta) from 0 to 2".
	Detail string `json:"detail"`
	// Matched are the source indices the selector resolved to, recorded
	// because the indices shift under the next stage and this is the only
	// record of what the caller actually addressed.
	Matched []int `json:"matched,omitempty"`
	// Rows is the row count after the op, so a listing is not needed to see
	// that a remove removed something.
	Rows int `json:"rows"`
}

EditOp is one applied transform.

type Edits added in v0.2.0

type Edits struct {
	// Fields are the document's top-level field paths the pipeline changed, in
	// first-touched order and deduplicated. `apply` builds its PATCH body from
	// exactly these.
	Fields []string `json:"fields"`
	// Ops is every transform applied, oldest first, so the envelope explains
	// itself after four stages of a pipe.
	Ops []EditOp `json:"ops"`
}

Edits is the provenance a local transform stamps on its envelope, and the only thing that makes `pay get | pay blocks … | pay apply` safe.

Without it `apply` would have to send the whole document back, which means PATCHing every field PayCLI happened to read — including the server-owned ones (`createdAt`, `_status`) and any relationship the read expanded into an object. `Fields` narrows the write to the paths the pipeline actually touched, so a pipeline that moved one block writes one key.

It is present only on the local-edit commands and on `apply`; a command that talks to Payload never sets it.

func (*Edits) Touch added in v0.2.0

func (e *Edits) Touch(field string)

Touch records a field as edited, keeping Fields in first-touched order and free of duplicates.

type Envelope

type Envelope struct {
	OK       bool          `json:"ok"`
	V        int           `json:"v"`
	Command  string        `json:"command"`
	DataKind DataKind      `json:"data_kind"`
	Partial  bool          `json:"partial,omitempty"`
	Target   *Target       `json:"target,omitempty"`
	Data     any           `json:"data,omitempty"`
	Changed  *Changed      `json:"changed,omitempty"`
	Edits    *Edits        `json:"edits,omitempty"`
	Page     *Page         `json:"page,omitempty"`
	Error    *apierr.Error `json:"error,omitempty"`
	Next     *Next         `json:"next,omitempty"`
	Meta     Meta          `json:"meta"`
	Warnings []Warning     `json:"warnings"`

	// RawBody is the server's response body for `--output raw`. It never
	// appears in the JSON envelope; the writer emits it instead of the
	// envelope. It is stored already redacted, with RawRedacted recording
	// whether redaction changed the bytes so the writer can print the
	// "--no-redact" notice on stderr (§10.3).
	RawBody     []byte `json:"-"`
	RawRedacted bool   `json:"-"`
	// contains filtered or unexported fields
}

Envelope is the one object PayCLI prints. Struct order IS the JSON key order, which is §10.1's listing with `partial` (§12.5), `changed` (§10.2) and `error` (§11.1) folded into their natural neighbours: those three sections each show a subset and they do not agree on relative position, so the success listing in §10.1 wins and the extras sit next to what they describe.

func New

func New(command string, kind DataKind, data any) *Envelope

New builds a success envelope. Warnings is always an array, never null.

func NewError

func NewError(command string, err error) *Envelope

NewError builds the error envelope (§11.1). Any error is accepted; one that never passed through apierr becomes `internal`, exit 1.

func (*Envelope) AddWarning

func (e *Envelope) AddWarning(w Warning) *Envelope

AddWarning appends a warning. Warnings never change ok or the exit code.

func (*Envelope) ApplyPath

func (e *Envelope) ApplyPath(expr string) error

ApplyPath compiles and applies expr, replacing envelope.data in place.

func (*Envelope) Compact

func (e *Envelope) Compact() ([]byte, error)

Compact renders the envelope on one line, which is what jsonl writes to stderr.

func (*Envelope) ExitCode

func (e *Envelope) ExitCode() int

ExitCode is the process status this envelope implies (§11.4).

func (*Envelope) MarshalIndentTo

func (e *Envelope) MarshalIndentTo() ([]byte, error)

MarshalIndentTo is the canonical JSON rendering: two-space indent and NO HTML escaping. Escaping matters: encoding/json turns "<" into "<" by default, which would mangle every "<redacted>" sentinel and every URL containing an ampersand in a where clause.

func (*Envelope) NarrowedToScalar

func (e *Envelope) NarrowedToScalar() bool

NarrowedToScalar reports that --path reduced the envelope to a plain value (or a list of plain values) with no structure left.

It exists so `--output id` can print a path-selected scalar. The §10.3 matrix is keyed on data_kind, and data_kind describes what the COMMAND produced, so `pay doctor --path .ok --output id` — an example the help registry itself prints — failed with format_unsupported "--output id cannot render capabilities output", even though by then .data is just `true`. Nothing about a bare scalar is capabilities-shaped.

func (*Envelope) PropagateRawRedaction

func (e *Envelope) PropagateRawRedaction() *Envelope

PropagateRawRedaction turns error.raw's redaction into the warnings[] entry §11.1 requires, so the agent knows the bytes differ from the wire.

func (*Envelope) RedactData

func (e *Envelope) RedactData() []string

RedactData masks every secret value inside .data (§5.3: "scrubs apiKey … from ALL output"), returning the paths that changed so the caller can say so in warnings[]. It is a no-op when nothing matched, and it is idempotent, so a command that already redacted its own payload pays nothing here.

The value is marshalled, spliced by redact.JSON and decoded back with UseNumber, which keeps a 19-digit document id and a trailing-zero decimal intact. Only a document that actually carried a secret is rebuilt.

func (*Envelope) WithChanged

func (e *Envelope) WithChanged(c *Changed) *Envelope

WithChanged sets the write summary.

func (*Envelope) WithEdits added in v0.2.0

func (e *Envelope) WithEdits(ed *Edits) *Envelope

WithEdits sets envelope.edits — the pipeline provenance a local transform carries to the next stage and, finally, to `pay apply`.

func (*Envelope) WithMeta

func (e *Envelope) WithMeta(m Meta) *Envelope

WithMeta sets the per-invocation context, redacting base_url (§5.3 makes redaction of meta.base_url mandatory).

It is also where §5.0's `anonymous_session` warning is attached, because this is the ONE funnel meta.auth_mode passes through on its way into an envelope — success and error alike. Emitting it here rather than per command is what makes "every envelope carries it" true by construction instead of by thirty-odd remembered call sites; an anonymous run that silently reports a reduced permission matrix as if it were the whole project is a wrong answer, not a cosmetic omission.

func (*Envelope) WithNext

func (e *Envelope) WithNext(n *Next) *Envelope

WithNext sets the recommended follow-up.

func (*Envelope) WithPage

func (e *Envelope) WithPage(p *Page) *Envelope

WithPage sets envelope.page. It is only meaningful for doc_list.

func (*Envelope) WithRawBody

func (e *Envelope) WithRawBody(body []byte, noRedact bool) *Envelope

WithRawBody attaches the server's body for `--output raw`, redacting it unless noRedact. When redaction changed the bytes the caller gets RawRedacted set, and the writer prints the one-line stderr notice naming --no-redact (§10.3).

func (*Envelope) WithTarget

func (e *Envelope) WithTarget(t *Target) *Envelope

WithTarget sets envelope.target.

type ErrorsTo

type ErrorsTo string

ErrorsTo selects which stream carries the error envelope (§11.1). The default is stdout — an agent reads one stream and branches on .ok — and PAY_ERRORS_TO=stderr / --errors-to stderr moves it for people who script around the Unix convention.

const (
	ErrorsToStdout ErrorsTo = "stdout"
	ErrorsToStderr ErrorsTo = "stderr"
)

func ParseErrorsTo

func ParseErrorsTo(s string) (ErrorsTo, error)

ParseErrorsTo validates --errors-to.

type Format

type Format string

Format is the value of --output (§10.3).

const (
	// FormatJSON is the DEFAULT: one pretty envelope object, for agents.
	FormatJSON Format = "json"
	// FormatJSONL streams bare documents on stdout, envelope on stderr.
	FormatJSONL Format = "jsonl"
	// FormatID prints one id per line and nothing else.
	FormatID Format = "id"
	// FormatRaw prints Payload's body with no envelope — after redaction,
	// unless --no-redact.
	FormatRaw Format = "raw"
	// FormatCSV is RFC 4180, for humans.
	FormatCSV Format = "csv"
	// FormatTable is aligned and width-truncated, for humans. Never
	// recommended to agents.
	FormatTable Format = "table"
)

func ParseFormat

func ParseFormat(s string) (Format, error)

ParseFormat validates --output. An unknown value is invalid_option (exit 5) with the valid list attached, never a silent fallback to json.

func (Format) Supports

func (f Format) Supports(kind DataKind) bool

Supports reports whether the format can render this data_kind.

type Locale

type Locale struct {
	Requested *string `json:"requested"`
	Fallback  *string `json:"fallback"`
}

Locale carries the RESOLVED values PayCLI actually sent, so an agent can see that fallback "none" is why a field came back null. Both are null on a non-localised project (§10.1).

type Meta

type Meta struct {
	RequestID            string  `json:"request_id"`
	CLIVersion           string  `json:"cli_version"`
	Profile              string  `json:"profile"`
	BaseURL              string  `json:"base_url"`
	APIPath              string  `json:"api_path"`
	AuthMode             string  `json:"auth_mode"`
	PayloadVersion       *string `json:"payload_version"`
	PayloadVersionSource string  `json:"payload_version_source"`
	Locale               Locale  `json:"locale"`
	DateField            *string `json:"date_field"`
	// SearchedFields is §9.4's mandatory report for --q: the text fields the
	// OR of `contains` was actually built over. Absent on a command that did
	// not search.
	SearchedFields    []string   `json:"searched_fields,omitempty"`
	Since             *string    `json:"since"`
	Until             *string    `json:"until"`
	DurationMS        int64      `json:"duration_ms"`
	HTTPRequests      int        `json:"http_requests"`
	Retries           int        `json:"retries"`
	Bytes             int64      `json:"bytes"`
	Cache             *CacheMeta `json:"cache,omitempty"`
	DiscoveryRevision string     `json:"discovery_revision,omitempty"`
	DryRun            bool       `json:"dry_run"`
	// Backups lists the §12.8 backup files this invocation wrote before it
	// changed or removed a document. Absent when nothing was backed up —
	// backups are off, or the command changed no existing document.
	Backups []string `json:"backups,omitempty"`
	// RichText records that --richtext md|text rendered the rich text of the
	// data for reading. Absent when rich text is the stored Lexical JSON (the
	// default). Its presence is what `pay apply` and `--data @-` refuse: a
	// rendered document must never be written back.
	RichText *RichTextMeta `json:"richtext,omitempty"`
	// ResetNested reports a --reset-nested write (§10.2.1): the carrier
	// phases sent before the requested body, the block tables they cleared
	// and whether the stored row read back clean. Absent on every other
	// command.
	ResetNested any `json:"reset_nested,omitempty"`
	// Upsert is what `pay upsert` decided (F6): absent on every other command.
	Upsert *UpsertMeta `json:"upsert,omitempty"`
	// RestoredFrom is the backup file `pay backups restore` wrote back
	// (§12.8.8); absent on every other command.
	RestoredFrom string `json:"restored_from,omitempty"`
}

Meta is the per-invocation context. payload_version is null rather than a fabricated constant when it could not be determined, and payload_version_source always says which it is (§7.11).

type Next

type Next struct {
	Reason       string         `json:"reason"`
	Cmd          string         `json:"cmd"`
	Args         map[string]any `json:"args,omitempty"`
	Alternatives []Alternative  `json:"alternatives,omitempty"`
}

Next is the follow-up PayCLI recommends. Cmd is literally runnable.

type Page

type Page struct {
	Limit       int  `json:"limit"`
	Page        int  `json:"page"`
	TotalPages  int  `json:"total_pages"`
	TotalDocs   int  `json:"total_docs"`
	Returned    int  `json:"returned"`
	HasNextPage bool `json:"has_next_page"`
	HasPrevPage bool `json:"has_prev_page"`
	NextPage    *int `json:"next_page"`
	PrevPage    *int `json:"prev_page"`
	Truncated   bool `json:"truncated"`
}

Page is present iff data_kind == "doc_list" (§10.1). Truncated is the one boolean that requires no arithmetic: true means more matching documents exist than this envelope holds (another page on a paginated read, or --all stopped at --max).

type Renderer

type Renderer interface {
	Render(env *Envelope) (exitCode int, err error)
}

Renderer writes one envelope to its destination and returns the process exit status implied by it. internal/cli/app.go is the only place that owns the process's real standard streams; everything else is handed a Renderer, which is what makes CLI-level golden testing possible at all.

type RichTextMeta added in v0.3.0

type RichTextMeta struct {
	// Format is what every editor state was replaced by: "md" or "text".
	Format string `json:"format"`
	// Rendered counts the editor states replaced.
	Rendered int `json:"rendered"`
	// Paths are the replaced values' paths (`content`, `layout[2].richText`,
	// `[0].content` in a list), at most MaxRichTextPaths of them.
	Paths []string `json:"paths"`
	// PathsTruncated reports that Paths was capped.
	PathsTruncated bool `json:"paths_truncated,omitempty"`
}

RichTextMeta is meta.richtext.

type Target

type Target struct {
	Kind     string `json:"kind"` // "collection" | "global"
	Slug     string `json:"slug"`
	Singular string `json:"singular,omitempty"`
	IDType   string `json:"id_type,omitempty"`
	ID       any    `json:"id,omitempty"`
}

Target names what the command acted on.

type UpsertMeta added in v0.3.0

type UpsertMeta struct {
	// Action is created | updated | unchanged after a real run, and the
	// plan vocabulary create | update | unchanged under --dry-run (nothing
	// happened yet).
	Action string `json:"action"`
	// MatchedBy is the --match fields and the values taken from the body.
	MatchedBy map[string]any `json:"matched_by"`
	// ID is the matched (or created) document's id; null for a dry-run create.
	ID any `json:"id"`
	// Forced is true when --force wrote a body that changed nothing.
	Forced bool `json:"forced,omitempty"`
}

UpsertMeta is meta.upsert.

type Warning

type Warning struct {
	Code         string         `json:"code"`
	Message      string         `json:"message"`
	Paths        []string       `json:"paths,omitempty"`
	Sent         map[string]any `json:"sent,omitempty"`
	Returned     map[string]any `json:"returned,omitempty"`
	StillMissing []string       `json:"still_missing,omitempty"`
	Hint         string         `json:"hint,omitempty"`
}

Warning never changes ok or the exit code (§10.1).

func AnonymousSessionWarning

func AnonymousSessionWarning(profile, baseURL string) Warning

AnonymousSessionWarning is §5.0's verbatim notice that no credential was resolved. The hint is a literally runnable login command for THIS profile.

func DataRedactedWarning

func DataRedactedWarning(paths []string) Warning

DataRedactedWarning is the §10.3-style notice that .data differs from the bytes the server sent, naming the flag that turns the masking off.

func IDTypeUnknownWarning

func IDTypeUnknownWarning(slug, profile string) Warning

IDTypeUnknownWarning is §7.6(a)'s mandatory transparency notice: the client-side id check was SKIPPED because PayCLI never learned this collection's id type, so a malformed id will come back from the server rather than from PayCLI. The hint is the one from §7.6(a) verbatim, so this warning and `pay explain limitations` cannot drift apart.

func OperatorPreviouslyFailedWarning

func OperatorPreviouslyFailedWarning(operator, collection, evidence string) Warning

OperatorPreviouslyFailedWarning is §7.11's reactive memo: this operator has already failed on this collection, with the recorded evidence, and PayCLI is sending it anyway rather than inventing a refusal.

type Writer

type Writer struct {
	Stdout io.Writer
	Stderr io.Writer

	// Format is --output. Zero value renders as json.
	Format Format
	// ErrorsTo is --errors-to.
	ErrorsTo ErrorsTo
	// Columns overrides the default column selection for csv and table.
	Columns []string
	// Width is the terminal width for table output; 0 uses DefaultWidth.
	Width int
	// Human adds the one-line stderr summary on an error (§11.1). It is on by
	// default; --quiet turns it off.
	Quiet bool
	// NoRedact is --no-redact. The zero value redacts, so a Writer built
	// anywhere (including in a test) can never be the thing that spills a
	// credential (§5.3).
	NoRedact bool
	// StdoutIsData says the command has already written raw bytes to Stdout
	// and owns that stream (today: `pay download -o -`). The envelope is then
	// rendered to Stderr, because appending JSON after a PNG produces a file
	// that is neither: a 463-byte image came out as a 1570-byte hybrid. It is
	// deliberately a writer-level flag rather than a per-command dance, so
	// every future byte-streaming command gets the same protection.
	StdoutIsData bool
}

Writer is the concrete Renderer.

func (*Writer) Render

func (w *Writer) Render(env *Envelope) (int, error)

Render writes env and returns the exit status it implies. Format/data_kind compatibility is checked here, so an unsupported combination fails loudly with format_unsupported (exit 5) instead of silently falling back to JSON.

Jump to

Keyboard shortcuts

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