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
- Composition
- Task Options
- Detection
- Tasks
- Execution
- Tool Installation
- Download and Extract
- Platform Helpers
- Context
- Output
- Path Helpers
- Plan Introspection
- Errors
- CLI
- JSON Execution
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
}
| Function | Description |
|---|---|
AllShimsConfig | Returns 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
./pokshows 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
| Function | Description |
|---|---|
Serial | Execute runnables sequentially; stops on first error |
Parallel | Execute runnables concurrently; buffers output to prevent interleaving |
WithOptions | Wrap 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:
| Option | Description |
|---|---|
WithPath | Run only in directories matching the regex patterns |
WithSkipPath | Skip directories matching the regex patterns |
WithSkipTask | Skip a task entirely, or from directories matching patterns |
WithDetect | Dynamically discover paths using a detection function |
WithNameSuffix | Create a named variant (e.g., py-test → py-test:3.9) |
WithForceRun | Bypass task deduplication for the wrapped runnable |
WithVerbose | Force verbose (streamed) output regardless of -v flag |
WithFlags | Set flag overrides for a task in scope |
WithNoticePatterns | Override 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
| Function | Description |
|---|---|
DetectByFile | Find 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:
| Component | Description |
|---|---|
effectiveName | Base name + optional suffix (e.g., py-test:3.9) |
path | Execution 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
| Function | Description |
|---|---|
pk.Do | Wrap a func(context.Context) error as a Runnable |
run.Exec | Execute external command with proper output handling |
run.RegisterPATH | Register 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/binto 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
| Function | Description |
|---|---|
run.Printf | Formatted output to context stdout |
run.Println | Line output to context stdout |
run.Errorf | Formatted 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
| Pattern | When to use | How tasks invoke |
|---|---|---|
| Symlink | Native binaries (Go, Rust, C) | run.Exec(ctx, "tool", ...) |
| Tool Exec | Standalone runtime-dependent tools | tool.Exec(ctx, ...) |
| Runtime Run | Project-managed tools | uv.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 availableExec(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
| Option | Description |
|---|---|
WithDestDir | Destination directory for extraction |
WithFormat | Archive format: "tar.gz", "tar", "zip", "gz", "" (raw) |
WithExtract | Add extraction options |
WithSymlink | Create symlink in .pocket/bin/ |
WithSkipIfExists | Skip download if file exists |
WithOutputName | Output 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
| Function | Description |
|---|---|
ExtractTarGz | Extract .tar.gz archive |
ExtractTar | Extract .tar archive |
ExtractZip | Extract .zip archive |
ExtractGz | Extract 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
| Option | Description |
|---|---|
WithExtractFile | Extract only the specified file |
WithRenameFile | Extract and rename a specific file |
WithFlatten | Flatten directory structure |
Symlink
| Function | Description |
|---|---|
CreateSymlink | Create symlink in .pocket/bin/ to given binary |
CreateSymlinkAs | Create symlink with custom name in .pocket/bin/ |
CreateSymlinkWithCompanions | Create symlink and copy companion files (e.g., DLLs) |
CopyFile | Copy 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
| Function | Description |
|---|---|
pk.HostOS | Current OS: darwin, linux, windows |
pk.HostArch | Current architecture: amd64, arm64 |
pk.BinaryName | Append .exe on Windows |
pk.DefaultArchiveFormat | Returns zip on Windows, tar.gz otherwise |
Architecture Conversion
| Function | Conversion |
|---|---|
pk.ArchToX8664 | amd64 → x86_64, arm64 → aarch64 |
pk.ArchToX64 | amd64 → x64 |
pk.OSToTitle | darwin → Darwin |
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)
| Function | Description |
|---|---|
run.GetFlags[T] | Retrieve the resolved flags struct from context |
run.PathFromContext | Current execution path relative to git root |
run.PlanFromContext | The *Plan from context (nil if not set) |
run.Verbose | Whether -v flag was provided |
Modifiers (Setters)
Context modifiers use the ContextWith* naming convention to distinguish them
from Option functions (which use With*).
| Function | Description |
|---|---|
run.ContextWithEnv | Set an environment variable for Exec calls |
run.ContextWithoutEnv | Filter out environment variables matching prefix |
run.ContextWithPath | Set 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
| Function | Description |
|---|---|
FromGitRoot | Absolute path relative to git repository root |
FromPocketDir | Absolute path relative to .pocket/ |
FromBinDir | Absolute path relative to .pocket/bin/ |
FromToolsDir | Absolute 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/Method | Description |
|---|---|
Plan.Tasks | Returns []TaskInfo with effective names |
Plan.ShimConfig | Returns 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:
| Error | Description |
|---|---|
ErrGitDiffUncommitted | Returned when -g flag detects uncommitted changes |
ErrCommitsInvalid | Returned when -c flag detects invalid commit messages |
if errors.Is(err, pk.ErrGitDiffUncommitted) {
// Handle uncommitted changes
}
CLI
Flags
| Flag | Description |
|---|---|
-c, --commits | Validate conventional commits after execution |
-g, --gitdiff | Run git diff check after execution |
-h, --help | Show help |
-j, --json | Emit the invocation plan as JSON instead of executing (see JSON Execution) |
-s, --serial | Force serial execution (disables parallelism and output buffering) |
-v, --verbose | Verbose mode |
--version | Show version |
Functions
| Function | Description |
|---|---|
RunMain | Main entry point; handles args, help, task execution |
ExecuteTask | Execute 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.jsonor./pok plan tree.jsonrenders a JSON tree as the human-readable plan view without executing it../pok execreads 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:
| Option | Equivalent flag | Description |
|---|---|---|
verbose | -v, --verbose | Stream command output |
serial | -s, --serial | Force serial execution |
gitdiff | -g, --gitdiff | Run git diff check after execution |
commits | -c, --commits | Validate conventional commits after execution |
Node types:
| Type | Required fields | Description |
|---|---|---|
task | name | Reference an existing Pocket task by effective name |
command | name, argv | Run a raw command; argv[0] is the executable |
serial | children | Sequential composition. Stops on first error |
parallel | children | Concurrent composition with buffered output |
Task and command fields:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Display name for commands; effective Pocket task name for task refs |
argv | string array | command | Raw argument vector. Only valid on command nodes |
paths | string array | no | Literal directories relative to git root. Defaults to task paths or root |
Composition fields:
| Field | Type | Required | Description |
|---|---|---|---|
children | node array | yes | Child 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, orparallel. commandnodes require non-emptyargvandname.tasknodes requirenameand must reference a task in the current Pocket project when executed.serialandparallelnodes require non-emptychildren.pathsis only valid ontaskandcommandnodes and must be non-empty when present.options, when present, may containverbose,serial,gitdiff, andcommitsbooleans.versionmust be1.
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.