API Reference

June 12, 2026 · View on GitHub

Technical reference for the github.com/fredrikaverpil/pocket/pk and github.com/fredrikaverpil/pocket/pk/run packages.

Table of Contents


Configuration

The Config struct is the main entry point for configuring Pocket.

type Config struct {
    Auto   Runnable     // Tasks executed on bare ./pok
    Manual []Runnable   // Tasks only run when explicitly invoked
    Plan   *PlanConfig  // Plan building, shims, and CI configuration
}

type PlanConfig struct {
    SkipDirs          []string    // Directories to skip during filesystem walk
    IncludeHiddenDirs bool        // Include hidden directories (default: false)
    Shims             *ShimConfig // Which shim scripts to generate
}

Directory Skipping

// Default directories skipped (used when SkipDirs is nil)
var DefaultSkipDirs = []string{"vendor", "node_modules", "dist", "__pycache__", "venv"}

Shim Configuration

type ShimConfig struct {
    Posix      bool  // pok (default when Shims is nil)
    Windows    bool  // pok.cmd
    PowerShell bool  // pok.ps1
}
FunctionDescription
AllShimsConfigReturns config with all shims enabled

Shim Scoping

Pocket generates shims at the repository root and at path scopes derived from WithPath and WithDetect. Root shims set TASK_SCOPE=".", so bare ./pok shows and executes the full auto task tree across all resolved paths.

Subdirectory shims set TASK_SCOPE to the shim's path. In that mode:

  • Bare ./pok shows and executes only tasks scoped to that path
  • ./pok <task> executes the named task only for that path
  • Root-only tasks are hidden and skipped from subdirectory shims
./pok                       # root: run all auto tasks across all paths
cd services/api && ./pok    # scoped: run only services/api tasks
./pok go-test               # scoped: run go-test only in services/api

Git Diff Check

Pocket can run git diff --exit-code after task execution to catch unintended file modifications. This is enabled with the -g flag:

./pok -g          # Run all auto tasks, then git diff
./pok lint -g     # Run lint task, then git diff

The -g flag causes Pocket to fail if there are uncommitted changes after tasks complete. This is useful in CI to ensure generated files are up to date.

Conventional Commits Check

Pocket can validate commit messages against the Conventional Commits format. This is enabled with the -c flag:

./pok -c          # Run all auto tasks, then validate commits
./pok lint -c     # Run lint task, then validate commits

The -c flag validates commits between HEAD and the upstream tracking branch (or the origin's default branch for new branches). Merge commits are skipped. Each commit message must match the format type[(scope)][!]: description, where the description must not start with uppercase.


Tasks

Tasks are the fundamental units of work.

Creating Tasks

Tasks are created as struct literals with exported fields:

type Task struct {
    Name       string                         // Required: unique identifier
    Usage      string                         // Short description for help output
    Do         func(context.Context) error    // Inline function (mutually exclusive with Body)
    Body       Runnable                       // Composed logic (mutually exclusive with Do)
    Flags      any                            // Struct with `flag` and `usage` tags
    Hidden     bool                           // Hide from CLI listings
    HideHeader bool                           // Suppress ":: taskname" header
    Global     bool                           // Deduplicate by name only, ignoring path
    Verbose    bool                           // Force verbose (streamed) output
}
var Lint = &pk.Task{
    Name:  "lint",
    Usage: "run linters",
    Do: func(ctx context.Context) error {
        return run.Exec(ctx, "golangci-lint", "run")
    },
}

Task Flags

Flags are declared as a struct on the task and accessed via run.GetFlags[T]:

func GetFlags[T any](ctx context.Context) T

GetFlags returns the resolved flags struct. The struct's field values provide defaults; flag and usage struct tags define the CLI name and help text.

Supported types: string, bool, int, int64, uint, uint64, float64, time.Duration, and pointer variants (*string, *bool, etc.) for optional overrides where nil means "not set".

type DeployFlags struct {
    Env    string `flag:"env" usage:"target environment"`
    DryRun bool   `flag:"dry-run" usage:"preview without deploying"`
}

var Deploy = &pk.Task{
    Name:  "deploy",
    Usage: "deploy the app",
    Flags: DeployFlags{Env: "staging"},
    Do: func(ctx context.Context) error {
        f := run.GetFlags[DeployFlags](ctx)
        // f.Env, f.DryRun
        // ...
    },
}

Task Examples

var Internal = &pk.Task{Name: "internal", Usage: "...", Body: body, Hidden: true}
var Export = &pk.Task{Name: "export", Usage: "...", Body: body, HideHeader: true}
var Install = &pk.Task{Name: "install:tool", Usage: "...", Body: body, Hidden: true, Global: true}

Composition

The Runnable interface is the core abstraction for executable units (conceptually referred to as "tasks"):

type Runnable interface {
    run(ctx context.Context) error
}

Combinators

FunctionDescription
SerialExecute runnables sequentially; stops on first error
ParallelExecute runnables concurrently; buffers output to prevent interleaving
WithOptionsWrap a runnable with configuration to create task instances
pk.Serial(Format, Lint, Test)
pk.Parallel(Lint, Test, Build)
pk.WithOptions(Test, pk.WithPath("services"))

Task Options

Options passed to WithOptions to control where and how tasks execute.

Generic Options (pk.With*)

These options work with any task:

OptionDescription
WithPathRun only in directories matching the regex patterns
WithSkipPathSkip directories matching the regex patterns
WithSkipTaskSkip a task entirely, or from directories matching patterns
WithDetectDynamically discover paths using a detection function
WithNameSuffixCreate a named variant (e.g., py-testpy-test:3.9)
WithForceRunBypass task deduplication for the wrapped runnable
WithVerboseForce verbose (streamed) output regardless of -v flag
WithFlagsSet flag overrides for a task in scope
WithNoticePatternsOverride warning detection patterns for the scope
pk.WithOptions(
    pk.Parallel(Lint, Test),
    pk.WithPath("services/.*"),
    pk.WithFlags(golang.TestFlags{Race: true}),
)

Task Instances and Variants

A task is a reusable definition (like python.Test). During planning, the composition tree is walked and every task becomes a task instance with its resolved configuration (paths, flags, context values).

Use WithOptions to configure instances. Use WithNameSuffix to create distinct variants of the same task:

// Same task definition, two distinct variants
pk.WithOptions(python.Test, pk.WithNameSuffix("3.9"), pk.WithFlags(python.TestFlags{Python: "3.9"}))
pk.WithOptions(python.Test, pk.WithNameSuffix("3.10"), pk.WithFlags(python.TestFlags{Python: "3.10"}))

Each variant has an effective name (base name + suffix). Variants are deduplicated separately, so py-test:3.9 and py-test:3.10 both run.

A task (or variant) referenced from multiple scopes becomes a single instance that runs in the union of all scopes' paths. All scopes must agree on the task's flag overrides — conflicting WithFlags values (including an override in one scope but not another) fail plan building. Use WithNameSuffix to create distinct variants instead.

Default Execution Path

Tasks run at the repository root (.) by default. Use WithPath or WithDetect to run tasks in specific directories.


Detection

Detection functions dynamically discover directories based on marker files. The tasks will run in the detected paths.

type DetectFunc func(dirs []string, gitRoot string) []string
FunctionDescription
DetectByFileFind directories containing any of the specified files
pk.WithOptions(
    pk.Parallel(Lint, Test),
    pk.WithDetect(pk.DetectByFile("go.mod", "package.json")),
)

Deduplication

Tasks are deduplicated during execution to prevent running the same work twice. The deduplication key is effectiveName@path:

ComponentDescription
effectiveNameBase name + optional suffix (e.g., py-test:3.9)
pathExecution directory relative to git root (e.g., .)

This means:

  • Same task at the same path runs only once
  • Same task at different paths runs once per path
  • Same task referenced from multiple scopes runs in the union of the scopes' paths
  • Different variants (via WithNameSuffix) run separately

Global tasks deduplicate by baseName@. only, ignoring the execution path. Use Global: true for tool installation tasks that should run once regardless of how many paths trigger them:

var InstallUV = &pk.Task{
    Name:   "install:uv",
    Usage:  "install uv",
    Body:   body,
    Hidden: true,
    Global: true,
}

Force execution with WithForceRun() to bypass deduplication entirely:

pk.WithOptions(CleanupTask, pk.WithForceRun())

Force verbose output to always stream output in real-time, regardless of the -v CLI flag. Set Verbose: true on the task, or use WithVerbose() in composition:

var Deploy = &pk.Task{Name: "deploy", Verbose: true, Do: deployFn}

// Or via composition:
pk.WithOptions(DeployTask, pk.WithVerbose())

Execution

Running Code

FunctionDescription
pk.DoWrap a func(context.Context) error as a Runnable
run.ExecExecute external command with proper output handling
run.RegisterPATHRegister a directory to be added to PATH for Exec
pk.Do(func(ctx context.Context) error {
    return run.Exec(ctx, "go", "test", "./...")
})

run.Exec behavior:

  • With -v: Output streams to stdout/stderr in real-time
  • Without -v: Output captured, shown on error or if warnings detected
  • Detects warnings via run.DefaultNoticePatterns: warn, deprecat, notice, caution, error (case-insensitive)
  • Override with WithNoticePatterns(...), or pass no patterns to disable
  • Adds .pocket/bin to PATH
  • Sends SIGINT for graceful shutdown (Unix)

run.RegisterPATH adds directories to PATH for all subsequent run.Exec calls. Use this for tools that can't be symlinked (e.g., neovim on Windows needs its runtime files):

run.RegisterPATH("/path/to/nvim/bin")

Output Functions

FunctionDescription
run.PrintfFormatted output to context stdout
run.PrintlnLine output to context stdout
run.ErrorfFormatted output to context stderr
run.Printf(ctx, "Processing %d items...\n", count)

Tool Installation

Each tool package owns its complete lifecycle: installation, versioning, and making itself available for execution.

Tool Availability Patterns

PatternWhen to useHow tasks invoke
SymlinkNative binaries (Go, Rust, C)run.Exec(ctx, "tool", ...)
Tool ExecStandalone runtime-dependent toolstool.Exec(ctx, ...)
Runtime RunProject-managed toolsuv.Run(ctx, opts, "tool", ...)

Symlink: Binary symlinked to .pocket/bin/, tasks invoke by name via run.Exec.

Tool Exec: Tool package exposes Exec() function that handles runtime invocation internally. No symlink (shebangs fail without runtime on PATH).

Runtime Run: Project controls versions via pyproject.toml or package.json. Use runtime's Run() function directly.

Go Tools

Import: "github.com/fredrikaverpil/pocket/tools/golang"

func Install(pkg, version string) pk.Runnable

Installs a Go package to .pocket/tools/go/<pkg>/<version>/ and symlinks to .pocket/bin/. Uses Symlink pattern.

golang.Install("github.com/golangci/golangci-lint/v2/cmd/golangci-lint", "v2.1.6")

Runtime-Dependent Tools

For Python/Node tools, see tools/prettier/ and tools/mdformat/ for examples of the Tool Exec pattern. Each exposes:

  • Install - Task ensuring the tool is available
  • Exec(ctx, args...) - Function to invoke the tool
// Usage in tasks
prettier.Exec(ctx, "--write", "**/*.md")
mdformat.Exec(ctx, "--wrap", "80", ".")

Download and Extract

Import: "github.com/fredrikaverpil/pocket/pk/download"

Download

func Download(url string, opts ...Opt) pk.Runnable
OptionDescription
WithDestDirDestination directory for extraction
WithFormatArchive format: "tar.gz", "tar", "zip", "gz", "" (raw)
WithExtractAdd extraction options
WithSymlinkCreate symlink in .pocket/bin/
WithSkipIfExistsSkip download if file exists
WithOutputNameOutput filename for "gz" format (required for gz)
import "github.com/fredrikaverpil/pocket/pk/download"

download.Download(
    "https://example.com/tool-v1.0.0-linux-amd64.tar.gz",
    download.WithDestDir(pk.FromToolsDir("tool", "v1.0.0")),
    download.WithFormat("tar.gz"),
    download.WithExtract(download.WithExtractFile("tool")),
    download.WithSymlink(),
    download.WithSkipIfExists(pk.FromToolsDir("tool", "v1.0.0", "tool")),
)

Extract

FunctionDescription
ExtractTarGzExtract .tar.gz archive
ExtractTarExtract .tar archive
ExtractZipExtract .zip archive
ExtractGzExtract a single gzipped file (not tar.gz)
// ExtractGz extracts a single gzipped file to destDir with the given name
func ExtractGz(src, destDir, destName string) error
OptionDescription
WithExtractFileExtract only the specified file
WithRenameFileExtract and rename a specific file
WithFlattenFlatten directory structure
FunctionDescription
CreateSymlinkCreate symlink in .pocket/bin/ to given binary
CreateSymlinkAsCreate symlink with custom name in .pocket/bin/
CreateSymlinkWithCompanionsCreate symlink and copy companion files (e.g., DLLs)
CopyFileCopy a file from src to dst
// CreateSymlinkAs creates a symlink with a custom name
linkPath, err := download.CreateSymlinkAs("/path/to/binary", "custom-name")

// CreateSymlinkWithCompanions copies companion files (useful on Windows)
linkPath, err := download.CreateSymlinkWithCompanions("/path/to/binary", "*.dll")

Platform Helpers

Platform detection and helpers are available directly from the pk package.

Runtime Detection

FunctionDescription
pk.HostOSCurrent OS: darwin, linux, windows
pk.HostArchCurrent architecture: amd64, arm64
pk.BinaryNameAppend .exe on Windows
pk.DefaultArchiveFormatReturns zip on Windows, tar.gz otherwise

Architecture Conversion

FunctionConversion
pk.ArchToX8664amd64x86_64, arm64aarch64
pk.ArchToX64amd64x64
pk.OSToTitledarwinDarwin

Constants

// OS constants - access via pk.Darwin, pk.Linux, pk.Windows
pk.Darwin  // "darwin"
pk.Linux   // "linux"
pk.Windows // "windows"

// Architecture constants - access via pk.AMD64, pk.ARM64
pk.AMD64 // "amd64"
pk.ARM64 // "arm64"

// Alternative naming - access via pk.X8664, pk.AARCH64, pk.X64
pk.X8664   // "x86_64"
pk.AARCH64 // "aarch64"
pk.X64     // "x64"

Context

Context accessors and modifiers are available from the pk/run package (imported as "github.com/fredrikaverpil/pocket/pk/run").

Accessors (Getters)

FunctionDescription
run.GetFlags[T]Retrieve the resolved flags struct from context
run.PathFromContextCurrent execution path relative to git root
run.PlanFromContextThe *Plan from context (nil if not set)
run.VerboseWhether -v flag was provided

Modifiers (Setters)

Context modifiers use the ContextWith* naming convention to distinguish them from Option functions (which use With*).

FunctionDescription
run.ContextWithEnvSet an environment variable for Exec calls
run.ContextWithoutEnvFilter out environment variables matching prefix
run.ContextWithPathSet the execution path for Exec calls
// Set an environment variable
ctx = run.ContextWithEnv(ctx, "MY_VAR=value")

// Remove environment variables matching prefix
ctx = run.ContextWithoutEnv(ctx, "VIRTUAL_ENV")

// Change execution directory
ctx = run.ContextWithPath(ctx, "services/api")

// Use with Exec
run.Exec(ctx, "mycmd", "arg1") // runs with modified environment/path

Output

The Output type and StdOutput function are internal to the engine (pk/internal/engine) and not part of the public API.


Path Helpers

FunctionDescription
FromGitRootAbsolute path relative to git repository root
FromPocketDirAbsolute path relative to .pocket/
FromBinDirAbsolute path relative to .pocket/bin/
FromToolsDirAbsolute path relative to .pocket/tools/
pk.FromToolsDir("golangci-lint", "v1.64.8", "bin", "golangci-lint")
// → /path/to/repo/.pocket/tools/golangci-lint/v1.64.8/bin/golangci-lint

Plan Introspection

The Plan represents the execution plan created from a Config.

type Plan struct {
    // Internal: tree, taskInstances, pathMappings, moduleDirectories, shimConfig
}

type TaskInfo struct {
    Name   string         `json:"name"`            // Effective name (e.g., "py-test:3.9")
    Usage  string         `json:"usage,omitempty"` // Description/help text
    Paths  []string       `json:"paths"`           // Directories this task runs in
    Flags  map[string]any `json:"flags,omitempty"` // Flag overrides from WithFlags()
    Hidden bool           `json:"hidden"`          // Whether task is hidden from help
    Manual bool           `json:"manual"`          // Whether task is manual-only
}
Function/MethodDescription
Plan.TasksReturns []TaskInfo with effective names
Plan.ShimConfigReturns resolved *ShimConfig
plan := run.PlanFromContext(ctx)
for _, info := range plan.Tasks() {
    fmt.Printf("Task: %s - %s (paths: %v)\n", info.Name, info.Usage, info.Paths)
}

Task names in TaskInfo include any suffix from WithNameSuffix. For example, a task named py-test wrapped with pk.WithNameSuffix("3.9") will have Name: "py-test:3.9".


Errors

Sentinel errors for error handling:

ErrorDescription
ErrGitDiffUncommittedReturned when -g flag detects uncommitted changes
ErrCommitsInvalidReturned when -c flag detects invalid commit messages
if errors.Is(err, pk.ErrGitDiffUncommitted) {
    // Handle uncommitted changes
}

CLI

Flags

FlagDescription
-c, --commitsValidate conventional commits after execution
-g, --gitdiffRun git diff check after execution
-h, --helpShow help
-j, --jsonEmit the invocation plan as JSON instead of executing (see JSON Execution)
-s, --serialForce serial execution (disables parallelism and output buffering)
-v, --verboseVerbose mode
--versionShow version

Functions

FunctionDescription
RunMainMain entry point; handles args, help, task execution
ExecuteTaskExecute a single task by name with plan context
// In .pocket/main.go
func main() {
    pk.RunMain(Config)
}

// ExecuteTask signature
func ExecuteTask(ctx context.Context, name string, p *Plan) error

JSON Execution

Pocket can be driven from a JSON document instead of .pocket/config.go. This is primarily intended for LLMs and agents that need to compose ad-hoc task trees on-the-fly without writing Go code.

Three CLI surfaces share the same schema:

  • ./pok --json [task] emits the invocation plan as JSON to stdout (no execution). Useful for inspecting an existing Pocket project.
  • ./pok plan < tree.json or ./pok plan tree.json renders a JSON tree as the human-readable plan view without executing it.
  • ./pok exec reads a JSON document from stdin and executes it through the same engine as the typed-config path (same composition, deduplication, output buffering, and post-actions).

Schema (v1)

A versioned root with optional global execution options and a single execution tree. Strict — unknown fields error. Each node has an explicit type discriminator.

{
  "version": 1,
  "options": {
    "gitdiff": true,
    "serial": true
  },
  "tree": {
    "type": "serial",
    "children": [
      {
        "type": "task",
        "name": "go-format",
        "paths": ["."]
      },
      {
        "type": "parallel",
        "children": [
          { "type": "task", "name": "go-test" },
          {
            "type": "command",
            "name": "custom-vet",
            "argv": ["go", "vet", "./..."]
          }
        ]
      }
    ]
  }
}

Global options map to Pocket's global CLI flags and are applied when the JSON is executed:

OptionEquivalent flagDescription
verbose-v, --verboseStream command output
serial-s, --serialForce serial execution
gitdiff-g, --gitdiffRun git diff check after execution
commits-c, --commitsValidate conventional commits after execution

Node types:

TypeRequired fieldsDescription
tasknameReference an existing Pocket task by effective name
commandname, argvRun a raw command; argv[0] is the executable
serialchildrenSequential composition. Stops on first error
parallelchildrenConcurrent composition with buffered output

Task and command fields:

FieldTypeRequiredDescription
namestringyesDisplay name for commands; effective Pocket task name for task refs
argvstring arraycommandRaw argument vector. Only valid on command nodes
pathsstring arraynoLiteral directories relative to git root. Defaults to task paths or root

Composition fields:

FieldTypeRequiredDescription
childrennode arrayyesChild nodes for serial/parallel

Validation rules (strict)

  • Unknown top-level or node-level fields error with the field path.
  • Every node must have type: task, command, serial, or parallel.
  • command nodes require non-empty argv and name.
  • task nodes require name and must reference a task in the current Pocket project when executed.
  • serial and parallel nodes require non-empty children.
  • paths is only valid on task and command nodes and must be non-empty when present.
  • options, when present, may contain verbose, serial, gitdiff, and commits booleans.
  • version must be 1.

Errors

Validation and parse errors are emitted to stderr as JSON, one object per error, so they can be parsed by an agent:

{ "error": "tree.children[0].argv: empty array" }

The CLI exits non-zero on any validation or execution error.

Global flags interact normally

-v, -s, -g, and -c work with exec the same way they work with any other task — they apply through the same context machinery:

./pok -v exec < tree.json          # stream task output instead of buffering
./pok -s exec < tree.json          # force serial execution of parallel nodes
./pok -g exec < tree.json          # run git diff check after execution

Inspecting a Go-defined project

The global --json flag emits the executable task tree of the current .pocket/config.go project:

./pok --json             # full Auto tree
./pok --json go-test     # single task reference

Go-defined task bodies are emitted as task references rather than raw commands:

{
  "version": 1,
  "tree": {
    "type": "task",
    "name": "go-test",
    "paths": ["."]
  }
}

The emitted output can be piped back into ./pok exec in the same Pocket project. Global execution flags are serialized as options, so ./pok --json -g go-test | ./pok exec preserves the git-diff post-action.

Schema document

Print the JSON Schema (Draft-07) for v1:

./pok exec --schema

Out of scope for v1

The following are intentional deferrals; the schema may add new node types or fields in later versions:

  • Typed flag overrides for referenced tasks.
  • Path detection (the equivalent of WithDetect): paths must be literal. Agents are expected to pre-resolve filesystem patterns themselves.
  • Scope-level options: WithForceRun, WithVerbose, WithNameSuffix, WithNoticePatterns.
  • File-based input for exec: stdin only.