create-theokit 1.0.9 → 1.0.10

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-theokit",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "type": "module",
5
5
  "description": "Scaffold a new TheoKit project",
6
6
  "license": "Apache-2.0",
@@ -10,18 +10,14 @@ This is a **full-stack TypeScript app** built with TheoKit — a framework for A
10
10
  server/
11
11
  routes/ → HTTP API routes (defineRoute + Zod validation)
12
12
  health.ts → GET /api/health
13
- tasks/
14
- index.ts → GET /api/tasks (list) + POST /api/tasks (create)
15
- [id].ts → GET/PUT/DELETE /api/tasks/:id
16
13
  db/
17
- schema.ts → Drizzle ORM schema (SQLite)
18
- index.ts → DB connection + auto-create tables
19
- seed.ts → Seed data (run with `npm run seed`)
14
+ schema.ts → Drizzle ORM schema (SQLite) — empty, add your tables
15
+ index.ts → DB connection (better-sqlite3, WAL mode)
20
16
  app/
21
17
  page.tsx → React frontend
22
18
  layout.tsx → Root layout
23
19
  tests/
24
- tasks.test.ts → Example API smoke test
20
+ tasks.test.ts → Example unit test
25
21
  ```
26
22
 
27
23
  ## Key Patterns
@@ -32,13 +28,13 @@ import { defineRoute } from 'theokit/server/define'
32
28
  import { z } from 'zod'
33
29
 
34
30
  export const GET = defineRoute({
35
- handler: () => db.select().from(tasks).all(),
31
+ handler: () => db.select().from(posts).all(),
36
32
  })
37
33
 
38
34
  export const POST = defineRoute({
39
35
  body: z.object({ title: z.string().min(3) }),
40
36
  status: 201,
41
- handler: ({ body }) => db.insert(tasks).values(body).returning().get(),
37
+ handler: ({ body }) => db.insert(posts).values(body).returning().get(),
42
38
  })
43
39
  ```
44
40
 
@@ -46,25 +42,30 @@ export const POST = defineRoute({
46
42
  ```typescript
47
43
  import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
48
44
 
49
- export const tasks = sqliteTable('tasks', {
45
+ export const posts = sqliteTable('posts', {
50
46
  id: integer('id').primaryKey({ autoIncrement: true }),
51
47
  title: text('title').notNull(),
52
- done: integer('done', { mode: 'boolean' }).notNull().default(false),
48
+ published: integer('published', { mode: 'boolean' }).notNull().default(false),
53
49
  })
54
50
  ```
55
51
 
52
+ ### Scaffold a Resource
53
+ ```bash
54
+ npx theokit generate resource posts title:string published:boolean
55
+ # Creates: schema table + routes (CRUD) + test
56
+ ```
57
+
56
58
  ### Validation
57
59
  - **Zod is the single source of truth** — define schema once, get types + validation + OpenAPI
58
60
  - `body: z.object(...)` in defineRoute validates automatically, returns 422 on failure
59
61
  - Use `z.infer<typeof schema>` for TypeScript types
60
62
 
61
63
  ### Dynamic Routes
62
- - `server/routes/tasks/[id].ts` → `/api/tasks/:id`
64
+ - `server/routes/posts/[id].ts` → `/api/posts/:id`
63
65
  - Params validated with `params: z.object({ id: z.coerce.number() })`
64
66
 
65
67
  ### Path Aliases
66
68
  - `@/*` → project root (configured in tsconfig.json)
67
- - `@/server/*` → `./server/*`
68
69
 
69
70
  ## Commands
70
71
 
@@ -73,10 +74,12 @@ npm run dev # Start dev server
73
74
  npm run build # Build for production
74
75
  npm run start # Run production build
75
76
  npm run test # Run tests (vitest)
76
- npm run seed # Seed database with sample data
77
77
  npm run lint # ESLint check
78
78
  npm run format # Prettier format
79
79
  npm run typecheck # TypeScript type check
80
+ npx theokit generate resource <name> <fields...> # Scaffold CRUD resource
81
+ npx drizzle-kit push # Apply schema changes (dev)
82
+ npx drizzle-kit generate # Generate migration files (prod)
80
83
  ```
81
84
 
82
85
  ## Don't
@@ -84,3 +87,5 @@ npm run typecheck # TypeScript type check
84
87
  - Don't use `any` — use Zod schemas + `z.infer<>`
85
88
  - Don't write raw `res.status().json()` — use defineRoute with status option
86
89
  - Don't parse request body manually — use `body: z.object(...)` in defineRoute
90
+ - Don't import from `theokit/dist/...` or `theokit/src/...` — use public exports only
91
+ - Don't call LLM APIs directly — use @Agent + @Tool decorators
@@ -1,51 +1,7 @@
1
- 'use client'
2
-
3
- import { useState, useEffect, useCallback, type FormEvent } from 'react'
4
-
5
- interface Task {
6
- id: number
7
- title: string
8
- priority: 'high' | 'medium' | 'low'
9
- done: boolean
10
- }
11
-
12
1
  export default function Page() {
13
- const [tasks, setTasks] = useState<Task[]>([])
14
- const [title, setTitle] = useState('')
15
- const [priority, setPriority] = useState<Task['priority']>('medium')
16
- const [formError, setFormError] = useState('')
17
-
18
- const loadTasks = useCallback(async () => {
19
- const res = await fetch('/api/tasks')
20
- if (res.ok) setTasks(await res.json())
21
- }, [])
22
-
23
- useEffect(() => {
24
- loadTasks()
25
- }, [loadTasks])
26
-
27
- const createTask = async (e: FormEvent) => {
28
- e.preventDefault()
29
- setFormError('')
30
- if (!title.trim()) 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
- }
41
- setTitle('')
42
- loadTasks()
43
- }
44
-
45
2
  return (
46
3
  <div className="page">
47
4
  <div className="main">
48
- {/* Hero */}
49
5
  <header className="hero">
50
6
  <img src="/logo.png" alt="TheoKit" width={72} height={72} className="hero-logo" />
51
7
  <h1>TheoKit</h1>
@@ -73,61 +29,11 @@ export default function Page() {
73
29
  </p>
74
30
  </header>
75
31
 
76
- {/* Tasks */}
77
- <section className="card">
78
- <h2>
79
- Tasks <span className="badge">defineRoute + Drizzle</span>
80
- </h2>
81
- <table>
82
- <thead>
83
- <tr>
84
- <th>Task</th>
85
- <th>Priority</th>
86
- <th>Status</th>
87
- </tr>
88
- </thead>
89
- <tbody>
90
- {tasks.map((t) => (
91
- <tr key={t.id} className={t.done ? 'done' : ''}>
92
- <td>
93
- {t.done ? '✅ ' : '○ '}
94
- {t.title}
95
- </td>
96
- <td>
97
- <span className={`prio prio-${t.priority}`}>{t.priority}</span>
98
- </td>
99
- <td>{t.done ? 'Done' : 'To do'}</td>
100
- </tr>
101
- ))}
102
- </tbody>
103
- </table>
104
- <form onSubmit={createTask} className="create-bar">
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
- >
116
- <option value="medium">Medium</option>
117
- <option value="high">High</option>
118
- <option value="low">Low</option>
119
- </select>
120
- <button type="submit">Add</button>
121
- </form>
122
- {formError && <p className="error">{formError}</p>}
123
- </section>
124
-
125
- {/* Features */}
126
32
  <div className="grid features">
127
33
  <div className="feature">
128
34
  <h3>defineRoute</h3>
129
35
  <p>
130
- Typed API routes with Zod validation. See <code>server/routes/tasks/</code>
36
+ Typed API routes with Zod validation. See <code>server/routes/</code>
131
37
  </p>
132
38
  </div>
133
39
  <div className="feature">
@@ -146,7 +52,6 @@ export default function Page() {
146
52
  </div>
147
53
  </div>
148
54
 
149
- {/* Footer */}
150
55
  <footer className="footer">
151
56
  Powered by{' '}
152
57
  <a href="https://usetheo.dev" target="_blank" rel="noopener noreferrer">
@@ -156,10 +61,6 @@ export default function Page() {
156
61
  <a href="https://github.com/usetheodev/theokit" target="_blank" rel="noopener noreferrer">
157
62
  GitHub
158
63
  </a>
159
- {' · '}
160
- <a href="https://discord.usetheo.dev" target="_blank" rel="noopener noreferrer">
161
- Discord
162
- </a>
163
64
  </footer>
164
65
  </div>
165
66
  </div>
@@ -1,24 +1,15 @@
1
1
  /**
2
- * TheoKit App — convention over configuration.
2
+ * TheoKit App — manual bootstrap (optional).
3
3
  *
4
- * "The framework that reduces noise for humans + AI."
4
+ * `theokit dev` auto-discovers server/routes/ — you don't need this file
5
+ * for basic usage. Use it when you need explicit controller/agent registration.
5
6
  *
6
- * Backend classes registered in server/index.ts (one barrel, like Rails).
7
- * Routes inferred from class names. Zero manual wiring.
7
+ * Example with controllers + agents:
8
+ *
9
+ * import 'reflect-metadata'
10
+ * import { TheoApp } from '@theokit/http/app'
11
+ * import { MyController } from './server/controllers/my.controller.js'
12
+ *
13
+ * const app = await TheoApp.create({ controllers: [MyController] })
14
+ * await app.listen(3000)
8
15
  */
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)
@@ -8,10 +8,8 @@
8
8
  "build": "theokit build",
9
9
  "start": "theokit start",
10
10
  "test": "vitest run",
11
- "seed": "npx tsx server/db/seed.ts",
12
11
  "db:migrate": "theokit db migrate",
13
12
  "db:generate": "theokit db generate",
14
- "db:seed": "npx tsx server/db/seed.ts",
15
13
  "lint": "eslint .",
16
14
  "lint:fix": "eslint . --fix",
17
15
  "format": "prettier --write .",
@@ -8,14 +8,3 @@ const sqlite = new Database('data/dev.db')
8
8
  sqlite.pragma('journal_mode = WAL')
9
9
 
10
10
  export const db = drizzle(sqlite, { schema })
11
-
12
- // Auto-create tables (simple push — no migration files needed for dev)
13
- sqlite.exec(`
14
- CREATE TABLE IF NOT EXISTS tasks (
15
- id INTEGER PRIMARY KEY AUTOINCREMENT,
16
- title TEXT NOT NULL,
17
- priority TEXT NOT NULL DEFAULT 'medium',
18
- done INTEGER NOT NULL DEFAULT 0,
19
- created_at TEXT NOT NULL DEFAULT (date('now'))
20
- )
21
- `)
@@ -1,13 +1,13 @@
1
- import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
2
-
3
- export const tasks = sqliteTable('tasks', {
4
- id: integer('id').primaryKey({ autoIncrement: true }),
5
- title: text('title').notNull(),
6
- priority: text('priority', { enum: ['low', 'medium', 'high'] })
7
- .notNull()
8
- .default('medium'),
9
- done: integer('done', { mode: 'boolean' }).notNull().default(false),
10
- createdAt: text('created_at')
11
- .notNull()
12
- .$defaultFn(() => new Date().toISOString().split('T')[0]),
13
- })
1
+ // Define your Drizzle tables here. Example:
2
+ //
3
+ // import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
4
+ //
5
+ // export const posts = sqliteTable('posts', {
6
+ // id: integer('id').primaryKey({ autoIncrement: true }),
7
+ // title: text('title').notNull(),
8
+ // published: integer('published', { mode: 'boolean' }).notNull().default(false),
9
+ // createdAt: text('created_at').notNull().$defaultFn(() => new Date().toISOString()),
10
+ // })
11
+ //
12
+ // Or scaffold a full resource (schema + routes + test):
13
+ // npx theokit generate resource posts title:string published:boolean
@@ -1,38 +1,18 @@
1
1
  import { describe, it, expect } from 'vitest'
2
- import { z } from 'zod'
3
2
 
4
- // Schema mirrors server/routes/tasks/index.ts POST body validation.
5
- // Unit test — no server dependency.
6
- const taskBodySchema = z.object({
7
- title: z.string().min(3),
8
- done: z.boolean().default(false),
9
- })
10
-
11
- describe('Task body validation', () => {
12
- it('accepts valid task', () => {
13
- const result = taskBodySchema.safeParse({ title: 'Buy groceries' })
14
- expect(result.success).toBe(true)
15
- if (result.success) {
16
- expect(result.data.title).toBe('Buy groceries')
17
- expect(result.data.done).toBe(false)
3
+ describe('Health route response shape', () => {
4
+ it('returns expected fields', () => {
5
+ // Mirrors the shape returned by server/routes/health.ts GET handler.
6
+ // Unit test — no server dependency.
7
+ const response = {
8
+ status: 'ok',
9
+ timestamp: Date.now(),
10
+ framework: 'TheoKit',
18
11
  }
19
- })
20
-
21
- it('defaults done to false', () => {
22
- const result = taskBodySchema.safeParse({ title: 'New task' })
23
- expect(result.success).toBe(true)
24
- if (result.success) expect(result.data.done).toBe(false)
25
- })
26
-
27
- it('rejects title shorter than 3 characters', () => {
28
- expect(taskBodySchema.safeParse({ title: 'ab' }).success).toBe(false)
29
- })
30
-
31
- it('rejects empty title', () => {
32
- expect(taskBodySchema.safeParse({ title: '' }).success).toBe(false)
33
- })
34
12
 
35
- it('rejects missing title', () => {
36
- expect(taskBodySchema.safeParse({ done: true }).success).toBe(false)
13
+ expect(response.status).toBe('ok')
14
+ expect(response.framework).toBe('TheoKit')
15
+ expect(typeof response.timestamp).toBe('number')
16
+ expect(response.timestamp).toBeGreaterThan(0)
37
17
  })
38
18
  })
@@ -1,43 +0,0 @@
1
- /**
2
- * AssistantAgent — AI-powered task management assistant.
3
- *
4
- * Uses the SAME guards and pipeline as HTTP controllers.
5
- * Tools from @Mixin(TaskTools) are available to the LLM.
6
- */
7
- import 'reflect-metadata'
8
- import {
9
- Agent, MainLoop, Mixin,
10
- Memory, Budget, Hook,
11
- } from '@theokit/agents'
12
- import { UseGuards, UseInterceptors } from '@theokit/http'
13
- import { RolesGuard, Roles, Role } from '../guards/auth.guard.js'
14
- import { TimingInterceptor } from '../interceptors/timing.interceptor.js'
15
- import { TaskTools } from '../toolboxes/task.tools.js'
16
-
17
- @Agent({
18
- // Convention: AssistantAgent → name: 'assistant', route: /api/agents/assistant
19
- model: 'openai/gpt-4o-mini',
20
- systemPrompt: `You are a helpful task management assistant.
21
- Use the tasks.* tools to list, search, create, and complete tasks.
22
- Be concise and actionable.`,
23
- })
24
- @UseGuards(RolesGuard)
25
- @UseInterceptors(TimingInterceptor)
26
- @Roles([Role.User])
27
- @Memory({ provider: 'built-in', scope: 'per-user' })
28
- @Budget({ maxCostUsd: 1.00, window: 'daily' })
29
- @Mixin(TaskTools)
30
- export class AssistantAgent {
31
- @MainLoop({ strategy: 'react', maxIterations: 5 })
32
- async run() {}
33
-
34
- @Hook('before:llm-call')
35
- async onBeforeLLM() {
36
- console.log(' 🧠 Agent thinking...')
37
- }
38
-
39
- @Hook('after:tool-call')
40
- async onToolDone() {
41
- console.log(' 🔧 Tool executed')
42
- }
43
- }
@@ -1,70 +0,0 @@
1
- /**
2
- * TasksController — CRUD API for tasks.
3
- *
4
- * Demonstrates: @Controller, @Get/@Post/@Delete, @Body with Zod,
5
- * @Param, @Query, @HttpCode, @UseGuards, @Roles, @IsPublic,
6
- * @UseInterceptors, @UseFilters, NotFoundException.
7
- */
8
- import 'reflect-metadata'
9
- import { z } from 'zod'
10
- import {
11
- Controller, Get, Post, Put, Delete,
12
- Body, Param, Query, HttpCode,
13
- UseGuards, UseInterceptors, UseFilters,
14
- NotFoundException,
15
- } from '@theokit/http'
16
- import { RolesGuard, Roles, Role, IsPublic } from '../guards/auth.guard.js'
17
- import { TimingInterceptor } from '../interceptors/timing.interceptor.js'
18
- import { HttpErrorFilter } from '../filters/http-error.filter.js'
19
- import { taskStore } from '../store.js'
20
-
21
- const zCreateTask = z.object({
22
- title: z.string().min(3, 'Title must be at least 3 characters'),
23
- priority: z.enum(['low', 'medium', 'high']).default('medium'),
24
- })
25
-
26
- @Controller() // → /api/tasks (convention: inferred from TasksController)
27
- @UseGuards(RolesGuard)
28
- @UseInterceptors(TimingInterceptor)
29
- @UseFilters(HttpErrorFilter)
30
- @Roles([Role.User])
31
- export class TasksController {
32
- @Get()
33
- @IsPublic(true)
34
- list() {
35
- return taskStore.list()
36
- }
37
-
38
- @Get('search')
39
- @IsPublic(true)
40
- search(@Query('q') q: string) {
41
- return taskStore.search(q ?? '')
42
- }
43
-
44
- @Get(':id')
45
- @IsPublic(true)
46
- findById(@Param('id') id: string) {
47
- const task = taskStore.get(Number(id))
48
- if (!task) throw new NotFoundException(`Task ${id} not found`)
49
- return task
50
- }
51
-
52
- @Post()
53
- create(@Body(zCreateTask) body: z.infer<typeof zCreateTask>) {
54
- return taskStore.create(body)
55
- }
56
-
57
- @Put(':id')
58
- update(@Param('id') id: string, @Body(z.object({ done: z.boolean() })) body: { done: boolean }) {
59
- const task = taskStore.update(Number(id), body)
60
- if (!task) throw new NotFoundException(`Task ${id} not found`)
61
- return task
62
- }
63
-
64
- @Delete(':id')
65
- @HttpCode(204)
66
- @Roles([Role.Admin])
67
- remove(@Param('id') id: string) {
68
- if (!taskStore.remove(Number(id))) throw new NotFoundException(`Task ${id} not found`)
69
- }
70
- }
@@ -1,29 +0,0 @@
1
- import { db } from './index.js'
2
- import { tasks } from './schema.js'
3
- import { sql } from 'drizzle-orm'
4
-
5
- const seedData = [
6
- {
7
- title: 'Set up TheoKit project',
8
- priority: 'high' as const,
9
- done: true,
10
- createdAt: '2026-01-01',
11
- },
12
- { title: 'Create first route', priority: 'high' as const, done: true, createdAt: '2026-01-02' },
13
- { title: 'Add AI agent', priority: 'medium' as const, done: false, createdAt: '2026-01-03' },
14
- { title: 'Deploy to production', priority: 'low' as const, done: false, createdAt: '2026-01-04' },
15
- ]
16
-
17
- // Idempotent: only insert if table is empty
18
- const count = db
19
- .select({ count: sql<number>`count(*)` })
20
- .from(tasks)
21
- .get()
22
- if (!count || count.count === 0) {
23
- for (const task of seedData) {
24
- db.insert(tasks).values(task).run()
25
- }
26
- console.log(`Seeded ${seedData.length} tasks`)
27
- } else {
28
- console.log(`Tasks table already has ${count.count} rows — skipping seed`)
29
- }
@@ -1,20 +0,0 @@
1
- /**
2
- * HttpErrorFilter — custom error response format.
3
- * Catches HttpException and returns structured JSON.
4
- */
5
- import { Catch, HttpException, type ExceptionFilter, type ArgumentsHost } from '@theokit/http'
6
-
7
- @Catch(HttpException)
8
- export class HttpErrorFilter implements ExceptionFilter {
9
- catch(exception: unknown, _host: ArgumentsHost): Response {
10
- const ex = exception as HttpException
11
- return new Response(JSON.stringify({
12
- success: false,
13
- error: { code: ex.statusCode, message: ex.message },
14
- timestamp: new Date().toISOString(),
15
- }), {
16
- status: ex.statusCode,
17
- headers: { 'content-type': 'application/json' },
18
- })
19
- }
20
- }
@@ -1,47 +0,0 @@
1
- /**
2
- * RolesGuard — RBAC authorization for controllers AND agents.
3
- *
4
- * Same guard, same pipeline, same behavior on both HTTP and AI routes.
5
- * Uses @Roles([Role.Admin]) on class/method and @IsPublic(true) to skip.
6
- */
7
- import {
8
- createDecorator, Reflector,
9
- type CanActivate, type ExecutionContext,
10
- } from '@theokit/http'
11
-
12
- export enum Role {
13
- User = 'user',
14
- Admin = 'admin',
15
- }
16
-
17
- /** Declare required roles for a route or agent. */
18
- export const Roles = createDecorator<Role[]>()
19
-
20
- /** Mark a route as public — skips auth entirely. */
21
- export const IsPublic = createDecorator<boolean>()
22
-
23
- const reflector = new Reflector()
24
-
25
- export class RolesGuard implements CanActivate {
26
- canActivate(context: ExecutionContext): boolean {
27
- // Public routes skip auth
28
- const isPublic = reflector.getAllAndOverride(
29
- IsPublic,
30
- context.getClass(),
31
- context.getMethodName(),
32
- )
33
- if (isPublic) return true
34
-
35
- // Check required roles
36
- const roles = reflector.getAllAndOverride(
37
- Roles,
38
- context.getClass(),
39
- context.getMethodName(),
40
- )
41
- if (!roles) return true // no roles required
42
-
43
- // Read role from request header (replace with JWT/session in production)
44
- const userRole = context.getRequest().headers.get('x-role') ?? ''
45
- return roles.some((r) => r === userRole)
46
- }
47
- }
@@ -1,14 +0,0 @@
1
- /**
2
- * TimingInterceptor — logs request duration.
3
- * Applied to both controllers and agents.
4
- */
5
- import type { Interceptor } from '@theokit/http'
6
-
7
- export class TimingInterceptor implements Interceptor {
8
- async intercept(_request: Request, next: () => Promise<unknown>): Promise<unknown> {
9
- const start = Date.now()
10
- const result = await next()
11
- console.log(` ${Date.now() - start}ms`)
12
- return result
13
- }
14
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * LoggerMiddleware — logs every incoming request.
3
- */
4
- import type { NestMiddleware } from '@theokit/http'
5
-
6
- export class LoggerMiddleware implements NestMiddleware {
7
- use(request: Request, next: () => Promise<Response | null>): Promise<Response | null> {
8
- const url = new URL(request.url)
9
- console.log(` ${new Date().toLocaleTimeString()} ${request.method} ${url.pathname}`)
10
- return next()
11
- }
12
- }
@@ -1,36 +0,0 @@
1
- import { defineRoute } from 'theokit/server/define'
2
- import { z } from 'zod'
3
- import { db } from '../../db/index.js'
4
- import { tasks } from '../../db/schema.js'
5
- import { eq } from 'drizzle-orm'
6
-
7
- export const GET = defineRoute({
8
- params: z.object({ id: z.coerce.number() }),
9
- handler: ({ params }) => {
10
- const task = db.select().from(tasks).where(eq(tasks.id, params.id)).get()
11
- if (!task) return new Response(JSON.stringify({ error: 'Task not found' }), { status: 404 })
12
- return task
13
- },
14
- })
15
-
16
- export const PUT = defineRoute({
17
- params: z.object({ id: z.coerce.number() }),
18
- body: z.object({
19
- title: z.string().optional(),
20
- priority: z.enum(['low', 'medium', 'high']).optional(),
21
- done: z.boolean().optional(),
22
- }),
23
- handler: ({ params, body }) => {
24
- const result = db.update(tasks).set(body).where(eq(tasks.id, params.id)).returning().get()
25
- if (!result) return new Response(JSON.stringify({ error: 'Task not found' }), { status: 404 })
26
- return result
27
- },
28
- })
29
-
30
- export const DELETE = defineRoute({
31
- params: z.object({ id: z.coerce.number() }),
32
- status: 204,
33
- handler: ({ params }) => {
34
- db.delete(tasks).where(eq(tasks.id, params.id)).run()
35
- },
36
- })
@@ -1,20 +0,0 @@
1
- import { defineRoute } from 'theokit/server/define'
2
- import { z } from 'zod'
3
- import { db } from '../../db/index.js'
4
- import { tasks } from '../../db/schema.js'
5
-
6
- export const GET = defineRoute({
7
- handler: () => db.select().from(tasks).all(),
8
- })
9
-
10
- export const POST = defineRoute({
11
- body: z.object({
12
- title: z.string().min(3, 'Title must be at least 3 characters'),
13
- priority: z.enum(['low', 'medium', 'high']).default('medium'),
14
- }),
15
- status: 201,
16
- handler: ({ body }) => {
17
- const result = db.insert(tasks).values(body).returning().get()
18
- return result
19
- },
20
- })
@@ -1,46 +0,0 @@
1
- /**
2
- * In-memory data store.
3
- * Replace with Drizzle + PostgreSQL for production.
4
- */
5
-
6
- export interface Task {
7
- id: number
8
- title: string
9
- priority: 'low' | 'medium' | 'high'
10
- done: boolean
11
- createdAt: string
12
- }
13
-
14
- let seq = 0
15
-
16
- const tasks: Task[] = [
17
- { id: ++seq, title: 'Set up TheoKit project', priority: 'high', done: true, createdAt: '2026-01-01' },
18
- { id: ++seq, title: 'Create first controller', priority: 'high', done: true, createdAt: '2026-01-02' },
19
- { id: ++seq, title: 'Add AI agent', priority: 'medium', done: false, createdAt: '2026-01-03' },
20
- { id: ++seq, title: 'Deploy to production', priority: 'low', done: false, createdAt: '2026-01-04' },
21
- ]
22
-
23
- export const taskStore = {
24
- list: () => [...tasks],
25
- get: (id: number) => tasks.find((t) => t.id === id),
26
- search: (q: string) => tasks.filter((t) => t.title.toLowerCase().includes(q.toLowerCase())),
27
- create: (data: { title: string; priority?: Task['priority'] }) => {
28
- const task: Task = { id: ++seq, title: data.title, priority: data.priority ?? 'medium', done: false, createdAt: new Date().toISOString().split('T')[0] }
29
- tasks.push(task)
30
- return task
31
- },
32
- update: (id: number, data: Partial<Pick<Task, 'title' | 'priority' | 'done'>>) => {
33
- const t = tasks.find((x) => x.id === id)
34
- if (!t) return null
35
- if (data.title !== undefined) t.title = data.title
36
- if (data.priority !== undefined) t.priority = data.priority
37
- if (data.done !== undefined) t.done = data.done
38
- return t
39
- },
40
- remove: (id: number) => {
41
- const idx = tasks.findIndex((t) => t.id === id)
42
- if (idx === -1) return false
43
- tasks.splice(idx, 1)
44
- return true
45
- },
46
- }
@@ -1,58 +0,0 @@
1
- /**
2
- * TaskTools — agent toolbox for task management.
3
- *
4
- * These tools are called by the LLM when the agent decides
5
- * to interact with the task data. Each @Tool method is
6
- * compiled to a defineTool() call at startup.
7
- */
8
- import 'reflect-metadata'
9
- import { z } from 'zod'
10
- import { Toolbox, Tool, Trace, Audit } from '@theokit/agents'
11
- import { taskStore } from '../store.js'
12
-
13
- @Toolbox() // Convention: TaskTools → namespace: 'task'
14
- @Trace(true)
15
- export class TaskTools {
16
- @Tool({
17
- name: 'list',
18
- description: 'List all tasks with their status and priority',
19
- input: z.object({}),
20
- })
21
- async list() {
22
- return JSON.stringify(taskStore.list())
23
- }
24
-
25
- @Tool({
26
- name: 'search',
27
- description: 'Search tasks by keyword in title',
28
- input: z.object({ query: z.string() }),
29
- })
30
- async search(input: { query: string }) {
31
- return JSON.stringify(taskStore.search(input.query))
32
- }
33
-
34
- @Tool({
35
- name: 'create',
36
- description: 'Create a new task',
37
- input: z.object({
38
- title: z.string(),
39
- priority: z.enum(['low', 'medium', 'high']).default('medium'),
40
- }),
41
- risk: 'medium',
42
- })
43
- @Audit(true)
44
- async create(input: { title: string; priority?: 'low' | 'medium' | 'high' }) {
45
- return JSON.stringify(taskStore.create(input))
46
- }
47
-
48
- @Tool({
49
- name: 'complete',
50
- description: 'Mark a task as done by its ID',
51
- input: z.object({ taskId: z.number() }),
52
- })
53
- @Audit(true)
54
- async complete(input: { taskId: number }) {
55
- const task = taskStore.update(input.taskId, { done: true })
56
- return task ? JSON.stringify(task) : 'Task not found'
57
- }
58
- }