command

package
v0.0.0-...-d1567f4 Latest Latest
Warning

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

Go to latest
Published: Jun 19, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package command defines the Command interface every gobash builtin implements, the Context value those builtins receive at dispatch time, the Result value they return, and the Registry that maps names to implementations. The package is the contract layer between the runtime (gobash + gobash/interp) and the individual built-in packages introduced in Phase 10.

Phase 8 lands the registry skeleton and the dispatch plumbing. The Context shape is intentionally minimal — only the fields whose types are stable in Phases 1–8. The spec lists additional fields (Fetch, Sleep, Trace, Limits, InvokeTool, FDs, JSBootstrap, etc.); each lands in the phase that owns its dependency:

  • Phase 9 adds Fetch (network.Doer).
  • Phase 10 wires Sleep / Trace / Limits / ExportedEnv through — the built-in command bodies are the first consumers.
  • Phase 11 adds Exec (sub-shell invocation) and Signal.
  • Phase 15 adds JSBootstrap and InvokeTool.

Adding fields is non-breaking; reserving them in the spec is enough.

Cited surface: the spec, §8.2, §8.4. Reference (read-only): vercel-labs/just-bash, src/commands/registry.ts.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RegisterBuiltin

func RegisterBuiltin(c Command)

RegisterBuiltin appends c to the package-level built-in slice. The expected callsite is a built-in package's init() function; the runtime never calls this. Nil c is ignored. Concurrent calls are NOT safe — init() is the only intended caller, and Go orders init()s sequentially per goroutine.

Types

type AliasTable

type AliasTable interface {
	Get(name string) (string, bool)
	Set(name, value string)
	Unset(name string) bool
	Clear()
	Names() []string
	All() map[string]string
}

AliasTable is the read/write surface for the `alias` / `unalias` builtins. The concrete implementation lives in internal/runtimestate; this interface lets command.Context (and the public Bash.Aliases getter) refer to it without leaking the internal type or creating an import cycle. A nil value is NOT safe to call through — the runtime always supplies a live table.

type Command

type Command interface {
	// Name returns the canonical lookup key for the command. The
	// registry uses this value as the map key, so it must not change
	// across calls.
	Name() Name

	// Execute runs the command. ctx is the parent execution context
	// (cancellation propagates through it). args is the full argv —
	// args[0] is the command name as invoked by the script, args[1:]
	// are the positional arguments. c carries the dispatch state.
	Execute(ctx context.Context, args []string, c *Context) Result

	// Trusted reports whether the command may run inside a Phase 17+
	// sandbox. It is metadata only; no Phase 8–16 code paths inspect
	// it. Built-ins constructed via Define default to true.
	Trusted() bool
}

Command is the interface every gobash built-in implements. The runtime invokes Execute with the full argv (NOT including the command name's index 0 in args) and the dispatch Context. A non-zero Result.ExitCode is NOT an error — it is reported through the runner as the script's exit status.

The spec: the interface also includes a Trusted() bool used only by the sandbox subpackage (Phase 17+). Built-ins that opt out of sandbox trust will return false; until the sandbox lands, every implementation returns true and the runtime ignores the value.

func DefaultBuiltins

func DefaultBuiltins() []Command

DefaultBuiltins returns a snapshot of every Command registered via RegisterBuiltin. The order matches registration order, which is init-graph order — stable within a single binary, but callers must not depend on it beyond "every entry appears exactly once".

func Define

func Define(name string, fn func(ctx context.Context, args []string, c *Context) Result) Command

Define wraps a plain function in a Command, mirroring the TS `defineCommand` helper from the spec The resulting Command's Name is the supplied string; Trusted is true (the sandbox-untrust case is opt-in via DefineUntrusted once Phase 17 needs it).

Define is the recommended way to construct simple commands in tests and in the Phase 10 built-in packages; it keeps the boilerplate of the Command interface out of every package init.

type Context

type Context struct {
	// FS is the virtual filesystem backing every script-side file
	// operation. Commands MUST route I/O through this field — never
	// through host os.* helpers — to preserve the sandbox contract.
	FS gbfs.FileSystem

	// Cwd is the working directory at dispatch time, resolved through
	// the VFS (not the host disk). Commands that perform relative
	// path resolution should join against this value.
	Cwd string

	// Env is the effective environment at dispatch time. Commands may
	// mutate this map; the runtime decides whether to persist the
	// mutation
	Env map[string]string

	// Stdin, Stdout, Stderr are the streams plumbed through from the
	// runner's current redirection state. Commands write structured
	// output here; they should NOT populate Result.Stdout/Stderr in
	// the same call (the runtime treats those as a fallback when the
	// writers are not consulted, e.g. for early returns).
	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer

	// Registry is a back-reference to the dispatch registry so
	// commands that delegate to peers (e.g. a future `which`) can
	// look them up without a global state lookup.
	Registry *Registry

	// Fetch is the network Doer made available to commands like
	// `curl` (Phase 10). It is nil when the host did not configure
	// BashOptions.Network or BashOptions.Fetch — commands that need
	// network MUST check for nil and produce a `network disabled`
	// diagnostic rather than panicking. Phase 9 plumbs this field;
	// Phase 10 wires it to its first real consumer.
	Fetch network.Doer

	// Sleep is the host-supplied sleep hook used by Phase 10's
	// `sleep` builtin. When nil, the builtin uses a real time.Sleep
	// guarded by ctx. Tests override this to elide wall-clock waits.
	Sleep SleepFunc

	// Trace is the host-supplied trace hook. Wave A built-ins do not
	// emit events; the field is plumbed today so later waves can
	// instrument hot paths without a follow-up Context surface bump.
	Trace TraceFunc

	// Limits is the resolved execution-limit set for the current
	// Exec. Wave A built-ins ignore it; Wave D's awk/sed/jq and the
	// optional runtimes (sqlite/python/js) consume the iteration and
	// timeout caps.
	Limits Limits

	// ExportedEnv is the per-dispatch exported-environment view used
	// by env/printenv/export (Phase 10 Wave G). Wave A does not
	// consume it; the field is plumbed today as a landing zone so
	// later waves do not require a Context surface bump.
	ExportedEnv map[string]string

	// Aliases is the per-Bash alias table consulted and mutated by
	// the `alias` / `unalias` built-ins (Phase 10 Wave G). Aliases
	// only expand at parse time when `shopt expand_aliases` is on;
	// the parse-side wiring lands in Phase 11. nil is treated as an
	// empty table.
	Aliases AliasTable

	// History is the per-Bash command history ring (Phase 10 Wave G).
	// The `history` built-in reads and mutates this ring. The runtime
	// itself does NOT currently push commands into the ring — Phase 19
	// will wire automatic recording at parse time. Hosts can populate
	// it manually via Bash.History() until then.
	History HistoryRing

	// Exec is the sub-shell invocation hook used by the Phase 10
	// Wave G `bash` / `sh` / `timeout` built-ins (which would
	// otherwise need to recurse into the runtime). The spec reserves
	// this field for Phase 11 (source / eval / .); Wave G needs it
	// earlier. nil means sub-shell features are unavailable; consumers
	// MUST nil-check and produce a clean diagnostic.
	Exec SubExecFunc

	// SourceDepth tracks recursive sub-shell / source / . / eval
	// invocations against Limits.MaxSourceDepth (Phase 11). The
	// runtime supplies the current depth at dispatch time; built-ins
	// that delegate to c.Exec must bump it by 1 in the
	// SubExecOptions they forward (the runtime then reads it back
	// when constructing the next dispatch Context). A value at or
	// above Limits.MaxSourceDepth means the call must be rejected
	// with a clean diagnostic.
	SourceDepth int

	// Shopt is the per-Bash shell-option table (`shopt`). The
	// `shopt` builtin reads and mutates it; the alias-expansion path
	// (Phase 11) consults `expand_aliases` at parse time. Nil-safe:
	// a nil table reports every option as off and silently drops
	// writes.
	Shopt ShoptTable
}

Context is the per-dispatch state passed to a Command's Execute method. The runtime constructs a fresh Context for every command invocation; mutating it is permitted but ephemeral — changes do NOT propagate back to the parent Bash unless the runtime itself writes them back (see Env mutation semantics in the spec).

Phase 8 wires the seven fields below; the rest land in their owning phases (see the package doc comment).

type HistoryRing

type HistoryRing interface {
	Add(cmd string)
	Clear()
	List() (seqs []int, cmds []string)
	Len() int
}

HistoryRing is the read/write surface for the `history` builtin. The concrete implementation lives in internal/runtimestate; this interface lets command.Context (and the public Bash.History getter) refer to it without leaking the internal type. A nil value is NOT safe to call through — the runtime always supplies a live ring.

type InvokeToolFunc

type InvokeToolFunc = func(ctx context.Context, path, argsJSON string) (string, error)

InvokeToolFunc is the host hook for js-exec tool calls (Phase 15). Declared here so the Context shape is frozen across phases.

type Limits

type Limits struct {
	MaxCallDepth             int
	MaxCommandCount          int
	MaxLoopIterations        int
	MaxAwkIterations         int
	MaxSedIterations         int
	MaxJqIterations          int
	MaxSqliteTimeout         time.Duration
	MaxPythonTimeout         time.Duration
	MaxJsTimeout             time.Duration
	MaxGlobOperations        int
	MaxStringLength          int
	MaxArrayElements         int
	MaxHeredocSize           int
	MaxSubstitutionDepth     int
	MaxBraceExpansionResults int
	MaxOutputSize            int
	MaxFileDescriptors       int
	MaxSourceDepth           int
}

Limits is the per-Exec resolved limit set commands may inspect at dispatch time. It mirrors the spec verbatim — the gobash root package's ResolvedLimits is a type alias to this struct so the runtime can pass through a single value.

Wave A built-ins (Phase 10) consume nothing from Limits; the field is plumbed today so Wave D's awk/sed/jq (MaxAwkIterations, MaxSedIterations, MaxJqIterations) and the optional runtimes (MaxSqliteTimeout, MaxPythonTimeout, MaxJsTimeout) can consume them without a follow-up Context surface bump.

type Name

type Name string

Name is the canonical lookup key for a Command. It mirrors the TS `CommandName` branded string type from src/commands/registry.ts. Stringification is straightforward; tests should construct Name values directly rather than going through fmt.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry maps Name to Command. It is safe to read after construction; concurrent Register/Lookup is NOT supported (the runtime registers everything at New time and the registry is effectively frozen after that).

Cited surface: the spec Reference (read-only): vercel-labs/just-bash, src/commands/registry.ts (CommandRegistry).

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns an empty Registry. Phase 8 callers (gobash.New) build a registry, register CustomCommands first (so they win over later built-in registrations), and apply the BashOptions.Commands filter to whatever built-ins Phase 10 will add. Until Phase 10 lands, only CustomCommands populate the registry.

Named NewRegistry rather than New to avoid shadowing the gobash.New constructor for callers that dot-import this package.

func (*Registry) Has

func (r *Registry) Has(name string) bool

Has reports whether name is registered. Equivalent to discarding the Command return from Lookup, kept as a convenience for the "register only if not already present" loop the Phase 10 built-in bootstrap will use.

func (*Registry) Lookup

func (r *Registry) Lookup(name string) (Command, bool)

Lookup returns the Command registered under name, or (nil, false) if no such command exists. Name lookup is case-sensitive and matches the script's literal command word.

func (*Registry) Names

func (r *Registry) Names() []Name

Names returns every registered command name, sorted for determinism. The sort matters: the spec materializes one /bin/X stub per registered name, and a stable order keeps the FS layout reproducible across runs.

func (*Registry) Register

func (r *Registry) Register(c Command)

Register adds c to the registry, overwriting any existing entry with the same Name. Caller order is therefore significant when CustomCommands need to win over built-ins: register customs LAST, or equivalently, register them first AND skip already-present names when adding built-ins.

The Phase 8 runtime registers CustomCommands BEFORE built-ins (per The spec "override built-ins") and the built-in registration loop skips names already present — see gobash.New.

type Result

type Result struct {
	Stdout   string
	Stderr   string
	ExitCode int
}

Result is the outcome of a Command.Execute call. A non-zero ExitCode is propagated to the runner as the command's exit status but is NOT treated as a harness error.

Stdout / Stderr are FALLBACK string buffers for commands that did not consume Context.Stdout / Context.Stderr directly. The runtime flushes any non-empty value here to the corresponding writer before translating ExitCode into an mvdan/sh ExitStatus. Commands that already wrote through the writers MUST leave these empty to avoid double-emission.

type ShoptTable

type ShoptTable interface {
	IsSet(name string) bool
	Set(name string, on bool)
	Names() []string
}

ShoptTable is the read/write surface for the `shopt` builtin. The concrete implementation lives in internal/runtimestate; this interface lets command.Context refer to it without an import cycle.

type SleepFunc

type SleepFunc = func(ctx context.Context, d time.Duration) error

SleepFunc is the sleep hook used by Phase 10's `sleep` builtin (and, later, `timeout`). When set on Context.Sleep, the builtin invokes it instead of time.Sleep so tests can elide wall-clock waits without mocking time globally. Returning a non-nil error aborts the sleep and is surfaced to the caller as the command's exit status (per The spec). Type alias keeps gobash.SleepFunc structurally compatible with command.SleepFunc.

type SubExecFunc

type SubExecFunc = func(ctx context.Context, script string, opts SubExecOptions) (Result, error)

SubExecFunc is the signature of the sub-shell invocation hook reserved for Phase 11. Declared here so Context.Exec's type stays stable across phases; nil until the source/eval/. builtins land.

type SubExecOptions

type SubExecOptions struct {
	Env        map[string]string
	ReplaceEnv bool
	Cwd        string
	Stdin      io.Reader
	Stdout     io.Writer
	Stderr     io.Writer
	Args       []string

	// SourceDepth, when non-zero, is the depth the sub-shell should
	// start at. The Phase 11 source / eval / . / bash / sh / timeout
	// builtins set this to parent.SourceDepth + 1 so MaxSourceDepth
	// trips cleanly across nested invocations.
	SourceDepth int
}

SubExecOptions mirrors the top-level ExecOptions for the Context.Exec hook introduced in Phase 11 (when source/eval/. land). It is declared here so the public surface freezes from Phase 8 onward; the field set will grow alongside ExecOptions.

type TraceEvent

type TraceEvent struct {
	Category string
	Name     string
	Duration time.Duration
	Details  map[string]any
}

TraceEvent describes a single instrumentation point produced by the runtime or a built-in. The spec freezes this shape; the gobash root package re-exports it via type alias for backward compatibility with Phase 1's surface.

type TraceFunc

type TraceFunc = func(TraceEvent)

TraceFunc receives instrumentation events emitted by the runtime and by trace-instrumented built-ins (Phase 10 lands the field on Context; consumers arrive in later waves). Nil-safe: built-ins guard the call site rather than relying on a no-op default.

Jump to

Keyboard shortcuts

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