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

cli

package
v0.21.0-dev Latest Latest
Warning

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

Go to latest
Published: Sep 6, 2026 License: BSD-3-Clause Imports: 29 Imported by: 0

Documentation

Overview

bashy is a Bash 5.3 compatible shell built on top of interp.

Index

Constants

This section is empty.

Variables

View Source
var (
	AgentOSDispatch = func() {}

	// AgentOSShutdown runs immediately before the shell exits, on EVERY exit path.
	//
	// A shell exits with os.Exit, and os.Exit DOES NOT RUN DEFERRED FUNCTIONS. So a
	// `defer shutdown()` in main() is dead code in a shell — which is exactly how the
	// first version of bashy's OTel wiring shipped: it built, five unit tests passed
	// against an in-memory recorder, and a real collector received ZERO BYTES, because
	// the batch span processor was never flushed.
	//
	// A test that mocks the emitter proves the emitter was CALLED. It does not prove the
	// data arrived. Only an exit hook does, in a program that exits like this one.
	AgentOSShutdown = func() {}
	// posix is passed so AgentOS extensions (e.g. --dry-run) stay inert under
	// --posix and absent from the pure bash drop-in.
	AgentOSWireExec func([]interp.RunnerOption, bool, []string, io.Reader, io.Writer, io.Writer) []interp.RunnerOption = func(o []interp.RunnerOption, _ bool, _ []string, in io.Reader, out, err io.Writer) []interp.RunnerOption {
		return append(o, interp.StdIO(in, out, err))
	}

	// AgentOSPreamble returns shell source defining default functions/aliases
	// (e.g. `docker() { … bashy podman … }`) registered BEFORE user startup files
	// so the user can override them. No-op for the pure `bash` drop-in.
	AgentOSPreamble = func() string { return "" }

	// AgentOSUsage returns bashy-only usage text appended to `--help`. The pure
	// bash drop-in leaves it empty so its help stays close to GNU Bash.
	AgentOSUsage = func() string { return "" }

	// AgentOSCommandLineNoExec lets the bashy-only front end request the
	// interpreter's parse-only path without exposing a non-Bash option through
	// the pure bash binary. It is used to make --dry-run --posix fail safe:
	// POSIX mode does not install AgentOS execution handlers, so the combination
	// must validate input without executing it.
	AgentOSCommandLineNoExec func(bool) bool = func(bool) bool { return false }
	// AgentOSStrictPosixParse selects the portable POSIX grammar for an
	// AgentOS validation request. Ordinary bash --posix keeps Bash grammar.
	AgentOSStrictPosixParse func(bool) bool = func(bool) bool { return false }
	// AgentOSBashPPDefault identifies the bashy entry point without coupling the
	// shared CLI package to cmd/bashy. The pure bash binary leaves this false.
	AgentOSBashPPDefault = false

	// SuppressedForkBuiltins names the qiangli/sh fork's extra builtins that the
	// pure `bash` drop-in disables so its command table matches bash 5.3 exactly.
	// bash 5.3 has NONE of these as builtins (source-verified: no matching .def);
	// disabling them makes the name resolve to the real external command (or
	// "not found" where absent, e.g. setsid on macOS), byte-identical to bash:
	//   - nohup, setsid: the fork builtins them for in-process detach over a
	//     closed SSH session; the drop-in defers to /usr/bin/{nohup,setsid}.
	//   - newgrp: the fork builtin is a non-functional stub ("not supported")
	//     that shadows the WORKING external /usr/bin/newgrp — the drop-in defers
	//     to the real one, which actually switches group like bash.
	//   - strmatch: an engine-only builtin bash never had; the drop-in carries
	//     no non-bash command. (The internal `[[ ]]` matcher is a direct Go call,
	//     unaffected by disabling the user-facing builtin.)
	//   - awd: bashy's "alter working directory" agentic builtin; bash never had
	//     it, so the drop-in resolves the name externally ("command not found").
	// The `bashy` AgentOS shell clears this in its init() to KEEP them — an
	// in-process embedded shell needs `nohup foo &` to survive a closed remote
	// session, which an external nohup over a goroutine job can't do. Users opt
	// out per command with the bash-native `enable -n <name>`.
	SuppressedForkBuiltins = []string{"nohup", "setsid", "newgrp", "strmatch", "awd"}
)

AgentOSDispatch and AgentOSWireExec are the injection points for the AgentOS surface. The pure `bash` drop-in (cmd/bash) leaves them at their no-op defaults, so its import graph never touches coreutils. The `bashy` system shell (cmd/bashy) sets them to the internal/agentos implementations. This is what keeps the two binaries independent.

View Source
var (
	VersionProduct       = "GNU bash"
	VersionCompatibility string
)

VersionProduct and VersionCompatibility control only the human-facing --version banner. The pure cmd/bash binary keeps the GNU-compatible banner; cmd/bashy overrides these labels so the AgentOS product does not claim to be GNU Bash. BASH_VERSION remains the compatibility version in both binaries.

Functions

func BashyVersion added in v0.12.0

func BashyVersion() string

BashyVersion returns bashy's own release version ("0.9.1") extracted from the stamped bashVersion suffix ("5.3.0(1)-bashy-v0.9.1"), or "" for an unstamped/dev build. Consumers (the skills host-version probe) treat "" as "omit".

func BuildID added in v0.17.0

func BuildID() string

BuildID returns the stamped Git build identifier, usually an exact release tag or a short commit SHA with an optional "-dirty" suffix.

func Main

func Main()

Main is the shell entry point, shared by cmd/bash and cmd/bashy.

func MaybeRunJobCarrierHelper added in v0.20.0

func MaybeRunJobCarrierHelper()

MaybeRunJobCarrierHelper turns the current process into a job-carrier helper — a kernel-visible stand-in for one background job — when argv is exactly [argv0, "--bashy-job-carrier"]. It never returns in that case.

It must run before flag parsing, startup files, AgentOS dispatch and telemetry: a carrier is pure process identity and must not execute shell or user code. cli.Main calls it first; cmd/bashy additionally calls it before telemetry.Init so a carrier never initializes the OTel plane.

The platform hook relays externally delivered terminating signals through the carrier wait status, including notify-only signals the Go runtime would otherwise consume. Its lifetime is bounded by the parent-owned stdin pipe: it exits on EOF, so it cannot outlive a parent shell that dies without reaping it.

func NewSessionRunner added in v0.5.0

func NewSessionRunner(io SessionIO) (*interp.Runner, error)

NewSessionRunner builds a runner for one warm-session request. It mirrors newRunner's `-c` command path faithfully — same coreutils userland wiring (AgentOSWireExec), same bash version vars, PATH default, exported-function import and AgentOS preamble — but takes its stdio/env/dir explicitly instead of reading process globals, so a single process can serve many callers.

func RunSessionCommand added in v0.5.0

func RunSessionCommand(ctx context.Context, io SessionIO) int

RunSessionCommand parses and runs the session request's command string in a freshly built runner, returning the shell exit status. It is the warm-session analogue of the cold `-c` path.

Types

type BashPPBinary added in v0.21.0

type BashPPBinary string

BashPPBinary identifies which of the two compiled entry points (BashPPBinaryBash, cmd/bash's pure Bash 5.3 drop-in, or BashPPBinaryBashy, cmd/bashy's AgentOS shell) is resolving the dialect. It is the "binary default" tier and identifies the pure bash front door whose Sprint 114 POSIX+Bash++ combination is an inert compatibility profile.

const (
	BashPPBinaryBash  BashPPBinary = "bash"
	BashPPBinaryBashy BashPPBinary = "bashy"
)

type BashPPResolution added in v0.21.0

type BashPPResolution struct {
	Enabled bool
	Source  BashPPSource
	Posix   bool
}

BashPPResolution is the resolved dialect selector: whether Bash++ is on, and which precedence tier decided it.

func ResolveBashPP added in v0.21.0

func ResolveBashPP(sel BashPPSelector) (BashPPResolution, error)

ResolveBashPP resolves the initial Bash++ dialect for one selector, applying the documented precedence (explicit CLI > environment > .bpp extension > binary default). For the pure bash front door, .bpp is only a filename and an affirmative Bash++ selector paired with startup POSIX mode selects the Sprint 114 inertness profile: both extensions and POSIX-mode parser/runtime differences are disabled so its result is byte-identical to the selector-off, POSIX-off invocation. Ordinary bash --posix and explicit --no-bashpp retain the POSIX profile. bashy --posix --bashpp remains a supported combined mode because bashy is not the Classic front door.

func (BashPPResolution) LangVariant added in v0.21.0

func (r BashPPResolution) LangVariant() syntax.LangVariant

LangVariant returns the concrete construction-time interpreter dialect.

func (BashPPResolution) ParserOptions added in v0.21.0

func (r BashPPResolution) ParserOptions(base syntax.LangVariant, extra ...syntax.ParserOption) []syntax.ParserOption

ParserOptions composes the selected grammar with the POSIX semantic profile. Keeping both decisions in one API prevents callers from selecting LangBashPP while accidentally dropping syntax.PosixMode(true).

type BashPPSelector added in v0.21.0

type BashPPSelector struct {
	// Binary is which compiled entry point is asking. Required.
	Binary BashPPBinary
	// Args is an os.Args-shaped slice (Args[0] is the program name, not
	// scanned). May be nil.
	Args []string
	// LookupEnv resolves an environment variable by name, in the shape of
	// os.LookupEnv. May be nil, meaning no environment tier.
	LookupEnv func(name string) (value string, ok bool)
	// Filename is the script path about to be parsed ("" for -c/stdin/
	// interactive input). The .bpp convention is only a source label for the
	// Bash++-default bashy binary; the bash drop-in requires an explicit
	// CLI/environment selector.
	Filename string
	// Posix is the already-resolved startup POSIX mode (effectiveStartupPosix
	// in main.go), not merely the --posix flag's literal presence.
	Posix bool
}

BashPPSelector is the input to ResolveBashPP: everything the precedence chain reads to decide the initial grammar for one parse. It takes an argv-shaped slice and an env lookup func, rather than reading os.Args and os.Environ directly, so resolution stays a pure, independently testable function — main.go supplies the real values when B2 wires this in.

type BashPPSource added in v0.21.0

type BashPPSource string

BashPPSource names which precedence tier decided a BashPPResolution.

const (
	BashPPSourceCLI           BashPPSource = "cli"            // --bashpp / --bash++ / --no-bashpp
	BashPPSourceEnv           BashPPSource = "env"            // BASHY_BASHPP=1|0
	BashPPSourceExtension     BashPPSource = "extension"      // .bpp
	BashPPSourceBinaryDefault BashPPSource = "binary-default" // bash off, bashy on
)

func (BashPPSource) Explicit added in v0.21.0

func (s BashPPSource) Explicit() bool

Explicit reports whether the source is a deliberate user request (a CLI flag or an environment variable) rather than an inferred default (the file extension or the binary's product default).

type SessionIO added in v0.5.0

type SessionIO struct {
	Command string    // the -c command string
	Dir     string    // working directory ("" = server cwd)
	Env     []string  // caller environment (replaces os.Environ())
	Stdin   io.Reader // caller stdin
	Stdout  io.Writer // caller stdout
	Stderr  io.Writer // caller stderr
}

SessionIO carries the per-request stdio/env/dir for a command run inside a warm `bashy serve` session. One serve process builds a fresh runner per request from this, so a session-routed `bashy -c "…"` behaves identically to a cold `bashy -c "…"` while skipping the process- and package-init tax.

Jump to

Keyboard shortcuts

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