diff --git a/README.md b/README.md
index db5453e..16416d6 100644
--- a/README.md
+++ b/README.md
@@ -55,6 +55,20 @@ that an LLM can read, diff, and safely modify without breaking the runtime.
| [`packages/expense/`](./packages/expense) | Employee expense & reimbursement β multi-line reports, category policy, amount-tiered approval, reimbursement tracking | β
v0 | 4011 | [](https://stackblitz.com/github/objectstack-ai/templates/tree/main/packages/expense) |
| `packages/sales-pipeline/` | Lite CRM (leads, opportunities, accounts) | π§ planned | 4007 | β |
+### Run them all at once
+
+Want every template in **one runtime, one app launcher** β as if you installed
+them all together from the marketplace? Use the `all` aggregator:
+
+```bash
+pnpm dev:all # builds every template, composes them, boots on :4000
+```
+
+It compiles each template's artifact into a single **environment** bundle and
+serves it (admin: `admin@objectos.ai` / `admin123`). See
+[`packages/all/`](./packages/all) for how composition happens at the
+environment layer without breaking the one-app-per-package rule (ADR-0019).
+
> **StackBlitz tip:** every template runs in the browser via `@objectstack/driver-sqlite-wasm` (sql.js). Local dev still uses `better-sqlite3` (listed as an optional dependency) for full native speed.
> Looking for a full reference app? See [hotcrm](https://github.com/objectstack-ai/hotcrm) β the production-grade CRM built on the same engine.
diff --git a/package.json b/package.json
index 4a4a56e..41c1819 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
},
"scripts": {
"dev": "pnpm --filter @objectlab/todo dev",
+ "dev:all": "pnpm -r --filter=!@objectlab/all build && pnpm --filter @objectlab/all dev",
"typecheck": "pnpm -r typecheck",
"build": "pnpm -r build",
"test": "pnpm -r test",
diff --git a/packages/all/README.md b/packages/all/README.md
new file mode 100644
index 0000000..5d1c89e
--- /dev/null
+++ b/packages/all/README.md
@@ -0,0 +1,96 @@
+# `@objectlab/all` β install everything from the marketplace
+
+A throwaway **workspace** that runs *every* template in this repo in **one
+runtime**, behind **one app launcher** β as if you had installed all of them
+together from the marketplace.
+
+It is not a template. It ships no objects of its own. It is an
+**environment compiler**: it gathers the compiled artifact of each template and
+composes them into a single environment bundle the runtime serves verbatim.
+
+```bash
+# from the repo root β build every template first
+pnpm -r build
+
+# then, from this package
+pnpm --filter @objectlab/all dev # compile + boot all apps on :4000
+```
+
+Open and you'll find all nine apps
+(`todo`, `compliance`, `content`, `contracts`, `expense`, `helpdesk`, `hr`,
+`procurement`, `pm`) side-by-side, each with its own namespace and seed data.
+
+Seeded admin: `admin@objectos.ai` / `admin123`.
+
+## How it works
+
+This piggy-backs on the runtime's **real** local-install mechanism. When you
+install an App from the marketplace, `MarketplaceInstallLocalPlugin` writes it to
+`.objectstack/installed-packages/.json` (a wrapper
+`{ packageId, manifestId, version, manifest, β¦ }` whose `manifest` is the App's
+full compiled artifact) and rehydrates it on boot. The compile command reads
+that same folder and composes everything into one static environment artifact.
+
+```
+packages/*/dist/objectstack.json β each template, compiled & self-contained
+ β (1) install β wrapper entry, runtime's exact format
+ βΌ
+.objectstack/installed-packages/.json β the REAL local-install folder
+ β (2) compile (scripts/compile-marketplace.mjs)
+ βΌ
+dist/objectstack.json β ONE environment artifact: apps[9]
+ β (3) serve
+ βΌ
+objectstack dev all --artifact dist/objectstack.json
+```
+
+1. **install** β populate `.objectstack/installed-packages/` from the workspace
+ templates, in the runtime's exact wrapper format, so the folder is
+ indistinguishable from a real marketplace install. Already-present entries are
+ left untouched β if you installed an App via the marketplace UI (or dropped a
+ compiled artifact into the folder), it's picked up as-is.
+2. **compile** β unwrap each installed entry to its artifact, concatenate all
+ metadata (objects, apps, views, flows, hooks, dataβ¦), de-duplicate
+ environment-level singletons (`roles` / `permissions` by name, `requires` as a
+ set), and synthesize one environment manifest with `apps[N]`.
+3. **serve** β `objectstack dev all --artifact β¦` loads the JSON directly and
+ boots a single runtime hosting every app.
+
+Scripts:
+
+| Command | Does |
+|---|---|
+| `pnpm compile` | (re)build `dist/objectstack.json` from the installed store |
+| `pnpm dev` | compile, then boot on `:4000` with a fresh ephemeral DB + seeded admin |
+| `pnpm start` | boot the already-compiled artifact (no recompile, persistent DB) |
+
+## Why this does not break ADR-0019 ("one app per package")
+
+ADR-0019 forbids an **authored package** (`type: 'app'`) from defining more than
+one app β the banned "suite contains apps" shape β and `defineStack()` enforces
+it via `validateSingleApp`.
+
+That rule governs **authoring a package**. It does **not** govern the
+**environment** a tenant runs: an environment legitimately hosts many
+independently-installed apps, each keeping its own namespace (the runtime keys
+namespaces as `Map>`). The cloud control plane does
+exactly this when it compiles a tenant environment from its installed packages.
+
+So we compose at the **environment layer** and emit the merged artifact
+directly. We never wrap the result in `defineStack()`; the CLI's `compile`/serve
+path validates with `ObjectStackDefinitionSchema` (schema only), not the
+`defineStack` wrapper β so the single-app and namespace-prefix gates correctly
+do not apply. This is the same reason `composeStacks()` exists in
+`@objectstack/spec`: composition is an environment-assembly primitive, not a
+package-authoring one.
+
+## Caveats
+
+- **Generic role/permission names collide.** `todo` and `content` both ship a
+ `lead` / `contributor` role and permission; the compiler keeps the first and
+ shadows the rest (logged on compile). Fine for a "see everything" dev
+ environment; for production isolation each app would namespace its roles.
+- **Throwaway, not a product.** This package is for local exploration, demos,
+ and cross-template QA β not something you publish to the marketplace.
+- Rebuild a template (`pnpm --filter @objectlab/ build`) then
+ `pnpm --filter @objectlab/all compile` to pick up its changes.
diff --git a/packages/all/package.json b/packages/all/package.json
new file mode 100644
index 0000000..ec5db48
--- /dev/null
+++ b/packages/all/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "@objectlab/all",
+ "version": "0.1.0",
+ "description": "Install-everything workspace β composes every ObjectStack template into one runtime, as if installed together from the marketplace.",
+ "license": "Apache-2.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "compile": "node scripts/compile-marketplace.mjs",
+ "dev": "pnpm run compile && objectstack dev all --artifact dist/objectstack.json -p 4000 --fresh",
+ "start": "objectstack dev all --artifact dist/objectstack.json -p 4000"
+ },
+ "dependencies": {
+ "@objectstack/account": "^7.4.1",
+ "@objectstack/cli": "^7.4.1",
+ "@objectstack/driver-sqlite-wasm": "^7.4.1",
+ "@objectstack/runtime": "^7.4.1",
+ "sql.js": "^1.14.1"
+ },
+ "optionalDependencies": {
+ "better-sqlite3": "^12.10.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "packageManager": "pnpm@10.33.0"
+}
diff --git a/packages/all/scripts/compile-marketplace.mjs b/packages/all/scripts/compile-marketplace.mjs
new file mode 100644
index 0000000..9e7c3d8
--- /dev/null
+++ b/packages/all/scripts/compile-marketplace.mjs
@@ -0,0 +1,224 @@
+#!/usr/bin/env node
+// Copyright (c) 2026 ObjectStack contributors. Apache-2.0 license.
+//
+// Marketplace environment compiler β compile whatever is installed locally.
+//
+// When you install Apps from the marketplace, the runtime's
+// `MarketplaceInstallLocalPlugin` persists each one to disk:
+//
+// /.objectstack/installed-packages/.json
+//
+// β¦as a wrapper entry `{ packageId, manifestId, version, manifest, β¦ }` where
+// `manifest` is the App's full compiled artifact. The runtime rehydrates these
+// at boot. This command does the offline equivalent: it reads that same folder
+// and composes every installed App into ONE environment artifact
+// (`dist/objectstack.json`) that `objectstack dev all --artifact β¦` serves as a
+// single runtime hosting every app β "install everything from the marketplace".
+//
+// Two verbs, both idempotent:
+// β’ install β populate `.objectstack/installed-packages/` from the workspace
+// templates, in the EXACT wrapper format the runtime writes, so
+// the folder is indistinguishable from a real local install.
+// (Skipped automatically if the folder is already populated β
+// e.g. you installed via the marketplace UI.)
+// β’ compile β read every installed entry and compose β dist/objectstack.json
+//
+// WHY THIS IS NOT A PROTOCOL VIOLATION (ADR-0019)
+// -----------------------------------------------
+// ADR-0019 bans an *authored package* (`type:'app'`) from defining more than one
+// app (the "suite contains apps" shape), enforced by `defineStack`'s
+// `validateSingleApp`. That rule governs *authoring a package*. It does NOT
+// govern the *environment* a tenant runs: an environment legitimately hosts many
+// independently-installed Apps, each keeping its own namespace (the runtime keys
+// namespaces as `Map>`) β which is exactly what the
+// rehydrate-at-boot path above already does. We compose at the ENVIRONMENT layer
+// and emit the merged artifact directly; we never wrap it in `defineStack()`,
+// and the CLI's serve path validates with `ObjectStackDefinitionSchema` (schema
+// only), so the single-app / namespace-prefix gates correctly do not apply.
+
+import {
+ readFileSync,
+ writeFileSync,
+ mkdirSync,
+ readdirSync,
+ existsSync,
+} from 'node:fs';
+import { dirname, join, resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const HERE = dirname(fileURLToPath(import.meta.url));
+const ALL_DIR = resolve(HERE, '..'); // packages/all (the runtime cwd for `dev all`)
+const PACKAGES_DIR = resolve(ALL_DIR, '..'); // packages
+
+// The real on-disk location the runtime's MarketplaceInstallLocalPlugin uses:
+// storageDir = resolve(process.cwd(), '.objectstack/installed-packages')
+const INSTALLED_DIR = join(ALL_DIR, '.objectstack', 'installed-packages');
+const OUT = join(ALL_DIR, 'dist', 'objectstack.json');
+
+// Mirror MarketplaceInstallLocalPlugin.safeFilename so files we write are
+// byte-identical to a real local install.
+const safeFilename = (manifestId) => `${manifestId.replace(/[^a-zA-Z0-9._-]/g, '_')}.json`;
+
+// Arrays concatenated at the environment layer (mirrors `composeStacks`'
+// CONCAT_ARRAY_FIELDS, minus the singletons we de-dupe below).
+const CONCAT_FIELDS = [
+ 'translations', 'objectExtensions', 'objects', 'apps', 'views', 'pages',
+ 'dashboards', 'reports', 'actions', 'themes', 'flows', 'jobs',
+ 'emailTemplates', 'sharingRules', 'policies', 'apis', 'webhooks', 'agents',
+ 'skills', 'hooks', 'mappings', 'analyticsCubes', 'connectors', 'datasources',
+ 'portals', 'data',
+];
+
+// Environment-level singletons keyed by `name`. Two installed apps can each ship
+// a generic "lead"/"contributor" role/permission β keep the first, shadow the
+// rest, so the runtime never double-registers.
+const DEDUP_BY_NAME = ['roles', 'permissions'];
+
+const log = (msg) => process.stdout.write(`${msg}\n`);
+
+/**
+ * `install` β populate `.objectstack/installed-packages/` from the workspace
+ * templates, in the runtime's wrapper format. No-op for templates already
+ * present (so a real marketplace install is never clobbered).
+ */
+function installFromWorkspace() {
+ mkdirSync(INSTALLED_DIR, { recursive: true });
+ const installed = [];
+ for (const name of readdirSync(PACKAGES_DIR).sort()) {
+ if (name === 'all') continue;
+ const artifactPath = join(PACKAGES_DIR, name, 'dist', 'objectstack.json');
+ if (!existsSync(artifactPath)) {
+ log(` Β· skip ${name} (no dist/objectstack.json β run \`pnpm -r build\` first)`);
+ continue;
+ }
+ const artifact = JSON.parse(readFileSync(artifactPath, 'utf8'));
+ const manifestId = artifact?.manifest?.id ?? `app.objectstack.template.${name}`;
+ const dest = join(INSTALLED_DIR, safeFilename(manifestId));
+ if (existsSync(dest)) {
+ installed.push(`${name} (already installed)`);
+ continue;
+ }
+ const entry = {
+ packageId: manifestId,
+ versionId: artifact?.manifest?.version ?? '0.0.0',
+ manifestId,
+ version: artifact?.manifest?.version ?? '0.0.0',
+ manifest: artifact, // the App's full compiled artifact
+ installedAt: '1970-01-01T00:00:00.000Z', // fixed β deterministic compile
+ installedBy: 'compile-marketplace',
+ withSampleData: false,
+ };
+ writeFileSync(dest, `${JSON.stringify(entry, null, 2)}\n`, 'utf8');
+ installed.push(name);
+ }
+ return installed;
+}
+
+/** Read every installed entry; unwrap to the App's full artifact. */
+function readInstalledArtifacts() {
+ if (!existsSync(INSTALLED_DIR)) return [];
+ const out = [];
+ for (const file of readdirSync(INSTALLED_DIR).filter((f) => f.endsWith('.json')).sort()) {
+ let entry;
+ try {
+ entry = JSON.parse(readFileSync(join(INSTALLED_DIR, file), 'utf8'));
+ } catch {
+ log(` ! ${file}: invalid JSON, skipped`);
+ continue;
+ }
+ // Accept either the runtime wrapper ({ manifest: }) or a bare
+ // artifact dropped straight into the folder.
+ const artifact = entry?.manifest?.objects || entry?.manifest?.apps ? entry.manifest : entry;
+ const source = artifact?.manifest?.namespace || entry?.manifestId || file.replace(/\.json$/, '');
+ out.push({ source, artifact });
+ }
+ return out;
+}
+
+/** `compile` β compose installed Apps into one environment artifact. */
+function compile(store) {
+ const env = {
+ manifest: {
+ id: 'app.objectstack.environment.all',
+ name: 'All Templates',
+ version: '0.1.0',
+ type: 'app',
+ description:
+ 'Local environment with every installed App composed together β the "install everything from the marketplace" workspace.',
+ },
+ };
+
+ const requires = new Set();
+ const supportedLocales = new Set();
+ let defaultLocale = 'en';
+ const dedup = Object.fromEntries(DEDUP_BY_NAME.map((k) => [k, new Map()]));
+ const objectOwner = new Map();
+
+ for (const { artifact, source } of store) {
+ for (const obj of artifact.objects ?? []) {
+ if (objectOwner.has(obj.name)) {
+ throw new Error(
+ `object '${obj.name}' is defined by both '${objectOwner.get(obj.name)}' and '${source}'. ` +
+ `Apps must prefix every object with their namespace (see TEMPLATE_GUIDE.md).`,
+ );
+ }
+ objectOwner.set(obj.name, source);
+ }
+
+ for (const field of CONCAT_FIELDS) {
+ const value = artifact[field];
+ if (Array.isArray(value) && value.length > 0) (env[field] ??= []).push(...value);
+ }
+
+ for (const field of DEDUP_BY_NAME) {
+ for (const item of artifact[field] ?? []) {
+ if (!dedup[field].has(item.name)) dedup[field].set(item.name, item);
+ else log(` ! ${field.slice(0, -1)} '${item.name}' from '${source}' shadowed (already installed)`);
+ }
+ }
+
+ for (const token of artifact.requires ?? []) requires.add(token);
+
+ if (artifact.i18n) {
+ if (artifact.i18n.defaultLocale) defaultLocale = artifact.i18n.defaultLocale;
+ for (const locale of artifact.i18n.supportedLocales ?? []) supportedLocales.add(locale);
+ }
+ }
+
+ for (const field of DEDUP_BY_NAME) {
+ const items = [...dedup[field].values()];
+ if (items.length > 0) env[field] = items;
+ }
+ if (requires.size > 0) env.requires = [...requires];
+
+ supportedLocales.add(defaultLocale);
+ env.i18n = { defaultLocale, supportedLocales: [...supportedLocales], fallbackLocale: defaultLocale };
+
+ return env;
+}
+
+// ββ main ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+log('βΆ Compiling marketplace environment (install-all)\n');
+
+const justInstalled = installFromWorkspace();
+if (justInstalled.length > 0) log(` installed-packages: ${justInstalled.join(', ')}`);
+
+const store = readInstalledArtifacts();
+if (store.length === 0) {
+ log('\nβ No installed packages found in .objectstack/installed-packages/.');
+ log(' Build the templates first: pnpm -r build');
+ log(' Or install Apps via the marketplace, then re-run this command.');
+ process.exit(1);
+}
+
+const env = compile(store);
+mkdirSync(dirname(OUT), { recursive: true });
+writeFileSync(OUT, `${JSON.stringify(env, null, 2)}\n`);
+
+log('');
+log(`β Composed ${store.length} apps Β· ${env.objects?.length ?? 0} objects Β· ${env.flows?.length ?? 0} flows`);
+log(` apps: ${(env.apps ?? []).map((a) => a.name).join(', ')}`);
+log(` installed-packages: ${INSTALLED_DIR.replace(`${PACKAGES_DIR}/`, '')}`);
+log(` β ${OUT.replace(`${PACKAGES_DIR}/`, '')}`);
+log('');
+log('Run it: objectstack dev all --artifact dist/objectstack.json -p 4000 --fresh');
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8be3121..d69c176 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -15,6 +15,28 @@ importers:
specifier: ^6.0.3
version: 6.0.3
+ packages/all:
+ dependencies:
+ '@objectstack/account':
+ specifier: ^7.4.1
+ version: 7.4.1
+ '@objectstack/cli':
+ specifier: ^7.4.1
+ version: 7.4.1(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@objectstack/core@7.4.1(ai@6.0.191(zod@4.4.3)))(@opentelemetry/api@1.9.1)(ai@6.0.191(zod@4.4.3))(better-call@1.3.5(zod@4.4.3))(better-sqlite3@12.10.0)(jose@6.2.3)(kysely@0.28.17)(mongodb@7.2.0)(nanostores@1.3.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ '@objectstack/driver-sqlite-wasm':
+ specifier: ^7.4.1
+ version: 7.4.1(ai@6.0.191(zod@4.4.3))(better-sqlite3@12.10.0)
+ '@objectstack/runtime':
+ specifier: ^7.4.1
+ version: 7.4.1(@better-auth/utils@0.4.0)(@better-fetch/fetch@1.1.21)(@opentelemetry/api@1.9.1)(ai@6.0.191(zod@4.4.3))(better-call@1.3.5(zod@4.4.3))(better-sqlite3@12.10.0)(jose@6.2.3)(kysely@0.28.17)(mongodb@7.2.0)(nanostores@1.3.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
+ sql.js:
+ specifier: ^1.14.1
+ version: 1.14.1
+ optionalDependencies:
+ better-sqlite3:
+ specifier: ^12.10.0
+ version: 12.10.0
+
packages/compliance:
dependencies:
'@objectstack/account':