create-theokit 1.25.2 → 2.0.0-next.0
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/dist/cli.js +53 -54
- package/dist/cli.js.map +1 -1
- package/package.json +8 -1
- package/templates/_bot-preset/{agents → src/server/agents}/tools/publish.ts +1 -1
- package/templates/_bot-preset/{server → src/server}/crons/daily-research.ts +2 -2
- package/templates/default/AGENTS.md.tmpl +58 -0
- package/templates/default/CLAUDE.md +4 -4
- package/templates/default/README.md.tmpl +56 -30
- package/templates/default/docs/ARCHITECTURE.md +103 -58
- package/templates/default/docs/CUSTOMIZATION.md +10 -10
- package/templates/default/docs/ENVIRONMENT.md +6 -6
- package/templates/default/dot-claude/rules/theokit-conventions.md +3 -3
- package/templates/default/dot-claude/skills/theokit-agents/SKILL.md +7 -7
- package/templates/default/dot-claude/skills/theokit-config/SKILL.md +1 -1
- package/templates/default/dot-claude/skills/theokit-frontend/SKILL.md +1 -1
- package/templates/default/dot-claude/skills/theokit-gateways/SKILL.md +1 -1
- package/templates/default/dot-claude/skills/theokit-routes/SKILL.md +1 -1
- package/templates/default/dot-claude/skills/theokit-ui/SKILL.md +1 -1
- package/templates/default/dot-theokit/THEO.md +36 -0
- package/templates/default/dot-theokit/personalities/README.md +35 -0
- package/templates/default/dot-theokit/personalities/concise.md +14 -0
- package/templates/default/dot-theokit/personalities/teacher.md +17 -0
- package/templates/default/dot-theokit/rules/README.md +30 -0
- package/templates/default/dot-theokit/rules/server-routes.md +16 -0
- package/templates/default/package.json.tmpl +2 -2
- package/templates/default/{agents → src/server/agents}/chat.ts +6 -0
- package/templates/default/src/server/agents/hooks/tool-audit.ts +75 -0
- package/templates/default/theo.config.ts +18 -1
- package/templates/default/tsconfig.json +4 -5
- /package/templates/_bot-preset/{agents → src/server/agents}/lib/bot-scope.ts +0 -0
- /package/templates/_bot-preset/{agents → src/server/agents}/lib/sandbox.ts +0 -0
- /package/templates/_bot-preset/{agents → src/server/agents}/publisher.ts +0 -0
- /package/templates/_bot-preset/{agents → src/server/agents}/researcher.ts +0 -0
- /package/templates/_bot-preset/{agents → src/server/agents}/tools/read-notes.ts +0 -0
- /package/templates/_bot-preset/{agents → src/server/agents}/tools/write-note.ts +0 -0
- /package/templates/_bot-preset/{server → src/server}/delivery.ts +0 -0
- /package/templates/default/{app → src/app}/about/page.tsx +0 -0
- /package/templates/default/{app → src/app}/components/ChatPanel.tsx +0 -0
- /package/templates/default/{app → src/app}/components/Composer.tsx +0 -0
- /package/templates/default/{app → src/app}/components/Header.tsx +0 -0
- /package/templates/default/{app → src/app}/components/Nav.tsx +0 -0
- /package/templates/default/{app → src/app}/error.tsx +0 -0
- /package/templates/default/{app → src/app}/hooks/use-transcript.test.ts +0 -0
- /package/templates/default/{app → src/app}/hooks/use-transcript.ts +0 -0
- /package/templates/default/{app → src/app}/layout.tsx +0 -0
- /package/templates/default/{app → src/app}/lib/constants.ts +0 -0
- /package/templates/default/{app → src/app}/lib/renderable.ts +0 -0
- /package/templates/default/{app → src/app}/loading.tsx +0 -0
- /package/templates/default/{app → src/app}/not-found.tsx +0 -0
- /package/templates/default/{app → src/app}/page.test.tsx +0 -0
- /package/templates/default/{app → src/app}/page.tsx +0 -0
- /package/templates/default/{agents → src/server/agents}/prompts/instructions.ts +0 -0
- /package/templates/default/{agents → src/server/agents}/skills/daily-briefing.ts +0 -0
- /package/templates/default/{agents → src/server/agents}/tools/current-time.ts +0 -0
- /package/templates/default/{agents → src/server/agents}/tools/send-notification.ts +0 -0
- /package/templates/default/{agents → src/server/agents}/tools/weather.ts +0 -0
- /package/templates/default/{server → src/server}/routes/health.ts +0 -0
- /package/templates/default/{shared → src/shared}/agent.ts +0 -0
|
@@ -1,60 +1,105 @@
|
|
|
1
1
|
# Architecture
|
|
2
2
|
|
|
3
|
-
How this TheoKit app is organized.
|
|
4
|
-
|
|
3
|
+
How this TheoKit app is organized. Two domains, named by what they are rather than by what they
|
|
4
|
+
contain: `src/app/` is the interface — the only code that reaches a browser — and `src/server/` is
|
|
5
|
+
the backend, everything that never leaves the machine.
|
|
6
|
+
|
|
7
|
+
The **agent lives inside the backend**, at `src/server/agents/`, because that is what it is: it
|
|
8
|
+
reads your API keys, calls tools that touch the network, and runs where the server runs. Every rule
|
|
9
|
+
that applies to `server/` applies to it. Within that folder the agent is still at the center — the
|
|
10
|
+
agent file and what composes it (prompts, tools, skills) sit together with clean names, the
|
|
5
11
|
"file = identity" convention agent frameworks like [Eve](https://eve.dev) use.
|
|
6
12
|
|
|
7
13
|
## Project structure
|
|
8
14
|
|
|
9
15
|
```
|
|
10
16
|
.
|
|
11
|
-
├──
|
|
12
|
-
│ ├──
|
|
13
|
-
│ ├──
|
|
14
|
-
│ │
|
|
15
|
-
│ ├──
|
|
16
|
-
│ │
|
|
17
|
-
│ │
|
|
18
|
-
│
|
|
19
|
-
│
|
|
20
|
-
├──
|
|
21
|
-
│
|
|
22
|
-
│
|
|
23
|
-
│
|
|
24
|
-
│
|
|
25
|
-
│ ├──
|
|
26
|
-
│ │ ├──
|
|
27
|
-
│ │ ├──
|
|
28
|
-
│ │ ├──
|
|
29
|
-
│ │
|
|
30
|
-
│ ├──
|
|
31
|
-
│ │
|
|
32
|
-
│
|
|
33
|
-
│
|
|
34
|
-
|
|
35
|
-
├──
|
|
36
|
-
│ └──
|
|
17
|
+
├── src/
|
|
18
|
+
│ ├── server/ # Backend — everything that never leaves the machine
|
|
19
|
+
│ │ ├── agents/ # The agent, and what composes it
|
|
20
|
+
│ │ │ ├── chat.ts # the agent → POST /api/agents/chat, useAgent('chat')
|
|
21
|
+
│ │ │ ├── prompts/ # system prompts / personas
|
|
22
|
+
│ │ │ │ └── instructions.ts
|
|
23
|
+
│ │ │ ├── tools/ # tools the agent can call
|
|
24
|
+
│ │ │ │ ├── weather.ts # remote — current weather via open-meteo (HTTP)
|
|
25
|
+
│ │ │ │ └── current-time.ts # local — date/time in an IANA timezone (no network)
|
|
26
|
+
│ │ │ ├── skills/ # procedures the model loads on demand (Skill.create)
|
|
27
|
+
│ │ │ │ └── daily-briefing.ts # a real skill: time → weather → a one-line nudge
|
|
28
|
+
│ │ │ └── hooks/ # lifecycle interception
|
|
29
|
+
│ │ │ └── tool-audit.ts # one structured line per tool call, with timing
|
|
30
|
+
│ │ └── routes/ # Backend routes / actions (POST/GET handlers, jobs)
|
|
31
|
+
│ ├── app/ # Frontend (web surface). tui → tui/, desktop → frontend/
|
|
32
|
+
│ │ ├── page.tsx # the `/` route — composition root (components + the hook)
|
|
33
|
+
│ │ ├── layout.tsx # root layout — composes <Header/> over the routed page
|
|
34
|
+
│ │ ├── error/loading/not-found.tsx # route surface (special files)
|
|
35
|
+
│ │ ├── about/page.tsx # an EXAMPLE `/about` route (delete when done)
|
|
36
|
+
│ │ ├── components/ # presentational UI (Tailwind, flat .tsx — no CSS modules)
|
|
37
|
+
│ │ │ ├── Header.tsx # the top bar (composes Nav + theme toggle)
|
|
38
|
+
│ │ │ ├── Nav.tsx # the navigation menu (a link per screen)
|
|
39
|
+
│ │ │ ├── ChatPanel.tsx # the transcript + streaming indicator + starters
|
|
40
|
+
│ │ │ └── Composer.tsx # the input + error card + new-chat
|
|
41
|
+
│ │ ├── hooks/ # custom hooks
|
|
42
|
+
│ │ │ └── use-transcript.ts # transcript STATE (history + streaming)
|
|
43
|
+
│ │ └── lib/ # app modules / config
|
|
44
|
+
│ │ └── constants.ts # greeting + starter prompts
|
|
45
|
+
│ └── shared/ # Code imported by BOTH sides — and only that
|
|
46
|
+
│ └── agent.ts # branding (name, model, greeting) — one source of truth
|
|
47
|
+
├── .theokit/ # Agent context, read at runtime — no rebuild to change it
|
|
48
|
+
│ ├── THEO.md # prepended every turn (priority 60)
|
|
49
|
+
│ ├── rules/ # path-scoped instructions, by glob (priority 45)
|
|
50
|
+
│ └── personalities/ # swappable system prompts (agent.usePersonality)
|
|
37
51
|
├── types/ # Framework ambient declarations (e.g. the job registry)
|
|
38
52
|
├── docs/ # This folder
|
|
39
53
|
└── theo.config.ts # App config (name, dirs, plugins)
|
|
40
54
|
```
|
|
41
55
|
|
|
56
|
+
## Two halves: what you compile, and what the agent reads
|
|
57
|
+
|
|
58
|
+
`src/server/agents/` is **code** — it changes when you deploy. `.theokit/` is **data** — the agent
|
|
59
|
+
reads it at runtime, so a wording change is an edit, not a release.
|
|
60
|
+
|
|
61
|
+
The SDK loads context from seven sources in priority order, and yours sit inside it:
|
|
62
|
+
|
|
63
|
+
| Priority | Source | Scope |
|
|
64
|
+
| -------- | ------------------------- | ------------------------ |
|
|
65
|
+
| 10 | `AGENTS.md` | walks up to the git root |
|
|
66
|
+
| 20 | `GEMINI.md` | walks up to the git root |
|
|
67
|
+
| 30 | `CLAUDE.md` | walks up to the git root |
|
|
68
|
+
| 40 | `.cursor/rules/*.mdc` | glob-activated |
|
|
69
|
+
| **45** | **`.theokit/rules/*.md`** | **glob-activated** |
|
|
70
|
+
| 50 | `.theokit/context/*.md` | glob-activated |
|
|
71
|
+
| **60** | **`.theokit/THEO.md`** | this directory only |
|
|
72
|
+
|
|
73
|
+
Later wins where they conflict, so `THEO.md` is the strongest thing in the file layer — which is
|
|
74
|
+
why it should hold facts rather than preferences. A preference that always wins is a preference you
|
|
75
|
+
cannot override with a personality.
|
|
76
|
+
|
|
77
|
+
**Personalities are not in that table**, and the reason is worth keeping straight: the seven sources
|
|
78
|
+
above are _context files_, merged into every relevant turn. A personality REPLACES the system
|
|
79
|
+
prompt, only while it is active, and only when someone asks for it:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
await agent.usePersonality('teacher', { save: true })
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Reserved names — `none`, `default`, `neutral` — clear it.
|
|
86
|
+
|
|
42
87
|
## Clean names, no phantom routes
|
|
43
88
|
|
|
44
|
-
An agent is a file: `agents/<name>.ts` → `POST /api/agents/<name>`. But the framework's scanner is
|
|
45
|
-
**folder-semantic** — thirteen sub-folder names under `agents/` (`tools/`, `skills/`, `prompts/`,
|
|
89
|
+
An agent is a file: `src/server/agents/<name>.ts` → `POST /api/agents/<name>`. But the framework's scanner is
|
|
90
|
+
**folder-semantic** — thirteen sub-folder names under `src/server/agents/` (`tools/`, `skills/`, `prompts/`,
|
|
46
91
|
`lib/`, `hooks/`, `channels/`, `connections/`, `subagents/`, `schedules/`, `sandbox/`, `workflows/`,
|
|
47
92
|
`evals/`, `memory/`) are **that concern, not routes**.
|
|
48
|
-
So the names stay clean (`tools/`, not `_tools/`) and `agents/tools/weather.ts` never becomes a phantom
|
|
93
|
+
So the names stay clean (`tools/`, not `_tools/`) and `src/server/agents/tools/weather.ts` never becomes a phantom
|
|
49
94
|
`/api/agents/tools/weather` endpoint. Markdown (`skills/*.md`) is never scanned either way. The
|
|
50
|
-
prompts/tools/skills are **shared** across every agent in `agents/`.
|
|
95
|
+
prompts/tools/skills are **shared** across every agent in `src/server/agents/`.
|
|
51
96
|
|
|
52
97
|
An agent that outgrows one file becomes a folder that co-locates its own composition —
|
|
53
|
-
`agents/<name>/index.ts` with `tools/` and `prompts/` beside it. Same route, same identity.
|
|
98
|
+
`src/server/agents/<name>/index.ts` with `tools/` and `prompts/` beside it. Same route, same identity.
|
|
54
99
|
|
|
55
100
|
## Composition
|
|
56
101
|
|
|
57
|
-
`agents/chat.ts` is thin on purpose — it wires the pieces together:
|
|
102
|
+
`src/server/agents/chat.ts` is thin on purpose — it wires the pieces together:
|
|
58
103
|
|
|
59
104
|
```ts
|
|
60
105
|
export default AgentBuilder.create()
|
|
@@ -71,7 +116,7 @@ Grow the agent by editing its neighbours, not by inflating `chat.ts`: persona
|
|
|
71
116
|
a new capability → `tools/<name>.ts` (then `.tool(<name>Tool)`), a documented procedure → a
|
|
72
117
|
`Skill.create(...)` in `skills/<name>.ts` (add it to the `.skills([...])` list). A **skill** is
|
|
73
118
|
loaded by the model on demand via the `skill_read` tool, so long procedures don't bloat every prompt. Add a
|
|
74
|
-
**second agent** as another `agents/<name>.ts`.
|
|
119
|
+
**second agent** as another `src/server/agents/<name>.ts`.
|
|
75
120
|
|
|
76
121
|
## Surfaces
|
|
77
122
|
|
|
@@ -79,34 +124,34 @@ The same agent is reached from three interchangeable frontends — only the tran
|
|
|
79
124
|
|
|
80
125
|
| Surface | Frontend dir | Transport |
|
|
81
126
|
| ------- | ------------ | ------------------------------------- |
|
|
82
|
-
| web | `app/`
|
|
127
|
+
| web | `src/app/` | `HttpTransport` (`/api/agents/chat`) |
|
|
83
128
|
| tui | `tui/` | `InProcessTransport` |
|
|
84
129
|
| desktop | `frontend/` | `ChannelTransport` (`@theokit/tauri`) |
|
|
85
130
|
|
|
86
|
-
All three render the same `@theokit/ui` chat and read `shared/agent.ts` for the greeting + model label.
|
|
131
|
+
All three render the same `@theokit/ui` chat and read `src/shared/agent.ts` for the greeting + model label.
|
|
87
132
|
|
|
88
133
|
## Frontend organization
|
|
89
134
|
|
|
90
|
-
The web `app/` is organized **type-based**, the layout most React apps grow into:
|
|
135
|
+
The web `src/app/` is organized **type-based**, the layout most React apps grow into:
|
|
91
136
|
|
|
92
|
-
| Folder
|
|
93
|
-
|
|
|
94
|
-
| `app/` root
|
|
95
|
-
| `components/`
|
|
96
|
-
| `hooks/`
|
|
97
|
-
| `lib/`
|
|
137
|
+
| Folder | Holds | Example |
|
|
138
|
+
| --------------- | --------------------------------------------------------- | ------------------------------------------------------- |
|
|
139
|
+
| `src/app/` root | the **route surface** — the only files the router serves | `page.tsx`, `layout.tsx`, `error/loading/not-found.tsx` |
|
|
140
|
+
| `components/` | presentational UI (flat `.tsx`; Tailwind, no CSS modules) | `Header`, `ChatPanel`, `Composer` |
|
|
141
|
+
| `hooks/` | custom hooks — where **state** lives | `use-transcript.ts` |
|
|
142
|
+
| `lib/` | app modules / config | `constants.ts` |
|
|
98
143
|
|
|
99
144
|
### Route files are not components
|
|
100
145
|
|
|
101
146
|
`page` · `layout` · `loading` · `error` · `not-found` are **route conventions**, not components. The
|
|
102
147
|
router binds them by **name + location**: matched by `^(page|layout|error|loading|not-found)\.(tsx|ts|jsx|js)$`
|
|
103
|
-
at a route segment (the `app/` root is the `/` route). `loading.tsx` becomes that route's Suspense
|
|
148
|
+
at a route segment (the `src/app/` root is the `/` route). `loading.tsx` becomes that route's Suspense
|
|
104
149
|
fallback, `error.tsx` its error boundary — _because they sit there, with that name_.
|
|
105
150
|
|
|
106
151
|
So `loading.tsx` looks like a component (it renders a spinner) but it is **not** one — move it into
|
|
107
152
|
`components/` and the router no longer finds it, and the route loses its loading UI. This is exactly the
|
|
108
153
|
Next.js App Router model TheoKit implements: the special files live at the route segment, and `components/`
|
|
109
|
-
/ `hooks/` / `lib/` sit alongside them. Having both at the `app/` root is the convention, not a mismatch.
|
|
154
|
+
/ `hooks/` / `lib/` sit alongside them. Having both at the `src/app/` root is the convention, not a mismatch.
|
|
110
155
|
(If a special file grows big, keep the thin route file and have it render a real component from
|
|
111
156
|
`components/` — e.g. `loading.tsx` → `<LoadingScreen/>`.)
|
|
112
157
|
|
|
@@ -126,17 +171,17 @@ a `pages/` folder you wire by hand: that's the Next.js-style convention TheoKit
|
|
|
126
171
|
|
|
127
172
|
### Adding a screen
|
|
128
173
|
|
|
129
|
-
Routing is **file-based**: a screen is a folder under `app/` with a `page.tsx`. The folder name is the URL
|
|
130
|
-
segment; the home screen is the flat `app/page.tsx`.
|
|
174
|
+
Routing is **file-based**: a screen is a folder under `src/app/` with a `page.tsx`. The folder name is the URL
|
|
175
|
+
segment; the home screen is the flat `src/app/page.tsx`.
|
|
131
176
|
|
|
132
|
-
| You want | Create
|
|
133
|
-
| -------------------- |
|
|
134
|
-
| a `/settings` screen | `app/settings/page.tsx` | `/settings` |
|
|
135
|
-
| a nested screen | `app/settings/billing/page.tsx` | `/settings/billing` |
|
|
136
|
-
| a dynamic screen | `app/users/[id]/page.tsx` | `/users/:id` (read the param with react-router's `useParams`) |
|
|
137
|
-
| a catch-all | `app/docs/[...slug]/page.tsx` | `/docs/*` |
|
|
177
|
+
| You want | Create | Serves |
|
|
178
|
+
| -------------------- | ----------------------------------- | ------------------------------------------------------------- |
|
|
179
|
+
| a `/settings` screen | `src/app/settings/page.tsx` | `/settings` |
|
|
180
|
+
| a nested screen | `src/app/settings/billing/page.tsx` | `/settings/billing` |
|
|
181
|
+
| a dynamic screen | `src/app/users/[id]/page.tsx` | `/users/:id` (read the param with react-router's `useParams`) |
|
|
182
|
+
| a catch-all | `src/app/docs/[...slug]/page.tsx` | `/docs/*` |
|
|
138
183
|
|
|
139
|
-
The one-command way: **`theokit generate page settings`** creates `app/settings/page.tsx` for you. Each
|
|
184
|
+
The one-command way: **`theokit generate page settings`** creates `src/app/settings/page.tsx` for you. Each
|
|
140
185
|
screen can have its own `layout.tsx` / `loading.tsx` / `error.tsx` / `not-found.tsx` (the route special
|
|
141
186
|
files, scoped to that segment).
|
|
142
187
|
|
|
@@ -158,11 +203,11 @@ export function SettingsLink() {
|
|
|
158
203
|
|
|
159
204
|
TheoKit's `theokit/client` gives you the Next-parity building blocks: **`Link`** (prefetch), **`Metadata`**
|
|
160
205
|
(set `<title>`/meta per route — used in `page.tsx` + `about/page.tsx`), **`Image`** (optimized `<img>`),
|
|
161
|
-
**`theoFetch`** + the **`theokit/react-query`** adapter (typed data fetching against your `server/`
|
|
206
|
+
**`theoFetch`** + the **`theokit/react-query`** adapter (typed data fetching against your `src/server/`
|
|
162
207
|
routes), and **`useAgent`** (the chat stream). Reach for these instead of generic libraries.
|
|
163
208
|
|
|
164
|
-
The primary menu is `app/components/Nav.tsx` (TheoKit `Link` + `useLocation` for the active route). A worked
|
|
165
|
-
example ships as `app/about/page.tsx` (the `/about` route, linked from the `Nav`, with its own `Metadata`
|
|
209
|
+
The primary menu is `src/app/components/Nav.tsx` (TheoKit `Link` + `useLocation` for the active route). A worked
|
|
210
|
+
example ships as `src/app/about/page.tsx` (the `/about` route, linked from the `Nav`, with its own `Metadata`
|
|
166
211
|
title); it explains this and tells you to delete it once you've got the idea.
|
|
167
212
|
|
|
168
213
|
See also: [CUSTOMIZATION](./CUSTOMIZATION.md).
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Common changes, and where they go. See [ARCHITECTURE](./ARCHITECTURE.md) for the full layout.
|
|
4
4
|
|
|
5
|
-
| I want to… | Edit
|
|
6
|
-
| --------------------------------------------------- |
|
|
7
|
-
| Change the model | `agents/chat.ts` (`.model(...)`) + the label in `shared/agent.ts`
|
|
8
|
-
| Change the persona / rules | `agents/prompts/instructions.ts`
|
|
9
|
-
| Add a tool (an action the agent can take) | new `agents/tools/<name>.ts`, then `.tool(<name>Tool)` in `agents/chat.ts`
|
|
10
|
-
| Add a skill (a procedure the model loads on demand) | `Skill.create(...)` in `agents/skills/<name>.ts`, then add it to `.skills([...])` in `agents/chat.ts`
|
|
11
|
-
| Add a second agent | new `agents/<name>.ts` → auto-served at `/api/agents/<name>`, bind with `useAgent('<name>')`
|
|
12
|
-
| Change the greeting / app name | `shared/agent.ts`
|
|
13
|
-
| Add a backend route | `server/routes/<name>.ts`
|
|
14
|
-
| Change the provider key | `.env` — `OPENROUTER_API_KEY` (or `ANTHROPIC_API_KEY` / `OPENAI_API_KEY`). See [ENVIRONMENT](./ENVIRONMENT.md)
|
|
5
|
+
| I want to… | Edit |
|
|
6
|
+
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
7
|
+
| Change the model | `src/server/agents/chat.ts` (`.model(...)`) + the label in `src/shared/agent.ts` |
|
|
8
|
+
| Change the persona / rules | `src/server/agents/prompts/instructions.ts` |
|
|
9
|
+
| Add a tool (an action the agent can take) | new `src/server/agents/tools/<name>.ts`, then `.tool(<name>Tool)` in `src/server/agents/chat.ts` |
|
|
10
|
+
| Add a skill (a procedure the model loads on demand) | `Skill.create(...)` in `src/server/agents/skills/<name>.ts`, then add it to `.skills([...])` in `src/server/agents/chat.ts` |
|
|
11
|
+
| Add a second agent | new `src/server/agents/<name>.ts` → auto-served at `/api/agents/<name>`, bind with `useAgent('<name>')` |
|
|
12
|
+
| Change the greeting / app name | `src/shared/agent.ts` |
|
|
13
|
+
| Add a backend route | `src/server/routes/<name>.ts` |
|
|
14
|
+
| Change the provider key | `.env` — `OPENROUTER_API_KEY` (or `ANTHROPIC_API_KEY` / `OPENAI_API_KEY`). See [ENVIRONMENT](./ENVIRONMENT.md) |
|
|
15
15
|
|
|
16
16
|
## Adding a tool
|
|
17
17
|
|
|
@@ -8,17 +8,17 @@ Configuration lives in `.env` (copy `.env.example`). Nothing here is committed
|
|
|
8
8
|
| `ANTHROPIC_API_KEY` | one of these | Use Anthropic directly instead of OpenRouter. |
|
|
9
9
|
| `OPENAI_API_KEY` | one of these | Use OpenAI directly. |
|
|
10
10
|
|
|
11
|
-
The FIRST segment of the model id in `agents/chat.ts` picks the provider, and that decides which key
|
|
11
|
+
The FIRST segment of the model id in `src/server/agents/chat.ts` picks the provider, and that decides which key
|
|
12
12
|
is needed. It is not a hint: `openai/gpt-4o-mini` goes to OpenAI and needs `OPENAI_API_KEY`, even
|
|
13
13
|
with an OpenRouter key present. Reaching another vendor's catalog THROUGH OpenRouter means naming
|
|
14
14
|
the gateway first — `openrouter/openai/gpt-4o-mini`, which is what the scaffold declares, matching
|
|
15
15
|
the key above. See <https://openrouter.ai/models> for the ids OpenRouter serves.
|
|
16
16
|
|
|
17
|
-
| Model id in `agents/chat.ts`
|
|
18
|
-
|
|
|
19
|
-
| `openrouter/openai/gpt-4o-mini`
|
|
20
|
-
| `anthropic/claude-sonnet-4-6`
|
|
21
|
-
| `openai/gpt-4o-mini`
|
|
17
|
+
| Model id in `src/server/agents/chat.ts` | Key it needs |
|
|
18
|
+
| --------------------------------------- | -------------------- |
|
|
19
|
+
| `openrouter/openai/gpt-4o-mini` | `OPENROUTER_API_KEY` |
|
|
20
|
+
| `anthropic/claude-sonnet-4-6` | `ANTHROPIC_API_KEY` |
|
|
21
|
+
| `openai/gpt-4o-mini` | `OPENAI_API_KEY` |
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
cp .env.example .env
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Imports
|
|
4
4
|
|
|
5
|
-
- Use `theokit/server/define` for
|
|
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
|
|
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 =
|
|
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
|
|
@@ -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 {
|
|
23
|
+
import { AgentBuilder } from '@theokit/agents'
|
|
24
24
|
import { z } from 'zod'
|
|
25
25
|
|
|
26
|
-
export default
|
|
27
|
-
input
|
|
28
|
-
model
|
|
29
|
-
system
|
|
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.
|
|
@@ -205,7 +205,7 @@ Before writing custom tools, check if they already exist:
|
|
|
205
205
|
|
|
206
206
|
## Anti-patterns
|
|
207
207
|
|
|
208
|
-
- NEVER call OpenAI/Anthropic/OpenRouter APIs directly — use `
|
|
208
|
+
- NEVER call OpenAI/Anthropic/OpenRouter APIs directly — use `AgentBuilder.create()`
|
|
209
209
|
- NEVER reimplement tool calling loop — the SDK handles it
|
|
210
210
|
- NEVER reimplement file/search/shell tools — use `@theokit/sdk-tools` (readFile, writeFile, search, etc.)
|
|
211
211
|
- NEVER store conversations manually — SDK persistence is automatic (the SDK owns storage)
|
|
@@ -135,4 +135,4 @@ Env vars are loaded from `.env` (dev) and `.env.production` (build). NEVER commi
|
|
|
135
135
|
- NEVER hardcode secrets in theo.config.ts — use environment variables
|
|
136
136
|
- NEVER set `security.csrf: false` in production
|
|
137
137
|
- NEVER use `ssr: true` without understanding hydration (start with `false`)
|
|
138
|
-
- NEVER add plugins that don't match `
|
|
138
|
+
- NEVER add plugins that don't match `TheoPlugin` interface
|
|
@@ -3,7 +3,7 @@ name: theokit-gateways
|
|
|
3
3
|
description: Receiving messages from Telegram, WhatsApp, Slack and other platforms — handleChannelWebhook, the @theokit/gateway-* adapters, signature validation, the onMessage seam
|
|
4
4
|
user-invocable: false
|
|
5
5
|
paths:
|
|
6
|
-
- 'server/routes/**'
|
|
6
|
+
- 'src/server/routes/**'
|
|
7
7
|
- 'server/channels/**'
|
|
8
8
|
- '**/*gateway*'
|
|
9
9
|
- '**/*webhook*'
|
|
@@ -3,7 +3,7 @@ name: theokit-ui
|
|
|
3
3
|
description: '@theokit/ui AI-native component library — AI-agent surfaces (ChatThread, ChatMessage, ChatComposer, ToolCallCard, AgentStream), theming, providers; generic primitives (CodeBlock, Sidebar, Button) come from @usetheo/ui'
|
|
4
4
|
user-invocable: false
|
|
5
5
|
paths:
|
|
6
|
-
- 'app/**'
|
|
6
|
+
- 'src/app/**'
|
|
7
7
|
- '**/*Chat*'
|
|
8
8
|
- '**/*chat*'
|
|
9
9
|
- '**/*Sidebar*'
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Product context
|
|
2
|
+
|
|
3
|
+
Prepended to your agent's context on **every turn**, and it is the strongest source in the file
|
|
4
|
+
layer (priority 60) — nothing else overrides it. That is what decides what belongs here.
|
|
5
|
+
|
|
6
|
+
> **Not the same file as `AGENTS.md` at the root.** That one tells agents how to work on this
|
|
7
|
+
> codebase: commands, layout, conventions. This one tells your agent what your product IS, for the
|
|
8
|
+
> conversations it has with your users. If a sentence would help someone edit the code, it goes
|
|
9
|
+
> there instead.
|
|
10
|
+
|
|
11
|
+
**Put facts here, not preferences.** A preference written here wins against every personality, so a
|
|
12
|
+
tone instruction in this file quietly makes `usePersonality` do nothing. Tone belongs in
|
|
13
|
+
`.theokit/personalities/`; instructions about specific files belong in `.theokit/rules/`.
|
|
14
|
+
|
|
15
|
+
Keep it short — every line costs tokens on every turn.
|
|
16
|
+
|
|
17
|
+
## What this app is
|
|
18
|
+
|
|
19
|
+
Replace this with two or three sentences: what your product does, who uses it, and the one thing an
|
|
20
|
+
agent must never get wrong about it.
|
|
21
|
+
|
|
22
|
+
## Vocabulary
|
|
23
|
+
|
|
24
|
+
The highest-value section, and the one most projects skip. Define the words your domain uses
|
|
25
|
+
differently from everyone else — a model that guesses what "account" means in your system will
|
|
26
|
+
guess wrong in a way that reads as fluent, which is the hardest kind of wrong to notice.
|
|
27
|
+
|
|
28
|
+
| Term | In this app it means |
|
|
29
|
+
| ------ | ---------------------------------------- |
|
|
30
|
+
| _term_ | _the definition your team actually uses_ |
|
|
31
|
+
|
|
32
|
+
## Boundaries
|
|
33
|
+
|
|
34
|
+
What the agent should refuse or hand off, stated as fact rather than as tone. "Refunds are issued
|
|
35
|
+
by support, never by the assistant" is a fact. "Be careful with refunds" is a preference, and it
|
|
36
|
+
belongs in a personality where a user could switch it.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Personalities
|
|
2
|
+
|
|
3
|
+
A personality is a **swappable system prompt**, chosen at runtime without redeploying:
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
await agent.usePersonality('teacher') // for this process
|
|
7
|
+
await agent.usePersonality('teacher', { save: true }) // persists across restarts
|
|
8
|
+
await agent.usePersonality('none') // back to the agent's own system prompt
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`none`, `default` and `neutral` are reserved names that clear the active preset. History is kept
|
|
12
|
+
across a switch — pass `{ reset: true }` to clear the session too.
|
|
13
|
+
|
|
14
|
+
Each file is markdown: frontmatter describes it, the **body is the system prompt**.
|
|
15
|
+
|
|
16
|
+
| Frontmatter | Type | Meaning |
|
|
17
|
+
| ------------- | -------- | ---------------------------------------------- |
|
|
18
|
+
| `name` | string | the name you pass to `usePersonality` |
|
|
19
|
+
| `description` | string | shown when listing presets |
|
|
20
|
+
| `tools` | string[] | restrict the agent to these tools while active |
|
|
21
|
+
| `model` | string | override the model while active |
|
|
22
|
+
| `tags` | string[] | free-form grouping |
|
|
23
|
+
|
|
24
|
+
## Personality, rule, or system prompt?
|
|
25
|
+
|
|
26
|
+
The three overlap enough that picking wrong is easy, and the difference is _when each one applies_:
|
|
27
|
+
|
|
28
|
+
| | Applies when | Changes at runtime |
|
|
29
|
+
| ------------------------ | -------------------------------------------- | ------------------------- |
|
|
30
|
+
| `chat.ts` `.system(...)` | always — it is what the agent IS | no, it ships in the build |
|
|
31
|
+
| a **personality** | while the user has it active | **yes**, `usePersonality` |
|
|
32
|
+
| a **rule** | when the conversation touches matching paths | no, but it is just a file |
|
|
33
|
+
|
|
34
|
+
Tone and stance belong in a personality. Facts about your domain belong in `THEO.md`. Instructions
|
|
35
|
+
about specific code belong in `rules/`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: concise
|
|
3
|
+
description: Short answers, no preamble — for users who already know the domain
|
|
4
|
+
tags:
|
|
5
|
+
- default
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Answer in as few words as the question honestly allows.
|
|
9
|
+
|
|
10
|
+
- No preamble. Do not restate the question, do not announce what you are about to do.
|
|
11
|
+
- One paragraph unless the answer genuinely has parts. Then use a list.
|
|
12
|
+
- Skip the caveats a domain expert already knows. Keep the ones that would change their decision.
|
|
13
|
+
- When you are unsure, say so in one sentence and say what would resolve it. Do not pad uncertainty
|
|
14
|
+
into a long hedge — a long hedge reads as confidence.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: teacher
|
|
3
|
+
description: Explains the reasoning, for someone learning the domain
|
|
4
|
+
tags:
|
|
5
|
+
- onboarding
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Explain your reasoning, not just your conclusion.
|
|
9
|
+
|
|
10
|
+
- Lead with the answer, then say why it is the answer. Someone who already knew can stop at the
|
|
11
|
+
first line; someone learning reads on.
|
|
12
|
+
- Name the thing you are doing when it has a name. A reader who learns the word can look it up
|
|
13
|
+
later; a reader who only sees the steps cannot.
|
|
14
|
+
- When there was a real alternative, say what it was and why you did not take it. The rejected
|
|
15
|
+
option is usually the more instructive half.
|
|
16
|
+
- Never simplify to the point of being wrong. If the honest answer is complicated, say it is
|
|
17
|
+
complicated and then give it.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Rules
|
|
2
|
+
|
|
3
|
+
Markdown files here are **path-scoped instructions**: they enter the agent's context only when the
|
|
4
|
+
conversation touches a file matching their `globs`. That is what separates a rule from `THEO.md`,
|
|
5
|
+
which is always present.
|
|
6
|
+
|
|
7
|
+
```markdown
|
|
8
|
+
---
|
|
9
|
+
description: what this rule is for
|
|
10
|
+
globs:
|
|
11
|
+
- src/server/routes/**/*.ts
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Your instructions here.
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
| Frontmatter | Type | Meaning |
|
|
18
|
+
| ------------- | -------- | ---------------------------------- |
|
|
19
|
+
| `description` | string | what the rule covers |
|
|
20
|
+
| `globs` | string[] | paths that activate it |
|
|
21
|
+
| `paths` | string[] | same idea, explicit paths |
|
|
22
|
+
| `alwaysApply` | boolean | ignore the globs and always attach |
|
|
23
|
+
| `enabled` | boolean | switch it off without deleting it |
|
|
24
|
+
|
|
25
|
+
A file with **no frontmatter at all** is treated as `alwaysApply: true`. That is a convenience worth
|
|
26
|
+
knowing about and easy to trigger by accident: a rule you meant to scope, whose frontmatter has a
|
|
27
|
+
typo, becomes a rule that fires on every turn.
|
|
28
|
+
|
|
29
|
+
Rules load at priority 45 — after `CLAUDE.md` (30) and `.cursor/rules` (40), before
|
|
30
|
+
`.theokit/context` (50) and `THEO.md` (60).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Conventions for backend route handlers
|
|
3
|
+
globs:
|
|
4
|
+
- src/server/routes/**/*.ts
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Route handler conventions
|
|
8
|
+
|
|
9
|
+
These apply when the conversation touches a file under `src/server/routes/`.
|
|
10
|
+
|
|
11
|
+
- A route validates its input at the boundary with Zod and trusts it afterwards. A handler that
|
|
12
|
+
re-checks the same shape three layers down is describing distrust of its own validation.
|
|
13
|
+
- Return a typed error, never a bare `null` or `-1`. The caller cannot tell a missing value from a
|
|
14
|
+
failure, and one of those is a bug worth an alert.
|
|
15
|
+
- Read secrets from the environment inside the handler, never at module scope — a module-level read
|
|
16
|
+
runs at import time, which is before the process has decided whether it is a build or a boot.
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"typecheck": "tsc --noEmit"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"theokit": "^0.
|
|
20
|
-
"@theokit/agents": "^
|
|
19
|
+
"theokit": "^0.64.0",
|
|
20
|
+
"@theokit/agents": "^12.1.0",
|
|
21
21
|
"@theokit/sdk": "^4.52.1",
|
|
22
22
|
"@theokit/ui": "^1.1.0",
|
|
23
23
|
"@usetheo/ui": "^0.26.0",
|
|
@@ -2,6 +2,7 @@ import { AgentBuilder } from '@theokit/agents'
|
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
|
|
4
4
|
import { BASE_INSTRUCTIONS } from './prompts/instructions.js'
|
|
5
|
+
import { toolAuditHooks } from './hooks/tool-audit.js'
|
|
5
6
|
import { dailyBriefingSkill } from './skills/daily-briefing.js'
|
|
6
7
|
import { currentTimeTool } from './tools/current-time.js'
|
|
7
8
|
import { sendNotificationTool } from './tools/send-notification.js'
|
|
@@ -67,4 +68,9 @@ export default AgentBuilder.create()
|
|
|
67
68
|
.approval('send_notification', { question: 'Send this notification?' })
|
|
68
69
|
// Skills the agent can consult on demand (hover `.skills` for how it works).
|
|
69
70
|
.skills([dailyBriefingSkill])
|
|
71
|
+
// Lifecycle hooks — one structured log line per tool call, with its duration. This is the
|
|
72
|
+
// observability half of the wiring: without it, a slow or looping agent is something you infer
|
|
73
|
+
// from a complaint instead of something you read in a log. `hooks/tool-audit.ts` explains the
|
|
74
|
+
// other seven events, and why this one deliberately does not veto anything.
|
|
75
|
+
.hooks(toolAuditHooks)
|
|
70
76
|
.build()
|