cli

package
v0.1.0-dev.20260827001235 Latest Latest
Warning

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

Go to latest
Published: Aug 27, 2026 License: Apache-2.0 Imports: 32 Imported by: 0

Documentation

Overview

Package cli provides shared CLI infrastructure for writ and lore commands.

Index

Constants

View Source
const (
	IndexEventGraph = "graph"
	IndexEventTrace = "trace"
)

IndexEventGraph marks an IndexEntry recording a graph write; IndexEventTrace marks one recording a trace write.

View Source
const (
	ExitOK          = 0  // Success
	ExitError       = 1  // Generic error
	ExitUsage       = 64 // Bad CLI syntax
	ExitDataErr     = 65 // Invalid manifest/config
	ExitNoInput     = 66 // File not found
	ExitUnavailable = 69 // Registry unreachable
	ExitSoftware    = 70 // Internal error (bug)
	ExitCantCreate  = 73 // Can't create file/symlink
	ExitIOErr       = 74 // Read/write failure
	ExitNoPerm      = 77 // Permission denied
)

Exit codes follow BSD sysexits.h conventions for portable process status.

Variables

View Source
var ErrManNotAvailable = errors.New("man command not available")

ErrManNotAvailable indicates the man command is not available on this system.

Functions

func AddOutputFlags

func AddOutputFlags(cmd *cobra.Command, opts *SinkOptions)

AddOutputFlags binds --format, --template, --filter, and --jq to opts. Call once during command setup, then call BuildPipeline from the cobra RunE to compose the result.Pipeline.

func AddSilentFlag

func AddSilentFlag(cmd *cobra.Command)

AddSilentFlag adds the --silent flag to a root command. The flag value is read by bootstrap (cobra PersistentPreRun) which forks construction of the narrator: silent → sink.Discard, otherwise → sink.Stderr.

func AddVersionFlag

func AddVersionFlag(rootCmd *cobra.Command, info VersionInfo)

AddVersionFlag installs `--version` on a root command, answering in one line.

Cobra generates the flag as soon as cobra.Command.Version is set; the template fixes the wording to docker's — `writ version 0.4.0, build ed6f468` — a single line that contacts nothing and exits. `-v` is deliberately not a shorthand for it: this repository's commands already use `-v` for verbose output, and the collision would be worse than the missing convenience.

Parameters:

  • `rootCmd`: the root command the flag is installed on.
  • `info`: the build-time metadata; `Version` and `Commit` appear in the line.

func BindFlags

func BindFlags(cmd *cobra.Command, toolName string, useSharedConfig bool) error

BindFlags binds all persistent flags from a command to Viper. Do this after defining flags and before Execute().

Flags are bound with the tool's section prefix when using shared config:

  • --repo flag → viper key "writ.repo" (with UseSharedConfig)
  • --repo flag → viper key "repo" (without UseSharedConfig)

func BuildPipeline

func BuildPipeline(opts SinkOptions, w io.Writer) (*result.Pipeline, error)

BuildPipeline composes a result.Pipeline from the populated SinkOptions writing through w. Filters compose in --filter-then--jq order; the formatter is selected by result.FormatterByName. The writer is wrapped in a sink.Sink via sink.New internally.

Returns an error when the formatter name is unknown, the template body fails to parse, the field expressions fail to parse, or the jq expression fails to compile.

func CollectFiles

func CollectFiles(base, dir string) []string

CollectFiles returns all file paths under dir, relative to base.

func CopyDir

func CopyDir(dstRoot fsroot.Dir, src string, dst fsroot.Path) error

CopyDir recursively copies a directory tree from the host filesystem into `dstRoot`.

Used by post-install hooks — `cmd/star` copies its extensions this way — so the destination root is supplied by the hook rather than constructed here (#405, phase 2b).

Parameters:

  • `dstRoot`: the tree the destination belongs to, opened by the caller.
  • `src`: the source directory, outside the root.
  • `dst`: the destination within `dstRoot`.

Returns:

  • `error`: non-nil when the source cannot be read or any destination cannot be written.

func DisplayManPage

func DisplayManPage(cmd *cobra.Command, header *doc.GenManHeader) (err error)

DisplayManPage generates a man page and displays it with the system pager. Returns ErrManNotAvailable if man is not available on this system.

func Error

func Error(format string, args ...any)

Error prints an error message via the installed narrator. Unlike Failure, this does not return an error — use for non-fatal errors.

func ExitCode

func ExitCode(err error) int

ExitCode extracts the exit code from an error. Returns the wrapped code if present, or ExitError (1) for plain errors.

func ExitWith

func ExitWith(code int, err error) error

ExitWith returns an error that carries a specific exit code.

func Failure

func Failure(format string, args ...any) error

Failure prints an error message via the installed narrator and returns the wrapped error. Use when the operation cannot continue.

func GraphsDir

func GraphsDir() string

GraphsDir returns the directory holding persisted graphs.

Returns:

  • `string`: the absolute graphs directory under the devlore state home.

func IndexPath

func IndexPath() string

IndexPath returns the run index's path at the store root.

Returns:

  • `string`: the absolute path of `index.ndjson` under the devlore state home.

func InitViper

func InitViper(cfg ViperConfig) error

InitViper initializes Viper with standard devlore conventions. Do this in PersistentPreRunE of the root command.

Precedence (lowest to highest):

  1. Config file defaults
  2. Config file values
  3. Environment variables (TOOL_KEY_NAME)
  4. Command-line flags

Environment variable mapping:

  • WRIT_REPO → writ.repo (with UseSharedConfig)
  • WRIT_VARS_USER_NAME → writ.vars.user_name
  • Dots become underscores, keys are case-insensitive

func LatestTracePath

func LatestTracePath(graphChecksum string) string

LatestTracePath returns the path to the `latest.yaml` symlink for the graph identified by `graphChecksum`.

Parameters:

Returns:

  • `string`: the absolute path to the graph's latest-trace symlink (which may not exist yet).

func LoadLatestTrace

func LoadLatestTrace(graphChecksum string) (*op.Trace, error)

LoadLatestTrace loads the most recent trace for the graph identified by `graphChecksum`.

Parameters:

Returns:

  • *op.Trace: the most recent trace for that graph.
  • `error`: non-nil if no trace exists for the graph or it cannot be read.

func LoadTrace

func LoadTrace(path string) (*op.Trace, error)

LoadTrace loads a single trace from `path`, verifying its tier-1 checksum.

Every trace read funnels through here into op.LoadTrace — the checksum trust boundary. A trace with a missing or mismatched checksum is refused (docs/architecture/5-graph-trace-integrity.md).

Parameters:

  • `path`: the trace file to read.

Returns:

  • *op.Trace: the deserialized, integrity-verified trace.
  • `error`: non-nil if the file cannot be read, decoded, or verified.

func NewConfigCmd

func NewConfigCmd(info ConfigInfo) *cobra.Command

NewConfigCmd creates the config command with git-style subcommands.

Dot-paths match the config file structure exactly. No implicit prefixing. "writ.repos.0.path" in the CLI reads writ.repos[0].path in the file. "secrets.mode" reads secrets.mode. WYSIWYG.

Usage:

tool config get <key>...                    # Get values
tool config set <key>=<value>...            # Set values
tool config unset <key>...                  # Remove keys
tool config list                            # List all settings
tool config edit                            # Open in $EDITOR
tool config validate                        # Validate against schema
tool config schema                          # Output JSON schema
tool config path                            # Show config file location

func NewHelpCmd

func NewHelpCmd(rootCmd *cobra.Command, header ManHeader) *cobra.Command

NewHelpCmd creates a help command that prefers man pages when available. This follows git's model: if man pages are installed, display them via pager; otherwise fall back to console text output.

func NewManCmd

func NewManCmd(rootCmd *cobra.Command, header ManHeader) *cobra.Command

NewManCmd creates the man command for displaying/installing man pages. Usage:

tool man              # display man page with pager
tool man --install    # install to ~/.local/share/man/man1/
tool man deploy       # display man page for subcommand

func NewRootCmd

func NewRootCmd(cfg RootConfig) *cobra.Command

NewRootCmd creates a root cobra command with all shared flags, metadata commands, and Viper configuration. The caller adds tool-specific flags and subcommands to the returned command.

Parameters:

  • cfg: root command configuration (name, descriptions, version info)

Returns:

  • *cobra.Command: configured root command with shared flags and metadata commands

func NewSelfCmd

func NewSelfCmd(rootCmd *cobra.Command, info SelfInstallInfo) *cobra.Command

NewSelfCmd creates the "self" command group with install, upgrade, and uninstall subcommands.

func NewVersionCmd

func NewVersionCmd(info VersionInfo) *cobra.Command

NewVersionCmd creates the version command, which prints the full build detail.

Output goes through cobra.Command.OutOrStdout rather than to os.Stdout directly, so a caller that redirects the command's output captures this like any other command's.

Parameters:

  • `info`: the build-time metadata to report.

Returns:

  • `*cobra.Command`: the `version` command, carrying its `--short` flag.

func Note

func Note(format string, args ...any)

Note prints an informational message via the installed narrator.

func OpenTree

func OpenTree(dir string) (fsroot.Dir, error)

OpenTree creates the directory at `dir` if it is absent, then opens a confined root at it.

CLI-side trees are addressed before they exist — a first install creates its prefix, and the config, state and cache trees appear on first use — but fsroot.OpenExisting is a query: it opens what is there and errors when nothing is. This is the compound operation that reconciles the two, and it is deliberately named for the creation so that opening keeps meaning only opening.

Creation runs through a root rather than os.MkdirAll because a mode is worth nothing on Windows without the access-control list that fsroot applies for it — a directory made 0o700 by os.MkdirAll is inherited-DACL and readable by every account with access to its parent. The chain is made through a root anchored at the volume, so every component is resolved by the kernel and no window opens between creating the path and opening it.

The mode is 0o700 because the XDG Base Directory Specification says so: "If, when attempting to write a file, the destination directory is non-existent an attempt should be made to create it with permission 0700." That is owner-only, which is also what makes it enforceable on Windows.

Parameters:

  • `dir`: the absolute path of the tree to open, created when absent.

Returns:

  • `fsroot.Dir`: a confined root anchored at `dir`. The caller owns it and must Close it.
  • `error`: when `dir` is not absolute on this platform, or the tree cannot be created or opened.

func Print

func Print(format string, args ...any)

Print emits raw text via the installed narrator.

func SetUI

func SetUI(n *status.Narrator)

SetUI installs the package-global narrator used by the cli facade functions (Note, Warn, Error, Failure, Success, Print).

Subsequent calls replace the installed narrator.

func SharedConfigPath

func SharedConfigPath() string

SharedConfigPath returns the path to the shared devlore config file.

func Success

func Success(format string, args ...any)

Success prints a success message via the installed narrator.

func TracesDir

func TracesDir() string

TracesDir returns the directory holding persisted execution traces.

Traces are grouped into a per-graph subdirectory keyed by graph checksum; see the package store overview.

Returns:

  • `string`: the absolute traces directory under the devlore state home.

func UI

func UI() *status.Narrator

UI returns the currently installed narrator.

func Warn

func Warn(format string, args ...any)

Warn prints a warning message via the installed narrator.

func WriteGraph

func WriteGraph(graph *op.Graph) (path string, err error)

WriteGraph persists `graph` under GraphsDir, keyed by its checksum, and returns the file path.

Idempotent: a graph with the same checksum is written once. Subsequent calls observe the existing file and return its path without rewriting — distinct runs of the same plan share one persisted graph. A first write also appends an IndexEventGraph line to the run index, carrying the origin's tool and scope so index readers can filter without opening the document.

Parameters:

  • `graph`: the assembled, immutable graph to persist. Must not be nil.

Returns:

  • `string`: the absolute path the graph is stored at.
  • `error`: non-nil if the directory cannot be created or the graph or its index line cannot be written.

func WriteTrace

func WriteTrace(trace *op.Trace) (path string, err error)

WriteTrace persists `trace` under TracesDir in its graph's subdirectory, updates the per-graph `latest.yaml` symlink to point at it, and appends an IndexEventTrace line to the run index.

Each run writes a distinct timestamped file, so a graph accumulates many traces. The subdirectory is keyed by op.Trace.GraphChecksum; `latest.yaml` is the convenience entry point for drift detection, reconciliation, and pause/restart.

Parameters:

  • `trace`: the captured executor trace to persist. Must not be nil and must carry a GraphChecksum.

Returns:

  • `string`: the absolute path the trace is stored at.
  • `error`: non-nil if the directory cannot be created or the trace/symlink cannot be written.

Types

type ConfigInfo

type ConfigInfo struct {
	Name          string // Tool name (e.g., "lore", "writ")
	Schema        []byte // Embedded JSON schema
	DefaultConfig []byte // Default configuration content
}

ConfigInfo contains configuration metadata for a tool.

type IndexEntry

type IndexEntry struct {

	// At is the UTC moment the store write happened.
	At time.Time `json:"at"`

	// Event is [IndexEventGraph] or [IndexEventTrace].
	Event string `json:"event"`

	// Tool is the producing program's name from the graph's origin; graph events only.
	Tool string `json:"tool,omitempty"`

	// Scope is the planning scope from the graph's origin; graph events only.
	Scope string `json:"scope,omitempty"`

	// GraphChecksum is the graph's canonical "sha256:<hex>" identity — the join key between events.
	GraphChecksum string `json:"graph_checksum"`

	// TraceFile is the trace's filename within its per-graph traces subdirectory; trace events only.
	TraceFile string `json:"trace_file,omitempty"`
}

IndexEntry is one line of the run index.

A graph event carries `Tool` and `Scope` (from the graph's origin) so readers can filter without opening the document; a trace event carries `TraceFile` and joins to its graph event through the shared `GraphChecksum`.

func ReadIndex

func ReadIndex() ([]IndexEntry, error)

ReadIndex reads the run index, tolerating a torn final line.

Lines that fail to parse are skipped — a crash mid-append must not fail every later read. A missing index file is an error (callers distinguish it via os.IsNotExist): per the deploy-family design, `writ status` treats a missing index as a hard error rather than degrading silently.

Returns:

  • `[]IndexEntry`: the parsed entries in append order.
  • `error`: non-nil when the index cannot be opened, including when it does not exist.

type ManHeader

type ManHeader struct {
	Title   string
	Section string
	Source  string
	Manual  string
}

ManHeader contains metadata for man page generation.

type RootConfig

type RootConfig struct {
	Name          string // Command name ("lore" or "writ")
	Short         string // One-line description
	Long          string // Multi-line description
	DefaultConfig []byte // Schema default config (e.g., schema.LoreDefaultConfig)
	Version       string // Semantic version, set via ldflags
	Commit        string // Git commit hash, set via ldflags
	BuildDate     string // Build timestamp, set via ldflags
}

RootConfig configures a root CLI command for lore or writ.

type SelfInstallInfo

type SelfInstallInfo struct {
	Name               string                  // Tool name (e.g., "lore", "writ", "star")
	Version            string                  // Semantic version (e.g., "0.4.0"), set via ldflags
	ManHeader          ManHeader               // Man page header metadata
	ConfigInfo         *ConfigInfo             // Config schema and defaults (nil to skip config init)
	PostInstallHooks   []func(string) []string // Hooks run after install; return installed file paths (relative to prefix)
	PostUninstallHooks []func(string) error    // Hooks run after uninstall
}

SelfInstallInfo contains metadata needed for self-installation.

type SinkOptions

type SinkOptions struct {
	Format   string
	Template string
	Filters  []string
	JQ       string
}

SinkOptions captures the populated values from AddOutputFlags. The struct is the input to BuildPipeline, which composes a result.Pipeline from the flag values.

type VersionInfo

type VersionInfo struct {
	Version   string // Semantic version (e.g., "0.1.0")
	Commit    string // Git commit hash
	BuildDate string // Build timestamp
}

VersionInfo contains version metadata set at build time.

type ViperConfig

type ViperConfig struct {
	// Name is the tool name (e.g., "lore", "writ")
	Name string

	// EnvPrefix is the environment variable prefix (e.g., "LORE", "WRIT")
	// If empty, defaults to uppercase Name
	EnvPrefix string

	// ConfigName is the config file name without extension (default: "config")
	ConfigName string

	// ConfigType is the config file type (default: "yaml")
	ConfigType string

	// UseSharedConfig uses ~/.config/devlore/config.yaml with tool-specific section
	// When true, config is read from the tool's section (e.g., config.writ.repo)
	UseSharedConfig bool
}

ViperConfig holds configuration for Viper initialization.

Jump to

Keyboard shortcuts

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