@tryhamster/gerbil 1.1.2 → 1.2.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 (110) hide show
  1. package/README.md +18 -16
  2. package/dist/{architectures-wan0swXu.mjs → architectures-Ol7yUmKf.mjs} +128 -68
  3. package/dist/architectures-Ol7yUmKf.mjs.map +1 -0
  4. package/dist/cli.mjs +8 -8
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/{defaults-9komdrbY.mjs → defaults-bJcmOaRG.mjs} +2 -2
  7. package/dist/defaults-bJcmOaRG.mjs.map +1 -0
  8. package/dist/frameworks/express.mjs +1 -1
  9. package/dist/frameworks/fastify.mjs +1 -1
  10. package/dist/frameworks/hono.mjs +1 -1
  11. package/dist/frameworks/next.d.mts +2 -2
  12. package/dist/frameworks/next.mjs +1 -1
  13. package/dist/frameworks/trpc.mjs +1 -1
  14. package/dist/gerbil-BjGA5L2E.mjs +4 -0
  15. package/dist/{gerbil-DZbWbIm2.d.mts → gerbil-CCVOEU-J.d.mts} +2 -2
  16. package/dist/{gerbil-DZbWbIm2.d.mts.map → gerbil-CCVOEU-J.d.mts.map} +1 -1
  17. package/dist/{gerbil-CQspp8fS.mjs → gerbil-Cv7rXGOV.mjs} +2 -2
  18. package/dist/{gerbil-CQspp8fS.mjs.map → gerbil-Cv7rXGOV.mjs.map} +1 -1
  19. package/dist/gpu/hooks.d.mts +27 -10
  20. package/dist/gpu/hooks.d.mts.map +1 -1
  21. package/dist/gpu/hooks.mjs +19 -12
  22. package/dist/gpu/hooks.mjs.map +1 -1
  23. package/dist/gpu/index.d.mts +2 -2
  24. package/dist/gpu/index.mjs +5 -5
  25. package/dist/{gpu-BYCQ5aTI.mjs → gpu-BWLMlkVW.mjs} +78 -21
  26. package/dist/gpu-BWLMlkVW.mjs.map +1 -0
  27. package/dist/{index-CKszv3lt.d.mts → index-CSLqgNu-.d.mts} +212 -133
  28. package/dist/index-CSLqgNu-.d.mts.map +1 -0
  29. package/dist/index.d.mts +2 -2
  30. package/dist/index.mjs +6 -6
  31. package/dist/integrations/ai-sdk.mjs +1 -1
  32. package/dist/integrations/langchain.mjs +1 -1
  33. package/dist/integrations/llamaindex.mjs +1 -1
  34. package/dist/integrations/mcp.d.mts +2 -2
  35. package/dist/integrations/mcp.mjs +4 -4
  36. package/dist/{mcp-CszII8GJ.mjs → mcp-C0yKaH39.mjs} +3 -3
  37. package/dist/{mcp-CszII8GJ.mjs.map → mcp-C0yKaH39.mjs.map} +1 -1
  38. package/dist/{moonshine-stt-DDUm5WfP.mjs → moonshine-stt-C4GKK-h5.mjs} +45 -5
  39. package/dist/{moonshine-stt-DDUm5WfP.mjs.map → moonshine-stt-C4GKK-h5.mjs.map} +1 -1
  40. package/dist/moonshine-stt-zJ9YEi1X.mjs +4 -0
  41. package/dist/{one-liner-DVche8Eg.mjs → one-liner-DfJh2O0X.mjs} +2 -2
  42. package/dist/{one-liner-DVche8Eg.mjs.map → one-liner-DfJh2O0X.mjs.map} +1 -1
  43. package/dist/repl-DTSxHD6t.mjs +9 -0
  44. package/dist/skills/index.d.mts +2 -2
  45. package/dist/skills/index.d.mts.map +1 -1
  46. package/dist/skills/index.mjs +3 -3
  47. package/dist/{skills-BQI-VSn2.mjs → skills-B5cDRVJ1.mjs} +2 -2
  48. package/dist/{skills-BQI-VSn2.mjs.map → skills-B5cDRVJ1.mjs.map} +1 -1
  49. package/package.json +1 -2
  50. package/dist/architectures-wan0swXu.mjs.map +0 -1
  51. package/dist/defaults-9komdrbY.mjs.map +0 -1
  52. package/dist/gerbil-tHnYb9nZ.mjs +0 -4
  53. package/dist/gpu-BYCQ5aTI.mjs.map +0 -1
  54. package/dist/index-CKszv3lt.d.mts.map +0 -1
  55. package/dist/moonshine-stt-BtR-KiSP.mjs +0 -4
  56. package/dist/repl-BYJA2U7t.mjs +0 -9
  57. package/docs/PROJECT-STATE.md +0 -321
  58. package/docs/adding-a-model-family.md +0 -280
  59. package/docs/ai-sdk.md +0 -205
  60. package/docs/architecture/README.md +0 -84
  61. package/docs/architecture/caching.md +0 -227
  62. package/docs/architecture/inference.md +0 -176
  63. package/docs/architecture/overview.md +0 -189
  64. package/docs/architecture/streaming.md +0 -261
  65. package/docs/architecture/webgpu.md +0 -213
  66. package/docs/browser.md +0 -762
  67. package/docs/cli.md +0 -155
  68. package/docs/embeddings.md +0 -156
  69. package/docs/frameworks.md +0 -90
  70. package/docs/gerbil-site-native-migration.md +0 -217
  71. package/docs/gpu-engine/architectures.md +0 -398
  72. package/docs/gpu-engine/ir.md +0 -372
  73. package/docs/gpu-engine/kernels.md +0 -718
  74. package/docs/gpu-engine/paper.html +0 -1759
  75. package/docs/gpu-engine/paper.md +0 -2109
  76. package/docs/gpu-engine/safetensors.md +0 -312
  77. package/docs/gpu-engine/tokenizer.md +0 -302
  78. package/docs/kernel-research-queue.md +0 -85
  79. package/docs/mcp-client.md +0 -224
  80. package/docs/mcp.md +0 -109
  81. package/docs/memory-rag.md +0 -91
  82. package/docs/memory.md +0 -301
  83. package/docs/metal-safari-intel.md +0 -190
  84. package/docs/mobile-failure-diagnosis.md +0 -124
  85. package/docs/mobile.md +0 -99
  86. package/docs/observability.md +0 -230
  87. package/docs/onnx-removal-plan.md +0 -339
  88. package/docs/repl.md +0 -473
  89. package/docs/research/autoresearch-portable.md +0 -933
  90. package/docs/research/dispatch-reduction-hivemind.md +0 -84
  91. package/docs/research/ios-safari-model-caching.md +0 -117
  92. package/docs/research/mobile-webgpu-speed-fusion.md +0 -135
  93. package/docs/research/native-stt-model-selection.md +0 -49
  94. package/docs/research/native-tts-model-selection.md +0 -90
  95. package/docs/research/native-vs-chromium-decision.md +0 -152
  96. package/docs/research/nemotron-mamba2-inference.md +0 -910
  97. package/docs/research/qwen35-multimodal.md +0 -293
  98. package/docs/research/qwen36-gemma4-targets.md +0 -337
  99. package/docs/research/sota-embedding-models.md +0 -179
  100. package/docs/research/sota-mobile-models-2026.md +0 -263
  101. package/docs/research/sota-modality-models.md +0 -202
  102. package/docs/research/tps-baselines.md +0 -71
  103. package/docs/research/webgpu-m4-reference.md +0 -104
  104. package/docs/site-update-plan.md +0 -155
  105. package/docs/skills.md +0 -261
  106. package/docs/structured-output.md +0 -123
  107. package/docs/stt.md +0 -111
  108. package/docs/tools.md +0 -304
  109. package/docs/tts.md +0 -147
  110. package/docs/vision.md +0 -158
package/docs/skills.md DELETED
@@ -1,261 +0,0 @@
1
- # Gerbil Skills
2
-
3
- Skills are reusable AI tasks with Zod-validated inputs and outputs.
4
-
5
- ## Built-in Skills
6
-
7
- ```typescript
8
- import {
9
- commit, summarize, explain, review,
10
- test, translate, extract, title
11
- } from "@tryhamster/gerbil/skills";
12
- ```
13
-
14
- ### commit
15
-
16
- Generate git commit messages from staged changes.
17
-
18
- ```typescript
19
- const msg = await commit({
20
- type: "conventional", // "conventional" | "simple" | "detailed"
21
- maxLength: 72
22
- });
23
- ```
24
-
25
- ### summarize
26
-
27
- Summarize content.
28
-
29
- ```typescript
30
- const summary = await summarize({
31
- content: document,
32
- length: "short", // "short" | "medium" | "long"
33
- format: "bullets", // "paragraph" | "bullets"
34
- focus: ["key points"]
35
- });
36
- ```
37
-
38
- ### explain
39
-
40
- Explain code or concepts.
41
-
42
- ```typescript
43
- const explanation = await explain({
44
- content: code,
45
- level: "beginner", // "beginner" | "intermediate" | "expert"
46
- language: "typescript"
47
- });
48
- ```
49
-
50
- ### review
51
-
52
- Code review.
53
-
54
- ```typescript
55
- const feedback = await review({
56
- code,
57
- focus: ["security", "performance"], // "security" | "performance" | "style" | "bugs" | "all"
58
- format: "detailed" // "inline" | "summary" | "detailed"
59
- });
60
- ```
61
-
62
- ### test
63
-
64
- Generate tests.
65
-
66
- ```typescript
67
- const tests = await test({
68
- code,
69
- framework: "vitest", // "jest" | "vitest" | "mocha" | "playwright"
70
- style: "unit" // "unit" | "integration" | "e2e"
71
- });
72
- ```
73
-
74
- ### translate
75
-
76
- Translate text.
77
-
78
- ```typescript
79
- const spanish = await translate({
80
- text,
81
- to: "es",
82
- from: "en", // optional, auto-detected
83
- preserveFormatting: true
84
- });
85
- ```
86
-
87
- ### extract
88
-
89
- Extract structured data.
90
-
91
- ```typescript
92
- const data = await extract({
93
- content: "John is 32 and lives in NYC",
94
- schema: z.object({ name: z.string(), age: z.number(), city: z.string() }),
95
- context: "Extract person info"
96
- });
97
- ```
98
-
99
- ### title
100
-
101
- Generate titles.
102
-
103
- ```typescript
104
- const heading = await title({
105
- content: article,
106
- style: "seo", // "professional" | "clickbait" | "seo" | "simple"
107
- maxLength: 60
108
- });
109
- ```
110
-
111
- ## Custom Skills
112
-
113
- ### defineSkill
114
-
115
- Create a custom skill:
116
-
117
- ```typescript
118
- import { defineSkill } from "@tryhamster/gerbil/skills";
119
- import { z } from "zod";
120
-
121
- export const sentiment = defineSkill({
122
- // Required
123
- name: "sentiment", // kebab-case
124
- description: "Analyze text sentiment",
125
-
126
- // Input/Output validation
127
- input: z.object({
128
- text: z.string(),
129
- detailed: z.boolean().default(false)
130
- }),
131
- output: z.object({
132
- sentiment: z.enum(["positive", "negative", "neutral"]),
133
- confidence: z.number(),
134
- keywords: z.array(z.string()).optional()
135
- }),
136
-
137
- // Defaults
138
- temperature: 0.3,
139
- maxTokens: 100,
140
- thinking: false,
141
-
142
- // Model (skill loads this automatically if different from current)
143
- model: "qwen2.5-coder-0.5b",
144
-
145
- // Metadata
146
- version: "1.0.0",
147
- author: "your-name",
148
-
149
- // Implementation - receives (input, gerbil)
150
- async run(input, gerbil) {
151
- const prompt = input.detailed
152
- ? `Analyze sentiment with keywords: ${input.text}`
153
- : `What is the sentiment: ${input.text}`;
154
-
155
- return gerbil.json(prompt, { schema: this.output });
156
- },
157
- });
158
- ```
159
-
160
- ### File Convention
161
-
162
- Skills can be loaded from files matching `*.skill.ts` or `*.skill.js`:
163
-
164
- ```typescript
165
- // skills/sentiment.skill.ts
166
- import { defineSkill } from "@tryhamster/gerbil/skills";
167
-
168
- export default defineSkill({
169
- name: "sentiment",
170
- // ...
171
- });
172
- ```
173
-
174
- ### Loading Skills
175
-
176
- ```typescript
177
- import { loadSkills, loadSkillPackage, useSkill } from "@tryhamster/gerbil/skills";
178
-
179
- // Load from directory
180
- await loadSkills("./skills"); // loads *.skill.ts files
181
-
182
- // Load from npm package
183
- await loadSkillPackage("gerbil-skills-extra");
184
-
185
- // Use by name
186
- const sentiment = useSkill("sentiment");
187
- const result = await sentiment({ text: "I love this!" });
188
- ```
189
-
190
- ### Skill-Specific Models
191
-
192
- Skills can specify which model they prefer:
193
-
194
- ```typescript
195
- export const codeReview = defineSkill({
196
- name: "code-review",
197
- description: "Review code for issues",
198
- model: "qwen2.5-coder-0.5b", // Uses coder model
199
-
200
- async run(input, gerbil) {
201
- // gerbil is already loaded with qwen2.5-coder-0.5b
202
- return gerbil.generate(`Review this code: ${input.code}`);
203
- },
204
- });
205
- ```
206
-
207
- When a skill specifies a different model than the currently loaded one, Gerbil automatically switches models. To avoid this overhead, you can pre-load the skill's model or pass your own Gerbil instance:
208
-
209
- ```typescript
210
- // Option 1: Let skill switch automatically (may incur loading time)
211
- const result = await codeReview({ code: myCode });
212
-
213
- // Option 2: Pass pre-loaded Gerbil instance
214
- const g = new Gerbil();
215
- await g.loadModel("qwen2.5-coder-0.5b");
216
- const result = await codeReview.run({ code: myCode }, g);
217
- ```
218
-
219
- ### Registry API
220
-
221
- ```typescript
222
- import {
223
- listSkills, // Get all skill names
224
- getSkillInfo, // Get skill metadata
225
- hasSkill, // Check if skill exists
226
- removeSkill, // Unregister a skill
227
- clearSkills // Clear all skills
228
- } from "@tryhamster/gerbil/skills";
229
-
230
- console.log(listSkills());
231
- // ["commit", "summarize", "explain", "review", ...]
232
-
233
- const info = getSkillInfo("commit");
234
- // { name, description, version, author, builtin: true }
235
- ```
236
-
237
- ## REPL Skills View
238
-
239
- In the Gerbil REPL, press `2` or navigate to **Skills** to access all available skills.
240
-
241
- **Controls:**
242
- - **Up/Down** — Navigate skill list
243
- - **Enter** — Select and run a skill
244
- - **c** — Create a new skill (opens wizard)
245
- - **Esc** — Back to menu
246
-
247
- The first option **"+ Create new skill"** opens a guided wizard that helps you build a custom skill step-by-step.
248
-
249
- ---
250
-
251
- ## MCP Integration
252
-
253
- Skills are automatically exposed as MCP tools:
254
-
255
- ```bash
256
- gerbil serve --mcp
257
- ```
258
-
259
- Each skill becomes a tool: `gerbil_commit`, `gerbil_summarize`, etc.
260
-
261
- Custom skills loaded via `loadSkills()` are also exposed.
@@ -1,123 +0,0 @@
1
- # Structured Output
2
-
3
- Gerbil can make a model return a **JSON object** instead of free-form text. The engine
4
- generates, extracts the JSON from the output, validates it, and — if it fails — retries with
5
- a corrective nudge until the result is valid or the retry budget is exhausted.
6
-
7
- This is available at three levels:
8
-
9
- - **Engine** — `WebGPUEngine.generateObject()` (lowest level)
10
- - **`Gerbil` class** — `g.generateObject()` (Node-facing wrapper)
11
- - **One-liner** — `generateObject()` (zero-setup singleton)
12
- - **React** — the `useObject` hook (`@tryhamster/gerbil/gpu/hooks`)
13
-
14
- > `generateObject` is distinct from the Zod-driven `Gerbil.json()`. `json()` parses against a
15
- > Zod schema; `generateObject()` validates with a predicate or a minimal
16
- > `{ required: [...] }` schema and is the same code path the `useObject` hook uses.
17
-
18
- ## Validation
19
-
20
- The `schema` option is an `ObjectValidator`, either:
21
-
22
- - **A predicate** `(o: unknown) => boolean` — full control; return `true` when the parsed
23
- value is acceptable.
24
- - **A minimal schema object** `{ required: string[]; properties?: Record<string, unknown> }`
25
- — only the `required` key set is enforced (every key must exist on the parsed object).
26
- - **Omitted** — any syntactically valid JSON value (object or array) is accepted.
27
-
28
- `maxRetries` (default `4`) is the number of retries **after** the first attempt, so up to
29
- `maxRetries + 1` generations run. The result includes `attempts` (1 = first try).
30
-
31
- ## Engine
32
-
33
- ```typescript
34
- import { WebGPUEngine } from "@tryhamster/gerbil/gpu";
35
-
36
- const engine = await WebGPUEngine.create({ repo: "mlx-community/Qwen3.5-0.8B-4bit" });
37
-
38
- const { object, text, attempts } = await engine.generateObject(
39
- 'Extract {name, age} from: "I am Sarah, 28"',
40
- { schema: { required: ["name", "age"] }, maxRetries: 4 },
41
- );
42
- // object === { name: "Sarah", age: 28 }
43
- ```
44
-
45
- `generateObject` extends the engine's `GenerateOptions`, so `maxTokens`, `systemPrompt`, and
46
- `sampling` (e.g. `{ temperature: 0.2 }`) are all available.
47
-
48
- ## `Gerbil` class
49
-
50
- ```typescript
51
- import { Gerbil } from "@tryhamster/gerbil";
52
-
53
- const g = new Gerbil();
54
- await g.loadModel("qwen3.5-0.8b");
55
-
56
- const { object } = await g.generateObject<{ primes: number[] }>(
57
- "List the first 3 primes as {primes: number[]}",
58
- { schema: (o) => Array.isArray((o as any).primes) },
59
- );
60
- ```
61
-
62
- ## One-liner
63
-
64
- ```typescript
65
- import { generateObject } from "@tryhamster/gerbil";
66
-
67
- const { object } = await generateObject(
68
- "Return {ok: true}",
69
- { model: "qwen3.5-0.8b", schema: { required: ["ok"] } },
70
- );
71
- ```
72
-
73
- ## React (`useObject`)
74
-
75
- ```tsx
76
- import { useObject } from "@tryhamster/gerbil/gpu/hooks";
77
-
78
- function CityExtractor() {
79
- const { generate, object, isGenerating, attempts } = useObject<{ city: string }>({
80
- model: "mlx-community/Qwen3.5-0.8B-4bit",
81
- });
82
-
83
- return (
84
- <div>
85
- <button
86
- disabled={isGenerating}
87
- onClick={() =>
88
- generate("Extract the city: I live in Paris", { schema: { required: ["city"] } })
89
- }
90
- >
91
- Extract
92
- </button>
93
- {object && <pre>{JSON.stringify(object, null, 2)} ({attempts} attempts)</pre>}
94
- </div>
95
- );
96
- }
97
- ```
98
-
99
- ## CLI
100
-
101
- ```bash
102
- # Inline, no schema (any valid JSON accepted)
103
- gerbil object "Return {ok: true} as JSON"
104
-
105
- # With a schema file (a minimal { required: [...] } validator)
106
- echo '{ "required": ["name", "age"] }' > person.json
107
- gerbil object "Extract {name, age}: I am Sarah, 28" --schema person.json
108
-
109
- # Flags: --model, --max-tokens, --temperature, --retries, --schema
110
- ```
111
-
112
- The command prints the parsed object as pretty JSON followed by the attempt count.
113
-
114
- ## Types
115
-
116
- ```typescript
117
- import type {
118
- GenerateObjectOptions,
119
- GenerateObjectResult,
120
- ObjectSchema,
121
- ObjectValidator,
122
- } from "@tryhamster/gerbil";
123
- ```
package/docs/stt.md DELETED
@@ -1,111 +0,0 @@
1
- # Speech-to-Text (STT)
2
-
3
- Gerbil transcribes audio with **Moonshine** — a native, raw-waveform encoder/decoder STT
4
- model (no FFT, no log-mel) running on the WebGPU engine. It uses a Conv1d front-end over the
5
- raw waveform, encodes once, then autoregressively decodes with cross-attention into the
6
- frozen encoder K/V. No ONNX, no API keys.
7
-
8
- > **Pre-1.0.** Moonshine is the only STT path. The old Whisper ONNX/transformers.js lane has
9
- > been removed. The `Gerbil`-class `transcribe()` method still works but now runs native
10
- > Moonshine under the hood (see [below](#gerbil-class-transcribe-native-wrapper)).
11
-
12
- ## Quick Start
13
-
14
- Moonshine has its own engine class, `MoonshineSTT`. Feed it raw **16 kHz mono PCM**
15
- (`Float32Array`).
16
-
17
- ```typescript
18
- import { MoonshineSTT } from "@tryhamster/gerbil/gpu";
19
-
20
- const stt = await MoonshineSTT.create({ repo: "UsefulSensors/moonshine-base" });
21
-
22
- // pcm: Float32Array, 16 kHz mono
23
- const result = await stt.transcribe(pcm);
24
- console.log(result.text);
25
-
26
- stt.destroy();
27
- ```
28
-
29
- ## API
30
-
31
- ### `MoonshineSTT.create(options?)`
32
-
33
- ```typescript
34
- interface MoonshineSTTOptions {
35
- /** HF repo (default "UsefulSensors/moonshine-base"). */
36
- repo?: string;
37
- revision?: string;
38
- hfToken?: string;
39
- cacheDir?: string;
40
- onProgress?: (loaded: number, total: number, message: string) => void;
41
- }
42
- ```
43
-
44
- ### `transcribe(pcm, options?)`
45
-
46
- ```typescript
47
- interface TranscribeOptions {
48
- /** Stop after this many decoded tokens (default 194). */
49
- maxNewTokens?: number;
50
- }
51
-
52
- interface TranscribeResult {
53
- text: string;
54
- /** Decoded token ids (excluding the start token, including trailing EOS). */
55
- tokens: number[];
56
- /** Encoder frames produced by the conv frontend. */
57
- encoderFrames: number;
58
- /** Audio duration in seconds (samples / 16000). */
59
- audioSeconds: number;
60
- }
61
- ```
62
-
63
- `pcm` must be raw 16 kHz mono `Float32Array` samples. If your audio is a WAV file or at a
64
- different sample rate, decode and resample it to 16 kHz first.
65
-
66
- ### Decoding / resampling helpers
67
-
68
- ```typescript
69
- // Reuse the audio utilities from the core package to get 16 kHz mono PCM.
70
- import { decodeWav, resampleAudio } from "@tryhamster/gerbil/core/stt";
71
-
72
- const { audio, sampleRate } = decodeWav(wavUint8Array);
73
- const pcm16k = sampleRate === 16000 ? audio : resampleAudio(audio, sampleRate, 16000);
74
-
75
- const { text } = await stt.transcribe(pcm16k);
76
- ```
77
-
78
- ## Model
79
-
80
- ### Moonshine
81
-
82
- - **Architecture:** `MoonshineForConditionalGeneration` (encoder-decoder)
83
- - **Front-end:** raw-waveform Conv1d (no FFT / log-mel)
84
- - **Default repo:** `UsefulSensors/moonshine-base`
85
- - **Sample rate:** 16 kHz mono
86
- - **Language:** English (the native engine is English-only; multilingual Whisper has been removed).
87
-
88
- ---
89
-
90
- ## `Gerbil`-class `transcribe()` (native wrapper)
91
-
92
- > The Whisper ONNX/transformers.js lane (and its multilingual support) has been removed. The
93
- > `Gerbil`-class `transcribe()` method now runs native Moonshine: it requires WebGPU,
94
- > accepts 16 kHz mono `Float32Array` PCM (not WAV bytes), is English-only, and does not
95
- > produce timestamps. The browser `useVoiceInput` hook is gone — use `useSTT` from
96
- > `@tryhamster/gerbil/gpu/hooks`. The AI SDK `gerbil.transcription()` provider also routes
97
- > through this native path.
98
-
99
- ```typescript
100
- import { Gerbil } from "@tryhamster/gerbil";
101
-
102
- const g = new Gerbil();
103
- const pcm16kMono = /* Float32Array @ 16 kHz */;
104
- const result = await g.transcribe(pcm16kMono);
105
- console.log(result.text);
106
- ```
107
-
108
- ## See Also
109
-
110
- - [Text-to-Speech (TTS)](./tts.md) — native Kani-TTS-2
111
- - [Browser Hooks](./browser.md) — React hooks