cabin new and cabin init
cabin new and cabin init generate a minimal single-package project layout: a cabin.toml, a
src/ tree, a public header tree (library scaffolds only), and a .gitignore. Both commands share
the same scaffold logic, so the generated bytes match exactly for the same kind / name pair.
cabin new <path>creates a new directory at<path>and fills it with the scaffold.<path>must not already exist.cabin initinitializes the current directory. The directory may already contain user files; the scaffold never overwrites them.
The default scaffold kind is a binary package (an executable target). Pass --lib to
generate a library package (a library target) instead. --bin and --lib are mutually
exclusive; passing both produces a clear error and no filesystem mutation.
Binary scaffold (default)
<dest>/
cabin.toml
src/
main.cc
.gitignore
The manifest declares a single executable target named after the package:
[package]
name = "hello"
version = "0.1.0"
cxx-standard = "c++17"
[target.hello]
type = "executable"
sources = ["src/main.cc"]
src/main.cc is a minimal int main that prints a greeting and returns zero. An existing
src/main.cc is preserved unchanged so re-running cabin init over a partially scaffolded project
never clobbers user code.
Library scaffold (--lib)
<dest>/
cabin.toml
include/
<package>/
<package>.hpp
src/
<package>.cc
.gitignore
The manifest declares a single library target with a public include/ directory:
[package]
name = "greeter"
version = "0.1.0"
cxx-standard = "c++17"
[target.greeter]
type = "library"
sources = ["src/greeter.cc"]
include-dirs = ["include"]
The generated header (include/<package>/<package>.hpp) and the implementation source
(src/<package>.cc) define a minimal add(int, int) function inside a namespace derived from the
package name. Hyphens in the package name are mapped to underscores when forming the C++ namespace
identifier (so hello-world becomes namespace hello_world), and a C++ keyword gains a trailing
underscore (class becomes namespace class_); the on-disk file paths keep the original package
name.
Existing header / source files at those paths are preserved unchanged.
.gitignore
Both cabin new and cabin init create a .gitignore at the destination when one does not already
exist. An existing .gitignore is preserved verbatim - Cabin never appends to it or rewrites it.
The generated file ignores Cabin’s default build-output directories:
/build/
/dist/
cabin.lock is intentionally not ignored. Cabin recommends committing the lockfile so
collaborators and CI converge on the same resolution; see lockfile.md.
Package name
Package names follow the package-name grammar: [a-z0-9][a-z0-9_-]*.
cabin new and cabin init scaffold a bare name; add a <scope>/ to [package].name before
publishing.
When --name is omitted, the package name is derived from the final path component, lowercased
(cabin new path/to/hello-world produces hello-world; cabin new Boa creates the directory
Boa with the package boa). If the lowercased component is still not a valid name (it is empty,
contains . or other characters outside the grammar, or starts with _ or -), Cabin falls back
to the literal name cabin-package.
Pass --name <name> to override the derived name. The override is used verbatim: --name Boa is
rejected with a suggestion of boa, as is any other name outside the grammar.
Errors
cabin new and cabin init produce actionable diagnostics for:
--binused together with--lib;- an invalid or scoped
--name(the message names the grammar and suggests the lowercase spelling when that alone would make it valid); - a destination that already exists (for
cabin new- the message suggestscabin initfor that case); - a parent directory that does not exist (for
cabin new); - a pre-existing
cabin.toml(rejected; nothing is overwritten).
When validation fails, cabin new removes the destination directory it created so a partially
scaffolded layout does not survive a validation error.