Skip to content

About

πŸš€ A CLI tool written in Go to standardize project scaffolding, library creation, and internal microservices using predefined templates and architectural patterns. Say goodbye to boilerplate!

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Latest commit

Β 

History

46 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

scaffold-cli

πŸš€ A CLI tool written in Go to standardize project scaffolding, library creation, and internal microservices using predefined templates and architectural patterns. Say goodbye to boilerplate!

License: MIT

scaffold-cli is a universal, dynamically-extensible scaffolding engine. Scaffolds, versions, dimensions, templates, selector values, and even the CLI flag names themselves are declared by templates at runtime β€” none of it is hardcoded in the binary. The engine only knows how to walk an inheritance chain and render; what gets generated comes entirely from a separate templates repository (see Requirements below).

scaffold-cli demo: list, drill into a version, dry-run a create, then lint the whole templates repo

Features

  • Nothing hardcoded. Scaffolds, versions, dimensions, templates, selector values, and even CLI flag names are declared entirely in the templates repository (jig.yaml), not the binary β€” point scaffold at a different templates repo and it drives a completely different toolchain.
  • One inheritance chain, one precedence rule. scaffold β†’ version β†’ dimension β†’ template β†’ overlay, later always wins β€” so a version can override a single file without duplicating everything else it doesn't change.
  • Inspect before you write anything. --dry-run (which files would be produced), --print (what's actually in them), and --explain (which level of the chain contributed each file) all run the full resolution with nothing touching disk.
  • Values files, not just flags. -f values.yaml supplies the scaffold/template/name and every variable in one file; -f may repeat (later wins), and a command-line flag always beats a file.
  • lint --build proves the output actually works, not just that templates parse β€” it runs each combination's own verify: command (e.g. mvn test) against a real scratch build and exits non-zero on any failure, so it doubles as a CI gate for the templates repo.
  • Prebuilt binaries for Linux, macOS, and Windows (amd64 + arm64) on every tagged release.

Requirements

  • Go 1.26 or newer (to build from source).
  • A templates repository laid out for this engine β€” see scaffold-templates, the companion repo that holds the actual scaffolds, versions, templates, and their jig.yaml-driven inheritance tree. scaffold-cli itself ships with none of that content; it only knows how to read and render it. By default the CLI looks for a checkout of it in a folder literally named scaffolding-code next to where you run it β€” clone it under that name, or point elsewhere with --scaffolding-code=<path> (see Configuration for every way to set that path).

Install

Quick install: the real install.sh downloading v0.1.0, verifying its checksum, and running

Every tagged release publishes prebuilt binaries for Linux, macOS, and Windows (amd64 + arm64) β€” see the Releases page. The install scripts below fetch the right one for your platform, verify its checksum, and put it on your PATH.

Linux / macOS:

curl -fsSL https://raw.githubusercontent.com/yusronMu77/scaffold-cli/main/install.sh | sh

Windows (PowerShell):

irm https://raw.githubusercontent.com/yusronMu77/scaffold-cli/main/install.ps1 | iex

Both accept a pinned version (SCAFFOLD_CLI_VERSION=v0.3.0 / -Version v0.3.0) and a custom install directory (SCAFFOLD_CLI_INSTALL_DIR / -InstallDir) if you'd rather not use the default.

Or grab an archive directly from the Releases page and extract the scaffold (or scaffold.exe) binary yourself.

Build from source

git clone https://github.com/yusronMu77/scaffold-cli.git
cd scaffold-cli
go build -o scaffold .

This produces a scaffold (or scaffold.exe on Windows) binary in the current directory. Verify it with:

./scaffold --version

Configuration

The --scaffolding-code=<path> flag can be skipped on every invocation by setting it once, resolved in this order (first match wins):

  1. --scaffolding-code=<path> on the command line.
  2. The SCAFFOLD_CODE environment variable.
  3. scaffolding_code: <path> in a .scaffold.yaml in the current directory.
  4. scaffolding_code: <path> in a .scaffold.yaml in your home directory.
  5. A scaffolding-code folder next to the scaffold executable itself.
  6. ./scaffolding-code, as a last resort.

Example .scaffold.yaml:

scaffolding_code: ../scaffold-templates

Usage

scaffold-cli has five commands: init to bootstrap a fresh templates root, list to browse what's available, create to generate a project, lint to check that a templates repository is healthy, and learn to draft a template from an existing example.

init β€” bootstrap a fresh templates root

scaffold init [path]

Writes a starter jig.yaml into path (default .), creating the directory if needed. It's a pure local file write - no network calls, no git init - meant for starting a brand-new, independent templates repository from zero rather than cloning scaffold-templates. The starter registry is deliberately empty (values: []), so list/create correctly report nothing to do until you register your first scaffold; pass --force to overwrite an existing jig.yaml at path.

list β€” browse what's available

scaffold list                        # known scaffolds
scaffold list <scaffold>             # versions, templates, and optional dimensions
scaffold list <scaffold> --full      # same, plus every template's own tree and variables
scaffold list <scaffold> <template>  # full selector tree and variables for that template

<template> picks a value from the required dimension of the resolved version β€” it's not how you pick the version itself. To browse a specific version, use its selector flag (--scaffold-version by default, whatever the scaffold's jig.yaml names it as):

scaffold list spring-boot --scaffold-version=hello-world

If that version has no templates/ dimension β€” it's a leaf, like hello-world above β€” list prints its variables directly instead of asking for a <template>.

create β€” generate a project

scaffold create <scaffold> <template> <name> [--flag=value ...]

All three positional arguments (scaffold, template, name) can also come from a values file:

scaffold create -f values.yaml
scaffold create -f base.yaml -f prod.yaml --name=payment-canary   # -f may repeat, later wins

A values file is just the flag namespace without the dashes (--package=x becomes package: x). Command-line flags always win over values-file entries.

Useful flags for inspecting before you write anything:

Flag What it shows
--dry-run Which files would be produced
--print What is actually in them, printed to stdout
--explain Which level of the inheritance chain contributed each file

--print-written is the one mode that does write: it performs a normal create and then echoes the exact final content of every file written or spliced, so a caller wanting both the write and a verifiable transcript of what landed gets both in one call instead of a --print pass followed by a separate create. It cannot be combined with --dry-run/--print/--explain.

lint β€” check that the templates repository is healthy

scaffold lint [<scaffold>] [--build]

Renders every registered combination in memory and reports anything that breaks β€” a registered value with no jig.yaml, a template that fails to parse, a variable with no default, and so on. Add --build to also run each combination's own verify: commands against a real scratch build (slower, but the only way to know the generated project actually builds). Exit code is non-zero on any failure, so it works as a CI gate.

learn β€” draft a template from an existing example

scaffold learn <path> --output=<dir> [--provider=anthropic|openai] [--model=...] [--base-url=...]
scaffold learn <path> --output=<dir> --draft=<path|->   # already-reasoned draft, no provider call
scaffold learn <path1> <path2> ... --output=<dir>       # generalize across 2+ examples at once

--output must be an empty (or not-yet-existing) directory; pass --force to write into one that already holds something. Credential files (*.pem, id_rsa, .env, kubeconfig, ...) and symlinks are never sent to a provider, and the ones skipped are listed before the call is made.

Points at one already-written example folder (a real controller, a CDK stack, ...), calls an LLM once to separate invariant structure from variable names/paths/fields, and writes the result to --output as a draft jig.yaml plus templated files. --output is required β€” the draft is a candidate, not yet wired into any templates repository, so it's on you to review it (and move it into place) before create/list/lint would ever see it. Regenerating afterward goes through the same deterministic create path as every other template β€” zero further AI calls per instance.

A draft's file paths are relative to the scanned example folder itself, not the destination the template will eventually write to once registered. Don't bake a real project's destination prefix (e.g. a per-instance nested subdirectory) into a draft's own paths β€” scaffold learn-review compares the draft's render byte-for-byte against that same example folder treated as the root, so a baked-in prefix reports the entire draft as mismatched. Add that nesting as a target: override afterward, once you've promoted the draft and are wiring it into the real templates tree.

learn is not tied to one LLM vendor. Set exactly one of these and it's picked automatically (--provider disambiguates if both happen to be set):

Env var Provider
ANTHROPIC_API_KEY Anthropic (Messages API)
OPENAI_API_KEY OpenAI-compatible Chat Completions API

--base-url points the OpenAI-compatible provider at any endpoint that mimics the same shape (Groq, OpenRouter, a local Ollama server, ...) without any code changes. --model overrides the per-provider default.

This makes a real call to whichever provider you configure, at that provider's usual cost.

If you're running learn locally without an agent driving it, here are vetted starting points for the --base-url path (e.g. an Ollama server) β€” not an exhaustive model roundup, just four that hold up per 2026-09 research:

Model Why
devstral:24b Apache 2.0; only one of the four with a published SWE-Bench Verified score (46.8%)
gpt-oss:20b Apache 2.0, OpenAI's own open-weight model; runs CPU-only on 16GB RAM, no GPU needed
qwen3-coder:30b Strongest coding quality per VRAM, but needs a 24GB+ GPU or a 32GB Mac
granite4:8b IBM; trained explicitly for tool-use and structured JSON output, fits small machines

Given two or more paths, learn generalizes across all of them in one call instead of just one β€” useful when a single example under-constrains what's actually invariant vs. variable. A variable's default is always drawn from the first path given, so review the result with scaffold learn-review <draft-dir> <path1> afterward, exactly as for a single example. A single <path> behaves exactly as it always has.

--draft=<path|-> skips the provider call entirely. An AI agent invoking learn (e.g. via scaffold-cli-skill) is already an LLM β€” rather than pay for a second, separately-billed model call, it can do the invariant/variable separation itself and hand the result straight to learn as JSON (matching the same schema a provider call would produce β€” see internal/learn/prompt.go for the exact shape), either as a file path or - for stdin. No ANTHROPIC_API_KEY/ OPENAI_API_KEY/--provider/--model/--base-url needed in this mode; combining --draft with any of those is rejected rather than silently ignored.

Creating a template

Every scaffold, version, dimension, template, and variable lives in the templates repository, not this engine β€” see Requirements. Adding or changing one means writing a jig.yaml; the full contract for that file (reserved names, the precedence rule, a minimal worked example) is documented in scaffold-templates' README.

A note on go.mod

Structured manifests (application.yml, package.json, ...) can opt into merge: so a later create deep-merges into what's already on disk instead of replacing it. go.mod is deliberately excluded from this: Go's own go mod tidy reconciles the require block from source imports far more correctly than scaffold-cli parsing/merging it as text ever could, and create never executes anything on your behalf (no post_hooks) β€” run go mod tidy manually after adding a new entity/component to a go-chi project.

License

Distributed under the MIT License.

Support

If scaffold-cli saves you some boilerplate, consider supporting its development:

About

πŸš€ A CLI tool written in Go to standardize project scaffolding, library creation, and internal microservices using predefined templates and architectural patterns. Say goodbye to boilerplate!

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages