ax

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Jun 11, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

README

ax-go

Agentic Experience (AX) foundation for Go CLI tools — the "Common DNA" for the rshade portfolio.

Go License

Status: 🚧 Implementation scaffold. The module, license, accepted ADRs, and initial Go package skeleton are in place. The public API is still early, but core primitives such as ax.Error, ax.Execute, ax.NewLogger, ax.ParseConfig, and ax.NewEntityID now compile and are covered by focused contract tests.

Mission

ax-go is the shared foundation that standardizes Agentic Experience (AX) across Go-based CLI tools. Its goal is simple:

Ensure all Go-based CLI tools are as powerful and predictable for LLM agents as they are for human engineers.

Rather than every CLI reinventing how it talks to an autonomous agent — how it emits data, reports errors, exposes its command tree, and stays safe under retries — ax-go encodes those conventions once so the whole portfolio shares the same predictable behavior.

Core Standards

These are the non-negotiable mandates every tool built on ax-go must follow.

The Golden Rule — stream separation
  • stdout is strictly reserved for the final data payload (JSON).
  • stderr carries everything else: logs, progress indicators, and structured error envelopes.

This lets an agent pipe stdout straight into a JSON parser while humans and log collectors read stderr.

Deterministic exit codes
Code Meaning
0 success
1 unknown / internal error
2 validation / bad input
3 network / timeout
4 authentication / permission
Determinism is machine-readable trust

Given the same inputs, two runs of the same command produce byte-identical stdout (modulo documented non-deterministic fields — timestamps, trace_id, auto-generated idempotency_key). This is the machine equivalent of trust: agents diff outputs across runs to detect drift, so determinism is what lets an agent safely delegate to an ax-go CLI. It is a stronger guarantee than the AX literature demands, and it is the project's clearest differentiator.

Machine discoverability (__schema)

Every tool implements a __schema command that emits a structured JSON map of its command tree, flags, types, and examples — so agents can ground themselves without guessing. The primary format is ax-native JSON, with __schema --as=mcp available as an MCP-compatible adapter (ADR-0003).

Asymmetric JSON flow
  • Input: accept Hujson (comments and trailing commas) for human convenience. Config reads are capped at 1 MiB by default at the read boundary; use ax.WithMaxConfigBytes when a CLI intentionally supports a larger bounded config. The public helpers are ax.ParseConfig(ctx, reader, &cfg, ...) and ax.ParseConfigFile(ctx, path, &cfg, ...), so slow cooperative sources can be canceled through context.Context.
  • Output: emit strict, minified JSON for bounded payloads; emit NDJSON for streaming / unbounded result sets.

Config read rejections are standard ax.Error envelopes. Oversized input uses the frozen error_code config_too_large; an out-of-range cap (negative or above ax.MaxConfigBytesCeiling, 1 GiB) uses config_max_bytes_invalid. Invalid Hujson or schema mismatches use config_invalid, and a nil ParseConfigOption uses config_option_invalid. All map to exit code 2 and are discoverable with errors.As(err, &axErr). Reads accept Hujson extensions, but writes remain strict JSON; preserving comments when mutating existing Hujson is reserved for the future AST Patch write path.

Agent-safety primitives
  • --idempotency-key — auto-generated UUID v4 if absent and surfaced in the output envelope, killing duplicate-create from agent retries.
  • --dry-run — universal middleware that emits the same envelope with dry_run: true and performs no side effects.
  • --format flag / AGENT_MODE env var / TTY auto-detect — selects machine vs. human output mode (ADR-0001).
Standard ax.Error envelope

A structured, machine-readable error format emitted to stderr. Schema defined in ADR-0002.

Engineering Standards

  • Allocation discipline: track allocations via standard testing.B benchmarks; target zero or near-zero allocations on hot paths. Benchmark serializer choices rather than asserting numeric bars.
  • Trace propagation: contexts carry and propagate W3C Trace Context IDs by default, via the OpenTelemetry SDK (ADR-0004, ADR-0005).
  • Observability backends: Grafana Loki for log aggregation (ADR-0006); Tempo / Jaeger / Honeycomb-compatible for traces via OTel.
  • ID strategy: OTel trace/span IDs for observability; UUID v4 for idempotency keys; UUID v7 for resource and entity IDs (ADR-0007). Never mix observability IDs with resource/entity IDs.
  • CLI framework: built on Cobra (ADR-0008). ax.Execute() wraps Cobra execution for mode resolution, schema wiring, error-envelope output, and OTel flush-on-exit.
  • Structured logging: ax.NewLogger(ctx) returns an ax.Logger backed by zerolog with trace correlation wired in (ADR-0009).
  • Idiomatic Go: package name is ax. Keep abstractions narrow and tied to accepted ADRs.

Architecture Decisions (ADRs)

The ADRs are a frozen legacy decision log. New public API or runtime behavior changes go through the Spec Kit workflow and record decisions in the feature's research.md; retired ADR decisions are absorbed there before the ADR file is deleted.

ADR Title Status
0001 Agent-Mode Trigger Accepted (2026-05-28)
0002 JSON Error Envelope Schema Accepted (2026-05-28)
0003 __schema Output Format Accepted (2026-05-28)
0004 Trace ID Format Accepted (2026-05-28)
0005 OpenTelemetry SDK Integration Accepted (2026-05-28)
0006 Grafana Loki Backend Integration Accepted (2026-05-28)
0007 ID Strategy Accepted (2026-05-28)
0008 CLI Framework — Cobra Accepted (2026-05-28)
0009 Structured Logger — ZeroLog Accepted (2026-05-28)
0011 Output Payload Format — Strict JSON / NDJSON Accepted (2026-05-28)
0012 Directory Layout Accepted (2026-05-30)

Full text and rationale live in docs/adr/.

Repository Layout

The public import path remains one package: github.com/rshade/ax-go as ax. Per Go's official module layout guidance, public package files stay at the module root. Private implementation mechanics live under internal/ so they do not become accidental public API before v1.0. Public JSON contract fixtures live under testdata/. Runnable support binaries belong under cmd/ when real command behavior exists. pkg/, src/, and broad public subpackages are intentionally avoided.

Examples

The runnable integration command in examples/integration/ exercises the public ax-go API from a real Cobra CLI. It covers bounded JSON envelopes, NDJSON streaming, Hujson config parsing, __schema, structured ax.Error output, idempotency keys, and stderr logging.

go run ./examples/integration --format=json --idempotency-key=demo-key --name=Ada
go run ./examples/integration stream --format=json --count=3
go run ./examples/integration __schema
go run ./examples/integration fail --format=json

Build-time version injection

Production CLIs built on ax-go should resolve their version once at process startup and pass the same value to every version surface. Keep the linker target as a writable var, then use ax.ResolveVersion:

var version string // set by -ldflags "-X main.version=..."

func run(ctx context.Context, root *cobra.Command) int {
    resolved := ax.ResolveVersion(version)

    logger := ax.NewLogger(ctx, ax.WithLoggerLabels(ax.Labels{
        Application: "mytool",
        Version:     resolved,
    }))
    _ = logger

    return ax.Execute(ctx, root, ax.WithVersion(resolved))
}

ResolveVersion returns a non-placeholder injected value when present, otherwise it falls back to the running binary's Go build metadata (Main.Version, then vcs.revision with a dirty marker) and finally to 0.0.0-unknown. It never returns an empty string or the bare placeholders dev or unknown.

Build the integration example with the documented injection target:

make build-example
./bin/ax-integration __schema

The target injects git describe --tags --always --dirty into main.version:

go build -ldflags "-X main.version=$(git describe --tags --always --dirty)" \
  -o bin/ax-integration ./examples/integration

Override VERSION for release and reproducible builds:

make build-example VERSION=v1.2.3

The same resolved value feeds __schema.version, the ax.Error envelope version, and the logger version label.

Roadmap

Sequenced from the accepted ADRs and the current scaffold:

  1. Complete telemetry exporters — keep the no-op default, add OTEL_EXPORTER_OTLP_ENDPOINT OTLP/HTTP auto-configuration, and add the AX_OTEL_DEBUG=1 stderr exporter path from ADR-0005.
  2. Harden __schema — enforce example coverage, expand output-mode declarations, and mature the MCP adapter from ADR-0003.
  3. Implement Loki direct push — keep stderr shipping as the default and add opt-in AX_LOKI_URL direct push from ADR-0006.
  4. Expand examples and benchmarks — keep examples/integration/ current with public API changes and benchmark hot paths with testing.B / -benchmem.

Contributing

Before changing public behavior, use the Spec Kit feature workflow. Read the constitution, absorb any governing frozen ADR decisions into the feature's research.md, and keep README plus examples/integration/ current with the public contract. Do not create or edit ADRs for new work.

License

Licensed under the Apache License 2.0.

Documentation

Overview

Package ax provides the Agentic Experience foundation for Go CLI tools.

The package keeps machine payloads on stdout, operational output on stderr, and exposes shared primitives for mode resolution, error envelopes, discoverability schemas, idempotency keys, logging, and trace propagation.

Index

Examples

Constants

View Source
const (
	// DefaultMaxConfigBytes is the default maximum config size: 1 MiB.
	DefaultMaxConfigBytes int64 = 1 << 20
	// MaxConfigBytesCeiling is the largest valid config read limit: 1 GiB.
	MaxConfigBytesCeiling int64 = internalconfig.MaxConfigBytesCeiling
)
View Source
const (
	// ExitSuccess indicates successful completion.
	ExitSuccess = 0
	// ExitInternal indicates an unknown or internal error.
	ExitInternal = 1
	// ExitValidation indicates invalid input or failed validation.
	ExitValidation = 2
	// ExitNetwork indicates a network failure or timeout.
	ExitNetwork = 3
	// ExitAuth indicates an authentication or permission failure.
	ExitAuth = 4
)
View Source
const (
	// ZeroTraceID is a valid zero-value W3C trace ID for no-active-span cases.
	ZeroTraceID = "00000000000000000000000000000000"
	// ZeroSpanID is a valid zero-value W3C span ID for no-active-span cases.
	ZeroSpanID = "0000000000000000"
)
View Source
const (
	// ErrorSchemaVersion is the current SemVer version of the error envelope.
	ErrorSchemaVersion = "1.0.0"
)
View Source
const ModeDetectionRule = "--format flag > AGENT_MODE env > TTY detection"

ModeDetectionRule documents the ADR-0001 resolution precedence applied by ResolveMode. It is surfaced verbatim in __schema output.

View Source
const SchemaVersion = ErrorSchemaVersion

SchemaVersion is the current SemVer version for ax-native schemas.

Variables

This section is empty.

Functions

func DryRunFromContext

func DryRunFromContext(ctx context.Context) bool

DryRunFromContext reports whether dry-run behavior is active.

func ErrorExitCode

func ErrorExitCode(err error) int

ErrorExitCode maps an error to the deterministic ax-go process exit code: nil maps to ExitSuccess (0); an *Error anywhere in the chain maps to its explicit ExitCode, winning over any sentinel buried in its cause chain; a non-envelope error wrapping context.DeadlineExceeded maps to ExitNetwork (3) and one wrapping context.Canceled to ExitInternal (1); anything else maps to ExitInternal (1).

func Execute

func Execute(ctx context.Context, root *cobra.Command, opts ...ExecuteOption) int

Execute wraps Cobra execution with AX mode resolution, idempotency, schema, error-envelope, and telemetry lifecycle behavior. It returns a deterministic exit code and leaves process termination to the caller.

func GRPCDial

func GRPCDial(ctx context.Context, target string, opts ...grpc.DialOption) (*grpc.ClientConn, error)

GRPCDial dials target with OTel client instrumentation.

func HTTPClient

func HTTPClient() *http.Client

HTTPClient returns an HTTP client with OTel propagation instrumentation.

func IdempotencyKeyFromContext

func IdempotencyKeyFromContext(ctx context.Context) (string, bool)

IdempotencyKeyFromContext returns the idempotency key stored in ctx.

func NewEntityID

func NewEntityID() (string, error)

NewEntityID returns a UUID v7 resource/entity identifier.

Example

ExampleNewEntityID returns a UUID v7 resource identifier in canonical 36-character form.

package main

import (
	"fmt"

	ax "github.com/rshade/ax-go"
)

func main() {
	id, err := ax.NewEntityID()
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(len(id))
}
Output:
36

func NewIdempotencyKey

func NewIdempotencyKey() string

NewIdempotencyKey returns a UUID v4 idempotency key.

Example

ExampleNewIdempotencyKey returns a UUID v4 string in canonical 36-character form, surfaced in the output envelope so retries are safe.

package main

import (
	"fmt"

	ax "github.com/rshade/ax-go"
)

func main() {
	key := ax.NewIdempotencyKey()
	fmt.Println(len(key))
}
Output:
36

func NewSchemaCommand

func NewSchemaCommand(root *cobra.Command, opts ...SchemaOption) *cobra.Command

NewSchemaCommand builds the reserved __schema command.

func ParseConfig

func ParseConfig(ctx context.Context, r io.Reader, dst any, opts ...ParseConfigOption) error

ParseConfig parses Hujson from r under a bounded read cap and unmarshals into dst.

Reads default to DefaultMaxConfigBytes and consume at most cap+1 bytes. Oversize input returns an errors.As-discoverable *Error with error_code config_too_large and exit code 2. A cap below zero or above MaxConfigBytesCeiling returns config_max_bytes_invalid and exit code 2. A nil ParseConfigOption returns config_option_invalid and exit code 2. Hujson parse and schema/type decode failures return config_invalid and exit code 2, with the underlying decode error preserved in the chain (reachable via errors.Is and errors.As through Unwrap). Invalid decode destinations, such as nil or non-pointer dst values, surface the underlying *json.InvalidUnmarshalError as caller misuse and are not classified as config_invalid. Every valid cap is at most MaxConfigBytesCeiling, so there is no unbounded read path. ctx cancellation is honored between chunk reads, not inside a single blocking Read. Wrapped context.DeadlineExceeded maps to exit code 3 via ErrorExitCode, and wrapped context.Canceled maps to exit code 1. A non-EOF source error before cap+1 bytes is returned with its chain preserved and is not classified as oversize; if the same read crosses the cap and returns a source error, the oversize validation error wins.

Example

ExampleParseConfig shows the read-side Hujson asymmetry: comments and trailing commas are accepted on input, and the result decodes into a normal struct.

package main

import (
	"context"
	"fmt"
	"strings"

	ax "github.com/rshade/ax-go"
)

func main() {
	const hujson = `{
		// comments and trailing commas are allowed on reads
		"name": "ax",
		"replicas": 3,
	}`

	var cfg struct {
		Name     string `json:"name"`
		Replicas int    `json:"replicas"`
	}
	if err := ax.ParseConfig(
		context.Background(),
		strings.NewReader(hujson),
		&cfg,
		ax.WithMaxConfigBytes(1<<10),
	); err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Printf("%s x%d\n", cfg.Name, cfg.Replicas)
}
Output:
ax x3

func ParseConfigFile

func ParseConfigFile(ctx context.Context, path string, dst any, opts ...ParseConfigOption) error

ParseConfigFile opens path and applies ParseConfig's contract to its contents.

The file is closed before return. Open failures are returned as-is; read, cap, context-cancellation, and Hujson decode behavior match ParseConfig.

Example

ExampleParseConfigFile reads and decodes a Hujson configuration file from disk, applying the same 1 MiB read cap as ParseConfig.

package main

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	ax "github.com/rshade/ax-go"
)

func main() {
	dir, err := os.MkdirTemp("", "ax-config")
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer os.RemoveAll(dir)

	path := filepath.Join(dir, "config.hujson")
	if err := os.WriteFile(path, []byte(`{"name": "ax"}`), 0o600); err != nil {
		fmt.Println("error:", err)
		return
	}

	var cfg struct {
		Name string `json:"name"`
	}
	if err := ax.ParseConfigFile(context.Background(), path, &cfg); err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(cfg.Name)
}
Output:
ax

func ResolveVersion

func ResolveVersion(injected string) string

ResolveVersion returns a non-empty tool version for agent-visible surfaces.

Resolution is deterministic for a given binary and uses this precedence: first the injected link-time value, then Go build metadata from the running binary, then the sentinel "0.0.0-unknown". The result is never empty and is never the bare strings "dev" or "unknown"; pass the returned value to WithVersion and WithLoggerLabels so __schema.version, ax.Error.version, and the logger "version" label agree.

Example

ExampleResolveVersion shows the deterministic injected-version path used by release builds. When the injected value is empty, ResolveVersion falls back to the running binary's build metadata and finally to "0.0.0-unknown".

package main

import (
	"fmt"

	ax "github.com/rshade/ax-go"
)

func main() {
	fmt.Println(ax.ResolveVersion("v1.2.3"))
}
Output:
v1.2.3

func SpanIDFromContext

func SpanIDFromContext(ctx context.Context) string

SpanIDFromContext returns the active W3C span ID or ZeroSpanID.

func TraceIDFromContext

func TraceIDFromContext(ctx context.Context) string

TraceIDFromContext returns the active W3C trace ID or ZeroTraceID.

func WithDryRun

func WithDryRun(ctx context.Context, dryRun bool) context.Context

WithDryRun returns a context carrying the dry-run state.

func WithIdempotencyKey

func WithIdempotencyKey(ctx context.Context, key string) context.Context

WithIdempotencyKey returns a context carrying the idempotency key for the run.

func WithMode

func WithMode(ctx context.Context, mode Mode) context.Context

WithMode returns a context carrying the resolved output mode.

func WriteError

func WriteError(w io.Writer, err error) error

WriteError writes err as a strict minified JSON error envelope followed by a newline.

func WriteJSON

func WriteJSON(w io.Writer, v any) error

WriteJSON writes v as strict minified JSON followed by a newline.

func WriteJSONLine

func WriteJSONLine(w io.Writer, v any) error

WriteJSONLine writes a single NDJSON line.

Types

type CommandSchema

type CommandSchema struct {
	Use      string          `json:"use"`
	Short    string          `json:"short,omitempty"`
	Long     string          `json:"long,omitempty"`
	Example  string          `json:"example,omitempty"`
	Flags    []FlagSchema    `json:"flags,omitempty"`
	Commands []CommandSchema `json:"commands,omitempty"`
}

CommandSchema describes a Cobra command and its direct children.

type Envelope

type Envelope[T any] struct {
	Data T        `json:"data"`
	Meta Metadata `json:"meta"`
}

Envelope is the standard bounded JSON success payload shape.

Example

ExampleEnvelope shows the envelope shape directly: a typed Data field and a Metadata block. span_id is omitted when empty.

package main

import (
	"fmt"
	"os"

	ax "github.com/rshade/ax-go"
)

func main() {
	env := ax.Envelope[string]{
		Data: "hello",
		Meta: ax.Metadata{TraceID: ax.ZeroTraceID},
	}
	if err := ax.WriteJSON(os.Stdout, env); err != nil {
		fmt.Println("error:", err)
	}
}
Output:
{"data":"hello","meta":{"trace_id":"00000000000000000000000000000000"}}

func NewEnvelope

func NewEnvelope[T any](ctx context.Context, data T) Envelope[T]

NewEnvelope wraps data with standard AX metadata from ctx.

Example

ExampleNewEnvelope wraps a payload in the standard success envelope, carrying trace metadata from the context. With no active span the IDs are the zero W3C values, so the output is deterministic.

package main

import (
	"context"
	"fmt"
	"os"

	ax "github.com/rshade/ax-go"
)

func main() {
	type result struct {
		ID string `json:"id"`
	}

	env := ax.NewEnvelope(context.Background(), result{ID: "abc"})
	if err := ax.WriteJSON(os.Stdout, env); err != nil {
		fmt.Println("error:", err)
	}
}
Output:
{"data":{"id":"abc"},"meta":{"trace_id":"00000000000000000000000000000000","span_id":"0000000000000000"}}

type Error

type Error struct {
	ErrorCode     string         `json:"error_code"`
	Message       string         `json:"message"`
	TraceID       string         `json:"trace_id"`
	Tool          string         `json:"tool"`
	Version       string         `json:"version"`
	SchemaVersion string         `json:"schema_version"`
	ActionableFix string         `json:"actionable_fix,omitempty"`
	Context       map[string]any `json:"context,omitempty"`
	Suggestions   []string       `json:"suggestions,omitempty"`
	// contains filtered or unexported fields
}

Error is the ADR-0002 structured error envelope emitted to stderr.

Example

ExampleError shows how a consumer classifies a failure: recover the *ax.Error envelope with errors.As, then branch on the stable error_code and exit code without parsing human-facing text.

package main

import (
	"context"
	"errors"
	"fmt"

	ax "github.com/rshade/ax-go"
)

func main() {
	err := ax.NewError(
		context.Background(),
		"config_max_bytes_invalid",
		"config max bytes must be between 0 and 1073741824",
		ax.WithErrorExitCode(ax.ExitValidation),
	)

	var axErr *ax.Error
	if errors.As(err, &axErr) {
		fmt.Println(axErr.ErrorCode)
		fmt.Println(axErr.ExitCode())
	}
}
Output:
config_max_bytes_invalid
2

func NewError

func NewError(ctx context.Context, code, message string, opts ...ErrorOption) *Error

NewError builds a structured error envelope using trace information from ctx.

Example

ExampleNewError builds a structured error envelope with an actionable fix and a deterministic exit code, then reads the exit code back out.

package main

import (
	"context"
	"fmt"

	ax "github.com/rshade/ax-go"
)

func main() {
	err := ax.NewError(
		context.Background(),
		"config_too_large",
		"config exceeds maximum size of 1048576 bytes",
		ax.WithActionableFix("reduce the config or raise the limit with WithMaxConfigBytes"),
		ax.WithErrorExitCode(ax.ExitValidation),
	)

	fmt.Println(err)
	fmt.Println(ax.ErrorExitCode(err))
}
Output:
config exceeds maximum size of 1048576 bytes
2

func (*Error) Error

func (e *Error) Error() string

Error returns the human-readable error message.

func (*Error) ExitCode

func (e *Error) ExitCode() int

ExitCode returns the deterministic process exit code associated with e.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the underlying cause attached via WithErrorCause, or nil when no cause was attached. It lets errors.Is and errors.As traverse from the envelope to the source error (for example, a config decode failure) without the cause ever appearing in the JSON envelope.

type ErrorOption

type ErrorOption func(*Error)

ErrorOption configures a structured Error.

func WithActionableFix

func WithActionableFix(fix string) ErrorOption

WithActionableFix sets a best-effort remediation hint.

func WithErrorCause

func WithErrorCause(err error) ErrorOption

WithErrorCause attaches the underlying source error to the envelope so errors.Is and errors.As reach it through Unwrap. The cause is never serialized into the JSON envelope; it exists only for in-process callers. Never attach a context.Canceled or context.DeadlineExceeded cause: context errors are returned raw (FR-010) so their sentinels map to exit codes only when no explicit envelope classification exists.

func WithErrorContext

func WithErrorContext(fields map[string]any) ErrorOption

WithErrorContext merges domain-specific context fields into the envelope.

func WithErrorExitCode

func WithErrorExitCode(code int) ErrorOption

WithErrorExitCode sets the deterministic process exit code.

func WithErrorTool

func WithErrorTool(tool string) ErrorOption

WithErrorTool sets the emitting tool name.

func WithErrorVersion

func WithErrorVersion(version string) ErrorOption

WithErrorVersion sets the emitting tool version.

func WithSuggestions

func WithSuggestions(suggestions ...string) ErrorOption

WithSuggestions sets optional candidate recovery actions.

type ErrorSchemaInfo

type ErrorSchemaInfo struct {
	SchemaVersion string   `json:"schema_version"`
	Required      []string `json:"required"`
	Optional      []string `json:"optional"`
}

ErrorSchemaInfo describes the shared stderr error envelope.

type ExecuteOption

type ExecuteOption func(*executeConfig)

ExecuteOption configures Execute.

func WithEnv

func WithEnv(env func(string) string) ExecuteOption

WithEnv sets the environment lookup used by Execute.

func WithStderr

func WithStderr(w io.Writer) ExecuteOption

WithStderr sets the operational output stream.

func WithStdin

func WithStdin(r io.Reader) ExecuteOption

WithStdin sets the input stream for Cobra.

func WithStdout

func WithStdout(w io.Writer) ExecuteOption

WithStdout sets the machine payload output stream.

func WithStdoutIsTTY

func WithStdoutIsTTY(isTTY bool) ExecuteOption

WithStdoutIsTTY overrides TTY detection, primarily for tests.

func WithTelemetryShutdownTimeout

func WithTelemetryShutdownTimeout(timeout time.Duration) ExecuteOption

WithTelemetryShutdownTimeout sets the OTel shutdown timeout.

func WithVersion

func WithVersion(version string) ExecuteOption

WithVersion sets the tool version reported in schema and error envelopes.

type FlagSchema

type FlagSchema struct {
	Name      string `json:"name"`
	Shorthand string `json:"shorthand,omitempty"`
	Type      string `json:"type"`
	Default   string `json:"default,omitempty"`
	Usage     string `json:"usage,omitempty"`
	Required  bool   `json:"required,omitempty"`
}

FlagSchema describes a command flag.

type Labels

type Labels struct {
	Environment string
	Application string
	Host        string
	Version     string
}

Labels are low-cardinality Loki-indexed fields.

type Logger

type Logger interface {
	Debug(ctx context.Context) *zerolog.Event
	Info(ctx context.Context) *zerolog.Event
	Warn(ctx context.Context) *zerolog.Event
	Error(ctx context.Context) *zerolog.Event
	WithLabels(labels Labels) Logger
	Zerolog() *zerolog.Logger
}

Logger is the ADR-0009 logging surface, initially backed by zerolog.

func NewLogger

func NewLogger(ctx context.Context, opts ...LoggerOption) Logger

NewLogger returns an ax Logger backed by zerolog and wired for trace correlation.

type LoggerOption

type LoggerOption func(*loggerConfig)

LoggerOption configures NewLogger.

func WithLoggerLabels

func WithLoggerLabels(labels Labels) LoggerOption

WithLoggerLabels attaches low-cardinality labels to every log line.

func WithLoggerLevel

func WithLoggerLevel(level zerolog.Level) LoggerOption

WithLoggerLevel sets the minimum zerolog level.

func WithLoggerWriter

func WithLoggerWriter(w io.Writer) LoggerOption

WithLoggerWriter sets the logger output writer. Defaults to stderr.

type MCPSchema

type MCPSchema struct {
	Tools []MCPTool `json:"tools"`
}

MCPSchema is the lightweight MCP-compatible adapter shape.

func BuildMCPSchema

func BuildMCPSchema(root *cobra.Command) MCPSchema

BuildMCPSchema adapts the command tree to a simple MCP tools list.

type MCPTool

type MCPTool struct {
	Name        string         `json:"name"`
	Description string         `json:"description,omitempty"`
	InputSchema map[string]any `json:"inputSchema"`
}

MCPTool describes one command as an MCP-compatible tool.

type Metadata

type Metadata struct {
	TraceID        string `json:"trace_id"`
	SpanID         string `json:"span_id,omitempty"`
	IdempotencyKey string `json:"idempotency_key,omitempty"`
	DryRun         bool   `json:"dry_run,omitempty"`
}

Metadata carries common machine-readable envelope fields.

type Mode

type Mode string

Mode describes whether output should be optimized for agents or humans.

Example

ExampleMode shows agent-mode resolution: with no --format flag, no AGENT_MODE, and a non-TTY stdout, ax resolves to machine-readable JSON.

package main

import (
	"fmt"

	ax "github.com/rshade/ax-go"
)

func main() {
	mode, err := ax.ResolveMode("", "", false)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(mode)
}
Output:
json
const (
	// ModeJSON is the machine-readable mode used by agents and pipelines.
	ModeJSON Mode = "json"
	// ModeHuman is the human-readable mode used for interactive terminals.
	ModeHuman Mode = "human"
)

func ModeFromContext

func ModeFromContext(ctx context.Context) (Mode, bool)

ModeFromContext returns the resolved output mode stored in ctx.

func ParseMode

func ParseMode(value string) (Mode, error)

ParseMode parses an explicit output mode.

func ResolveMode

func ResolveMode(explicitFormat, agentMode string, stdoutIsTTY bool) (Mode, error)

ResolveMode applies ADR-0001 precedence: explicit --format flag, then AGENT_MODE, then TTY detection.

func (Mode) String

func (m Mode) String() string

String returns the wire value for the mode.

type ParseConfigOption

type ParseConfigOption func(*parseConfigOptions)

ParseConfigOption configures ParseConfig and ParseConfigFile.

func WithMaxConfigBytes

func WithMaxConfigBytes(maxBytes int64) ParseConfigOption

WithMaxConfigBytes sets the maximum config bytes for one parse invocation.

The value is not global and does not affect later calls. Zero is a valid, honored limit: empty input passes the size check and then follows normal parse semantics, while any non-empty input is rejected as config_too_large. Values below zero or above MaxConfigBytesCeiling return config_max_bytes_invalid, mapped to exit code 2; there is no unbounded read path. Passing a nil ParseConfigOption is rejected as config_option_invalid, also mapped to exit code 2.

type Schema

type Schema struct {
	SchemaVersion string          `json:"schema_version"`
	Tool          string          `json:"tool"`
	Version       string          `json:"version"`
	ModeDetection string          `json:"mode_detection"`
	Command       CommandSchema   `json:"command"`
	ErrorEnvelope ErrorSchemaInfo `json:"error_envelope"`
}

Schema is the ax-native reflective JSON tree emitted by __schema.

func BuildSchema

func BuildSchema(root *cobra.Command, opts ...SchemaOption) Schema

BuildSchema reflects a Cobra command tree into the ax-native schema.

type SchemaOption

type SchemaOption func(*schemaConfig)

SchemaOption configures BuildSchema and NewSchemaCommand.

func WithSchemaVersion

func WithSchemaVersion(version string) SchemaOption

WithSchemaVersion sets the tool version reported by __schema.

type Telemetry

type Telemetry struct {
	TracerProvider *sdktrace.TracerProvider
}

Telemetry owns the OTel provider lifecycle for a short-lived CLI process.

func StartTelemetry

func StartTelemetry(ctx context.Context, opts ...TelemetryOption) (context.Context, *Telemetry, error)

StartTelemetry installs W3C trace propagation and extracts TRACEPARENT.

The initial scaffold uses the OTel SDK no-op exporter path. OTLP and debug exporters can be layered onto this lifecycle without changing callers.

func (*Telemetry) Shutdown

func (t *Telemetry) Shutdown(ctx context.Context) error

Shutdown flushes and shuts down the configured tracer provider.

type TelemetryOption

type TelemetryOption func(*telemetryConfig)

TelemetryOption configures StartTelemetry.

func WithTelemetryEnv

func WithTelemetryEnv(env func(string) string) TelemetryOption

WithTelemetryEnv sets the environment lookup used for trace extraction.

Directories

Path Synopsis
examples
integration command
internal
cli
cmd/doccover command
Command doccover enforces ExampleXxx coverage on ax-go's primary API surface.
Command doccover enforces ExampleXxx coverage on ax-go's primary API surface.
mcp

Jump to

Keyboard shortcuts

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