A small application to fork: one owner, local SQLite, a Notes model, an assistant that can read and write your notes, WebMCP tools for browser agents, and a read-only MCP endpoint. Built on s-m-r-t.
The application itself is src/lib/models/Note.ts, the Notes page, and the
choices in the assistant and MCP routes. Everything else is framework wiring;
the files that are still here only because s-m-r-t does not provide them yet
are listed at the end, with the issue that would remove each one.
- Node.js 26 or newer
- pnpm 11.25 (
packageManagerpins it) - An Anthropic API key for the assistant (or an OpenAI key; see below)
pnpm install
cp .env.example .env # then set ANTHROPIC_API_KEY in .env
pnpm app:setup # build, create the local database, issue a one-time owner invitation
pnpm dev # http://127.0.0.1:5173
pnpm app:open # opens the one-time /setup?token=… page in your browserOn the setup page, enter your name and email. That creates you as the owner,
signs you in, and takes you to your notes. The invitation works once, only
from this machine, and expires; if it has expired, run pnpm app:stop,
pnpm app:recover, then pnpm app:open again.
Your data lives in your operating system's application-data directory, never
in this checkout. pnpm app:doctor prints where, plus any problems it finds.
- Notes. Add notes on the home page.
- Assistant. Open the Assistant tool on the right edge of the shell, start
a conversation, and ask for something like "add a note called Groceries
listing eggs and flour, then tell me how many notes I have". It can only use
the two tools named in
src/routes/api/assistant/[...path]/+server.ts:notes.readandnotes.create. - Browser agents (WebMCP). While you are signed in, the page registers
read-only
note_listandnote_gettools with browsers that support WebMCP. - MCP clients.
POST /mcpserves the read-only Note tools to a caller that holdsnotes.read: your signed-in browser session, or a local MCP client with a token (next section).
Mint a token after pnpm app:setup (it works while the app is running). The
token is printed once, in the token field; only its hash is stored:
pnpm exec smrt app token create --scopes notes.read --label claude-desktop
pnpm exec smrt app token list # ids, scopes, expiry; never the token
pnpm exec smrt app token revoke <id>Tokens expire after 30 days (--expires changes that). Then point the client
at the smrt-mcp-bridge stdio bridge in this checkout, with pnpm dev (or
pnpm app:start) running. For Claude Desktop:
{
"mcpServers": {
"notes": {
"command": "/absolute/path/to/smrt-start/node_modules/.bin/smrt-mcp-bridge",
"args": ["--env-prefix=NOTES", "--mcp-path=/mcp"],
"env": {
"NOTES_SERVER_URL": "http://127.0.0.1:5173",
"NOTES_TOKEN": "smrt_mcp_…"
}
}
}
}The bridge sends the token only to NOTES_SERVER_URL. On /mcp a valid token
acts as you, capped to its scopes and your current permissions; an unknown,
revoked, or expired token gets 401 and never falls back to a browser
session.
Through the bridge, a notes.read token lists note_get and note_list and
can call them, in pnpm dev and in the production build. Without a token,
tools/list returns an empty list. A refused token makes every request after
initialize fail with the bridge error MCP upstream request failed.
Change the ai block in smrt.config.ts and put the key in .env:
ai: { provider: 'openai', model: 'gpt-4o', apiKeyEnv: 'OPENAI_API_KEY' },OPENAI_API_KEY=sk-…Restart pnpm dev after changing smrt.config.ts.
smrt cookbook apply <cookbook.json> <dir> creates a new app from this
template, its default (--template <path|github:owner/repo[#ref]> picks
another). Apply adds the packages the cookbook's recipes need to
dependencies and writes the cookbook, verbatim, to smrt.cookbook.json at
the project root. Nothing else changes, and this app does the rest:
- Shell. With a
smrt.cookbook.json, the app renders throughCookbookApp(src/routes/+layout.svelte): the navigation sections, the section pages (/s/<section>), the layout and the theme come from the cookbook. The root layout loads it withloadCookbookApp(src/routes/+layout.server.ts). A model's entry in the navigation opens/m/<package>/<Model>(a keyed entry, such as one status of a model, adds a third segment), which rendersRecipeScreensfor the model's generated web definition. Without a cookbook, nothing changes: the Notes app above. - Packages.
smrt.config.tsbuildsconsumer.packagesfrompackage.json: every@happyvertical/smrt-*dependency that publishes a manifest. Applying a cookbook adds dependencies, so its models and migrations are built and applied bypnpm app:setup/pnpm db:migratewithout editing the config. Tooling and UI packages publish no manifest and are skipped. - Notes is optional.
NOTESinsrc/lib/features.tsis'auto'(the default: kept with no cookbook, dropped with one),'keep'or'drop'. Dropped,Noteis not scanned or migrated, the Notes tests are skipped, the assistant has no tools,/mcpserves none, and/opens the cookbook's first section. Kept next to a cookbook, Notes stays on/(the shell's title links there). To remove the example for good, deleteNote.ts, the Notes page and its tests. - Overviews and layout are preferences. A section page is an editable
overview surface (the shell's pencil toggle edits it). Edits are stored per
person through smrt-preferences (
_smrt_ui_preferences, theoverviewandshell-layoutkinds), never written to the file, laid over the cookbook when the page loads, and validated again on save (src/lib/server/cookbook.ts,POST /api/cookbook/preferences).?scope=tenantstores the organization default instead and needsoverviews.customize/shell.customize; the shell has no control for it yet. - Recipe components. Recipes can name components and loaders to place in
the shell. A bundled app imports them from a fixed map,
src/lib/cookbook/exports.ts; add a line there when an applied package's recipes ship Svelte exports from a module it does not list. - Demo data. Nothing is seeded.
demoSeedin a recipe describes fixtures for browser demos, not records, and has no server-side contract yet.
Try it without publishing anything: smrt cookbook validate and --dry-run
print what apply would do, then pnpm install, pnpm app:setup,
pnpm app:doctor and pnpm test run as usual.
Edit or add classes in src/lib/models/, then run pnpm db:migrate. The
smrt() plugin in vite.config.ts regenerates the REST routes, types,
WebMCP definitions, and registration on every pnpm dev / pnpm build;
db:migrate applies the matching schema. Never edit the generated files (they
are gitignored).
| Script | Does |
|---|---|
pnpm dev |
Vite dev server (smrt app dev) |
pnpm build |
Production build (smrt app build) |
pnpm app:setup |
Build, migrate, and issue the owner invitation |
pnpm app:start / app:stop |
Run / stop the production build on loopback |
pnpm app:open |
Open the app (or the pending owner invitation) |
pnpm app:recover |
Replace an expired, unclaimed owner invitation |
pnpm app:doctor |
Secret-free diagnostics and recovery steps |
pnpm db:migrate |
Rebuild, then apply schema changes |
pnpm typecheck |
tsc and svelte-check |
pnpm test |
Vitest |
pnpm lint |
Biome |
These files (or parts of files) are not about notes. Each stays until the s-m-r-t change named next to it ships, or is marked as staying.
| File | Why it is here | Removed by |
|---|---|---|
src/lib/server/smrt.ts |
One call, createSmrtSvelteKitRuntime(); hooks, pages, mounted routes, and generated /api/* routes import its runtime |
Stays: the module the framework looks for, and where runtime options go |
src/hooks.server.ts |
Re-exports the runtime's handle / init |
Not filed: a two-line file; revisit with happyvertical/smrt#3378 |
src/app.d.ts |
Types App.Locals as the runtime's locals |
Not filed: a two-line file; revisit with happyvertical/smrt#3378 |
src/routes/+layout.server.ts |
Mounts createSessionLayoutLoad() |
happyvertical/smrt#3378 (smrt add, mount by default) |
src/routes/setup/+page.svelte, +page.server.ts |
Mounts the owner-setup form and action | Same as above |
src/routes/settings/+page.svelte |
Mounts ShellSettingsPage |
Same as above |
src/routes/api/_runtime/health/+server.ts, diagnostics/+server.ts |
Mount the health and diagnostics handlers | Same as above |
src/routes/.well-known/oauth-protected-resource/mcp/+server.ts |
RFC 9728 metadata for hosted bearer challenges; 404 in the local profile | Not filed: only a hosted profile uses it; revisit with happyvertical/smrt#3378 |
src/lib/features.ts, smrt.config.ts (modelPackages) |
The Notes switch, the cookbook file check and the model-package list read from package.json |
The package list: not filed (smrt-start#4); consumer.packages from the dependencies a cookbook adds, or written by apply |
src/lib/server/cookbook.ts, src/routes/api/cookbook/preferences/+server.ts |
Lays the stored overview and shell-layout preferences over smrt.cookbook.json and stores edits |
Not filed (smrt-start#4): a smrt-preferences adapter for CookbookApp, so the shell persists its own edits |
src/lib/cookbook/exports.ts |
The static map that resolves recipe exports to components | Not a gap: a bundler needs a static list; only the packages the app depends on can be listed |
src/lib/rest-source.ts, src/routes/m/[pkg]/[model]/[[key]]/, src/routes/s/[slug]/ |
The entry and section pages of the cookbook shell | Not a gap: the smrt() preset hosts the cookbook's package models over /api/<collection> and @smrt/web carries their definitions; these pages are the app's routes over them |
package.json name (@smrt-app/start) |
Must be a scoped name; an unscoped name breaks generated registration at runtime | Stays: happyvertical/smrt#3408 makes the scoped-name rule explicit and fails early instead |
vite.config.ts (objectsDir) |
The preset scans src/lib/objects by default; this app keeps models in src/lib/models |
Not a gap: an app choice |
pnpm-workspace.yaml (minimumReleaseAgeExclude) |
Lets pnpm install s-m-r-t releases younger than its supply-chain age window | Not a smrt gap: entries can go once the release is older than the window |
svelte.config.js, tsconfig.json, src/app.html, vitest.config.ts, biome.json (also leaves smrt.cookbook.json unformatted: apply writes it) |
SvelteKit, TypeScript, Vitest, and Biome configuration | Not a smrt gap |
Tracked under happyvertical/smrt#3367. s-m-r-t 0.53.5 fixed everything the
earlier versions of this list named: smrt app dev binds 127.0.0.1 like the
invitation URL, and smrt app needs no runtime or packages.cli.database
config (#3410); signing in removes the invitation hand-off files (#3411); the
WebMCP note_list tool accepts the generated { items } list (#3412); local
MCP tokens and the bridge's --mcp-path (#3413); the assistant mount's
defaults (#3414); AppShell's dock registry (#3415); and the runtime-only
smrt.ts (#3416). s-m-r-t 0.55.1 fixed /mcp failing every tools/list and
tools/call with Duplicate MCP tool name: agentconfig_list (#3490), and
let the MCP routes take the runtime directly, which removed this template's
last hand-written MCP wiring (#3491). Later releases shipped the cookbook
intake this list last waited on: the cookbook shell (smrt-svelte ./cookbook)
and smrt cookbook apply, which copies this template by default
(smrt-start#4).
Still open on 0.57.2:
- ChatGPT needs a hosted profile with public OAuth; this template is local-only, and smrt-app-mcp validates bearer tokens but is not an OAuth authorization server.
/api/_resourcesis not generated. Thesmrt()preset checks for@happyvertical/smrt-users/sveltekitwith CommonJS resolution, which that package's import-only exports refuse, so the build logsSkipping _resources routealthough smrt-users is a dependency (happyvertical/smrt#3913).
MIT © Happy Vertical Corporation