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. (Lint config lives in the app shell’s biome.json and applies to every member — no biome.json ships in the new repo.) It’s the fastest way to go from idea to “a member of the workspace that typechecks.”
One-shot
npx @tinycld/bootstrap my-feature
The positional argument is the slug - 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 it 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 toapp/andcore/. 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 clonesapp+corewhen--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
npm 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 my-feature \
--yes \
--preset full \
--icon check-square \
--no-server \
--no-link
See the bootstrap CLI reference for the full flag list, including the --tooling / --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 app + core if needed, adding your package to the workspaces array, and running npm 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, bring the package into a workspace yourself:
# from a workspace root that already has app/ and core/
cd ~/code/tinycld
npx @tinycld/bootstrap@latest --tooling # ensures app + core are present
# move/clone your package into a member slot named my-feature, then:
npm install # links it + runs the generator
cd app && npm run checks
npm run checks (from app/) runs the workspace lint + typecheck with your package present. If it’s green, your scaffolded package is ready to develop in. Run a single member’s checks with npx tinycld-pkg check from the member directory.
Then start the app:
cd ~/code/tinycld/app
npm 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.
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 its own repo (tinycld/core) and a workspace member - the scaffolded tsconfig.json extends ../app/tsconfig.package-base.json and aliases @tinycld/core/* to ../core/*, 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.
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.