Adding a package

A checkout with just the tinycld shell (no feature packages) runs as a lean shell. To enable a feature - mail, contacts, calendar, drive, anything third-party - you bring its repository into the workspace as a member and run a single pnpm install at the root. “Linking” in this layout means being a present workspace member: pnpm creates the node_modules/@tinycld/* symlink on install, and the generator wires the package in on the postinstall hook.

Fresh-checkout default

Out of the box, the generator scans whichever member directories are present. With only the tinycld shell cloned, the app runs without any feature packages - you get authentication and an empty workspace, and that’s about it. You add exactly the features you want by re-running @tinycld/bootstrap --assemble-only with the slugs you need.

Add a feature with the bootstrap CLI

Run @tinycld/bootstrap in assemble-only mode from the workspace root. It clones the named feature (and the tinycld shell if it’s somehow missing), skipping any directory that already exists, and writes any missing workspace coordination files - so it’s safe to re-run as often as you like:

cd ~/code/tinycld
npx @tinycld/bootstrap@latest --assemble-only --with contacts
pnpm install            # links the new member + reruns the generator
cd tinycld && pnpm run dev

Pass --with more than once to add several at a time:

npx @tinycld/bootstrap@latest --assemble-only --with contacts --with mail --with drive
pnpm install

Pin to a specific tag/branch by suffixing --with <name>@<ref>. The same syntax works for tinycld (the shell, with @tinycld/core nested) so you can pin the whole workspace to a known-good combination:

npx @tinycld/bootstrap@latest --assemble-only \
    --with tinycld@v1.2.0 \
    --with mail@v0.3.1 --with contacts@main

Honor a non-default git host with TINYCLD_REPO_BASE (CI uses https://github.com/tinycld because runners have no SSH key; the default is git@github.com:tinycld):

TINYCLD_REPO_BASE=https://github.com/tinycld npx @tinycld/bootstrap@latest --assemble-only --with mail

What the install does

The root pnpm install produces the same end state every time, whether you just bootstrapped or are reinstalling after pulling changes:

  1. Creates a symlink under node_modules/@tinycld/<name> pointing at the member, so the canonical package name (read from the member’s own package.json.name - @tinycld/contacts, @acme/custom, or a bare my-pkg) resolves everywhere. Third-party scopes are first-class; the tooling doesn’t favor @tinycld/.
  2. Runs the code generator (tinycld/scripts/generate.ts) on the postinstall hook. It enumerates the present members (via tinycld.packages.ts), then materializes route re-exports, writes tinycld/tinycld.config.ts (the typed installed-package source of truth), generates help and Tailwind source wiring, and symlinks the package’s PocketBase migrations and Go server module into tinycld/server/.

Remove a package

Delete the member directory and re-run pnpm install at the root:

cd ~/code/tinycld
rm -rf contacts
pnpm install            # reruns the generator without contacts

The generator cleans up that package’s generated route re-exports and wiring on the next run. pnpm-workspace.yaml continues to list every possible member; pnpm tolerates members whose directories are absent, so you don’t need to edit the member list.

Typical workflow

cd ~/code/tinycld
npx @tinycld/bootstrap@latest --assemble-only --with contacts --with mail --with drive
pnpm install
cd tinycld && pnpm run dev

Those commands turn a lean shell into a working mail-contacts-drive app. The feature set is just “which member directories are present” - a CI job reproduces the same build by assembling the same members via bootstrap --assemble-only.

For the anatomy of what you just added, see Manifest. For scaffolding a new package of your own, see Creating a package.