output

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Aug 13, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package output renders one result three ways: for a person, for an agent, and for pasting into a pull request.

Index

Constants

View Source
const (
	EmbedSurfaces = "GitHub, Linear, Notion — renders the image"
	// PlainSurfaces replaces EmbedSurfaces when there is no image to embed and
	// the markdown form is only a plain link, so the label stays honest.
	PlainSurfaces = "GitHub, Linear, Notion — plain link, no preview to embed"
	LinkSurfaces  = "Slack, Basecamp — they unfurl the link themselves"
)

EmbedSurfaces and LinkSurfaces name where each form belongs, so the choice never has to be guessed. Exported because the MCP server says the same thing.

View Source
const StatusFinished = "finished"

StatusFinished is the registry's word for a closed run. Its statuses are open and finished — nothing sets "running" — and the difference decides which breadcrumbs a run gets, so it is named here rather than spelled out at each place that branches on it, where the two spellings drifted apart once already.

Variables

This section is empty.

Functions

func Artifact

func Artifact(a *api.Artifact, f Format, quiet, colour bool, now time.Time) string

Artifact renders a single artifact that already exists, for the commands that read or claim one rather than upload it.

The JSON envelope is deliberately the same as an upload's, so an agent parses one shape whichever command it ran. Only the human line differs: `uploads show` did not upload anything, and saying it did would be a lie.

func Authorizing

func Authorizing(a Authorization, f Format, colour bool) string

Authorizing renders that notice.

It goes to stderr rather than stdout, which keeps the one document on stdout the receipt a program parses — but it means that a login which then *fails* leaves stderr carrying this notice ahead of the error envelope. That is unavoidable for a command with something to say before it knows its outcome, so it is said in the same format the rest of the output is in: prose for a person, a JSON document for a program, which makes stderr a stream of documents whose last one is the outcome rather than prose with JSON stuck to it.

The code is shown so it can be compared against the one on the page. That comparison is the whole reason a code exists — the slug collects the key and never appears in a browser, so what the page asks somebody to approve has to be identifiable as the request their own terminal made. It takes no quiet: --quiet drops an envelope, and there is no envelope here to drop. The `authorizing` key is not a wrapper around a result but the thing that says this is not one, so removing it would take away the only field a reader has to tell an interim notice from the verdict on the same stream.

func Claimed

func Claimed(a *api.Artifact, f Format, quiet, colour bool, now time.Time) string

Claimed renders an artifact that has just been claimed. It is Artifact plus the one thing a claim leaves undone: a claimed upload belongs to a workspace but to no run, and `uploads attach` is the only way it ever gets one — the upload could not name a run when it was created, and claiming does not give it one. Saying so here is the difference between an agent knowing that and having to read the help to find out.

Silent when the artifact already has a run, which is what `claim --run` does in one step, and under --quiet, which asks for the record and nothing suggested. Markdown and url are the paste forms and are untouched.

A person gets the same thing, as a line under the artifact. The human output prints a run when there is one and simply omits the fact when there is not, so leaving this to the envelope would have meant the interactive default — the one a person actually sees — never learning that `uploads attach` exists, which is the whole gap this closes.

func ConfigShow added in v0.2.0

func ConfigShow(v ConfigView, f Format, quiet, colour bool) string

ConfigShow renders the effective configuration.

func ConfigWrote added in v0.2.0

func ConfigWrote(key, value, path string, f Format, quiet, colour bool) string

ConfigWrote is the receipt for `config set` and `config unset`: which key, in which file, and what it now says. value is empty for an unset.

func DefaultWorkspace added in v0.2.0

func DefaultWorkspace(name, path string, f Format, quiet, colour bool) string

DefaultWorkspace is the receipt for `workspaces use`: which stored key is now the machine-wide fallback, and where that pointer was written. It is only the fallback — a repo config or the environment still outranks it, and the crumb points at the command that says what actually resolves.

func Error

func Error(err error, f Format, quiet, colour bool) string

Error renders a failure. Human output leads with the code and ends with the fix; JSON hands back the flattened body.

func HumanBytes

func HumanBytes(n int64) string

HumanBytes renders a byte count the way the terminal output does.

func Key

func Key(k *api.Key, f Format, quiet, colour bool) string

Key renders a verified API key: which key it is, and the workspace every call with it lands in — the fact worth confirming before an upload. There is no link to a key, so markdown and url fall back to the JSON envelope.

func List

func List(p *api.Page, l Listing, f Format, quiet, colour bool, now time.Time) string

List renders a page of artifacts.

func MarkdownLink(a *api.Artifact, title string) string

MarkdownLink is the paste-ready link for one artifact. The registry renders this itself — an image embed that clicks through to the card page, anything else a plain link to the card — so its version is used unless a title was asked for, which only the caller knows.

func MarkdownSurfacesFor

func MarkdownSurfacesFor(p Paste) string

MarkdownSurfacesFor is the honest label for a paste's markdown form: it only promises an image where the markdown actually embeds one.

An image embed links through to the card page, so it is spelled `[![…](file)](card)` and no longer starts with the bang. The embed is looked for anywhere in the string rather than at the front, so this side does not start calling every image a plain link the day the registry changes what it wraps the embed in. A label cannot counterfeit one: the escaper turns a `[` in a filename into `\[`.

func RelativeExpiry

func RelativeExpiry(iso string, now time.Time) string

RelativeExpiry turns an RFC 3339 timestamp into "expires in 24h".

func Removed

func Removed(slug string, f Format, quiet, colour bool) string

Removed renders a completed takedown. There is no link left to paste, so markdown and url fall back to the JSON envelope.

func Run

func Run(r *api.Run, f Format, quiet, colour bool) string

Run renders a run without its artifacts, for `runs start` and `runs finish`.

func RunDetail

func RunDetail(r *api.Run, f Format, quiet, colour bool) string

RunDetail renders one run and everything recorded on it. Unlike Run, which reports what just happened to one, this is the whole record — the metadata included, since a run is where all of it lives and the registry keeps none on the artifacts themselves.

func RunList

func RunList(p *api.RunPage, l Listing, f Format, quiet, colour bool) string

RunList renders a page of a workspace's runs, newest first. There is no link to a run, so markdown and url fall back to the JSON envelope.

func StoredKey

func StoredKey(l *Login, f Format, quiet, colour bool) string

StoredKey renders what `auth login` just did.

func Upload

func Upload(r Result, f Format, quiet, colour bool, now time.Time) string

Upload renders a successful upload.

func WorkspaceList added in v0.2.0

func WorkspaceList(ws Workspaces, f Format, quiet, colour bool) string

WorkspaceList renders the store. There is no link to a workspace, so markdown and url fall back to the JSON envelope.

Types

type Authorization

type Authorization struct {
	Code string `json:"code"`
	Page string `json:"page"`
	// Opened says whether a browser was asked to open the page, so a caller knows
	// whether the person in front of it has to do that part themselves.
	Opened bool `json:"opened"`
}

Authorization is what a browser login says about itself while it is still waiting: the code to confirm, and the page to confirm it on.

It is not wrapped in an Envelope, and that is deliberate. An envelope carries `ok`, which is a verdict on a command that has finished — and this is written before the command knows whether it worked at all. An agent that read `ok: true` here would take a login that has not happened yet for one that has, so the shape is different on purpose and says what it is instead.

type Breadcrumb struct {
	// Action is the short verb phrase, for a menu or a log line.
	Action string `json:"action"`
	// Cmd is the whole command, ready to run once any <placeholder> in it has
	// been substituted.
	Cmd string `json:"cmd"`
	// Description says what running it achieves, and what happens if it is not
	// run when that is the point.
	Description string `json:"description"`
}

Breadcrumb is one call left to make, spelled out well enough to run without consulting anything: what it would do, the command that does it with this result's own slugs and tokens already in it, and why it is worth doing.

The three fields are a contract, so all three are always present. An agent reading this decides on the description and pastes the cmd — a breadcrumb that leaves either to be worked out is one it has to go and read the help for, which is the cost this exists to remove.

Cmd carries real arguments, never placeholders, wherever the result knows them. Where it genuinely does not — the run to attach a freshly claimed upload to is the caller's to choose, the file a new run is to be fed is not a thing this side has ever seen — the value is angle-bracketed so it cannot be mistaken for one. An angle-bracketed word is to be substituted before the command is run, and never pasted into a shell as it stands: `<` and `>` are redirection there, so a verbatim paste runs something other than what was suggested, and usually writes a file named after the placeholder.

The share crumb is the one whose Cmd is not a krowk command, for the reason given where it is built: there is no portable command for handing a link to a person, so it carries the link itself.

func AttachCrumb

func AttachCrumb(a *api.Artifact) Breadcrumb

AttachCrumb is the command that puts a claimed upload under a run. The run is the one argument this side cannot fill in: the caller holding the claim token is the one that knows which run the upload came from, and guessing at one would be a command that fails.

func ClaimCrumb

func ClaimCrumb(a *api.Artifact) Breadcrumb

ClaimCrumb is the command that keeps one anonymous upload, with its own token already in it. Exported because it is the breadcrumb the human format prints too, and the two must not drift into quoting different commands.

type ConfigView added in v0.2.0

type ConfigView struct {
	Workspace string `json:"workspace,omitempty"`
	// Sources says which layer each value came from, keyed by config key.
	Sources    map[string]string `json:"sources,omitempty"`
	GlobalPath string            `json:"global_path"`
	// RepoPath is empty outside a git repository, where there is no repo layer
	// to write to.
	RepoPath string `json:"repo_path,omitempty"`
}

ConfigView is the effective configuration as `krowk config show` reports it: each value alongside which layer said so, and where the two files live — including the one that does not exist yet, because that is where `config set` would write.

type Envelope

type Envelope struct {
	OK          bool           `json:"ok"`
	Data        any            `json:"data,omitempty"`
	Paste       *Paste         `json:"paste,omitempty"`
	Summary     string         `json:"summary,omitempty"`
	Breadcrumbs []Breadcrumb   `json:"breadcrumbs,omitempty"`
	Error       map[string]any `json:"error,omitempty"`
}

Envelope wraps every JSON result.

type Format

type Format string

Format is the shape of a rendered result.

const (
	Human    Format = "human"
	JSON     Format = "json"
	Markdown Format = "markdown"
	URL      Format = "url"
)

func ResolveFormat

func ResolveFormat(flag string, jsonFlag, isTTY bool) (Format, error)

ResolveFormat defaults to human on a terminal and JSON when piped, so an agent capturing stdout gets structured data without asking for it.

type Listing

type Listing struct {
	// Run names the run the page was scoped to, and is empty for a whole
	// workspace's listing.
	Run string
	// Limit is the caller's --limit, or zero when it did not pass one. Zero is
	// how the flag arrives unset, and is also what the registry reads as "use the
	// default", so it is the honest way to say "the caller chose nothing".
	Limit int
}

Listing is what scoped the page being rendered, so the command for the next one can be the same query with only the cursor moved.

type Login

type Login struct {
	Path      string `json:"path"`
	Confirmed bool   `json:"confirmed"`
	KeyID     string `json:"key_id,omitempty"`
	Workspace string `json:"workspace,omitempty"`
	// Reason says why the registry did not confirm the key, and is empty when it
	// did.
	Reason string `json:"reason,omitempty"`
	// Shadowed says KROWK_TOKEN is set, which outranks the file this login just
	// wrote. Without it the receipt would name a workspace the next upload does not
	// land in — the one fact a login is a receipt for, wrong.
	Shadowed bool `json:"shadowed_by_env,omitempty"`
}

Login is the result of storing a key: where it landed, and whether the registry confirmed it on the way in.

Confirmed is the field worth reading. A login that could not reach the registry still stores the token, so "it worked" and "it is going to work" are different answers, and an agent capturing stdout has no other way to tell them apart. There is no link to a login, so markdown and url fall back to the JSON envelope.

type Paste

type Paste struct {
	// Markdown embeds the image and links through to the artifact. This is
	// the form for GitHub, Linear and Notion.
	Markdown string `json:"markdown"`
	// URL is the bare link, for Slack and Basecamp, which unfurl it themselves.
	URL string `json:"url"`
}

Paste is one upload in the two forms its destinations need. There is no single paste-ready string: GitHub does not build preview cards for third-party links, so only the image embed shows the artifact there, while Slack renders no markdown image embeds at all and unfurls a bare URL into a card of its own. So the CLI carries both and says which is which.

func PasteFor

func PasteFor(a *api.Artifact, title string) Paste

PasteFor builds both forms for one artifact.

type Result

type Result struct {
	Artifacts []*api.Artifact `json:"artifacts"`
	Run       *api.Run        `json:"run,omitempty"`
	Notes     []string        `json:"notes,omitempty"`
	// Title is the label the caller asked for, kept so markdown output can use
	// it in place of a filename.
	Title string `json:"title,omitempty"`
}

Result is what one upload command produced: an artifact per file, and the run they were grouped under when there was one.

Notes carry what the caller should know that is not a failure — chiefly that a keyless upload had nowhere to put the run metadata it was given.

func (Result) Bytes

func (r Result) Bytes() int64

Bytes is the total across every artifact.

type TakenDown

type TakenDown struct {
	Slug      string `json:"slug"`
	TakenDown bool   `json:"taken_down"`
}

TakenDown is what a takedown leaves to report. The registry answers 204 and there is no artifact left to render — a url and markdown naming bytes that are gone would be a lie — so the slug and the fact are the whole result.

The fact is its own field rather than a `state`, because the API's `state` says whether an upload landed (pending, ready) and a tombstone keeps whichever it had. Spelling a takedown as a state would make the two disagree.

type Workspaces added in v0.2.0

type Workspaces struct {
	Stored []api.WorkspaceKey `json:"stored"`
	// Resolved is the workspace uploads from this directory land in, and Source
	// says which layer decided it — a flag, the environment, a config file, or
	// the store's own default. Empty when nothing resolves, which is the
	// anonymous case.
	Resolved string `json:"resolved,omitempty"`
	Source   string `json:"source,omitempty"`
	// Shadowed says KROWK_TOKEN is set, which outranks every stored key: the
	// resolution below names the key krowk would have picked, and the
	// environment is using a different one. Without this line the listing
	// would answer the question wrongly with confidence.
	Shadowed bool `json:"shadowed_by_env,omitempty"`
	// KeyMissing says the resolved workspace cannot actually produce a key, so
	// every upload here fails — the one qualification without which "uploads
	// from here land in X" would be a lie told to whoever reads the listing.
	KeyMissing bool `json:"key_missing,omitempty"`
}

Workspaces is what `krowk workspaces` reports: every key the machine holds, and which workspace a command run here would actually use. The two halves answer different questions — "what could I use" is the store, "what will be used" is the resolution — and an agent deciding whether to log in needs both.

Jump to

Keyboard shortcuts

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