create-theokit 1.25.1 → 1.25.3

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.25.1",
3
+ "version": "1.25.3",
4
4
  "type": "module",
5
5
  "description": "Scaffold a new TheoKit project",
6
6
  "repository": {
@@ -8,13 +8,13 @@ This project includes TheoKit-aware skills that activate automatically when you
8
8
 
9
9
  | Skill | Triggers when editing | What it provides |
10
10
  | ---------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
11
- | theokit-routes | `server/routes/**` | defineRoute API, Zod validation, HTTP methods, dynamic params |
11
+ | theokit-routes | `server/routes/**` | the route() builder, Zod validation, HTTP methods, dynamic params |
12
12
  | theokit-gateways | `server/routes/**`, `**/*webhook*` | handleChannelWebhook, the `@theokit/gateway-*` adapters, signature validation, the onMessage seam |
13
13
  | theokit-agents | `**/*agent*`, `**/*tool*`, `**/*Agent*`, `**/*Tool*` | @Agent, @Tool, @Toolbox decorators, LLM integration |
14
14
  | theokit-database | `**/*schema*`, `**/*db*`, `**/drizzle*`, `**/*migration*`, `**/*seed*` | Drizzle ORM, SQLite, schema patterns, migrations |
15
15
  | theokit-frontend | `app/**` | File-based routing, layouts, theoFetch, useAgent |
16
16
  | theokit-ui | `app/**`, `**/*Chat*`, `**/*Sidebar*`, `**/*Theme*` | @theokit/ui AI components: ChatThread, ChatMessage, ToolCallCard, theming (generic primitives like CodeBlock/Sidebar come from @usetheo/ui) |
17
- | theokit-config | `theo.config*`, `**/*config*` | defineConfig options, plugins, security, storage |
17
+ | theokit-config | `theo.config*`, `**/*config*` | the config() builder, plugins, security, storage |
18
18
 
19
19
  ### Settings
20
20
 
@@ -17,7 +17,7 @@ Common changes, and where they go. See [ARCHITECTURE](./ARCHITECTURE.md) for the
17
17
 
18
18
  ```ts
19
19
  // agents/tools/echo.ts
20
- import { tool } from 'theokit/server'
20
+ import { tool } from 'theokit/server/define'
21
21
  import { z } from 'zod'
22
22
 
23
23
  export const echoTool = tool('echo')
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Imports
4
4
 
5
- - Use `theokit/server/define` for defineRoute, defineAction, defineWebSocket
5
+ - Use `theokit/server/define` for `route()`, `action()`, `websocket()`, `tool()`
6
6
  - Use `theokit/client` for theoFetch, createAppClient
7
7
  - Use `theokit/server/auth` for session/auth APIs
8
8
  - NEVER import from `theokit/dist/...` or `theokit/src/...`
@@ -13,12 +13,12 @@
13
13
  - Zod is the single source of truth for types and validation
14
14
  - Define schema ONCE with `z.object(...)`, derive types with `z.infer<>`
15
15
  - NEVER duplicate a Zod schema as a manual TypeScript interface
16
- - NEVER parse request body manually — use `body:` in defineRoute
16
+ - NEVER parse request body manually — use `.body(z.object({ }))` on the `route()` chain
17
17
 
18
18
  ## Routes
19
19
 
20
20
  - File at `server/routes/tasks/[id].ts` maps to `/api/tasks/:id`
21
- - Export HTTP method handlers: `export const GET = defineRoute({...})`
21
+ - Export HTTP method handlers: `export const GET = route().policy('public').handler(…).build()`
22
22
  - Every method declares `policy` — who may call it. `'public'` is a valid answer and an explicit one;
23
23
  omitting it fails the build with the file named
24
24
  - Use `params: z.object({...})` for URL params, `body:` for request body
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: theokit-agents
3
- description: TheoKit agent/LLM integration — agents/*.ts convention (defineAgent), @Agent decorator (advanced/DI), defineAgentTool, useAgent client hook
3
+ description: TheoKit agent/LLM integration — agents/*.ts convention (AgentBuilder), the tool() builder, capabilities (advanced/DI), useAgent client hook
4
4
  user-invocable: false
5
5
  paths:
6
6
  - '**/*agent*'
@@ -20,14 +20,14 @@ Create an `agents/<name>.ts` file at the project root. It is automatically serve
20
20
 
21
21
  ```typescript
22
22
  // agents/chat.ts
23
- import { defineAgent } from '@theokit/agents'
23
+ import { AgentBuilder } from '@theokit/agents'
24
24
  import { z } from 'zod'
25
25
 
26
- export default defineAgent({
27
- input: z.object({ message: z.string() }),
28
- model: 'openrouter/openai/gpt-4o-mini',
29
- system: 'You are a helpful assistant.',
30
- })
26
+ export default AgentBuilder.create()
27
+ .input(z.object({ message: z.string() }))
28
+ .model('openrouter/openai/gpt-4o-mini')
29
+ .system('You are a helpful assistant.')
30
+ .build()
31
31
  ```
32
32
 
33
33
  The endpoint streams the ai-sdk `UIMessageStream` that `useAgent` (client hook) consumes.
@@ -58,32 +58,52 @@ export const assistantAgent = applyCapabilities([
58
58
  // The framework runs the LLM loop via @theokit/sdk.
59
59
  ```
60
60
 
61
- ## Tools — defineAgentTool
61
+ ## Tools — `tool()`
62
+
63
+ Declare a tool with the `tool()` builder from `theokit/server/define`, then chain it onto the agent
64
+ with `.tool(…)`. It is the same API `agents/tools/weather.ts` in this project uses — read that file
65
+ for a working one.
66
+
67
+ ```typescript
68
+ // agents/tools/current-time.ts
69
+ import { tool } from 'theokit/server/define'
70
+ import { z } from 'zod'
62
71
 
63
- Declare typed tools with `defineAgentTool` (from `theokit/server`) and pass them to
64
- `defineAgent`'s `tools` array.
72
+ export const currentTimeTool = tool('current_time')
73
+ .describe('Return the current ISO timestamp')
74
+ .input(z.object({}))
75
+ .execute(async () => new Date().toISOString())
76
+ .build()
77
+ ```
65
78
 
66
79
  ```typescript
67
80
  // agents/chat.ts
68
- import { defineAgent } from '@theokit/agents'
69
- import { defineAgentTool } from 'theokit/server'
81
+ import { AgentBuilder } from '@theokit/agents'
70
82
  import { z } from 'zod'
71
83
 
72
- const currentTimeTool = defineAgentTool({
73
- name: 'current_time',
74
- description: 'Return the current ISO timestamp',
75
- inputSchema: z.object({}),
76
- handler: async () => new Date().toISOString(),
77
- })
84
+ import { currentTimeTool } from './tools/current-time.js'
78
85
 
79
- export default defineAgent({
80
- input: z.object({ message: z.string() }),
81
- model: 'openrouter/openai/gpt-4o-mini',
82
- system: 'You are a helpful assistant.',
83
- tools: [currentTimeTool],
84
- })
86
+ export default AgentBuilder.create()
87
+ .input(z.object({ message: z.string() }))
88
+ .model('openrouter/openai/gpt-4o-mini')
89
+ .system('You are a helpful assistant.')
90
+ .tool(currentTimeTool)
91
+ .build()
85
92
  ```
86
93
 
94
+ A tool is pure metadata plus a handler: it describes a capability and does local or HTTP work, and
95
+ it NEVER calls an LLM — the agent decides when to invoke it.
96
+
97
+ **Import from `theokit/server/define`, not `theokit/server`.** The umbrella subpath still resolves
98
+ and prints a deprecation warning naming a removal release; every symbol lives under a domain
99
+ subpath (`define`, `auth`, `http`, `security`, …).
100
+
101
+ > **`defineAgentTool` does not exist.** Earlier versions of this skill taught it. The name is still
102
+ > declared in the published `.d.ts`, so an editor will autocomplete it and `tsc` will accept it —
103
+ > and there is no runtime export behind it on any subpath, so the call throws on the first request
104
+ > (usetheokit/theokit#542). If you find it in older code or in a generated snippet, replace it with
105
+ > the `tool()` builder above.
106
+
87
107
  ### Toolbox class (advanced — state + injected dependencies)
88
108
 
89
109
  A toolbox declares its tools as DATA and keeps handlers as ordinary methods, so the class can hold
@@ -177,7 +197,7 @@ Before writing custom tools, check if they already exist:
177
197
  ## Rules
178
198
 
179
199
  - Tool `name` and `description` are ALWAYS explicit — never inferred from method names (G4)
180
- - Tool `input` uses Zod schema — same pattern as defineRoute
200
+ - Tool `.input()` takes a Zod schema — same pattern as `route().body(…)`
181
201
  - `@UseGuards()` works on agents (shared with HTTP pipeline)
182
202
  - `@UseInterceptors()` and `@UseFilters()` on agents are metadata-only (emit warnings)
183
203
  - Agent runtime is `@theokit/sdk` — NEVER call LLM APIs directly via fetch
@@ -185,7 +205,7 @@ Before writing custom tools, check if they already exist:
185
205
 
186
206
  ## Anti-patterns
187
207
 
188
- - NEVER call OpenAI/Anthropic/OpenRouter APIs directly — use `defineAgent` or `@Agent`
208
+ - NEVER call OpenAI/Anthropic/OpenRouter APIs directly — use `AgentBuilder.create()`
189
209
  - NEVER reimplement tool calling loop — the SDK handles it
190
210
  - NEVER reimplement file/search/shell tools — use `@theokit/sdk-tools` (readFile, writeFile, search, etc.)
191
211
  - NEVER store conversations manually — SDK persistence is automatic (the SDK owns storage)
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: theokit-config
3
- description: TheoKit configuration — defineConfig, plugins, security, storage, agents, build targets
3
+ description: TheoKit configuration — the config() builder, plugins, security, storage, agents, build targets
4
4
  user-invocable: false
5
5
  paths:
6
6
  - 'theo.config*'
@@ -12,37 +12,39 @@ paths:
12
12
  ## theo.config.ts
13
13
 
14
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,
15
+ import { config } from 'theokit'
16
+
17
+ export default config()
18
+ .set({
19
+ // Basic
20
+ name: 'my-app', // DNS-1123 format (lowercase + hyphens)
21
+ port: 3000, // Dev + production port
22
+
23
+ // SSR (default: false)
24
+ ssr: false,
25
+
26
+ // Security (defaults are secure)
27
+ security: {
28
+ csrf: true, // CSRF protection (default: true)
29
+ csp: 'report-only', // Content Security Policy
30
+ },
31
+
32
+ // Agent runtime
33
+ agents: {
34
+ maxRegistries: 100,
35
+ registry: {
36
+ maxAgents: 100,
37
+ idleTimeoutMs: 30 * 60_000,
38
+ },
37
39
  },
38
- },
39
40
 
40
- // DevTools overlay (dev only)
41
- devtools: true,
41
+ // DevTools overlay (dev only)
42
+ devtools: true,
42
43
 
43
- // Plugins
44
- plugins: [],
45
- })
44
+ // Plugins
45
+ plugins: [],
46
+ })
47
+ .build()
46
48
  ```
47
49
 
48
50
  ## Common Configuration Patterns
@@ -50,50 +52,58 @@ export default defineConfig({
50
52
  ### Adding CORS
51
53
 
52
54
  ```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,
55
+ import { config } from 'theokit'
56
+
57
+ export default config()
58
+ .set({
59
+ // CORS is handled by the framework — configure in route-level or globally
60
+ security: {
61
+ cors: {
62
+ origin: ['http://localhost:3000', 'https://myapp.com'],
63
+ credentials: true,
64
+ },
61
65
  },
62
- },
63
- })
66
+ })
67
+ .build()
64
68
  ```
65
69
 
66
70
  ### Storage (Postgres + Redis)
67
71
 
68
72
  ```typescript
69
- export default defineConfig({
70
- storage: {
71
- postgres: [{ url: process.env.DATABASE_URL }],
72
- redis: [{ url: process.env.REDIS_URL }],
73
- },
74
- })
73
+ export default config()
74
+ .set({
75
+ storage: {
76
+ postgres: [{ url: process.env.DATABASE_URL }],
77
+ redis: [{ url: process.env.REDIS_URL }],
78
+ },
79
+ })
80
+ .build()
75
81
  ```
76
82
 
77
83
  ### Rate Limiting
78
84
 
79
85
  ```typescript
80
- export default defineConfig({
81
- rateLimit: {
82
- global: { max: 100, windowMs: 60_000 },
83
- },
84
- })
86
+ export default config()
87
+ .set({
88
+ rateLimit: {
89
+ global: { max: 100, windowMs: 60_000 },
90
+ },
91
+ })
92
+ .build()
85
93
  ```
86
94
 
87
95
  ### OpenAPI Generation
88
96
 
89
97
  ```typescript
90
- export default defineConfig({
91
- openapi: {
92
- title: 'My App API',
93
- version: '1.0.0',
94
- outDir: '.theokit',
95
- },
96
- })
98
+ export default config()
99
+ .set({
100
+ openapi: {
101
+ title: 'My App API',
102
+ version: '1.0.0',
103
+ outDir: '.theokit',
104
+ },
105
+ })
106
+ .build()
97
107
  ```
98
108
 
99
109
  ## CLI Commands
@@ -125,4 +135,4 @@ Env vars are loaded from `.env` (dev) and `.env.production` (build). NEVER commi
125
135
  - NEVER hardcode secrets in theo.config.ts — use environment variables
126
136
  - NEVER set `security.csrf: false` in production
127
137
  - NEVER use `ssr: true` without understanding hydration (start with `false`)
128
- - NEVER add plugins that don't match `defineTheoPlugin` interface
138
+ - NEVER add plugins that don't match `TheoPlugin` interface
@@ -71,7 +71,7 @@ Developing against any of this needs a public URL. `theo.config.ts` has `allowed
71
71
  that — see the framework README.
72
72
 
73
73
  **Give it a `Request` whose body has not been read.** It calls `request.json()` itself, so a wrapper
74
- that has already parsed the body — `defineRoute` offers a parsed `body` in its handler context —
74
+ that has already parsed the body — `route().body(…)` offers a parsed `body` in its handler context —
75
75
  leaves nothing for it to read. Mount it where you still hold the original request, or pass a clone.
76
76
 
77
77
  `ChannelMessage` is `{ agent, platform, payload }`. There is no `request` inside `onMessage`, because
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: theokit-routes
3
- description: TheoKit server routes — defineRoute, Zod validation, HTTP methods, dynamic params, error handling
3
+ description: TheoKit server routes — the route() builder, Zod validation, HTTP methods, dynamic params, error handling
4
4
  user-invocable: false
5
5
  paths:
6
6
  - 'server/routes/**'
@@ -9,41 +9,47 @@ paths:
9
9
 
10
10
  # TheoKit Routes
11
11
 
12
- ## defineRoute API
12
+ ## The `route()` builder
13
13
 
14
14
  ```typescript
15
- import { defineRoute } from 'theokit/server/define'
15
+ import { route } from 'theokit/server/define'
16
16
  import { z } from 'zod'
17
17
 
18
- // GET handler — no body, optional params/query
19
- export const GET = defineRoute({
20
- policy: 'public', // who may call it — required
21
- params: z.object({ id: z.coerce.number() }), // URL params
22
- query: z.object({ page: z.coerce.number().optional() }), // Query string
23
- handler: ({ params, query }) => {
24
- return { id: params.id, page: query?.page }
25
- },
26
- })
27
-
28
- // POST handler with body validation + custom status
29
- export const POST = defineRoute({
30
- policy: ({ subject }) => subject !== null, // any authenticated caller
31
- body: z.object({
32
- title: z.string().min(3),
33
- done: z.boolean().default(false),
34
- }),
35
- status: 201,
36
- handler: ({ body }) => {
37
- // body is fully typed from Zod schema
18
+ // GET — no body, optional params/query
19
+ export const GET = route()
20
+ .policy('public') // who may call it — required
21
+ .params(z.object({ id: z.coerce.number() })) // URL params
22
+ .query(z.object({ page: z.coerce.number().optional() })) // query string
23
+ .handler(({ params, query }) => ({ id: params.id, page: query?.page }))
24
+ .build()
25
+
26
+ // POST — body validation + custom status
27
+ export const POST = route()
28
+ .policy(({ subject }) => subject !== null) // any authenticated caller
29
+ .body(
30
+ z.object({
31
+ title: z.string().min(3),
32
+ done: z.boolean().default(false),
33
+ }),
34
+ )
35
+ .status(201)
36
+ .handler(({ body }) => {
37
+ // body is fully typed from the Zod schema
38
38
  return db.insert(tasks).values(body).returning().get()
39
- },
40
- })
41
-
42
- // PUT, DELETE follow the same pattern
43
- export const PUT = defineRoute({ policy: 'public', body: z.object({...}), handler: ({body, params}) => {...} })
44
- export const DELETE = defineRoute({ policy: 'public', params: z.object({id: z.coerce.number()}), handler: ({params}) => {...} })
39
+ })
40
+ .build()
45
41
  ```
46
42
 
43
+ The chain is `.policy()`, `.params()`, `.query()`, `.body()`, `.status()`, `.response()`,
44
+ `.csrf()`, `.handler()`, and `.build()` closes it. `server/routes/health.ts` in this project is a
45
+ working one — read it rather than this block if the two ever disagree.
46
+
47
+ `.csrf(false)` opts a single route out of CSRF enforcement. It is for endpoints that legitimately
48
+ receive third-party POSTs — a Stripe or WhatsApp webhook, an OAuth callback — which authenticate by
49
+ signature rather than by session. `policy('public')` answers a different question (may an
50
+ unauthenticated caller reach this) and does NOT lift the CSRF gate: without `.csrf(false)` a webhook
51
+ is refused `CSRF_INVALID` before its signature is ever checked.
52
+
47
53
  ## policy — who may call this route
48
54
 
49
55
  Required on every exported method. The scanner refuses a route file that omits it and names the
@@ -68,40 +74,40 @@ headers and no cookies: identity arrives as `subject`, established by the transp
68
74
  | `server/routes/tasks/[id].ts` | `/api/tasks/:id` | Dynamic param |
69
75
  | `server/routes/users/[...slug].ts` | `/api/users/*` | Catch-all |
70
76
 
71
- ## defineAction (Server Actions)
77
+ ## `action()` (Server Actions)
72
78
 
73
79
  ```typescript
74
- import { defineAction } from 'theokit/server/define'
80
+ import { action } from 'theokit/server/define'
75
81
  import { z } from 'zod'
76
82
 
77
- export const createTask = defineAction({
78
- input: z.object({ title: z.string() }),
79
- handler: ({ input }) => {
80
- return db.insert(tasks).values(input).returning().get()
81
- },
82
- })
83
+ export const createTask = action()
84
+ .input(z.object({ title: z.string() }))
85
+ .handler(({ input }) => db.insert(tasks).values(input).returning().get())
86
+ .build()
83
87
  ```
84
88
 
89
+ The chain is `.input()`, `.accept()`, `.csrf()`, `.handler()`, and `.build()`.
90
+
85
91
  ## Error Handling
86
92
 
87
93
  ```typescript
88
- import { TheoError } from 'theokit'
94
+ import { TheoError } from 'theokit/server/http'
89
95
 
90
- export const GET = defineRoute({
91
- policy: 'public',
92
- handler: ({ params }) => {
96
+ export const GET = route()
97
+ .policy('public')
98
+ .handler(({ params }) => {
93
99
  const task = db.select().from(tasks).where(eq(tasks.id, params.id)).get()
94
100
  if (!task) throw new TheoError({ code: 'NOT_FOUND', message: 'Task not found' })
95
101
  return task
96
- },
97
- })
102
+ })
103
+ .build()
98
104
  ```
99
105
 
100
106
  Valid error codes: `BAD_REQUEST` (400), `UNAUTHORIZED` (401), `FORBIDDEN` (403), `NOT_FOUND` (404), `CONFLICT` (409), `UNPROCESSABLE_ENTITY` (422), `TOO_MANY_REQUESTS` (429), `INTERNAL_SERVER_ERROR` (500).
101
107
 
102
108
  ## Anti-patterns
103
109
 
104
- - NEVER use `res.status().json()` — use defineRoute with `status:` option
105
- - NEVER parse `req.body` manually — use `body: z.object(...)` in defineRoute
110
+ - NEVER use `res.status().json()` — use `.status(201)` on the `route()` chain
111
+ - NEVER parse `req.body` manually — use `.body(z.object({ … }))` on the chain
106
112
  - NEVER create routes outside `server/routes/` — they won't be discovered
107
113
  - NEVER export non-HTTP-method names — only `GET`, `POST`, `PUT`, `DELETE`, `PATCH`