cli

package
v1.0.3 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

Package cli implements the moth binary's remote-client mode: named contexts (server URL + personal access token) stored in ~/.config/moth/config.toml, connect clients for the moth.admin.v1 services authenticated by that token, and the declarative dump/apply logic built on ProjectSpec.

Index

Constants

This section is empty.

Variables

View Source
var ErrInitAborted = errors.New("aborted — nothing was created")

ErrInitAborted is returned when the operator abandons the wizard at the final confirmation. Nothing has been created at that point: the wizard holds every answer client-side and only writes after the confirm.

Functions

func ConfigPath

func ConfigPath() (string, error)

ConfigPath returns the CLI config file location: $XDG_CONFIG_HOME/moth/config.toml, falling back to ~/.config/moth/config.toml.

func MarshalJSON

func MarshalJSON(m proto.Message) ([]byte, error)

MarshalJSON renders a proto message as stable, indented JSON for --json output. protojson deliberately randomizes its whitespace, so the output is re-indented through encoding/json to stay byte-stable for scripts and golden tests (field order is protojson's, i.e. schema order).

func MergeSettings

func MergeSettings(current, desired *adminv1.ProjectSettings) *adminv1.ProjectSettings

MergeSettings returns desired with its "unset" fields (zero numerics, empty timezone, nil optional/sub-messages, absent redirect_schemes / redirect_origins lists) filled from current, i.e. the full settings object an UpdateProject must send. proto3 booleans cannot express "unset", so false always means false — the plan's SettingsChanges makes an accidental reset visible.

func MonetizationSpecFromCatalog

func MonetizationSpecFromCatalog(ents []*adminv1.Entitlement, prods []*adminv1.Product) *adminv1.MonetizationSpec

MonetizationSpecFromCatalog renders the live entitlements and products as a MonetizationSpec — the block `moth project dump` emits so a re-applied dump converges to an empty plan. Product entitlement grants are emitted as stable entitlement identifiers (not server ids). A project with no catalog yields a non-nil empty spec so the round-trip stays lossless (absent == untouched; empty == an explicitly empty catalog, which for an already-empty project is a no-op).

func SaveConfig

func SaveConfig(path string, cfg Config) error

SaveConfig writes the config file (0600 — it holds credentials), creating parent directories as needed.

func SpecFromYAML

func SpecFromYAML(data []byte) (*adminv1.ProjectSpec, error)

SpecFromYAML parses a `moth project dump` document (unknown fields are rejected, so typos fail loudly instead of silently applying nothing).

func SpecToYAML

func SpecToYAML(spec *adminv1.ProjectSpec) ([]byte, error)

SpecToYAML serializes a ProjectSpec as the YAML document `moth project dump` emits and `moth project apply -f` consumes. The field names are the proto ones (snake_case); the conversion goes through protojson so the document stays a faithful, versionable rendering of the proto message.

Types

type ApplyPlan

type ApplyPlan struct {
	Slug           string `json:"slug"`
	Create         bool   `json:"create"`
	UpdateName     bool   `json:"update_name"`
	UpdateSettings bool   `json:"update_settings"`
	UpdateTheme    bool   `json:"update_theme"`
	ResetTheme     bool   `json:"reset_theme"`
	// SettingsChanges names the settings fields an update will change
	// (proto field names, in field order), so an accidental boolean reset
	// is visible in the plan before it is applied.
	SettingsChanges []string `json:"settings_changes,omitempty"`
	// Notes flags operations the diff cannot verify, e.g. write-only
	// provider secrets that are re-sent on every apply.
	Notes []string `json:"notes,omitempty"`
}

ApplyPlan lists what one `moth project apply` run will change. An empty plan is the idempotency signal: the live state already matches the spec.

func PlanApply

PlanApply diffs a spec against the live state (current == nil when no project has the spec's slug; theme is the project's current theme, nil on create) and returns the plan plus the settings message an update must send. The sent settings are the spec's merged over the current ones — zero numeric fields, an empty timezone, absent redirect_schemes / redirect_origins lists and absent sub-messages mean "keep what the server has" — so partial hand-written specs stay idempotent and never clobber unrelated fields. Plain proto3 booleans are the exception (omitted means false); the plan's SettingsChanges lists every field about to change.

func (ApplyPlan) Empty

func (p ApplyPlan) Empty() bool

Empty reports whether the apply is a no-op.

func (ApplyPlan) Summary

func (p ApplyPlan) Summary() []string

Summary renders the plan as human lines ("create project", ...).

type Client

Client bundles one connect client per moth.admin.v1 service, all sharing an http.Client that authenticates with a personal access token. The CLI command groups are thin wrappers over these — the same generated clients the admin SPA uses, so the two surfaces cannot diverge in capability.

func New

func New(baseURL, pat string) *Client

New builds the admin clients for the server at baseURL, sending `authorization: Bearer <pat>` on every request (the credential the admin auth interceptor accepts alongside cookie sessions).

type Config

type Config struct {
	// CurrentContext is the context used when --context/MOTH_CONTEXT is
	// not given.
	CurrentContext string             `toml:"current-context,omitempty"`
	Contexts       map[string]Context `toml:"contexts,omitempty"`
}

Config is the on-disk CLI configuration, kubectl-style.

func LoadConfig

func LoadConfig(path string) (Config, error)

LoadConfig reads the config file; a missing file is an empty config.

func (Config) Resolve

func (c Config) Resolve(name string) (string, Context, error)

Resolve picks the context to use: the named one when name is not empty (--context / MOTH_CONTEXT), the current-context otherwise. The returned errors tell the operator how to fix an unconfigured CLI.

func (*Config) SetContext

func (c *Config) SetContext(name string, ctx Context)

SetContext adds or replaces a named context and makes it current.

type Context

type Context struct {
	// URL is the server base URL ("https://auth.example.com").
	URL string `toml:"url"`
	// Token is a personal access token (moth_pat_...).
	Token string `toml:"token"`
}

Context is one named server + credential pair.

type InitAnswers

type InitAnswers struct {
	// Spec carries name, slug (may be empty: derived server-side), settings
	// including provider credentials entered in-flow, and the monetization
	// catalog. It is the document the finishing `moth project apply` spec is
	// rendered from.
	Spec *adminv1.ProjectSpec
	// Profile records the answers themselves — platforms and feature intent
	// — for UpdateProfile; the derived checklist keys off it.
	Profile *adminv1.Profile
	// Push is the milestone-20 settings to install, nil when the backend
	// will not send pushes.
	Push *adminv1.PushSettings
	// Deferred lists what the wizard honestly did not finish, one human
	// line each ("Google sign-in credentials — run 'moth setup google'").
	Deferred []string
}

InitAnswers is everything `moth project init` collected: the desired state (the same ProjectSpec `moth project apply` consumes, plus the push settings and profile the spec cannot carry) and the work the operator explicitly deferred.

func RunInitWizard

func RunInitWizard(p *setup.Prompter) (*InitAnswers, error)

RunInitWizard runs the interactive ask-configure-defer flow of `moth project init` over the prompter and returns the collected answers. It performs no RPC and writes nothing: creation is atomic after the final confirmation, so abandoning at any prompt (including answering "n" at the review step, which returns ErrInitAborted) leaves no project behind.

type MonetizationPlan

type MonetizationPlan struct {
	CreateEntitlements []string `json:"create_entitlements,omitempty"`
	UpdateEntitlements []string `json:"update_entitlements,omitempty"`
	DeleteEntitlements []string `json:"delete_entitlements,omitempty"`
	CreateProducts     []string `json:"create_products,omitempty"`
	UpdateProducts     []string `json:"update_products,omitempty"`
	DeleteProducts     []string `json:"delete_products,omitempty"`
}

MonetizationPlan lists the catalog changes one apply will make, by stable identifier (entitlement/product identifiers, not server ids). An empty plan is the idempotency signal: the live catalog already matches the spec. Unlike settings (which merge partial specs), the catalog is full desired state — entitlements/products absent from the spec are deleted — so a dump re-applied converges to an empty plan.

func PlanMonetization

func PlanMonetization(spec *adminv1.MonetizationSpec, currentEntitlements []*adminv1.Entitlement, currentProducts []*adminv1.Product) MonetizationPlan

PlanMonetization diffs a monetization spec against the live catalog (the project's current entitlements and products, as the admin services return them) and returns the create/update/delete plan, keyed on identifiers. A nil spec means the spec omitted the monetization block entirely and the catalog is left untouched (empty plan) — distinct from an explicit empty catalog, which deletes everything. Product entitlement grants are compared by entitlement identifier (resolved through currentEntitlements) so the diff is stable across the id/identifier boundary.

func (MonetizationPlan) Empty

func (p MonetizationPlan) Empty() bool

Empty reports whether the monetization apply is a no-op.

func (MonetizationPlan) Summary

func (p MonetizationPlan) Summary() []string

Summary renders the plan as human lines.

Jump to

Keyboard shortcuts

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