Documentation
¶
Overview ¶
Package cli contains side-effect-free command orchestration for FlowBaton.
Index ¶
- Constants
- Variables
- func DefaultExploreCrew(deps ExploreDeps) (explore.Crew, error)
- func DefaultExploreModels(getenv func(string) string) (explore.ModelSet, error)
- func DefaultOutputDirectory(options TestOptions, home string, now time.Time) string
- func ExitCodeFor(err error) int
- type ArtifactSink
- type AuthRunner
- type BugreportRunner
- type CheckSyntaxRunner
- type Checker
- type CryptoRandom
- type DBRunner
- type DeviceSession
- type DriverRecordingController
- func (controller *DriverRecordingController) Close(ctx context.Context) error
- func (controller *DriverRecordingController) Start(ctx context.Context, request engine.RecordingStartRequest) error
- func (controller *DriverRecordingController) Stop(ctx context.Context) ([]device.Artifact, error)
- func (controller *DriverRecordingController) StopAll(ctx context.Context) ([]device.Artifact, error)
- type DriverSetupRunner
- type ExploreDeps
- type ExploreInvoker
- type ExploreRunner
- type ExploreToolOptions
- type ExploreToolResult
- type GenerateCompletionRunner
- type HierarchyRunner
- type ImageChecker
- type InputGenerator
- type ListDevicesRunner
- type MCPRunner
- type ParserChecker
- type Preflight
- type QueryRunner
- type RecordRunner
- type ResourceReader
- type ScreenshotRunner
- type ServeBootstrap
- type ServeOptions
- type ServeRunner
- type SessionFactory
- type Shard
- type Source
- type StartDeviceRunner
- type TestOptions
- type TestRunner
- type TestSession
- type UnsupportedRecordingController
- type UsageError
Constants ¶
const ( ExitOK = 0 ExitInvalid = 2 CheckSyntaxUsage = "usage: flowbaton check-syntax FILE|-\n" )
const ExitFailure = 1
ExitFailure is the exit code for a run that was understood and did not pass.
const ExploreUsage = "usage: flowbaton explore --app ID -p ios|android|web [--device UDID] " +
"[--driver-port PORT] [--max-tests N] [--max-steps N] [--styles LIST] " +
"[--state-dir DIR] [--output DIR] [--session-name NAME] [--pilot] [--record] " +
"[--api-key KEY] [--api-url URL]\n"
ExploreUsage is the one-line usage for the subcommand.
const TestUsage = "usage: flowbaton test [options] FILE|DIR...\n"
TestUsage is the one-line usage for the subcommand.
Variables ¶
var ErrExploreNotAssembled = errors.New("explore support is not assembled in this build")
ErrExploreNotAssembled reports that this build has no exploration crew wiring behind the runner's injectable seams.
var TopLevelSubcommands = []string{
"check-syntax",
"test",
"record",
"explore",
"list-devices",
"start-device",
"hierarchy",
"query",
"bugreport",
"driver-setup",
"mcp",
"serve",
"db",
"auth",
"generate-completion",
}
TopLevelSubcommands is the canonical list of subcommands flowbaton dispatches. generate-completion reads it so a new command shows up in completion the moment it is added here; a test pins the set so dispatch and completion do not silently drift apart.
Functions ¶
func DefaultExploreCrew ¶
func DefaultExploreCrew(deps ExploreDeps) (explore.Crew, error)
DefaultExploreCrew assembles the production role implementations over one open driver. Memory stores live under the configured state directory; the research cache keeps UI maps across sessions.
func DefaultExploreModels ¶
DefaultExploreModels builds the tiered chat models from the process environment through the aiengine provider wall.
func DefaultOutputDirectory ¶
func DefaultOutputDirectory(options TestOptions, home string, now time.Time) string
DefaultOutputDirectory resolves where a run's artifacts go.
specs/03-cli-tooling.md section 1 gives the precedence: an explicit --test-output-dir wins, then --debug-output, then a timestamped directory under the user's home. --flatten-debug-output drops the timestamp segment, which is what a CI job wants when it collects a fixed path.
func ExitCodeFor ¶
ExitCodeFor classifies an error into the documented exit codes. Anything not explicitly a usage error is a failure: a misclassification that returned OK would report a green suite that never ran.
Types ¶
type ArtifactSink ¶
type ArtifactSink struct {
// contains filtered or unexported fields
}
ArtifactSink writes run artifacts, into one of two directories.
A name the flow author wrote resolves against the process working directory: a flow saying `takeScreenshot: settings` leaves ./settings.png where the operator ran from, whichever directory the flow itself lives in. Everything the author did not name — the automatic failure captures — is the run's own bookkeeping and stays in the run directory.
func NewArtifactSink ¶
func NewArtifactSink(directory, authoredDirectory string) *ArtifactSink
func (*ArtifactSink) Write ¶
func (sink *ArtifactSink) Write( _ context.Context, request engine.ArtifactWriteRequest, ) (engine.ArtifactWriteResult, error)
type AuthRunner ¶
type BugreportRunner ¶
type BugreportRunner struct {
Collect func(ctx context.Context, serial, outputPath string) error
CollectIOS func(ctx context.Context, udid, outputPath string) error
ResolveSerial func(context.Context) (string, error)
}
BugreportRunner holds the collection and serial resolution behind fields so a test can record the call without a device. The defaults reach adb / simctl.
type CheckSyntaxRunner ¶
CheckSyntaxRunner enforces the command's exact stdout/stderr/exit contract.
type Checker ¶
Checker owns parse plus recursive capability preflight for one selected root. A successful return is the sole authority for printing OK.
type CryptoRandom ¶
type CryptoRandom struct{}
CryptoRandom is the process random source, shared by the JS faker binding and the random-input commands so there is one notion of randomness.
func (CryptoRandom) Intn ¶
func (CryptoRandom) Intn(n int) int
type DeviceSession ¶
type DeviceSession struct {
Driver device.Driver
// OutputDirectory receives screenshots and other run artifacts.
OutputDirectory string
// BaseDirectory resolves flow resources such as runScript files.
BaseDirectory string
// Clock is optional; a nil clock uses the wall clock. Tests supply their
// own so a flow's waits do not take real time.
Clock engine.Clock
// ExecutionID is optional; a blank one is derived from the run's own start.
ExecutionID string
// Shard is which share of the run this session executes. It is what the
// reserved FLOWBATON_SHARD_* and FLOWBATON_DEVICE_UDID variables are built
// from, so a flow can report where it ran.
Shard Shard
}
DeviceSession runs a prepared program against one device.
It exists to hold the assembly of engine.Dependencies in one place. That assembly is the part where a missing service is invisible: the engine takes every boundary as an interface, so a nil one compiles and then fails inside a flow, where it reads as a flow failure rather than a wiring mistake.
func NewDeviceSession ¶
func NewDeviceSession(ctx context.Context, options TestOptions, shard Shard) (DeviceSession, error)
NewDeviceSession builds a session for one shard, or explains why it cannot.
func (DeviceSession) Execute ¶
func (session DeviceSession) Execute( ctx context.Context, program *engine.Program, options TestOptions, ) ([]engine.FlowResult, error)
Execute opens the device, runs every selected root, and closes the device even when a flow fails.
type DriverRecordingController ¶
type DriverRecordingController struct {
// contains filtered or unexported fields
}
DriverRecordingController holds the capture id between the two commands.
func NewDriverRecordingController ¶
func NewDriverRecordingController( driver screenRecordingDriver, directory string, ) *DriverRecordingController
func (*DriverRecordingController) Close ¶
func (controller *DriverRecordingController) Close(ctx context.Context) error
Close makes DriverRecordingController usable as a cleanup resource while retaining StopAll's artifact-returning form for callers that need evidence.
func (*DriverRecordingController) Start ¶
func (controller *DriverRecordingController) Start( ctx context.Context, request engine.RecordingStartRequest, ) error
type DriverSetupRunner ¶
DriverSetupRunner builds the driver. The build is a field so a test can record the invocation without running xcodebuild.
type ExploreDeps ¶
type ExploreDeps struct {
Driver device.Driver
Models explore.ModelSet
Config explore.Config
Stdout io.Writer
}
ExploreDeps carries everything crew assembly needs.
type ExploreInvoker ¶
type ExploreInvoker interface {
Explore(ctx context.Context, options ExploreToolOptions) (ExploreToolResult, error)
}
ExploreInvoker runs one exploration session for the explore tool. An interface rather than a struct-of-funcs so a test injects one fake with the whole behavior.
type ExploreRunner ¶
type ExploreRunner struct {
// NewModels builds the tiered model set from the environment. Nil means
// this build carries no model wiring, and the command refuses after flag
// validation.
NewModels func(getenv func(string) string) (explore.ModelSet, error)
// NewCrew assembles the role implementations. Nil refuses like NewModels.
NewCrew func(deps ExploreDeps) (explore.Crew, error)
// NewDriver is injected by tests; nil constructs the real platform driver
// through the same path the test session uses.
NewDriver func(ctx context.Context, options TestOptions, udid string, port int) (device.Driver, error)
// Clock is optional; nil uses the wall clock. The default session name
// and output directory read it.
Clock engine.Clock
// Getenv is optional; nil reads the process environment.
Getenv func(string) string
// Environ is optional; nil reads the process environment. It feeds the
// same diagnostic-port fallback `hierarchy` uses.
Environ func() []string
}
ExploreRunner runs one exploration session end to end: resolve the device, open the driver once, drive the session, write the report and exported flows, close the driver.
func ProductionExploreRunner ¶
func ProductionExploreRunner() ExploreRunner
ProductionExploreRunner returns the fully assembled explore command: chat models from the environment and the production crew over the session driver. The zero ExploreRunner stays refusal-only so tests can assert the unassembled error; the entry point calls this instead.
type ExploreToolOptions ¶
type ExploreToolOptions struct {
AppID string
Platform string
Device string
DriverPort int
MaxTests int
MaxSteps int
OutputDir string
}
ExploreToolOptions is one resolved explore invocation. OutputDir, when set, has already been confined to the MCP base directory.
type ExploreToolResult ¶
ExploreToolResult carries the session report markdown and the exported flow paths.
type GenerateCompletionRunner ¶
type GenerateCompletionRunner struct{}
GenerateCompletionRunner emits a shell completion script. Pure: no device, no I/O beyond stdout.
type HierarchyRunner ¶
type HierarchyRunner struct {
Fetch func(ctx context.Context, platform, udid string, appIDs []string, target string) (device.TreeNode, error)
}
HierarchyRunner holds the tree fetch behind a field so a test can stand in a known tree without a device. The default opens the real driver, snapshots, and closes it.
type ImageChecker ¶
type ImageChecker struct{}
ImageChecker forwards to the shared check rather than repeating it.
func (ImageChecker) Check ¶
func (ImageChecker) Check( _ context.Context, request engine.ImageCheckRequest, ) (imagecheck.Result, error)
type InputGenerator ¶
type InputGenerator struct {
// contains filtered or unexported fields
}
InputGenerator supplies values for the inputRandom* commands.
func NewInputGenerator ¶
func NewInputGenerator() *InputGenerator
NewInputGenerator builds a generator over the process random source.
func (*InputGenerator) Generate ¶
func (generator *InputGenerator) Generate( _ context.Context, request engine.InputRequest, ) (string, error)
type ListDevicesRunner ¶
type ListDevicesRunner struct {
IOS func(context.Context) ([]ios.Device, error)
IOSPhysical func(context.Context) ([]iosdevice.Device, error)
Android func(context.Context) ([]android.Device, error)
}
ListDevicesRunner holds the listing calls behind fields so a test can stand in a fake without a simulator or an attached phone. The defaults reach real tooling.
func (ListDevicesRunner) Run ¶
func (runner ListDevicesRunner) Run(ctx context.Context, args []string, stdout, stderr io.Writer) int
Run lists devices for the selected platform(s). With no -p it lists both, and a platform whose tooling is absent is a note rather than a failure — the command is most wanted exactly on a machine that has only one SDK. With an explicit -p, that platform's error is the failure the operator asked to hear.
type MCPRunner ¶
type MCPRunner struct {
Checker Checker
ListDevices ListDevicesRunner
Hierarchy HierarchyRunner
Query QueryRunner
// RunFlow executes run_flow calls through the same pipeline as the test
// subcommand. Its zero value builds real device sessions.
RunFlow TestRunner
// Screenshot captures one frame from a device. Its zero value opens the
// real driver, in the same way the hierarchy diagnostic does.
Screenshot ScreenshotRunner
// StartDevice boots simulators and launches emulators through the same
// orchestration as the start-device subcommand. Its zero value reaches
// simctl and the Android tooling.
StartDevice StartDeviceRunner
// Explore serves the explore tool. Nil adapts the real ExploreRunner,
// which refuses until its crew wiring is assembled.
Explore ExploreInvoker
// BaseDir confines inline flow links exposed through MCP. Run fills it from
// --base-dir (or the current working directory).
BaseDir string
}
MCPRunner builds and serves the flowbaton MCP server over stdio. Its dependencies are fields so a test can drive the same server through an in-memory transport.
type ParserChecker ¶
type ParserChecker struct {
Preflight Preflight
}
ParserChecker parses the selected root before invoking recursive preflight. A missing preflight fails closed and can never produce a successful check.
func NewParserChecker ¶
func NewParserChecker() ParserChecker
NewParserChecker returns the production syntax checker. Parsing is followed by recursive capability analysis before a syntax check can succeed.
type Preflight ¶
Preflight is the narrow integration boundary required from the recursive graph/capability package. It receives the parsed root and the cwd resolution context, and must remain side-effect-free.
type QueryRunner ¶
type QueryRunner struct {
Fetch func(ctx context.Context, platform, udid, appID, expression string) ([]device.TreeNode, error)
}
QueryRunner holds the query behind a field so a test can match without a device. The default opens the real driver, queries, and closes it.
type RecordRunner ¶
type RecordRunner struct {
NewSession SessionFactory
}
RecordRunner records one flow. The session hook is the same one TestRunner takes, so a test drives it without a device.
type ResourceReader ¶
type ResourceReader struct {
// contains filtered or unexported fields
}
ResourceReader reads flow resources confined to one base directory.
func NewResourceReader ¶
func NewResourceReader(baseDirectory string) *ResourceReader
func (*ResourceReader) Read ¶
func (reader *ResourceReader) Read( _ context.Context, request engine.ResourceReadRequest, ) (engine.ResourceReadResult, error)
type ScreenshotRunner ¶
type ScreenshotRunner struct {
Fetch func(ctx context.Context, platform, udid string) ([]byte, error)
}
ScreenshotRunner holds the frame capture behind a field so a test can stand in known bytes without a device. The default opens the real driver, captures one uncompressed frame, and closes it.
type ServeBootstrap ¶
type ServeBootstrap struct {
OpenStore func(context.Context, string) (serveRuntimeStore, error)
BuildDriver func(context.Context, serveDevice) (device.Driver, error)
LoadTLS func(string, string, string) (*tls.Config, error)
LoadPrivateKey func(string, string) (string, ed25519.PrivateKey, error)
RunServer func(context.Context, server.RuntimeConfig) error
Executable func() (string, error)
ProcessID func() int
}
func (ServeBootstrap) Run ¶
func (bootstrap ServeBootstrap) Run(ctx context.Context, options ServeOptions) (resultErr error)
type ServeOptions ¶
type ServeOptions struct {
Address string
DatabaseURL string
TLSCertificate string
TLSPrivateKey string
ClientCA string
SigningKey string
SigningKeyID string
NodeID string
PublicAddress string
Inventory string
WorkerConcurrency int
WorkerPoll time.Duration
WorkerClaim time.Duration
WorkerTimeout time.Duration
NodeHeartbeat time.Duration
}
type ServeRunner ¶
type ServeRunner struct {
Serve func(context.Context, ServeOptions) error
}
ServeRunner parses the public serve command and delegates construction to the runtime bootstrap owned by main. The field keeps secrets and listeners out of argument parsing tests.
func DefaultServeRunner ¶
func DefaultServeRunner() ServeRunner
type SessionFactory ¶
type SessionFactory func(context.Context, Shard, TestOptions) (TestSession, error)
SessionFactory builds the session for one shard.
type Shard ¶
type Shard struct {
// Index is 0-based; the human-facing number is Index+1.
Index int
// Roots are the flow paths this shard runs, in the plan's own order.
Roots []string
// Device is the udid this shard runs on. It is blank for an unsharded run
// with no --device, so the driver keeps saying which flag is missing.
Device string
// OutputDirectory is where this shard's artifacts go. The runner fills it
// in after planning; planning itself has no opinion about the filesystem.
OutputDirectory string
// DriverPort is the loopback port this shard's driver talks to. Shard 1
// keeps the contract's port; later shards get one each, because two shards
// on one port drive the same runner.
DriverPort int
}
Shard is one device's share of a run.
func PlanShards ¶
PlanShards divides a discovered plan into per-device shards.
type Source ¶
type Source struct {
Name string
BaseDir string
// ConfineTo, when non-empty, requires every linked flow to remain beneath
// this directory after symlink evaluation. The CLI leaves it empty because
// an operator-selected flow may intentionally link elsewhere; MCP sets it to
// --base-dir because inline content is untrusted client input.
ConfineTo string
Data []byte
}
Source is one selected syntax-check root. BaseDir is always the invocation working directory; this makes links from stdin resolve from cwd without changing their user-facing source name from "-".
func (Source) ResolveLink ¶
ResolveLink resolves a parser-produced link against the invocation working directory. Absolute links remain absolute.
type StartDeviceRunner ¶
type StartDeviceRunner struct {
// Boot boots an existing iOS simulator by udid (default: simctl boot).
Boot func(ctx context.Context, platform, udid string) error
// ListAVDs enumerates installed Android AVD names (default: emulator -list-avds).
ListAVDs func(ctx context.Context) ([]string, error)
// LaunchAVD launches an Android AVD by name (default: emulator -avd <name>).
LaunchAVD func(ctx context.Context, avd, locale string) error
// CreateSim creates a new iOS simulator and returns its udid (default: simctl create).
CreateSim func(ctx context.Context, options deviceCreateOptions) (string, error)
// CreateAVD creates a new Android AVD and returns its name (default: avdmanager create).
CreateAVD func(ctx context.Context, options deviceCreateOptions) (string, error)
// WaitReady blocks until the started target is usable (default: simctl
// bootstatus or adb's sys.boot_completed property).
WaitReady func(ctx context.Context, platform, target string) error
// ReadyTimeout bounds readiness independently from a long-lived parent.
ReadyTimeout time.Duration
// ConfigureLocale applies the requested locale after an iOS simulator is
// booted. Android receives its locale as an emulator launch property.
ConfigureLocale func(ctx context.Context, platform, target, locale string) error
}
StartDeviceRunner holds each external operation behind a field so tests can record calls without a real device. The defaults reach simctl/emulator.
type TestOptions ¶
type TestOptions struct {
Roots []string
ConfigPath string
Env map[string]string
IncludeTags []string
ExcludeTags []string
Format string
TestSuiteName string
Output string
DebugOutput string
TestOutputDir string
FlattenDebugOutput bool
Continuous bool
Headless bool
ReinstallDriver bool
ScreenSize string
APIURL string
APIKey string
Platform string
Devices []string
ShardSplit int
ShardAll int
// RecordTo is where `record` wants the video. It is NOT parsed from a
// command line: the `test` command has no such flag. RecordRunner sets it.
RecordTo string
// SequencedRoots and ContinueOnFailure come from the workspace's
// executionOrder, not from argv. They decide whether a failed flow ends
// the suite; see engine.Dependencies for the rule.
SequencedRoots int
ContinueOnFailure bool
// contains filtered or unexported fields
}
TestOptions is one parsed `test` command line.
func ParseTestOptions ¶
func ParseTestOptions(args []string) (TestOptions, error)
ParseTestOptions reads a `test` command line.
Flags and positionals may interleave. Boolean flags never consume the following argument — doing so would silently drop a flow from the run, which is the worst way for a suite to pass.
type TestRunner ¶
type TestRunner struct {
// NewSession acquires one shard's device and executes its flows. It is
// injected because no production session can be built without a device; a
// nil factory falls back to the real one.
NewSession SessionFactory
Loader capability.FlowLoader
// Clock is optional; nil uses the wall clock. It fixes the run's own notion
// of now, which both the default output directory and the report timestamp
// read, so two identical runs render identical bytes.
Clock engine.Clock
// Environ is optional; nil reads the process environment. It supplies the
// shell's FLOWBATON_ variables, per specs/01-core-engine.md:101.
Environ func() []string
// AllocatePort is optional; nil asks the operating system for an ephemeral
// port. Injected so a test can pin the ports a sharded run picks.
AllocatePort func() (int, error)
// PollInterval is how often continuous mode re-stamps its watch set; zero
// uses defaultPollInterval. Injected so a test does not wait on it.
PollInterval time.Duration
// RecordTo is set by RecordRunner, never by argv. See TestOptions.RecordTo.
RecordTo string
}
TestRunner discovers flows, preflights them, splits them into shards, and runs each shard on its own device.
Discovery, preflight, and shard planning all run BEFORE anything touches a device. That order is the point: a config error, an unknown command, a tag filter that selects nothing, or a shard count with no devices behind it are all knowable without a simulator, and finding them after devices have been acquired wastes the slowest part of a run and reports a setup mistake as a flow failure.
func (TestRunner) Run ¶
func (runner TestRunner) Run( ctx context.Context, args []string, stdout io.Writer, stderr io.Writer, ) int
Run executes one `test` invocation and returns its process exit code.
func (TestRunner) RunOptions ¶
func (runner TestRunner) RunOptions( ctx context.Context, options TestOptions, stdout io.Writer, stderr io.Writer, ) int
RunOptions is Run past its argument parsing. `record` uses it: it has to read the positionals itself (a flow plus an optional output file), so it parses the same options and then hands them over rather than rebuilding a command line for this to parse a second time.
type TestSession ¶
type TestSession interface {
Execute(context.Context, *engine.Program, TestOptions) ([]engine.FlowResult, error)
}
TestSession is the boundary between the host pipeline and a device.
type UnsupportedRecordingController ¶
type UnsupportedRecordingController struct{}
UnsupportedRecordingController refuses both halves of the recording lifecycle.
It is what a driver without the stop half gets. Reporting success and producing no file would tell a run it has a recording it does not have.
func (UnsupportedRecordingController) Start ¶
func (UnsupportedRecordingController) Start(context.Context, engine.RecordingStartRequest) error
type UsageError ¶
type UsageError struct {
Message string
}
UsageError marks a command line that could not be understood. It is the only thing that exits 2; everything else exits 1.
func (*UsageError) Error ¶
func (err *UsageError) Error() string
Source Files
¶
- android_agent_apks.go
- android_sdk.go
- auth_runtime.go
- bugreport.go
- bugreport_simctl.go
- capability_preflight.go
- check_syntax.go
- continuous.go
- db_runtime.go
- debug_artifacts.go
- device_selection.go
- driver_assets.go
- driver_ports.go
- driver_setup.go
- explore.go
- explore_wire.go
- generate_completion.go
- hierarchy.go
- ios_runner_bundle.go
- list_devices.go
- mcp.go
- mcp_viewer.go
- query.go
- query_match.go
- record.go
- recording.go
- reporting.go
- reserved_environment.go
- serve_runtime.go
- services.go
- session.go
- shard_devices.go
- sharding.go
- start_device.go
- test_options.go
- test_runner.go
- web_chrome_options.go