create-theokit 1.4.0 → 1.5.1

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.4.0",
3
+ "version": "1.5.1",
4
4
  "type": "module",
5
5
  "description": "Scaffold a new TheoKit project",
6
6
  "license": "Apache-2.0",
@@ -1,29 +1,31 @@
1
1
  import { agent } from '@theokit/agents'
2
+ import { defineSkillReadTool } from '@theokit/sdk'
2
3
  import { z } from 'zod'
3
4
 
4
- import { BASE_INSTRUCTIONS } from './_lib/instructions.js'
5
- import { weatherTool } from './_tools/weather.js'
5
+ import { BASE_INSTRUCTIONS } from './prompts/instructions.js'
6
+ import { dailyBriefingSkill } from './skills/daily-briefing.js'
7
+ import { currentTimeTool } from './tools/current-time.js'
8
+ import { weatherTool } from './tools/weather.js'
6
9
 
7
10
  /**
8
- * Chat agent — the zero-config `agents/*.ts` convention.
11
+ * The `chat` agent — served at `POST /api/agents/chat` and bound by `useAgent('chat')`. This file IS the
12
+ * agent; it composes its neighbours under `agents/`: the persona in `prompts/`, capabilities in `tools/`,
13
+ * procedures in `skills/`. Those folders are that concern, NOT extra routes — the framework's scanner
14
+ * treats `prompts/ tools/ skills/ lib/ …` as semantic folders, so `agents/tools/weather.ts` never becomes
15
+ * a `/api/agents/tools/weather` endpoint. Add a second agent as another `agents/<name>.ts`. See
16
+ * `docs/ARCHITECTURE.md`.
9
17
  *
10
- * This one file is auto-served at `POST /api/agents/chat` (dev + build), streaming the
11
- * ai-sdk `UIMessageStream` that `useAgent('chat')` consumes on the client. No manual route,
12
- * no manual client wiring. `@theokit/sdk` runs it; conversation turns auto-persist per
13
- * session (the SDK owns storage).
14
- *
15
- * Provider: resolved from the environment — OPENROUTER_API_KEY (preferred — gateway to many
16
- * models) OR ANTHROPIC_API_KEY / OPENAI_API_KEY. The `model` id is prefixed with the provider
17
- * namespace so OpenRouter routes it upstream (see https://openrouter.ai/models).
18
- *
19
- * The agent is composed from its neighbours (see `docs/ARCHITECTURE.md`): the persona lives in
20
- * `agents/_lib/instructions.ts` and tools in `agents/_tools/` (both underscore-prefixed so the
21
- * route scanner skips them). Add a tool with `tool('name')…build()` and chain it via `.tool(...)`;
22
- * add a skill as `agents/skills/<name>.md`.
18
+ * `@theokit/sdk` runs the agent; conversation turns auto-persist per session. Provider is resolved from
19
+ * the environment OPENROUTER_API_KEY (preferred) OR ANTHROPIC_API_KEY / OPENAI_API_KEY; the model id is
20
+ * provider-prefixed so OpenRouter routes it upstream (https://openrouter.ai/models).
23
21
  */
24
22
  export default agent()
25
23
  .input(z.object({ message: z.string() }))
26
24
  .model('openai/gpt-4o-mini')
27
25
  .system(BASE_INSTRUCTIONS)
28
26
  .tool(weatherTool)
27
+ .tool(currentTimeTool)
28
+ // `defineSkillReadTool` exposes the skills to the model as an on-demand `skill_read` tool — the model
29
+ // sees each skill's name + description and loads the full body only when it needs it.
30
+ .tool(defineSkillReadTool([dailyBriefingSkill]))
29
31
  .build()
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The `chat` agent's system prompt (persona + rules). Lives in `prompts/` — a semantic sub-folder the
3
+ * framework's agent scanner skips, so it never becomes a route. Grow the persona here instead of inlining
4
+ * a long string in `index.ts`.
5
+ */
6
+ export const BASE_INSTRUCTIONS = `You are a helpful assistant living inside a TheoKit app.
7
+
8
+ ## Voice
9
+ - Answer clearly and concisely. Prefer a direct answer over a preamble.
10
+ - If you are unsure, say so rather than inventing an answer.
11
+
12
+ ## Tools — call them, don't guess
13
+ - **Current weather** for a place → call the \`weather\` tool.
14
+ - **Current date / time** (optionally for a timezone) → call the \`current_time\` tool.
15
+ - Never state a time or the weather from memory; always call the tool. If a tool errors, tell the user
16
+ what failed and what you need (e.g. a valid city or IANA timezone).
17
+
18
+ ## Skills
19
+ - You have a \`skill_read\` tool that lists documented procedures (skills). When a request matches a
20
+ skill's description — e.g. the user asks for a "briefing" / "morning update" / "what's my day looking
21
+ like" (the **daily-briefing** skill) — call \`skill_read\` to load its steps, then follow them.`
@@ -0,0 +1,31 @@
1
+ import { createSkill } from '@theokit/sdk'
2
+
3
+ /**
4
+ * A real, code-defined skill (`createSkill`) — a documented procedure the MODEL can pull in on demand.
5
+ *
6
+ * Unlike a loose Markdown note, this is wired into the agent: `agents/chat.ts` passes it to
7
+ * `defineSkillReadTool([...])`, which gives the model a `skill_read` tool. The model sees the skill's
8
+ * name + description in every turn (cheap) and calls `skill_read('daily-briefing')` to load the full
9
+ * `instructions` only when it actually needs them — so long procedures don't bloat every prompt.
10
+ *
11
+ * Lives in `agents/skills/` — a semantic folder the route scanner skips, so it never becomes an endpoint.
12
+ */
13
+ export const dailyBriefingSkill = createSkill({
14
+ name: 'daily-briefing',
15
+ description:
16
+ "Produce a short 'good morning' briefing (today's date, the weather, and a one-line nudge).",
17
+ instructions: `# Daily briefing
18
+
19
+ Trigger: the user asks for a "briefing", "morning update", or "what's my day looking like".
20
+
21
+ Steps:
22
+ 1. Ask for (or reuse) the user's city and IANA timezone if you don't already have them.
23
+ 2. Call the \`current_time\` tool with their timezone → the local date and time.
24
+ 3. Call the \`weather\` tool with their city → the current conditions.
25
+ 4. Reply in exactly three lines:
26
+ - **Today** — weekday + date (from current_time).
27
+ - **Weather** — conditions + temperature (from weather).
28
+ - **Nudge** — one short, friendly suggestion based on the weather.
29
+
30
+ Never invent the time or weather — always call the tools. Keep it to three lines unless asked for more.`,
31
+ })
@@ -0,0 +1,35 @@
1
+ import { tool } from 'theokit/server'
2
+ import { z } from 'zod'
3
+
4
+ /**
5
+ * A local, deterministic tool — the current date + time, optionally in an IANA timezone. No network,
6
+ * always works: the counterpart to `weather.ts` (which hits an HTTP API), so the scaffold shows both a
7
+ * remote-call tool and a pure local one. Imported into `chat.ts` and chained with `.tool(currentTimeTool)`.
8
+ */
9
+ export const currentTimeTool = tool('current_time')
10
+ .describe('Get the current date and time, optionally for a specific IANA timezone.')
11
+ .input(
12
+ z.object({
13
+ timezone: z
14
+ .string()
15
+ .optional()
16
+ .describe('IANA timezone, e.g. "Europe/Lisbon" or "America/Sao_Paulo". Defaults to UTC.'),
17
+ }),
18
+ )
19
+ .execute(async ({ timezone }) => {
20
+ const tz = timezone ?? 'UTC'
21
+ try {
22
+ const formatted = new Intl.DateTimeFormat('en-US', {
23
+ timeZone: tz,
24
+ dateStyle: 'full',
25
+ timeStyle: 'long',
26
+ }).format(new Date())
27
+ return `Current time (${tz}): ${formatted}`
28
+ } catch {
29
+ // fail-fast with a clear, actionable message (Rule 8) rather than a cryptic RangeError.
30
+ throw new Error(
31
+ `Unknown timezone "${tz}". Use an IANA name like "Europe/Lisbon" or "America/Sao_Paulo".`,
32
+ )
33
+ }
34
+ })
35
+ .build()
@@ -5,8 +5,8 @@ import { z } from 'zod'
5
5
  * An example agent tool — current weather for a place, via the keyless open-meteo API.
6
6
  *
7
7
  * A tool is PURE metadata + a handler: it describes a capability and runs local/HTTP work; it NEVER calls
8
- * an LLM (the agent decides when to invoke it). Lives in `agents/_tools/` (underscore-prefixed) so the
9
- * route scanner skips it it is imported into `agents/chat.ts` and chained with `.tool(weatherTool)`.
8
+ * an LLM (the agent decides when to invoke it). Lives in `tools/` — a semantic sub-folder the agent
9
+ * scanner skips — and is imported into `chat.ts` and chained with `.tool(weatherTool)`.
10
10
  *
11
11
  * Add your own the same way: `tool('name').describe(...).input(z.object({...})).execute(async (input) => …).build()`.
12
12
  */
@@ -1,20 +1,22 @@
1
1
  # Architecture
2
2
 
3
- How this TheoKit app is organized. The layout puts the **agent at the center** and keeps a clean split
4
- between the agent, the backend, the frontend, and cross-layer code.
3
+ How this TheoKit app is organized. The layout puts the **agent at the center**: the agent file and the
4
+ folders it composes (prompts, tools, skills) live together under `agents/`, with clean names — the
5
+ "file = identity" convention agent frameworks like [Eve](https://eve.dev) use.
5
6
 
6
7
  ## Project structure
7
8
 
8
9
  ```
9
10
  .
10
- ├── agents/ # The agent lives here (auto-served at POST /api/agents/<name>)
11
- │ ├── chat.ts # the agent composed from its neighbours below
12
- │ ├── _lib/ # internal helpers (underscore = NOT a route)
13
- │ │ └── instructions.ts # the system prompt / persona
14
- │ ├── _tools/ # tools the agent can call (underscore = NOT a route)
15
- │ │ └── weather.ts # example: tool('weather')…build()
16
- │ └── skills/ # Markdown procedures the agent (and you) can follow
17
- └── getting-started.md
11
+ ├── agents/ # The agent, and what composes it
12
+ │ ├── chat.ts # the agent POST /api/agents/chat, useAgent('chat')
13
+ │ ├── prompts/ # system prompts / personas
14
+ │ │ └── instructions.ts
15
+ │ ├── tools/ # tools the agent can call
16
+ │ │ ├── weather.ts # remote — current weather via open-meteo (HTTP)
17
+ └── current-time.ts # local date/time in an IANA timezone (no network)
18
+ └── skills/ # procedures the model loads on demand (createSkill)
19
+ │ └── daily-briefing.ts # a real skill: time → weather → a one-line nudge
18
20
  ├── app/ # Frontend (web surface). tui → tui/, desktop → frontend/
19
21
  ├── server/ # Backend routes / actions (POST/GET handlers, jobs)
20
22
  ├── shared/ # Code imported by more than one layer
@@ -24,13 +26,14 @@ between the agent, the backend, the frontend, and cross-layer code.
24
26
  └── theo.config.ts # App config (name, dirs, plugins)
25
27
  ```
26
28
 
27
- ## Why `_lib/` and `_tools/` have an underscore
29
+ ## Clean names, no phantom routes
28
30
 
29
- The framework turns **every `.ts` under `agents/` into a `POST /api/agents/<name>` endpoint** (that is the
30
- zero-config convention that lets `useAgent('chat')` bind with no manual wiring). Files that are *not*
31
- agents the persona, the tools would otherwise become phantom endpoints, so they live in
32
- underscore-prefixed folders, which the route scanner skips (the same convention Next.js uses for private
33
- folders). `skills/` needs no underscore because Markdown is never scanned.
31
+ An agent is a file: `agents/<name>.ts` `POST /api/agents/<name>`. But the framework's scanner is
32
+ **folder-semantic** the conventional sub-folders under `agents/` (`prompts/`, `tools/`, `skills/`,
33
+ `lib/`, `hooks/`, `channels/`, `connections/`, `subagents/`, `schedules/`) are **that concern, not routes**.
34
+ So the names stay clean (`tools/`, not `_tools/`) and `agents/tools/weather.ts` never becomes a phantom
35
+ `/api/agents/tools/weather` endpoint. Markdown (`skills/*.md`) is never scanned either way. The
36
+ prompts/tools/skills are **shared** across every agent in `agents/`.
34
37
 
35
38
  ## Composition
36
39
 
@@ -40,13 +43,18 @@ folders). `skills/` needs no underscore because Markdown is never scanned.
40
43
  export default agent()
41
44
  .input(z.object({ message: z.string() }))
42
45
  .model('openai/gpt-4o-mini')
43
- .system(BASE_INSTRUCTIONS) // agents/_lib/instructions.ts
44
- .tool(weatherTool) // agents/_tools/weather.ts
46
+ .system(BASE_INSTRUCTIONS) // agents/prompts/instructions.ts
47
+ .tool(weatherTool) // agents/tools/weather.ts
48
+ .tool(currentTimeTool) // agents/tools/current-time.ts
49
+ .tool(defineSkillReadTool([dailyBriefingSkill])) // agents/skills/daily-briefing.ts
45
50
  .build()
46
51
  ```
47
52
 
48
- Grow the agent by editing its neighbours, not by inflating `chat.ts`: persona → `_lib/instructions.ts`,
49
- a new capability → `_tools/<name>.ts` (then `.tool(<name>Tool)`), a documented procedure → `skills/<name>.md`.
53
+ Grow the agent by editing its neighbours, not by inflating `chat.ts`: persona → `prompts/instructions.ts`,
54
+ a new capability → `tools/<name>.ts` (then `.tool(<name>Tool)`), a documented procedure → a
55
+ `createSkill(...)` in `skills/<name>.ts` (add it to the `defineSkillReadTool([...])` list). A **skill** is
56
+ loaded by the model on demand via the `skill_read` tool, so long procedures don't bloat every prompt. Add a
57
+ **second agent** as another `agents/<name>.ts`.
50
58
 
51
59
  ## Surfaces
52
60
 
@@ -5,18 +5,18 @@ Common changes, and where they go. See [ARCHITECTURE](./ARCHITECTURE.md) for the
5
5
  | I want to… | Edit |
6
6
  |------------|------|
7
7
  | Change the model | `agents/chat.ts` (`.model(...)`) + the label in `shared/agent.ts` |
8
- | Change the persona / rules | `agents/_lib/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 documented procedure) | new `agents/skills/<name>.md` |
11
- | Change the greeting / app name | `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) | `createSkill(...)` in `agents/skills/<name>.ts`, then add it to `defineSkillReadTool([...])` in `agents/chat.ts` |
12
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
13
  | Add a backend route | `server/routes/<name>.ts` |
14
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
 
18
18
  ```ts
19
- // agents/_tools/echo.ts
19
+ // agents/tools/echo.ts
20
20
  import { tool } from 'theokit/server'
21
21
  import { z } from 'zod'
22
22
 
@@ -29,7 +29,7 @@ export const echoTool = tool('echo')
29
29
 
30
30
  ```ts
31
31
  // agents/chat.ts
32
- import { echoTool } from './_tools/echo.js'
32
+ import { echoTool } from './tools/echo.js'
33
33
  // …
34
34
  .tool(weatherTool)
35
35
  .tool(echoTool) // chain as many as you need
@@ -15,9 +15,9 @@
15
15
  "typecheck": "tsc --noEmit"
16
16
  },
17
17
  "dependencies": {
18
- "theokit": "^0.30.3",
18
+ "theokit": "^0.31.0",
19
19
  "@theokit/agents": "^0.35.2",
20
- "@theokit/sdk": "^2.13.0",
20
+ "@theokit/sdk": "^2.25.0",
21
21
  "@theokit/ui": "^1.0.0",
22
22
  "@usetheo/ui": "^0.14.0",
23
23
  "ai": "^7.0.0",
@@ -1,15 +0,0 @@
1
- /**
2
- * The agent's base system prompt.
3
- *
4
- * Kept in `agents/_lib/` (an underscore-prefixed folder) so the framework's `agents/*` route scanner
5
- * skips it — only real agents like `agents/chat.ts` become `POST /api/agents/<name>` endpoints. Import
6
- * this into an agent's `.system(...)`; grow it here (persona, guardrails, tool guidance) instead of
7
- * inlining a long string in the agent file.
8
- */
9
- import { AGENT } from '../../shared/agent.js'
10
-
11
- export const BASE_INSTRUCTIONS = `You are ${AGENT.name}, a helpful assistant living inside a TheoKit app.
12
-
13
- - Answer clearly and concisely.
14
- - When the user asks about current weather, call the \`weather\` tool instead of guessing.
15
- - If you are unsure, say so rather than inventing an answer.`
@@ -1,20 +0,0 @@
1
- # Getting started
2
-
3
- A **skill** is a Markdown file that documents a repeatable task for your agent — a recipe it (and you) can
4
- follow. Skills live in `agents/skills/` and are plain `.md`, so the route scanner ignores them; reference
5
- them from your instructions or load them into a prompt as your app grows.
6
-
7
- This example just documents the scaffold itself:
8
-
9
- ## Answer a question
10
-
11
- 1. Read the user's message.
12
- 2. If it needs live weather, call the `weather` tool (see `agents/_tools/weather.ts`).
13
- 3. Otherwise answer directly, concisely.
14
-
15
- ## Add a capability
16
-
17
- - **A tool** (an action the agent can take) → `agents/_tools/<name>.ts`, then `.tool(<name>Tool)` in
18
- `agents/chat.ts`.
19
- - **A skill** (a documented procedure) → a new `agents/skills/<name>.md` like this one.
20
- - **The persona / rules** → edit `agents/_lib/instructions.ts`.