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.
@@ -1,68 +1,288 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useMemo, useState } from 'react'
4
+ import {
5
+ ChatThread,
6
+ ChatMessage,
7
+ ChatComposer,
8
+ ToolCallCard,
9
+ AgentStreaming,
10
+ AgentErrorCard,
11
+ EmptyState,
12
+ QuickActionChips,
13
+ ContextWindowBar,
14
+ CommandPalette,
15
+ Avatar,
16
+ Tooltip,
17
+ Button,
18
+ ScrollArea,
19
+ type Message,
20
+ type QuickAction,
21
+ type CommandItem,
22
+ type ToolCallStatus,
23
+ } from '@theokit/ui'
24
+ import { Sparkles, Wrench, RotateCcw, Command } from 'lucide-react'
25
+ import { useAgentStream } from 'theokit/client'
26
+
27
+ /**
28
+ * Default scaffold — an Agent Surface, composed entirely from TheoUI.
29
+ *
30
+ * ChatThread / ChatMessage → conversation
31
+ * ToolCallCard → expandable tool invocations
32
+ * AgentStreaming → streaming indicator
33
+ * AgentErrorCard → error display
34
+ * ChatComposer → bottom input bar
35
+ * EmptyState → first-load screen
36
+ * ContextWindowBar → context usage at top
37
+ * CommandPalette → ⌘K quick actions
38
+ * Avatar → assistant face in messages
39
+ * Tooltip → hints on icons
40
+ *
41
+ * `useAgentStream` handles SSE consumption, AbortController cleanup, and
42
+ * StrictMode safety. Replace the mock at server/routes/chat.ts with your
43
+ * real LLM provider (OpenAI / Anthropic / local).
44
+ */
45
+
46
+ type ConversationItem =
47
+ | { kind: 'message'; id: string; role: 'user' | 'assistant'; content: string; timestamp: string }
48
+ | {
49
+ kind: 'tool'
50
+ id: string
51
+ tool: string
52
+ target?: string
53
+ status: ToolCallStatus
54
+ output?: string
55
+ timestamp: string
56
+ }
57
+ | { kind: 'error'; id: string; message: string; timestamp: string }
58
+
59
+ const QUICK_ACTIONS: QuickAction[] = [
60
+ { id: 'summarize', label: 'Summarize this page', icon: Sparkles },
61
+ { id: 'tools', label: 'Show available tools', icon: Wrench },
62
+ { id: 'reset', label: 'Start a new conversation', icon: RotateCcw },
63
+ ]
64
+
65
+ const COMMAND_ITEMS: CommandItem[] = QUICK_ACTIONS.map((a) => ({
66
+ id: a.id,
67
+ label: a.label,
68
+ icon: a.icon,
69
+ group: 'Quick actions',
70
+ }))
71
+
72
+ // Mock context-window usage — replace with real model state.
73
+ const CONTEXT_USED = 4_200
74
+ const CONTEXT_TOTAL = 200_000
75
+ const MODEL_NAME = 'mock-llm'
76
+
77
+ const ASSISTANT_AVATAR = (
78
+ <Avatar size="sm" tone="primary">
79
+ <Avatar.Fallback>TH</Avatar.Fallback>
80
+ </Avatar>
81
+ )
82
+ const USER_AVATAR = (
83
+ <Avatar size="sm" tone="muted">
84
+ <Avatar.Fallback>YOU</Avatar.Fallback>
85
+ </Avatar>
86
+ )
87
+
1
88
  export default function Page() {
89
+ const [composerValue, setComposerValue] = useState('')
90
+ const [userMessages, setUserMessages] = useState<ConversationItem[]>([])
91
+ const [paletteOpen, setPaletteOpen] = useState(false)
92
+ const { events, send, status, reset } = useAgentStream<{ message: string }>('/api/chat')
93
+
94
+ // ⌘K / Ctrl+K opens the CommandPalette.
95
+ useEffect(() => {
96
+ function onKey(e: KeyboardEvent) {
97
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
98
+ e.preventDefault()
99
+ setPaletteOpen((v) => !v)
100
+ }
101
+ }
102
+ window.addEventListener('keydown', onKey)
103
+ return () => window.removeEventListener('keydown', onKey)
104
+ }, [])
105
+
106
+ const items = useMemo<ConversationItem[]>(() => {
107
+ const ts = new Date().toISOString()
108
+ const agentItems: ConversationItem[] = events.map((event, i) => {
109
+ const id = `e-${i}`
110
+ switch (event.type) {
111
+ case 'message':
112
+ return { kind: 'message', id, role: 'assistant', content: event.content, timestamp: ts }
113
+ case 'tool_call':
114
+ return {
115
+ kind: 'tool',
116
+ id,
117
+ tool: event.name,
118
+ target:
119
+ typeof event.args === 'object' && event.args !== null
120
+ ? Object.entries(event.args as Record<string, unknown>)
121
+ .map(([k, v]) => `${k}=${JSON.stringify(v)}`)
122
+ .join(' ')
123
+ : undefined,
124
+ status: 'running',
125
+ timestamp: ts,
126
+ }
127
+ case 'tool_result':
128
+ return {
129
+ kind: 'tool',
130
+ id,
131
+ tool: event.name,
132
+ status: 'success',
133
+ output:
134
+ typeof event.data === 'string' ? event.data : JSON.stringify(event.data, null, 2),
135
+ timestamp: ts,
136
+ }
137
+ case 'error':
138
+ return { kind: 'error', id, message: event.message, timestamp: ts }
139
+ }
140
+ })
141
+ return [...userMessages, ...agentItems]
142
+ }, [userMessages, events])
143
+
144
+ function handleSubmit(value: string) {
145
+ const trimmed = value.trim()
146
+ if (!trimmed) return
147
+ const id = `u-${userMessages.length}`
148
+ setUserMessages((prev) => [
149
+ ...prev,
150
+ { kind: 'message', id, role: 'user', content: trimmed, timestamp: new Date().toISOString() },
151
+ ])
152
+ send({ message: trimmed })
153
+ setComposerValue('')
154
+ }
155
+
156
+ function handleQuickAction(id: string) {
157
+ setPaletteOpen(false)
158
+ if (id === 'reset') {
159
+ setUserMessages([])
160
+ reset()
161
+ return
162
+ }
163
+ const action = QUICK_ACTIONS.find((a) => a.id === id)
164
+ if (action) handleSubmit(action.label)
165
+ }
166
+
167
+ const isStreaming = status === 'streaming'
168
+ const isEmpty = items.length === 0 && !isStreaming
169
+ const hasError = status === 'error'
170
+
2
171
  return (
3
- <div className="page">
4
- <div className="main">
5
- <header className="hero">
6
- <img src="/logo.png" alt="TheoKit" width={72} height={72} className="hero-logo" />
7
- <h1>TheoKit</h1>
8
- <p className="tagline">Build the app your agent lives in.</p>
9
- <nav className="ctas">
10
- <a
11
- href="https://usetheo.dev"
12
- target="_blank"
13
- rel="noopener noreferrer"
14
- className="btn primary"
15
- >
16
- Get Started
17
- </a>
18
- <a
19
- href="https://github.com/usetheodev/theokit"
20
- target="_blank"
21
- rel="noopener noreferrer"
22
- className="btn secondary"
23
- >
24
- Documentation
25
- </a>
26
- </nav>
27
- <p className="hint">
28
- Edit <code>app/page.tsx</code> to get started. Changes hot-reload instantly.
29
- </p>
30
- </header>
31
-
32
- <div className="grid features">
33
- <div className="feature">
34
- <h3>defineRoute</h3>
35
- <p>
36
- Typed API routes with Zod validation. See <code>server/routes/</code>
37
- </p>
38
- </div>
39
- <div className="feature">
40
- <h3>Drizzle + SQLite</h3>
41
- <p>
42
- Type-safe database with zero config. Schema in <code>server/db/schema.ts</code>
43
- </p>
44
- </div>
45
- <div className="feature">
46
- <h3>@Agent + @Tool</h3>
47
- <p>AI agents with SSE streaming, budget control, and human-in-the-loop approval.</p>
48
- </div>
49
- <div className="feature">
50
- <h3>React + Vite</h3>
51
- <p>File-based routing, HMR, SSR streaming. Edit and see changes instantly.</p>
52
- </div>
172
+ <>
173
+ <ContextWindowBar
174
+ used={CONTEXT_USED}
175
+ total={CONTEXT_TOTAL}
176
+ trailing={MODEL_NAME}
177
+ label="Context window"
178
+ compact
179
+ className="border-border/60 border-b px-6 py-2"
180
+ />
181
+
182
+ <ScrollArea className="flex-1">
183
+ <div className="mx-auto flex w-full max-w-3xl flex-col gap-4 px-6 py-6">
184
+ {isEmpty ? (
185
+ <EmptyState
186
+ eyebrow="Theo Agent"
187
+ icon={Sparkles}
188
+ title="What should we build today?"
189
+ description="Ask anything. This scaffold ships with a mock LLM at server/routes/chat.ts so you can see the wiring before plugging in a real model."
190
+ action={<QuickActionChips actions={QUICK_ACTIONS} onSelect={handleQuickAction} />}
191
+ />
192
+ ) : (
193
+ <ChatThread>
194
+ {items.map((item) => {
195
+ if (item.kind === 'message') {
196
+ const message: Message = {
197
+ id: item.id,
198
+ role: item.role,
199
+ content: item.content,
200
+ timestamp: item.timestamp,
201
+ model: item.role === 'assistant' ? MODEL_NAME : undefined,
202
+ }
203
+ return (
204
+ <ChatMessage
205
+ key={item.id}
206
+ message={message}
207
+ avatar={item.role === 'assistant' ? ASSISTANT_AVATAR : USER_AVATAR}
208
+ />
209
+ )
210
+ }
211
+ if (item.kind === 'tool') {
212
+ return (
213
+ <ToolCallCard
214
+ key={item.id}
215
+ tool={item.tool}
216
+ icon={Wrench}
217
+ target={item.target}
218
+ status={item.status}
219
+ output={item.output}
220
+ timestamp={item.timestamp}
221
+ />
222
+ )
223
+ }
224
+ return (
225
+ <AgentErrorCard
226
+ key={item.id}
227
+ kind="model"
228
+ title="Agent error"
229
+ description={item.message}
230
+ />
231
+ )
232
+ })}
233
+ {isStreaming && <AgentStreaming model={MODEL_NAME} />}
234
+ </ChatThread>
235
+ )}
53
236
  </div>
237
+ </ScrollArea>
54
238
 
55
- <footer className="footer">
56
- Powered by{' '}
57
- <a href="https://usetheo.dev" target="_blank" rel="noopener noreferrer">
58
- TheoKit
59
- </a>
60
- {' · '}
61
- <a href="https://github.com/usetheodev/theokit" target="_blank" rel="noopener noreferrer">
62
- GitHub
63
- </a>
64
- </footer>
239
+ <div className="border-border/60 border-t bg-background/50 backdrop-blur">
240
+ <div className="mx-auto w-full max-w-3xl px-6 py-4">
241
+ {hasError && (
242
+ <div className="mb-3">
243
+ <AgentErrorCard
244
+ kind="network"
245
+ title="Stream ended with an error"
246
+ description="The connection to the agent endpoint was interrupted. Reset to try again."
247
+ action={
248
+ <Button variant="ghost" size="sm" onClick={() => reset()}>
249
+ Reset
250
+ </Button>
251
+ }
252
+ />
253
+ </div>
254
+ )}
255
+ <ChatComposer
256
+ value={composerValue}
257
+ onValueChange={setComposerValue}
258
+ onSubmit={handleSubmit}
259
+ running={isStreaming}
260
+ placeholder="Ask the agent…"
261
+ leadingActions={
262
+ <Tooltip label="Open command palette (⌘K)" side="top">
263
+ <Button
264
+ type="button"
265
+ variant="ghost"
266
+ size="icon"
267
+ onClick={() => setPaletteOpen(true)}
268
+ aria-label="Open command palette"
269
+ >
270
+ <Command className="size-4" />
271
+ </Button>
272
+ </Tooltip>
273
+ }
274
+ />
275
+ </div>
65
276
  </div>
66
- </div>
277
+
278
+ <CommandPalette
279
+ open={paletteOpen}
280
+ onOpenChange={setPaletteOpen}
281
+ items={COMMAND_ITEMS}
282
+ onSelect={handleQuickAction}
283
+ placeholder="Run a command…"
284
+ emptyMessage="No matching commands."
285
+ />
286
+ </>
67
287
  )
68
288
  }
@@ -114,6 +114,19 @@ for await (const event of stream) {
114
114
  }
115
115
  ```
116
116
 
117
+ ## SDK Ecosystem — "you are here" map
118
+
119
+ Before writing custom tools, check if they already exist:
120
+
121
+ | Package | What it provides | When to use |
122
+ |---------|-----------------|-------------|
123
+ | `@theokit/sdk` | `Agent.create()`, `defineTool()` (primitive), `Run.stream()` | Core agent runtime — always installed |
124
+ | `@theokit/sdk-tools` | Ready-made tools: `createReadFileTool`, `createWriteFileTool`, `createSearchTool`, etc. | **Check here FIRST** before writing custom tools for coding agents |
125
+ | `@theokit/di-agent` | DI-powered agent with decorator injection | When using dependency injection pattern |
126
+ | `@theokit/di` | Core DI container (`@Injectable`, `@Inject`) | When `@theokit/di-agent` needs explicit bindings |
127
+
128
+ **`defineTool()` in `@theokit/sdk` is the primitive API.** For coding agents, `@theokit/sdk-tools` has batteries-included tools that wrap `defineTool()` with file system access, search, shell execution, etc. Don't reimplement what `sdk-tools` already provides.
129
+
117
130
  ## Rules
118
131
 
119
132
  - Tool `name` and `description` are ALWAYS explicit — never inferred from method names (G4)
@@ -122,11 +135,13 @@ for await (const event of stream) {
122
135
  - `@UseInterceptors()` and `@UseFilters()` on agents are metadata-only (emit warnings)
123
136
  - Agent runtime is `@theokit/sdk` — NEVER call LLM APIs directly via fetch
124
137
  - Pick ONE surface per endpoint — don't mix defineAgentEndpoint with @Agent for the same route
138
+ - Check `@theokit/sdk-tools` BEFORE writing custom tools — it may already exist
125
139
 
126
140
  ## Anti-patterns
127
141
 
128
142
  - NEVER call OpenAI/Anthropic/OpenRouter APIs directly — use @Agent or defineAgentEndpoint
129
143
  - NEVER reimplement tool calling loop — the SDK handles it
144
+ - NEVER reimplement file/search/shell tools — use `@theokit/sdk-tools` (readFile, writeFile, search, etc.)
130
145
  - NEVER store conversations manually — use @Memory (decorator) or SDK persistence
131
146
  - NEVER infer tool capability from method name — always provide explicit `name` + `description`
132
147
  - NEVER mix both surfaces for the same endpoint — pick manual OR decorator
@@ -1,18 +1,14 @@
1
1
  import tseslint from 'typescript-eslint'
2
2
  import prettierConfig from 'eslint-config-prettier'
3
- import drizzle from 'eslint-plugin-drizzle'
4
3
 
5
4
  export default tseslint.config(
6
- { ignores: ['dist/', 'node_modules/', 'drizzle/'] },
5
+ { ignores: ['dist/', 'node_modules/'] },
7
6
  ...tseslint.configs.recommended,
8
7
  prettierConfig,
9
8
  {
10
- plugins: { drizzle },
11
9
  rules: {
12
10
  '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
13
11
  '@typescript-eslint/no-explicit-any': 'warn',
14
- 'drizzle/enforce-delete-with-where': 'error',
15
- 'drizzle/enforce-update-with-where': 'error',
16
12
  },
17
13
  },
18
14
  )
@@ -8,8 +8,6 @@
8
8
  "build": "theokit build",
9
9
  "start": "theokit start",
10
10
  "test": "vitest run",
11
- "db:migrate": "theokit db migrate",
12
- "db:generate": "theokit db generate",
13
11
  "lint": "eslint .",
14
12
  "lint:fix": "eslint . --fix",
15
13
  "format": "prettier --write .",
@@ -17,25 +15,28 @@
17
15
  "typecheck": "tsc --noEmit"
18
16
  },
19
17
  "dependencies": {
20
- "theokit": "^0.5.4",
18
+ "theokit": "^0.6.0",
19
+ "@theokit/sdk": "^1.1.0",
20
+ "@theokit/ui": "^0.14.0",
21
+ "lucide-react": "^0.469.0",
21
22
  "react": "^19.0.0",
22
23
  "react-dom": "^19.0.0",
23
24
  "react-router": "^7.0.0",
24
- "zod": "^4.0.0",
25
- "drizzle-orm": "^0.44.0",
26
- "better-sqlite3": "^12.0.0"
25
+ "zod": "^4.0.0"
27
26
  },
28
27
  "devDependencies": {
29
- "@types/better-sqlite3": "^7.6.0",
30
28
  "@types/react": "^19.0.0",
31
29
  "@types/react-dom": "^19.0.0",
32
- "drizzle-kit": "^0.30.0",
30
+ "tailwindcss": "^4.0.0",
31
+ "@tailwindcss/vite": "^4.0.0",
33
32
  "eslint": "^9.0.0",
34
33
  "eslint-config-prettier": "^10.0.0",
35
- "eslint-plugin-drizzle": "^0.2.0",
36
34
  "prettier": "^3.0.0",
37
35
  "typescript": "^5.5.0",
38
36
  "typescript-eslint": "^8.0.0",
39
37
  "vitest": "^3.0.0"
38
+ },
39
+ "pnpm": {
40
+ "onlyBuiltDependencies": ["esbuild", "better-sqlite3", "workerd"]
40
41
  }
41
42
  }
@@ -0,0 +1,69 @@
1
+ import { z } from 'zod'
2
+ import {
3
+ defineAgentEndpoint,
4
+ defineAgentTool,
5
+ streamAgentRun,
6
+ createConversationHistory,
7
+ type AgentEvent,
8
+ } from 'theokit/server'
9
+
10
+ /**
11
+ * Chat agent endpoint — persistent conversation via createConversationHistory.
12
+ *
13
+ * Each browser tab gets a stable conversation id cookie on first visit;
14
+ * subsequent requests resume the same agent. Conversation turns auto-persist
15
+ * in `<cwd>/.theokit/agents/<conversationId>/messages.jsonl` (SDK owns
16
+ * storage). Tools: current_time example. Memory facts: opt-in via
17
+ * options.memory (off by default).
18
+ *
19
+ * Provider: OPENROUTER_API_KEY (preferred — gateway to many models) OR
20
+ * ANTHROPIC_API_KEY (direct Anthropic).
21
+ */
22
+
23
+ const currentTime = defineAgentTool({
24
+ name: 'current_time',
25
+ description: 'Get the current ISO timestamp on the server.',
26
+ inputSchema: z.object({}),
27
+ handler: () => new Date().toISOString(),
28
+ })
29
+
30
+ export const POST = defineAgentEndpoint({
31
+ async *handler({ body, request, cookieHeaders, signal }): AsyncGenerator<AgentEvent> {
32
+ const safeBody =
33
+ body !== null && typeof body === 'object' && !Array.isArray(body)
34
+ ? (body as { message?: string })
35
+ : {}
36
+ const { message = '' } = safeBody
37
+ // Provider resolution centralizada (Strategy pattern) — theokit/server resolve
38
+ // apiKey + baseUrl + provider automático via OPENROUTER_API_KEY / OPENAI_API_KEY /
39
+ // ANTHROPIC_API_KEY presente no env. Wire protocol: OpenAI Chat Completions
40
+ // (universal — todos os providers implementam essa API). Consumer NÃO tem
41
+ // conditionals sobre provider — é responsabilidade do framework.
42
+ // Wrap full agent lifecycle in try/catch — provider errors (invalid KEY,
43
+ // 401, rate-limit, model-not-found, 5xx) MUST surface as AgentEvent
44
+ // 'error' so the client renders an actionable message instead of a
45
+ // silent SSE closure. Dogfood chaos Phase 12 validates this contract.
46
+ try {
47
+ const { agent } = await createConversationHistory({
48
+ request,
49
+ response: { headers: cookieHeaders },
50
+ options: {
51
+ // Model id is prefixed with the provider namespace. When using
52
+ // OPENROUTER_API_KEY (default), prefixes route to the correct
53
+ // upstream — `openai/`, `anthropic/`, `google/`, `meta-llama/`,
54
+ // `mistralai/`, `groq/`, etc. See https://openrouter.ai/models.
55
+ // Without the prefix the SDK falls back to a stub response.
56
+ model: { id: 'openai/gpt-4o-mini' },
57
+ tools: [currentTime],
58
+ },
59
+ })
60
+ const run = await agent.send(message, { signal })
61
+ yield* streamAgentRun(run)
62
+ // Intentionally NO agent.dispose() — the agent stays registered so the
63
+ // next request from the same conversation resumes it (continuity).
64
+ } catch (err) {
65
+ const msg = err instanceof Error ? err.message : String(err)
66
+ yield { type: 'error', message: `Agent error: ${msg}` }
67
+ }
68
+ },
69
+ })
@@ -1,5 +1,3 @@
1
1
  import { defineConfig } from 'theokit'
2
2
 
3
- export default defineConfig({
4
- port: 3000,
5
- })
3
+ export default defineConfig({})
@@ -1,22 +1,15 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "experimentalDecorators": true,
7
- "emitDecoratorMetadata": true,
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
8
6
  "strict": true,
9
- "jsx": "react-jsx",
7
+ "noEmit": true,
10
8
  "esModuleInterop": true,
11
9
  "skipLibCheck": true,
12
- "outDir": "dist",
13
- "baseUrl": ".",
14
- "paths": {
15
- "@/*": ["./*"],
16
- "@/server/*": ["./server/*"],
17
- "@/app/*": ["./app/*"]
18
- }
10
+ "jsx": "react-jsx",
11
+ "isolatedModules": true,
12
+ "resolveJsonModule": true
19
13
  },
20
- "include": ["**/*.ts", "**/*.tsx"],
21
- "exclude": ["node_modules", "dist"]
14
+ "include": ["app/**/*.ts", "app/**/*.tsx", "server/**/*.ts"]
22
15
  }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * JobRegistry augmentation — REQUIRED for typed `ctx.queue.enqueue` calls.
3
+ *
4
+ * Without this augmentation, `ctx.queue.enqueue('foo', ...)` errors with:
5
+ * "Type 'foo' is not assignable to type 'never'"
6
+ *
7
+ * This is the canonical TheoKit jobs onboarding bug (EC-110). To add a
8
+ * job:
9
+ *
10
+ * 1. Create `server/jobs/<name>.ts` exporting `defineJob('<name>', ...)`
11
+ * 2. Add `'<name>': { ...inputShape }` below
12
+ * 3. Use `ctx.queue.enqueue('<name>', { ...input })` from any route handler
13
+ *
14
+ * See: docs/concepts/jobs.md
15
+ */
16
+ declare module 'theokit/server' {
17
+ interface JobRegistry {
18
+ // Add your jobs here. Examples (uncomment and customize):
19
+ //
20
+ // 'process-document': { documentId: string }
21
+ // 'send-email': { to: string; subject: string; body: string }
22
+ }
23
+ }
24
+
25
+ export {}
@@ -0,0 +1,20 @@
1
+ FROM node:22-alpine
2
+
3
+ WORKDIR /app
4
+
5
+ # Install pnpm (or use npm if pnpm not available in image)
6
+ RUN corepack enable && corepack prepare pnpm@latest --activate || true
7
+
8
+ # Copy manifest and install
9
+ COPY package.json ./
10
+ RUN pnpm install --frozen-lockfile || npm install
11
+
12
+ # Copy source
13
+ COPY . .
14
+
15
+ EXPOSE 8002
16
+
17
+ HEALTHCHECK --interval=10s --timeout=5s --retries=3 \
18
+ CMD wget --spider -q http://localhost:8002/health || exit 1
19
+
20
+ CMD ["pnpm", "start"]
@@ -0,0 +1,38 @@
1
+ # Agent service (Node / Hono)
2
+
3
+ This is a TheoKit polyglot sidecar generated by `create-theokit --backend node`.
4
+
5
+ ## Requirements
6
+
7
+ - **Node.js 22+** (matches TheoKit's floor)
8
+ - **pnpm** or **npm** (TheoKit's CLI prefers pnpm)
9
+
10
+ ## Run it
11
+
12
+ The TheoKit app boots this service automatically via `pnpm dev` (per `theo.config.ts > services`).
13
+
14
+ To run standalone:
15
+
16
+ ```bash
17
+ cd services/agent-node
18
+ pnpm install
19
+ pnpm dev
20
+ ```
21
+
22
+ ## Endpoints
23
+
24
+ - `GET /health` — healthcheck (returns `{"status":"ok"}`)
25
+ - `POST /echo` — example: `{ "message": "..." }` → `{ "echo": "..." }`
26
+
27
+ ## What is wired
28
+
29
+ - Native fetch handler via Hono (works on TheoCloud and local)
30
+ - JSON-line stdout logs
31
+ - W3C `traceparent` propagation from incoming headers
32
+ - TheoKit injects `THEOKIT_SERVICE_NAME` + `THEOKIT_SERVICE_PORT` env vars
33
+
34
+ ## Add a tool the TheoKit agent can call
35
+
36
+ Add another `app.post('/tool/<name>', ...)` handler. From the TheoKit TS side,
37
+ `services.worker.<name>({...})` is auto-typed when an OpenAPI URL is reachable
38
+ (Hey API integration — Phase 5).
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "{{name}}-agent-node",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "tsx watch src/index.ts",
7
+ "start": "tsx src/index.ts",
8
+ "build": "echo 'no build step; tsx runs TS directly'"
9
+ },
10
+ "dependencies": {
11
+ "hono": "^4.6.0",
12
+ "@hono/node-server": "^1.13.0"
13
+ },
14
+ "devDependencies": {
15
+ "tsx": "^4.19.0",
16
+ "typescript": "^5.7.0"
17
+ }
18
+ }