Creating a package
@tinycld/bootstrap is the interactive scaffolder for new packages. One command produces a feature repo that matches the conventions every first-party package follows - manifest, CI workflow, tsconfig, sample screens or a settings panel, and (optionally) a Go server stub. (The canonical lint config lives at tinycld/biome.json and applies to every member — no biome.json ships in the new repo by default.) It’s the fastest way to go from idea to “a member of the workspace that typechecks.”
One-shot
npx @tinycld/bootstrap --new my-feature
--new <slug> selects scaffold mode (the alternative is --assemble-only, which assembles a workspace instead of scaffolding a package — see the CLI reference). The slug is kebab-case, 3–40 chars; it becomes the npm package name (@tinycld/my-feature), the URL segment (/a/[orgSlug]/my-feature/), and the Go module path (tinycld.org/packages/my-feature). Omit the slug to be asked.
Where the scaffolder puts things
The default target directory depends on whether you’re already in a TinyCld workspace:
- Attach mode — if your current directory is a workspace root (its
package.jsondeclares"name": "@tinycld/workspace"), the new package lands as a member at./<slug>/next to thetinycldshell. This is the typical setup for active TinyCld contributors. - Bootstrap mode — if your current directory is not a workspace root, the scaffolder creates a wrapper directory
./tinycld-<slug>/, assembles a workspace inside it (writes the workspacepackage.jsonand clones thetinycldshell, with@tinycld/corenested, when--linkis set or accepted), and scaffolds the package at./tinycld-<slug>/<slug>/. You end up with a self-contained workspace you cancdinto and run.
The detection looks for a package.json whose name is @tinycld/workspace — a coincidentally-named directory won’t false-match.
You can always override the auto-detection with --target ./somewhere-else.
Prompts
The scaffolder walks you through a short interactive session:
- Human-readable name - defaults to title-cased slug, used in
manifest.nameand the nav label. - Description - one sentence, reused in the manifest,
package.json, and README. - Preset - pick
fullfor a data package (mail/contacts/drive shape) orsettings-onlyfor a settings-panel-only package (google-takeout-import shape). - Icon, nav order, shortcut - only for the full preset. The icon is any lucide-react-native name.
- Include a Go server? - full preset only. No if you only need JS hooks and migrations.
- Target directory - defaults to
./my-feature. Must not exist or must be empty. - Link into the workspace now? - the scaffolder can assemble (or attach to) the workspace and run
pnpm installat the root for you, so the new member is wired in. Say yes to skip the manual steps below.
Every prompt has a matching flag, so the scaffolder can run fully non-interactively (handy for CI, scripted setups, and autonomous coding agents):
npx @tinycld/bootstrap --new my-feature \
--yes \
--preset full \
--icon check-square \
--no-server \
--no-link
See the bootstrap CLI reference for the full flag list, including the --assemble-only / --with workspace-assembly mode.
Presets
full - data package
Matches @tinycld/contacts, @tinycld/mail, @tinycld/calendar, @tinycld/drive. Package TypeScript lives under a tinycld/<slug>/ prefix (which the package.json exports map maps to subpaths like @tinycld/my-feature/screens/*). You get:
manifest.tswithroutes,nav,collections,migrations,seed,sidebar, and optionallyserver.tinycld/<slug>/screens/{_layout, index, [id]}.tsx- list + detail routes.tinycld/<slug>/{collections.ts, types.ts}- pbtsdb registration and schema types.tinycld/<slug>/{sidebar.tsx, provider.tsx}- optional UI scaffolding.tinycld/<slug>/seed.ts- an async seed function with a working example write.pb-migrations/<timestamp>_create_<slug>.js- a starter PocketBase migration.server/{go.mod, register.go}- Go module stub if the Go prompt was yes.
settings-only - service package
Matches @tinycld/google-takeout-import. No routes, no nav entry, no collections, no server - just a settings panel:
manifest.tswith onlyname,slug,description,settings.tinycld/<slug>/settings/main.tsx- the panel component.tinycld/<slug>/types.ts- empty surface for any public type exports.
Use this for integrations (import/export tools), admin surfaces, or anything that lives entirely under /a/<orgSlug>/settings/.
After scaffolding
If you accepted the Link into the workspace now? prompt, the scaffolder has already assembled (or attached to) the workspace - writing the workspace package.json, cloning the tinycld shell (with @tinycld/core nested) if needed, and running pnpm install at the root. Only git and GitHub remain manual:
cd my-feature
git init
git add .
git commit -m 'chore: initial scaffold'
gh repo create tinycld/my-feature --public --source=. --push
If you declined the link prompt, or want to link later, run @tinycld/bootstrap in assemble-only mode from the workspace root to make sure the tinycld shell is present, then install:
# from a workspace root (or an empty dir you want to become one)
cd ~/code/tinycld
npx @tinycld/bootstrap@latest --assemble-only # ensures the tinycld shell is present
# place your package directory (named after its slug) next to tinycld/, then:
pnpm install # links it + runs the generator
cd tinycld && pnpm run checks
pnpm run checks (from tinycld/) runs the ecosystem-wide Biome pass and the app’s typecheck. If it’s green, your scaffolded package is ready to develop in. Run a single member’s typecheck + unit tests with pnpm exec tinycld-pkg check from the member directory.
Then start the app:
cd ~/code/tinycld/tinycld
pnpm run dev
This builds and runs the Go PocketBase server, the Expo dev server, and a single-port HTTP proxy that fronts both. Open the URL it prints (default http://localhost:7100, or https:// if a localhost cert is present). For the full preset, your package’s nav entry shows up in the sidebar; for settings-only, your panel appears under the org settings.
To log in, first run pnpm run db:reset (from tinycld/) once — it seeds a test user and org and prints the credentials to use. See Logging in for the details.
What the templates assume
Scaffolded code imports core via the scoped path:
import { useOrgLiveQuery } from '@tinycld/core/lib/use-org-live-query'
import { Modal } from '@tinycld/core/ui/modal'
Intra-package imports use relative paths. ~/tinycld/<slug>/* is also aliased to the package’s own nested source.
@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. Don’t install core as a dependency. See Screens for the full story.
Contributing to another package’s sidebar
Not every new package wants its own nav entry. If your feature naturally extends one that already exists — for example, booking pages that belong inside the calendar sidebar — you can scaffold a package without nav or routes and ship a sidebar contribution instead. Drop sidebarContributions into your manifest, point at a component file, and add the matching package.json exports wildcard. The full pattern (including ordering, validation, and runtime gating with usePackages()) is documented in Sidebar slots.
For what each generated file means, walk through Anatomy. For bringing the new package into a workspace once it’s pushed, see Adding a package.