Documentation
¶
Overview ¶
Package cmdutil provides shared infrastructure for command implementations: the Factory for dependency injection, typed errors for centralized classification, build metadata extraction, and flag helpers. Command packages import cmdutil to access these building blocks; cmdutil itself has no knowledge of individual commands.
Index ¶
- Constants
- Variables
- func AvailableTitleWidth(termWidth, overhead int) int
- func ColorBlockedText(cs *iostreams.ColorScheme, text string) string
- func ColorEmpty(cs *iostreams.ColorScheme, text string) string
- func ColorSecondaryText(cs *iostreams.ColorScheme, s domain.SecondaryState, text string) string
- func ColorState(cs *iostreams.ColorScheme, state domain.State) string
- func ColorStateText(cs *iostreams.ColorScheme, state domain.State, text string) string
- func ColumnNames(cols []Column) []string
- func ColumnarHeaderCells(cols []Column) []string
- func ColumnarRowCells(item driving.IssueListItemDTO, cols []Column, rc RenderContext) []string
- func FormatBlockerSuffix(blockerIDs []string) string
- func FormatDetailState(cs *iostreams.ColorScheme, primary domain.State, ...) string
- func FormatJSONTimestamp(t time.Time) string
- func FormatState(cs *iostreams.ColorScheme, primary domain.State, ...) string
- func HasColumn(cols []Column, name string) bool
- func NewTracker(f *Factory) (driving.Service, error)
- func OverheadForColumns(cols []Column) int
- func ParseLabelFilters(raw []string) ([]driving.LabelFilterInput, error)
- func ParseLabels(raw []string) ([]driving.LabelInput, error)
- func ParseOrderBy(s string) (driving.OrderBy, driving.SortDirection, error)
- func ResolveFlatListOrderBy(s string) (driving.OrderBy, driving.SortDirection, error)
- func ResolveLimit(limitFlag int, noLimitFlag bool) (int, error)
- func StripANSI(s string) string
- func TruncateTitle(title string, maxWidth int) string
- func ValidColumnNames() string
- func ValidOrderNames() string
- func WordWrap(text string, width int) string
- func WriteJSON(w io.Writer, v any) error
- type BuildInfo
- type Column
- type ExitCode
- type Factory
- type FlagError
- type IDResolver
- type ListItemOutput
- type ListOutput
- type RenderContext
- type TableWriter
Constants ¶
const ( // FlagCategoryRequired is the flag category for mandatory flags. The user // must supply these for the command to succeed. FlagCategoryRequired = "Required" // FlagCategorySupplemental is the flag category for optional flags that // modify command behavior but are not strictly necessary. FlagCategorySupplemental = "Supplemental" )
const DefaultLimit = 20
DefaultLimit is the default result limit for all list operations. Commands set this as the Value on their --limit IntFlag so the help text displays the correct default and the flag destination is pre-populated.
Variables ¶
var DefaultColumns = []Column{
columnRegistry["ID"],
columnRegistry["PRIORITY"],
columnRegistry["ROLE"],
columnRegistry["STATE"],
columnRegistry["TITLE"],
}
DefaultColumns is the default column set and order for tabular issue list output. Parent and timestamp columns are available via --columns but excluded from the default to preserve title space on standard-width terminals.
var ErrInvalidLimit = errors.New("--limit must be a positive integer; use --no-limit for unbounded results")
ErrInvalidLimit indicates that a non-positive value was provided for the --limit flag. The value must be a positive integer. Callers should use --no-limit to request unbounded results instead.
var ErrSilent = errors.New("silent error")
ErrSilent is a sentinel error indicating the error message has already been printed to the user. The top-level error handler should exit with a non-zero code but not print the error again. Commands use this after displaying a formatted error message directly to IOStreams.ErrOut.
Functions ¶
func AvailableTitleWidth ¶
AvailableTitleWidth calculates how many columns the title may occupy given the terminal width and the overhead consumed by non-title columns and tab padding. Returns 0 when termWidth is 0 (non-TTY), signaling that callers should not truncate. Returns at least minTitleWidth when the terminal is very narrow, so the title remains somewhat readable.
func ColorBlockedText ¶
func ColorBlockedText(cs *iostreams.ColorScheme, text string) string
ColorBlockedText applies the canonical "blocked" color (134, purple) to arbitrary text.
func ColorEmpty ¶
func ColorEmpty(cs *iostreams.ColorScheme, text string) string
ColorEmpty applies the "empty" color (dark gray, 238) to text. Use this for zero-count indicators in dashboard-style output.
func ColorSecondaryText ¶
func ColorSecondaryText(cs *iostreams.ColorScheme, s domain.SecondaryState, text string) string
ColorSecondaryText applies the canonical color for a secondary state to arbitrary text. Use this when the text is not the secondary state name itself — e.g., wrapping a label like "[blocked]" in the blocked color.
func ColorState ¶
func ColorState(cs *iostreams.ColorScheme, state domain.State) string
ColorState applies the canonical ANSI color to a primary state string. Use this when rendering a bare state label outside of FormatState or FormatDetailState — e.g., blocker state annotations in the show command.
func ColorStateText ¶
ColorStateText applies the canonical color for the given primary state to arbitrary text. Use this when the text is not the state name itself — e.g., coloring a numeric count with the state's color.
func ColumnNames ¶ added in v0.2.0
ColumnNames returns the canonical names of the given columns in order.
func ColumnarHeaderCells ¶ added in v0.2.0
ColumnarHeaderCells returns the header strings for the given columns, suitable for passing to TableWriter.AddRow.
func ColumnarRowCells ¶ added in v0.2.0
func ColumnarRowCells(item driving.IssueListItemDTO, cols []Column, rc RenderContext) []string
ColumnarRowCells returns the rendered cell values for a single issue row, suitable for passing to TableWriter.AddRow.
func FormatBlockerSuffix ¶
FormatBlockerSuffix formats blocker IDs for display after the issue title in text output. Shows at most maxBlockerDisplay IDs; appends "…" when truncated. Accepts pre-formatted string IDs from the service-layer DTO.
func FormatDetailState ¶
func FormatDetailState(cs *iostreams.ColorScheme, primary domain.State, details []domain.SecondaryState) string
FormatDetailState formats a primary state with multiple secondary conditions for detail views. Returns "primary (sec1, sec2)" when secondary conditions are present, or just "primary" when the slice is empty or nil. Both the primary state and each secondary condition are individually colored.
func FormatJSONTimestamp ¶
FormatJSONTimestamp formats t as a UTC timestamp with millisecond precision and a Z suffix, suitable for JSON output. Returns the empty string when t is the zero value, so callers can use omitzero struct tags to suppress it.
func FormatState ¶
func FormatState(cs *iostreams.ColorScheme, primary domain.State, secondary domain.SecondaryState) string
FormatState formats a primary state with a single secondary state for list views. Returns "primary (secondary)" when a secondary state is present, or just "primary" when secondary is SecondaryNone. Both the primary and secondary state texts are colored according to the unified state palette.
func HasColumn ¶ added in v0.2.0
HasColumn reports whether the given column set includes a column with the specified canonical name. This is a convenience wrapper around columnsContain for use by command packages.
func NewTracker ¶
NewTracker constructs the application service from the Factory's Store connection. This is a convenience function that prevents boilerplate in every command — the Factory provides the architecture-neutral database connection; this function wires it into the application layer. The SQLite store satisfies both driven.Transactor and driven.Migrator, so it is passed for both roles to enable schema migration through the service.
func OverheadForColumns ¶ added in v0.2.0
OverheadForColumns estimates the non-title character overhead for title truncation based on the selected columns. Each column contributes its typical display width plus inter-column padding. The TITLE column itself is excluded since it is the one being truncated.
These are conservative approximations intended to prevent title truncation from being too aggressive. Actual rendered widths may vary — for example, STATE can range from "open" (4) to "open (claimed)" (14). The estimates use worst-case values to avoid clipping titles.
func ParseLabelFilters ¶
func ParseLabelFilters(raw []string) ([]driving.LabelFilterInput, error)
ParseLabelFilters converts a slice of "key:value" strings into service-layer LabelFilterInput DTOs. A wildcard value ("*") matches any value for the key and is represented by an empty Value field. Negation ("!key:value") is indicated by a true Negate field.
func ParseLabels ¶
func ParseLabels(raw []string) ([]driving.LabelInput, error)
ParseLabels parses a slice of "key:value" strings into service-layer LabelInput DTOs. Validation of the key and value content is deferred to the service layer, which constructs domain Label values internally.
func ParseOrderBy ¶
ParseOrderBy converts a user-supplied sort string into a service-layer OrderBy value and SortDirection. Valid inputs match the column names accepted by --columns (ID, CREATED, PARENT_ID, PARENT_CREATED, PRIORITY, ROLE, STATE, TITLE) plus the alias "MODIFIED". An optional ":asc" or ":desc" suffix controls the direction; the default is ascending. Matching is case-insensitive. An empty string defaults to OrderByPriority ascending.
func ResolveFlatListOrderBy ¶ added in v0.2.0
ResolveFlatListOrderBy parses a user-supplied --order flag value for a flat listing command (such as "ready" or "blocked") where family-anchored priority ordering is not meaningful. It behaves identically to ParseOrderBy except that driving.OrderByPriority is remapped to driving.OrderByPriorityCreated, so that same-priority issues from different parents interleave by creation time rather than grouping under their parents. Keying the remap off the parsed OrderBy ensures that all priority variants — "priority", "PRIORITY", "priority:asc", "priority:desc", whitespace-padded values, and the empty default — are handled consistently. An invalid sort order is returned as an error from ParseOrderBy.
func ResolveLimit ¶
ResolveLimit determines the effective limit for list operations based on the --limit flag and the --no-limit flag.
The --limit flag's default value should be set to DefaultLimit at command construction time, so the limitFlag always carries a meaningful positive value when the user does not override it.
Precedence: --no-limit (unlimited) > --limit N (explicit positive). A non-positive limitFlag value is rejected with ErrInvalidLimit.
func StripANSI ¶ added in v0.2.0
StripANSI removes all ANSI SGR escape sequences from a string, returning only the visible text content. Exported for use in test assertions.
func TruncateTitle ¶
TruncateTitle truncates a title string to fit within maxWidth columns. If the title fits, it is returned unchanged. If it exceeds maxWidth, the last visible character is replaced with "…" (U+2026). When maxWidth is zero or negative, the title is returned unchanged — this signals a non-TTY environment where no truncation should occur.
func ValidColumnNames ¶ added in v0.2.0
func ValidColumnNames() string
ValidColumnNames returns a deterministic, comma-separated list of valid column names for use in error messages.
func ValidOrderNames ¶ added in v0.2.0
func ValidOrderNames() string
ValidOrderNames returns a deterministic, comma-separated list of valid --order flag values for use in help text and error messages.
Types ¶
type BuildInfo ¶
type BuildInfo struct {
// VCS is the version control system name (e.g., "git").
VCS string
// Revision is the full commit identifier at which the binary was built.
Revision string
// Time is the commit timestamp in RFC 3339 format.
Time string
// Dirty is true when the working tree had uncommitted changes at build time.
Dirty bool
}
BuildInfo holds version-control metadata embedded by the Go toolchain at build time. The zero value represents an unknown build — all string fields are empty and Dirty is false.
func ReadBuildInfo ¶
func ReadBuildInfo() BuildInfo
ReadBuildInfo extracts VCS metadata from the running binary's embedded build information. Returns a zero-value BuildInfo when the information is unavailable (e.g., when running via `go run` or in tests).
type Column ¶ added in v0.2.0
type Column struct {
// Name is the canonical, case-insensitive identifier used in --columns
// flag values (e.g., "ID", "PRIORITY").
Name string
// Header is the all-caps string rendered in the header row.
Header string
// Render extracts and formats the column value from an IssueListItemDTO.
// The RenderContext provides color scheme and terminal width information
// needed by columns like STATE and TITLE that apply formatting.
Render func(item driving.IssueListItemDTO, rc RenderContext) string
}
Column represents a single column in tabular issue list output. Each column has a display header and a function that renders the column value for a given issue list item. Columns are the building blocks of the configurable column selection model used by list, search, ready, blocked, and epic children commands.
Column values contain a function field (Render), making the struct non-comparable with ==. Compare columns by Name instead.
func ColumnByName ¶ added in v0.2.0
ColumnByName looks up a column by its canonical name (case-insensitive). Returns the Column and true if found, or a zero Column and false if the name is not recognized.
func ParseColumns ¶ added in v0.2.0
ParseColumns parses a comma-separated string of column names and returns the corresponding Column slice in the order specified. Column names are case-insensitive. Returns an error listing valid column names when any name is unrecognized. An empty input string returns the DefaultColumns.
type ExitCode ¶
type ExitCode int
ExitCode represents the process exit code returned to the operating system. Each value has a specific semantic meaning per §9 of the specification, allowing scripts, CI systems, and AI agents to distinguish between different failure modes.
const ( // ExitOK indicates the command completed successfully. ExitOK ExitCode = 0 // ExitError indicates a general or unexpected error. ExitError ExitCode = 1 // ExitNotFound indicates the requested entity (issue, comment, etc.) // does not exist. ExitNotFound ExitCode = 2 // ExitClaimConflict indicates the issue is already claimed and the // claim is not stale, or the claim ID does not match. ExitClaimConflict ExitCode = 3 // ExitValidation indicates invalid input (bad flags, missing required // fields, constraint violations). ExitValidation ExitCode = 4 // ExitDatabase indicates a database error (corruption, locked, etc.). ExitDatabase ExitCode = 5 // ExitPending indicates the operation was accepted but is not yet // complete. Used for asynchronous workflows. ExitPending ExitCode = 8 )
func ClassifyError ¶
ClassifyError maps a command error to an exit code and prints appropriate messages to stderr. This is the single place where error types are translated into user-visible behavior and exit codes.
The signalCancelIsError parameter controls whether context.Canceled (typically from SIGINT/SIGTERM via signal.NotifyContext) produces a non-zero exit code. When false, signal cancellation is treated as graceful shutdown (exit 0) — the correct default for Kubernetes-deployed services where SIGTERM is a normal lifecycle event.
Uses Go 1.26's errors.AsType for type-safe, generic error classification — no need for a target variable declaration before the call.
type Factory ¶
type Factory struct {
// AppName is the application binary name, used in version output and
// user-facing messages.
AppName string
// AppVersion is the build version, injected at compile time via -ldflags
// or set to "dev" during development.
AppVersion string
// BuildInfo holds version-control metadata (VCS name, commit, timestamp)
// embedded by the Go toolchain at build time.
BuildInfo BuildInfo
// IOStreams provides abstracted I/O with TTY awareness and color control.
// Constructed eagerly because it is needed by almost every command and
// has no expensive initialization.
IOStreams *iostreams.IOStreams
// Workspace is the explicit workspace directory path, set via the
// --workspace global flag or the NP_WORKSPACE environment variable. When
// non-empty, database discovery uses this directory directly instead of
// walking up from the current working directory. The flag takes
// precedence over the environment variable.
Workspace string
// DatabasePath returns the absolute path to the database file, using
// workspace-aware discovery. When Workspace is set, it checks that
// directory directly via LookupDatabase; otherwise it walks up from the
// current working directory via DiscoverDatabase. Commands that need the
// .np/ directory path (for backup files, reset-key hashes, or path
// reporting) should call this rather than invoking DiscoverDatabase
// directly, which would bypass the --workspace override.
DatabasePath func() (string, error)
// Store returns the SQLite database connection. Constructed lazily on
// first access — database discovery and opening happen at that point.
// Commands that need the application service construct it from this
// connection via cmdutil.NewTracker.
Store func() (*sqlite.Store, error)
// SignalCancelIsError controls whether signal-triggered context
// cancellation (SIGINT, SIGTERM) produces a non-zero exit code.
//
// Long-running processes — HTTP servers, Kafka consumers, queue workers —
// receive SIGTERM as a routine part of their lifecycle (e.g., Kubernetes
// pod eviction, rolling deploys). For these, signal cancellation is
// expected and should exit cleanly with code 0, so leave this false (the
// default).
//
// Short-lived processes — CLI tools, Kubernetes Jobs, cron jobs — run to
// completion under normal conditions. A signal interrupting one of these
// typically means something went wrong (user abort, preemption, timeout),
// so set this to true to surface the interruption as a non-zero exit code.
SignalCancelIsError bool
}
Factory provides lazy-loaded, substitutable dependencies to commands. Function fields are not called until a command actually needs the dependency, and they can be replaced in tests with trivial stubs or panicking functions to catch accidental dependency usage.
Eager fields (like IOStreams) are cheap to construct and used by virtually every command. As the application grows, expensive dependencies (HTTP clients, database pools) are added as function-typed fields whose cost is deferred until actual use.
Function fields also serve as live configuration providers for long-running services. When a Factory function closes over a mutable configuration source (file watcher, K8s ConfigMap, Vault lease), callers that invoke the function per unit of work — per HTTP request, per batch iteration, per queue message — always receive resources built from the current configuration. This enables credential rotation, feature flag updates, and connection pool recycling without process restarts. Short-lived CLI commands can safely call Factory functions once; the pattern adds no overhead but does not provide its full benefit in that context.
type FlagError ¶
type FlagError struct {
Err error
}
FlagError indicates invalid flag usage or flag combination. The top-level error handler should print the error along with the command's usage text to help the user correct their invocation.
func FlagErrorf ¶
FlagErrorf creates a FlagError with a formatted message. Commands use this when flag validation fails — the top-level error handler will print the error along with usage text to guide the user.
type IDResolver ¶
type IDResolver struct {
// contains filtered or unexported fields
}
IDResolver resolves issue ID strings that may be either full IDs (PREFIX-random) or bare random parts (just the 5-char Crockford string). It caches the database prefix on first use so that subsequent calls do not require additional database round-trips.
func NewIDResolver ¶
func NewIDResolver(svc driving.Service) *IDResolver
NewIDResolver creates an IDResolver backed by the given driving. The prefix is fetched lazily on the first call to Resolve.
type ListItemOutput ¶
type ListItemOutput struct {
ID string `json:"id"`
Role string `json:"role"`
State string `json:"state"`
SecondaryState string `json:"secondary_state,omitempty"`
DisplayStatus string `json:"display_status"`
Priority string `json:"priority"`
Title string `json:"title"`
BlockerIDs []string `json:"blocker_ids,omitempty"`
ParentID string `json:"parent_id,omitempty"`
ParentCreatedAt string `json:"parent_created_at,omitempty"`
CreatedAt string `json:"created_at"`
}
ListItemOutput is the JSON representation of a single issue in a list. It is shared by the list, ready, and blocked commands so that their --json output is structurally identical.
func ConvertListItems ¶
func ConvertListItems(items []driving.IssueListItemDTO) []ListItemOutput
ConvertListItems transforms a slice of service-layer IssueListItemDTO values into the shared JSON output representation.
type ListOutput ¶
type ListOutput struct {
Issues []ListItemOutput `json:"issues"`
HasMore bool `json:"has_more"`
}
ListOutput is the JSON representation of a list command result.
type RenderContext ¶ added in v0.2.0
type RenderContext struct {
// ColorScheme controls ANSI coloring of state columns. Nil means no color.
ColorScheme *iostreams.ColorScheme
// MaxTitleWidth is the maximum number of columns the title may occupy.
// Zero means no truncation (non-TTY).
MaxTitleWidth int
}
RenderContext provides environmental information that column render functions need to format values correctly. It carries the color scheme for state colorization and the maximum title width for truncation.
type TableWriter ¶ added in v0.2.0
type TableWriter struct {
// contains filtered or unexported fields
}
TableWriter collects rows of cells and writes them as a padded, aligned table. Unlike text/tabwriter, it measures visible width by stripping ANSI escape sequences, so colored cells align correctly with uncolored headers.
Usage:
tw := NewTableWriter(w, 2)
tw.AddRow("ID", "STATE", "TITLE")
tw.AddRow(id, coloredState, title)
_ = tw.Flush()
func NewTableWriter ¶ added in v0.2.0
func NewTableWriter(w io.Writer, padding int) *TableWriter
NewTableWriter creates a TableWriter that writes aligned output to w. The padding parameter controls the minimum number of spaces between columns.
func (*TableWriter) AddRow ¶ added in v0.2.0
func (t *TableWriter) AddRow(cells ...string)
AddRow appends a row of cell values. Every row must have the same number of cells as the first row added to this writer. Flush returns an error if any row violates this constraint.
func (*TableWriter) Flush ¶ added in v0.2.0
func (t *TableWriter) Flush() error
Flush computes the maximum visible width per column across all rows, then writes every row with cells padded to their column width plus inter-column padding. The last cell in each row is written without trailing padding. Returns an error if any row has a different number of cells than the first row, or if any write to the underlying writer fails.