Help us shape the future of Go! Take the 2026 Go Developer Survey

postcmd

package
v0.28.1 Latest Latest
Warning

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

Go to latest
Published: Oct 23, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Overview

Package postcmd is the posting state machine behind `keryx approve` and `keryx publish`/`publish all`/`publish due` (0055 R-POST/R-AUTH, 0060 D1–D7, 0061 D1–D4). It drives an article's announcement set (draft → approved → posted, per outlet: one piece on one platform), enforces the approval gate, the on-the-hour schedule and idempotency, and fans out across platforms via the publish seam. The publisher resolver, the enabled-check, the clock, the filesystem and the post enumerator are injected so the whole machine is unit-tested with a fake publisher and no network.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefaultHourFor added in v0.19.0

func DefaultHourFor(cfg config.Reader, plat social.Platform) (int, *time.Location)

DefaultHourFor reads `platforms.<p>.announce.hour` and `announce.timezone` (0060 D4): the hour is per platform so the day's announcements stagger, the zone is one project-wide key because the stagger is meaningful in the author's day and the pipeline runs in UTC. Missing keys mean 09:00 UTC.

func FormatLines added in v0.5.0

func FormatLines(results []Result) []string

FormatLines renders per-outlet outcomes as human lines, sorted by platform — the shared formatter for both the CLI (FormatResults) and the studio post job.

func FormatResults

func FormatResults(w io.Writer, results []Result)

FormatResults renders per-outlet results as text lines.

func Refusal added in v0.21.0

func Refusal(accepts publish.Accepts, kind social.Kind, img *social.Image) string

Refusal is the pre-flight on shape alone: the reason accepts would turn down a piece of this kind (a reel carries a video, a post at most an image, with its public URL when it has one), or "" when it takes it. The studio greys a platform's tab with it before anyone composes copy that can never be sent (0061 D4).

Types

type ApproveOptions added in v0.19.0

type ApproveOptions struct {
	// At is the due time: RFC3339 on the hour, or a bare date that takes the
	// platform's default hour. Empty takes the article's date at that hour, or,
	// when the article has no date, is due immediately.
	At string
	// Revoke returns the outlets to draft and clears their schedule.
	Revoke bool
}

ApproveOptions schedules an approve.

type Poster

type Poster struct {
	FS   afero.Fs
	Root string // reel-workspace root (e.g. "reels")
	// Resolve constructs the publisher for a platform (nil-able adapters are an
	// error). Enabled reports `platforms.<name>.enabled`. Now is the clock.
	Resolve func(platform string) (publish.Publisher, error)
	Enabled func(platform string) bool
	// Contract reports a platform's media contract without constructing it
	// (0060 D3); nil falls back to the publish registry.
	Contract func(platform string) (publish.Accepts, bool)
	// Constraints resolves a platform's posting constraints (config-tunable, R-SOC-5);
	// nil falls back to the built-in defaults.
	Constraints func(platform social.Platform) social.Constraint
	Now         func() time.Time
	// DefaultHour is the hour of day an outlet on a platform is due when the
	// schedule names only a date, and the zone that hour is in (0060 D4); nil
	// means 09:00 UTC.
	DefaultHour func(platform social.Platform) (int, *time.Location)
	// ArticleDate is the article's own date (YYYY-MM-DD) for a bundle, "" when
	// unknown; an approve with no --at is due on that date (0060 D4). nil means
	// unknown.
	ArticleDate func(ctx context.Context, bundle string) string
	// Articles enumerates every article bundle in the project, for `publish due`
	// with no target (0060 D6). nil means the sweep is unavailable.
	Articles func(ctx context.Context) ([]string, error)
	// Hydrate pulls a reel workspace's pinned-but-missing media before its video
	// is read (spec 0039 §3.5, R-STO-7): on a fresh CI clone the rendered reel
	// lives in the object store. nil means no object store.
	Hydrate func(ctx context.Context, reelDir string) error
	// Reachable checks that a public image URL serves an image, for the
	// adapters that fetch by URL (Instagram, Threads): an undeployed cover is
	// refused before the send, so the platform never fetches a 404 and caches
	// it; a dry run only reports it. nil skips the check.
	Reachable func(ctx context.Context, url string) error
	// RequireCredentials makes a dry-run fail when the publisher cannot be
	// constructed. Off, the dry-run reports the credential as absent and passes,
	// which is what a merge-request pipeline with no secrets needs (0060 OQ2).
	RequireCredentials bool
}

Poster carries the injected dependencies for the posting machine.

func New

func New(ctx context.Context, p *props.Props) Poster

New builds a Poster wired to the live props: the publish registry (config- resolved), the platform-enabled + constraints config checks, the content reader, and the wall clock.

func NewFor added in v0.5.0

func NewFor(fs afero.Fs, stores publish.Stores, root string) Poster

NewFor builds a Poster over an explicit filesystem + config + reel root — the studio drives it against the active project (per-project fs/config), the CLI via New. Platform-enabled, constraints, default hours and the content reader resolve from stores.Config; credentials resolve from stores.Accounts when one was opened.

func (Poster) Approve

func (p Poster) Approve(ctx context.Context, t Target, platform string, opts ApproveOptions) ([]Result, error)

Approve sets the approval gate for a platform (or "all") in scope: it refuses any outlet whose copy has hard constraint violations (R-POST-10), sets the schedule on the hour (R-POST-11, R-ANN-4), refuses two outlets on one platform sharing an hour anywhere in the article (R-ANN-5), and persists. --revoke returns to draft.

func (Poster) Publish added in v0.19.0

func (p Poster) Publish(ctx context.Context, t Target, platform string, dryRun, force bool) ([]Result, error)

Publish sends one platform's outlets in scope (R-POST-1..4): refuses unless approved, is a no-op when already posted (unless force), and on success records posted/posted_at/post_url/post_id. dryRun validates without sending. A platform with several outlets in scope sends the earliest due and defers the rest (R-ANN-5).

func (Poster) PublishAll added in v0.19.0

func (p Poster) PublishAll(ctx context.Context, t Target, dryRun bool) ([]Result, error)

PublishAll sends every enabled + approved outlet in scope independently, recording each success even if another fails (R-POST-5..8); it returns an error if any failed.

func (Poster) PublishDue added in v0.19.0

func (p Poster) PublishDue(ctx context.Context, t Target, dryRun bool) ([]Result, error)

PublishDue sends the approved outlets whose hour has come (R-POST-12/13) — the unattended CI entrypoint, idempotent. With an empty Target it sweeps every article the project's content sources name (0060 D6).

func (Poster) PublishSubset added in v0.19.0

func (p Poster) PublishSubset(ctx context.Context, t Target, names []string, dryRun bool) ([]Result, error)

PublishSubset sends the named enabled + approved platforms — one load/save, the same per-platform resilience as PublishAll (a failure doesn't abort the rest). A named platform that isn't enabled+approved is simply not sent.

type Result

type Result struct {
	Article  string `json:"article,omitempty"`
	Piece    string `json:"piece,omitempty"`
	Kind     string `json:"kind,omitempty"`
	Platform string `json:"platform"`
	Status   string `json:"status"` // posted | skipped | deferred | failed | dry-run | approved | draft
	URL      string `json:"url,omitempty"`
	Detail   string `json:"detail,omitempty"`
}

Result is one outlet's posting outcome (for per-platform reporting / json).

type Target added in v0.19.0

type Target = social.Target

Target is what a command works on (social.Target): an article, one of its pieces, or a reel workspace through its bundle.

Jump to

Keyboard shortcuts

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