bootstrap CLI
@tinycld/bootstrap does two jobs. In scaffold mode (--new) it produces a new feature package repo that already passes the workspace’s generator checks, CI, and typecheck. In assemble-only mode (--assemble-only) it assembles a workspace — writing the root coordination files from embedded templates and cloning app + core (plus any features you name with --with) into the current directory.
One of the two mode flags is required on every invocation. Running npx @tinycld/bootstrap with no mode flag prints a usage summary and exits 2.
Scaffold mode (--new)
npx @tinycld/bootstrap --new <slug>
The positional argument paired with --new is the package slug — kebab-case, 3–40 characters. It becomes:
- The npm name:
@tinycld/<slug> - The URL segment:
/a/[orgSlug]/<slug>/ - The Go module path:
tinycld.org/packages/<slug>
Omit the positional to be prompted for it.
Flags
Every prompt has a matching flag. Pass --yes to take defaults for everything else.
| Flag | Type | Description |
|---|---|---|
--new | boolean | Selects scaffold mode. Mutually exclusive with --assemble-only. |
<slug> | positional | Package slug. Required when --yes is set; otherwise prompted. |
--yes, -y | boolean | Skip all prompts and use defaults. Requires the positional slug. |
--name | string | Human-readable name. Defaults to title-cased slug. |
--description | string | One-sentence description. |
--preset | full | settings-only | Defaults to full under --yes. |
--icon | string | Lucide icon name. Full preset only. Default box. |
--nav-order | number | Integer 0–99. Full preset only. Default 20. |
--shortcut | string | Single lowercase letter, or empty. Full preset only. |
--server, --no-server | boolean | Include the Go server stub. Full preset only. Default true. |
--target | string | Output directory. Default depends on the cwd: if it’s a workspace root, defaults to ./<slug>/; otherwise to ./tinycld-<slug>/<slug>/ (bootstrap mode — see below). |
--link, --no-link | boolean | Link the new package into the workspace (assemble/attach + pnpm install at the root). Without either, you’ll be prompted; under --yes, defaults to linking. |
--no-link always wins over --yes — pass both when you want to scaffold without touching a workspace.
Non-interactive example
npx @tinycld/bootstrap --new my-feature \
--yes \
--preset full \
--icon check-square \
--no-server \
--no-link
This scaffolds with all defaults, no Go server, and no workspace linking. Suitable for CI, scripted setups, and autonomous coding agents.
Assemble-only mode (--assemble-only)
--assemble-only assembles a workspace in the current directory instead of scaffolding a package. It writes the workspace coordination files (package.json, pnpm-workspace.yaml, tinycld.packages.ts, vitest.config.ts, shared test stubs under tests/, .node-version, .go-version) from embedded templates inside bootstrap itself — there is no separate meta-repo to clone. It then clones the members you need: the tinycld shell (which carries @tinycld/core nested inside it) is always cloned, and each --with <feature> adds one feature. Directories that already exist are skipped (and files are never overwritten), so it’s safe to re-run.
# in an empty dir — assembles the workspace (root + app + core + features):
mkdir ~/code/tinycld && cd ~/code/tinycld
npx @tinycld/bootstrap@latest --assemble-only --with mail --with contacts
pnpm install # links members + runs the generator (postinstall)
| Flag | Type | Description |
|---|---|---|
--assemble-only | boolean | Selects assemble-only mode. Mutually exclusive with --new. |
--with <feature> | string (repeatable) | Also clone this feature member. Pass once per feature. Accepts --with name@ref to pin to a tag/branch/commit. Unknown feature names are rejected. |
Assemble-only honors TINYCLD_REPO_BASE to pick the git host (CI uses https://github.com/tinycld; the default is git@github.com:tinycld). It does not run pnpm install itself — the caller controls that (so CI can choose pnpm install --frozen-lockfile). A partial checkout is fully supported: npm tolerates members whose directories are absent, and the generator scans only the present ones.
Workspace detection (scaffold mode)
When --target is not set, the scaffolder picks one of two layouts based on the current directory:
- Attach — if
<cwd>/package.jsondeclares"name": "@tinycld/workspace", the new package goes to<cwd>/<slug>/as a member, and (with--link) the existing workspacepackage.jsongets the member added andpnpm installruns at the root. - Bootstrap — otherwise, the scaffolder creates
<cwd>/tinycld-<slug>/, places the package at<cwd>/tinycld-<slug>/<slug>/, and (with--link) assembles a workspace around it — writing the workspacepackage.jsonand cloningapp+corevia the same assemble-only logic — before installing.
This means npx @tinycld/bootstrap --new my-todo from an empty directory produces a self-contained ./tinycld-my-todo/ workspace with everything you need to run the app, while running it from inside a workspace root just adds another member next to app/ and core/.
The detection check is strict: it reads <cwd>/package.json and matches name === "@tinycld/workspace". A coincidentally-named directory won’t false-match.
Prompts (interactive scaffold mode)
| Prompt | Example | Notes |
|---|---|---|
| Package slug | my-feature | Skipped if passed as argv. Validates kebab-case; minimum 3 chars. |
| Human-readable name | My Feature | Defaults to title-cased slug. Used in manifest name and nav label. |
| One-sentence description | Does a thing well. | Used in manifest description, package.json, and README.md. |
| Preset | full or settings-only | See Presets. |
| Lucide icon | box | Full preset only. Any lucide-react-native name. Default box. |
| Nav order | 20 | Full preset only. Integer 0–99; controls sidebar position. |
| Keyboard shortcut | f | Full preset only. Single lowercase letter, or blank. |
| Include Go server? | y / n | Full preset only. If n, server/ and the manifest’s server field are omitted. |
| Target directory | ./my-feature | Must not exist or must be empty. |
| Link into workspace? | y / n | If yes, the scaffolder assembles (or attaches to) the workspace and runs pnpm install at the root. |
Presets
full — data package
The shape of @tinycld/contacts, @tinycld/mail, @tinycld/calendar, @tinycld/drive. You get routes, a sidebar, an optional provider, pbtsdb collections, PocketBase migrations, seed data, and (optionally) a Go server stub. Package TypeScript lives under a tinycld/<slug>/ prefix.
settings-only — service package
The shape of @tinycld/google-takeout-import. The package contributes a single Personal Settings panel — no routes, no nav entry, no collections, no server. Use this for integrations and admin-style tools.
Generated files
Both presets produce:
manifest.ts,package.json,tsconfig.json— lint config is not duplicated; the canonicaltinycld/biome.jsoncovers every memberREADME.md,.gitignore.github/workflows/ci.yml— assembles the workspace viabootstrap --assemble-only, installs at the root, and runstinycld-pkg check(+ e2e)tests/manifest.test.ts— smoke test asserting the manifest shape
The full preset additionally produces:
pb-migrations/<timestamp>_create_<slug>.jsserver/go.mod,server/register.go(if the Go server prompt is yes)tinycld/<slug>/{collections,provider,seed,sidebar,types}.tstinycld/<slug>/screens/{_layout,[id],index}.tsx
The settings-only preset additionally produces:
tinycld/<slug>/types.tstinycld/<slug>/settings/main.tsx
After scaffolding
The CLI prints the steps it didn’t run — it never touches git or gh. Typical next steps:
cd my-feature
git init
git add .
git commit -m 'chore: initial scaffold'
gh repo create tinycld/my-feature --public --source=. --push
If you didn’t pass --link (or said no to the prompt), bring the package into a workspace now:
# from a workspace root that already has the tinycld shell
cd ~/code/tinycld
pnpm install # links the new member + runs the generator
cd tinycld && pnpm run checks
Adding the new package as a present member is enough to see it in navigation; the generator wires routes, collections, migrations, and settings panels automatically.
Import conventions in scaffolded code
Templates use the scoped @tinycld/core path:
import { useOrgLiveQuery } from '@tinycld/core/lib/use-org-live-query'
import { Modal } from '@tinycld/core/ui/modal'
@tinycld/core is a nested member inside the tinycld repo (at tinycld/core/). The scaffolded tsconfig.json extends @tinycld/core/tsconfig.package-base.json by package name, and @tinycld/core/* resolves by package name through the node_modules/@tinycld/* symlink, so resolution works as soon as the package is a present member.
Intra-package imports use relative paths; ~/tinycld/<slug>/* is also aliased to the package’s own nested source if you want an absolute form.
For the published source and template internals, see the bootstrap repo.