@tryhamster/gerbil 1.0.0-rc.1 → 1.0.0-rc.11

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 (90) hide show
  1. package/README.md +14 -5
  2. package/dist/browser/{index.d.mts → index.d.ts} +354 -3
  3. package/dist/browser/index.d.ts.map +1 -0
  4. package/dist/browser/{index.mjs → index.js} +631 -259
  5. package/dist/browser/index.js.map +1 -0
  6. package/dist/{chrome-backend-Y9F7W5VQ.mjs → chrome-backend-CORwaIyC.mjs} +1 -1
  7. package/dist/{chrome-backend-Y9F7W5VQ.mjs.map → chrome-backend-CORwaIyC.mjs.map} +1 -1
  8. package/dist/{chrome-backend-JEPeM2YE.mjs → chrome-backend-DIKYoWj-.mjs} +1 -1
  9. package/dist/cli.mjs +14 -15
  10. package/dist/cli.mjs.map +1 -1
  11. package/dist/frameworks/express.d.mts +1 -1
  12. package/dist/frameworks/express.mjs +3 -4
  13. package/dist/frameworks/express.mjs.map +1 -1
  14. package/dist/frameworks/fastify.d.mts +1 -1
  15. package/dist/frameworks/fastify.mjs +2 -3
  16. package/dist/frameworks/fastify.mjs.map +1 -1
  17. package/dist/frameworks/hono.d.mts +1 -1
  18. package/dist/frameworks/hono.mjs +2 -3
  19. package/dist/frameworks/hono.mjs.map +1 -1
  20. package/dist/frameworks/next.d.mts +2 -2
  21. package/dist/frameworks/next.mjs +2 -3
  22. package/dist/frameworks/next.mjs.map +1 -1
  23. package/dist/frameworks/react.d.mts +1 -1
  24. package/dist/frameworks/trpc.d.mts +1 -1
  25. package/dist/frameworks/trpc.mjs +2 -3
  26. package/dist/frameworks/trpc.mjs.map +1 -1
  27. package/dist/gerbil-DJGqq7BX.mjs +4 -0
  28. package/dist/{gerbil-yoSpRHgv.mjs → gerbil-DoDGHe6Z.mjs} +187 -19
  29. package/dist/gerbil-DoDGHe6Z.mjs.map +1 -0
  30. package/dist/{gerbil-POAz8peb.d.mts → gerbil-qOTe1nl2.d.mts} +2 -2
  31. package/dist/{gerbil-POAz8peb.d.mts.map → gerbil-qOTe1nl2.d.mts.map} +1 -1
  32. package/dist/index.d.mts +19 -3
  33. package/dist/index.d.mts.map +1 -1
  34. package/dist/index.mjs +6 -7
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/integrations/ai-sdk.d.mts +1 -1
  37. package/dist/integrations/ai-sdk.mjs +4 -5
  38. package/dist/integrations/ai-sdk.mjs.map +1 -1
  39. package/dist/integrations/langchain.d.mts +1 -1
  40. package/dist/integrations/langchain.mjs +2 -3
  41. package/dist/integrations/langchain.mjs.map +1 -1
  42. package/dist/integrations/llamaindex.d.mts +1 -1
  43. package/dist/integrations/llamaindex.mjs +2 -3
  44. package/dist/integrations/llamaindex.mjs.map +1 -1
  45. package/dist/integrations/mcp-client.mjs +2 -2
  46. package/dist/integrations/mcp.d.mts +2 -2
  47. package/dist/integrations/mcp.mjs +5 -6
  48. package/dist/kokoro-BNTb6egA.mjs +20210 -0
  49. package/dist/kokoro-BNTb6egA.mjs.map +1 -0
  50. package/dist/{mcp-Bitg4sjX.mjs → mcp-kzDDWIoS.mjs} +3 -3
  51. package/dist/{mcp-Bitg4sjX.mjs.map → mcp-kzDDWIoS.mjs.map} +1 -1
  52. package/dist/{one-liner-B1rmFto6.mjs → one-liner-DxnNs_JK.mjs} +2 -2
  53. package/dist/{one-liner-B1rmFto6.mjs.map → one-liner-DxnNs_JK.mjs.map} +1 -1
  54. package/dist/repl-DGUw4fCc.mjs +9 -0
  55. package/dist/skills/index.d.mts +2 -2
  56. package/dist/skills/index.d.mts.map +1 -1
  57. package/dist/skills/index.mjs +4 -5
  58. package/dist/{skills-5DxAV-rn.mjs → skills-DulrOPeP.mjs} +12 -12
  59. package/dist/skills-DulrOPeP.mjs.map +1 -0
  60. package/dist/stt-1WIefHwc.mjs +3 -0
  61. package/dist/{stt-Bv_dum-R.mjs → stt-CG_7KB_0.mjs} +3 -2
  62. package/dist/stt-CG_7KB_0.mjs.map +1 -0
  63. package/dist/{tools-IYPrqoek.mjs → tools-Bi1P7Xoy.mjs} +2 -2
  64. package/dist/{tools-IYPrqoek.mjs.map → tools-Bi1P7Xoy.mjs.map} +1 -1
  65. package/dist/{tts-5yWeP_I0.mjs → tts-B1pZMlDv.mjs} +1 -1
  66. package/dist/{tts-DG6denWG.mjs → tts-CyHhcLtN.mjs} +6 -4
  67. package/dist/tts-CyHhcLtN.mjs.map +1 -0
  68. package/dist/{types-s6Py2_DL.d.mts → types-CiTc7ez3.d.mts} +1 -1
  69. package/dist/{types-s6Py2_DL.d.mts.map → types-CiTc7ez3.d.mts.map} +1 -1
  70. package/dist/{utils-CkB4Roi6.mjs → utils-CZBZ8dgR.mjs} +1 -1
  71. package/dist/{utils-CkB4Roi6.mjs.map → utils-CZBZ8dgR.mjs.map} +1 -1
  72. package/docs/architecture/overview.md +15 -7
  73. package/docs/tts.md +11 -8
  74. package/package.json +6 -6
  75. package/dist/browser/index.d.mts.map +0 -1
  76. package/dist/browser/index.mjs.map +0 -1
  77. package/dist/gerbil-DeQlX_Mt.mjs +0 -5
  78. package/dist/gerbil-yoSpRHgv.mjs.map +0 -1
  79. package/dist/models-BAtL8qsA.mjs +0 -171
  80. package/dist/models-BAtL8qsA.mjs.map +0 -1
  81. package/dist/models-CE0fBq0U.d.mts +0 -22
  82. package/dist/models-CE0fBq0U.d.mts.map +0 -1
  83. package/dist/repl-D20JO260.mjs +0 -10
  84. package/dist/skills-5DxAV-rn.mjs.map +0 -1
  85. package/dist/stt-Bv_dum-R.mjs.map +0 -1
  86. package/dist/stt-KzSoNvwI.mjs +0 -3
  87. package/dist/tts-DG6denWG.mjs.map +0 -1
  88. /package/dist/{auto-update-DsWBBnEk.mjs → auto-update-S9s5-g0C.mjs} +0 -0
  89. /package/dist/{chunk-Ct1HF2bE.mjs → chunk-CkXuGtQK.mjs} +0 -0
  90. /package/dist/{microphone-D-6y9aiE.mjs → microphone-DaMZFRuR.mjs} +0 -0
@@ -1,171 +0,0 @@
1
- //#region src/core/models.ts
2
- const BUILTIN_MODELS = {
3
- "qwen3-0.6b": {
4
- id: "qwen3-0.6b",
5
- repo: "onnx-community/Qwen3-0.6B-ONNX",
6
- description: "Qwen3 0.6B - Best balance of speed and quality, supports thinking",
7
- size: "~400MB",
8
- contextLength: 32768,
9
- supportsThinking: true,
10
- supportsJson: true,
11
- family: "qwen"
12
- },
13
- "qwen2.5-0.5b": {
14
- id: "qwen2.5-0.5b",
15
- repo: "onnx-community/Qwen2.5-0.5B-Instruct",
16
- description: "Qwen2.5 0.5B - Fast and capable",
17
- size: "~350MB",
18
- contextLength: 32768,
19
- supportsThinking: false,
20
- supportsJson: true,
21
- family: "qwen"
22
- },
23
- "qwen2.5-coder-0.5b": {
24
- id: "qwen2.5-coder-0.5b",
25
- repo: "onnx-community/Qwen2.5-Coder-0.5B-Instruct",
26
- description: "Qwen2.5 Coder 0.5B - Optimized for code",
27
- size: "~400MB",
28
- contextLength: 32768,
29
- supportsThinking: false,
30
- supportsJson: true,
31
- family: "qwen"
32
- },
33
- "smollm2-360m": {
34
- id: "smollm2-360m",
35
- repo: "HuggingFaceTB/SmolLM2-360M-Instruct",
36
- description: "SmolLM2 360M - Fast, good for simple tasks",
37
- size: "~250MB",
38
- contextLength: 8192,
39
- supportsThinking: false,
40
- supportsJson: false,
41
- family: "smollm"
42
- },
43
- "smollm2-135m": {
44
- id: "smollm2-135m",
45
- repo: "HuggingFaceTB/SmolLM2-135M-Instruct",
46
- description: "SmolLM2 135M - Fastest, basic generation",
47
- size: "~100MB",
48
- contextLength: 8192,
49
- supportsThinking: false,
50
- supportsJson: false,
51
- family: "smollm"
52
- },
53
- "phi-3-mini": {
54
- id: "phi-3-mini",
55
- repo: "microsoft/Phi-3-mini-4k-instruct-onnx",
56
- description: "Phi-3 Mini - High quality, larger model",
57
- size: "~2.1GB",
58
- contextLength: 4096,
59
- supportsThinking: false,
60
- supportsJson: true,
61
- family: "phi"
62
- },
63
- "ministral-3b": {
64
- id: "ministral-3b",
65
- repo: "mistralai/Ministral-3-3B-Instruct-2512-ONNX",
66
- description: "Ministral 3 3B - Vision + Reasoning, 256k context",
67
- size: "~2.5GB",
68
- contextLength: 262144,
69
- supportsThinking: true,
70
- supportsJson: true,
71
- supportsVision: true,
72
- visionEncoderSize: "0.4B",
73
- family: "mistral"
74
- }
75
- };
76
- /**
77
- * Parse model identifier and resolve to source
78
- *
79
- * Supported formats:
80
- * - "qwen3-0.6b" (built-in)
81
- * - "hf:org/model" (HuggingFace shorthand)
82
- * - "https://huggingface.co/org/model" (full URL)
83
- * - "file:./path/to/model" (local path)
84
- */
85
- function resolveModel(modelId) {
86
- if (BUILTIN_MODELS[modelId]) return {
87
- type: "builtin",
88
- path: BUILTIN_MODELS[modelId].repo
89
- };
90
- if (modelId.startsWith("hf:")) return {
91
- type: "huggingface",
92
- path: modelId.slice(3)
93
- };
94
- if (modelId.startsWith("https://huggingface.co/")) return {
95
- type: "huggingface",
96
- path: modelId.replace("https://huggingface.co/", "")
97
- };
98
- if (modelId.startsWith("file:")) return {
99
- type: "local",
100
- path: modelId.slice(5)
101
- };
102
- if (modelId.includes("/")) return {
103
- type: "huggingface",
104
- path: modelId
105
- };
106
- return {
107
- type: "huggingface",
108
- path: modelId
109
- };
110
- }
111
- /**
112
- * Get model config (built-in only)
113
- */
114
- function getModelConfig(modelId) {
115
- return BUILTIN_MODELS[modelId] || null;
116
- }
117
- const FAMILY_CONTEXT_DEFAULTS = {
118
- qwen: 32768,
119
- mistral: 262144,
120
- llama: 8192,
121
- phi: 4096,
122
- smollm: 8192,
123
- other: 4096
124
- };
125
- /**
126
- * Create model config for external model
127
- */
128
- function createExternalModelConfig(modelId, repo, contextLength) {
129
- let family = "other";
130
- const repoLower = repo.toLowerCase();
131
- if (repoLower.includes("qwen")) family = "qwen";
132
- else if (repoLower.includes("smollm")) family = "smollm";
133
- else if (repoLower.includes("phi")) family = "phi";
134
- else if (repoLower.includes("mistral") || repoLower.includes("ministral")) family = "mistral";
135
- else if (repoLower.includes("llama")) family = "llama";
136
- const supportsVision = repoLower.includes("vision") || repoLower.includes("vlm") || repoLower.includes("image-text") || repoLower.includes("ministral");
137
- return {
138
- id: modelId,
139
- repo,
140
- description: `External model: ${repo}`,
141
- size: "Unknown",
142
- contextLength: contextLength || FAMILY_CONTEXT_DEFAULTS[family] || 4096,
143
- supportsThinking: family === "qwen" || family === "mistral",
144
- supportsJson: family === "qwen" || family === "phi" || family === "mistral",
145
- supportsVision,
146
- family
147
- };
148
- }
149
- /**
150
- * Fetch context length from HuggingFace model config
151
- */
152
- async function fetchModelContextLength(repo) {
153
- try {
154
- const res = await fetch(`https://huggingface.co/${repo}/raw/main/config.json`);
155
- if (!res.ok) return null;
156
- const config = await res.json();
157
- return config.max_position_embeddings || config.n_positions || config.max_seq_len || config.sliding_window || config.context_length || null;
158
- } catch {
159
- return null;
160
- }
161
- }
162
- /**
163
- * List all built-in models
164
- */
165
- function listBuiltinModels() {
166
- return Object.values(BUILTIN_MODELS);
167
- }
168
-
169
- //#endregion
170
- export { listBuiltinModels as a, getModelConfig as i, createExternalModelConfig as n, resolveModel as o, fetchModelContextLength as r, BUILTIN_MODELS as t };
171
- //# sourceMappingURL=models-BAtL8qsA.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"models-BAtL8qsA.mjs","names":["BUILTIN_MODELS: Record<string, ModelConfig>","FAMILY_CONTEXT_DEFAULTS: Record<string, number>","family: ModelConfig[\"family\"]"],"sources":["../src/core/models.ts"],"sourcesContent":["/**\n * Model Registry\n *\n * Supports built-in models and any HuggingFace model via hf:org/model syntax\n */\n\nimport type { ModelConfig, ModelSource } from \"./types.js\";\n\n// ============================================\n// Built-in Models (curated & tested)\n// ============================================\n\nexport const BUILTIN_MODELS: Record<string, ModelConfig> = {\n \"qwen3-0.6b\": {\n id: \"qwen3-0.6b\",\n repo: \"onnx-community/Qwen3-0.6B-ONNX\",\n description: \"Qwen3 0.6B - Best balance of speed and quality, supports thinking\",\n size: \"~400MB\",\n contextLength: 32_768,\n supportsThinking: true,\n supportsJson: true,\n family: \"qwen\",\n },\n \"qwen2.5-0.5b\": {\n id: \"qwen2.5-0.5b\",\n repo: \"onnx-community/Qwen2.5-0.5B-Instruct\",\n description: \"Qwen2.5 0.5B - Fast and capable\",\n size: \"~350MB\",\n contextLength: 32_768,\n supportsThinking: false,\n supportsJson: true,\n family: \"qwen\",\n },\n \"qwen2.5-coder-0.5b\": {\n id: \"qwen2.5-coder-0.5b\",\n repo: \"onnx-community/Qwen2.5-Coder-0.5B-Instruct\",\n description: \"Qwen2.5 Coder 0.5B - Optimized for code\",\n size: \"~400MB\",\n contextLength: 32_768,\n supportsThinking: false,\n supportsJson: true,\n family: \"qwen\",\n },\n \"smollm2-360m\": {\n id: \"smollm2-360m\",\n repo: \"HuggingFaceTB/SmolLM2-360M-Instruct\",\n description: \"SmolLM2 360M - Fast, good for simple tasks\",\n size: \"~250MB\",\n contextLength: 8192,\n supportsThinking: false,\n supportsJson: false,\n family: \"smollm\",\n },\n \"smollm2-135m\": {\n id: \"smollm2-135m\",\n repo: \"HuggingFaceTB/SmolLM2-135M-Instruct\",\n description: \"SmolLM2 135M - Fastest, basic generation\",\n size: \"~100MB\",\n contextLength: 8192,\n supportsThinking: false,\n supportsJson: false,\n family: \"smollm\",\n },\n \"phi-3-mini\": {\n id: \"phi-3-mini\",\n repo: \"microsoft/Phi-3-mini-4k-instruct-onnx\",\n description: \"Phi-3 Mini - High quality, larger model\",\n size: \"~2.1GB\",\n contextLength: 4096,\n supportsThinking: false,\n supportsJson: true,\n family: \"phi\",\n },\n \"ministral-3b\": {\n id: \"ministral-3b\",\n repo: \"mistralai/Ministral-3-3B-Instruct-2512-ONNX\",\n description: \"Ministral 3 3B - Vision + Reasoning, 256k context\",\n size: \"~2.5GB\",\n contextLength: 262_144,\n supportsThinking: true,\n supportsJson: true,\n supportsVision: true,\n visionEncoderSize: \"0.4B\",\n family: \"mistral\",\n },\n};\n\n// ============================================\n// Model Resolution\n// ============================================\n\n/**\n * Parse model identifier and resolve to source\n *\n * Supported formats:\n * - \"qwen3-0.6b\" (built-in)\n * - \"hf:org/model\" (HuggingFace shorthand)\n * - \"https://huggingface.co/org/model\" (full URL)\n * - \"file:./path/to/model\" (local path)\n */\nexport function resolveModel(modelId: string): ModelSource {\n // Built-in model\n if (BUILTIN_MODELS[modelId]) {\n return {\n type: \"builtin\",\n path: BUILTIN_MODELS[modelId].repo,\n };\n }\n\n // HuggingFace shorthand: hf:org/model\n if (modelId.startsWith(\"hf:\")) {\n const repo = modelId.slice(3);\n return {\n type: \"huggingface\",\n path: repo,\n };\n }\n\n // HuggingFace URL\n if (modelId.startsWith(\"https://huggingface.co/\")) {\n const repo = modelId.replace(\"https://huggingface.co/\", \"\");\n return {\n type: \"huggingface\",\n path: repo,\n };\n }\n\n // Local file\n if (modelId.startsWith(\"file:\")) {\n const path = modelId.slice(5);\n return {\n type: \"local\",\n path,\n };\n }\n\n // Assume it's a HuggingFace repo if it contains a slash\n if (modelId.includes(\"/\")) {\n return {\n type: \"huggingface\",\n path: modelId,\n };\n }\n\n // Unknown - treat as HuggingFace\n return {\n type: \"huggingface\",\n path: modelId,\n };\n}\n\n/**\n * Get model config (built-in only)\n */\nexport function getModelConfig(modelId: string): ModelConfig | null {\n return BUILTIN_MODELS[modelId] || null;\n}\n\n// Default context lengths by model family (when config.json is unavailable)\nconst FAMILY_CONTEXT_DEFAULTS: Record<string, number> = {\n qwen: 32_768,\n mistral: 262_144, // Ministral models support up to 256K\n llama: 8192,\n phi: 4096,\n smollm: 8192,\n other: 4096,\n};\n\n/**\n * Create model config for external model\n */\nexport function createExternalModelConfig(\n modelId: string,\n repo: string,\n contextLength?: number,\n): ModelConfig {\n // Try to infer family from repo name\n let family: ModelConfig[\"family\"] = \"other\";\n const repoLower = repo.toLowerCase();\n\n if (repoLower.includes(\"qwen\")) {\n family = \"qwen\";\n } else if (repoLower.includes(\"smollm\")) {\n family = \"smollm\";\n } else if (repoLower.includes(\"phi\")) {\n family = \"phi\";\n } else if (repoLower.includes(\"mistral\") || repoLower.includes(\"ministral\")) {\n family = \"mistral\";\n } else if (repoLower.includes(\"llama\")) {\n family = \"llama\";\n }\n\n // Detect vision models from common patterns\n const supportsVision =\n repoLower.includes(\"vision\") ||\n repoLower.includes(\"vlm\") ||\n repoLower.includes(\"image-text\") ||\n repoLower.includes(\"ministral\");\n\n return {\n id: modelId,\n repo,\n description: `External model: ${repo}`,\n size: \"Unknown\",\n contextLength: contextLength || FAMILY_CONTEXT_DEFAULTS[family] || 4096,\n supportsThinking: family === \"qwen\" || family === \"mistral\",\n supportsJson: family === \"qwen\" || family === \"phi\" || family === \"mistral\",\n supportsVision,\n family,\n };\n}\n\n/**\n * Fetch context length from HuggingFace model config\n */\nexport async function fetchModelContextLength(repo: string): Promise<number | null> {\n try {\n const res = await fetch(`https://huggingface.co/${repo}/raw/main/config.json`);\n if (!res.ok) {\n return null;\n }\n\n const config = await res.json();\n\n // Different models use different field names\n return (\n config.max_position_embeddings ||\n config.n_positions ||\n config.max_seq_len ||\n config.sliding_window || // Some models use this\n config.context_length ||\n null\n );\n } catch {\n return null;\n }\n}\n\n/**\n * List all built-in models\n */\nexport function listBuiltinModels(): ModelConfig[] {\n return Object.values(BUILTIN_MODELS);\n}\n\n/**\n * Search HuggingFace models (placeholder - would need HF API)\n */\nexport async function searchModels(query: string): Promise<ModelConfig[]> {\n // TODO: Implement HuggingFace API search\n // For now, filter built-in models\n const q = query.toLowerCase();\n return listBuiltinModels().filter(\n (m) =>\n m.id.toLowerCase().includes(q) ||\n m.description.toLowerCase().includes(q) ||\n m.family.toLowerCase().includes(q),\n );\n}\n"],"mappings":"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"}
@@ -1,22 +0,0 @@
1
- import { _ as ModelSource, g as ModelConfig } from "./types-s6Py2_DL.mjs";
2
-
3
- //#region src/core/models.d.ts
4
-
5
- declare const BUILTIN_MODELS: Record<string, ModelConfig>;
6
- /**
7
- * Parse model identifier and resolve to source
8
- *
9
- * Supported formats:
10
- * - "qwen3-0.6b" (built-in)
11
- * - "hf:org/model" (HuggingFace shorthand)
12
- * - "https://huggingface.co/org/model" (full URL)
13
- * - "file:./path/to/model" (local path)
14
- */
15
- declare function resolveModel(modelId: string): ModelSource;
16
- /**
17
- * List all built-in models
18
- */
19
- declare function listBuiltinModels(): ModelConfig[];
20
- //#endregion
21
- export { listBuiltinModels as n, resolveModel as r, BUILTIN_MODELS as t };
22
- //# sourceMappingURL=models-CE0fBq0U.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"models-CE0fBq0U.d.mts","names":[],"sources":["../src/core/models.ts"],"sourcesContent":[],"mappings":";;;;cAYa,gBAAgB,eAAe;;;;;;;;;;iBAwF5B,YAAA,mBAA+B;;;;iBA6I/B,iBAAA,CAAA,GAAqB"}
@@ -1,10 +0,0 @@
1
- import "./gerbil-yoSpRHgv.mjs";
2
- import "./chrome-backend-Y9F7W5VQ.mjs";
3
- import "./models-BAtL8qsA.mjs";
4
- import "./utils-CkB4Roi6.mjs";
5
- import "./one-liner-B1rmFto6.mjs";
6
- import "./skills-5DxAV-rn.mjs";
7
- import "./tools-IYPrqoek.mjs";
8
- import { n as startRepl, t as setCleanupPromise } from "./cli.mjs";
9
-
10
- export { setCleanupPromise };
@@ -1 +0,0 @@
1
- {"version":3,"file":"skills-5DxAV-rn.mjs","names":["ctx: SkillContext<TInput>","path","fs","loaded: string[]","analysisPrompts: Record<string, string>","stylePrompts: Record<string, string>","saveWav","join","tmpdir","captionPrompts: Record<string, string>","lengthGuides: Record<string, number>","platformGuides: Record<string, string>","execSync","comparisonPrompts: Record<string, string>","focusPrompts: Record<string, string>","detailLengths: Record<string, number>","saveWav","playAudio","speak","extractionPrompts: Record<string, string>","formatInstructions: Record<string, string>","extract","saveWav","existsSync","textToRead: string","readFileSync","join","tmpdir","join","tmpdir","existsSync","readFileSync","text: string"],"sources":["../src/skills/registry.ts","../src/skills/loader.ts","../src/skills/builtin/analyze-screenshot.ts","../src/skills/builtin/announce.ts","../src/skills/builtin/caption-image.ts","../src/skills/builtin/commit.ts","../src/skills/builtin/compare-images.ts","../src/skills/builtin/describe-image.ts","../src/skills/builtin/explain.ts","../src/skills/builtin/extract.ts","../src/skills/builtin/extract-from-image.ts","../src/skills/builtin/read-aloud.ts","../src/skills/builtin/review.ts","../src/skills/builtin/speak.ts","../src/skills/builtin/summarize.ts","../src/skills/builtin/test.ts","../src/skills/builtin/title.ts","../src/skills/builtin/transcribe.ts","../src/skills/builtin/translate.ts"],"sourcesContent":["/**\n * Skill Registry\n *\n * Central registry for all skills (built-in and custom).\n */\n\nimport type { Gerbil } from \"../core/gerbil.js\";\nimport { getInstance } from \"../core/one-liner.js\";\nimport type { Skill, SkillContext, SkillDefinition, SkillInfo } from \"./types.js\";\n\n// ============================================\n// Registry Storage\n// ============================================\n\nconst registry = new Map<string, Skill>();\nconst skillSources = new Map<string, string>(); // name -> source file\n\n// Reserved names that cannot be used for custom skills (CLI commands/views)\n// Note: Built-in skills like \"commit\", \"summarize\" ARE allowed because they\n// have dedicated CLI commands, but new skills cannot use these view names\nconst RESERVED_NAMES = new Set([\n // REPL views (would conflict with CLI shortcuts)\n \"repl\",\n \"chat\",\n \"skills\",\n \"tools\",\n \"model\",\n \"integrate\",\n \"benchmark\",\n \"info\",\n \"serve\",\n \"cache\",\n // CLI commands\n \"generate\",\n \"models\",\n \"bench\",\n // Aliases\n \"r\",\n \"c\",\n \"g\",\n // Other reserved\n \"help\",\n \"version\",\n \"gerbil\",\n]);\n\n// ============================================\n// Define Skill\n// ============================================\n\n/**\n * Define and register a skill\n *\n * @example\n * ```ts\n * const sentiment = defineSkill({\n * name: \"sentiment\",\n * description: \"Analyze sentiment of text\",\n * input: z.object({ text: z.string() }),\n * output: z.object({ sentiment: z.enum([\"positive\", \"negative\", \"neutral\"]) }),\n * async run({ input, gerbil }) {\n * return gerbil.json(`Analyze sentiment: ${input.text}`, { schema: this.output });\n * }\n * });\n * ```\n */\nexport function defineSkill<TInput, TOutput>(\n definition: SkillDefinition<TInput, TOutput>,\n): Skill<TInput, TOutput> {\n // Validate name format\n if (!/^[a-z][a-z0-9-]*$/.test(definition.name)) {\n throw new Error(`Skill name must be kebab-case starting with a letter: ${definition.name}`);\n }\n\n // Check for reserved names (CLI commands)\n if (RESERVED_NAMES.has(definition.name)) {\n throw new Error(\n `Skill name \"${definition.name}\" is reserved for CLI commands. Choose a different name.`,\n );\n }\n\n // Create the skill function\n const execute = async (input: TInput): Promise<TOutput> => skill.run(input);\n\n // Create the skill object\n const skill = Object.assign(execute, {\n definition,\n\n async run(input: TInput, gerbil?: Gerbil): Promise<TOutput> {\n // Get or create Gerbil instance\n const g = gerbil ?? (await getInstance(definition.model));\n\n // Validate input if schema provided\n let validatedInput = input;\n if (definition.input) {\n const parsed = definition.input.safeParse(input);\n if (!parsed.success) {\n throw new Error(`Invalid input for skill \"${definition.name}\": ${parsed.error.message}`);\n }\n validatedInput = parsed.data;\n }\n\n // Create context\n const ctx: SkillContext<TInput> = {\n input: validatedInput,\n gerbil: g,\n rawInput: input,\n definition: definition as SkillDefinition<TInput, unknown>,\n };\n\n // Run the skill\n const result = await definition.run.call(definition, ctx);\n\n // Validate output if schema provided\n if (definition.output && typeof result !== \"string\") {\n const parsed = definition.output.safeParse(result);\n if (!parsed.success) {\n throw new Error(\n `Invalid output from skill \"${definition.name}\": ${parsed.error.message}`,\n );\n }\n return parsed.data;\n }\n\n return result as TOutput;\n },\n }) as Skill<TInput, TOutput>;\n\n // Register the skill\n registry.set(definition.name, skill as Skill);\n\n return skill;\n}\n\n// ============================================\n// Use Skill\n// ============================================\n\n/**\n * Get a skill by name\n *\n * @example\n * ```ts\n * const sentiment = useSkill(\"sentiment\");\n * const result = await sentiment({ text: \"I love this!\" });\n * ```\n */\nexport function useSkill<TInput = unknown, TOutput = unknown>(\n name: string,\n): Skill<TInput, TOutput> {\n const skill = registry.get(name);\n if (!skill) {\n throw new Error(`Skill not found: \"${name}\". Available: ${listSkills().join(\", \") || \"none\"}`);\n }\n return skill as Skill<TInput, TOutput>;\n}\n\n// ============================================\n// Registry Operations\n// ============================================\n\n/**\n * List all registered skill names\n */\nexport function listSkills(): string[] {\n return Array.from(registry.keys()).sort();\n}\n\n/**\n * Get skill metadata\n */\nexport function getSkillInfo(name: string): SkillInfo | undefined {\n const skill = registry.get(name);\n if (!skill) {\n return;\n }\n\n return {\n name: skill.definition.name,\n description: skill.definition.description,\n version: skill.definition.version,\n author: skill.definition.author,\n builtin: !skillSources.has(name),\n source: skillSources.get(name),\n };\n}\n\n/**\n * Check if a skill exists\n */\nexport function hasSkill(name: string): boolean {\n return registry.has(name);\n}\n\n/**\n * Remove a skill from registry\n */\nexport function removeSkill(name: string): boolean {\n skillSources.delete(name);\n return registry.delete(name);\n}\n\n/**\n * Clear all skills from registry\n */\nexport function clearSkills(): void {\n registry.clear();\n skillSources.clear();\n}\n\n/**\n * Get all skill info\n */\nexport function getAllSkillInfo(): SkillInfo[] {\n return listSkills().map((name) => getSkillInfo(name)!);\n}\n\n/**\n * Check if a name is reserved (cannot be used for custom skills)\n */\nexport function isReservedName(name: string): boolean {\n return RESERVED_NAMES.has(name);\n}\n\n/**\n * Get list of reserved names\n */\nexport function getReservedNames(): string[] {\n return Array.from(RESERVED_NAMES).sort();\n}\n\n// ============================================\n// Internal: Register with source\n// ============================================\n\n/**\n * Register a skill with its source file (used by loader)\n * @internal\n */\nexport function registerSkillWithSource(skill: Skill, source: string): void {\n registry.set(skill.definition.name, skill);\n skillSources.set(skill.definition.name, source);\n}\n","/**\n * Skill Loader\n *\n * Load skills from files, directories, and packages.\n */\n\nimport { pathToFileURL } from \"node:url\";\nimport { registerSkillWithSource } from \"./registry.js\";\nimport type { LoadSkillsOptions, Skill } from \"./types.js\";\n\n// ============================================\n// Auto-Load Project Skills\n// ============================================\n\n/**\n * Load skills from the project's .gerbil/skills/ directory\n *\n * This is the standard location for project-specific skills.\n * Creates the directory structure if it doesn't exist.\n *\n * @example\n * ```ts\n * // Load from .gerbil/skills/ in current working directory\n * const loaded = await loadProjectSkills();\n * console.log(`Loaded ${loaded.length} project skills`);\n * ```\n */\nexport async function loadProjectSkills(cwd: string = process.cwd()): Promise<string[]> {\n const path = await import(\"node:path\");\n const fs = await import(\"node:fs\");\n\n const gerbilDir = path.join(cwd, \".gerbil\");\n const skillsDir = path.join(gerbilDir, \"skills\");\n\n // If .gerbil/skills doesn't exist, that's fine - just return empty\n if (!fs.existsSync(skillsDir)) {\n return [];\n }\n\n return loadSkills(skillsDir);\n}\n\n// ============================================\n// Load from Directory\n// ============================================\n\n/**\n * Load all skills from a directory\n *\n * @example\n * ```ts\n * // Load all *.skill.ts and *.skill.js files\n * const loaded = await loadSkills(\"./skills\");\n * console.log(`Loaded ${loaded.length} skills`);\n * ```\n */\nexport async function loadSkills(dir: string, options: LoadSkillsOptions = {}): Promise<string[]> {\n const { patterns = [\"*.skill.ts\", \"*.skill.js\"] } = options;\n const loaded: string[] = [];\n\n try {\n const fs = await import(\"node:fs\");\n const path = await import(\"node:path\");\n\n const resolvedDir = path.resolve(dir);\n\n if (!fs.existsSync(resolvedDir)) {\n throw new Error(`Skills directory not found: ${dir}`);\n }\n\n const files = fs.readdirSync(resolvedDir);\n\n for (const file of files) {\n const matches = patterns.some((pattern) => {\n const regex = new RegExp(`^${pattern.replace(/\\*/g, \".*\").replace(/\\./g, \"\\\\.\")}$`);\n return regex.test(file);\n });\n\n if (matches) {\n const filePath = path.join(resolvedDir, file);\n const skill = await loadSkill(filePath);\n if (skill) {\n loaded.push(skill.definition.name);\n }\n }\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"MODULE_NOT_FOUND\") {\n throw new Error(`Skills directory not found: ${dir}`);\n }\n throw error;\n }\n\n return loaded;\n}\n\n// ============================================\n// Load Single Skill\n// ============================================\n\n/**\n * Load a single skill from a file\n *\n * @example\n * ```ts\n * const skill = await loadSkill(\"./skills/sentiment.skill.ts\");\n * if (skill) {\n * const result = await skill({ text: \"Hello\" });\n * }\n * ```\n */\nexport async function loadSkill(filePath: string): Promise<Skill | null> {\n try {\n const path = await import(\"node:path\");\n const resolvedPath = path.resolve(filePath);\n\n // Use dynamic import with file URL for ESM compatibility\n const fileUrl = pathToFileURL(resolvedPath).href;\n const module = await import(fileUrl);\n\n // Get the default export\n const skill = module.default as Skill;\n\n if (!skill?.definition) {\n return null;\n }\n\n // Register with source tracking\n registerSkillWithSource(skill, resolvedPath);\n\n return skill;\n } catch (_error) {\n return null;\n }\n}\n\n// ============================================\n// Load from Package\n// ============================================\n\n/**\n * Load skills from an npm package\n *\n * @example\n * ```ts\n * // Load skills from a package\n * const loaded = await loadSkillPackage(\"gerbil-skills-extra\");\n * ```\n */\nexport async function loadSkillPackage(packageName: string): Promise<string[]> {\n try {\n const module = await import(packageName);\n const loaded: string[] = [];\n\n // Look for exported skills\n for (const [_key, value] of Object.entries(module)) {\n if (isSkill(value)) {\n const skill = value as Skill;\n registerSkillWithSource(skill, `npm:${packageName}`);\n loaded.push(skill.definition.name);\n }\n }\n\n // Also check default export if it's an object of skills\n if (module.default && typeof module.default === \"object\") {\n for (const [_key, value] of Object.entries(module.default)) {\n if (isSkill(value)) {\n const skill = value as Skill;\n registerSkillWithSource(skill, `npm:${packageName}`);\n loaded.push(skill.definition.name);\n }\n }\n }\n\n return loaded;\n } catch (error) {\n throw new Error(`Failed to load skill package \"${packageName}\": ${error}`);\n }\n}\n\n// ============================================\n// Helpers\n// ============================================\n\n/**\n * Check if a value is a Skill\n */\nfunction isSkill(value: unknown): value is Skill {\n return (\n typeof value === \"function\" &&\n typeof (value as Skill).definition === \"object\" &&\n typeof (value as Skill).definition.name === \"string\" &&\n typeof (value as Skill).definition.run === \"function\"\n );\n}\n","/**\n * Analyze Screenshot Skill\n *\n * Analyze a UI screenshot for design, accessibility, or QA purposes.\n */\n\nimport { z } from \"zod\";\nimport { defineSkill } from \"../registry.js\";\n\nconst AnalyzeScreenshotInput = z.object({\n /** Screenshot URL or data URI */\n image: z.string(),\n\n /** Analysis type */\n type: z\n .enum([\"ui-review\", \"accessibility\", \"suggestions\", \"qa\", \"ux-audit\", \"mobile-check\"])\n .default(\"ui-review\"),\n\n /** Specific areas to focus on (optional) */\n focus: z.array(z.string()).optional(),\n});\n\nexport type AnalyzeScreenshotInput = z.infer<typeof AnalyzeScreenshotInput>;\n\nconst analysisPrompts: Record<string, string> = {\n \"ui-review\": `Review this UI screenshot as a design expert. Analyze:\n- Visual hierarchy and layout\n- Typography and readability\n- Color scheme and contrast\n- Spacing and alignment\n- Component consistency\n- Overall design quality\n\nProvide specific observations and suggestions.`,\n\n accessibility: `Analyze this screenshot for accessibility issues. Check for:\n- Color contrast ratios\n- Text size and readability\n- Touch target sizes\n- Visual affordances for interactive elements\n- Potential issues for users with visual impairments\n- Missing alt text indicators\n\nList specific issues found and how to fix them.`,\n\n suggestions: `As a senior UX designer, review this UI and suggest improvements:\n- Modern design patterns that could be applied\n- Usability improvements\n- Visual polish opportunities\n- User experience enhancements\n- Ways to make it more engaging\n\nBe specific and actionable.`,\n\n qa: `Perform QA analysis on this screenshot. Look for:\n- Visual bugs or glitches\n- Alignment and spacing issues\n- Broken or missing elements\n- Inconsistencies with design standards\n- Text overflow or truncation issues\n- Responsive design problems\n\nReport each issue with its location and severity.`,\n\n \"ux-audit\": `Conduct a UX audit of this interface:\n- User flow clarity\n- Call-to-action visibility\n- Information architecture\n- Cognitive load assessment\n- Error prevention\n- User feedback mechanisms\n\nProvide a structured audit report.`,\n\n \"mobile-check\": `Evaluate this UI for mobile usability:\n- Touch target sizes (minimum 44x44px)\n- Thumb-zone accessibility\n- Content priority on small screens\n- Gesture affordances\n- Loading indicators\n- Mobile-specific patterns\n\nIdentify mobile-specific issues and recommendations.`,\n};\n\nexport const analyzeScreenshot = defineSkill({\n name: \"analyze-screenshot\",\n description: \"Analyze a UI screenshot for design, accessibility, or QA\",\n version: \"1.0.0\",\n model: \"ministral-3b\",\n input: AnalyzeScreenshotInput,\n maxTokens: 1200,\n temperature: 0.3,\n\n async run({ input, gerbil }) {\n const { image, type = \"ui-review\", focus } = input;\n\n if (!gerbil.supportsVision()) {\n throw new Error(\n `Current model doesn't support vision. Load a vision model like \"ministral-3b\" first.`,\n );\n }\n\n let prompt = analysisPrompts[type];\n\n if (focus && focus.length > 0) {\n prompt += `\\n\\nPay special attention to: ${focus.join(\", \")}.`;\n }\n\n const result = await gerbil.generate(prompt, {\n images: [{ source: image }],\n maxTokens: this.maxTokens,\n temperature: this.temperature,\n });\n\n return result.text;\n },\n});\n","/**\n * Announce Skill\n *\n * Generate and speak an announcement using AI.\n * Useful for scripts that need to give voice updates.\n */\n\nimport { execSync } from \"child_process\";\nimport { unlinkSync, writeFileSync } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\nimport { z } from \"zod\";\nimport { defineSkill } from \"../registry.js\";\n\nconst AnnounceInput = z.object({\n /** What to announce (will be rephrased by AI) */\n message: z.string(),\n\n /** Announcement style */\n style: z.enum([\"casual\", \"formal\", \"excited\", \"calm\", \"urgent\"]).default(\"casual\"),\n\n /** Voice ID */\n voice: z\n .enum([\"af_heart\", \"af_bella\", \"af_nicole\", \"am_fenrir\", \"am_michael\", \"bf_emma\", \"bm_george\"])\n .default(\"af_heart\"),\n\n /** Speech speed */\n speed: z.number().min(0.5).max(2.0).default(1.0),\n\n /** Just generate text, don't speak */\n textOnly: z.boolean().default(false),\n});\n\nexport type AnnounceInput = z.infer<typeof AnnounceInput>;\n\nconst stylePrompts: Record<string, string> = {\n casual: \"Rephrase this as a casual, friendly announcement. Keep it natural and conversational.\",\n formal: \"Rephrase this as a professional, formal announcement. Be clear and dignified.\",\n excited: \"Rephrase this as an excited, enthusiastic announcement! Add energy and positivity.\",\n calm: \"Rephrase this as a calm, soothing announcement. Be gentle and reassuring.\",\n urgent: \"Rephrase this as an urgent announcement. Be direct and emphasize importance.\",\n};\n\nfunction saveWav(filename: string, audio: Float32Array, sampleRate: number): void {\n const buffer = Buffer.alloc(44 + audio.length * 2);\n buffer.write(\"RIFF\", 0);\n buffer.writeUInt32LE(36 + audio.length * 2, 4);\n buffer.write(\"WAVE\", 8);\n buffer.write(\"fmt \", 12);\n buffer.writeUInt32LE(16, 16);\n buffer.writeUInt16LE(1, 20);\n buffer.writeUInt16LE(1, 22);\n buffer.writeUInt32LE(sampleRate, 24);\n buffer.writeUInt32LE(sampleRate * 2, 28);\n buffer.writeUInt16LE(2, 32);\n buffer.writeUInt16LE(16, 34);\n buffer.write(\"data\", 36);\n buffer.writeUInt32LE(audio.length * 2, 40);\n\n for (let i = 0; i < audio.length; i++) {\n const s = Math.max(-1, Math.min(1, audio[i]));\n buffer.writeInt16LE(Math.round(s * 32767), 44 + i * 2);\n }\n\n writeFileSync(filename, buffer);\n}\n\nexport const announce = defineSkill({\n name: \"announce\",\n description: \"Generate and speak an AI-crafted announcement\",\n version: \"1.0.0\",\n input: AnnounceInput,\n temperature: 0.7,\n maxTokens: 150,\n\n async run({ input, gerbil }) {\n const { message, style = \"casual\", voice = \"af_heart\", speed = 1.0, textOnly = false } = input;\n\n // Generate announcement text using AI\n const result = await gerbil.generate(message, {\n system: `${stylePrompts[style]}\nKeep it brief (1-2 sentences max). \nOutput only the announcement text, nothing else.`,\n temperature: this.temperature,\n maxTokens: this.maxTokens,\n });\n\n const announcementText = result.text.trim();\n\n if (textOnly) {\n return announcementText;\n }\n\n // Speak the announcement\n const speechResult = await gerbil.speak(announcementText, { voice, speed });\n\n // Play audio\n const tempFile = join(tmpdir(), `gerbil-announce-${Date.now()}.wav`);\n saveWav(tempFile, speechResult.audio, speechResult.sampleRate);\n\n try {\n const platform = process.platform;\n if (platform === \"darwin\") {\n execSync(`afplay \"${tempFile}\"`, { stdio: \"inherit\" });\n } else if (platform === \"linux\") {\n try {\n execSync(`aplay \"${tempFile}\"`, { stdio: \"inherit\" });\n } catch {\n execSync(`paplay \"${tempFile}\"`, { stdio: \"inherit\" });\n }\n } else if (platform === \"win32\") {\n execSync(`powershell -c \"(New-Object Media.SoundPlayer '${tempFile}').PlaySync()\"`, {\n stdio: \"inherit\",\n });\n }\n } finally {\n try {\n unlinkSync(tempFile);\n } catch {\n // Ignore\n }\n }\n\n return announcementText;\n },\n});\n","/**\n * Caption Image Skill\n *\n * Generate captions, alt text, or social media descriptions for images.\n */\n\nimport { z } from \"zod\";\nimport { defineSkill } from \"../registry.js\";\n\nconst CaptionImageInput = z.object({\n /** Image URL or data URI */\n image: z.string(),\n\n /** Caption type */\n type: z\n .enum([\"alt-text\", \"caption\", \"social-media\", \"seo\", \"artistic\", \"technical\"])\n .default(\"caption\"),\n\n /** Tone/style for the caption */\n tone: z\n .enum([\"professional\", \"casual\", \"playful\", \"formal\", \"descriptive\"])\n .default(\"professional\"),\n\n /** Maximum length hint */\n maxLength: z.enum([\"short\", \"medium\", \"long\"]).default(\"medium\"),\n\n /** Platform hint for social media captions */\n platform: z.enum([\"twitter\", \"instagram\", \"linkedin\", \"facebook\"]).optional(),\n});\n\nexport type CaptionImageInput = z.infer<typeof CaptionImageInput>;\n\nconst captionPrompts: Record<string, string> = {\n \"alt-text\": `Generate accessible alt text for this image.\n- Describe the essential content concisely\n- Include relevant details for screen reader users\n- Avoid starting with \"Image of\" or \"Picture of\"\n- Be factual and objective`,\n\n caption: `Write a caption for this image.\n- Capture the essence of the image\n- Make it engaging and informative\n- Consider the context and mood`,\n\n \"social-media\": `Create a social media caption for this image.\n- Make it engaging and shareable\n- Include relevant emoji where appropriate\n- Suggest hashtags if applicable\n- Optimize for engagement`,\n\n seo: `Write SEO-optimized alt text and description for this image.\n- Include relevant keywords naturally\n- Be descriptive but concise\n- Focus on searchable terms\n- Maintain readability`,\n\n artistic: `Write an artistic, evocative caption for this image.\n- Capture the mood and emotion\n- Use creative language\n- Tell a micro-story if appropriate\n- Be memorable and unique`,\n\n technical: `Write a technical description of this image.\n- Describe components and elements precisely\n- Use appropriate technical terminology\n- Note measurements or specifications if visible\n- Be objective and detailed`,\n};\n\nconst lengthGuides: Record<string, number> = {\n short: 100,\n medium: 200,\n long: 400,\n};\n\nconst platformGuides: Record<string, string> = {\n twitter: \"Keep under 280 characters. Make it punchy and tweetable.\",\n instagram: \"Include relevant hashtags. Can be longer and more storytelling.\",\n linkedin: \"Keep it professional. Focus on value and insights.\",\n facebook: \"Can be conversational. Ask questions to encourage engagement.\",\n};\n\nexport const captionImage = defineSkill({\n name: \"caption-image\",\n description: \"Generate captions, alt text, or social media descriptions for images\",\n version: \"1.0.0\",\n model: \"ministral-3b\",\n input: CaptionImageInput,\n temperature: 0.7,\n\n async run({ input, gerbil }) {\n const {\n image,\n type = \"caption\",\n tone = \"professional\",\n maxLength = \"medium\",\n platform,\n } = input;\n\n if (!gerbil.supportsVision()) {\n throw new Error(\n `Current model doesn't support vision. Load a vision model like \"ministral-3b\" first.`,\n );\n }\n\n let prompt = captionPrompts[type];\n prompt += `\\n\\nTone: ${tone}`;\n\n if (platform && type === \"social-media\") {\n prompt += `\\n\\nPlatform: ${platform}. ${platformGuides[platform]}`;\n }\n\n const result = await gerbil.generate(prompt, {\n images: [{ source: image }],\n maxTokens: lengthGuides[maxLength],\n temperature: this.temperature,\n });\n\n return result.text;\n },\n});\n","/**\n * Commit Skill\n *\n * Generate git commit messages from staged changes.\n */\n\nimport { z } from \"zod\";\nimport { defineSkill } from \"../registry.js\";\n\nconst CommitInput = z.object({\n /** Git diff (auto-detected if not provided) */\n diff: z.string().optional(),\n\n /** Commit style */\n type: z.enum([\"conventional\", \"simple\", \"detailed\"]).default(\"conventional\"),\n\n /** Max length for commit message */\n maxLength: z.number().default(72),\n});\n\nexport type CommitInput = z.infer<typeof CommitInput>;\n\nasync function getGitDiff(): Promise<string> {\n try {\n const { execSync } = await import(\"node:child_process\");\n return execSync(\"git diff --staged\", { encoding: \"utf-8\" });\n } catch {\n return \"\";\n }\n}\n\nfunction getSystemPrompt(type: CommitInput[\"type\"]): string {\n switch (type) {\n case \"conventional\":\n return `Generate a git commit message following the Conventional Commits format.\nUse one of these types: feat, fix, docs, style, refactor, perf, test, chore.\nFormat: type(scope): description\nKeep it under 72 characters.\nNo period at the end.\nOnly output the commit message, nothing else.`;\n\n case \"detailed\":\n return `Generate a detailed git commit message with a subject line and body.\nSubject: under 72 chars, imperative mood\nBody: explain what and why\nOnly output the commit message, nothing else.`;\n\n default:\n return `Generate a concise git commit message.\nUse imperative mood (e.g., \"Add\", \"Fix\", \"Update\").\nKeep it under 72 characters.\nNo period at the end.\nOnly output the commit message, nothing else.`;\n }\n}\n\nexport const commit = defineSkill({\n name: \"commit\",\n description: \"Generate a git commit message from staged changes\",\n version: \"1.0.0\",\n input: CommitInput,\n temperature: 0.3,\n maxTokens: 100,\n\n async run({ input, gerbil }) {\n const { diff, type = \"conventional\", maxLength = 72 } = input;\n\n // Get diff from git if not provided\n const actualDiff = diff || (await getGitDiff());\n\n if (!actualDiff || actualDiff.trim().length === 0) {\n throw new Error(\"No changes staged. Run `git add` first.\");\n }\n\n const result = await gerbil.generate(\n `Generate a commit message for the following diff:\\n\\n${actualDiff}`,\n {\n system: getSystemPrompt(type),\n maxTokens: this.maxTokens,\n temperature: this.temperature,\n },\n );\n\n // Clean and truncate\n let message = result.text.trim();\n message = message.replace(/<think>[\\s\\S]*?<\\/think>/g, \"\").trim(); // Remove think tags\n message = message.replace(/<\\/?think>/g, \"\").trim(); // Remove unclosed tags\n message = message.replace(/^[\"']|[\"']$/g, \"\"); // Remove quotes\n message = message.split(\"\\n\")[0]; // First line only\n if (message.length > maxLength) {\n message = `${message.substring(0, maxLength - 3)}...`;\n }\n\n return message;\n },\n});\n","/**\n * Compare Images Skill\n *\n * Compare two images and describe their differences.\n */\n\nimport { z } from \"zod\";\nimport { defineSkill } from \"../registry.js\";\n\nconst CompareImagesInput = z.object({\n /** First image URL or data URI */\n image1: z.string(),\n\n /** Second image URL or data URI */\n image2: z.string(),\n\n /** Comparison type */\n type: z\n .enum([\"visual-diff\", \"design-comparison\", \"before-after\", \"a-b-test\", \"version-diff\"])\n .default(\"visual-diff\"),\n\n /** Specific aspects to compare */\n focus: z.array(z.string()).optional(),\n});\n\nexport type CompareImagesInput = z.infer<typeof CompareImagesInput>;\n\nconst comparisonPrompts: Record<string, string> = {\n \"visual-diff\": `Compare these two images and identify all visual differences.\n- List specific elements that differ\n- Note position, color, size, and content changes\n- Identify additions and removals\n- Rate the significance of each change`,\n\n \"design-comparison\": `Compare these two designs as a design expert.\n- Analyze layout differences\n- Compare typography and colors\n- Evaluate which design is more effective\n- Suggest which elements work better in each`,\n\n \"before-after\": `Analyze these before and after images.\n- Describe what changed\n- Evaluate if the changes are improvements\n- Note any unintended consequences\n- Summarize the overall transformation`,\n\n \"a-b-test\": `Analyze these two variants for A/B testing purposes.\n- Identify the key differences being tested\n- Predict which might perform better and why\n- Note potential user experience impacts\n- Suggest what metrics to measure`,\n\n \"version-diff\": `Compare these two versions of the UI.\n- Document all changes between versions\n- Categorize changes (bug fix, feature, style)\n- Identify breaking changes\n- Note any regressions`,\n};\n\nexport const compareImages = defineSkill({\n name: \"compare-images\",\n description: \"Compare two images and describe their differences\",\n version: \"1.0.0\",\n model: \"ministral-3b\",\n input: CompareImagesInput,\n maxTokens: 1500,\n temperature: 0.3,\n\n async run({ input, gerbil }) {\n const { image1, image2, type = \"visual-diff\", focus } = input;\n\n if (!gerbil.supportsVision()) {\n throw new Error(\n `Current model doesn't support vision. Load a vision model like \"ministral-3b\" first.`,\n );\n }\n\n let prompt = `I'm showing you two images for comparison.\\n\\n${comparisonPrompts[type]}`;\n\n if (focus && focus.length > 0) {\n prompt += `\\n\\nFocus specifically on: ${focus.join(\", \")}.`;\n }\n\n prompt += \"\\n\\nThe first image is shown first, followed by the second image.\";\n\n const result = await gerbil.generate(prompt, {\n images: [{ source: image1 }, { source: image2 }],\n maxTokens: this.maxTokens,\n temperature: this.temperature,\n });\n\n return result.text;\n },\n});\n","/**\n * Describe Image Skill\n *\n * Describe an image using vision AI.\n */\n\nimport { z } from \"zod\";\nimport { defineSkill } from \"../registry.js\";\n\nconst DescribeImageInput = z.object({\n /** Image URL or data URI */\n image: z.string(),\n\n /** What to focus on */\n focus: z.enum([\"general\", \"details\", \"text\", \"objects\", \"scene\", \"colors\"]).default(\"general\"),\n\n /** Output format */\n format: z.enum([\"paragraph\", \"bullets\", \"structured\"]).default(\"paragraph\"),\n\n /** Maximum detail level */\n detail: z.enum([\"brief\", \"normal\", \"comprehensive\"]).default(\"normal\"),\n});\n\nexport type DescribeImageInput = z.infer<typeof DescribeImageInput>;\n\nconst focusPrompts: Record<string, string> = {\n general:\n \"Describe this image comprehensively, covering the main subject, context, and notable elements.\",\n details:\n \"Describe this image in detail, including colors, textures, lighting, and small elements.\",\n text: \"Extract and describe any text visible in this image. Include the text content and its context.\",\n objects:\n \"List and describe all objects you can identify in this image, including their positions.\",\n scene: \"Describe the scene, setting, and atmosphere of this image. What story does it tell?\",\n colors: \"Analyze the color palette and visual composition of this image.\",\n};\n\nconst detailLengths: Record<string, number> = {\n brief: 150,\n normal: 400,\n comprehensive: 800,\n};\n\nexport const describeImage = defineSkill({\n name: \"describe-image\",\n description: \"Describe an image using vision AI\",\n version: \"1.0.0\",\n model: \"ministral-3b\",\n input: DescribeImageInput,\n temperature: 0.5,\n\n async run({ input, gerbil }) {\n const { image, focus = \"general\", format = \"paragraph\", detail = \"normal\" } = input;\n\n // Check if model supports vision\n if (!gerbil.supportsVision()) {\n throw new Error(\n `Current model doesn't support vision. Load a vision model like \"ministral-3b\" first.`,\n );\n }\n\n const formatInstructions: Record<string, string> = {\n paragraph: \"Write your description as natural flowing paragraphs.\",\n bullets: \"Format your description as clear bullet points.\",\n structured:\n \"Structure your description with sections: Overview, Main Elements, Details, Observations.\",\n };\n\n const prompt = `${focusPrompts[focus]}\\n\\n${formatInstructions[format]}`;\n\n const result = await gerbil.generate(prompt, {\n images: [{ source: image }],\n maxTokens: detailLengths[detail],\n temperature: this.temperature,\n });\n\n return result.text;\n },\n});\n","/**\n * Explain Skill\n *\n * Explain code or concepts at various levels.\n * Optionally speaks the explanation aloud.\n */\n\nimport { execSync } from \"node:child_process\";\nimport { unlinkSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { z } from \"zod\";\nimport { defineSkill } from \"../registry.js\";\n\nconst ExplainInput = z.object({\n /** Content to explain */\n content: z.string(),\n\n /** Explanation level */\n level: z.enum([\"beginner\", \"intermediate\", \"expert\"]).default(\"intermediate\"),\n\n /** Programming language (for code) */\n language: z.string().optional(),\n\n /** Speak the explanation aloud using TTS */\n speak: z.boolean().default(false),\n\n /** Voice for TTS (if speak is true) */\n voice: z.enum([\"af_heart\", \"af_bella\", \"bf_emma\", \"am_fenrir\"]).default(\"af_heart\"),\n});\n\nexport type ExplainInput = z.infer<typeof ExplainInput>;\n\nconst levelGuide = {\n beginner: \"Explain like I'm new to programming. Use simple terms and analogies.\",\n intermediate: \"Explain for someone with programming experience.\",\n expert: \"Explain with technical depth, including implementation details.\",\n};\n\nfunction saveWav(filename: string, audio: Float32Array, sampleRate: number): void {\n const buffer = Buffer.alloc(44 + audio.length * 2);\n buffer.write(\"RIFF\", 0);\n buffer.writeUInt32LE(36 + audio.length * 2, 4);\n buffer.write(\"WAVE\", 8);\n buffer.write(\"fmt \", 12);\n buffer.writeUInt32LE(16, 16);\n buffer.writeUInt16LE(1, 20);\n buffer.writeUInt16LE(1, 22);\n buffer.writeUInt32LE(sampleRate, 24);\n buffer.writeUInt32LE(sampleRate * 2, 28);\n buffer.writeUInt16LE(2, 32);\n buffer.writeUInt16LE(16, 34);\n buffer.write(\"data\", 36);\n buffer.writeUInt32LE(audio.length * 2, 40);\n\n for (let i = 0; i < audio.length; i++) {\n const s = Math.max(-1, Math.min(1, audio[i]));\n buffer.writeInt16LE(Math.round(s * 32767), 44 + i * 2);\n }\n\n writeFileSync(filename, buffer);\n}\n\nfunction playAudio(audioFile: string): void {\n const platform = process.platform;\n if (platform === \"darwin\") {\n execSync(`afplay \"${audioFile}\"`, { stdio: \"inherit\" });\n } else if (platform === \"linux\") {\n try {\n execSync(`aplay \"${audioFile}\"`, { stdio: \"inherit\" });\n } catch {\n execSync(`paplay \"${audioFile}\"`, { stdio: \"inherit\" });\n }\n } else if (platform === \"win32\") {\n execSync(`powershell -c \"(New-Object Media.SoundPlayer '${audioFile}').PlaySync()\"`, {\n stdio: \"inherit\",\n });\n }\n}\n\nexport const explain = defineSkill({\n name: \"explain\",\n description: \"Explain code or concepts at various levels (optionally speak aloud)\",\n version: \"1.0.0\",\n input: ExplainInput,\n temperature: 0.5,\n maxTokens: 500,\n\n async run({ input, gerbil }) {\n const { content, level = \"intermediate\", language, speak = false, voice = \"af_heart\" } = input;\n\n let systemPrompt = `You are a patient teacher.\n${levelGuide[level]}`;\n\n if (language) {\n systemPrompt += `\\nThis is ${language} code.`;\n }\n\n systemPrompt += \"\\nBe clear and concise.\";\n\n const result = await gerbil.generate(`Explain this:\\n\\n${content}`, {\n system: systemPrompt,\n maxTokens: this.maxTokens,\n temperature: this.temperature,\n });\n\n const explanation = result.text;\n\n // Optionally speak the explanation\n if (speak) {\n // Split into sentences for smoother playback\n const sentences = explanation.split(/(?<=[.!?])\\s+/).filter((s) => s.trim());\n\n for (const sentence of sentences) {\n if (!sentence.trim()) continue;\n\n const speechResult = await gerbil.speak(sentence, { voice });\n const tempFile = join(tmpdir(), `gerbil-explain-${Date.now()}.wav`);\n saveWav(tempFile, speechResult.audio, speechResult.sampleRate);\n\n try {\n playAudio(tempFile);\n } finally {\n try {\n unlinkSync(tempFile);\n } catch {\n // Ignore\n }\n }\n }\n }\n\n return explanation;\n },\n});\n","/**\n * Extract Skill\n *\n * Extract structured data from content.\n */\n\nimport { z } from \"zod\";\nimport { defineSkill } from \"../registry.js\";\n\nconst ExtractInput = z.object({\n /** Content to extract from */\n content: z.string(),\n\n /** Zod schema for extraction (passed at runtime) */\n schema: z.any(),\n\n /** Additional context */\n context: z.string().optional(),\n});\n\nexport type ExtractInput = z.infer<typeof ExtractInput>;\n\nexport const extract = defineSkill({\n name: \"extract\",\n description: \"Extract structured data from content\",\n version: \"1.0.0\",\n input: ExtractInput,\n temperature: 0.3,\n\n async run({ input, gerbil }) {\n const { content, schema, context } = input;\n\n let prompt = \"Extract structured data from the following content.\";\n\n if (context) {\n prompt += `\\nContext: ${context}`;\n }\n\n prompt += `\\n\\nContent:\\n${content}`;\n\n return gerbil.json(prompt, { schema });\n },\n});\n","/**\n * Extract from Image Skill\n *\n * Extract text, code, data, or structured information from images.\n */\n\nimport { z } from \"zod\";\nimport { defineSkill } from \"../registry.js\";\n\nconst ExtractFromImageInput = z.object({\n /** Image URL or data URI */\n image: z.string(),\n\n /** What to extract */\n extract: z.enum([\"text\", \"code\", \"data\", \"table\", \"diagram\", \"form\", \"receipt\"]).default(\"text\"),\n\n /** Output format */\n outputFormat: z.enum([\"raw\", \"json\", \"markdown\", \"csv\"]).default(\"raw\"),\n\n /** Language hint for code extraction */\n language: z.string().optional(),\n});\n\nexport type ExtractFromImageInput = z.infer<typeof ExtractFromImageInput>;\n\nconst extractionPrompts: Record<string, string> = {\n text: `Extract all visible text from this image.\n- Preserve the original formatting and structure as much as possible\n- Include headings, paragraphs, and any labels\n- Note any text that's unclear or partially visible\n- Maintain the reading order`,\n\n code: `Extract the code visible in this image.\n- Preserve exact syntax, indentation, and formatting\n- Include comments if visible\n- Note any parts that are unclear\n- Format as a proper code block`,\n\n data: `Extract all data, numbers, and structured information from this image.\n- Include labels and their associated values\n- Preserve numerical precision\n- Note units and currencies\n- Identify any patterns or relationships`,\n\n table: `Extract the table data from this image.\n- Identify all columns and rows\n- Preserve cell alignment where meaningful\n- Handle merged cells appropriately\n- Include headers and any totals`,\n\n diagram: `Describe and extract information from this diagram or flowchart.\n- List all nodes/boxes and their labels\n- Describe connections and their directions\n- Note any annotations or legends\n- Explain the flow or relationships`,\n\n form: `Extract form fields and their values from this image.\n- List each field label and its value\n- Note required fields if indicated\n- Include dropdown selections\n- Preserve the form structure`,\n\n receipt: `Extract receipt/invoice information from this image.\n- Vendor/store name\n- Date and time\n- Line items with quantities and prices\n- Subtotals, taxes, and total\n- Payment method if shown`,\n};\n\nconst formatInstructions: Record<string, string> = {\n raw: \"Output the extracted content as plain text.\",\n json: \"Output the extracted content as valid JSON with appropriate structure.\",\n markdown: \"Format the output as Markdown with proper headings and formatting.\",\n csv: \"Output tabular data in CSV format with proper quoting.\",\n};\n\nexport const extractFromImage = defineSkill({\n name: \"extract-from-image\",\n description: \"Extract text, code, tables, or data from images\",\n version: \"1.0.0\",\n model: \"ministral-3b\",\n input: ExtractFromImageInput,\n maxTokens: 2000,\n temperature: 0.1, // Low temp for accuracy\n\n async run({ input, gerbil }) {\n const { image, extract = \"text\", outputFormat = \"raw\", language } = input;\n\n if (!gerbil.supportsVision()) {\n throw new Error(\n `Current model doesn't support vision. Load a vision model like \"ministral-3b\" first.`,\n );\n }\n\n let prompt = extractionPrompts[extract];\n\n if (language && extract === \"code\") {\n prompt += `\\n\\nThe code appears to be ${language}.`;\n }\n\n prompt += `\\n\\n${formatInstructions[outputFormat]}`;\n\n const result = await gerbil.generate(prompt, {\n images: [{ source: image }],\n maxTokens: this.maxTokens,\n temperature: this.temperature,\n });\n\n return result.text;\n },\n});\n","/**\n * Read Aloud Skill\n *\n * Read text or file content aloud using TTS.\n * Supports streaming for long content.\n */\n\nimport { execSync } from \"child_process\";\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\nimport { z } from \"zod\";\nimport { defineSkill } from \"../registry.js\";\n\nconst ReadAloudInput = z.object({\n /** File path to read, or text content if no file */\n content: z.string(),\n\n /** Treat content as file path (default: auto-detect) */\n isFile: z.boolean().optional(),\n\n /** Voice ID */\n voice: z\n .enum([\"af_heart\", \"af_bella\", \"af_nicole\", \"am_fenrir\", \"am_michael\", \"bf_emma\", \"bm_george\"])\n .default(\"af_heart\"),\n\n /** Speech speed */\n speed: z.number().min(0.5).max(2.0).default(1.0),\n\n /** Maximum characters to read (default: 5000) */\n maxLength: z.number().default(5000),\n\n /** Summarize if content exceeds maxLength instead of truncating */\n summarizeIfLong: z.boolean().default(false),\n});\n\nexport type ReadAloudInput = z.infer<typeof ReadAloudInput>;\n\nfunction saveWav(filename: string, audio: Float32Array, sampleRate: number): void {\n const buffer = Buffer.alloc(44 + audio.length * 2);\n buffer.write(\"RIFF\", 0);\n buffer.writeUInt32LE(36 + audio.length * 2, 4);\n buffer.write(\"WAVE\", 8);\n buffer.write(\"fmt \", 12);\n buffer.writeUInt32LE(16, 16);\n buffer.writeUInt16LE(1, 20);\n buffer.writeUInt16LE(1, 22);\n buffer.writeUInt32LE(sampleRate, 24);\n buffer.writeUInt32LE(sampleRate * 2, 28);\n buffer.writeUInt16LE(2, 32);\n buffer.writeUInt16LE(16, 34);\n buffer.write(\"data\", 36);\n buffer.writeUInt32LE(audio.length * 2, 40);\n\n for (let i = 0; i < audio.length; i++) {\n const s = Math.max(-1, Math.min(1, audio[i]));\n buffer.writeInt16LE(Math.round(s * 32767), 44 + i * 2);\n }\n\n writeFileSync(filename, buffer);\n}\n\nfunction playAudio(audioFile: string): void {\n const platform = process.platform;\n if (platform === \"darwin\") {\n execSync(`afplay \"${audioFile}\"`, { stdio: \"inherit\" });\n } else if (platform === \"linux\") {\n try {\n execSync(`aplay \"${audioFile}\"`, { stdio: \"inherit\" });\n } catch {\n execSync(`paplay \"${audioFile}\"`, { stdio: \"inherit\" });\n }\n } else if (platform === \"win32\") {\n execSync(`powershell -c \"(New-Object Media.SoundPlayer '${audioFile}').PlaySync()\"`, {\n stdio: \"inherit\",\n });\n }\n}\n\nexport const readAloud = defineSkill({\n name: \"read-aloud\",\n description: \"Read text or file content aloud using TTS\",\n version: \"1.0.0\",\n input: ReadAloudInput,\n\n async run({ input, gerbil }) {\n const {\n content,\n isFile,\n voice = \"af_heart\",\n speed = 1.0,\n maxLength = 5000,\n summarizeIfLong = false,\n } = input;\n\n // Determine if content is a file path\n const shouldReadFile = isFile ?? existsSync(content);\n\n let textToRead: string;\n\n if (shouldReadFile) {\n if (!existsSync(content)) {\n throw new Error(`File not found: ${content}`);\n }\n textToRead = readFileSync(content, \"utf-8\");\n } else {\n textToRead = content;\n }\n\n // Handle long content\n if (textToRead.length > maxLength) {\n if (summarizeIfLong) {\n // Use AI to summarize\n const summary = await gerbil.generate(\n `Summarize this content in a way that's good for reading aloud (2-3 paragraphs max):\\n\\n${textToRead.slice(0, 10000)}`,\n {\n system: \"You are a skilled narrator. Create a clear, spoken-word friendly summary.\",\n maxTokens: 500,\n temperature: 0.3,\n },\n );\n textToRead = summary.text;\n } else {\n // Truncate\n textToRead = textToRead.slice(0, maxLength) + \"...\";\n }\n }\n\n // Generate and play speech using streaming for long content\n const sentences = textToRead.split(/(?<=[.!?])\\s+/).filter((s) => s.trim());\n let totalDuration = 0;\n\n for (const sentence of sentences) {\n if (!sentence.trim()) continue;\n\n const result = await gerbil.speak(sentence, { voice, speed });\n totalDuration += result.duration;\n\n // Play this chunk\n const tempFile = join(tmpdir(), `gerbil-read-${Date.now()}.wav`);\n saveWav(tempFile, result.audio, result.sampleRate);\n\n try {\n playAudio(tempFile);\n } finally {\n try {\n unlinkSync(tempFile);\n } catch {\n // Ignore\n }\n }\n }\n\n const charCount = textToRead.length;\n const source = shouldReadFile ? `file \"${content}\"` : \"text\";\n\n return `Read ${charCount} characters from ${source} (${totalDuration.toFixed(1)}s audio)`;\n },\n});\n","/**\n * Review Skill\n *\n * Code review with configurable focus areas.\n */\n\nimport { z } from \"zod\";\nimport { defineSkill } from \"../registry.js\";\n\nconst ReviewInput = z.object({\n /** Code to review */\n code: z.string(),\n\n /** Focus areas */\n focus: z.array(z.enum([\"security\", \"performance\", \"style\", \"bugs\", \"all\"])).default([\"all\"]),\n\n /** Output format */\n format: z.enum([\"inline\", \"summary\", \"detailed\"]).default(\"summary\"),\n});\n\nexport type ReviewInput = z.infer<typeof ReviewInput>;\n\nexport const review = defineSkill({\n name: \"review\",\n description: \"Code review with configurable focus areas\",\n version: \"1.0.0\",\n input: ReviewInput,\n temperature: 0.3,\n maxTokens: 600,\n\n async run({ input, gerbil }) {\n const { code, focus = [\"all\"], format = \"summary\" } = input;\n\n let systemPrompt = `You are a senior code reviewer.\nReview the code for:`;\n\n if (focus.includes(\"all\")) {\n systemPrompt +=\n \"\\n- Security vulnerabilities\\n- Performance issues\\n- Code style and readability\\n- Potential bugs\";\n } else {\n if (focus.includes(\"security\")) {\n systemPrompt += \"\\n- Security vulnerabilities\";\n }\n if (focus.includes(\"performance\")) {\n systemPrompt += \"\\n- Performance issues\";\n }\n if (focus.includes(\"style\")) {\n systemPrompt += \"\\n- Code style and readability\";\n }\n if (focus.includes(\"bugs\")) {\n systemPrompt += \"\\n- Potential bugs\";\n }\n }\n\n if (format === \"summary\") {\n systemPrompt += \"\\n\\nProvide a brief summary of issues found.\";\n } else if (format === \"detailed\") {\n systemPrompt += \"\\n\\nProvide detailed feedback with suggestions.\";\n } else {\n systemPrompt += \"\\n\\nProvide inline-style comments.\";\n }\n\n const result = await gerbil.generate(`Review this code:\\n\\n${code}`, {\n system: systemPrompt,\n maxTokens: this.maxTokens,\n temperature: this.temperature,\n });\n\n return result.text;\n },\n});\n","/**\n * Speak Skill\n *\n * Convert text to speech using on-device TTS.\n */\n\nimport { execSync } from \"child_process\";\nimport { unlinkSync, writeFileSync } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\nimport { z } from \"zod\";\nimport { defineSkill } from \"../registry.js\";\n\nconst SpeakInput = z.object({\n /** Text to speak */\n text: z.string(),\n\n /** Voice ID (default: af_heart) */\n voice: z\n .enum([\n \"af_heart\",\n \"af_bella\",\n \"af_nicole\",\n \"af_sarah\",\n \"am_fenrir\",\n \"am_michael\",\n \"bf_emma\",\n \"bf_isabella\",\n \"bm_george\",\n \"bm_lewis\",\n ])\n .default(\"af_heart\"),\n\n /** Speech speed 0.5-2.0 (default: 1.0) */\n speed: z.number().min(0.5).max(2.0).default(1.0),\n\n /** Save to file instead of playing */\n output: z.string().optional(),\n});\n\nexport type SpeakInput = z.infer<typeof SpeakInput>;\n\n/**\n * Save Float32Array as WAV file\n */\nfunction saveWav(filename: string, audio: Float32Array, sampleRate: number): void {\n const buffer = Buffer.alloc(44 + audio.length * 2);\n\n // WAV header\n buffer.write(\"RIFF\", 0);\n buffer.writeUInt32LE(36 + audio.length * 2, 4);\n buffer.write(\"WAVE\", 8);\n buffer.write(\"fmt \", 12);\n buffer.writeUInt32LE(16, 16);\n buffer.writeUInt16LE(1, 20);\n buffer.writeUInt16LE(1, 22);\n buffer.writeUInt32LE(sampleRate, 24);\n buffer.writeUInt32LE(sampleRate * 2, 28);\n buffer.writeUInt16LE(2, 32);\n buffer.writeUInt16LE(16, 34);\n buffer.write(\"data\", 36);\n buffer.writeUInt32LE(audio.length * 2, 40);\n\n for (let i = 0; i < audio.length; i++) {\n const s = Math.max(-1, Math.min(1, audio[i]));\n buffer.writeInt16LE(Math.round(s * 32767), 44 + i * 2);\n }\n\n writeFileSync(filename, buffer);\n}\n\nexport const speak = defineSkill({\n name: \"speak\",\n description: \"Convert text to speech using on-device TTS (Kokoro-82M)\",\n version: \"1.0.0\",\n input: SpeakInput,\n\n async run({ input, gerbil }) {\n const { text, voice = \"af_heart\", speed = 1.0, output } = input;\n\n // Generate speech\n const result = await gerbil.speak(text, { voice, speed });\n\n if (output) {\n // Save to specified file\n saveWav(output, result.audio, result.sampleRate);\n return `Saved ${result.duration.toFixed(1)}s of audio to ${output}`;\n }\n\n // Play audio using system player\n const tempFile = join(tmpdir(), `gerbil-speak-${Date.now()}.wav`);\n saveWav(tempFile, result.audio, result.sampleRate);\n\n try {\n // Detect platform and use appropriate player\n const platform = process.platform;\n if (platform === \"darwin\") {\n execSync(`afplay \"${tempFile}\"`, { stdio: \"inherit\" });\n } else if (platform === \"linux\") {\n // Try common Linux audio players\n try {\n execSync(`aplay \"${tempFile}\"`, { stdio: \"inherit\" });\n } catch {\n try {\n execSync(`paplay \"${tempFile}\"`, { stdio: \"inherit\" });\n } catch {\n execSync(`play \"${tempFile}\"`, { stdio: \"inherit\" });\n }\n }\n } else if (platform === \"win32\") {\n execSync(`powershell -c \"(New-Object Media.SoundPlayer '${tempFile}').PlaySync()\"`, {\n stdio: \"inherit\",\n });\n }\n } finally {\n // Clean up temp file\n try {\n unlinkSync(tempFile);\n } catch {\n // Ignore cleanup errors\n }\n }\n\n return `Spoke ${result.duration.toFixed(1)}s of audio (voice: ${voice}, speed: ${speed}x)`;\n },\n});\n","/**\n * Summarize Skill\n *\n * Summarize content in various lengths and formats.\n */\n\nimport { z } from \"zod\";\nimport { defineSkill } from \"../registry.js\";\n\nconst SummarizeInput = z.object({\n /** Content to summarize */\n content: z.string(),\n\n /** Summary length */\n length: z.enum([\"short\", \"medium\", \"long\"]).default(\"medium\"),\n\n /** Output format */\n format: z.enum([\"paragraph\", \"bullets\"]).default(\"paragraph\"),\n\n /** Focus areas */\n focus: z.array(z.string()).optional(),\n});\n\nexport type SummarizeInput = z.infer<typeof SummarizeInput>;\n\nconst lengthGuide = {\n short: \"2-3 sentences\",\n medium: \"1 paragraph (4-6 sentences)\",\n long: \"2-3 paragraphs\",\n};\n\nconst maxTokensGuide = {\n short: 100,\n medium: 200,\n long: 400,\n};\n\nexport const summarize = defineSkill({\n name: \"summarize\",\n description: \"Summarize content in various lengths and formats\",\n version: \"1.0.0\",\n input: SummarizeInput,\n temperature: 0.3,\n\n async run({ input, gerbil }) {\n const { content, length = \"medium\", format = \"paragraph\", focus } = input;\n\n let systemPrompt = `You are a summarization expert.\nSummarize the content in ${lengthGuide[length]}.`;\n\n if (format === \"bullets\") {\n systemPrompt += \"\\nUse bullet points.\";\n }\n\n if (focus && focus.length > 0) {\n systemPrompt += `\\nFocus on: ${focus.join(\", \")}.`;\n }\n\n systemPrompt += \"\\nOnly output the summary, nothing else.\";\n\n const result = await gerbil.generate(`Summarize this:\\n\\n${content}`, {\n system: systemPrompt,\n maxTokens: maxTokensGuide[length],\n temperature: this.temperature,\n });\n\n return result.text;\n },\n});\n","/**\n * Test Skill\n *\n * Generate tests for code.\n */\n\nimport { z } from \"zod\";\nimport { defineSkill } from \"../registry.js\";\n\nconst TestInput = z.object({\n /** Code to test */\n code: z.string(),\n\n /** Test framework */\n framework: z.enum([\"jest\", \"vitest\", \"mocha\", \"playwright\"]).default(\"vitest\"),\n\n /** Test style */\n style: z.enum([\"unit\", \"integration\", \"e2e\"]).default(\"unit\"),\n});\n\nexport type TestInput = z.infer<typeof TestInput>;\n\nexport const test = defineSkill({\n name: \"test\",\n description: \"Generate tests for code\",\n version: \"1.0.0\",\n input: TestInput,\n temperature: 0.3,\n maxTokens: 800,\n\n async run({ input, gerbil }) {\n const { code, framework, style } = input;\n\n const systemPrompt = `You are a test engineer.\nGenerate ${style} tests using ${framework} for the provided code.\nInclude edge cases and error scenarios.\nOnly output the test code, no explanations.`;\n\n const result = await gerbil.generate(`Generate tests for:\\n\\n${code}`, {\n system: systemPrompt,\n maxTokens: this.maxTokens,\n temperature: this.temperature,\n });\n\n return result.text;\n },\n});\n","/**\n * Title Skill\n *\n * Generate titles for content.\n */\n\nimport { z } from \"zod\";\nimport { defineSkill } from \"../registry.js\";\n\nconst TitleInput = z.object({\n /** Content to generate title for */\n content: z.string(),\n\n /** Title style */\n style: z.enum([\"professional\", \"clickbait\", \"seo\", \"simple\"]).default(\"professional\"),\n\n /** Max length */\n maxLength: z.number().default(60),\n});\n\nexport type TitleInput = z.infer<typeof TitleInput>;\n\nconst styleGuide = {\n professional: \"Clear, informative, and professional\",\n clickbait: \"Engaging and curiosity-inducing\",\n seo: \"SEO-optimized with relevant keywords\",\n simple: \"Simple and straightforward\",\n};\n\nexport const title = defineSkill({\n name: \"title\",\n description: \"Generate titles for content\",\n version: \"1.0.0\",\n input: TitleInput,\n temperature: 0.7,\n maxTokens: 30,\n\n async run({ input, gerbil }) {\n const { content, style = \"professional\", maxLength = 60 } = input;\n\n const systemPrompt = `Generate a title for the content.\nStyle: ${styleGuide[style]}\nMax length: ${maxLength} characters\nOnly output the title, nothing else.`;\n\n const result = await gerbil.generate(`Generate a title for:\\n\\n${content.substring(0, 1000)}`, {\n system: systemPrompt,\n maxTokens: this.maxTokens,\n temperature: this.temperature,\n });\n\n let generatedTitle = result.text.trim();\n generatedTitle = generatedTitle.replace(/^[\"']|[\"']$/g, \"\");\n\n if (generatedTitle.length > maxLength) {\n generatedTitle = `${generatedTitle.substring(0, maxLength - 3)}...`;\n }\n\n return generatedTitle;\n },\n});\n","/**\n * Transcribe Skill\n *\n * Convert audio to text using on-device STT (Whisper).\n */\n\nimport { existsSync, readFileSync, writeFileSync } from \"fs\";\nimport { resolve } from \"path\";\nimport { z } from \"zod\";\nimport { defineSkill } from \"../registry.js\";\n\nconst TranscribeInput = z.object({\n /** Audio file path (WAV, MP3, etc.) or URL */\n audio: z.string(),\n\n /** STT model ID (default: whisper-tiny.en) */\n model: z\n .enum([\"whisper-tiny.en\", \"whisper-base.en\", \"whisper-small.en\", \"whisper-large-v3-turbo\"])\n .default(\"whisper-tiny.en\"),\n\n /** Language hint for multilingual models */\n language: z.string().optional(),\n\n /** Include timestamps in output */\n timestamps: z.boolean().default(false),\n\n /** Save transcription to file */\n output: z.string().optional(),\n});\n\nexport type TranscribeInput = z.infer<typeof TranscribeInput>;\n\nexport const transcribe = defineSkill({\n name: \"transcribe\",\n description: \"Transcribe audio to text using on-device STT (Whisper)\",\n version: \"1.0.0\",\n input: TranscribeInput,\n\n async run({ input, gerbil }) {\n const { audio, model = \"whisper-tiny.en\", language, timestamps = false, output } = input;\n\n // Load audio file\n const filePath = resolve(audio);\n if (!existsSync(filePath)) {\n throw new Error(`Audio file not found: ${filePath}`);\n }\n\n const audioData = new Uint8Array(readFileSync(filePath));\n\n // Load the specified STT model\n await gerbil.loadSTT(model);\n\n // Transcribe\n const result = await gerbil.transcribe(audioData, {\n language,\n timestamps,\n });\n\n // Format output\n let text: string;\n if (timestamps && result.segments) {\n text = result.segments\n .map((seg) => `[${seg.start.toFixed(1)}s - ${seg.end.toFixed(1)}s] ${seg.text}`)\n .join(\"\\n\");\n } else {\n text = result.text;\n }\n\n // Save to file if requested\n if (output) {\n writeFileSync(output, text);\n return `Transcribed ${result.duration.toFixed(1)}s of audio to ${output}`;\n }\n\n return text;\n },\n});\n","/**\n * Translate Skill\n *\n * Translate text between languages.\n */\n\nimport { z } from \"zod\";\nimport { defineSkill } from \"../registry.js\";\n\nconst TranslateInput = z.object({\n /** Text to translate */\n text: z.string(),\n\n /** Source language (auto-detected if not provided) */\n from: z.string().optional(),\n\n /** Target language */\n to: z.string(),\n\n /** Preserve formatting */\n preserveFormatting: z.boolean().default(true),\n});\n\nexport type TranslateInput = z.infer<typeof TranslateInput>;\n\nexport const translate = defineSkill({\n name: \"translate\",\n description: \"Translate text between languages\",\n version: \"1.0.0\",\n input: TranslateInput,\n temperature: 0.3,\n\n async run({ input, gerbil }) {\n const { text, from, to, preserveFormatting } = input;\n\n let systemPrompt = `You are a professional translator.\nTranslate the text to ${to}.`;\n\n if (from) {\n systemPrompt += `\\nThe source language is ${from}.`;\n }\n\n if (preserveFormatting) {\n systemPrompt += \"\\nPreserve the original formatting (paragraphs, lists, etc.).\";\n }\n\n systemPrompt += \"\\nOnly output the translation, nothing else.\";\n\n const result = await gerbil.generate(`Translate:\\n\\n${text}`, {\n system: systemPrompt,\n maxTokens: Math.ceil(text.length / 2),\n temperature: this.temperature,\n });\n\n return result.text;\n },\n});\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"stt-Bv_dum-R.mjs","names":["WHISPER_MODELS: STTModelConfig[]","tfDevice: \"webgpu\" | \"cpu\" | \"wasm\"","audioData: Float32Array","pipelineOptions: any","transcribeResult: TranscribeResult","audioBuffer: Float32Array[]","intervalId: ReturnType<typeof setInterval> | null","e: any"],"sources":["../src/core/stt.ts"],"sourcesContent":["/**\n * Speech-to-Text with Whisper\n *\n * Provides local speech recognition using Whisper ONNX models via transformers.js.\n * Supports multiple model sizes and languages.\n *\n * @example\n * ```ts\n * const stt = new WhisperSTT();\n * await stt.load({ onProgress: (p) => console.log(p.status) });\n *\n * // Transcribe audio (Float32Array at 16kHz)\n * const result = await stt.transcribe(audioData);\n * console.log(result.text);\n *\n * // With timestamps\n * const result = await stt.transcribe(audioData, { timestamps: true });\n * for (const seg of result.segments) {\n * console.log(`[${seg.start.toFixed(1)}s] ${seg.text}`);\n * }\n * ```\n */\n\nimport type {\n LoadSTTOptions,\n ProgressInfo,\n STTModelConfig,\n StreamingTranscriptionOptions,\n StreamingTranscriptionSession,\n TranscribeOptions,\n TranscribeResult,\n TranscribeSegment,\n} from \"./types.js\";\n\n// ============================================\n// Model Registry\n// ============================================\n\n/**\n * Available Whisper models\n * Ordered by size (smallest first)\n */\nexport const WHISPER_MODELS: STTModelConfig[] = [\n {\n id: \"whisper-tiny.en\",\n repo: \"onnx-community/whisper-tiny.en\",\n description: \"Tiny English-only model, fastest\",\n size: \"39M\",\n multilingual: false,\n languages: [\"en\"],\n sampleRate: 16000,\n },\n {\n id: \"whisper-tiny\",\n repo: \"onnx-community/whisper-tiny\",\n description: \"Tiny multilingual model\",\n size: \"39M\",\n multilingual: true,\n languages: [\"en\", \"es\", \"fr\", \"de\", \"it\", \"pt\", \"nl\", \"ru\", \"zh\", \"ja\", \"ko\"],\n sampleRate: 16000,\n },\n {\n id: \"whisper-base.en\",\n repo: \"onnx-community/whisper-base.en\",\n description: \"Base English-only model, good balance\",\n size: \"74M\",\n multilingual: false,\n languages: [\"en\"],\n sampleRate: 16000,\n },\n {\n id: \"whisper-base\",\n repo: \"onnx-community/whisper-base\",\n description: \"Base multilingual model\",\n size: \"74M\",\n multilingual: true,\n languages: [\"en\", \"es\", \"fr\", \"de\", \"it\", \"pt\", \"nl\", \"ru\", \"zh\", \"ja\", \"ko\"],\n sampleRate: 16000,\n },\n {\n id: \"whisper-small.en\",\n repo: \"onnx-community/whisper-small.en\",\n description: \"Small English-only model, high quality\",\n size: \"244M\",\n multilingual: false,\n languages: [\"en\"],\n sampleRate: 16000,\n },\n {\n id: \"whisper-small\",\n repo: \"onnx-community/whisper-small\",\n description: \"Small multilingual model\",\n size: \"244M\",\n multilingual: true,\n languages: [\"en\", \"es\", \"fr\", \"de\", \"it\", \"pt\", \"nl\", \"ru\", \"zh\", \"ja\", \"ko\"],\n sampleRate: 16000,\n },\n {\n id: \"whisper-large-v3-turbo\",\n repo: \"onnx-community/whisper-large-v3-turbo\",\n description: \"Large Turbo model, 5.4x faster, 80+ languages\",\n size: \"809M\",\n multilingual: true,\n languages: [\n \"en\",\n \"es\",\n \"fr\",\n \"de\",\n \"it\",\n \"pt\",\n \"nl\",\n \"ru\",\n \"zh\",\n \"ja\",\n \"ko\",\n \"ar\",\n \"hi\",\n \"vi\",\n \"th\",\n ],\n sampleRate: 16000,\n },\n];\n\n// Default model\nconst DEFAULT_MODEL = \"whisper-tiny.en\";\n\n// ============================================\n// Audio Utilities\n// ============================================\n\n/**\n * Decode WAV file to Float32Array\n * Handles stereo to mono conversion\n */\nexport function decodeWav(buffer: Uint8Array): { audio: Float32Array; sampleRate: number } {\n const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);\n\n // Validate RIFF header\n const riff = String.fromCharCode(buffer[0], buffer[1], buffer[2], buffer[3]);\n if (riff !== \"RIFF\") {\n throw new Error(\"Invalid WAV file: missing RIFF header\");\n }\n\n // Get format details\n const numChannels = view.getUint16(22, true);\n const sampleRate = view.getUint32(24, true);\n const bitsPerSample = view.getUint16(34, true);\n\n if (bitsPerSample !== 16) {\n throw new Error(`Unsupported bit depth: ${bitsPerSample}. Only 16-bit WAV is supported.`);\n }\n\n // Find data chunk\n let dataOffset = 12;\n while (dataOffset < buffer.length - 8) {\n const chunkId = String.fromCharCode(\n buffer[dataOffset],\n buffer[dataOffset + 1],\n buffer[dataOffset + 2],\n buffer[dataOffset + 3],\n );\n const chunkSize = view.getUint32(dataOffset + 4, true);\n if (chunkId === \"data\") {\n dataOffset += 8;\n break;\n }\n dataOffset += 8 + chunkSize;\n }\n\n const dataSize = buffer.length - dataOffset;\n const bytesPerSample = bitsPerSample / 8;\n const totalSamples = Math.floor(dataSize / bytesPerSample);\n const samplesPerChannel = Math.floor(totalSamples / numChannels);\n\n // Convert to mono Float32Array\n const audio = new Float32Array(samplesPerChannel);\n\n for (let i = 0; i < samplesPerChannel; i++) {\n if (numChannels === 2) {\n const left = view.getInt16(dataOffset + i * 4, true);\n const right = view.getInt16(dataOffset + i * 4 + 2, true);\n audio[i] = (left + right) / 2 / 32768;\n } else {\n const sample = view.getInt16(dataOffset + i * 2, true);\n audio[i] = sample / 32768;\n }\n }\n\n return { audio, sampleRate };\n}\n\n/**\n * Resample audio to target sample rate using linear interpolation\n */\nexport function resampleAudio(audio: Float32Array, fromRate: number, toRate: number): Float32Array {\n if (fromRate === toRate) return audio;\n\n const ratio = toRate / fromRate;\n const newLength = Math.round(audio.length * ratio);\n const result = new Float32Array(newLength);\n\n for (let i = 0; i < newLength; i++) {\n const srcIndex = i / ratio;\n const floor = Math.floor(srcIndex);\n const ceil = Math.min(floor + 1, audio.length - 1);\n const t = srcIndex - floor;\n result[i] = audio[floor] * (1 - t) + audio[ceil] * t;\n }\n\n return result;\n}\n\n// ============================================\n// WhisperSTT Class\n// ============================================\n\n/**\n * Speech-to-Text using Whisper ONNX models\n */\nexport class WhisperSTT {\n private modelConfig: STTModelConfig;\n private pipeline: any = null;\n private loadPromise: Promise<void> | null = null;\n private _isLoaded = false;\n private _deviceMode: \"webgpu\" | \"cpu\" = \"cpu\";\n\n constructor(modelId: string = DEFAULT_MODEL) {\n const config = WHISPER_MODELS.find((m) => m.id === modelId);\n if (!config) {\n const available = WHISPER_MODELS.map((m) => m.id).join(\", \");\n throw new Error(`Unknown STT model: ${modelId}. Available: ${available}`);\n }\n this.modelConfig = config;\n }\n\n /**\n * Check if model is loaded\n */\n isLoaded(): boolean {\n return this._isLoaded;\n }\n\n /**\n * Get model configuration\n */\n getModelConfig(): STTModelConfig {\n return this.modelConfig;\n }\n\n /**\n * Get model info (alias for getModelConfig)\n */\n getModelInfo(): STTModelConfig {\n return this.modelConfig;\n }\n\n /**\n * Get current device mode\n */\n getDeviceMode(): \"webgpu\" | \"cpu\" {\n return this._deviceMode;\n }\n\n /**\n * List available models\n */\n static listModels(): STTModelConfig[] {\n return [...WHISPER_MODELS];\n }\n\n /**\n * Load the STT model\n */\n async load(options: LoadSTTOptions = {}): Promise<void> {\n if (this._isLoaded) return;\n if (this.loadPromise) {\n await this.loadPromise;\n return;\n }\n\n this.loadPromise = this._load(options);\n await this.loadPromise;\n }\n\n private async _load(options: LoadSTTOptions = {}): Promise<void> {\n const { onProgress, device = \"auto\" } = options;\n\n onProgress?.({ status: \"Loading transformers.js...\" });\n\n // Import transformers.js dynamically\n const transformers = await import(\"@huggingface/transformers\");\n const { pipeline, env } = transformers;\n\n // Check if we're in Node.js or browser\n const isNode = typeof process !== \"undefined\" && process.versions?.node;\n\n // Configure environment based on runtime\n if (isNode) {\n // Node.js: allow local models (for CLI/server use)\n env.allowLocalModels = true;\n env.allowRemoteModels = true;\n } else {\n // Browser: use IndexedDB cache, fetch from HuggingFace CDN\n env.useBrowserCache = true;\n env.allowLocalModels = false;\n }\n\n // Determine device\n // Note: Whisper ONNX models work best with fp32 on CPU/WASM\n // WebGPU support for ASR is limited, so we use CPU for reliability\n let tfDevice: \"webgpu\" | \"cpu\" | \"wasm\" = \"cpu\";\n\n // In browser, use WASM for better compatibility\n if (!isNode) {\n tfDevice = \"wasm\";\n }\n\n // Store device mode\n this._deviceMode = \"cpu\"; // STT always reports as CPU since WASM is CPU-based\n\n onProgress?.({ status: `Loading ${this.modelConfig.id}...` });\n\n // Load the ASR pipeline\n // Always use fp32 for Whisper models (fp16 not available for ONNX ASR)\n this.pipeline = await pipeline(\"automatic-speech-recognition\", this.modelConfig.repo, {\n dtype: \"fp32\",\n device: tfDevice,\n progress_callback: (progress: any) => {\n if (progress.status === \"progress\" && progress.file) {\n onProgress?.({\n status: `Downloading ${progress.file}`,\n progress: Math.round(progress.progress || 0),\n file: progress.file,\n });\n }\n },\n });\n\n this._isLoaded = true;\n onProgress?.({ status: `Ready (${tfDevice.toUpperCase()})!` });\n }\n\n /**\n * Transcribe audio to text\n *\n * @param audio - Audio data as Float32Array (mono, 16kHz preferred) or Uint8Array (WAV file)\n * @param options - Transcription options\n * @returns Transcription result with text and optional timestamps\n */\n async transcribe(\n audio: Float32Array | Uint8Array,\n options: TranscribeOptions = {},\n ): Promise<TranscribeResult> {\n if (!this._isLoaded) {\n throw new Error(\"STT model not loaded. Call load() first.\");\n }\n\n const { language, timestamps = false, onProgress } = options;\n const startTime = performance.now();\n\n // Convert Uint8Array (WAV) to Float32Array\n let audioData: Float32Array;\n let inputSampleRate = 16000;\n\n if (audio instanceof Uint8Array) {\n onProgress?.({ status: \"Decoding audio...\" });\n const decoded = decodeWav(audio);\n audioData = decoded.audio;\n inputSampleRate = decoded.sampleRate;\n } else {\n audioData = audio;\n }\n\n // Resample to 16kHz if needed\n if (inputSampleRate !== 16000) {\n onProgress?.({ status: \"Resampling to 16kHz...\" });\n audioData = resampleAudio(audioData, inputSampleRate, 16000);\n }\n\n const audioDuration = audioData.length / 16000;\n onProgress?.({ status: `Transcribing ${audioDuration.toFixed(1)}s of audio...` });\n\n // Build pipeline options\n const pipelineOptions: any = {};\n\n // Only set language for multilingual models\n if (language && this.modelConfig.multilingual) {\n pipelineOptions.language = language;\n pipelineOptions.task = \"transcribe\";\n }\n\n // Enable timestamps if requested\n if (timestamps) {\n pipelineOptions.return_timestamps = true;\n }\n\n // Run transcription\n const result = await this.pipeline(audioData, pipelineOptions);\n\n const totalTime = performance.now() - startTime;\n\n // Build result\n let text = result.text?.trim() || \"\";\n\n // Filter out Whisper artifacts\n if (text === \"[BLANK_AUDIO]\" || text === \"(blank audio)\" || text === \"[BLANK AUDIO]\") {\n text = \"\";\n }\n\n const transcribeResult: TranscribeResult = {\n text,\n language: language || (this.modelConfig.multilingual ? \"auto\" : \"en\"),\n duration: audioDuration,\n totalTime,\n };\n\n // Add segments if timestamps were requested\n if (timestamps && result.chunks) {\n transcribeResult.segments = result.chunks.map(\n (chunk: any): TranscribeSegment => ({\n text: chunk.text?.trim() || \"\",\n start: chunk.timestamp?.[0] || 0,\n end: chunk.timestamp?.[1] || 0,\n }),\n );\n }\n\n onProgress?.({ status: \"Done!\" });\n\n return transcribeResult;\n }\n\n /**\n * Create a streaming transcription session\n *\n * Transcribes audio in real-time by processing chunks at regular intervals.\n * Perfect for live captioning, call transcription, or real-time subtitles.\n *\n * @param options - Streaming options\n * @returns Streaming session controller\n *\n * @example\n * ```ts\n * const session = stt.createStreamingSession({\n * chunkDuration: 3000, // Transcribe every 3 seconds\n * onChunk: (text, idx) => console.log(`Chunk ${idx}: ${text}`),\n * onTranscript: (fullText) => console.log(\"Full:\", fullText),\n * });\n *\n * // Feed audio data as it comes in (Float32Array at 16kHz)\n * session.feedAudio(audioChunk);\n *\n * // Or manually trigger transcription\n * await session.flush();\n *\n * // Stop and get final transcript\n * const finalText = await session.stop();\n * ```\n */\n createStreamingSession(\n options: StreamingTranscriptionOptions = {},\n ): StreamingTranscriptionSession {\n const {\n chunkDuration = 3000,\n minChunkSize = 8000, // ~0.5 seconds at 16kHz\n onChunk,\n onTranscript,\n onError,\n language,\n } = options;\n\n let audioBuffer: Float32Array[] = [];\n let fullTranscript = \"\";\n let chunkIndex = 0;\n let intervalId: ReturnType<typeof setInterval> | null = null;\n let isRunning = false;\n\n const getBufferSize = (): number => {\n return audioBuffer.reduce((sum, chunk) => sum + chunk.length, 0);\n };\n\n const mergeBuffer = (): Float32Array => {\n const totalLength = getBufferSize();\n const merged = new Float32Array(totalLength);\n let offset = 0;\n for (const chunk of audioBuffer) {\n merged.set(chunk, offset);\n offset += chunk.length;\n }\n return merged;\n };\n\n const transcribeBuffer = async (): Promise<string> => {\n if (!this._isLoaded || getBufferSize() < minChunkSize) {\n return \"\";\n }\n\n const audio = mergeBuffer();\n audioBuffer = []; // Clear buffer\n\n try {\n const result = await this.transcribe(audio, { language });\n const text = result.text.trim();\n\n if (text) {\n chunkIndex++;\n onChunk?.(text, chunkIndex);\n\n // Append to full transcript\n fullTranscript = fullTranscript + (fullTranscript ? \" \" : \"\") + text;\n onTranscript?.(fullTranscript);\n }\n\n return text;\n } catch (e: any) {\n onError?.(e.message || \"Transcription failed\");\n return \"\";\n }\n };\n\n let aborted = false;\n\n const session: StreamingTranscriptionSession = {\n feedAudio: (audio: Float32Array) => {\n if (!aborted) {\n audioBuffer.push(audio);\n }\n },\n\n flush: async () => {\n if (aborted) return \"\";\n return transcribeBuffer();\n },\n\n start: () => {\n if (isRunning || aborted) return;\n isRunning = true;\n\n intervalId = setInterval(async () => {\n if (isRunning && !aborted) {\n await transcribeBuffer();\n }\n }, chunkDuration);\n },\n\n stop: async () => {\n isRunning = false;\n\n if (intervalId) {\n clearInterval(intervalId);\n intervalId = null;\n }\n\n // Transcribe any remaining audio (unless aborted)\n if (!aborted && getBufferSize() >= minChunkSize) {\n await transcribeBuffer();\n }\n\n return fullTranscript;\n },\n\n abort: () => {\n // Immediately stop without final transcription\n aborted = true;\n isRunning = false;\n\n if (intervalId) {\n clearInterval(intervalId);\n intervalId = null;\n }\n\n audioBuffer = [];\n },\n\n isRunning: () => isRunning,\n\n getTranscript: () => fullTranscript,\n\n getChunkCount: () => chunkIndex,\n\n reset: () => {\n audioBuffer = [];\n fullTranscript = \"\";\n chunkIndex = 0;\n },\n };\n\n return session;\n }\n\n /**\n * Dispose of resources\n */\n dispose(): void {\n this.pipeline = null;\n this._isLoaded = false;\n this.loadPromise = null;\n }\n}\n"],"mappings":"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"}
@@ -1,3 +0,0 @@
1
- import { i as resampleAudio, n as WhisperSTT, r as decodeWav, t as WHISPER_MODELS } from "./stt-Bv_dum-R.mjs";
2
-
3
- export { WhisperSTT };
@@ -1 +0,0 @@
1
- {"version":3,"file":"tts-DG6denWG.mjs","names":["KOKORO_VOICES: VoiceInfo[]","SUPERTONIC_VOICES: VoiceInfo[]","TTS_MODELS: Record<string, TTSModelConfig>","dtype: \"fp32\" | \"fp16\" | \"q8\" | \"q4\" | \"q4f16\"","allAudio: Float32Array[]","chunk: AudioChunk"],"sources":["../src/core/tts.ts"],"sourcesContent":["/**\n * Text-to-Speech with Kokoro-82M\n *\n * Provides local TTS using the Kokoro-82M model with multiple voice options.\n * Uses kokoro-js for proper phoneme conversion (G2P) and audio generation.\n * Supports streaming audio generation and works in both Node.js and browser.\n *\n * @example\n * ```ts\n * const tts = new KokoroTTS();\n * await tts.load({ onProgress: (p) => console.log(p.status) });\n *\n * // List available voices\n * const voices = tts.listVoices();\n *\n * // Generate audio\n * const result = await tts.speak(\"Hello world\", { voice: \"af_bella\" });\n * // result.audio = Float32Array, result.sampleRate = 24000\n *\n * // Stream audio chunks\n * for await (const chunk of tts.speakStream(\"Long text...\")) {\n * playAudioChunk(chunk);\n * }\n * ```\n */\n\nimport type {\n AudioChunk,\n LoadTTSOptions,\n SpeakOptions,\n SpeakResult,\n TTSModelConfig,\n VoiceInfo,\n} from \"./types.js\";\n\n// Regex for sentence splitting (defined at module level for performance)\nconst SENTENCE_SPLIT_REGEX = /(?<=[.!?])\\s+/;\n\n// ============================================\n// Voice Registry\n// ============================================\n\n/**\n * Kokoro voice definitions\n * Voice IDs follow pattern: {language}{gender}_{name}\n * - a = American English\n * - b = British English\n * - f = female, m = male\n */\nexport const KOKORO_VOICES: VoiceInfo[] = [\n // American English - Female (ordered by quality)\n {\n id: \"af_heart\",\n name: \"Heart\",\n gender: \"female\",\n language: \"en-us\",\n description: \"American female, highest quality voice (Grade A)\",\n embeddingFile: \"voices/af_heart.bin\",\n },\n {\n id: \"af_bella\",\n name: \"Bella\",\n gender: \"female\",\n language: \"en-us\",\n description: \"American female, warm and friendly (Grade A-)\",\n embeddingFile: \"voices/af_bella.bin\",\n },\n {\n id: \"af_nicole\",\n name: \"Nicole\",\n gender: \"female\",\n language: \"en-us\",\n description: \"American female, soft and gentle (Grade B-)\",\n embeddingFile: \"voices/af_nicole.bin\",\n },\n {\n id: \"af_sarah\",\n name: \"Sarah\",\n gender: \"female\",\n language: \"en-us\",\n description: \"American female, clear and professional (Grade C+)\",\n embeddingFile: \"voices/af_sarah.bin\",\n },\n {\n id: \"af_sky\",\n name: \"Sky\",\n gender: \"female\",\n language: \"en-us\",\n description: \"American female, young and energetic (Grade C-)\",\n embeddingFile: \"voices/af_sky.bin\",\n },\n {\n id: \"af_alloy\",\n name: \"Alloy\",\n gender: \"female\",\n language: \"en-us\",\n description: \"American female (Grade C)\",\n embeddingFile: \"voices/af_alloy.bin\",\n },\n {\n id: \"af_aoede\",\n name: \"Aoede\",\n gender: \"female\",\n language: \"en-us\",\n description: \"American female (Grade C+)\",\n embeddingFile: \"voices/af_aoede.bin\",\n },\n {\n id: \"af_kore\",\n name: \"Kore\",\n gender: \"female\",\n language: \"en-us\",\n description: \"American female (Grade C+)\",\n embeddingFile: \"voices/af_kore.bin\",\n },\n {\n id: \"af_nova\",\n name: \"Nova\",\n gender: \"female\",\n language: \"en-us\",\n description: \"American female (Grade C)\",\n embeddingFile: \"voices/af_nova.bin\",\n },\n {\n id: \"af_river\",\n name: \"River\",\n gender: \"female\",\n language: \"en-us\",\n description: \"American female (Grade D)\",\n embeddingFile: \"voices/af_river.bin\",\n },\n {\n id: \"af_jessica\",\n name: \"Jessica\",\n gender: \"female\",\n language: \"en-us\",\n description: \"American female (Grade D)\",\n embeddingFile: \"voices/af_jessica.bin\",\n },\n // American English - Male\n {\n id: \"am_fenrir\",\n name: \"Fenrir\",\n gender: \"male\",\n language: \"en-us\",\n description: \"American male, best quality (Grade C+)\",\n embeddingFile: \"voices/am_fenrir.bin\",\n },\n {\n id: \"am_michael\",\n name: \"Michael\",\n gender: \"male\",\n language: \"en-us\",\n description: \"American male, warm and friendly (Grade C+)\",\n embeddingFile: \"voices/am_michael.bin\",\n },\n {\n id: \"am_puck\",\n name: \"Puck\",\n gender: \"male\",\n language: \"en-us\",\n description: \"American male (Grade C+)\",\n embeddingFile: \"voices/am_puck.bin\",\n },\n {\n id: \"am_adam\",\n name: \"Adam\",\n gender: \"male\",\n language: \"en-us\",\n description: \"American male, deep voice (Grade F+)\",\n embeddingFile: \"voices/am_adam.bin\",\n },\n {\n id: \"am_echo\",\n name: \"Echo\",\n gender: \"male\",\n language: \"en-us\",\n description: \"American male (Grade D)\",\n embeddingFile: \"voices/am_echo.bin\",\n },\n {\n id: \"am_eric\",\n name: \"Eric\",\n gender: \"male\",\n language: \"en-us\",\n description: \"American male (Grade D)\",\n embeddingFile: \"voices/am_eric.bin\",\n },\n {\n id: \"am_liam\",\n name: \"Liam\",\n gender: \"male\",\n language: \"en-us\",\n description: \"American male (Grade D)\",\n embeddingFile: \"voices/am_liam.bin\",\n },\n {\n id: \"am_onyx\",\n name: \"Onyx\",\n gender: \"male\",\n language: \"en-us\",\n description: \"American male (Grade D)\",\n embeddingFile: \"voices/am_onyx.bin\",\n },\n {\n id: \"am_santa\",\n name: \"Santa\",\n gender: \"male\",\n language: \"en-us\",\n description: \"American male, festive (Grade D-)\",\n embeddingFile: \"voices/am_santa.bin\",\n },\n // British English - Female\n {\n id: \"bf_emma\",\n name: \"Emma\",\n gender: \"female\",\n language: \"en-gb\",\n description: \"British female, elegant and clear (Grade B-)\",\n embeddingFile: \"voices/bf_emma.bin\",\n },\n {\n id: \"bf_isabella\",\n name: \"Isabella\",\n gender: \"female\",\n language: \"en-gb\",\n description: \"British female, sophisticated (Grade C)\",\n embeddingFile: \"voices/bf_isabella.bin\",\n },\n {\n id: \"bf_alice\",\n name: \"Alice\",\n gender: \"female\",\n language: \"en-gb\",\n description: \"British female (Grade D)\",\n embeddingFile: \"voices/bf_alice.bin\",\n },\n {\n id: \"bf_lily\",\n name: \"Lily\",\n gender: \"female\",\n language: \"en-gb\",\n description: \"British female (Grade D)\",\n embeddingFile: \"voices/bf_lily.bin\",\n },\n // British English - Male\n {\n id: \"bm_george\",\n name: \"George\",\n gender: \"male\",\n language: \"en-gb\",\n description: \"British male, distinguished (Grade C)\",\n embeddingFile: \"voices/bm_george.bin\",\n },\n {\n id: \"bm_fable\",\n name: \"Fable\",\n gender: \"male\",\n language: \"en-gb\",\n description: \"British male (Grade C)\",\n embeddingFile: \"voices/bm_fable.bin\",\n },\n {\n id: \"bm_lewis\",\n name: \"Lewis\",\n gender: \"male\",\n language: \"en-gb\",\n description: \"British male, friendly (Grade D+)\",\n embeddingFile: \"voices/bm_lewis.bin\",\n },\n {\n id: \"bm_daniel\",\n name: \"Daniel\",\n gender: \"male\",\n language: \"en-gb\",\n description: \"British male (Grade D)\",\n embeddingFile: \"voices/bm_daniel.bin\",\n },\n];\n\n// ============================================\n// Supertonic Voice Registry\n// ============================================\n\n/**\n * Supertonic voice definitions\n * 4 built-in voices: F1, F2 (female), M1, M2 (male)\n */\nexport const SUPERTONIC_VOICES: VoiceInfo[] = [\n {\n id: \"F1\",\n name: \"Female 1\",\n gender: \"female\",\n language: \"en\",\n description: \"Female voice 1 - Clear and natural\",\n embeddingFile: \"voices/F1.bin\",\n },\n {\n id: \"F2\",\n name: \"Female 2\",\n gender: \"female\",\n language: \"en\",\n description: \"Female voice 2 - Warm and expressive\",\n embeddingFile: \"voices/F2.bin\",\n },\n {\n id: \"M1\",\n name: \"Male 1\",\n gender: \"male\",\n language: \"en\",\n description: \"Male voice 1 - Deep and confident\",\n embeddingFile: \"voices/M1.bin\",\n },\n {\n id: \"M2\",\n name: \"Male 2\",\n gender: \"male\",\n language: \"en\",\n description: \"Male voice 2 - Friendly and casual\",\n embeddingFile: \"voices/M2.bin\",\n },\n];\n\n// ============================================\n// TTS Model Registry\n// ============================================\n\nexport const TTS_MODELS: Record<string, TTSModelConfig> = {\n \"kokoro-82m\": {\n id: \"kokoro-82m\",\n repo: \"onnx-community/Kokoro-82M-v1.0-ONNX\",\n description: \"Kokoro 82M - High-quality multilingual TTS\",\n size: \"~330MB\",\n sampleRate: 24000,\n voices: KOKORO_VOICES,\n defaultVoice: \"af_heart\",\n languages: [\"en-us\", \"en-gb\"],\n },\n \"supertonic-66m\": {\n id: \"supertonic-66m\",\n repo: \"onnx-community/Supertonic-TTS-ONNX\",\n description: \"Supertonic 66M - Fast on-device TTS (167x realtime)\",\n size: \"~250MB\",\n sampleRate: 44100,\n voices: SUPERTONIC_VOICES,\n defaultVoice: \"F1\",\n languages: [\"en\"],\n },\n};\n\n/**\n * Get TTS model config by ID\n */\nexport function getTTSModelConfig(modelId: string): TTSModelConfig | null {\n return TTS_MODELS[modelId] || null;\n}\n\n/**\n * List all available TTS models\n */\nexport function listTTSModels(): TTSModelConfig[] {\n return Object.values(TTS_MODELS);\n}\n\n// ============================================\n// Kokoro TTS Class (wraps kokoro-js)\n// ============================================\n\n// kokoro-js types\ninterface KokoroJSAudio {\n audio: Float32Array;\n sampling_rate: number;\n}\n\ninterface KokoroJSInstance {\n generate(text: string, options?: { voice?: string; speed?: number }): Promise<KokoroJSAudio>;\n list_voices(): Array<{\n name: string;\n language: string;\n gender: string;\n traits: string;\n targetQuality: string;\n overallGrade: string;\n }>;\n}\n\n/**\n * Kokoro TTS - Local text-to-speech with voice selection\n *\n * Uses kokoro-js (official Kokoro library by xenova) for high-quality speech synthesis.\n * Includes proper G2P (grapheme-to-phoneme) conversion for accurate pronunciation.\n */\nexport class KokoroTTS {\n private kokoroInstance: KokoroJSInstance | null = null;\n private modelConfig: TTSModelConfig;\n private loadPromise: Promise<void> | null = null;\n private _isLoaded = false;\n private _deviceMode: \"webgpu\" | \"cpu\" = \"cpu\";\n\n constructor(modelId = \"kokoro-82m\") {\n const config = getTTSModelConfig(modelId);\n if (!config) {\n throw new Error(\n `Unknown TTS model: ${modelId}. Available: ${Object.keys(TTS_MODELS).join(\", \")}`,\n );\n }\n this.modelConfig = config;\n }\n\n // ============================================\n // Model Loading\n // ============================================\n\n /**\n * Load the TTS model\n *\n * @example\n * ```ts\n * const tts = new KokoroTTS();\n * await tts.load({\n * onProgress: (p) => console.log(p.status, p.progress),\n * device: \"webgpu\",\n * });\n * ```\n */\n async load(options: LoadTTSOptions = {}): Promise<void> {\n if (this._isLoaded) {\n return;\n }\n\n // Prevent duplicate loads\n if (this.loadPromise) {\n return this.loadPromise;\n }\n\n this.loadPromise = this._load(options);\n await this.loadPromise;\n }\n\n private async _load(options: LoadTTSOptions = {}): Promise<void> {\n const { onProgress, device = \"auto\" } = options;\n\n onProgress?.({ status: `Loading TTS model (${this.modelConfig.id})...` });\n\n try {\n // Dynamically import kokoro-js\n const kokoroModule = await import(\"kokoro-js\");\n const { KokoroTTS: KokoroJS } = kokoroModule;\n\n // Determine device/dtype\n const isBrowser = typeof window !== \"undefined\";\n let dtype: \"fp32\" | \"fp16\" | \"q8\" | \"q4\" | \"q4f16\" = \"fp32\";\n\n if (device === \"webgpu\" || (device === \"auto\" && isBrowser && \"gpu\" in navigator)) {\n dtype = \"fp16\";\n this._deviceMode = \"webgpu\";\n } else {\n dtype = \"fp32\";\n this._deviceMode = \"cpu\";\n }\n\n onProgress?.({ status: `Loading model with ${dtype} precision...` });\n\n // Load the model using kokoro-js\n // kokoro-js handles:\n // - Model loading with proper quantization\n // - Phoneme tokenization (G2P) via phonemizer\n // - Voice embeddings\n this.kokoroInstance = (await KokoroJS.from_pretrained(this.modelConfig.repo, {\n dtype,\n progress_callback: (progress: any) => {\n if (progress.status === \"progress\" && progress.file) {\n onProgress?.({\n status: `Downloading ${progress.file}`,\n progress: Math.round(progress.progress || 0),\n file: progress.file,\n });\n } else if (progress.status === \"ready\") {\n onProgress?.({ status: \"Model ready\" });\n }\n },\n })) as unknown as KokoroJSInstance;\n\n this._isLoaded = true;\n onProgress?.({ status: `Ready (${this._deviceMode.toUpperCase()})!` });\n } catch (error) {\n this.loadPromise = null;\n throw error;\n }\n }\n\n /**\n * Ensure model is loaded (lazy loading)\n */\n async ensureLoaded(options?: LoadTTSOptions): Promise<void> {\n if (!this._isLoaded) {\n await this.load(options);\n }\n }\n\n // ============================================\n // Voice Management\n // ============================================\n\n /**\n * Get list of available voices\n *\n * @example\n * ```ts\n * const voices = tts.listVoices();\n * // [{ id: \"af_heart\", name: \"Heart\", gender: \"female\", ... }, ...]\n * ```\n */\n listVoices(): VoiceInfo[] {\n return [...this.modelConfig.voices];\n }\n\n /**\n * Get a specific voice by ID\n */\n getVoice(voiceId: string): VoiceInfo | null {\n return this.modelConfig.voices.find((v) => v.id === voiceId) || null;\n }\n\n /**\n * Get voices by gender\n */\n getVoicesByGender(gender: \"male\" | \"female\"): VoiceInfo[] {\n return this.modelConfig.voices.filter((v) => v.gender === gender);\n }\n\n /**\n * Get voices by language\n */\n getVoicesByLanguage(language: string): VoiceInfo[] {\n return this.modelConfig.voices.filter(\n (v) => v.language === language || v.language.startsWith(language),\n );\n }\n\n // ============================================\n // Speech Generation\n // ============================================\n\n /**\n * Generate speech from text\n *\n * @example\n * ```ts\n * const result = await tts.speak(\"Hello world\", {\n * voice: \"af_heart\",\n * speed: 1.0,\n * });\n *\n * // Play in browser\n * const audioContext = new AudioContext();\n * const buffer = audioContext.createBuffer(1, result.audio.length, result.sampleRate);\n * buffer.copyToChannel(result.audio, 0);\n * const source = audioContext.createBufferSource();\n * source.buffer = buffer;\n * source.connect(audioContext.destination);\n * source.start();\n * ```\n */\n async speak(text: string, options: SpeakOptions = {}): Promise<SpeakResult> {\n await this.ensureLoaded({ onProgress: options.onProgress });\n\n const { voice = this.modelConfig.defaultVoice, speed = 1.0 } = options;\n\n // Validate voice exists in our registry\n const voiceInfo = this.getVoice(voice);\n if (!voiceInfo) {\n throw new Error(`Unknown voice: ${voice}. Use listVoices() to see available options.`);\n }\n\n if (!this.kokoroInstance) {\n throw new Error(\"Model not loaded\");\n }\n\n const startTime = performance.now();\n\n // Generate audio using kokoro-js\n const result = await this.kokoroInstance.generate(text, {\n voice,\n speed,\n });\n\n const totalTime = performance.now() - startTime;\n\n return {\n audio: result.audio,\n sampleRate: result.sampling_rate,\n duration: result.audio.length / result.sampling_rate,\n voice,\n totalTime,\n };\n }\n\n /**\n * Stream speech generation (yields audio chunks as they're generated)\n *\n * @example\n * ```ts\n * for await (const chunk of tts.speakStream(\"Long text...\")) {\n * // chunk.samples = Float32Array\n * // chunk.sampleRate = 24000\n * // chunk.isFinal = boolean\n * playChunk(chunk);\n * }\n * ```\n */\n async *speakStream(\n text: string,\n options: SpeakOptions = {},\n ): AsyncGenerator<AudioChunk, SpeakResult, unknown> {\n await this.ensureLoaded({ onProgress: options.onProgress });\n\n const { voice = this.modelConfig.defaultVoice, speed = 1.0 } = options;\n\n // Validate voice\n const voiceInfo = this.getVoice(voice);\n if (!voiceInfo) {\n throw new Error(`Unknown voice: ${voice}. Use listVoices() to see available options.`);\n }\n\n if (!this.kokoroInstance) {\n throw new Error(\"Model not loaded\");\n }\n\n const startTime = performance.now();\n\n // Split text into sentences for streaming\n const sentences = this.splitIntoSentences(text);\n const allAudio: Float32Array[] = [];\n let chunkIndex = 0;\n let sampleRate = this.modelConfig.sampleRate;\n\n for (let i = 0; i < sentences.length; i++) {\n const sentence = sentences[i];\n if (!sentence.trim()) {\n continue;\n }\n\n const result = await this.kokoroInstance.generate(sentence, {\n voice,\n speed,\n });\n\n sampleRate = result.sampling_rate;\n allAudio.push(result.audio);\n\n const chunk: AudioChunk = {\n samples: result.audio,\n sampleRate: result.sampling_rate,\n index: chunkIndex++,\n isFinal: i === sentences.length - 1,\n };\n\n yield chunk;\n options.onAudioChunk?.(chunk);\n }\n\n // Concatenate all audio\n const totalLength = allAudio.reduce((sum, arr) => sum + arr.length, 0);\n const fullAudio = new Float32Array(totalLength);\n let offset = 0;\n for (const chunk of allAudio) {\n fullAudio.set(chunk, offset);\n offset += chunk.length;\n }\n\n const totalTime = performance.now() - startTime;\n\n return {\n audio: fullAudio,\n sampleRate,\n duration: fullAudio.length / sampleRate,\n voice,\n totalTime,\n };\n }\n\n /**\n * Split text into sentences for streaming\n */\n private splitIntoSentences(text: string): string[] {\n // Split on sentence boundaries while preserving the delimiters\n return text.split(SENTENCE_SPLIT_REGEX).filter((s) => s.trim());\n }\n\n // ============================================\n // Status & Info\n // ============================================\n\n /**\n * Check if model is loaded\n */\n isLoaded(): boolean {\n return this._isLoaded;\n }\n\n /**\n * Get current device mode\n */\n getDeviceMode(): \"webgpu\" | \"cpu\" {\n return this._deviceMode;\n }\n\n /**\n * Get model configuration\n */\n getModelInfo(): TTSModelConfig {\n return { ...this.modelConfig };\n }\n\n /**\n * Get sample rate\n */\n getSampleRate(): number {\n return this.modelConfig.sampleRate;\n }\n\n // ============================================\n // Cleanup\n // ============================================\n\n /**\n * Dispose of resources\n */\n async dispose(): Promise<void> {\n // kokoro-js doesn't expose a dispose method currently\n // but we clear our reference\n this.kokoroInstance = null;\n this._isLoaded = false;\n this.loadPromise = null;\n }\n}\n\n// ============================================\n// Supertonic TTS Class (uses transformers.js)\n// ============================================\n\n/**\n * Supertonic TTS - Fast on-device text-to-speech\n *\n * Uses transformers.js with the Supertonic-TTS-ONNX model.\n * Generates speech at 167x realtime with 66M parameters.\n * Outputs at 44100 Hz sample rate.\n */\nexport class SupertonicTTS {\n private pipeline: any = null;\n private modelConfig: TTSModelConfig;\n private loadPromise: Promise<void> | null = null;\n private _isLoaded = false;\n private _deviceMode: \"webgpu\" | \"cpu\" = \"cpu\";\n private voiceEmbeddings: Map<string, Float32Array> = new Map();\n\n constructor(modelId = \"supertonic-66m\") {\n const config = getTTSModelConfig(modelId);\n if (!config) {\n throw new Error(\n `Unknown TTS model: ${modelId}. Available: ${Object.keys(TTS_MODELS).join(\", \")}`,\n );\n }\n this.modelConfig = config;\n }\n\n /**\n * Load the TTS model\n */\n async load(options: LoadTTSOptions = {}): Promise<void> {\n if (this._isLoaded) {\n return;\n }\n\n if (this.loadPromise) {\n return this.loadPromise;\n }\n\n this.loadPromise = this._load(options);\n await this.loadPromise;\n }\n\n private async _load(options: LoadTTSOptions = {}): Promise<void> {\n const { onProgress, device = \"auto\" } = options;\n\n onProgress?.({ status: `Loading TTS model (${this.modelConfig.id})...` });\n\n try {\n // Dynamically import transformers.js\n const { pipeline } = await import(\"@huggingface/transformers\");\n\n // Determine device\n const isBrowser = typeof window !== \"undefined\";\n if (device === \"webgpu\" || (device === \"auto\" && isBrowser && \"gpu\" in navigator)) {\n this._deviceMode = \"webgpu\";\n } else {\n this._deviceMode = \"cpu\";\n }\n\n onProgress?.({ status: `Loading Supertonic model...` });\n\n // Create TTS pipeline\n this.pipeline = await pipeline(\"text-to-speech\", this.modelConfig.repo, {\n dtype: \"fp32\", // Supertonic works best with fp32\n device: this._deviceMode,\n progress_callback: (progress: any) => {\n if (progress.status === \"progress\" && progress.file) {\n onProgress?.({\n status: `Downloading ${progress.file}`,\n progress: Math.round(progress.progress || 0),\n file: progress.file,\n });\n }\n },\n });\n\n // Load voice embeddings\n onProgress?.({ status: \"Loading voice embeddings...\" });\n await this.loadVoiceEmbeddings();\n\n this._isLoaded = true;\n onProgress?.({ status: `Ready (${this._deviceMode.toUpperCase()})!` });\n } catch (error) {\n this.loadPromise = null;\n throw error;\n }\n }\n\n /**\n * Load speaker embeddings for all voices\n * Supertonic uses 101x128 = 12,928 floats per voice\n */\n private async loadVoiceEmbeddings(): Promise<void> {\n // In browser, we'd fetch from HuggingFace Hub\n // In Node.js, we'd load from cache\n // For now, we'll lazy-load embeddings when needed\n // The pipeline handles this internally when we pass speaker_embeddings\n }\n\n async ensureLoaded(options?: LoadTTSOptions): Promise<void> {\n if (!this._isLoaded) {\n await this.load(options);\n }\n }\n\n listVoices(): VoiceInfo[] {\n return [...this.modelConfig.voices];\n }\n\n getVoice(voiceId: string): VoiceInfo | null {\n return this.modelConfig.voices.find((v) => v.id === voiceId) || null;\n }\n\n getVoicesByGender(gender: \"male\" | \"female\"): VoiceInfo[] {\n return this.modelConfig.voices.filter((v) => v.gender === gender);\n }\n\n /**\n * Generate speech from text\n */\n async speak(text: string, options: SpeakOptions = {}): Promise<SpeakResult> {\n await this.ensureLoaded({ onProgress: options.onProgress });\n\n const { voice = this.modelConfig.defaultVoice } = options;\n\n // Validate voice\n const voiceInfo = this.getVoice(voice);\n if (!voiceInfo) {\n throw new Error(`Unknown voice: ${voice}. Use listVoices() to see available options.`);\n }\n\n if (!this.pipeline) {\n throw new Error(\"Model not loaded\");\n }\n\n const startTime = performance.now();\n\n // Get or create speaker embedding\n // Supertonic expects [101, 128] = 12,928 floats\n let speakerEmbedding = this.voiceEmbeddings.get(voice);\n if (!speakerEmbedding) {\n // Load from HuggingFace Hub via fetch\n try {\n const voiceUrl = `https://huggingface.co/${this.modelConfig.repo}/resolve/main/voices/${voice}.bin`;\n const response = await fetch(voiceUrl);\n if (response.ok) {\n const buffer = await response.arrayBuffer();\n speakerEmbedding = new Float32Array(buffer);\n this.voiceEmbeddings.set(voice, speakerEmbedding);\n } else {\n throw new Error(`Failed to load voice: ${response.status}`);\n }\n } catch {\n // Fallback: create neutral embedding (not ideal but works)\n speakerEmbedding = new Float32Array(101 * 128).fill(0.1);\n this.voiceEmbeddings.set(voice, speakerEmbedding);\n }\n }\n\n // Generate audio\n const result = await this.pipeline(text, {\n speaker_embeddings: speakerEmbedding,\n });\n\n const totalTime = performance.now() - startTime;\n const audio = result.audio as Float32Array;\n const sampleRate = result.sampling_rate as number;\n\n return {\n audio,\n sampleRate,\n duration: audio.length / sampleRate,\n voice,\n totalTime,\n };\n }\n\n /**\n * Stream speech generation\n */\n async *speakStream(\n text: string,\n options: SpeakOptions = {},\n ): AsyncGenerator<AudioChunk, SpeakResult, unknown> {\n await this.ensureLoaded({ onProgress: options.onProgress });\n\n const { voice = this.modelConfig.defaultVoice, speed = 1.0 } = options;\n\n const voiceInfo = this.getVoice(voice);\n if (!voiceInfo) {\n throw new Error(`Unknown voice: ${voice}. Use listVoices() to see available options.`);\n }\n\n const startTime = performance.now();\n\n // Split text into sentences for streaming\n const sentences = text.split(SENTENCE_SPLIT_REGEX).filter((s) => s.trim());\n const allAudio: Float32Array[] = [];\n let chunkIndex = 0;\n let sampleRate = this.modelConfig.sampleRate;\n\n for (let i = 0; i < sentences.length; i++) {\n const sentence = sentences[i];\n if (!sentence.trim()) continue;\n\n const result = await this.speak(sentence, { voice, speed });\n sampleRate = result.sampleRate;\n allAudio.push(result.audio);\n\n const chunk: AudioChunk = {\n samples: result.audio,\n sampleRate: result.sampleRate,\n index: chunkIndex++,\n isFinal: i === sentences.length - 1,\n };\n\n yield chunk;\n options.onAudioChunk?.(chunk);\n }\n\n // Concatenate all audio\n const totalLength = allAudio.reduce((sum, arr) => sum + arr.length, 0);\n const fullAudio = new Float32Array(totalLength);\n let offset = 0;\n for (const chunk of allAudio) {\n fullAudio.set(chunk, offset);\n offset += chunk.length;\n }\n\n const totalTime = performance.now() - startTime;\n\n return {\n audio: fullAudio,\n sampleRate,\n duration: fullAudio.length / sampleRate,\n voice,\n totalTime,\n };\n }\n\n isLoaded(): boolean {\n return this._isLoaded;\n }\n\n getDeviceMode(): \"webgpu\" | \"cpu\" {\n return this._deviceMode;\n }\n\n getModelInfo(): TTSModelConfig {\n return { ...this.modelConfig };\n }\n\n getSampleRate(): number {\n return this.modelConfig.sampleRate;\n }\n\n async dispose(): Promise<void> {\n this.pipeline = null;\n this.voiceEmbeddings.clear();\n this._isLoaded = false;\n this.loadPromise = null;\n }\n}\n\n// ============================================\n// Unified TTS Factory\n// ============================================\n\nexport type TTSBackend = KokoroTTS | SupertonicTTS;\n\n/**\n * Create a TTS instance based on model ID\n */\nexport function createTTS(modelId: string = \"kokoro-82m\"): TTSBackend {\n if (modelId.startsWith(\"supertonic\")) {\n return new SupertonicTTS(modelId);\n }\n return new KokoroTTS(modelId);\n}\n\nexport default KokoroTTS;\n"],"mappings":"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"}
File without changes