A general-purpose developer CLI for scaffolding projects from templates. Define variables, write template files, run hooks — specs handles the rest.
- Installation
- Quick start
- Commands
- Template structure
- project.yaml
- Skipping binary files
- Source formats
- Template functions
- Storage
- Development
- License
Homebrew (macOS):
brew install specsnl/tap/specsFrom source:
go install github.com/specsnl/specs-cli@latestDownload a binary from the releases page.
Use a template directly without registering it first:
specs use github:specsnl/my-template ./my-projectOr register a template and reuse it later:
specs template download github:specsnl/my-template my-template
specs template use my-template ./my-projectA one-off command: fetch a template from any source, execute it into <target-dir>, then discard the download. Nothing is saved to the registry. For templates you'll reuse, use specs template download instead.
specs use github:specsnl/go-service ./new-service
specs use ./local-template ./output --use-defaults
specs use github:specsnl/go-service ./new-service --arg projectName=my-serviceFlags:
| Flag | Description |
|---|---|
--values <file> |
Load variable values from a JSON or YAML file (.yaml/.yml → YAML, otherwise JSON) |
--arg <key=value> |
Set a single variable (repeatable) |
--use-defaults |
Accept all defaults without prompting |
--no-hooks |
Skip pre/post-use hooks |
Manage a local registry of named templates. Unlike specs use, downloaded templates are stored persistently and can be reused.
| Subcommand | Description |
|---|---|
list / ls |
List registered templates with update status. Stale statuses (older than 24 hours) are refreshed automatically on each run. |
save <path> <name> |
Register a local directory as a template |
download <source> <name> |
Download a remote template and save it to the local registry |
use <name> <target-dir> |
Execute a registered template |
validate <path> |
Check if a template directory is valid |
rename / mv <old> <new> |
Rename a registered template |
delete / rm / remove / del <name>... |
Remove one or more templates from the registry |
update [name] |
Force-refresh the cached update status; updates all template statuses if no name is given |
upgrade [name] |
Apply available updates; upgrades all remote templates if no name is given |
template use accepts the same flags as specs use (--values, --arg, --use-defaults, --no-hooks).
template download and template save accept -f / --force to overwrite an existing template with the same name.
For machine-readable list output, use the global --output json flag (see Global flags).
| Flag | Description |
|---|---|
--debug |
Enable debug-level logging |
--safe-mode |
Disable env/filesystem functions and skip hooks |
--no-env-prefix |
Remove the SPECS_ prefix from hook environment variables |
--output / -o |
Output format: pretty (default, styled) or json (NDJSON, for scripting) |
A template is a directory with this layout:
my-template/
├── project.yaml # Variable schema, defaults, and hooks
└── template/ # Files and directories to render
├── {{ .projectName }}/
│ └── main.go
└── README.md
Both project.yaml (or project.json) and a template/ directory are required.
Templates use {{ }} by default — standard Go text/template syntax:
Hello, {{ .projectName }}!
All standard Go template syntax works inside {{ }}, including if, range, with, and pipes.
Directory and file names are also templated:
{{ .projectName }}/
{{ if .useDocker }}Dockerfile{{ end }}
main.go
To avoid conflicts with tools that also use {{ }} (e.g. GitHub Actions, Helm), add __delimiters to project.yaml to use a custom pair:
__delimiters:
left: "[["
right: "]]"With [[ ]] configured, {{ }} in your template files passes through unchanged.
Defines the variables your template accepts and their defaults, plus optional computed values and hooks.
Each variable is a top-level key whose value is the default. The prompt type is inferred from the YAML value type:
| YAML value | Prompt |
|---|---|
"my-app" (string) |
Text input |
false / true (bool) |
Yes/No confirm |
["MIT", "Apache-2.0"] (array) |
Select list |
For select lists, the first option is the default — it is pre-selected when prompting interactively and chosen automatically when using --use-defaults.
# Variables — value is the default; type is inferred from the YAML value
projectName: "my-app"
useDocker: false
license:
- MIT
- Apache-2.0
- GPL-2.0
# A string default can reference other variables using {{ }} expressions
dockerImage: "{{ hostname }}.azurecr.io/{{ .projectName }}"
# Computed values — derived after prompting, not shown to the user
computed:
packagePath: "github.com/{{ username }}/{{ .projectName }}"
year: "{{ now | date \"2006\" }}"
# Hooks — run before and after rendering
hooks:
pre-use:
- echo "Creating {{ .projectName }}..."
post-use:
- git init
- go mod tidyEntries under computed: are evaluated after all prompting is complete. They add new keys to the template context and are never shown as prompts. Values may reference user-provided variables.
specs analyzes your template files at runtime. Variables that only appear inside conditional blocks ({{ if .someFlag }}) are only prompted when their condition is actually satisfied — keeping the interactive flow focused and minimal.
Hooks run shell commands before (pre-use) or after (post-use) rendering. They have access to all template variables as environment variables (prefixed with SPECS_ by default):
# Available in hooks:
# SPECS_PROJECTNAME=my-app
# SPECS_PACKAGEPATH=github.com/user/my-app
echo "Initializing $SPECS_PROJECTNAME"
git initHook commands may use {{ }} template expressions, which are rendered before execution. To skip hooks for a single run, pass --no-hooks.
Alternatively, hooks can be defined as scripts in a hooks/ directory at the template root (next to project.yaml):
my-template/
├── project.yaml
├── template/
└── hooks/
├── pre-use.sh
└── post-use.sh
Either inline YAML hooks or a hooks/ directory may be used — not both.
Create a .specsverbatim file in the template root to list glob patterns for files that should be copied as-is without template rendering:
*.png
*.jpg
*.gif
*.woff2
dist/**
The <source> argument in specs use and specs template download accepts:
| Format | Example |
|---|---|
| GitHub shorthand | github:user/repo |
| GitHub + branch | github:user/repo:main |
| HTTPS URL | https://github.com/user/repo |
| SSH (SCP-style) | git@github.com:user/repo |
| SSH URL | ssh://git@github.com/user/repo |
| Local path | ./path/to/template |
| Local (explicit) | file:./path/to/template |
Local paths are only accepted by specs use. For registering a local directory as a named template, use specs template save instead.
SSH clones authenticate automatically via SSH agent (if SSH_AUTH_SOCK is set) or standard key files (~/.ssh/id_ed25519, id_rsa, id_ecdsa). Host key verification uses ~/.ssh/known_hosts.
Templates have access to 200+ functions provided by Sprout, plus a set of specs-specific functions.
| Function | Signature | Description |
|---|---|---|
hostname |
hostname → string |
System hostname |
username |
username → string |
Current OS username |
toBinary |
toBinary <int> → string |
Integer to binary string |
formatFilesize |
formatFilesize <bytes> → string |
Human-readable file size (e.g. "1.0 MB") |
password |
password <length> <digits> <symbols> <noUpper> <allowRepeat> → string |
Generate a secure random password |
Examples:
Default registry: {{ hostname }}.azurecr.io
Author: {{ username }}
Secret key: {{ password 32 4 4 false false }}
Sprout organizes its functions into registries. All of the following are available in templates:
| Category | Example functions |
|---|---|
| Strings | toUpper, toLower, toPascalCase, toSnakeCase, toKebabCase, trim, replace, contains, repeat |
| Encoding | base64Encode, base64Decode, toJson, fromJson, toYaml, fromYaml |
| Regex | regexMatch, regexFind, regexReplaceAll |
| Collections | list, dict, append, prepend, uniq, keys, values, merge |
| Date & time | now, date, dateModify, dateAgo, duration |
| Identity | uuidv4, uuidv5 |
| Crypto | sha256sum, sha1sum, md5sum, bcrypt |
| Numeric | add, sub, mul, div, mod, floor, ceil, round |
| Semver | semver, semverCompare |
| Network | getHostByName |
| Random | randInt, randAlpha, randAlphaNum, randAscii |
| Reflection | typeOf, kindOf, kindIs |
| Environment | env, expandenv (disabled in --safe-mode) |
| Filesystem | osBase, osDir, osExt (disabled in --safe-mode) |
Note: Sprout uses Go-convention camelCase names. If you're migrating from sprig, see the rename table in the architecture docs.
Full function reference: docs.gosprout.dev.
Templates are stored under the XDG config directory (respects $XDG_CONFIG_HOME):
~/.config/specs/
└── templates/
├── my-template/
└── another-template/
Requirements: Task and Docker — build and test commands run inside a Docker container, so no local Go installation is needed. If you prefer to run Go commands directly on your host instead, Go 1.26+ is required.
Build the Docker images first (one-time setup):
task dc:buildThen:
task build # Build the binary for the current platform
task test # Run unit testsList all available tasks:
task --listMIT — see LICENSE.