44 — locara init Template Structure
What locara init <template> <name> actually creates on disk. Templates are the developer’s first contact with the framework; they define the mental model.
Available templates (v1)
| Template | Purpose | Modalities |
|---|---|---|
blank | Minimal scaffold; just chat | text-to-text |
chat | Polished chat app with streaming + tool calls | text-to-text, optional tools |
transcribe | Audio + STT scaffold (the Transcribe reference) | speech-to-text + text-to-text + embedding |
docvault | Document + OCR scaffold (the DocVault reference) | image-to-text + text-to-text + embedding |
voice | Voice-to-voice assistant scaffold | voice-to-voice |
The transcribe and docvault templates produce starter code with the same architecture as the reference apps (described in apps/transcribe/SPEC.md and apps/docvault/SPEC.md); the templates are simpler, with reduced features for learning.
Common files (every template)
my-app/
├── locara.json # Manifest
├── package.json # Bun + Tauri deps
├── bun.lockb # Bun lockfile
├── tsconfig.json
├── vite.config.ts
├── src-tauri/
│ ├── Cargo.toml # depends on locara-runtime, locara-core, etc.
│ ├── tauri.conf.json
│ ├── build.rs
│ └── src/
│ ├── main.rs # Tauri bootstrap; wires up Locara plugins
│ └── lib.rs
├── src/
│ ├── main.tsx # React entry
│ ├── App.tsx # Root component
│ ├── lib/ # App-specific logic
│ ├── routes/ # If using a router
│ └── components/ui/ # Empty initially; populated via `locara add`
├── db/
│ ├── schema.sql # Empty initially; user adds tables
│ └── migrations/
│ └── 0001_initial.sql # Auto-generated from schema.sql
├── tests/
│ ├── capabilities.test.ts # Required capability tests
│ └── unit.test.ts
├── public/
│ └── icon.png # Default Locara-branded icon; user replaces
├── screenshots/ # Empty; user adds before publish
├── README.md
├── .gitignore
└── .env.example
Common file contents
locara.json (blank template)
{
"schema": "locara/v1",
"name": "my-app",
"publisher": "your-publisher-id",
"version": "0.0.1",
"displayName": "My App",
"description": "An app I'm building with Locara.",
"license": "Apache-2.0",
"icon": "./public/icon.png",
"screenshots": [],
"category": "productivity",
"modalities": ["text-to-text"],
"tooling": [],
"capabilities": {
"net": false,
"fs": {
"user-selected": false,
"app-data": "read-write"
},
"device": {
"microphone": false,
"camera": false
},
"models": [
"qwen2.5-3b-instruct-q4@sha256:..."
]
},
"profiles": {
"mid": { "min_ram_gb": 16 }
},
"storage": {
"schema": "./db/schema.sql"
},
"entry": {
"frontend": "./src/main.tsx"
}
}
package.json
{
"name": "my-app",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
"dev": "locara dev",
"build": "locara build",
"test": "locara test",
"publish": "locara publish"
},
"dependencies": {
"@locara/sdk": "^0.1.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@locara/manifest": "^0.1.0",
"@locara/test": "^0.1.0",
"@tauri-apps/api": "^2.0.0",
"@tauri-apps/cli": "^2.0.0",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"tailwindcss": "^3.4.0",
"typescript": "^5.0.0",
"vite": "^5.0.0"
}
}
src-tauri/Cargo.toml
[package]
name = "my-app"
version = "0.0.1"
edition = "2024"
[dependencies]
locara-runtime = { version = "0.1" }
locara-core = { version = "0.1" }
locara-storage = { version = "0.1" }
tauri = { version = "2.0", features = [] }
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
[build-dependencies]
tauri-build = "2.0"
src-tauri/src/main.rs (boilerplate)
// SPDX-License-Identifier: Apache-2.0
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
tauri::Builder::default()
.plugin(locara_runtime::plugin::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
The locara_runtime::plugin::init() call registers all the Locara Tauri commands automatically. The app doesn’t have to wire each command manually.
src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { App } from './App'
import './styles.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
src/App.tsx (blank template)
import { useState } from 'react'
import { llm } from '@locara/sdk'
export function App() {
const [input, setInput] = useState('')
const [response, setResponse] = useState('')
async function handleSubmit() {
const stream = llm.chatStream({
model: 'qwen2.5-3b-instruct-q4',
messages: [{ role: 'user', content: input }],
})
setResponse('')
for await (const delta of stream) {
setResponse(prev => prev + (delta.content ?? ''))
}
}
return (
<main className="min-h-screen flex flex-col p-8 bg-zinc-900 text-white">
<h1 className="text-3xl font-semibold mb-6">My App</h1>
<textarea
value={input}
onChange={(e) => setInput(e.target.value)}
className="w-full p-4 rounded bg-zinc-800 mb-4"
rows={4}
placeholder="Ask anything..."
/>
<button
onClick={handleSubmit}
className="self-start px-6 py-2 rounded bg-blue-600 hover:bg-blue-700 transition"
>
Send
</button>
{response && (
<div className="mt-6 p-4 rounded bg-zinc-800 whitespace-pre-wrap">{response}</div>
)}
</main>
)
}
db/schema.sql (blank template)
-- Your app's database schema goes here.
-- Run `locara db migrate` after changing this file.
-- Example:
-- CREATE TABLE conversations (
-- id TEXT PRIMARY KEY,
-- title TEXT NOT NULL,
-- created_at INTEGER NOT NULL DEFAULT (unixepoch())
-- );
tests/capabilities.test.ts (every template)
import { test, expect, runApp } from '@locara/test'
test('does not attempt network', async () => {
const app = await runApp()
await app.simulate('user-interaction')
expect(app.networkAttempts).toEqual([])
})
test('only loads declared models', async () => {
const app = await runApp()
await app.simulate('user-interaction')
for (const model of app.modelsLoaded) {
expect(app.manifest.capabilities.models).toContainEqual(
expect.stringContaining(model)
)
}
})
// Add app-specific capability tests here as you build features.
README.md
# My App
A Locara app.
## Development
```bash
bun install
bun run dev
Build
bun run build
Output: dist/my-app-0.0.1.dmg (signed locally with your dev cert).
Publish
bun run publish
Submits to Locara registry CI for build + sign + notarize.
Manifest
See locara.json for capability declarations. Don’t edit manifest fields outside the defaults without understanding what they do — capability changes can require user re-consent on update.
See Locara docs for the full SDK reference.
### `.gitignore`
node_modules/ dist/ target/ src-tauri/target/ .env .locara/ *.dylib *.dSYM .DS_Store
### `.env.example`
For locara init --from-prompt if you use it later
ANTHROPIC_API_KEY= OPENAI_API_KEY=
For publishing (set after locara login)
LOCARA_PUBLISHER_TOKEN=
## Per-template variations
### `chat` template
Adds:
- `<Chat>`, `<MessageBubble>`, `<ChatInput>` copied via `locara add`.
- A `messages` table in `db/schema.sql`.
- Streaming chat UI in `App.tsx` with conversation history.
- Optional tool-calling demo (commented; user uncomments to enable).
### `transcribe` template
Adds:
- Manifest declares `speech-to-text`, microphone, transcription model.
- `<AudioInput>`, `<TranscriptStream>` components.
- Schema with `recordings` + `segments` tables (subset of full Transcribe).
- Live recording UX in `App.tsx`.
### `docvault` template
Adds:
- Manifest declares `image-to-text`, OCR + filesystem tools.
- `<DocDropzone>`, `<DocPreview>` components.
- Schema with `documents` + `chunks` tables (subset of full DocVault).
- Drop-zone + ingestion in `App.tsx`.
### `voice` template
Adds:
- Manifest declares `voice-to-voice`, microphone, models for STT + LLM + TTS.
- `<AudioInput>`, `<TranscriptStream>` components.
- Voice-loop logic in `App.tsx` (record → transcribe → LLM → TTS → play).
- Note about computational cost for full pipeline.
## `locara init --from-prompt "..."`
When the user provides a natural-language prompt, the CLI:
1. Calls the user's chosen LLM (Anthropic / OpenAI / etc.) with the prompt + a system prompt explaining Locara templates + manifest schema.
2. Receives a JSON response indicating: which template to start from, what manifest changes to make, what initial code to write.
3. Applies the JSON as a "starter customization" on top of the chosen template.
4. Writes the resulting files.
Example prompt: `"a fully-local journal app that lets me search across all my entries"` →
- Starts from `blank` template.
- Adds `nomic-embed-text-v1.5` to models, declares `text-to-embedding` modality.
- Generates an `entries` table with FTS5 + sqlite-vec.
- Writes a starting App.tsx with entry-list + new-entry form + search.
The user reviews the generated files, runs `locara dev`, iterates from there.
This is the agent-friendly authoring path. The templates ground what the LLM produces in valid Locara structure.
## Updating templates
Templates are versioned alongside the framework. When `@locara/sdk@1.5` ships, the templates are updated to use any new APIs.
Old projects don't auto-update; users opt in by running `locara init` again into a fresh directory and copying over their app code (rare).
## Open questions
- **(open)** Should `locara init` install dependencies (`bun install`) automatically or just print the next-step? Auto is friendlier; explicit is more transparent. Lean auto.
- **(open)** Tailwind preconfigured or opt-in? Components rely on Tailwind; lean preconfigured.
- **(open)** Default model in templates — Qwen2.5-3B (covers low-tier; smaller download)? Or 7B for better quality? Lean 3B as default; user can change.
- **(open)** Should templates include `git init`? Probably yes; first commit on creation.
## Cross-references
- [06-cli.md](./06-cli.md) — `locara init` command spec
- [02-manifest.md](./02-manifest.md) — `locara.json` schema
- [05-sdk.md](./05-sdk.md) — SDK API used in templates
- [11-components.md](./11-components.md) — components added via `locara add`
- [Transcribe SPEC](../apps/transcribe/SPEC.md) — what the transcribe template grows into
- [DocVault SPEC](../apps/docvault/SPEC.md) — what the docvault template grows into