pocket

package module
v0.0.0-...-04aa2e7 Latest Latest
Warning

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

Go to latest
Published: Jan 18, 2026 License: MIT Imports: 27 Imported by: 0

README

pocket

A cross-platform build system inspired by Sage. Define tasks, compose them with Serial/Parallel, and let pocket handle tool installation.

[!NOTE]

You don't need Go installed to use Pocket. The ./pok shim automatically downloads Go to .pocket/ if needed.

[!WARNING]

Under heavy development. Breaking changes will occur until the initial release.

TODOS

  • If the plan GHA workflow fails, the job still passes, see here.
  • Clean up docs; add diagrams.

Features

  • Zero-install: The ./pok shim bootstraps Go and all dependencies automatically
  • Cross-platform: Works on Windows, macOS, and Linux (no Makefiles)
  • Composable: Define tasks, compose with Serial()/Parallel() - Pocket handles the execution graph
  • Monorepo-ready: Auto-detects directories (by go.mod, pyproject.toml, etc.) with per-directory task visibility
  • Tool management: Downloads and caches tools in .pocket/

Quickstart

Bootstrap

Run in your project root (requires Go for this step only):

go run github.com/fredrikaverpil/pocket/cmd/pocket@latest init

This creates .pocket/ and ./pok (the wrapper script).

Your first task

Edit .pocket/config.go and add a task to your config's ManualRun:

package main

import (
    "context"
    "github.com/fredrikaverpil/pocket"
)

var Config = pocket.Config{
    ManualRun: []pocket.Runnable{Hello},
}

var Hello = pocket.Task("hello", "say hello", hello)

func hello(ctx context.Context) error {
    if pocket.Verbose(ctx) {
        pocket.Println(ctx, "Running hello function...")
    }
    pocket.Println(ctx, "Hello from pocket!")
    return nil
}
./pok -h        # list tasks
./pok hello     # run task
./pok hello -h  # show help for task (options, usage)
./pok -v hello  # run with verbose output
Composition

This is where Pocket shines. Compose tasks in AutoRun with Serial() and Parallel() for controlled execution order. Combined with path filtering, Pocket generates "pok" shims in each detected directory with fine-grained control over which tasks are available at each location.

var Config = pocket.Config{
    AutoRun: pocket.Serial(
        Format,              // first
        pocket.Parallel(     // then these in parallel
            Lint,
            Test,
        ),
        Build,               // last
    ),
}
./pok       # run entire AutoRun tree
./pok plan  # show execution tree (useful for debugging composition)
Dependencies

Tasks can depend on other tasks. Dependencies are deduplicated automatically - each task runs at most once per execution.

var Install = pocket.Task("install:tool", "install tool",
    pocket.InstallGo("github.com/org/tool", "v1.0.0"),
    pocket.AsHidden(),
)

var Lint = pocket.Task("lint", "run linter", pocket.Serial(
    Install,  // runs first (deduplicated across the tree)
    pocket.Run("tool", "lint", "./..."),
))

Concepts

Tasks

The core abstraction is Runnable - anything that can be executed. Tasks are named Runnables created with pocket.Task() that appear in the CLI:

// Simple: static command
var Format = pocket.Task("format", "format code",
    pocket.Run("go", "fmt", "./..."),
)

// Composed: multiple steps
var Build = pocket.Task("build", "build the project", pocket.Serial(
    Install,
    pocket.Run("go", "build", "./..."),
))

// Dynamic: args computed at runtime
var Lint = pocket.Task("lint", "run linter", lintCmd())

func lintCmd() pocket.Runnable {
    return pocket.Do(func(ctx context.Context) error {
        args := []string{"run"}
        if pocket.Verbose(ctx) {
            args = append(args, "-v")
        }
        return pocket.Exec(ctx, "golangci-lint", args...)
    })
}

Tasks can be:

  • Visible: Shown in ./pok -h, callable from CLI
  • Hidden: Not shown in help, used as dependencies (pocket.AsHidden())
Executing Commands

Pocket provides two ways to run external commands:

Run(name, args...) - Static command with fixed arguments:

pocket.Run("go", "fmt", "./...")

Do(fn) - Dynamic commands or arbitrary Go code:

pocket.Do(func(ctx context.Context) error {
    args := []string{"test"}
    if pocket.Verbose(ctx) {
        args = append(args, "-v")
    }
    return pocket.Exec(ctx, "go", args...)
})

Use Do for dynamic arguments, complex logic, file I/O, or multiple commands. Both run with proper output handling and respect the current path context. They're no-ops in collect mode (plan generation).

Serial and Parallel

Use Serial and Parallel to compose Runnables:

pocket.Serial(fn1, fn2, fn3)    // run in sequence
pocket.Parallel(fn1, fn2, fn3)  // run concurrently

With dependencies - compose install dependencies into your task:

var Lint = pocket.Task("lint", "run linter", pocket.Serial(
    linter.Install,  // runs first (deduplicated)
    lintCmd(),       // then the actual linting
))

func lintCmd() pocket.Runnable {
    return pocket.Do(func(ctx context.Context) error {
        args := []string{"run"}
        if pocket.Verbose(ctx) {
            args = append(args, "-v")
        }
        return pocket.Exec(ctx, linter.Name, args...)
    })
}

[!NOTE]

Be careful when using pocket.Parallel(). Only parallelize tasks that don't conflict - typically read-only operations like linting or testing. Tasks that mutate files (formatters, code generators) should run in serial before other tasks read those files.

Tools vs Tasks

Pocket conceptually distinguishes between tools (installers) and tasks (runners). Tools are responsible for downloading and installing binaries; tasks use those binaries to do work.

1. Tool Package

A tool package ensures a binary is available. It exports:

  • Name - the binary name (used with pocket.Run or pocket.Exec)
  • Install - a hidden task that downloads/installs the binary
  • Config (optional) - configuration file lookup settings
// tools/golangcilint/golangcilint.go
package golangcilint

const Name = "golangci-lint"
const Version = "v2.0.2"

// For Go tools: use InstallGo directly
var Install = pocket.Task("install:golangci-lint", "install golangci-lint",
    pocket.InstallGo("github.com/golangci/golangci-lint/v2/cmd/golangci-lint", Version),
    pocket.AsHidden(),
)

var Config = pocket.ToolConfig{
    UserFiles:   []string{".golangci.yml", ".golangci.yaml", ".golangci.toml"},
    DefaultFile: ".golangci.yml",
    DefaultData: defaultConfig,
}

For tools with complex installation (downloads, extraction):

// tools/stylua/stylua.go
package stylua

const Name = "stylua"
const Version = "v2.0.2"

var Install = pocket.Task("install:stylua", "install stylua",
    pocket.Download(downloadURL(),
        pocket.WithDestDir(destDir()),
        pocket.WithFormat(pocket.DefaultArchiveFormat()),
        pocket.WithExtract(pocket.WithExtractFile(pocket.BinaryName(Name))),
        pocket.WithSymlink(),
        pocket.WithSkipIfExists(binaryPath()),
    ),
    pocket.AsHidden(),
)
2. Task Package

A task package provides related tasks that use tools:

// tasks/golang/lint.go
package golang

var Lint = pocket.Task("go-lint", "run golangci-lint", pocket.Serial(
    golangcilint.Install,  // ensure tool is installed first
    lintCmd(),             // then run linting
), pocket.Opts(LintOptions{}))

func lintCmd() pocket.Runnable {
    return pocket.Do(func(ctx context.Context) error {
        opts := pocket.Options[LintOptions](ctx)
        args := []string{"run"}
        if pocket.Verbose(ctx) {
            args = append(args, "-v")
        }
        if !opts.SkipFix {
            args = append(args, "--fix")
        }
        args = append(args, "./...")
        return pocket.Exec(ctx, golangcilint.Name, args...)
    })
}

The Tasks() function composes tasks, and Detect() enables auto-discovery:

// tasks/golang/workflow.go
package golang

func Tasks() pocket.Runnable {
    return pocket.Serial(Format, Lint, Test)
}

func Detect() func() []string {
    return func() []string { return pocket.DetectByFile("go.mod") }
}
Summary
Type Purpose Exports Example
Tool Installs binary Name, Install, optional Config ruff, golangcilint
Task Uses tools TaskDefs + Tasks() + Detect() python, golang
Standalone vs Project Tools

Different ecosystems have different conventions for where tools are installed. Pocket supports both approaches:

Standalone tools (.pocket/tools/):

Tools are installed once and shared across runs. Best for tools that don't need to match project-specific versions:

// Tool is installed to .pocket/tools/golangci-lint/v2.0.2/
// and symlinked to .pocket/bin/golangci-lint
var Install = pocket.Task("install:golangci-lint", "install golangci-lint",
    pocket.InstallGo("github.com/golangci/golangci-lint/v2/cmd/golangci-lint", Version),
    pocket.AsHidden(),
)

// Task uses the standalone tool
var Lint = pocket.Task("go-lint", "run linter", pocket.Serial(
    golangcilint.Install,
    pocket.Run(golangcilint.Name, "run", "./..."),
))

Project tools (.venv/, node_modules/, etc.):

Tools are defined in the project's manifest (pyproject.toml, package.json) and installed into the project's environment. Best when tool versions should match project requirements:

// Python: uses ruff from pyproject.toml, installed into .venv/
var Lint = pocket.Task("py-lint", "lint Python files", pocket.Serial(
    uv.Install,                // ensure uv is available
    syncCmd(),                 // uv sync --all-groups (installs ruff to .venv/)
    lintCmd(),                 // uv run ruff check .
))

func lintCmd() pocket.Runnable {
    return pocket.Do(func(ctx context.Context) error {
        return uv.Run(ctx, "3.9", "ruff", "check", ".")  // runs from .venv/
    })
}

// Node.js: similar pattern with node_modules/
var Lint = pocket.Task("lint", "lint files", pocket.Serial(
    pocket.Run("npm", "install"),           // installs to node_modules/
    pocket.Run("npx", "eslint", "."),       // runs from node_modules/
))
Approach Location Best for
Standalone .pocket/tools/ Go tools, system utilities
Project .venv/, node_modules/ Python/Node tools with version locks

The Python task package (tasks/python/) uses project mode by default, running tools via uv run from the project's .venv/.

3. Tool Configuration

Tools that use config files can export a ToolConfig. Tasks then use pocket.ConfigPath() to find an existing config or create a default one:

// In tool package: define where to look for config
var Config = pocket.ToolConfig{
    UserFiles: []string{
        "ruff.toml",                      // relative: check in task CWD
        pocket.FromGitRoot("ruff.toml"),  // absolute: check in repo root
    },
    DefaultFile: "ruff.toml",    // fallback filename
    DefaultData: defaultConfig,  // embedded default config
}

// In task: find or create config
func lint(ctx context.Context) error {
    configPath, _ := pocket.ConfigPath(ctx, "ruff", ruff.Config)
    return pocket.Exec(ctx, ruff.Name, "check", "--config", configPath, ".")
}

ConfigPath checks each path in UserFiles:

  • Relative paths are resolved from the task's current directory (Path(ctx))
  • Absolute paths are used as-is (use FromGitRoot() for repo-root configs)

If no user config is found, it writes DefaultData to .pocket/tools/<tool>/<DefaultFile> and returns that path. This lets each directory in a monorepo have its own config, while providing sensible defaults.

Config Usage

The config ties everything together:

var Config = pocket.Config{
    // AutoRun executes on ./pok (no arguments)
    AutoRun: pocket.Serial(
        // Use task collections with auto-detection
        pocket.RunIn(golang.Tasks(), pocket.Detect(golang.Detect())),
        pocket.RunIn(markdown.Tasks(), pocket.Detect(markdown.Detect())),
    ),

    // ManualRun requires explicit ./pok <name>
    ManualRun: []pocket.Runnable{
        Deploy,
        Release,
    },
}

var Deploy = pocket.Task("deploy", "deploy to production", deploy)
var Release = pocket.Task("release", "create a release", release)

Running ./pok executes AutoRun. Running ./pok deploy executes the Deploy task.

Path Filtering

Use RunIn() to control where tasks are visible and run. RunIn() is optional - tasks without it only run at the git root:

var Config = pocket.Config{
    AutoRun: pocket.Parallel(
        // These use RunIn - run in each detected Go/Markdown location
        pocket.RunIn(golang.Tasks(), pocket.Detect(golang.Detect())),
        pocket.RunIn(markdown.Tasks(), pocket.Detect(markdown.Detect())),

        // No RunIn - only runs at git root
        github.Workflows,
    ),
}

For e.g. monorepos, use RunIn() to control where tasks run:

// Run in specific directories
pocket.RunIn(myTask, pocket.Include("services/api", "services/web"))

// Auto-detect directories containing go.mod
pocket.RunIn(golang.Tasks(), pocket.Detect(golang.Detect()))

// Exclude directories
pocket.RunIn(golang.Tasks(), pocket.Detect(golang.Detect()), pocket.Exclude("vendor"))

// Combine detection with filtering
pocket.RunIn(golang.Tasks(),
    pocket.Detect(golang.Detect()),
    pocket.Include("services/.*"),
    pocket.Exclude("testdata"),
)
Skipping Tasks in Specific Paths

While Exclude() excludes entire task compositions from directories, use Skip() to skip specific tasks within a composition. This controls which tasks run in each detected directory - it doesn't modify the task's arguments.

For example, in a monorepo with multiple Go services (each with their own go.mod), you might want to skip slow tests in certain services:

var Config = pocket.Config{
    AutoRun: pocket.RunIn(golang.Tasks(),
        pocket.Detect(golang.Detect()),
        pocket.Skip(golang.Test, "services/api", "services/worker"),
    ),

    // Make skipped tests available under a different name
    ManualRun: []pocket.Runnable{
        pocket.RunIn(pocket.Clone(golang.Test, pocket.Named("integration-test")),
            pocket.Include("services/api", "services/worker"),
        ),
    },
}

Here services/api/ and services/worker/ are separate Go modules detected by golang.Detect(). All composed tasks (format, lint, vulncheck) run in all detected modules, but go-test is skipped in those two. The skipped tests are available as integration-test when run from those directories.

Note: Clone(..., Named(...)) creates a copy of the task with a different CLI name. This avoids duplicate names when the same task appears in both AutoRun and ManualRun.

Options

Tasks can accept options:

type DeployOptions struct {
    Env    string `arg:"env" usage:"target environment"`
    DryRun bool   `arg:"dry-run" usage:"print without executing"`
}

var Deploy = pocket.Task("deploy", "deploy to environment", deploy,
    pocket.Opts(DeployOptions{Env: "staging"}),
)

func deploy(ctx context.Context) error {
    opts := pocket.Options[DeployOptions](ctx)
    if opts.DryRun {
        pocket.Printf(ctx, "Would deploy to %s\n", opts.Env)
        return nil
    }
    // deploy...
    return nil
}
./pok deploy                     # uses default (staging)
./pok deploy -env=prod -dry-run  # override at runtime

Reference

Helpers
// Composition
pocket.Serial(task1, task2, task3)     // run in sequence
pocket.Parallel(task1, task2, task3)   // run concurrently
pocket.Clone(task, opts...)            // copy task with modifications (Named, Opts, etc.)
pocket.WithOpts(task, opts)            // copy task with new options struct

// Command execution (returns Runnable)
pocket.Run("cmd", "arg1", "arg2")     // static command
pocket.Do(func(ctx) error)            // dynamic commands, arbitrary Go code

// Lower-level execution (inside Do() closures)
pocket.Exec(ctx, "cmd", "arg1", "arg2")       // run command in current path
pocket.ExecIn(ctx, "dir", "cmd", "args"...)   // run command in specific dir
pocket.Command(ctx, "cmd", "args"...)         // create exec.Cmd with .pocket/bin in PATH
pocket.Printf(ctx, "format %s", arg)          // formatted output to stdout
pocket.Println(ctx, "message")                // line output to stdout

// Context
pocket.Options[T](ctx)        // get typed options from context
pocket.Path(ctx)              // current path (for path-filtered tasks)
pocket.Verbose(ctx)           // whether -v flag is set
pocket.CWD(ctx)               // where CLI was invoked (relative to git root)

// Paths
pocket.GitRoot()              // git repository root
pocket.FromGitRoot("subdir")  // path relative to git root
pocket.FromPocketDir("file")  // path relative to .pocket/
pocket.FromToolsDir("tool")   // path relative to .pocket/tools/
pocket.FromBinDir("tool")     // path relative to .pocket/bin/
pocket.BinaryName("tool")     // append .exe on Windows

// Detection
pocket.DetectByFile("go.mod")       // find dirs containing file
pocket.DetectByExtension(".lua")    // find dirs with file extension

// Installation (returns Runnable)
pocket.InstallGo("github.com/org/tool", "v1.0.0")  // go install

// Installation helpers (inside Do() closures)
pocket.CreateSymlink("path/to/binary")              // symlink to .pocket/bin/
pocket.ConfigPath(ctx, "tool", config)              // find/create config file

// Download & Extract (returns Runnable)
pocket.Download(url,
    pocket.WithDestDir(dir),                              // extraction destination
    pocket.WithFormat("tar.gz"),                          // format: tar.gz, tar, zip, ""
    pocket.WithExtract(pocket.WithExtractFile(name)),     // extract specific file
    pocket.WithExtract(pocket.WithRenameFile(src, dest)), // rename during extract
    pocket.WithExtract(pocket.WithFlatten()),             // flatten directory structure
    pocket.WithSymlink(),                                 // symlink to .pocket/bin/
    pocket.WithSkipIfExists(path),                        // skip if file exists
    pocket.WithHTTPHeader(key, value),                    // add HTTP header
)
pocket.FromLocal(path, opts...)  // process local file with same options

// Platform
pocket.HostOS()                     // runtime.GOOS ("darwin", "linux", "windows")
pocket.HostArch()                   // runtime.GOARCH ("amd64", "arm64")
pocket.DefaultArchiveFormat()       // "zip" on Windows, "tar.gz" otherwise
pocket.DefaultArchiveFormatFor(os)  // "zip" for Windows, "tar.gz" otherwise
pocket.ArchToX8664(arch)            // convert "amd64" → "x86_64"
pocket.ArchToAMD64(arch)            // convert "x86_64" → "amd64"
pocket.ArchToX64(arch)              // convert "amd64" → "x64"
pocket.OSToTitle(os)                // convert "darwin" → "Darwin"
pocket.OSToUpper(os)                // convert "darwin" → "DARWIN"

// Platform constants
pocket.Darwin, pocket.Linux, pocket.Windows          // OS names
pocket.AMD64, pocket.ARM64                           // Go-style arch
pocket.X8664, pocket.AARCH64, pocket.X64             // alternative arch names

// Module
pocket.GoVersionFromDir("dir")    // read Go version from go.mod
Config Structure
var Config = pocket.Config{
    // AutoRun: runs on ./pok (no arguments)
    AutoRun: pocket.RunIn(golang.Tasks(),
        pocket.Detect(golang.Detect()),
        pocket.Skip(golang.Test, "services/worker"),
    ),

    // ManualRun: requires ./pok <name>
    ManualRun: []pocket.Runnable{
        pocket.RunIn(
            pocket.Clone(golang.Test, pocket.Named("slow-test")),
            pocket.Include("services/worker"),
        ),
    },

    // Shim: configure wrapper scripts
    Shim: &pocket.ShimConfig{
        Name:       "pok",   // base name
        Posix:      true,    // ./pok
        Windows:    true,    // pok.cmd
        PowerShell: true,    // pok.ps1
    },

    // SkipGenerate: don't run "generate" before tasks (default: false)
    SkipGenerate: false,

    // SkipGitDiff: don't fail on uncommitted changes after tasks (default: false)
    SkipGitDiff: false,
}
Introspection

The introspection API lets you analyze the task tree without executing it. This is useful for CI/CD integration (e.g., generating GitHub Actions matrices).

// Collect task info from a Runnable tree
tasks, err := pocket.CollectTasks(autoRun)
// Returns []pocket.TaskInfo with Name, Usage, Paths, Hidden

// Build full introspection plan from Config
plan, err := pocket.BuildIntrospectPlan(cfg)
// Returns pocket.IntrospectPlan with AutoRun tree and ManualRun flat list

Types:

// TaskInfo represents a task for introspection
type TaskInfo struct {
    Name   string   `json:"name"`             // CLI command name
    Usage  string   `json:"usage"`            // Description/help text
    Paths  []string `json:"paths,omitempty"`  // Directories this task runs in
    Hidden bool     `json:"hidden,omitempty"` // Whether task is hidden from help
}

// IntrospectPlan represents the full introspection structure
type IntrospectPlan struct {
    AutoRun   []*PlanStep `json:"autoRun,omitempty"`   // Tree structure
    ManualRun []TaskInfo  `json:"manualRun,omitempty"` // Flat list
}

// PlanStep represents a node in the execution tree
type PlanStep struct {
    Type     string      `json:"type"`               // "serial", "parallel", "func"
    Name     string      `json:"name,omitempty"`     // Task name (for "func" type)
    Usage    string      `json:"usage,omitempty"`    // Task description
    Hidden   bool        `json:"hidden,omitempty"`   // Hidden from help
    Deduped  bool        `json:"deduped,omitempty"`  // Would be skipped (dedup)
    Path     string      `json:"path,omitempty"`     // Path context
    Children []*PlanStep `json:"children,omitempty"` // Nested steps
}

CLI access:

./pok plan -json  # outputs IntrospectPlan as JSON

Example: GitHub Actions matrix generation:

// tasks/github/matrix.go
func MatrixTask(autoRun pocket.Runnable, cfg MatrixConfig) *pocket.TaskDef {
    return pocket.Task("gha-matrix", "output GitHub Actions matrix JSON",
        pocket.Do(func(ctx context.Context) error {
            tasks, err := pocket.CollectTasks(autoRun)
            if err != nil {
                return err
            }
            // Generate matrix entries from tasks...
            return nil
        }),
    )
}

Documentation

  • Architecture - Internal design: execution model, shim generation, path resolution

Documentation

Index

Constants

View Source
const (
	// DirName is the name of the pocket directory.
	DirName = ".pocket"
	// ToolsDirName is the name of the tools subdirectory.
	ToolsDirName = "tools"
	// BinDirName is the name of the bin subdirectory (for symlinks).
	BinDirName = "bin"
)
View Source
const (
	Darwin  = "darwin"
	Linux   = "linux"
	Windows = "windows"
)

OS name constants matching runtime.GOOS values.

View Source
const (
	// Go-style architecture names (matching runtime.GOARCH).
	AMD64 = "amd64"
	ARM64 = "arm64"

	// Alternative naming conventions used by various tools.
	X8664   = "x86_64"
	AARCH64 = "aarch64"
	X64     = "x64"
)

Architecture constants in various naming conventions.

View Source
const WaitDelay = 5 * time.Second

WaitDelay is the grace period given to child processes to handle termination signals before being force-killed.

Variables

This section is empty.

Functions

func ArchToAMD64

func ArchToAMD64(arch string) string

ArchToAMD64 converts x86_64/aarch64 naming to Go-style architecture names.

x86_64 -> amd64
aarch64 -> arm64

Other values are returned unchanged.

func ArchToX64

func ArchToX64(arch string) string

ArchToX64 converts Go-style architecture names to x64/arm64 naming.

amd64 -> x64
arm64 -> arm64 (unchanged)

Other values are returned unchanged.

func ArchToX8664

func ArchToX8664(arch string) string

ArchToX8664 converts Go-style architecture names to x86_64/aarch64 naming.

amd64 -> x86_64
arm64 -> aarch64

Other values are returned unchanged.

func BinaryName

func BinaryName(name string) string

BinaryName returns the binary name with the correct extension for the current OS. On Windows, it appends ".exe" to the name.

func CWD

func CWD(ctx context.Context) string

CWD returns where the CLI was invoked (relative to git root).

func Command

func Command(ctx context.Context, name string, args ...string) *exec.Cmd

Command creates an exec.Cmd with PATH prepended with .pocket/bin, stdout/stderr connected to os.Stdout/os.Stderr, and graceful shutdown configured.

When the context is cancelled, the command receives SIGINT first (allowing graceful shutdown), then SIGKILL after WaitDelay.

If stdout is a TTY, color-forcing environment variables are added so that tools output ANSI colors even when their output is buffered (for parallel execution).

Note: For commands run from task actions, prefer TaskContext.Command() which automatically wires output to the task's output writers for proper parallel buffering.

To redirect output (e.g., for buffering in parallel execution), set cmd.Stdout and cmd.Stderr after creating the command.

func ConfigPath

func ConfigPath(ctx context.Context, toolName string, cfg ToolConfig) (string, error)

ConfigPath returns the path to a tool's config file.

For each path in UserFiles:

  • Absolute paths are checked as-is (use FromGitRoot() for repo-root configs)
  • Relative paths are checked in the task's current directory (from Path(ctx))

If no user config is found, writes DefaultData to .pocket/tools/<name>/<DefaultFile>. Returns empty string and no error if cfg is empty.

Example:

var golangciConfig = pocket.ToolConfig{
    UserFiles:   []string{".golangci.yml", ".golangci.yaml"},
    DefaultFile: "golangci.yml",
    DefaultData: defaultConfig,
}

func lint(ctx context.Context) error {
    configPath, err := pocket.ConfigPath(ctx, "golangci-lint", golangciConfig)
    if err != nil {
        return err
    }
    return pocket.Exec(ctx, "golangci-lint", "run", "-c", configPath)
}

func CopyFile

func CopyFile(src, dst string) error

CopyFile copies a file from src to dst.

func CreateSymlink(binaryPath string) (string, error)

CreateSymlink creates a symlink in .pocket/bin/ pointing to the given binary. On Windows, it copies the file instead since symlinks require admin privileges. Returns the path to the symlink (or copy on Windows).

func DefaultArchiveFormat

func DefaultArchiveFormat() string

DefaultArchiveFormat returns the typical archive format for the current OS. Returns "zip" on Windows, "tar.gz" on other platforms.

func DefaultArchiveFormatFor

func DefaultArchiveFormatFor(os string) string

DefaultArchiveFormatFor returns the typical archive format for the given OS. Returns "zip" for Windows, "tar.gz" for other platforms.

func DetectByExtension

func DetectByExtension(extensions ...string) []string

DetectByExtension finds directories containing files with any of the specified extensions. Returns paths relative to git root, sorted alphabetically. Excludes .pocket directory and hidden directories. Each directory is returned only once, even if multiple matching files are found.

func DetectByFile

func DetectByFile(filenames ...string) []string

DetectByFile finds directories containing any of the specified files (e.g., "go.mod"). Returns paths relative to git root, sorted alphabetically. Excludes .pocket directory and hidden directories. Each directory is returned only once, even if multiple marker files are found.

func Exec

func Exec(ctx context.Context, name string, args ...string) error

Exec runs an external command with output directed to the current context. The command runs in the current path directory.

func ExecIn

func ExecIn(ctx context.Context, dir, name string, args ...string) error

ExecIn runs an external command in a specific directory.

func ExtractTar

func ExtractTar(src, destDir string, opts ...ExtractOpt) error

ExtractTar extracts a .tar archive to destDir. If no options are provided, all files are extracted preserving directory structure. Use WithRenameFile or WithExtractFile to limit extraction to specific files.

func ExtractTarGz

func ExtractTarGz(src, destDir string, opts ...ExtractOpt) error

ExtractTarGz extracts a .tar.gz archive to destDir. If no options are provided, all files are extracted preserving directory structure. Use WithRenameFile or WithExtractFile to limit extraction to specific files.

func ExtractZip

func ExtractZip(src, destDir string, opts ...ExtractOpt) error

ExtractZip extracts a .zip archive to destDir. If no options are provided, all files are extracted preserving directory structure. Use WithRenameFile or WithExtractFile to limit extraction to specific files.

func FromBinDir

func FromBinDir(elem ...string) string

FromBinDir returns a path relative to the .pocket/bin directory. If no elements are provided, returns the bin directory itself.

func FromGitRoot

func FromGitRoot(elem ...string) string

FromGitRoot returns a path relative to the git root.

func FromPocketDir

func FromPocketDir(elem ...string) string

FromPocketDir returns a path relative to the .pocket directory.

func FromToolsDir

func FromToolsDir(elem ...string) string

FromToolsDir returns a path relative to the .pocket/tools directory.

func GitRoot

func GitRoot() string

GitRoot returns the root directory of the git repository.

func GoVersionFromDir

func GoVersionFromDir(dir string) (string, error)

GoVersionFromDir reads the Go version from go.mod in the given directory. Returns the version string (e.g., "1.21") or an error if the file cannot be read or doesn't contain a go directive.

func HostArch

func HostArch() string

HostArch returns the current architecture (runtime.GOARCH).

func HostOS

func HostOS() string

HostOS returns the current operating system (runtime.GOOS).

func OSToTitle

func OSToTitle(os string) string

OSToTitle converts an OS name to title case.

darwin -> Darwin
linux -> Linux
windows -> Windows

func OSToUpper

func OSToUpper(os string) string

OSToUpper converts an OS name to uppercase.

darwin -> DARWIN
linux -> LINUX

func Options

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

Options retrieves typed options from the context. It handles both struct and pointer types for T, always looking up the base struct type.

func Path

func Path(ctx context.Context) string

Path returns the current execution path (relative to git root).

func PrependPath

func PrependPath(env []string, dir string) []string

PrependPath prepends a directory to the PATH in the given environment.

func Printf

func Printf(ctx context.Context, format string, args ...any)

Printf writes formatted output to stdout.

func Println

func Println(ctx context.Context, args ...any)

Println writes a line to stdout.

func RegisterGenerateAll

func RegisterGenerateAll(fn GenerateAllFunc)

RegisterGenerateAll registers the scaffold.GenerateAll function. This is called by internal/scaffold.init() to avoid import cycles.

func RunConfig

func RunConfig(cfg Config)

RunConfig is the main entry point for running a pocket configuration. It parses CLI flags, discovers functions, and runs the appropriate ones.

Example usage in .pocket/main.go:

func main() {
    pocket.RunConfig(Config)
}

func TestContext

func TestContext(out *Output) context.Context

TestContext creates a context suitable for testing.

func Verbose

func Verbose(ctx context.Context) bool

Verbose returns whether verbose mode is enabled.

Types

type Config

type Config struct {
	// AutoRun defines the execution tree for ./pok (no arguments).
	// Use Serial() and Parallel() to control execution order.
	// All tasks in AutoRun execute when running ./pok without arguments.
	//
	// Example:
	//
	//	AutoRun: pocket.Serial(
	//	    pocket.RunIn(golang.Tasks(), pocket.Detect(golang.Detect())),
	//	    pocket.RunIn(python.Tasks(), pocket.Detect(python.Detect())),
	//	),
	AutoRun Runnable

	// ManualRun registers additional tasks that only run when explicitly
	// invoked with ./pok <taskname>. These tasks appear in ./pok -h under
	// "Manual Tasks" and support the same wrappers as AutoRun (RunIn, etc.).
	//
	// Example:
	//
	//	ManualRun: []pocket.Runnable{
	//	    deployTask,
	//	    pocket.RunIn(benchmarkTask, pocket.Include("services/api")),
	//	},
	ManualRun []Runnable

	// Shim controls shim script generation.
	// By default, only Posix (./pok) is generated with name "pok".
	Shim *ShimConfig

	// SkipGenerate disables running "generate" at the start of the "all" task.
	// By default, "all" regenerates files before running tasks.
	// Set to true to skip regeneration.
	SkipGenerate bool

	// SkipGitDiff disables the git diff check at the end of the "all" task.
	// By default, "all" fails if there are uncommitted changes after running all tasks.
	// Set to true to disable this check.
	SkipGitDiff bool
}

Config defines the configuration for a project using pocket.

func (Config) WithDefaults

func (c Config) WithDefaults() Config

WithDefaults returns a copy of the config with default values applied.

type ConfigPlan

type ConfigPlan struct {
	// Tasks collected from AutoRun and ManualRun trees
	Tasks []*TaskDef
	// AutoRunNames tracks which tasks are from AutoRun (vs ManualRun)
	AutoRunNames map[string]bool
	// PathMappings maps task names to their PathFilter for visibility
	PathMappings map[string]*PathFilter
	// AllTask is the hidden task that runs the full AutoRun tree
	AllTask *TaskDef
	// BuiltinTasks are always-available tasks (plan, clean, generate, etc.)
	BuiltinTasks []*TaskDef
	// ModuleDirectories are all directories where shims should be generated
	ModuleDirectories []string
	// Config is the original configuration (for builtin tasks that need it)
	Config *Config
}

ConfigPlan holds all collected data from walking a Config's task trees. This is the result of the planning phase, before CLI execution.

func BuildConfigPlan

func BuildConfigPlan(cfg Config) *ConfigPlan

BuildConfigPlan walks the Config's task trees and collects all data needed for CLI execution. This is the single point where tree walking happens.

func GetConfigPlan

func GetConfigPlan(ctx context.Context) *ConfigPlan

GetConfigPlan returns the ConfigPlan from context. Returns nil if not set (e.g., in collect mode or testing).

func (*ConfigPlan) Validate

func (p *ConfigPlan) Validate() error

Validate checks the ConfigPlan for errors (e.g., duplicate task names).

type DownloadOpt

type DownloadOpt func(*downloadConfig)

DownloadOpt configures download and extraction behavior.

func WithDestDir

func WithDestDir(dir string) DownloadOpt

WithDestDir sets the destination directory for extraction.

func WithExtract

func WithExtract(opt ExtractOpt) DownloadOpt

WithExtract adds extraction options from the extract package. Use this to pass WithRenameFile, WithExtractFile, or WithFlatten options.

Example:

Download(ctx, url,
    WithExtract(WithRenameFile("tool-1.0.0/tool", "tool")),
    WithExtract(WithExtractFile("LICENSE")),
)

func WithFormat

func WithFormat(format string) DownloadOpt

WithFormat sets the archive format. Supported formats: "tar.gz", "tar", "zip", or "" for raw copy.

func WithHTTPHeader

func WithHTTPHeader(key, value string) DownloadOpt

WithHTTPHeader adds an HTTP header to the download request. Multiple calls accumulate headers.

func WithSkipIfExists

func WithSkipIfExists(path string) DownloadOpt

WithSkipIfExists skips the download if the specified file exists.

func WithSymlink() DownloadOpt

WithSymlink creates a symlink in .pocket/bin/ after extraction. The symlink points to the first extracted file.

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine orchestrates plan collection and execution.

func NewEngine

func NewEngine(root Runnable) *Engine

NewEngine creates an engine for the given runnable tree.

func (*Engine) Plan

func (e *Engine) Plan(ctx context.Context) (*ExecutionPlan, error)

Plan collects the complete execution plan without running anything.

type ExecutionPlan

type ExecutionPlan struct {
	// contains filtered or unexported fields
}

ExecutionPlan holds the complete plan collected during modeCollect.

func (*ExecutionPlan) ModuleDirectories

func (p *ExecutionPlan) ModuleDirectories() []string

ModuleDirectories returns all unique directories where tasks should run. This is derived from pathMappings and always includes "." (root). Used for shim generation.

func (*ExecutionPlan) PathMappings

func (p *ExecutionPlan) PathMappings() map[string]*PathFilter

PathMappings returns the collected path mappings.

func (*ExecutionPlan) Print

func (p *ExecutionPlan) Print(ctx context.Context, showHidden, showDedup bool)

Print outputs the execution plan tree.

func (*ExecutionPlan) Steps

func (p *ExecutionPlan) Steps() []*PlanStep

Steps returns the top-level steps in the plan.

func (*ExecutionPlan) TaskDefs

func (p *ExecutionPlan) TaskDefs() []*TaskDef

TaskDefs returns the collected TaskDefs (visible, non-hidden, deduplicated by name). This replaces the need to call funcs() separately.

func (*ExecutionPlan) Tasks

func (p *ExecutionPlan) Tasks() []TaskInfo

Tasks flattens the execution plan into a list of TaskInfo. This extracts all func steps from the tree, combining with path information collected during the walk. Tasks without path mappings get ["."].

type ExtractOpt

type ExtractOpt func(*extractConfig)

ExtractOpt configures extraction behavior.

func WithExtractFile

func WithExtractFile(name string) ExtractOpt

WithExtractFile extracts only the specified file (by base name). The file is extracted with its original name. Multiple calls accumulate files to extract.

func WithFlatten

func WithFlatten() ExtractOpt

WithFlatten flattens directory structure, extracting all files to destDir root. File names are preserved but directory paths are discarded.

func WithRenameFile

func WithRenameFile(srcPath, destName string) ExtractOpt

WithRenameFile extracts a specific file and optionally renames it. srcPath can be a full path within the archive or just the base name. If destName is empty, the original base name is preserved. Multiple calls accumulate files to extract.

Example:

ExtractTarGz(src, dest,
    WithRenameFile("golangci-lint-1.55.0-linux-amd64/golangci-lint", "golangci-lint"),
)

type GenerateAllFunc

type GenerateAllFunc func(plan *ConfigPlan) ([]string, error)

GenerateAllFunc is the function signature for scaffold.GenerateAll. This is set by internal/scaffold at init time to avoid import cycles. Accepts ConfigPlan to reuse cached ModuleDirectories (avoids re-walking trees).

type IntrospectPlan

type IntrospectPlan struct {
	AutoRun   []*PlanStep `json:"autoRun,omitempty"`
	ManualRun []TaskInfo  `json:"manualRun,omitempty"`
}

IntrospectPlan represents the full introspection structure. AutoRun shows the compositional tree structure (serial/parallel). ManualRun shows tasks as a flat list.

func BuildIntrospectPlan

func BuildIntrospectPlan(cfg Config) (IntrospectPlan, error)

BuildIntrospectPlan builds the introspection plan structure from config. This captures the full tree structure for AutoRun and flat list for ManualRun. The caller is responsible for JSON marshaling.

type Output

type Output struct {
	Stdout io.Writer
	Stderr io.Writer
}

Output holds stdout and stderr writers for task output. This is passed through the Runnable chain to direct output appropriately.

func GetOutput

func GetOutput(ctx context.Context) *Output

GetOutput returns the current output writers.

func StdOutput

func StdOutput() *Output

StdOutput returns an Output that writes to os.Stdout and os.Stderr.

func (*Output) Printf

func (o *Output) Printf(format string, a ...any) (int, error)

Printf formats and prints to stdout.

func (*Output) Println

func (o *Output) Println(a ...any) (int, error)

Println prints to stdout with a newline.

type PathFilter

type PathFilter struct {
	// contains filtered or unexported fields
}

PathFilter wraps a Runnable with path filtering. It implements Runnable, so it can be used anywhere a Runnable is expected.

func RunIn

func RunIn(r Runnable, opts ...PathOpt) *PathFilter

RunIn wraps a Runnable with path filtering. Use options to control where the Runnable executes.

Example:

pocket.RunIn(golang.Tasks(),
    pocket.Detect(golang.Detect()),
    pocket.Include("services/.*"),
    pocket.Exclude("vendor"),
)

func (*PathFilter) Resolve

func (p *PathFilter) Resolve() []string

Resolve returns all directories where this Runnable should run. It combines detection results with explicit includes, then filters by excludes. Results are sorted and deduplicated.

func (*PathFilter) ResolveFor

func (p *PathFilter) ResolveFor(cwd string) []string

ResolveFor returns the resolved paths filtered for the given working directory. If cwd is ".", returns all resolved paths. Otherwise, returns only paths that match cwd.

func (*PathFilter) RunsIn

func (p *PathFilter) RunsIn(dir string) bool

RunsIn returns true if this Runnable should run in the given directory. The directory should be relative to git root.

type PathOpt

type PathOpt func(*PathFilter)

PathOpt configures path filtering behavior for RunIn.

func Detect

func Detect(fn func() []string) PathOpt

Detect sets a detection function that returns directories where the Runnable should execute. The function should return directories relative to git root.

func Exclude

func Exclude(patterns ...string) PathOpt

Exclude adds patterns (regex) for directories to exclude. Directories matching any pattern are excluded from results.

func Include

func Include(patterns ...string) PathOpt

Include adds patterns (regex) for directories to include. Directories matching any pattern are included.

func Skip

func Skip(task *TaskDef, paths ...string) PathOpt

Skip configures a task to be skipped in specific paths. If no paths are specified, the task is skipped everywhere within this filter. Paths support regex patterns matched against the current execution path.

To make skipped tasks available for manual execution, add them to ManualRun with a different name using WithName():

AutoRun: pocket.RunIn(golang.Tasks(),
    pocket.Detect(golang.Detect()),
    pocket.Skip(golang.Test, "services/api", "services/worker"),
),
ManualRun: []pocket.Runnable{
    pocket.RunIn(golang.Test.WithName("integration-test"),
        pocket.Include("services/api", "services/worker"),
    ),
}

type PlanStep

type PlanStep struct {
	Type     string      `json:"type"`               // "serial", "parallel", "func"
	Name     string      `json:"name,omitempty"`     // Function name
	Usage    string      `json:"usage,omitempty"`    // Function usage/description
	Hidden   bool        `json:"hidden,omitempty"`   // Whether this is a hidden function
	Deduped  bool        `json:"deduped,omitempty"`  // Would be skipped due to deduplication
	Children []*PlanStep `json:"children,omitempty"` // Nested steps (for serial/parallel groups)
}

PlanStep represents a single step in the execution plan.

type Runnable

type Runnable interface {
	// contains filtered or unexported methods
}

Runnable is the interface for anything that can be executed. It uses unexported methods to prevent external implementation, ensuring only pocket types (TaskDef, serial, parallel, PathFilter) can satisfy it.

Users create Runnables via:

  • pocket.Task() for individual functions
  • pocket.Serial() for sequential execution
  • pocket.Parallel() for concurrent execution
  • pocket.RunIn() for path filtering

func Do

func Do(fn func(context.Context) error) Runnable

Do creates a Runnable from a function. Use this for arbitrary Go code that doesn't fit the Run/RunWith model, such as file I/O, API calls, or conditional logic.

Example:

pocket.Do(func(ctx context.Context) error {
    return os.WriteFile("output.txt", data, 0644)
})

func Download

func Download(url string, opts ...DownloadOpt) Runnable

Download creates a Runnable that fetches a URL and optionally extracts it. Progress and status messages are written to the context's output.

Example:

var Install = pocket.Task("install:tool", "install tool",
    pocket.Download(url,
        pocket.WithDestDir(binDir),
        pocket.WithFormat("tar.gz"),
        pocket.WithExtract(pocket.WithRenameFile("tool-1.0.0/tool", "tool")),
        pocket.WithSymlink(),
    ),
).Hidden()

func FromLocal

func FromLocal(path string, opts ...DownloadOpt) Runnable

FromLocal creates a Runnable that processes a local file (extract/copy). Useful for processing pre-downloaded or bundled archives.

func InstallGo

func InstallGo(pkg, version string) Runnable

InstallGo creates a Runnable that installs a Go binary using 'go install'. The binary is installed to .pocket/tools/go/<pkg>/<version>/ and symlinked to .pocket/bin/.

Example:

var Install = pocket.Task("install:linter", "install linter",
    pocket.InstallGo("github.com/golangci/golangci-lint/cmd/golangci-lint", version),
).Hidden()

func Parallel

func Parallel(items ...any) Runnable

Parallel composes items to run concurrently.

Returns a Runnable that executes items in parallel. Use it to:

  • Run independent tasks concurrently: Parallel(Lint, Test)
  • Compose in Config: Parallel(task1, task2)

Items can be *TaskDef, Runnable, or func(context.Context) error.

Example:

var CI = pocket.Task("ci", "run CI", pocket.Parallel(
    Lint,
    Test,
))

func Run

func Run(name string, args ...string) Runnable

Run creates a Runnable that executes an external command. The command runs in the current path directory with .pocket/bin in PATH.

Example:

pocket.Run("go", "fmt", "./...")
pocket.Run("golangci-lint", "run", "--fix", "./...")

func Serial

func Serial(items ...any) Runnable

Serial composes items to run sequentially.

Returns a Runnable that executes items in order. Use it to:

  • Define dependencies: Serial(Install, TaskImpl)
  • Compose tasks in Config: Serial(Format, Lint, Test)

Items can be *TaskDef, Runnable, or func(context.Context) error.

Example:

var Lint = pocket.Task("lint", "run linter", pocket.Serial(
    golangcilint.Install,
    func(ctx context.Context) error {
        return pocket.Exec(ctx, "golangci-lint", "run", "./...")
    },
))

type ShimConfig

type ShimConfig struct {
	// Name is the base name of the generated shim scripts (without extension).
	// Default: "pok"
	Name string

	// Posix generates a bash script (./pok).
	// This is enabled by default if ShimConfig is nil.
	Posix bool

	// Windows generates a batch file (pok.cmd).
	// The batch file requires Go to be installed and in PATH.
	Windows bool

	// PowerShell generates a PowerShell script (pok.ps1).
	// The PowerShell script can auto-download Go if not found.
	PowerShell bool
}

ShimConfig controls shim script generation.

type TaskDef

type TaskDef struct {
	// contains filtered or unexported fields
}

TaskDef represents a named function that can be executed. Create with pocket.Task() - this is the only way to create runnable functions.

The body can be:

  • pocket.Run(name, args...) - static command
  • pocket.Do(fn) - dynamic commands or arbitrary Go code
  • pocket.Serial(...) or pocket.Parallel(...) - compositions

Example:

// Simple: static command
var Format = pocket.Task("go-format", "format Go code",
    pocket.Run("go", "fmt", "./..."),
)

// Composed: install dependency then run
var Lint = pocket.Task("go-lint", "run linter", pocket.Serial(
    InstallLinter,
    pocket.Run("golangci-lint", "run", "./..."),
))

// Dynamic: args computed at runtime
var Test = pocket.Task("go-test", "run tests", testCmd())
func testCmd() pocket.Runnable {
    return pocket.Do(func(ctx context.Context) error {
        args := []string{"test"}
        if pocket.Verbose(ctx) {
            args = append(args, "-v")
        }
        return pocket.Exec(ctx, "go", append(args, "./...")...)
    })
}

// Hidden: tool installers
var InstallLinter = pocket.Task("install:linter", "install linter",
    pocket.InstallGo("github.com/org/linter", "v1.0.0"),
).Hidden()

func Clone

func Clone(task *TaskDef, opts ...TaskOpt) *TaskDef

Clone creates a copy of a task with modifications applied. This is useful for creating task variants at runtime, such as renaming a task to avoid duplicate names in ManualRun.

Example:

// Give a task a different name for ManualRun
pocket.Clone(golang.Test, pocket.Named("integration-test"))

// Clone with multiple modifications
pocket.Clone(myTask, pocket.Named("new-name"), pocket.AsHidden())

func Task

func Task(name, usage string, body any, opts ...TaskOpt) *TaskDef

Task creates a new named task. This is the primary way to create tasks that appear in the CLI.

The name is used for CLI commands (e.g., "go-format" becomes ./pok go-format). The usage is displayed in help output. The body can be:

  • Runnable - from Run, Do, Serial, Parallel
  • func(context.Context) error - legacy, wrapped automatically

Use options to configure the task:

var Lint = pocket.Task("lint", "run linter", lintCmd(),
    pocket.Opts(LintOptions{}),
    pocket.AsHidden(),
)

func WithOpts

func WithOpts(task *TaskDef, opts any) *TaskDef

WithOpts creates a copy of a task with different options. This is useful for creating task variants at runtime, such as applying CLI-parsed options or package-level configuration.

Example:

// In a task package's Tasks() function:
lintTask := Lint
if cfg.lint != (LintOptions{}) {
    lintTask = pocket.WithOpts(Lint, cfg.lint)
}

// In CLI option parsing:
taskWithOpts := pocket.WithOpts(task, parsedOpts)

func (*TaskDef) GetOpts

func (f *TaskDef) GetOpts() any

GetOpts returns the function's options, or nil if none.

func (*TaskDef) IsHidden

func (f *TaskDef) IsHidden() bool

IsHidden returns whether the function is hidden from CLI help.

func (*TaskDef) Name

func (f *TaskDef) Name() string

Name returns the function's CLI name.

func (*TaskDef) Run

func (f *TaskDef) Run(ctx context.Context) error

Run executes this function with the given context. This is useful for testing or programmatic execution.

func (*TaskDef) Usage

func (f *TaskDef) Usage() string

Usage returns the function's help text.

type TaskInfo

type TaskInfo struct {
	Name   string   `json:"name"`             // CLI command name
	Usage  string   `json:"usage"`            // Description/help text
	Paths  []string `json:"paths,omitempty"`  // Directories this task runs in
	Hidden bool     `json:"hidden,omitempty"` // Whether task is hidden from help
}

TaskInfo represents a task for introspection. This is the public type used by the introspection API for CI/CD integration.

func CollectTasks

func CollectTasks(r Runnable) ([]TaskInfo, error)

CollectTasks extracts task information from a Runnable tree. This uses Engine.Plan() internally to collect tasks without executing them. Path mappings are collected during the same walk. Tasks without RunIn() wrappers get ["."] (root only).

type TaskOpt

type TaskOpt func(*TaskDef)

TaskOpt configures a task created with Task().

func AsHidden

func AsHidden() TaskOpt

AsHidden marks a task as hidden from CLI help. Hidden tasks can still be executed but don't appear in ./pok -h. Use this for internal tasks like tool installers.

Example:

var Install = pocket.Task("install:tool", "install tool",
    pocket.InstallGo("github.com/org/tool", "v1.0.0"),
    pocket.AsHidden(),
)

func AsSilent

func AsSilent() TaskOpt

AsSilent suppresses the task header output (e.g., ":: task-name"). Use this for tasks that produce machine-readable output (JSON, etc.).

Example:

var Matrix = pocket.Task("gha-matrix", "output GHA matrix JSON",
    matrixCmd(),
    pocket.AsSilent(),
)

func Named

func Named(name string) TaskOpt

Named sets a different CLI name for the task. Use this when the same task needs different names in different contexts.

Example:

pocket.Task("integration-test", "run integration tests", testImpl,
    pocket.Named("integration-test"),
)

func Opts

func Opts(opts any) TaskOpt

Opts attaches CLI options to a task. Options are accessible via pocket.Options[T](ctx) in the function.

Example:

type FormatOptions struct {
    Config string `arg:"config" usage:"path to config file"`
}

var Format = pocket.Task("format", "format code", formatImpl,
    pocket.Opts(FormatOptions{Config: ".golangci.yml"}),
)

func Usage

func Usage(usage string) TaskOpt

Usage sets different help text for the task.

Example:

pocket.Task("test", "run tests", testImpl,
    pocket.Usage("run integration tests"),
)

type ToolConfig

type ToolConfig struct {
	// UserFiles are filenames to search for in the repo root.
	// Checked in order; first match wins.
	UserFiles []string

	// DefaultFile is the filename for the bundled default config,
	// written to .pocket/tools/<name>/ if no user config exists.
	DefaultFile string

	// DefaultData is the bundled default configuration content.
	DefaultData []byte
}

ToolConfig describes how to find or create a tool's configuration file.

Directories

Path Synopsis
cmd
pocket command
internal
scaffold
Package scaffold provides generation of .pocket/ scaffold files.
Package scaffold provides generation of .pocket/ scaffold files.
shim
Package shim provides generation of the ./pok wrapper scripts.
Package shim provides generation of the ./pok wrapper scripts.
Package tasks provides the entry point for running pocket tasks.
Package tasks provides the entry point for running pocket tasks.
github
Package github provides GitHub-related tasks.
Package github provides GitHub-related tasks.
golang
Package golang provides Go development tasks.
Package golang provides Go development tasks.
lua
Package lua provides Lua-related build tasks.
Package lua provides Lua-related build tasks.
markdown
Package markdown provides Markdown formatting tasks.
Package markdown provides Markdown formatting tasks.
python
Package python provides Python-related build tasks using ruff and mypy.
Package python provides Python-related build tasks using ruff and mypy.
tools
bun
Package bun provides bun runtime integration.
Package bun provides bun runtime integration.
golangcilint
Package golangcilint provides golangci-lint integration.
Package golangcilint provides golangci-lint integration.
govulncheck
Package govulncheck provides govulncheck integration.
Package govulncheck provides govulncheck integration.
mdformat
Package mdformat provides mdformat (Markdown formatter) tool integration.
Package mdformat provides mdformat (Markdown formatter) tool integration.
nvim
Package nvim provides Neovim tool integration.
Package nvim provides Neovim tool integration.
prettier
Package prettier provides prettier (code formatter) integration.
Package prettier provides prettier (code formatter) integration.
stylua
Package stylua provides stylua tool integration.
Package stylua provides stylua tool integration.
tsqueryls
Package tsqueryls provides ts_query_ls tool integration.
Package tsqueryls provides ts_query_ls tool integration.
uv
Package uv provides uv (Python package manager) tool integration.
Package uv provides uv (Python package manager) tool integration.

Jump to

Keyboard shortcuts

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