A CLI tool for managing development workspaces with pattern-based scaffolding, workspace metadata, and plugin support.
brew install cloudygreybeard/tap/hackgo install github.com/cloudygreybeard/hack@latestgit clone https://github.com/cloudygreybeard/hack
cd hack
make installThe shell wrapper enables hack to change your working directory. Install it with:
hack bootstrap --installThis detects your shell and appends the integration to the appropriate rc file (~/.bashrc, ~/.zshrc, or ~/.config/fish/config.fish).
To print the snippet without installing:
hack bootstrap # auto-detect shell
hack bootstrap --shell zsh # specific shellhack # cd to the most recently modified workspace
hack api # cd to the best-matching workspace (fuzzy ranked)
hack list # list all workspaces
hack list api # filter by substringhack create my-project # empty workspace with README.md
hack create my-project -p go-cli # apply a pattern
hack create my-project -p go-cli -a myapp # custom app directory name
hack create my-project -p go-cli -i # interactive variable prompts
hack create my-project --label lang=go # set labels at creation time
hack create my-project -p go-cli --dry-run # preview without writing fileshack edit my-project # open in editor/IDE (configurable)
hack rename old-name new-name # rename workspace (preserves date prefix)
hack rename proj 2026-03-20.new # rename with new date prefix
hack rm my-project # remove workspace (with confirmation)
hack archive my-project # move to .archive/
hack archive --list # list archived workspaces
hack archive --restore foo # restore from archivehack rename renames the workspace directory, updates .hack.yaml metadata, and migrates Cursor IDE workspace storage so that chat history and settings follow the rename.
Cursor must be closed before renaming. If Cursor is running, hack rename refuses to proceed (override with --force, though this is not recommended). Renaming a workspace while an agent session is active in that workspace will terminate the session: the agent's working directory, file watchers, and shell sessions all become invalid, and the conversation context references paths that no longer exist. Close all Cursor windows for the workspace before renaming.
Use --no-cursor to skip the Cursor storage migration entirely (useful on systems without Cursor or when chat history is not important).
Workspaces store Kubernetes-style labels and annotations in .hack.yaml:
hack label my-project domain=aro lang=go # set labels
hack label my-project domain- # remove a label (trailing dash)
hack label my-project --list # show labels
hack annotate my-project jira=OCPBUGS-123 # set annotations
hack list -l domain=aro # filter by label selector
hack list -l domain=aro,lang=go # multiple labels (AND)
hack list --show-labels # display labels alongside namesPatterns are reusable project templates stored in ~/.hack/patterns/. Each contains a pattern.yaml and a template/ directory with files processed through Go text/template.
hack pattern list # list installed patterns
hack pattern show go-cli # show details, variables, hooks
hack pattern install ./my-pattern # install from local directory
hack pattern install org/repo # install from GitHub (shorthand)
hack pattern install org/repo//subpath # install from repo subdirectory
hack pattern install https://example.com/p.tar.gz # install from tarball
hack pattern sync ./patterns # bulk install from a directory
hack pattern update # re-install all from recorded sources
hack pattern update go-cli # update a specific pattern
hack pattern list --outdated # check for newer versionsReverse-scaffold a pattern from an existing workspace:
hack pattern extract my-project # extract to ./my-project/
hack pattern extract my-project -n my-pat # custom pattern name
hack pattern extract my-project --install # extract and install directly
hack pattern extract my-project --no-templatise # copy files without substitution
hack pattern extract my-project --app-only myapp # extract only an app subdirectoryThe extract command replaces concrete values (workspace name, app name, module path, year) with template variables and escapes pre-existing template expressions for round-trip safety.
~/.hack/patterns/my-pattern/
├── pattern.yaml
└── template/
├── README.md.tmpl # processed through text/template
├── static-file.txt # copied as-is
└── {{app_name}}/ # directory name expanded from variables
├── main.go.tmpl
└── go.mod.tmpl
name: my-pattern
description: Example pattern
version: 1.0.0
weight: 10
labels:
lang: go
type: cli
default_labels:
lang: go
inherits:
- pattern: base-pattern
- patternSelector:
matchLabels:
layer: common
variables:
- name: name
description: Project name
required: true
- name: module
description: Go module path
default: ""
post_create:
- "cd {{.app_name}} && go mod tidy"| Variable | Description | Example |
|---|---|---|
{{.name}} |
Workspace name | my-project |
{{.app_name}} |
App directory name | my-tool |
{{.Name}} |
TitleCase app name | MyTool |
{{.module}} |
Go module path | github.com/org/my-tool |
{{.year}} |
Current year | 2026 |
{{.date}} |
Creation date | 2026-01-26 |
Patterns can inherit from others via inherits in pattern.yaml. Each entry is either a direct name reference or a label-based selector:
inherits:
- pattern: base-refs
- patternSelector:
matchLabels:
layer: commonInheritance is resolved with topological sort. The weight field controls application order (lower weight applied first). Cycles are detected and rejected.
A minimal example pattern is included in examples/patterns/hello/:
hack pattern install examples/patterns/hello
hack create my-project -p helloExecutables in ~/.hack/plugins/ are automatically discovered and registered as subcommands. A file named hack-deploy becomes available as hack deploy.
hack plugin list # list installed pluginsPlugins receive these environment variables:
HACK_ROOT_DIR-- root directory for workspacesHACK_PATTERNS_DIR-- patterns directoryHACK_PLUGINS_DIR-- plugins directory
Configuration is read from (in ascending order of precedence):
- Built-in defaults
- Config file (
~/.hack.yaml) - Environment variables (
HACK_*) - Command-line flags
Create a config file:
hack config init # create ~/.hack.yaml with defaults
hack config show # display current configuration
hack config path # show config file location# ~/.hack.yaml
root_dir: ~/hack
patterns_dir: ~/.hack/patterns
plugins_dir: ~/.hack/plugins
editor: vim
ide: ""
edit_mode: auto # auto, terminal, or ide
git_init: true
create_readme: true
interactive: false
default_org: "" # GitHub org for module paths (empty uses example.com)hack completion bash | source /dev/stdin # bash
hack completion zsh > "${fpath[1]}/_hack" # zsh
hack completion fish | source # fish
hack completion powershell | Out-String | Invoke-Expression # powershellThe demo recording runs real hack commands inside a container for reproducibility. To re-record:
./hack/record-demo.shSee hack/demo/ for the Containerfile, demo script, and stub patterns used in the recording.
Apache 2.0. See LICENSE.