create-theokit 1.0.7 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/cli.js +5 -0
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +1 -1
  4. package/templates/default/AGENTS.md +41 -51
  5. package/templates/default/CLAUDE.md +25 -0
  6. package/templates/default/README.md.tmpl +53 -51
  7. package/templates/default/_gitignore +31 -0
  8. package/templates/default/app/page.tsx +78 -39
  9. package/templates/default/app.ts +24 -0
  10. package/templates/default/dot-claude/rules/theokit-conventions.md +36 -0
  11. package/templates/default/dot-claude/settings.json +23 -0
  12. package/templates/default/dot-claude/skills/theokit-agents/SKILL.md +105 -0
  13. package/templates/default/dot-claude/skills/theokit-config/SKILL.md +128 -0
  14. package/templates/default/dot-claude/skills/theokit-database/SKILL.md +112 -0
  15. package/templates/default/dot-claude/skills/theokit-frontend/SKILL.md +89 -0
  16. package/templates/default/dot-claude/skills/theokit-routes/SKILL.md +89 -0
  17. package/templates/default/drizzle.config.ts +10 -0
  18. package/templates/default/eslint.config.mjs +5 -1
  19. package/templates/default/package.json.tmpl +13 -3
  20. package/templates/default/public/index.html +70 -0
  21. package/templates/default/server/agents/assistant.agent.ts +6 -2
  22. package/templates/default/server/controllers/tasks.controller.ts +5 -13
  23. package/templates/default/server/db/index.ts +21 -0
  24. package/templates/default/server/db/schema.ts +13 -0
  25. package/templates/default/server/db/seed.ts +29 -0
  26. package/templates/default/server/index.ts +2 -18
  27. package/templates/default/server/routes/tasks/[id].ts +36 -0
  28. package/templates/default/server/routes/tasks/index.ts +20 -0
  29. package/templates/default/server/toolboxes/task.tools.ts +2 -1
  30. package/templates/default/tests/tasks.test.ts +10 -0
  31. package/templates/default/theo.config.ts +0 -6
@@ -2,36 +2,42 @@
2
2
 
3
3
  import { useState, useEffect, useCallback, type FormEvent } from 'react'
4
4
 
5
- interface Task { id: number; title: string; priority: 'high' | 'medium' | 'low'; done: boolean }
6
- type Role = '' | 'user' | 'admin'
5
+ interface Task {
6
+ id: number
7
+ title: string
8
+ priority: 'high' | 'medium' | 'low'
9
+ done: boolean
10
+ }
7
11
 
8
12
  export default function Page() {
9
13
  const [tasks, setTasks] = useState<Task[]>([])
10
- const [role, setRole] = useState<Role>('user')
11
14
  const [title, setTitle] = useState('')
12
15
  const [priority, setPriority] = useState<Task['priority']>('medium')
13
16
  const [formError, setFormError] = useState('')
14
17
 
15
- const hdrs = useCallback((): Record<string, string> => {
16
- const h: Record<string, string> = { 'Content-Type': 'application/json' }
17
- if (role) h['x-role'] = role
18
- return h
19
- }, [role])
20
-
21
18
  const loadTasks = useCallback(async () => {
22
19
  const res = await fetch('/api/tasks')
23
20
  if (res.ok) setTasks(await res.json())
24
21
  }, [])
25
22
 
26
- useEffect(() => { loadTasks() }, [loadTasks])
23
+ useEffect(() => {
24
+ loadTasks()
25
+ }, [loadTasks])
27
26
 
28
27
  const createTask = async (e: FormEvent) => {
29
28
  e.preventDefault()
30
29
  setFormError('')
31
30
  if (!title.trim()) return
32
- const res = await fetch('/api/tasks', { method: 'POST', headers: hdrs(), body: JSON.stringify({ title, priority }) })
33
- if (res.status === 403) { setFormError('403 — Need User role'); return }
34
- if (!res.ok) { const b = await res.json(); setFormError(b.error?.issues?.[0]?.message ?? `Error ${res.status}`); return }
31
+ const res = await fetch('/api/tasks', {
32
+ method: 'POST',
33
+ headers: { 'Content-Type': 'application/json' },
34
+ body: JSON.stringify({ title, priority }),
35
+ })
36
+ if (!res.ok) {
37
+ const b = await res.json()
38
+ setFormError(b.error?.issues?.[0]?.message ?? `Error ${res.status}`)
39
+ return
40
+ }
35
41
  setTitle('')
36
42
  loadTasks()
37
43
  }
@@ -45,10 +51,20 @@ export default function Page() {
45
51
  <h1>TheoKit</h1>
46
52
  <p className="tagline">Build the app your agent lives in.</p>
47
53
  <nav className="ctas">
48
- <a href="https://usetheo.dev" target="_blank" rel="noopener noreferrer" className="btn primary">
54
+ <a
55
+ href="https://usetheo.dev"
56
+ target="_blank"
57
+ rel="noopener noreferrer"
58
+ className="btn primary"
59
+ >
49
60
  Get Started
50
61
  </a>
51
- <a href="https://github.com/usetheodev/theokit" target="_blank" rel="noopener noreferrer" className="btn secondary">
62
+ <a
63
+ href="https://github.com/usetheodev/theokit"
64
+ target="_blank"
65
+ rel="noopener noreferrer"
66
+ className="btn secondary"
67
+ >
52
68
  Documentation
53
69
  </a>
54
70
  </nav>
@@ -57,34 +73,46 @@ export default function Page() {
57
73
  </p>
58
74
  </header>
59
75
 
60
- {/* Role */}
61
- <div className="role-bar">
62
- <label htmlFor="role">Role:</label>
63
- <select id="role" value={role} onChange={e => setRole(e.target.value as Role)}>
64
- <option value="">None (public)</option>
65
- <option value="user">User</option>
66
- <option value="admin">Admin</option>
67
- </select>
68
- </div>
69
-
70
76
  {/* Tasks */}
71
77
  <section className="card">
72
- <h2>Tasks <span className="badge">@Controller</span></h2>
78
+ <h2>
79
+ Tasks <span className="badge">defineRoute + Drizzle</span>
80
+ </h2>
73
81
  <table>
74
- <thead><tr><th>Task</th><th>Priority</th><th>Status</th></tr></thead>
82
+ <thead>
83
+ <tr>
84
+ <th>Task</th>
85
+ <th>Priority</th>
86
+ <th>Status</th>
87
+ </tr>
88
+ </thead>
75
89
  <tbody>
76
- {tasks.map(t => (
90
+ {tasks.map((t) => (
77
91
  <tr key={t.id} className={t.done ? 'done' : ''}>
78
- <td>{t.done ? '✅ ' : '○ '}{t.title}</td>
79
- <td><span className={`prio prio-${t.priority}`}>{t.priority}</span></td>
92
+ <td>
93
+ {t.done ? '✅ ' : '○ '}
94
+ {t.title}
95
+ </td>
96
+ <td>
97
+ <span className={`prio prio-${t.priority}`}>{t.priority}</span>
98
+ </td>
80
99
  <td>{t.done ? 'Done' : 'To do'}</td>
81
100
  </tr>
82
101
  ))}
83
102
  </tbody>
84
103
  </table>
85
104
  <form onSubmit={createTask} className="create-bar">
86
- <input value={title} onChange={e => setTitle(e.target.value)} placeholder="New task..." required minLength={3} />
87
- <select value={priority} onChange={e => setPriority(e.target.value as Task['priority'])}>
105
+ <input
106
+ value={title}
107
+ onChange={(e) => setTitle(e.target.value)}
108
+ placeholder="New task..."
109
+ required
110
+ minLength={3}
111
+ />
112
+ <select
113
+ value={priority}
114
+ onChange={(e) => setPriority(e.target.value as Task['priority'])}
115
+ >
88
116
  <option value="medium">Medium</option>
89
117
  <option value="high">High</option>
90
118
  <option value="low">Low</option>
@@ -97,12 +125,16 @@ export default function Page() {
97
125
  {/* Features */}
98
126
  <div className="grid features">
99
127
  <div className="feature">
100
- <h3>@Controller</h3>
101
- <p>NestJS-style decorators with convention naming. <code>TasksController</code> → <code>/api/tasks</code></p>
128
+ <h3>defineRoute</h3>
129
+ <p>
130
+ Typed API routes with Zod validation. See <code>server/routes/tasks/</code>
131
+ </p>
102
132
  </div>
103
133
  <div className="feature">
104
- <h3>defineRoute</h3>
105
- <p>Typed API routes with Zod validation. See <code>server/routes/health.ts</code></p>
134
+ <h3>Drizzle + SQLite</h3>
135
+ <p>
136
+ Type-safe database with zero config. Schema in <code>server/db/schema.ts</code>
137
+ </p>
106
138
  </div>
107
139
  <div className="feature">
108
140
  <h3>@Agent + @Tool</h3>
@@ -116,11 +148,18 @@ export default function Page() {
116
148
 
117
149
  {/* Footer */}
118
150
  <footer className="footer">
119
- Powered by <a href="https://usetheo.dev" target="_blank" rel="noopener noreferrer">TheoKit</a>
151
+ Powered by{' '}
152
+ <a href="https://usetheo.dev" target="_blank" rel="noopener noreferrer">
153
+ TheoKit
154
+ </a>
120
155
  {' · '}
121
- <a href="https://github.com/usetheodev/theokit" target="_blank" rel="noopener noreferrer">GitHub</a>
156
+ <a href="https://github.com/usetheodev/theokit" target="_blank" rel="noopener noreferrer">
157
+ GitHub
158
+ </a>
122
159
  {' · '}
123
- <a href="https://discord.usetheo.dev" target="_blank" rel="noopener noreferrer">Discord</a>
160
+ <a href="https://discord.usetheo.dev" target="_blank" rel="noopener noreferrer">
161
+ Discord
162
+ </a>
124
163
  </footer>
125
164
  </div>
126
165
  </div>
@@ -0,0 +1,24 @@
1
+ /**
2
+ * TheoKit App — convention over configuration.
3
+ *
4
+ * "The framework that reduces noise for humans + AI."
5
+ *
6
+ * Backend classes registered in server/index.ts (one barrel, like Rails).
7
+ * Routes inferred from class names. Zero manual wiring.
8
+ */
9
+ import 'reflect-metadata'
10
+ import { readFileSync } from 'node:fs'
11
+ import { TheoApp } from '@theokit/http/app'
12
+ import { TasksController, AssistantAgent, TaskTools } from './server/index.js'
13
+
14
+ let html: string | undefined
15
+ try { html = readFileSync(new URL('./public/index.html', import.meta.url), 'utf-8') } catch { /* no frontend */ }
16
+
17
+ const app = await TheoApp.create({
18
+ controllers: [TasksController],
19
+ agents: [AssistantAgent],
20
+ providers: [TaskTools],
21
+ html,
22
+ })
23
+
24
+ await app.listen(3000)
@@ -0,0 +1,36 @@
1
+ # TheoKit Conventions
2
+
3
+ ## Imports
4
+
5
+ - Use `theokit/server/define` for defineRoute, defineAction, defineWebSocket
6
+ - Use `theokit/client` for theoFetch, createAppClient
7
+ - Use `theokit/server/auth` for session/auth APIs
8
+ - NEVER import from `theokit/dist/...` or `theokit/src/...`
9
+ - NEVER import internal modules: `theokit/core`, `theokit/vite-plugin`, `theokit/adapters/*`
10
+
11
+ ## Validation
12
+
13
+ - Zod is the single source of truth for types and validation
14
+ - Define schema ONCE with `z.object(...)`, derive types with `z.infer<>`
15
+ - NEVER duplicate a Zod schema as a manual TypeScript interface
16
+ - NEVER parse request body manually — use `body:` in defineRoute
17
+
18
+ ## Routes
19
+
20
+ - File at `server/routes/tasks/[id].ts` maps to `/api/tasks/:id`
21
+ - Export HTTP method handlers: `export const GET = defineRoute({...})`
22
+ - Use `params: z.object({...})` for URL params, `body:` for request body
23
+ - Use `status: 201` for creation responses, not manual `res.status()`
24
+
25
+ ## Types
26
+
27
+ - No `any` in production code
28
+ - No `@ts-ignore` or `@ts-expect-error`
29
+ - No `as` type assertions — use Zod schemas or type guards
30
+
31
+ ## Database
32
+
33
+ - Schema lives in `server/db/schema.ts` (Drizzle ORM)
34
+ - Connection in `server/db/index.ts`
35
+ - Seeds in `server/db/seed.ts`
36
+ - Use `npx drizzle-kit push` for dev, `npx drizzle-kit generate` for prod migrations
@@ -0,0 +1,23 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npm run *)",
5
+ "Bash(npx theokit *)",
6
+ "Bash(npx vitest *)",
7
+ "Bash(npx tsc *)",
8
+ "Bash(npx eslint *)",
9
+ "Bash(npx drizzle-kit *)",
10
+ "Bash(git status)",
11
+ "Bash(git diff *)",
12
+ "Bash(git log *)"
13
+ ],
14
+ "deny": [
15
+ "Read(.env*)",
16
+ "Read(**/.env*)",
17
+ "Bash(sudo *)",
18
+ "Bash(rm -rf *)",
19
+ "Bash(git push --force *)",
20
+ "Bash(git reset --hard *)"
21
+ ]
22
+ }
23
+ }
@@ -0,0 +1,105 @@
1
+ ---
2
+ name: theokit-agents
3
+ description: TheoKit agent/LLM integration — @Agent, @Tool, @Toolbox decorators, streaming, memory
4
+ user-invocable: false
5
+ paths:
6
+ - "**/*agent*"
7
+ - "**/*Agent*"
8
+ - "**/*tool*"
9
+ - "**/*Tool*"
10
+ - "**/*toolbox*"
11
+ - "**/*Toolbox*"
12
+ ---
13
+
14
+ # TheoKit Agents & Tools
15
+
16
+ ## @Agent Decorator
17
+
18
+ ```typescript
19
+ import { Agent, MainLoop, Hook, Memory, Budget } from '@theokit/agents'
20
+
21
+ @Agent({
22
+ model: 'openai/gpt-4o-mini', // Required: LLM model
23
+ systemPrompt: 'You are a helpful task assistant.',
24
+ })
25
+ @Memory({ provider: 'built-in', scope: 'per-user' }) // Optional
26
+ @Budget({ maxCostUsd: 1.00, window: 'daily' }) // Optional
27
+ export class AssistantAgent {
28
+ @MainLoop({ strategy: 'react', maxIterations: 5 })
29
+ async run() {
30
+ // Framework handles the LLM loop
31
+ }
32
+
33
+ @Hook('before:llm-call')
34
+ async onBeforeLLM(ctx) {
35
+ // Intercept before each LLM call
36
+ }
37
+ }
38
+ ```
39
+
40
+ Convention: `AssistantAgent` class name maps to `GET/POST /api/agents/assistant`.
41
+
42
+ ## @Tool Decorator
43
+
44
+ ```typescript
45
+ import { Toolbox, Tool } from '@theokit/agents'
46
+ import { z } from 'zod'
47
+
48
+ @Toolbox()
49
+ export class TaskTools {
50
+ @Tool({
51
+ name: 'list_tasks',
52
+ description: 'List all tasks, optionally filtered by status',
53
+ input: z.object({
54
+ done: z.boolean().optional(),
55
+ }),
56
+ })
57
+ async listTasks({ done }: { done?: boolean }) {
58
+ const all = db.select().from(tasks).all()
59
+ return done !== undefined ? all.filter(t => t.done === done) : all
60
+ }
61
+
62
+ @Tool({
63
+ name: 'create_task',
64
+ description: 'Create a new task with a title',
65
+ input: z.object({
66
+ title: z.string().min(1),
67
+ }),
68
+ })
69
+ async createTask({ title }: { title: string }) {
70
+ return db.insert(tasks).values({ title }).returning().get()
71
+ }
72
+ }
73
+ ```
74
+
75
+ ## Frontend — useAgentStream
76
+
77
+ ```typescript
78
+ import { useAgentStream } from 'theokit/client'
79
+
80
+ function ChatUI() {
81
+ const { status, events, send } = useAgentStream('/api/agents/assistant')
82
+
83
+ return (
84
+ <div>
85
+ {events.map(e => <p key={e.id}>{e.content}</p>)}
86
+ <button onClick={() => send({ message: 'Hello' })}>Send</button>
87
+ </div>
88
+ )
89
+ }
90
+ ```
91
+
92
+ ## Rules
93
+
94
+ - Tool `name` and `description` are ALWAYS explicit — never inferred from method names (G4)
95
+ - Tool `input` uses Zod schema — same pattern as defineRoute
96
+ - `@UseGuards()` works on agents (shared with HTTP pipeline)
97
+ - `@UseInterceptors()` and `@UseFilters()` on agents are metadata-only (emit warnings, not enforced at runtime)
98
+ - Agent runtime is `@theokit/sdk` — NEVER call LLM APIs directly via fetch
99
+
100
+ ## Anti-patterns
101
+
102
+ - NEVER call OpenAI/Anthropic/OpenRouter APIs directly — use @Agent + @Tool
103
+ - NEVER reimplement tool calling loop — the SDK handles it
104
+ - NEVER store conversations manually — use @Memory
105
+ - NEVER infer tool capability from method name — always provide explicit `name` + `description`
@@ -0,0 +1,128 @@
1
+ ---
2
+ name: theokit-config
3
+ description: TheoKit configuration — defineConfig, plugins, security, storage, agents, build targets
4
+ user-invocable: false
5
+ paths:
6
+ - "theo.config*"
7
+ - "**/*config*"
8
+ ---
9
+
10
+ # TheoKit Configuration
11
+
12
+ ## theo.config.ts
13
+
14
+ ```typescript
15
+ import { defineConfig } from 'theokit'
16
+
17
+ export default defineConfig({
18
+ // Basic
19
+ name: 'my-app', // DNS-1123 format (lowercase + hyphens)
20
+ port: 3000, // Dev + production port
21
+
22
+ // SSR (default: false)
23
+ ssr: false,
24
+
25
+ // Security (defaults are secure)
26
+ security: {
27
+ csrf: true, // CSRF protection (default: true)
28
+ csp: 'report-only', // Content Security Policy
29
+ },
30
+
31
+ // Agent runtime
32
+ agents: {
33
+ maxRegistries: 100,
34
+ registry: {
35
+ maxAgents: 100,
36
+ idleTimeoutMs: 30 * 60_000,
37
+ },
38
+ },
39
+
40
+ // DevTools overlay (dev only)
41
+ devtools: true,
42
+
43
+ // Plugins
44
+ plugins: [],
45
+ })
46
+ ```
47
+
48
+ ## Common Configuration Patterns
49
+
50
+ ### Adding CORS
51
+
52
+ ```typescript
53
+ import { defineConfig } from 'theokit'
54
+
55
+ export default defineConfig({
56
+ // CORS is handled by the framework — configure in route-level or globally
57
+ security: {
58
+ cors: {
59
+ origin: ['http://localhost:3000', 'https://myapp.com'],
60
+ credentials: true,
61
+ },
62
+ },
63
+ })
64
+ ```
65
+
66
+ ### Storage (Postgres + Redis)
67
+
68
+ ```typescript
69
+ export default defineConfig({
70
+ storage: {
71
+ postgres: [{ url: process.env.DATABASE_URL }],
72
+ redis: [{ url: process.env.REDIS_URL }],
73
+ },
74
+ })
75
+ ```
76
+
77
+ ### Rate Limiting
78
+
79
+ ```typescript
80
+ export default defineConfig({
81
+ rateLimit: {
82
+ global: { max: 100, windowMs: 60_000 },
83
+ },
84
+ })
85
+ ```
86
+
87
+ ### OpenAPI Generation
88
+
89
+ ```typescript
90
+ export default defineConfig({
91
+ openapi: {
92
+ title: 'My App API',
93
+ version: '1.0.0',
94
+ outDir: 'docs/api',
95
+ },
96
+ })
97
+ ```
98
+
99
+ ## CLI Commands
100
+
101
+ ```bash
102
+ npx theokit dev # Start dev server with HMR
103
+ npx theokit build # Build for Node.js
104
+ npx theokit build --target=node # Explicit target
105
+ npx theokit start # Run production build
106
+ npx theokit routes # List all discovered endpoints
107
+ npx theokit generate route tasks # Scaffold a new route
108
+ npx theokit generate resource posts title:string # Scaffold CRUD resource
109
+ npx theokit db migrate # Run database migrations
110
+ npx theokit db seed # Seed database
111
+ ```
112
+
113
+ ## Environment Variables
114
+
115
+ - `PORT` — Server port (overrides config)
116
+ - `HOST` — Server host
117
+ - `NODE_ENV` — `development` | `production`
118
+ - `DATABASE_URL` — Postgres connection string (when using postgres storage)
119
+ - `REDIS_URL` — Redis connection string (when using redis storage)
120
+
121
+ Env vars are loaded from `.env` (dev) and `.env.production` (build). NEVER commit `.env` files.
122
+
123
+ ## Anti-patterns
124
+
125
+ - NEVER hardcode secrets in theo.config.ts — use environment variables
126
+ - NEVER set `security.csrf: false` in production
127
+ - NEVER use `ssr: true` without understanding hydration (start with `false`)
128
+ - NEVER add plugins that don't match `defineTheoPlugin` interface
@@ -0,0 +1,112 @@
1
+ ---
2
+ name: theokit-database
3
+ description: TheoKit database — Drizzle ORM, SQLite schema, migrations, seeds, db commands
4
+ user-invocable: false
5
+ paths:
6
+ - "**/*schema*"
7
+ - "**/*db*"
8
+ - "**/drizzle*"
9
+ - "**/*migration*"
10
+ - "**/*seed*"
11
+ ---
12
+
13
+ # TheoKit Database (Drizzle + SQLite)
14
+
15
+ ## Schema Definition
16
+
17
+ ```typescript
18
+ // server/db/schema.ts
19
+ import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
20
+
21
+ export const tasks = sqliteTable('tasks', {
22
+ id: integer('id').primaryKey({ autoIncrement: true }),
23
+ title: text('title').notNull(),
24
+ done: integer('done', { mode: 'boolean' }).notNull().default(false),
25
+ createdAt: text('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
26
+ })
27
+
28
+ export const users = sqliteTable('users', {
29
+ id: integer('id').primaryKey({ autoIncrement: true }),
30
+ email: text('email').notNull().unique(),
31
+ name: text('name').notNull(),
32
+ })
33
+ ```
34
+
35
+ ## DB Connection
36
+
37
+ ```typescript
38
+ // server/db/index.ts
39
+ import { drizzle } from 'drizzle-orm/better-sqlite3'
40
+ import Database from 'better-sqlite3'
41
+ import * as schema from './schema.js'
42
+
43
+ const sqlite = new Database('data/dev.db')
44
+ export const db = drizzle(sqlite, { schema })
45
+ ```
46
+
47
+ ## Common Queries
48
+
49
+ ```typescript
50
+ import { db } from '@/server/db'
51
+ import { tasks } from '@/server/db/schema'
52
+ import { eq } from 'drizzle-orm'
53
+
54
+ // Select all
55
+ db.select().from(tasks).all()
56
+
57
+ // Select by ID
58
+ db.select().from(tasks).where(eq(tasks.id, 1)).get()
59
+
60
+ // Insert + return
61
+ db.insert(tasks).values({ title: 'New task' }).returning().get()
62
+
63
+ // Update
64
+ db.update(tasks).set({ done: true }).where(eq(tasks.id, 1)).run()
65
+
66
+ // Delete
67
+ db.delete(tasks).where(eq(tasks.id, 1)).run()
68
+ ```
69
+
70
+ ## Seeds
71
+
72
+ ```typescript
73
+ // server/db/seed.ts
74
+ import { db } from './index.js'
75
+ import { tasks } from './schema.js'
76
+
77
+ await db.insert(tasks).values([
78
+ { title: 'Learn TheoKit', done: false },
79
+ { title: 'Build an agent', done: false },
80
+ ]).run()
81
+
82
+ console.log('Seeded database')
83
+ ```
84
+
85
+ Run: `npm run seed` or `npx tsx server/db/seed.ts`
86
+
87
+ ## CLI Commands
88
+
89
+ ```bash
90
+ npx drizzle-kit push # Apply schema changes to dev DB (no migration files)
91
+ npx drizzle-kit generate # Generate SQL migration files (for production)
92
+ npm run seed # Run seed script
93
+ ```
94
+
95
+ ## Scaffolding Resources
96
+
97
+ ```bash
98
+ npx theokit generate resource posts title:string published:boolean
99
+ # Creates: server/db/schema.ts (appends table)
100
+ # server/routes/posts/index.ts (GET + POST)
101
+ # server/routes/posts/[id].ts (GET + PUT + DELETE)
102
+ # tests/posts.test.ts (smoke test)
103
+ ```
104
+
105
+ Supported field types: `string`, `text`, `number`, `boolean`
106
+
107
+ ## Anti-patterns
108
+
109
+ - NEVER use raw SQL for schema — use Drizzle's schema builder
110
+ - NEVER delete `data/dev.db` to apply changes — use `npx drizzle-kit push`
111
+ - NEVER put DB connection logic in route files — import from `server/db/index.ts`
112
+ - NEVER use `id`, `createdAt`, or `created_at` as field names in `generate resource` — they're auto-added