@theokit/cli 3.0.2 → 4.0.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +239 -0
  2. package/LICENSE +2 -2
  3. package/README.md +13 -0
  4. package/dist/bin/theokit.cjs +59 -28
  5. package/dist/bin/theokit.cjs.map +1 -1
  6. package/dist/bin/theokit.js +59 -28
  7. package/dist/bin/theokit.js.map +1 -1
  8. package/dist/index.cjs +59 -28
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +127 -10
  11. package/dist/index.d.ts +127 -10
  12. package/dist/index.js +59 -28
  13. package/dist/index.js.map +1 -1
  14. package/package.json +19 -15
  15. package/templates/chatbot/.env.example +14 -0
  16. package/templates/chatbot/README.md +34 -0
  17. package/templates/chatbot/package.json +20 -0
  18. package/templates/chatbot/src/index.ts +88 -0
  19. package/templates/chatbot/tsconfig.json +12 -0
  20. package/templates/minimal/README.md +1 -1
  21. package/templates/multi-agent/.env.example +14 -0
  22. package/templates/multi-agent/README.md +33 -0
  23. package/templates/multi-agent/package.json +20 -0
  24. package/templates/multi-agent/src/index.ts +90 -0
  25. package/templates/multi-agent/tsconfig.json +12 -0
  26. package/templates/rag-agent/.env.example +14 -0
  27. package/templates/rag-agent/README.md +34 -0
  28. package/templates/rag-agent/package.json +21 -0
  29. package/templates/rag-agent/src/index.ts +115 -0
  30. package/templates/rag-agent/tsconfig.json +12 -0
  31. package/templates/telegram-bot/README.md +4 -4
  32. package/templates/telegram-bot/package.json +2 -2
  33. package/templates/telegram-bot/src/index.ts +2 -2
  34. package/templates/workflow-automation/.env.example +14 -0
  35. package/templates/workflow-automation/README.md +33 -0
  36. package/templates/workflow-automation/package.json +20 -0
  37. package/templates/workflow-automation/src/index.ts +86 -0
  38. package/templates/workflow-automation/tsconfig.json +12 -0
package/package.json CHANGED
@@ -1,18 +1,23 @@
1
1
  {
2
2
  "name": "@theokit/cli",
3
- "version": "3.0.2",
3
+ "version": "4.0.0",
4
4
  "description": "Developer CLI for @theokit/sdk — init, dev, inspect, eval. Adoption Roadmap #1.",
5
5
  "license": "Apache-2.0",
6
+ "homepage": "https://github.com/usetheokit/theokit-sdk#readme",
7
+ "bugs": "https://github.com/usetheokit/theokit-sdk/issues",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/usetheokit/theokit-sdk.git",
11
+ "directory": "packages/cli"
12
+ },
6
13
  "type": "module",
14
+ "sideEffects": false,
7
15
  "engines": {
8
16
  "node": ">=22.12.0"
9
17
  },
10
18
  "main": "./dist/index.cjs",
11
19
  "module": "./dist/index.js",
12
20
  "types": "./dist/index.d.ts",
13
- "bin": {
14
- "theokit": "./dist/bin/theokit.js"
15
- },
16
21
  "exports": {
17
22
  ".": {
18
23
  "import": {
@@ -32,16 +37,20 @@
32
37
  "LICENSE",
33
38
  "CHANGELOG.md"
34
39
  ],
40
+ "publishConfig": {
41
+ "provenance": true,
42
+ "access": "public"
43
+ },
35
44
  "dependencies": {
36
45
  "@clack/prompts": "^0.7.0",
37
46
  "commander": "^12.1.0",
38
47
  "picocolors": "^1.0.0",
39
48
  "tsx": "^4.19.0",
40
49
  "zod": "^4.0.0",
41
- "@theokit/sdk": "^4.43.0"
50
+ "@theokit/sdk": "^4.54.0"
42
51
  },
43
52
  "peerDependencies": {
44
- "@theokit/acp": "^3.0.2"
53
+ "@theokit/acp": "^4.0.0"
45
54
  },
46
55
  "peerDependenciesMeta": {
47
56
  "@theokit/acp": {
@@ -53,18 +62,13 @@
53
62
  "tsup": "^8.5.0",
54
63
  "typescript": "^5.8.0",
55
64
  "vitest": "^4.1.8",
56
- "@theokit/acp": "3.0.2"
65
+ "@theokit/acp": "4.0.0"
57
66
  },
58
- "publishConfig": {
59
- "access": "public"
60
- },
61
- "repository": {
62
- "type": "git",
63
- "url": "git+https://github.com/usetheodev/theokit-sdk.git",
64
- "directory": "packages/cli"
67
+ "bin": {
68
+ "theokit": "./dist/bin/theokit.js"
65
69
  },
66
70
  "scripts": {
67
- "build": "tsup",
71
+ "build": "tsup && node ../../tools/repair-dts-imports.mjs .",
68
72
  "typecheck": "tsc --noEmit",
69
73
  "test": "vitest run"
70
74
  }
@@ -0,0 +1,14 @@
1
+ # Copy to .env and fill in:
2
+ #
3
+ # THEOKIT_API_KEY — any non-empty string. Used by the SDK for cloud
4
+ # catalog reads; not strictly required for chat against Anthropic/OpenAI
5
+ # directly. Set to "local" if you're not using Theo cloud.
6
+ THEOKIT_API_KEY=local
7
+
8
+ # Provider key — pick ONE. The SDK auto-detects which one is set.
9
+ # ANTHROPIC_API_KEY=sk-ant-...
10
+ # OPENAI_API_KEY=sk-...
11
+ # OPENROUTER_API_KEY=sk-or-...
12
+
13
+ # Override the model (default: anthropic/claude-3-5-sonnet-latest).
14
+ # AGENT_MODEL=openai/gpt-4o-mini
@@ -0,0 +1,34 @@
1
+ # {{projectName}}
2
+
3
+ A conversational agent that remembers, scaffolded by `theokit init`.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ pnpm install
9
+ cp .env.example .env
10
+ # Edit .env — set ANTHROPIC_API_KEY / OPENAI_API_KEY / OPENROUTER_API_KEY
11
+ pnpm dev
12
+ ```
13
+
14
+ ## What this does
15
+
16
+ 1. `Agent.getOrCreate(id, …)` — a stable id, so a second run resumes the same
17
+ conversation instead of starting a fresh one.
18
+ 2. Reads a line, streams the reply, loops.
19
+ 3. Reports `result.status === "error"` with the provider's reason, so a run that
20
+ ends without answering says why.
21
+
22
+ Persistence needs no configuration: every finished turn is appended to a session
23
+ transcript automatically. `SESSION_DIR` only chooses where it lands.
24
+
25
+ ## Requirements
26
+
27
+ - Node 22.12+.
28
+ - One of: Anthropic / OpenAI / OpenRouter API key.
29
+
30
+ ## Next steps
31
+
32
+ - `SESSION_DIR=~/.claude` writes sessions the Claude Code CLI can `--continue`.
33
+ - `AGENT_ID=support` runs a second, independent conversation from the same code.
34
+ - Add tools with `Tool.create({ name, description, inputSchema, handler })`.
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "tsx --env-file=.env src/index.ts",
8
+ "start": "tsx --env-file=.env src/index.ts"
9
+ },
10
+ "dependencies": {
11
+ "@theokit/sdk": "^{{sdkVersion}}"
12
+ },
13
+ "devDependencies": {
14
+ "tsx": "^4.19.0",
15
+ "typescript": "^5.8.0"
16
+ },
17
+ "engines": {
18
+ "node": ">=22.12.0"
19
+ }
20
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * {{projectName}} — a conversational agent that remembers.
3
+ *
4
+ * Reads a line, streams the reply, and keeps the thread going. Every finished
5
+ * turn is persisted automatically, so the conversation survives a restart —
6
+ * there is nothing to wire for that. `local.sessionDir` only chooses WHERE the
7
+ * transcript lives; point it at `~/.claude` and the Claude Code CLI can
8
+ * `--continue` the same session.
9
+ */
10
+
11
+ import { createInterface } from "node:readline/promises";
12
+ import { Agent, type Run } from "@theokit/sdk";
13
+
14
+ const API_KEY = process.env.THEOKIT_API_KEY ?? "local";
15
+ const MODEL = process.env.AGENT_MODEL ?? "anthropic/claude-3-5-sonnet-latest";
16
+
17
+ /** A stable id is what makes the next run resume this conversation instead of starting a new one. */
18
+ const AGENT_ID = process.env.AGENT_ID ?? "chatbot";
19
+
20
+ /**
21
+ * Drain one run's assistant stream to stdout and hand back the text.
22
+ *
23
+ * Extracted because the loop is the same in every template and nests three deep inside whatever
24
+ * calls it — reading it once here beats re-reading it inside each caller.
25
+ */
26
+ async function streamReply(run: Run): Promise<string> {
27
+ let text = "";
28
+ for await (const event of run.stream()) {
29
+ if (event.type !== "assistant") continue;
30
+ for (const part of event.message.content) {
31
+ if (part.type === "text") {
32
+ process.stdout.write(part.text);
33
+ text += part.text;
34
+ }
35
+ }
36
+ }
37
+ return text;
38
+ }
39
+
40
+ async function main(): Promise<void> {
41
+ // `getOrCreate` — not `create`. On a second run it reattaches to the stored
42
+ // transcript for this id; `create` would start a fresh thread each time.
43
+ const agent = await Agent.getOrCreate(AGENT_ID, {
44
+ apiKey: API_KEY,
45
+ model: { id: MODEL },
46
+ local: {
47
+ cwd: process.cwd(),
48
+ ...(process.env.SESSION_DIR !== undefined ? { sessionDir: process.env.SESSION_DIR } : {}),
49
+ },
50
+ systemPrompt:
51
+ "You are a friendly assistant. Keep answers to one to three sentences, " +
52
+ "and use what was said earlier in the conversation.",
53
+ });
54
+
55
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
56
+ console.log(`${AGENT_ID} ready · model ${MODEL} · Ctrl+C to quit\n`);
57
+
58
+ // A `for await` over the prompt, not a recursive callback: the loop below
59
+ // reads as the conversation it is, and an error in one turn does not bury the
60
+ // stack under every turn before it.
61
+ try {
62
+ while (true) {
63
+ const line = (await rl.question("You: ")).trim();
64
+ if (line.length === 0) continue;
65
+
66
+ const run = await agent.send(line);
67
+ process.stdout.write("Bot: ");
68
+ await streamReply(run);
69
+
70
+ const result = await run.wait();
71
+ process.stdout.write("\n\n");
72
+
73
+ // A run can END without succeeding. Reporting the reason here is the
74
+ // difference between "the bot went quiet" and a diagnosis.
75
+ if (result.status === "error") {
76
+ console.error(`run failed: ${result.error?.message ?? "no reason reported"}\n`);
77
+ }
78
+ }
79
+ } finally {
80
+ rl.close();
81
+ await agent.dispose();
82
+ }
83
+ }
84
+
85
+ main().catch((cause) => {
86
+ console.error("chatbot failed:", cause instanceof Error ? cause.message : cause);
87
+ process.exit(1);
88
+ });
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "lib": ["ES2023", "ESNext.Disposable"]
10
+ },
11
+ "include": ["src/**/*.ts"]
12
+ }
@@ -24,6 +24,6 @@ pnpm dev
24
24
 
25
25
  ## Next steps
26
26
 
27
- - Add custom tools via `defineTool` (see `@theokit/sdk` docs).
27
+ - Add custom tools with `Tool.create({ name, description, inputSchema, handler })`.
28
28
  - Switch to a local Ollama model by setting `AGENT_MODEL=ollama/llama3.2:3b`.
29
29
  - Wire to a chat platform via `@theokit/gateway`.
@@ -0,0 +1,14 @@
1
+ # Copy to .env and fill in:
2
+ #
3
+ # THEOKIT_API_KEY — any non-empty string. Used by the SDK for cloud
4
+ # catalog reads; not strictly required for chat against Anthropic/OpenAI
5
+ # directly. Set to "local" if you're not using Theo cloud.
6
+ THEOKIT_API_KEY=local
7
+
8
+ # Provider key — pick ONE. The SDK auto-detects which one is set.
9
+ # ANTHROPIC_API_KEY=sk-ant-...
10
+ # OPENAI_API_KEY=sk-...
11
+ # OPENROUTER_API_KEY=sk-or-...
12
+
13
+ # Override the model (default: anthropic/claude-3-5-sonnet-latest).
14
+ # AGENT_MODEL=openai/gpt-4o-mini
@@ -0,0 +1,33 @@
1
+ # {{projectName}}
2
+
3
+ A router and two specialists, scaffolded by `theokit init`.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ pnpm install
9
+ cp .env.example .env
10
+ pnpm dev "Translate to French: good morning"
11
+ ```
12
+
13
+ ## What this does
14
+
15
+ 1. `AgentFactory.create({ … })` captures the configuration all three agents
16
+ share — key, model, workspace.
17
+ 2. A classifier reads the input and names the specialist.
18
+ 3. `factory.forSession(id, { systemPrompt })` builds that specialist; only the
19
+ prompt differs.
20
+
21
+ Writing `Agent.create` three times would work, and would drift the shared half
22
+ apart on the first edit. The factory is what keeps one prefix in one place.
23
+
24
+ ## Requirements
25
+
26
+ - Node 22.12+.
27
+ - One of: Anthropic / OpenAI / OpenRouter API key.
28
+
29
+ ## Next steps
30
+
31
+ - Swap `forSession` for `getOrCreate` when a specialist should remember earlier
32
+ turns.
33
+ - Add a specialist by adding one entry to `SPECIALISTS` — nothing else changes.
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "tsx --env-file=.env src/index.ts",
8
+ "start": "tsx --env-file=.env src/index.ts"
9
+ },
10
+ "dependencies": {
11
+ "@theokit/sdk": "^{{sdkVersion}}"
12
+ },
13
+ "devDependencies": {
14
+ "tsx": "^4.19.0",
15
+ "typescript": "^5.8.0"
16
+ },
17
+ "engines": {
18
+ "node": ">=22.12.0"
19
+ }
20
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * {{projectName}} — a router and two specialists.
3
+ *
4
+ * A classifier reads the input and names the specialist; that specialist answers.
5
+ * All three share one configuration prefix through `AgentFactory`, so the only
6
+ * thing that varies per agent is its system prompt.
7
+ */
8
+
9
+ import { AgentFactory, type Run } from "@theokit/sdk";
10
+
11
+ const API_KEY = process.env.THEOKIT_API_KEY ?? "local";
12
+ const MODEL = process.env.AGENT_MODEL ?? "anthropic/claude-3-5-sonnet-latest";
13
+
14
+ /** The whole difference between the three agents. */
15
+ const SPECIALISTS = {
16
+ classifier:
17
+ 'Reply with exactly one word and nothing else: "summarize" if the user wants a ' +
18
+ 'summary, "translate" if they want a translation, otherwise "summarize".',
19
+ summarizer: "Condense the input into two or three bullet points. No preamble.",
20
+ translator:
21
+ "Translate the input to the language the user names. If none is named, " +
22
+ "translate to English. Reply with the translation only.",
23
+ } as const;
24
+
25
+ type Specialist = keyof typeof SPECIALISTS;
26
+
27
+ // One prefix, many agents — this is what `AgentFactory` is for. Writing
28
+ // `Agent.create` three times would drift the shared half apart on the first edit.
29
+ const factory = AgentFactory.create({
30
+ apiKey: API_KEY,
31
+ model: { id: MODEL },
32
+ local: { cwd: process.cwd() },
33
+ });
34
+
35
+ /**
36
+ * Drain one run's assistant stream and hand back the text.
37
+ *
38
+ * Extracted because the loop is the same in every template and nests three deep inside whatever
39
+ * calls it — reading it once here beats re-reading it inside each caller.
40
+ */
41
+ async function streamReply(run: Run): Promise<string> {
42
+ let text = "";
43
+ for await (const event of run.stream()) {
44
+ if (event.type !== "assistant") continue;
45
+ for (const part of event.message.content) {
46
+ if (part.type === "text") {
47
+ text += part.text;
48
+ }
49
+ }
50
+ }
51
+ return text;
52
+ }
53
+
54
+ async function ask(role: Specialist, message: string): Promise<string> {
55
+ // `forSession` — a fresh agent per call. Use `getOrCreate` instead when the
56
+ // specialist should remember earlier turns.
57
+ const agent = await factory.forSession(`multi-${role}`, {
58
+ systemPrompt: SPECIALISTS[role],
59
+ });
60
+ try {
61
+ const run = await agent.send(message);
62
+ const reply = await streamReply(run);
63
+ const result = await run.wait();
64
+ if (result.status === "error") {
65
+ throw new Error(`${role} failed: ${result.error?.message ?? "no reason reported"}`);
66
+ }
67
+ return reply.trim();
68
+ } finally {
69
+ await agent.dispose();
70
+ }
71
+ }
72
+
73
+ async function main(): Promise<void> {
74
+ const input = process.argv[2] ?? "Summarize: the quick brown fox jumps over the lazy dog.";
75
+ console.log(`Input: ${input}\n`);
76
+
77
+ const verdict = (await ask("classifier", input)).toLowerCase();
78
+ // The model is asked for one word and usually gives one. `includes` rather
79
+ // than `===` so a stray period or quote does not silently route everything
80
+ // to the fallback — the failure would look like a bad classifier.
81
+ const role: Specialist = verdict.includes("translate") ? "translator" : "summarizer";
82
+ console.log(`Router chose: ${role} (said "${verdict}")\n`);
83
+
84
+ console.log(await ask(role, input));
85
+ }
86
+
87
+ main().catch((cause) => {
88
+ console.error("multi-agent failed:", cause instanceof Error ? cause.message : cause);
89
+ process.exit(1);
90
+ });
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "lib": ["ES2023", "ESNext.Disposable"]
10
+ },
11
+ "include": ["src/**/*.ts"]
12
+ }
@@ -0,0 +1,14 @@
1
+ # Copy to .env and fill in:
2
+ #
3
+ # THEOKIT_API_KEY — any non-empty string. Used by the SDK for cloud
4
+ # catalog reads; not strictly required for chat against Anthropic/OpenAI
5
+ # directly. Set to "local" if you're not using Theo cloud.
6
+ THEOKIT_API_KEY=local
7
+
8
+ # Provider key — pick ONE. The SDK auto-detects which one is set.
9
+ # ANTHROPIC_API_KEY=sk-ant-...
10
+ # OPENAI_API_KEY=sk-...
11
+ # OPENROUTER_API_KEY=sk-or-...
12
+
13
+ # Override the model (default: anthropic/claude-3-5-sonnet-latest).
14
+ # AGENT_MODEL=openai/gpt-4o-mini
@@ -0,0 +1,34 @@
1
+ # {{projectName}}
2
+
3
+ Retrieval-augmented generation over your own files, scaffolded by `theokit init`.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ pnpm install
9
+ cp .env.example .env
10
+ mkdir -p .theokit/memory && echo "# Notes" > .theokit/memory/MEMORY.md
11
+ pnpm dev "What is in this knowledge base?"
12
+ ```
13
+
14
+ ## What this does
15
+
16
+ 1. `Memory.openIndex({ cwd })` — SQLite full-text search, no native dependency.
17
+ 2. `index.sync()` scans `.theokit/memory/` and reports what it indexed. An empty
18
+ index says so up front, so a thin answer is not mistaken for a weak model.
19
+ 3. `Tool.create(…)` exposes the index to the model, which decides when to search.
20
+ 4. The agent answers from retrieved snippets and cites them.
21
+
22
+ `sources` belongs to the SEARCH, not to opening the index — it narrows which
23
+ corpora a single query reads.
24
+
25
+ ## Requirements
26
+
27
+ - Node 22.12+.
28
+ - One of: Anthropic / OpenAI / OpenRouter API key.
29
+
30
+ ## Next steps
31
+
32
+ - Hybrid vector recall: `Memory.openIndex({ cwd, embedding: { provider: "openai" } })`.
33
+ - LanceDB backend: `backend: "lance"` — which then REQUIRES an embedding runtime
34
+ and the `@lancedb/lancedb` peer.
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "tsx --env-file=.env src/index.ts",
8
+ "start": "tsx --env-file=.env src/index.ts"
9
+ },
10
+ "dependencies": {
11
+ "@theokit/sdk": "^{{sdkVersion}}",
12
+ "zod": "^4.0.0"
13
+ },
14
+ "devDependencies": {
15
+ "tsx": "^4.19.0",
16
+ "typescript": "^5.8.0"
17
+ },
18
+ "engines": {
19
+ "node": ">=22.12.0"
20
+ }
21
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * {{projectName}} — retrieval-augmented generation over your own files.
3
+ *
4
+ * Indexes `.theokit/memory/`, exposes the index to the model as a tool, and
5
+ * lets the model decide when to search. The agent answers from retrieved
6
+ * snippets and cites them.
7
+ */
8
+
9
+ import { Agent, Memory, type Run, Tool } from "@theokit/sdk";
10
+ import { z } from "zod";
11
+
12
+ const API_KEY = process.env.THEOKIT_API_KEY ?? "local";
13
+ const MODEL = process.env.AGENT_MODEL ?? "anthropic/claude-3-5-sonnet-latest";
14
+
15
+ /**
16
+ * Drain one run's assistant stream to stdout and hand back the text.
17
+ *
18
+ * Extracted because the loop is the same in every template and nests three deep inside whatever
19
+ * calls it — reading it once here beats re-reading it inside each caller.
20
+ */
21
+ async function streamReply(run: Run): Promise<string> {
22
+ let text = "";
23
+ for await (const event of run.stream()) {
24
+ if (event.type !== "assistant") continue;
25
+ for (const part of event.message.content) {
26
+ if (part.type === "text") {
27
+ process.stdout.write(part.text);
28
+ text += part.text;
29
+ }
30
+ }
31
+ }
32
+ return text;
33
+ }
34
+
35
+ async function main(): Promise<void> {
36
+ // Default backend is SQLite with full-text search and no native dependency.
37
+ // Add `embedding: { provider: "openai" }` for hybrid vector recall, or
38
+ // `backend: "lance"` (which then REQUIRES an embedding runtime).
39
+ const index = await Memory.openIndex({ cwd: process.cwd() });
40
+
41
+ try {
42
+ const synced = await index.sync();
43
+ console.log(
44
+ `Indexed ${synced.filesScanned} file(s) · ${synced.chunksWritten} chunk(s) written · ` +
45
+ `backend ${index.status().backend}`,
46
+ );
47
+
48
+ if (index.status().chunksIndexed === 0) {
49
+ // Saying this up front is the difference between "the model is bad" and
50
+ // "there was nothing to retrieve".
51
+ console.warn(
52
+ "\nThe index is empty. Put some markdown under .theokit/memory/ and re-run,\n" +
53
+ "otherwise the agent has nothing to cite and will say so.\n",
54
+ );
55
+ }
56
+
57
+ const searchKnowledge = Tool.create({
58
+ name: "search_knowledge",
59
+ description:
60
+ "Search the local knowledge base and return matching snippets with citations. " +
61
+ "Call this before answering any question about the user's own documents.",
62
+ inputSchema: z.object({
63
+ query: z.string().describe("What to look for, in natural language."),
64
+ maxResults: z.number().int().min(1).max(20).default(5),
65
+ }),
66
+ handler: async ({ query, maxResults }) => {
67
+ // `sources` belongs to the SEARCH, not to opening the index: it narrows
68
+ // which corpora this one query reads.
69
+ const hits = await index.search(query, {
70
+ maxResults,
71
+ sources: ["memory", "wiki"],
72
+ });
73
+ if (hits.length === 0) return "No matching documents.";
74
+ return hits
75
+ .map((h, i) => `[${i + 1}] ${h.citation} (score ${h.score.toFixed(2)})\n${h.snippet}`)
76
+ .join("\n\n");
77
+ },
78
+ });
79
+
80
+ const agent = await Agent.create({
81
+ agentId: "rag-agent",
82
+ apiKey: API_KEY,
83
+ model: { id: MODEL },
84
+ systemPrompt:
85
+ "Answer from the local knowledge base. Call search_knowledge first, cite the " +
86
+ "sources it returns by their [n] markers, and say plainly when the base does " +
87
+ "not contain the answer rather than filling the gap from memory.",
88
+ tools: [searchKnowledge],
89
+ local: { cwd: process.cwd() },
90
+ });
91
+
92
+ try {
93
+ const question = process.argv[2] ?? "What is in this knowledge base?";
94
+ console.log(`\nQuestion: ${question}\n`);
95
+
96
+ const run = await agent.send(question);
97
+ await streamReply(run);
98
+ const result = await run.wait();
99
+ process.stdout.write("\n");
100
+ if (result.status === "error") {
101
+ console.error(`\nrun failed: ${result.error?.message ?? "no reason reported"}`);
102
+ process.exitCode = 1;
103
+ }
104
+ } finally {
105
+ await agent.dispose();
106
+ }
107
+ } finally {
108
+ await index.close();
109
+ }
110
+ }
111
+
112
+ main().catch((cause) => {
113
+ console.error("rag-agent failed:", cause instanceof Error ? cause.message : cause);
114
+ process.exit(1);
115
+ });
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "lib": ["ES2023", "ESNext.Disposable"]
10
+ },
11
+ "include": ["src/**/*.ts"]
12
+ }
@@ -22,14 +22,14 @@ Then DM your bot in Telegram. Every message gets streamed through the agent.
22
22
  ## What this does
23
23
 
24
24
  - `GatewayRunner` + `TelegramAdapter` handle the platform plumbing.
25
- - `createAgentFactory` keeps one persistent agent per chat (memory + state
26
- preserved across turns).
25
+ - `AgentFactory.create` captures the shared config once; `factory.getOrCreate`
26
+ keeps one persistent agent per chat, so memory and state survive across turns.
27
27
  - Each inbound message → `agent.send` → reply back via `ctx.reply`.
28
28
 
29
29
  ## Customize
30
30
 
31
31
  - Switch model: `AGENT_MODEL=ollama/llama3.2:3b` for local Ollama.
32
- - Add custom tools: import `defineTool` from `@theokit/sdk` and pass via
33
- `createAgentFactory({ tools: [...] })`.
32
+ - Add custom tools: `Tool.create({ name, description, inputSchema, handler })`
33
+ from `@theokit/sdk`, passed as `AgentFactory.create({ tools: [...] })`.
34
34
  - Slash commands: `runner.command("name", handler)` — see
35
35
  `@theokit/gateway` docs.
@@ -9,8 +9,8 @@
9
9
  },
10
10
  "dependencies": {
11
11
  "@theokit/sdk": "^{{sdkVersion}}",
12
- "@theokit/gateway": "^{{sdkVersion}}",
13
- "@theokit/gateway-telegram": "^{{sdkVersion}}",
12
+ "@theokit/gateway": "^0.5.0",
13
+ "@theokit/gateway-telegram": "^0.1.0",
14
14
  "grammy": "^1.30.0",
15
15
  "zod": "^4.0.0"
16
16
  },