cliproto

package
v0.35.4 Latest Latest
Warning

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

Go to latest
Published: May 21, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package cliproto holds the contracts shared by the CLI, daemon, server, and desktop: error/exit codes, stdout printers, and IPC types.

Everything in this package mirrors docs/ERRORS.md and docs/WIRE.md. If you change a string here, change those docs too — and the test fixtures.

Index

Constants

View Source
const (
	DefaultRenderCols = 200
	DefaultRenderRows = 60
)

Default emulator dimensions for peek/read text rendering. Wide enough that most TUIs (Claude Code, htop, vim) won't reflow text awkwardly; tall enough to fit a typical scrollback target. If a wrap session ran at narrower dimensions, its cursor-positioning bytes still land at the right (col, row) inside this larger grid — they just leave more trailing whitespace.

Configurable later via flags / env / per-source metadata; hardcoded for now is fine since the goal is "more readable than nothing" rather than "byte-perfect replica of the original session".

View Source
const (
	IPCStatus = "Status"
	IPCLogin  = "Login"
	IPCCreate = "Create"
	IPCSwitch = "Switch"
	// IPCSend / IPCSendBatch — the publish-IPC verbs used by
	// `ppz send`, `ppz command`, terminal stdin forwarding, etc.
	// Renamed from IPCBroadcast/IPCBroadcastBatch in Phase 1.5 to
	// match the surviving user-facing verb (`ppz broadcast` itself
	// was removed in Phase 1 — locked decision #16).
	IPCSend      = "Send"
	IPCSendBatch = "SendBatch"

	IPCList          = "List"
	IPCListWatch     = "ListWatch"
	IPCSubscribe     = "Subscribe"
	IPCRead          = "Read"
	IPCConnect       = "Connect"
	IPCDisconnect    = "Disconnect"
	IPCPipeCreate    = "PipeCreate"
	IPCPipeDestroy   = "PipeDestroy"
	IPCSourceDestroy = "SourceDestroy"

	// Phase 1.5 namespace (manifold) state verbs. `ppz set namespace
	// PATH` / `ppz unset namespace` — symmetric with handle. No
	// IPCGetNamespace: `ppz status` carries it in StatusReply.
	IPCSetNamespace   = "SetNamespace"
	IPCUnsetNamespace = "UnsetNamespace"

	// Diag verb (Phase 0 — agent hardening). Returns the daemon's
	// recent NATS connection-state events for `ppz diagnostics`. Works
	// without credentials and without a live NATS connection — the
	// whole point is being able to introspect a sick daemon.
	IPCDiag = "Diag"

	// Who verb — `ppz who`. Returns the daemon's in-memory snapshot of
	// the most recent heartbeat per source handle. Client-side filters
	// and rendering happen in cmdWho; the daemon just dumps the cache.
	IPCWho = "Who"
)

IPC method names. Keep in sync with WIRE.md §7.

View Source
const (
	LoginCheckOK      = "ok"
	LoginCheckInvalid = "invalid"
)

LoginCheck values reported by StatusReply. Constants live in cliproto so both daemon (writer) and CLI (reader) share them without import cycles. "" is the zero value and means "not applicable" (e.g. no credentials stored).

Variables

This section is empty.

Functions

func ExitCode

func ExitCode(c Code) int

ExitCode returns the integer the CLI exits with for a given Code. Unknown codes map to 1 (generic error).

func FormatPipePath added in v0.31.0

func FormatPipePath(manifold, source, name string) string

FormatPipePath renders the four-role pipe path for user display, with empty slots omitted. Used by PrintPipeCreate, PrintPipeDestroy, and the `to=` field of send output.

func FormatReadMessage added in v0.24.0

func FormatReadMessage(w io.Writer, m ReadMessage, loc *time.Location)

FormatReadMessage renders one ReadMessage in the tabular default. Layout:

HH:MM:SS  <sender-col>  <body>
                        <continuation>
                        <continuation>

Body shape:

  • Subject starting with "ack:" → "<subject> → <last-8-hex-of-id>" (system protocol message — payload is ignored for display).
  • Subject otherwise non-empty → "[<subject>] <payload-line-1>".
  • Subject empty → "<payload-line-1>".

Multi-line payloads emit subsequent lines indented under the body column (no time, no sender). loc controls the timezone of HH:MM:SS — production callers pass time.Local.

func FprintError

func FprintError(w io.Writer, err *Error)

FprintError writes the standard one-line error to stderr.

func HTTPStatus

func HTTPStatus(c Code) int

HTTPStatus returns the HTTP status code for a given Code, or 500 if unknown.

func IsTabularReadPipe added in v0.24.0

func IsTabularReadPipe(pipe string) bool

IsTabularReadPipe reports whether `ppz read <handle>.<pipe>` should render in the v0.23 three-column tabular form by default. Only the human-visible inbox-shaped pipes do — pty pipes (stdout / stdin / stdctrl) and user-named custom pipes stay byte-faithful so replays work and arbitrary tooling on top doesn't get reflowed bytes.

func Message

func Message(c Code) string

Message is the standard human-readable message for a Code, per ERRORS.md.

func PrintBroadcast

func PrintBroadcast(w io.Writer, r SendReply)

func PrintConnect

func PrintConnect(w io.Writer, r ConnectReply)

func PrintCreate

func PrintCreate(w io.Writer, r CreateReply)

func PrintDaemonAlreadyRunning

func PrintDaemonAlreadyRunning(w io.Writer, pid int)

func PrintDaemonStarted

func PrintDaemonStarted(w io.Writer, pid int)

func PrintDisconnect

func PrintDisconnect(w io.Writer)

func PrintList

func PrintList(w io.Writer, sources []Source, iso bool)

PrintList renders sources as an aligned table with a header row. Default time format is relative duration ("5 minutes ago" / "just now"); pass iso=true for RFC3339 timestamps in the LAST column instead.

CREATOR is rightmost. PAYLOAD becomes a padded column (it used to be trailing un-padded, but CREATOR now needs vertical alignment so PAYLOAD pads to its widest preview — bounded at 60 chars by TruncatePayload).

func PrintListJSON

func PrintListJSON(w io.Writer, sources []Source)

PrintListJSON emits one JSON object per (source, pipe) row in the same shape as the API + a `last_at` ISO string. Full untruncated payload — `ppz ls` is the only path that surfaces the latest payload without going through `ppz read`, so agents reading --json get the real bytes.

`creator` carries the same username the table shows: pipe-level if set, otherwise the source's creator (auto-pipe inheritance).

func PrintListJSONWithUncollared added in v0.31.0

func PrintListJSONWithUncollared(w io.Writer, sources []Source, uncollared []UncollaredPipe)

PrintListJSONWithUncollared is the JSON variant including uncollared pipes. Phase 1.5. The `namespace` key carries the row's manifold (empty string for root) and mirrors the NAMESPACE table column — present on every row shape so JSON consumers don't have to special- case collared vs uncollared.

func PrintListWithUncollared added in v0.31.0

func PrintListWithUncollared(w io.Writer, sources []Source, uncollared []UncollaredPipe, iso bool)

PrintListWithUncollared renders the same table as PrintList but also includes uncollared (sourceless) pipes. Phase 1.5.

NAMESPACE owns the manifold for both row shapes; PIPE carries only `<handle>.<pipe>` (collared) or `<pipe>` (uncollared) — the manifold prefix never appears in PIPE.

func PrintLogin

func PrintLogin(w io.Writer, r LoginReply)

func PrintPipeCreate

func PrintPipeCreate(w io.Writer, r PipeCreateReply)

PrintPipeCreate prints the pinned line:

created pipe=<PATH> retention=ttl=<dur>,msgs=<n>,bytes=<b>

PATH is the four-role path with empty slots omitted (Phase 1.5):

  • Collared root: cindy.archive
  • Collared with manifold: team1.cindy.archive
  • Uncollared root: room
  • Uncollared manifold: team1.room

`dur` is rendered via time.Duration's String() so 24h/168h round-trip without manual zero-pad. `bytes` is the raw integer.

func PrintPipeDestroy

func PrintPipeDestroy(w io.Writer, r PipeDestroyReply)

func PrintSourceDestroy added in v0.21.0

func PrintSourceDestroy(w io.Writer, r SourceDestroyReply)

func PrintStatus

func PrintStatus(w io.Writer, s StatusReply)

func PrintStatusWithEnv

func PrintStatusWithEnv(w io.Writer, s StatusReply, envCurrent, currentJsonPath string, color bool)

PrintStatusWithEnv prints `ppz status`. Four states map to the top `daemon:` line; the rest of the body depends on which state we're in:

  • not running: just the one line.
  • not logged in: pid + a hint pointing at `ppz login`.
  • authentication error: pid + server URL + a hint to refresh the credential. The daemon learned this from a server call returning E_INVALID_API_KEY.
  • logged in: pid + server URL + org name + current source.

envCurrent / currentJsonPath are the CLI-side facts surfaced for the env-vs-daemon disagreement annotation (see source-current-env-precedence for the contract). Only consulted when we're in the "logged in" state.

color toggles ANSI colour escapes. The CLI flips it on for interactive stdout and off for pipes / NO_COLOR / e2e fixtures.

func PrintStatusWithEnvAndCLIVersion added in v0.19.0

func PrintStatusWithEnvAndCLIVersion(w io.Writer, s StatusReply, envCurrent, currentJsonPath string, color bool, cliVersion string)

func PrintStatusWithUpdateInfo added in v0.31.11

func PrintStatusWithUpdateInfo(w io.Writer, s StatusReply, envCurrent, currentJsonPath string, color bool, cliVersion string, updateAvailable bool)

PrintStatusWithUpdateInfo is the canonical `ppz status` printer. updateAvailable is the caller's resolved view of "is the CLI behind the published manifest" — when true AND daemon == CLI, the daemon line renders amber and recommends `ppz upgrade`. Out-of-sync wins over update-available (restart first, upgrade after); see status_test TestPrintStatus_RedOutOfSyncTakesPriorityOverUpdate.

func PrintSwitch

func PrintSwitch(w io.Writer, r SwitchReply)

func RelativeTime

func RelativeTime(t, now time.Time) string

RelativeTime renders the gap from t→now as a coarse human duration: "just now" / "N seconds ago" / "N minutes ago" / "N hours ago" / "N days ago". Used in `ppz ls` output and the server GUI org page so operators see freshness at a glance without parsing absolute timestamps. Negative durations (clock skew, future timestamps) clamp to "just now".

func RenderTerminal

func RenderTerminal(in []byte, cols, rows int) string

RenderTerminal feeds raw PTY bytes through a virtual terminal of the given dimensions, then dumps the resulting screen as plain-text rows.

The bytes MUST be a single contiguous buffer — vt10x's `Write` is UTF-8-boundary-sensitive and can lose intermediate bytes if a multi- byte rune straddles two `Write` calls. Callers feeding chunked input (multiple JetStream messages, multiple PTY reads, etc.) should concatenate first, then call this once.

Behaviour:

  • Cursor moves resolve into cell positions; OSC/CSI/charset noise is consumed by the emulator (zero leakage in the output).
  • Each row's trailing whitespace is trimmed.
  • Trailing all-blank rows are dropped so a mostly-empty screen produces a short snapshot rather than 60 newlines.
  • Output ends with a single trailing `\n` if it's non-empty.
  • Empty input produces an empty string.

Limitation: alternate-screen state. If the captured byte stream is from a TUI that is currently *on* alt-screen (Claude Code, vim mid- session), vt10x renders the alt-screen content — which is what you want. If the TUI exited and switched back to main screen, you get the main-screen content (typically the shell prompt). This matches what `cat session.bin` would render in your terminal.

func TerminalWidth added in v0.31.5

func TerminalWidth() int

TerminalWidth reports the caller's terminal width in columns, suitable for layout decisions in `ppz ls`, `ppz --help`, and any other adaptive renderer. Resolution order:

  1. COLUMNS env var — the conventional override, honoured first because it's the only knob that works for pipes / scripts / unit tests where stdout isn't a tty.
  2. term.GetSize(stdout) — actual tty width when running interactively.
  3. defaultWidth (80) — universally compatible fallback when neither signal is available.

func TruncatePayload

func TruncatePayload(s string) string

TruncatePayload renders a payload for display in `ppz ls`: strip ANSI CSI escapes (ESC `[` … final-byte) and all C0 controls + DEL so the preview can never alter the user's terminal state, replace newlines with spaces, trim trailing whitespace, then cap at 60 bytes (UTF-8 safe).

Without this, a wrapped terminal's last .stdout chunk — typically a shell prompt with cursor moves and colour escapes — would render verbatim mid-listing and clear the screen, set bold, etc.

Types

type AuthExchangeReply

type AuthExchangeReply struct {
	JWT         string    `json:"jwt"`
	NATSURL     string    `json:"nats_url"`
	AccountID   string    `json:"account_id"`
	AccountName string    `json:"account_name"`
	ExpiresAt   time.Time `json:"expires_at"`

	// Auth V2 Phase 3 — short-lived NATS user credentials. The daemon
	// uses NATSUserJWT + NATSUserSeed in nats.UserJWT(...) when
	// connecting to the NATS server URL. Re-fetch before ExpiresAt
	// (currently 5min) by re-running /auth/exchange with the same
	// bearer.
	NATSUserJWT  string `json:"nats_user_jwt"`
	NATSUserSeed string `json:"nats_user_seed"`
}

type AuthExchangeRequest

type AuthExchangeRequest struct {
	APIKey string `json:"api_key"`
	// AccountID (Phase 3.5): which org's account to mint a User JWT in.
	// Optional — server defaults to the bearer's primary org (first
	// owned, or first member). Multi-org users specify it explicitly
	// to switch which org their daemon talks to.
	AccountID string `json:"account_id,omitempty"`
}

type Code

type Code string

Code is a stable error identifier. Strings are part of the wire contract.

const (
	ENotLoggedIn       Code = "E_NOT_LOGGED_IN"
	EDaemonNotRunning  Code = "E_DAEMON_NOT_RUNNING"
	EInvalidAPIKey     Code = "E_INVALID_API_KEY"
	ESourceTaken       Code = "E_SOURCE_TAKEN"
	ESourceNotFound    Code = "E_SOURCE_NOT_FOUND"
	EInvalidHandle     Code = "E_INVALID_HANDLE"
	ENoCurrentSource   Code = "E_NO_CURRENT_SOURCE"
	EPayloadTooLarge   Code = "E_PAYLOAD_TOO_LARGE"
	EServerUnreachable Code = "E_SERVER_UNREACHABLE"
	ENATSUnreachable   Code = "E_NATS_UNREACHABLE"
	EInvalidPipe       Code = "E_INVALID_PIPE"
	EPipeTaken         Code = "E_PIPE_TAKEN"
	EPipeNotFound      Code = "E_PIPE_NOT_FOUND"
	// EInvalidSubject is returned when a caller (CLI flag parser or IPC
	// client) tries to set a Subject value that violates the reserved-
	// prefix invariant. Daemon-emitted protocol messages own the `ack:`
	// prefix; users cannot stamp it themselves.
	EInvalidSubject Code = "E_INVALID_SUBJECT"

	// EInvalidManifold is returned when a manifold (hierarchical-grouping
	// path) segment fails validation. Each dot-separated segment must
	// match the handle regex (lowercase alnum + hyphens, max 32, no
	// leading/trailing hyphen). Phase 1.5.
	EInvalidManifold Code = "E_INVALID_MANIFOLD"

	// ENameTaken — Phase 1.5.1 first-wins collision rule. Within a
	// manifold, user-typed names share a namespace across source
	// handles and uncollared pipe names; a source at manifold M also
	// reserves the manifold-prefix path M.<handle> from any uncollared
	// pipe creation. Surfaces when a create would conflict with an
	// existing row of either shape.
	ENameTaken Code = "E_NAME_TAKEN"
)

type ConnectReply

type ConnectReply struct {
	Handle string `json:"handle"`
}

type ConnectRequest

type ConnectRequest struct {
	Handle  string `json:"handle"`
	Session string `json:"session,omitempty"`
}

ConnectRequest is the input to `ppz connect <handle>`. The daemon ensures the source exists (idempotent — pre-existing source is fine), then sets it as `current`.

type CreateInviteReply added in v0.22.0

type CreateInviteReply struct {
	Invite Invite `json:"invite"`
}

type CreateInviteRequest added in v0.22.0

type CreateInviteRequest struct {
	Username string `json:"username"`
}

CreateInviteRequest is the body for POST /api/v1/orgs/{slug}/invites and the /orgs/{id}/invites GUI form. Owner-only — handlers gate.

type CreateReply

type CreateReply struct {
	Handle   string   `json:"handle"`
	Manifold string   `json:"manifold,omitempty"` // Phase 1.5.1
	Subject  string   `json:"subject"`
	Pipes    []string `json:"pipes,omitempty"` // pipe names provisioned for this source
}

type CreateRequest

type CreateRequest struct {
	Handle   string `json:"handle"`
	Kind     string `json:"kind,omitempty"`     // "message" (default) or "pty"
	Manifold string `json:"manifold,omitempty"` // Phase 1.5.1: namespace-aware source create
	Session  string `json:"session,omitempty"`  // sets per-session current after create
}

type CreateSourceReply

type CreateSourceReply struct {
	ID        string    `json:"id"`
	Handle    string    `json:"handle"`
	Manifold  string    `json:"manifold,omitempty"` // Phase 1.5.1
	Kind      string    `json:"kind"`
	Subject   string    `json:"subject"`
	CreatedAt time.Time `json:"created_at"`
}

type CreateSourceRequest

type CreateSourceRequest struct {
	Handle   string `json:"handle"`
	Kind     string `json:"kind,omitempty"`     // "message" (default) or "pty"
	Manifold string `json:"manifold,omitempty"` // Phase 1.5.1: source lives at this manifold
}

type DiagEvent added in v0.26.0

type DiagEvent struct {
	Type   string    `json:"type"`
	At     time.Time `json:"at"`
	Reason string    `json:"reason,omitempty"`
}

DiagEvent is one connection-state transition in DiagReply. Fields mirror the daemon's NATSEvent struct (kept as a separate type so the IPC contract is independent of internal storage).

type DiagReply added in v0.26.0

type DiagReply struct {
	NATSState         string      `json:"nats_state,omitempty"`
	NATSDropsLastHour int         `json:"nats_drops_last_hour,omitempty"`
	NATSEvents        []DiagEvent `json:"nats_events"`
}

DiagReply carries the daemon's introspection snapshot. Phase 0: just the NATS connection state + recent connection-state events. Future phases will extend with refresh-loop state, JetStream consumer lag, etc.

type DiagRequest added in v0.26.0

type DiagRequest struct{}

DiagRequest is the input to `ppz diagnostics` — currently empty. Reserved for future scoping flags (per-subsystem filters, since-when, etc.).

type DisconnectReply

type DisconnectReply struct{}

DisconnectReply is empty — the only outcome of disconnect is "current" being cleared on the daemon side. Returns no fields.

type DisconnectRequest

type DisconnectRequest struct {
	Session string `json:"session,omitempty"`
}

DisconnectRequest carries the session id whose current source should be cleared. Empty Session normalises to "default".

type Error

type Error struct {
	Code    Code   `json:"code"`
	Message string `json:"message"`
}

Error is a wire-level error carrying a Code and an arbitrary message.

func New

func New(c Code) *Error

New builds an *Error using the standard Message for the code.

func NewInvalidHandle

func NewInvalidHandle(handle string) *Error

NewInvalidHandle: invalid handle 'BAD'

func NewInvalidPipeName

func NewInvalidPipeName(name string) *Error

NewInvalidPipeName: pipe name 'X' is invalid (regex rejection / etc.)

func NewInvalidPipeReserved

func NewInvalidPipeReserved(name string) *Error

NewInvalidPipeReserved: pipe name 'system' is reserved

func NewManifoldReservedBySource added in v0.31.1

func NewManifoldReservedBySource(prefix, sourceHandle, sourceManifold string) *Error

NewManifoldReservedBySource: manifold path 'team1' is reserved by source 'team1' at root. Phase 1.5.1 — source X at manifold M reserves the manifold-prefix path M.X because the source's auto-pipes already live at those subjects. `prefix` is the colliding manifold path (e.g. "team1.subteam"). `sourceHandle` is the source's bare name (e.g. "subteam"). `sourceManifold` is where the source lives (e.g. "team1", or "" for root).

func NewNameTakenBySource added in v0.31.1

func NewNameTakenBySource(name, manifold string) *Error

NewNameTakenBySource: name 'foo' is already taken by source at root (or at manifold 'team-a'). Phase 1.5.1 collision rule.

func NewNameTakenByUncollaredPipe added in v0.31.1

func NewNameTakenByUncollaredPipe(name, manifold string) *Error

NewNameTakenByUncollaredPipe: name 'foo' is already taken by uncollared pipe at root (or manifold). Phase 1.5.1 collision rule.

func NewPipeNotFound

func NewPipeNotFound(pipe, handle string) *Error

NewPipeNotFound: pipe 'archive' not found on source 'foo'

func NewPipeTaken

func NewPipeTaken(pipe, handle string) *Error

NewPipeTaken: pipe 'archive' already exists on source 'foo'

func NewSourceNotFound

func NewSourceNotFound(handle string) *Error

NewSourceNotFound: source 'foo' not found

func NewSourceTaken

func NewSourceTaken(handle string) *Error

NewSourceTaken: source 'foo' already exists in this account

func NewUncollaredPipeNotFound added in v0.31.0

func NewUncollaredPipeNotFound(name, manifold string) *Error

NewUncollaredPipeNotFound: uncollared pipe 'room' not found at manifold ” (or at manifold 'team-a'). Phase 1.5 — avoids rendering an empty "on source ”" tail for sourceless pipes.

func NewUncollaredPipeTaken added in v0.31.1

func NewUncollaredPipeTaken(name, manifold string) *Error

NewUncollaredPipeTaken: uncollared pipe 'archive' already exists at root (or at manifold 'team-a'). Phase 1.5.1 — companion to NewUncollaredPipeNotFound.

func (*Error) Error

func (e *Error) Error() string

type HTTPError

type HTTPError struct {
	Error Error `json:"error"`
}

HTTPError is the body shape of a non-2xx HTTP response.

type Invite added in v0.22.0

type Invite struct {
	ID              string `json:"id"`
	AccountID       string `json:"account_id"`
	AccountName     string `json:"account_name,omitempty"`
	InviteeUsername string `json:"invitee_username"`
	InviterUserID   string `json:"inviter_user_id"`
	Status          string `json:"status"`
	CreatedAt       string `json:"created_at"`
	DecidedAt       string `json:"decided_at,omitempty"`
}

Invite is the API projection of a db.Invite row plus the account name (so the dashboard can render "Pending invitation to <account>" without a second join).

type ListInvitesReply added in v0.22.0

type ListInvitesReply struct {
	Invites []Invite `json:"invites"`
}

type ListReply

type ListReply struct {
	Sources []Source `json:"sources"`
	// Phase 1.5: uncollared (sourceless) pipes — pipes with source_id IS
	// NULL. Walking sources alone misses them. The daemon enriches each
	// row with JetStream stats the same way it does PipeInfos.
	UncollaredPipes []UncollaredPipe `json:"uncollared_pipes,omitempty"`
}

type ListRequest

type ListRequest struct {
	Session string `json:"session,omitempty"` // cursor key
}

type ListSourcesReply

type ListSourcesReply struct {
	Sources []Source `json:"sources"`
}

type ListUncollaredPipesReply added in v0.31.0

type ListUncollaredPipesReply struct {
	Pipes []UncollaredPipeListEntry `json:"pipes"`
}

ListUncollaredPipesReply is the server response for GET /api/v1/pipes (uncollared listing). One entry per sourceless pipe in the account. Phase 1.5.

type ListWatchRequest

type ListWatchRequest struct {
	Session  string   `json:"session,omitempty"`
	Patterns []string `json:"patterns,omitempty"`
}

ListWatchRequest is `ppz ls --watch`. The daemon returns the same shape as ListReply, but only after the calling session has at least one unread message on a pipe whose handle matches one of Patterns (or any handle if Patterns is empty).

Semantics: level-triggered. If unread > 0 already at call time on a matching handle, the daemon returns immediately. Otherwise it blocks until a new message arrives on a matching subject, then returns.

Patterns are filepath.Match-style globs (`*`, `?`, `[abc]`) matched against the handle (not handle.pipe). Multiple patterns OR-combine.

type LoginReply

type LoginReply struct {
	URL       string `json:"url"`
	KeyPrefix string `json:"key_prefix"`
	AccountID string `json:"account_id"`
}

type LoginRequest

type LoginRequest struct {
	URL    string `json:"url"`
	APIKey string `json:"api_key"`
}

type PipeCreateReply

type PipeCreateReply struct {
	Handle     string `json:"handle"`
	Manifold   string `json:"manifold,omitempty"` // Phase 1.5
	Name       string `json:"name"`
	StreamName string `json:"stream_name"`
	TTLSeconds int    `json:"ttl_seconds"`
	MaxMsgs    int    `json:"max_msgs"`
	MaxBytes   int64  `json:"max_bytes"`
}

PipeCreateReply mirrors the server's resolved retention (after defaults are filled in) so the CLI prints exactly what was provisioned.

type PipeCreateRequest

type PipeCreateRequest struct {
	Handle       string  `json:"handle"`
	Manifold     string  `json:"manifold,omitempty"`
	SourceHandle *string `json:"source_handle,omitempty"`
	Name         string  `json:"name"`
	TTLSeconds   *int    `json:"ttl_seconds,omitempty"`
	MaxMsgs      *int    `json:"max_msgs,omitempty"`
	MaxBytes     *int64  `json:"max_bytes,omitempty"`

	// Session is set by the CLI for daemon-side manifold lookup; the IPC
	// transport drops it before forwarding to the server. Phase 1.5.
	Session string `json:"session,omitempty"`
}

PipeCreateRequest is the input to `ppz pipe create <name>` — and the body of POST /api/v1/sources/{handle}/pipes and POST /api/v1/pipes (Phase 1.5 sourceless form). Retention overrides are pointers so "absent" (= use default) is distinguishable from "explicitly zero".

Phase 1.5 fields per locked decision #18 four-role grammar:

  • Manifold: hierarchical-grouping segment string (” = root)
  • SourceHandle: actor identity name; nil = uncollared (sourceless)

Handle is retained as a backward-compat alias for SourceHandle until Cycle B finishes threading the new fields through the daemon and CLI; Cycle E's docs commit removes it.

type PipeDestroyReply

type PipeDestroyReply struct {
	Handle   string `json:"handle"`
	Manifold string `json:"manifold,omitempty"` // Phase 1.5: present for uncollared destroys
	Name     string `json:"name"`
}

type PipeDestroyRequest

type PipeDestroyRequest struct {
	Handle string `json:"handle"`
	Name   string `json:"name"`
	// Phase 1.5: BareTarget set by the CLI when the user typed
	// `ppz pipe destroy LEAF` without a dot. Daemon resolves as an
	// uncollared pipe at the session's current namespace.
	BareTarget string `json:"bare_target,omitempty"`
	Session    string `json:"session,omitempty"`
	// Manifold is set by callers that already know the target pipe's
	// manifold (e.g. the glob path needs to destroy uncollared pipes
	// across manifolds, not just the session's). When empty, the
	// daemon falls back to the session's current_namespace.
	Manifold string `json:"manifold,omitempty"`
}

type PipeInfo

type PipeInfo struct {
	Pipe      string     `json:"pipe"`
	Total     uint64     `json:"total"`
	Unread    uint64     `json:"unread"`
	LastSeq   uint64     `json:"last_seq,omitempty"`
	LastAt    *time.Time `json:"last_at,omitempty"`
	Preview   string     `json:"preview,omitempty"`    // truncated to 60 bytes for table view
	Payload   string     `json:"payload,omitempty"`    // full untruncated payload for `ls --json`
	CreatedBy string     `json:"created_by,omitempty"` // username; empty → inherit Source.CreatedBy
}

PipeInfo is per-pipe state surfaced by ppz ls. Total + LastSeq come from the JetStream stream's Info; Unread is computed daemon-side from the session's cursor file. Preview is the most recent payload truncated to 60 chars (UTF-8 safe; ANSI CSI sequences and C0 controls stripped).

CreatedBy is the username of the user who created this pipe. Empty for auto-provisioned pipes (broadcast / inbox / stdin / stdout / stdctrl) — the renderer falls back to the source's CreatedBy when this field is empty so CREATOR is never blank in the output. omitempty keeps the wire shape clean when a daemon-side intermediate doesn't carry the join.

type ReadEvent

type ReadEvent struct {
	Message *ReadMessage `json:"msg,omitempty"`
	Error   *Error       `json:"error,omitempty"`
	// Meta is an optional leading event (sent before any Message events)
	// carrying out-of-band stream metadata — currently the source pty's
	// dimensions for `<h>.stdout` reads, sourced from the latest
	// `<h>.stdctrl` resize. Lets the CLI configure its --tty renderer
	// to match the source size before consuming bytes.
	Meta *ReadMeta `json:"meta,omitempty"`
}

ReadEvent is the wire format of one streamed line in a Read response. Exactly one of Message / Error / Meta is set on each event. End-of-stream is signaled by the daemon closing the connection.

type ReadMessage

type ReadMessage struct {
	ID           string `json:"id"`
	Sender       string `json:"sender"`
	Subject      string `json:"subject"`
	Payload      string `json:"payload"`
	CreatedAt    string `json:"created_at"`
	InReplyTo    string `json:"in_reply_to"`
	AckRequested bool   `json:"ack_requested"`
}

ReadMessage is the daemon's serialized form of one envelope. The CLI formats this as either bare payload text or a JSON object depending on whether --json was passed.

Sender mirrors the envelope's `sender` (publisher's current source at publish time). Subject mirrors the envelope's `subject` (optional header-line, free-form for users / `ack:*` for system messages). Both are empty for legacy retained messages published before v0.23.0 (those carried `handle` instead, which is now silently dropped on parse).

type ReadMeta

type ReadMeta struct {
	Cols int `json:"cols,omitempty"`
	Rows int `json:"rows,omitempty"`
}

ReadMeta carries leading metadata about the stream. Currently a dimension hint for terminal renders; future fields can join here (cwd, exit code, last activity ts, etc.) without breaking older CLI builds (unknown JSON fields are ignored).

type ReadRequest

type ReadRequest struct {
	Handle    string `json:"handle"`
	Channel   string `json:"channel"`            // pipe name: broadcast / stdin / stdout
	Limit     int    `json:"limit,omitempty"`    // 0 = unlimited; non-zero = tail-N (reread only)
	Skip      int    `json:"skip,omitempty"`     // drop the first N retained messages (reread only)
	SinceMS   int64  `json:"since_ms,omitempty"` // 0 = no time filter; >0 = only msgs newer than (now − this many ms) (reread only)
	JSON      bool   `json:"json,omitempty"`     // emit envelope as JSON instead of payload text
	Follow    bool   `json:"follow,omitempty"`
	Session   string `json:"session,omitempty"`    // cursor key — defaults to "default" daemon-side
	NoAdvance bool   `json:"no_advance,omitempty"` // observational reads (terminal view) skip cursor advance
	All       bool   `json:"all,omitempty"`        // forensic mode (`reread`): ignore the cursor (deliver everything) and don't advance it. Implies NoAdvance.

	// Phase 1.5: BareTarget carries the raw user input when `ppz
	// read/reread LEAF` was bare. The daemon resolves it as an
	// uncollared pipe at the session's current_namespace. Handle and
	// Channel are empty in this case.
	BareTarget string `json:"bare_target,omitempty"`
}

ReadRequest carries the parsed `ppz read` parameters from the CLI to the daemon. The daemon streams ReadEvent JSON lines back on the same connection; the CLI reads until EOF (or until SIGINT closes the socket).

The `channel` JSON tag is preserved for IPC backward-compat within the Phase A rename — it carries a pipe name (sub-bucket). Phase B reorganises IPC field names alongside the verb refactor.

type SendBatchReply added in v0.31.0

type SendBatchReply struct {
	IDs     []string `json:"ids"`
	Subject string   `json:"subject"`
	Bytes   []int    `json:"bytes"`
}

SendBatchReply mirrors SendReply but as parallel arrays, one entry per published payload. IDs[i] / Bytes[i] correspond to Payloads[i] in the request. Subject is shared across the batch (all messages land on the same handle.pipe).

type SendBatchRequest added in v0.31.0

type SendBatchRequest struct {
	Handle     string   `json:"handle,omitempty"`
	Channel    string   `json:"channel,omitempty"`
	BareTarget string   `json:"bare_target,omitempty"` // Phase 1.5: see SendRequest.BareTarget
	Payloads   []string `json:"payloads"`
	Session    string   `json:"session,omitempty"`
}

SendBatchRequest publishes N payloads in one IPC round-trip. Used by streaming producers (terminal share's stdout drain, `ppz broadcast` line-streaming) where the per-call NATS round- trip cost dominates throughput under WAN. Validation runs once for the whole batch; the daemon issues N async nc.Publish calls followed by ONE nc.Flush, then replies with N ids — preserving the same "bytes confirmed at server" contract as the single IPCSend call, just amortised across the batch.

type SendReply added in v0.31.0

type SendReply struct {
	ID      string `json:"id"`
	Subject string `json:"subject"`
	Bytes   int    `json:"bytes"`
}

type SendRequest added in v0.31.0

type SendRequest struct {
	// Optional explicit target. If both empty, daemon publishes to its
	// current source on .broadcast. If Handle is set, publishes to
	// <Handle>.<Channel|"broadcast">. Used by `ppz send` and by
	// `ppz broadcast` when PPZ_CURRENT_HANDLE is exported (e.g. inside
	// a `ppz terminal` child).
	//
	// `channel` JSON tag preserved for IPC backward-compat within Phase A;
	// it carries a pipe name. Phase B reorganises.
	Handle  string `json:"handle,omitempty"`
	Channel string `json:"channel,omitempty"`
	Payload string `json:"payload"`
	// MsgSubject is an optional envelope-level subject (header-line). Free-
	// form for users (set via `ppz send --subject`); subjects starting with
	// `ack:` are reserved for daemon-internal protocol messages (ack
	// emission) and rejected at the IPC trust boundary in handleSend.
	MsgSubject string `json:"msg_subject,omitempty"`
	// InReplyTo / AckRequested mirror the new envelope fields (v0.25.0).
	// JSON tags align with the envelope (`in_reply_to`, `ack_requested`)
	// rather than the older `msg_subject` precedent — these are 1:1 with
	// envelope fields.
	InReplyTo    string `json:"in_reply_to,omitempty"`
	AckRequested bool   `json:"ack_requested,omitempty"`
	// Session keys the per-session current-source fallback when neither
	// Handle nor PPZ_CURRENT_HANDLE is set.
	Session string `json:"session,omitempty"`

	// Phase 1.5: BareTarget carries the raw target string when the user
	// typed `ppz send LEAF` without a dot. The CLI mangles the bare form
	// to {Handle: LEAF, Channel: "inbox"} for backward compat with the
	// collared recipient.inbox shorthand; BareTarget lets the daemon
	// recognise the original bare form and fall back to uncollared pipe
	// resolution if the source handle lookup misses. Empty when the user
	// typed an explicit `<H>.<P>` form.
	BareTarget string `json:"bare_target,omitempty"`
}

type SetNamespaceReply added in v0.31.0

type SetNamespaceReply struct {
	Namespace string `json:"namespace"`
}

type SetNamespaceRequest added in v0.31.0

type SetNamespaceRequest struct {
	Namespace string `json:"namespace"` // dot-separated path; ” = root (clear)
	Session   string `json:"session,omitempty"`
}

type Source

type Source struct {
	Handle   string `json:"handle"`
	Manifold string `json:"manifold,omitempty"` // Phase 1.5.1
	Kind     string `json:"kind,omitempty"`
	// Pipes is the list of user-created pipe names on this source (NOT the
	// auto-provisioned set — derive those from Kind). Set by the server's
	// /api/v1/sources response.
	Pipes     []string   `json:"pipes,omitempty"`
	PipeInfos []PipeInfo `json:"pipe_infos,omitempty"`
	CreatedBy string     `json:"created_by,omitempty"`
	// Legacy broadcast-only summary; kept for the GUI handlers that still
	// read these directly from the postgres-backed sources table.
	LastBroadcastAt      *time.Time `json:"last_broadcast_at,omitempty"`
	LastBroadcastPayload *string    `json:"last_broadcast_payload,omitempty"`
}

Source carries the source-level fields ppz ls renders. CreatedBy is the username of the user who created the source; populated server-side by joining sources.created_by_user_id to users.username.

type SourceDestroyReply added in v0.21.0

type SourceDestroyReply struct {
	Handle   string `json:"handle"`
	Manifold string `json:"manifold,omitempty"` // Phase 1.5.2: render manifold.handle in display
}

type SourceDestroyRequest added in v0.21.0

type SourceDestroyRequest struct {
	Handle string `json:"handle"`
}

type SourceKind

type SourceKind string

Source kinds, mirrored from internal/db so non-db callers can use them.

const (
	KindMessage SourceKind = "message"
	KindPTY     SourceKind = "pty"
)

type StatusReply

type StatusReply struct {
	DaemonPID          int        `json:"daemon_pid"`
	DaemonVersion      string     `json:"daemon_version,omitempty"`
	LoggedIn           bool       `json:"logged_in"`
	URL                string     `json:"url,omitempty"`
	KeyPrefix          string     `json:"key_prefix,omitempty"`
	AccountID          string     `json:"account_id,omitempty"`
	AccountName        string     `json:"account_name,omitempty"`
	LastTokenRefreshAt *time.Time `json:"last_token_refresh_at,omitempty"`
	// LoginCheck is the daemon's last verification result against the
	// server. "ok" means a recent server-touching call succeeded;
	// "invalid" means the server returned E_INVALID_API_KEY (key
	// revoked / rotated since login). Empty / "unknown" means we
	// haven't observed yet — status performs an active probe in that
	// case so the user always sees the truth.
	LoginCheck string `json:"login_check,omitempty"`
	Current    string `json:"current,omitempty"`
	// CurrentNamespace is the per-session manifold (Phase 1.5). Empty
	// when unset = root namespace. Rendered as a `namespace: …` line by
	// `ppz status`.
	CurrentNamespace string `json:"current_namespace,omitempty"`
	// CurrentPath is the daemon-side path to current.json — surfaced so
	// `ppz status`'s env/daemon-disagree warning can point users at the
	// actual file (which lives in the daemon's home, not the CLI's, when
	// they're separate processes).
	CurrentPath string `json:"current_path,omitempty"`
	// NATSState is one of "connected", "disconnected", "connecting" —
	// the daemon's current NATS connection state. Empty means
	// unobserved (no connection ever attempted, e.g. fresh daemon
	// pre-login). Drives the `nats:` line in `ppz status` output;
	// underlying event log is available via `ppz diagnostics`. (Phase 0 of
	// agent hardening, docs/WIRE.md §8.)
	NATSState string `json:"nats_state,omitempty"`
}

type StatusRequest

type StatusRequest struct {
	Session string `json:"session,omitempty"`
}

StatusRequest carries the caller's session id so the daemon can return the per-session current source. Empty Session normalises to "default".

type SwitchReply

type SwitchReply struct {
	Handle string `json:"handle"`
}

type SwitchRequest

type SwitchRequest struct {
	Handle  string `json:"handle"`
	Session string `json:"session,omitempty"`
}

type UncollaredPipe added in v0.31.0

type UncollaredPipe struct {
	Manifold string   `json:"manifold,omitempty"` // ” = root namespace
	Name     string   `json:"name"`
	Info     PipeInfo `json:"info"`
}

UncollaredPipe is the wire projection of a sourceless pipe row + its JetStream stats. Phase 1.5.

type UncollaredPipeListEntry added in v0.31.0

type UncollaredPipeListEntry struct {
	Manifold  string `json:"manifold,omitempty"`
	Name      string `json:"name"`
	CreatedBy string `json:"created_by,omitempty"`
}

type UnsetNamespaceReply added in v0.31.0

type UnsetNamespaceReply struct{}

type UnsetNamespaceRequest added in v0.31.0

type UnsetNamespaceRequest struct {
	Session string `json:"session,omitempty"`
}

type WhoEntry added in v0.33.0

type WhoEntry struct {
	Handle    string    `json:"handle"`
	Owner     string    `json:"owner,omitempty"`
	Payload   string    `json:"payload"`
	ArrivedAt time.Time `json:"arrived_at"`
}

WhoEntry is one row of the daemon's heartbeat cache. Payload is the verbatim heartbeat JSON the pty wrapper published; consumers (cmdWho) unmarshal it as HeartbeatPayload to extract harness/model/ host fields.

Owner is the username that owns the underlying source — resolved at query time from the server's source listing rather than embedded in the heartbeat payload, so transferring ownership server-side reflects in `ppz who` on the next call without restarting the agent. Empty when the daemon couldn't reach the server, or when the cache has a beat for a source the server no longer knows about.

type WhoReply added in v0.33.0

type WhoReply struct {
	Entries []WhoEntry `json:"entries"`
}

WhoReply carries the daemon's sorted-by-handle snapshot of every known heartbeat. Lifetime is the daemon process — a restart clears the cache and the next round of beats re-populates it.

type WhoRequest added in v0.33.0

type WhoRequest struct{}

WhoRequest is the input to `ppz who`. Empty for v1 — filters are applied client-side so the daemon stays a pure snapshot provider.

Jump to

Keyboard shortcuts

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