create-theokit 1.0.13 → 1.0.15

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.
@@ -0,0 +1,58 @@
1
+ /**
2
+ * TheoKit polyglot agent service — Hono sidecar.
3
+ *
4
+ * Conforms to the Like-Vercel runtime contract (ADR-0015):
5
+ * - Fetch-handler entry (native fetch handler from Hono)
6
+ * - GET /health convention (200 / 503)
7
+ * - JSON-line stdout logs
8
+ * - W3C traceparent propagation
9
+ * - Env vars at runtime
10
+ *
11
+ * Generated by `create-theokit --backend node`.
12
+ */
13
+ import { Hono } from 'hono'
14
+ import { serve } from '@hono/node-server'
15
+
16
+ const SERVICE_NAME = process.env.THEOKIT_SERVICE_NAME ?? 'agent-node'
17
+ const SERVICE_PORT = Number.parseInt(process.env.THEOKIT_SERVICE_PORT ?? '8002', 10)
18
+
19
+ function log(
20
+ level: 'info' | 'warn' | 'error',
21
+ message: string,
22
+ extra: Record<string, unknown> = {},
23
+ ) {
24
+ // eslint-disable-next-line no-console -- structured stdout per ADR-0015 invariant #5
25
+ console.log(
26
+ JSON.stringify({
27
+ timestamp: new Date().toISOString(),
28
+ level,
29
+ message,
30
+ service: SERVICE_NAME,
31
+ ...extra,
32
+ }),
33
+ )
34
+ }
35
+
36
+ const app = new Hono()
37
+
38
+ // ADR-0015 invariant #6 — W3C traceparent propagation
39
+ app.use(async (c, next) => {
40
+ const tp = c.req.header('traceparent')
41
+ if (tp) {
42
+ log('info', 'request', { traceparent: tp, path: c.req.path })
43
+ }
44
+ await next()
45
+ })
46
+
47
+ // ADR-0015 invariant #4 — healthcheck convention
48
+ app.get('/health', (c) => c.json({ status: 'ok' }))
49
+
50
+ // Example endpoint — TheoKit proxies /api/worker/* to this service.
51
+ app.post('/echo', async (c) => {
52
+ const body = (await c.req.json()) as { message: string }
53
+ return c.json({ echo: body.message })
54
+ })
55
+
56
+ serve({ fetch: app.fetch, port: SERVICE_PORT }, () => {
57
+ log('info', `agent-node listening on :${String(SERVICE_PORT)}`)
58
+ })
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "lib": ["ES2022"],
10
+ "types": ["node"]
11
+ },
12
+ "include": ["src/**/*.ts"]
13
+ }
@@ -1,91 +0,0 @@
1
- # AGENTS.md — TheoKit App
2
-
3
- Guide for coding agents (Claude, Copilot, Cursor) working on this TheoKit project.
4
-
5
- ## Architecture
6
-
7
- This is a **full-stack TypeScript app** built with TheoKit — a framework for AI agent apps.
8
-
9
- ```
10
- server/
11
- routes/ → HTTP API routes (defineRoute + Zod validation)
12
- health.ts → GET /api/health
13
- db/
14
- schema.ts → Drizzle ORM schema (SQLite) — empty, add your tables
15
- index.ts → DB connection (better-sqlite3, WAL mode)
16
- app/
17
- page.tsx → React frontend
18
- layout.tsx → Root layout
19
- tests/
20
- tasks.test.ts → Example unit test
21
- ```
22
-
23
- ## Key Patterns
24
-
25
- ### Routes (defineRoute)
26
- ```typescript
27
- import { defineRoute } from 'theokit/server/define'
28
- import { z } from 'zod'
29
-
30
- export const GET = defineRoute({
31
- handler: () => db.select().from(posts).all(),
32
- })
33
-
34
- export const POST = defineRoute({
35
- body: z.object({ title: z.string().min(3) }),
36
- status: 201,
37
- handler: ({ body }) => db.insert(posts).values(body).returning().get(),
38
- })
39
- ```
40
-
41
- ### Database (Drizzle + SQLite)
42
- ```typescript
43
- import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
44
-
45
- export const posts = sqliteTable('posts', {
46
- id: integer('id').primaryKey({ autoIncrement: true }),
47
- title: text('title').notNull(),
48
- published: integer('published', { mode: 'boolean' }).notNull().default(false),
49
- })
50
- ```
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
-
58
- ### Validation
59
- - **Zod is the single source of truth** — define schema once, get types + validation + OpenAPI
60
- - `body: z.object(...)` in defineRoute validates automatically, returns 422 on failure
61
- - Use `z.infer<typeof schema>` for TypeScript types
62
-
63
- ### Dynamic Routes
64
- - `server/routes/posts/[id].ts` → `/api/posts/:id`
65
- - Params validated with `params: z.object({ id: z.coerce.number() })`
66
-
67
- ### Path Aliases
68
- - `@/*` → project root (configured in tsconfig.json)
69
-
70
- ## Commands
71
-
72
- ```bash
73
- npm run dev # Start dev server
74
- npm run build # Build for production
75
- npm run start # Run production build
76
- npm run test # Run tests (vitest)
77
- npm run lint # ESLint check
78
- npm run format # Prettier format
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)
83
- ```
84
-
85
- ## Don't
86
-
87
- - Don't use `any` — use Zod schemas + `z.infer<>`
88
- - Don't write raw `res.status().json()` — use defineRoute with status option
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,377 +0,0 @@
1
- /* TheoKit — design system */
2
-
3
- :root {
4
- --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
5
- --font-mono: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', Menlo, Consolas, monospace;
6
-
7
- --bg: #fafafa;
8
- --bg-content: #ffffff;
9
- --card: #ffffff;
10
- --border: #e5e5e5;
11
- --text: #171717;
12
- --text-secondary: #666666;
13
- --text-muted: #999999;
14
- --accent: #6366f1;
15
- --accent-hover: #4f46e5;
16
- --btn-primary-bg: #171717;
17
- --btn-primary-hover: #383838;
18
- --btn-secondary-hover: #f2f2f2;
19
- --btn-secondary-border: #ebebeb;
20
- --green: #22c55e;
21
- --red: #ef4444;
22
- --yellow: #eab308;
23
- }
24
-
25
- @media (prefers-color-scheme: dark) {
26
- :root {
27
- --bg: #0a0a0a;
28
- --bg-content: #0a0a0a;
29
- --card: #141414;
30
- --border: #2a2a2a;
31
- --text: #ededed;
32
- --text-secondary: #999999;
33
- --text-muted: #666666;
34
- --accent: #818cf8;
35
- --accent-hover: #6366f1;
36
- --btn-primary-bg: #ededed;
37
- --btn-primary-hover: #cccccc;
38
- --btn-secondary-hover: #1a1a1a;
39
- --btn-secondary-border: #2a2a2a;
40
- --green: #22c55e;
41
- --red: #ef4444;
42
- --yellow: #eab308;
43
- }
44
- html { color-scheme: dark; }
45
- }
46
-
47
- * { margin: 0; padding: 0; box-sizing: border-box; }
48
-
49
- html, body {
50
- max-width: 100vw;
51
- overflow-x: hidden;
52
- }
53
-
54
- body {
55
- font-family: var(--font-sans);
56
- background: var(--bg);
57
- color: var(--text);
58
- min-height: 100vh;
59
- display: flex;
60
- flex-direction: column;
61
- align-items: center;
62
- -webkit-font-smoothing: antialiased;
63
- -moz-osx-font-smoothing: grayscale;
64
- }
65
-
66
- a { color: inherit; text-decoration: none; }
67
- code, pre { font-family: var(--font-mono); }
68
-
69
- /* ─── Page container ────────────────────────────────── */
70
-
71
- .page {
72
- display: flex;
73
- flex: 1;
74
- flex-direction: column;
75
- align-items: center;
76
- width: 100%;
77
- }
78
-
79
- .main {
80
- display: flex;
81
- flex: 1;
82
- flex-direction: column;
83
- width: 100%;
84
- max-width: 900px;
85
- background: var(--bg-content);
86
- padding: 80px 48px 48px;
87
- }
88
-
89
- @media (max-width: 768px) {
90
- .main { padding: 48px 20px 32px; }
91
- }
92
-
93
- /* ─── Hero ──────────────────────────────────────────── */
94
-
95
- .hero {
96
- display: flex;
97
- flex-direction: column;
98
- align-items: center;
99
- text-align: center;
100
- gap: 16px;
101
- margin-bottom: 48px;
102
- }
103
-
104
- .hero-logo {
105
- border-radius: 16px;
106
- margin-bottom: 8px;
107
- }
108
-
109
- .hero h1 {
110
- font-size: 40px;
111
- font-weight: 700;
112
- letter-spacing: -2.4px;
113
- line-height: 48px;
114
- text-wrap: balance;
115
- }
116
-
117
- .hero .tagline {
118
- font-size: 18px;
119
- line-height: 28px;
120
- color: var(--text-secondary);
121
- max-width: 440px;
122
- text-wrap: balance;
123
- }
124
-
125
- .hero .hint {
126
- font-size: 14px;
127
- color: var(--text-muted);
128
- margin-top: 8px;
129
- }
130
-
131
- .hero .hint code {
132
- background: var(--card);
133
- border: 1px solid var(--border);
134
- padding: 2px 8px;
135
- border-radius: 6px;
136
- font-size: 13px;
137
- }
138
-
139
- @media (max-width: 768px) {
140
- .hero h1 { font-size: 32px; line-height: 40px; letter-spacing: -1.92px; }
141
- .hero .tagline { font-size: 16px; }
142
- }
143
-
144
- /* ─── CTA Buttons ───────────────────────────────────── */
145
-
146
- .ctas {
147
- display: flex;
148
- gap: 12px;
149
- margin-top: 8px;
150
- }
151
-
152
- .btn {
153
- display: inline-flex;
154
- justify-content: center;
155
- align-items: center;
156
- height: 40px;
157
- padding: 0 20px;
158
- border-radius: 128px;
159
- border: 1px solid transparent;
160
- font-size: 14px;
161
- font-weight: 500;
162
- cursor: pointer;
163
- transition: background 0.2s, border-color 0.2s;
164
- text-decoration: none;
165
- }
166
-
167
- .btn.primary {
168
- background: var(--btn-primary-bg);
169
- color: var(--bg);
170
- }
171
-
172
- .btn.secondary {
173
- border-color: var(--btn-secondary-border);
174
- color: var(--text);
175
- }
176
-
177
- @media (hover: hover) and (pointer: fine) {
178
- .btn.primary:hover { background: var(--btn-primary-hover); }
179
- .btn.secondary:hover { background: var(--btn-secondary-hover); border-color: transparent; }
180
- }
181
-
182
- /* ─── Role selector ─────────────────────────────────── */
183
-
184
- .role-bar {
185
- display: flex;
186
- justify-content: center;
187
- align-items: center;
188
- gap: 8px;
189
- margin-bottom: 32px;
190
- font-size: 14px;
191
- color: var(--text-muted);
192
- }
193
-
194
- .role-bar select {
195
- padding: 6px 12px;
196
- background: var(--card);
197
- border: 1px solid var(--border);
198
- border-radius: 6px;
199
- color: var(--text);
200
- font-size: 13px;
201
- }
202
-
203
- /* ─── Grid ──────────────────────────────────────────── */
204
-
205
- .grid {
206
- display: grid;
207
- grid-template-columns: 1fr 1fr;
208
- gap: 24px;
209
- width: 100%;
210
- }
211
-
212
- @media (max-width: 768px) {
213
- .grid { grid-template-columns: 1fr; }
214
- }
215
-
216
- /* ─── Cards ─────────────────────────────────────────── */
217
-
218
- .card {
219
- background: var(--card);
220
- border: 1px solid var(--border);
221
- border-radius: 12px;
222
- padding: 24px;
223
- width: 100%;
224
- }
225
-
226
- .card h2 {
227
- font-size: 15px;
228
- font-weight: 600;
229
- margin-bottom: 16px;
230
- display: flex;
231
- align-items: center;
232
- gap: 8px;
233
- }
234
-
235
- /* ─── Badges ────────────────────────────────────────── */
236
-
237
- .badge {
238
- font-size: 11px;
239
- padding: 2px 10px;
240
- border-radius: 99px;
241
- font-weight: 500;
242
- background: color-mix(in srgb, var(--accent) 12%, transparent);
243
- color: var(--accent);
244
- }
245
-
246
- .badge-ai {
247
- background: color-mix(in srgb, var(--yellow) 12%, transparent);
248
- color: var(--yellow);
249
- }
250
-
251
- /* ─── Table ─────────────────────────────────────────── */
252
-
253
- table { width: 100%; border-collapse: collapse; font-size: 13px; }
254
- th {
255
- text-align: left; padding: 8px 6px;
256
- color: var(--text-muted); border-bottom: 1px solid var(--border);
257
- font-weight: 500; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em;
258
- }
259
- td { padding: 8px 6px; border-bottom: 1px solid var(--border); }
260
- tr.done td { opacity: 0.4; text-decoration: line-through; }
261
-
262
- .prio { font-size: 11px; padding: 2px 8px; border-radius: 99px; font-weight: 500; }
263
- .prio-high { background: color-mix(in srgb, var(--red) 12%, transparent); color: var(--red); }
264
- .prio-medium { background: color-mix(in srgb, var(--yellow) 12%, transparent); color: var(--yellow); }
265
- .prio-low { background: color-mix(in srgb, var(--green) 12%, transparent); color: var(--green); }
266
-
267
- /* ─── Forms ─────────────────────────────────────────── */
268
-
269
- .create-bar { display: flex; gap: 8px; margin-top: 16px; }
270
- .create-bar input {
271
- flex: 1; padding: 8px 12px;
272
- background: var(--bg); border: 1px solid var(--border);
273
- border-radius: 8px; color: var(--text); font-size: 13px;
274
- outline: none; transition: border-color 0.2s;
275
- }
276
- .create-bar input:focus { border-color: var(--accent); }
277
- .create-bar select {
278
- padding: 8px 10px; background: var(--bg);
279
- border: 1px solid var(--border); border-radius: 8px;
280
- color: var(--text); font-size: 13px;
281
- }
282
- .create-bar button, .chat-bar button {
283
- padding: 8px 16px; background: var(--accent); color: white;
284
- border: none; border-radius: 8px; cursor: pointer;
285
- font-weight: 600; font-size: 13px; transition: background 0.2s;
286
- }
287
- @media (hover: hover) and (pointer: fine) {
288
- .create-bar button:hover, .chat-bar button:hover { background: var(--accent-hover); }
289
- }
290
- .create-bar button:disabled, .chat-bar button:disabled { opacity: 0.4; cursor: not-allowed; }
291
- .error { color: var(--red); font-size: 13px; margin-top: 6px; }
292
-
293
- /* ─── Chat ──────────────────────────────────────────── */
294
-
295
- .chat-box {
296
- height: 320px; overflow-y: auto; padding: 14px;
297
- background: var(--bg); border: 1px solid var(--border);
298
- border-radius: 10px; margin-bottom: 12px;
299
- font-size: 13px; line-height: 1.6;
300
- }
301
-
302
- .msg { margin-bottom: 8px; padding: 8px 12px; border-radius: 8px; }
303
- .msg.user { background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--accent); }
304
- .msg.agent { background: var(--card); border: 1px solid var(--border); }
305
- .msg.tool { background: color-mix(in srgb, var(--yellow) 8%, transparent); color: var(--yellow); font-size: 12px; font-family: var(--font-mono); }
306
- .msg.system { color: var(--text-muted); font-size: 12px; font-style: italic; }
307
- .msg.error { color: var(--red); font-size: 13px; }
308
-
309
- .chat-bar { display: flex; gap: 8px; }
310
- .chat-bar input {
311
- flex: 1; padding: 10px 14px;
312
- background: var(--bg); border: 1px solid var(--border);
313
- border-radius: 10px; color: var(--text); font-size: 14px;
314
- outline: none; transition: border-color 0.2s;
315
- }
316
- .chat-bar input:focus { border-color: var(--accent); }
317
-
318
- /* ─── Features grid ─────────────────────────────────── */
319
-
320
- .features {
321
- margin-top: 32px;
322
- }
323
-
324
- .feature {
325
- padding: 20px;
326
- border: 1px solid var(--border);
327
- border-radius: 12px;
328
- background: var(--card);
329
- }
330
-
331
- .feature h3 {
332
- font-size: 14px;
333
- font-weight: 600;
334
- margin-bottom: 6px;
335
- font-family: var(--font-mono);
336
- color: var(--accent);
337
- }
338
-
339
- .feature p {
340
- font-size: 13px;
341
- line-height: 1.5;
342
- color: var(--text-secondary);
343
- }
344
-
345
- .feature code {
346
- background: var(--bg);
347
- border: 1px solid var(--border);
348
- padding: 1px 5px;
349
- border-radius: 4px;
350
- font-size: 12px;
351
- }
352
-
353
- /* ─── Footer ────────────────────────────────────────── */
354
-
355
- .footer {
356
- text-align: center;
357
- padding: 40px 24px 32px;
358
- font-size: 13px;
359
- color: var(--text-muted);
360
- }
361
-
362
- .footer a { color: var(--text-secondary); transition: color 0.2s; }
363
- .footer a:hover { color: var(--accent); }
364
-
365
- /* ─── Loading ───────────────────────────────────────── */
366
-
367
- .loading-spinner {
368
- width: 24px; height: 24px;
369
- border: 2px solid var(--border);
370
- border-top-color: var(--accent);
371
- border-radius: 50%;
372
- animation: spin 0.8s linear infinite;
373
- }
374
-
375
- @keyframes spin {
376
- to { transform: rotate(360deg); }
377
- }
@@ -1,15 +0,0 @@
1
- /**
2
- * TheoKit App — manual bootstrap (optional).
3
- *
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.
6
- *
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)
15
- */
@@ -1,10 +0,0 @@
1
- import { defineConfig } from 'drizzle-kit'
2
-
3
- export default defineConfig({
4
- schema: './server/db/schema.ts',
5
- out: './drizzle',
6
- dialect: 'sqlite',
7
- dbCredentials: {
8
- url: './data/dev.db',
9
- },
10
- })
@@ -1,10 +0,0 @@
1
- import { drizzle } from 'drizzle-orm/better-sqlite3'
2
- import Database from 'better-sqlite3'
3
- import { mkdirSync } from 'node:fs'
4
- import * as schema from './schema.js'
5
-
6
- mkdirSync('data', { recursive: true })
7
- const sqlite = new Database('data/dev.db')
8
- sqlite.pragma('journal_mode = WAL')
9
-
10
- export const db = drizzle(sqlite, { schema })
@@ -1,13 +0,0 @@
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,2 +0,0 @@
1
- // Routes are auto-discovered from server/routes/ by theokit dev.
2
- // No manual registration needed — convention over configuration.
@@ -1,18 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
-
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',
11
- }
12
-
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)
17
- })
18
- })