Consume Tinbox from your own repo
Wire the tinbox package into a sibling repo before npm publish — the file: dep form, build-first, the exports subpaths, the zod-4 peer, and exactly what init/setup write.
You want to use Tinbox from a separate repo — to deploy your own instance, or
to author skills/agents against the live engine types — and Tinbox isn't on npm
yet. This guide is the shortcut past the archaeology: the dependency form, the
build-first rule, the exports subpaths that matter, the peer versions, and a
manifest of exactly what init/setup write so you can plan around them.
The dependency, before npm
The in-repo example uses "tinbox": "workspace:*", which only resolves inside
this monorepo. From a sibling repo, point the dep at the built package
directory:
// your-repo/package.json (or apps/<x>/package.json)
{
"dependencies": {
"tinbox": "file:../path/to/tinbox-mono/packages/tinbox"
}
}
Two things that bite here:
- Get the relative depth right. It's relative to the
package.jsonthat declares it, not the repo root. Fromyour-repo/apps/tinbox/package.jsonto a siblingtinbox-mono, that's typicallyfile:../../../tinbox-mono/packages/tinbox— count the..segments carefully. - Isolate the cross-repo
file:dep from your root lockfile. Give the consuming project its ownpnpm-workspace.yaml+ lockfile so thefile:link doesn't leak into your monorepo's root lockfile (this is how the drivechina tenant is structured).
Build the package first — dist/ must exist
The tinbox package ships built — its exports resolve to dist/:
"exports": {
"./cf": { "import": "./dist/cf/index.js", "types": "./dist/cf/index.d.ts" },
"./cf/skills": { "import": "./dist/cf/skills.js", "types": "./dist/cf/skills.d.ts" },
"./cf/types": { "import": "./dist/cf/types.js", "types": "./dist/cf/types.d.ts" },
"./cf/plugin": { "import": "./dist/cf/plugin.js", "types": "./dist/cf/plugin.d.ts" },
"./cf/agents": { "import": "./dist/cf/agents.js", "types": "./dist/cf/agents.d.ts" },
"./sdk": { "import": "./dist/sdk.js", "types": "./dist/sdk.d.ts" }
}
A fresh checkout has no dist/ — import "tinbox/cf/skills" resolves to
dist/cf/skills.js, which doesn't exist until a build runs. So:
# in tinbox-mono
pnpm -F tinbox build
# then re-install in your consuming repo so it picks up the fresh dist/
pnpm install --filter your-project
The
tinbox buildergonomic. When your instance isfile:-linked to a source checkout,tinbox build(a CLI verb) rebuilds the source package and relinks it before you deploy — so afile:consumer ships its latestdist/without you remembering the two-step.tinbox setup/ the tenantredeployscript run it for you.
The subpaths you'll actually import:
| Import | What it is |
|---|---|
tinbox/cf | createWorker() + TinboxConfig — the deploy entrypoint |
tinbox/cf/skills | defineSkill, builtinSkills, runEvals, stubFetch — author + test skills |
tinbox/cf/types | the worker env / binding types |
tinbox/cf/agents · tinbox/cf/plugin | agent config + the better-auth plugin |
tinbox/sdk | the Resend-style sending SDK |
Peer versions — pin zod to 4
The engine and its auth stack (better-auth / better-call) require zod 4
(^4.4.3). tinbox declares zod as a peerDependency with that major pinned,
so a consumer on zod 3 gets an actionable warning rather than a silent
dual-zod schema-identity bug (two zod copies whose instanceof checks don't match,
so inputSchema validation mysteriously fails). If you author skills/agents with
zod schemas, install the matching major:
pnpm add zod@^4.4.3
(The peer is marked optional, so a deploy-only consumer that never imports zod isn't forced to add it — but the moment you write a schema, match the major.)
What init / setup write — the output manifest
tinbox init / tinbox setup provision live Cloudflare resources, so you can't
run them headless — but you can plan around them. Two ways to see the shape
without touching Cloudflare:
tinbox setup --dry-run(alias--print-plan) prints the full plan — every resource it would create, the wrangler.jsonc ids it would fill, the secrets it expects, and the phase order — with no auth and no side effects.- The manifest below.
Files written / edited:
| Path | What happens |
|---|---|
wrangler.jsonc | the REPLACE_WITH_* placeholders for d1_databases[].database_id and kv_namespaces[].id are replaced with the real resource ids (comment-preserving) |
tinbox.config.ts | auth.owner is set to the sign-in email that may claim first-run ownership: Tinbox-auth by default, or local/external auth if the tenant skips Tinbox-auth |
.env.prod | gitignored — the local record of generated secrets that were pushed to Cloudflare |
.tinbox/logs/<ts>-setup.log | a tee of the run's output (gitignored) |
Cloudflare resources created (idempotent — existing ones are left as-is):
- a D1 database (
tinbox-<org>-db), an R2 bucket (tinbox-<org>-mail), a KV namespace (tinbox-<org>-kv), and the Queues referenced inwrangler.jsonc(-newsletter,-newsletter-dlq,-projection).
Secrets generated/pushed (to wrangler secret put + mirrored to .env.prod):
BETTER_AUTH_SECRET(required),ASSET_SIGNING_KEY(required), theVAPID_*trio (Web Push, generated), and any external key you supply (e.g.ANTHROPIC_API_KEY).
The one inherently-manual step: inbound Email Routing is account-level (not
expressible in wrangler.jsonc). setup can enable it + the catch-all → your
worker when you give it a CLOUDFLARE_API_TOKEN with email_routing:write;
otherwise it's a one-time dashboard step. tinbox doctor verifies it either way.
Half-configured
wrangler.jsonccaveat. A scaffolded config carriesREPLACE_WITH_*placeholders, so it can'twrangler deploy(ordev) until setup fills the real ids. If you commit it, a teammate/CI that pulls it can't deploy until they runtinbox setup(which is idempotent and fills them). The scaffold setsworkers_dev: trueso once deployed there's an immediate*.workers.devURL to smoke-test while a custom domain's cert provisions.
See also
- Writing a skill — the
defineSkillAPI and how to TDD a skill offline withrunEvals. - Deploy to Cloudflare — the full deploy paths.
tinbox doctor— read-only "what's set up vs still needed", with a--waitthat blocks until your custom domain serves.