@tryhamster/gerbil 1.0.0-rc.0 → 1.0.0-rc.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -14
- package/dist/auto-update-S9s5-g0C.mjs +3 -0
- package/dist/browser/index.d.ts +1009 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +2492 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/{chrome-backend-C5Un08O4.mjs → chrome-backend-CORwaIyC.mjs} +514 -73
- package/dist/chrome-backend-CORwaIyC.mjs.map +1 -0
- package/dist/{chrome-backend-CtwPENIW.mjs → chrome-backend-DIKYoWj-.mjs} +1 -1
- package/dist/cli.mjs +3359 -647
- package/dist/cli.mjs.map +1 -1
- package/dist/frameworks/express.d.mts +1 -1
- package/dist/frameworks/express.mjs +3 -4
- package/dist/frameworks/express.mjs.map +1 -1
- package/dist/frameworks/fastify.d.mts +1 -1
- package/dist/frameworks/fastify.mjs +2 -3
- package/dist/frameworks/fastify.mjs.map +1 -1
- package/dist/frameworks/hono.d.mts +1 -1
- package/dist/frameworks/hono.mjs +2 -3
- package/dist/frameworks/hono.mjs.map +1 -1
- package/dist/frameworks/next.d.mts +2 -2
- package/dist/frameworks/next.mjs +2 -3
- package/dist/frameworks/next.mjs.map +1 -1
- package/dist/frameworks/react.d.mts +1 -1
- package/dist/frameworks/trpc.d.mts +1 -1
- package/dist/frameworks/trpc.mjs +2 -3
- package/dist/frameworks/trpc.mjs.map +1 -1
- package/dist/gerbil-DJGqq7BX.mjs +4 -0
- package/dist/gerbil-DoDGHe6Z.mjs +1631 -0
- package/dist/gerbil-DoDGHe6Z.mjs.map +1 -0
- package/dist/gerbil-qOTe1nl2.d.mts +431 -0
- package/dist/gerbil-qOTe1nl2.d.mts.map +1 -0
- package/dist/index.d.mts +411 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +7 -6
- package/dist/index.mjs.map +1 -1
- package/dist/integrations/ai-sdk.d.mts +122 -4
- package/dist/integrations/ai-sdk.d.mts.map +1 -1
- package/dist/integrations/ai-sdk.mjs +238 -11
- package/dist/integrations/ai-sdk.mjs.map +1 -1
- package/dist/integrations/langchain.d.mts +132 -2
- package/dist/integrations/langchain.d.mts.map +1 -1
- package/dist/integrations/langchain.mjs +175 -8
- package/dist/integrations/langchain.mjs.map +1 -1
- package/dist/integrations/llamaindex.d.mts +1 -1
- package/dist/integrations/llamaindex.mjs +2 -3
- package/dist/integrations/llamaindex.mjs.map +1 -1
- package/dist/integrations/mcp-client.mjs +4 -4
- package/dist/integrations/mcp-client.mjs.map +1 -1
- package/dist/integrations/mcp.d.mts +2 -2
- package/dist/integrations/mcp.d.mts.map +1 -1
- package/dist/integrations/mcp.mjs +5 -6
- package/dist/kokoro-BNTb6egA.mjs +20210 -0
- package/dist/kokoro-BNTb6egA.mjs.map +1 -0
- package/dist/kokoro-CMOGDSgT.js +20212 -0
- package/dist/kokoro-CMOGDSgT.js.map +1 -0
- package/dist/{mcp-R8kRLIKb.mjs → mcp-kzDDWIoS.mjs} +10 -37
- package/dist/mcp-kzDDWIoS.mjs.map +1 -0
- package/dist/microphone-DaMZFRuR.mjs +3 -0
- package/dist/{one-liner-BUQR0nqq.mjs → one-liner-DxnNs_JK.mjs} +2 -2
- package/dist/{one-liner-BUQR0nqq.mjs.map → one-liner-DxnNs_JK.mjs.map} +1 -1
- package/dist/repl-DGUw4fCc.mjs +9 -0
- package/dist/skills/index.d.mts +305 -14
- package/dist/skills/index.d.mts.map +1 -1
- package/dist/skills/index.mjs +5 -6
- package/dist/skills-DulrOPeP.mjs +1435 -0
- package/dist/skills-DulrOPeP.mjs.map +1 -0
- package/dist/stt-1WIefHwc.mjs +3 -0
- package/dist/stt-CG_7KB_0.mjs +434 -0
- package/dist/stt-CG_7KB_0.mjs.map +1 -0
- package/dist/stt-Dne6SENv.js +434 -0
- package/dist/stt-Dne6SENv.js.map +1 -0
- package/dist/{tools-BsiEE6f2.mjs → tools-Bi1P7Xoy.mjs} +6 -7
- package/dist/{tools-BsiEE6f2.mjs.map → tools-Bi1P7Xoy.mjs.map} +1 -1
- package/dist/transformers.web-DiD1gTwk.js +44695 -0
- package/dist/transformers.web-DiD1gTwk.js.map +1 -0
- package/dist/transformers.web-u34VxRFM.js +3 -0
- package/dist/tts-B1pZMlDv.mjs +3 -0
- package/dist/tts-C2FzKuSx.js +725 -0
- package/dist/tts-C2FzKuSx.js.map +1 -0
- package/dist/tts-CyHhcLtN.mjs +731 -0
- package/dist/tts-CyHhcLtN.mjs.map +1 -0
- package/dist/types-CiTc7ez3.d.mts +353 -0
- package/dist/types-CiTc7ez3.d.mts.map +1 -0
- package/dist/{utils-7vXqtq2Q.mjs → utils-CZBZ8dgR.mjs} +1 -1
- package/dist/{utils-7vXqtq2Q.mjs.map → utils-CZBZ8dgR.mjs.map} +1 -1
- package/docs/ai-sdk.md +137 -21
- package/docs/browser.md +241 -2
- package/docs/memory.md +72 -0
- package/docs/stt.md +494 -0
- package/docs/tts.md +569 -0
- package/docs/vision.md +396 -0
- package/package.json +21 -22
- package/dist/auto-update-BbNHbSU1.mjs +0 -3
- package/dist/browser/index.d.mts +0 -262
- package/dist/browser/index.d.mts.map +0 -1
- package/dist/browser/index.mjs +0 -755
- package/dist/browser/index.mjs.map +0 -1
- package/dist/chrome-backend-C5Un08O4.mjs.map +0 -1
- package/dist/gerbil-BfnsFWRE.mjs +0 -644
- package/dist/gerbil-BfnsFWRE.mjs.map +0 -1
- package/dist/gerbil-BjW-z7Fq.mjs +0 -5
- package/dist/gerbil-DZ1k3ChC.d.mts +0 -138
- package/dist/gerbil-DZ1k3ChC.d.mts.map +0 -1
- package/dist/mcp-R8kRLIKb.mjs.map +0 -1
- package/dist/models-DKULvhOr.mjs +0 -136
- package/dist/models-DKULvhOr.mjs.map +0 -1
- package/dist/models-De2-_GmQ.d.mts +0 -22
- package/dist/models-De2-_GmQ.d.mts.map +0 -1
- package/dist/skills-D3CEpgDc.mjs +0 -630
- package/dist/skills-D3CEpgDc.mjs.map +0 -1
- package/dist/types-BS1N92Jt.d.mts +0 -183
- package/dist/types-BS1N92Jt.d.mts.map +0 -1
- /package/dist/{chunk-Ct1HF2bE.mjs → chunk-CkXuGtQK.mjs} +0 -0
|
@@ -0,0 +1,1435 @@
|
|
|
1
|
+
import { a as getInstance } from "./one-liner-DxnNs_JK.mjs";
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { unlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { execSync as execSync$1 } from "child_process";
|
|
9
|
+
import { existsSync as existsSync$1, readFileSync as readFileSync$1, unlinkSync as unlinkSync$1, writeFileSync as writeFileSync$1 } from "fs";
|
|
10
|
+
import { tmpdir as tmpdir$1 } from "os";
|
|
11
|
+
import { join as join$1, resolve } from "path";
|
|
12
|
+
|
|
13
|
+
//#region src/skills/registry.ts
|
|
14
|
+
const registry = /* @__PURE__ */ new Map();
|
|
15
|
+
const skillSources = /* @__PURE__ */ new Map();
|
|
16
|
+
const RESERVED_NAMES = new Set([
|
|
17
|
+
"repl",
|
|
18
|
+
"chat",
|
|
19
|
+
"skills",
|
|
20
|
+
"tools",
|
|
21
|
+
"model",
|
|
22
|
+
"integrate",
|
|
23
|
+
"benchmark",
|
|
24
|
+
"info",
|
|
25
|
+
"serve",
|
|
26
|
+
"cache",
|
|
27
|
+
"generate",
|
|
28
|
+
"models",
|
|
29
|
+
"bench",
|
|
30
|
+
"r",
|
|
31
|
+
"c",
|
|
32
|
+
"g",
|
|
33
|
+
"help",
|
|
34
|
+
"version",
|
|
35
|
+
"gerbil"
|
|
36
|
+
]);
|
|
37
|
+
/**
|
|
38
|
+
* Define and register a skill
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* const sentiment = defineSkill({
|
|
43
|
+
* name: "sentiment",
|
|
44
|
+
* description: "Analyze sentiment of text",
|
|
45
|
+
* input: z.object({ text: z.string() }),
|
|
46
|
+
* output: z.object({ sentiment: z.enum(["positive", "negative", "neutral"]) }),
|
|
47
|
+
* async run({ input, gerbil }) {
|
|
48
|
+
* return gerbil.json(`Analyze sentiment: ${input.text}`, { schema: this.output });
|
|
49
|
+
* }
|
|
50
|
+
* });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
function defineSkill(definition) {
|
|
54
|
+
if (!/^[a-z][a-z0-9-]*$/.test(definition.name)) throw new Error(`Skill name must be kebab-case starting with a letter: ${definition.name}`);
|
|
55
|
+
if (RESERVED_NAMES.has(definition.name)) throw new Error(`Skill name "${definition.name}" is reserved for CLI commands. Choose a different name.`);
|
|
56
|
+
const execute = async (input) => skill.run(input);
|
|
57
|
+
const skill = Object.assign(execute, {
|
|
58
|
+
definition,
|
|
59
|
+
async run(input, gerbil) {
|
|
60
|
+
const g = gerbil ?? await getInstance(definition.model);
|
|
61
|
+
let validatedInput = input;
|
|
62
|
+
if (definition.input) {
|
|
63
|
+
const parsed = definition.input.safeParse(input);
|
|
64
|
+
if (!parsed.success) throw new Error(`Invalid input for skill "${definition.name}": ${parsed.error.message}`);
|
|
65
|
+
validatedInput = parsed.data;
|
|
66
|
+
}
|
|
67
|
+
const ctx = {
|
|
68
|
+
input: validatedInput,
|
|
69
|
+
gerbil: g,
|
|
70
|
+
rawInput: input,
|
|
71
|
+
definition
|
|
72
|
+
};
|
|
73
|
+
const result = await definition.run.call(definition, ctx);
|
|
74
|
+
if (definition.output && typeof result !== "string") {
|
|
75
|
+
const parsed = definition.output.safeParse(result);
|
|
76
|
+
if (!parsed.success) throw new Error(`Invalid output from skill "${definition.name}": ${parsed.error.message}`);
|
|
77
|
+
return parsed.data;
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
registry.set(definition.name, skill);
|
|
83
|
+
return skill;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get a skill by name
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const sentiment = useSkill("sentiment");
|
|
91
|
+
* const result = await sentiment({ text: "I love this!" });
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
function useSkill(name) {
|
|
95
|
+
const skill = registry.get(name);
|
|
96
|
+
if (!skill) throw new Error(`Skill not found: "${name}". Available: ${listSkills().join(", ") || "none"}`);
|
|
97
|
+
return skill;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* List all registered skill names
|
|
101
|
+
*/
|
|
102
|
+
function listSkills() {
|
|
103
|
+
return Array.from(registry.keys()).sort();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get skill metadata
|
|
107
|
+
*/
|
|
108
|
+
function getSkillInfo(name) {
|
|
109
|
+
const skill = registry.get(name);
|
|
110
|
+
if (!skill) return;
|
|
111
|
+
return {
|
|
112
|
+
name: skill.definition.name,
|
|
113
|
+
description: skill.definition.description,
|
|
114
|
+
version: skill.definition.version,
|
|
115
|
+
author: skill.definition.author,
|
|
116
|
+
builtin: !skillSources.has(name),
|
|
117
|
+
source: skillSources.get(name)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Check if a skill exists
|
|
122
|
+
*/
|
|
123
|
+
function hasSkill(name) {
|
|
124
|
+
return registry.has(name);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Remove a skill from registry
|
|
128
|
+
*/
|
|
129
|
+
function removeSkill(name) {
|
|
130
|
+
skillSources.delete(name);
|
|
131
|
+
return registry.delete(name);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Clear all skills from registry
|
|
135
|
+
*/
|
|
136
|
+
function clearSkills() {
|
|
137
|
+
registry.clear();
|
|
138
|
+
skillSources.clear();
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get all skill info
|
|
142
|
+
*/
|
|
143
|
+
function getAllSkillInfo() {
|
|
144
|
+
return listSkills().map((name) => getSkillInfo(name));
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Register a skill with its source file (used by loader)
|
|
148
|
+
* @internal
|
|
149
|
+
*/
|
|
150
|
+
function registerSkillWithSource(skill, source) {
|
|
151
|
+
registry.set(skill.definition.name, skill);
|
|
152
|
+
skillSources.set(skill.definition.name, source);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/skills/loader.ts
|
|
157
|
+
/**
|
|
158
|
+
* Skill Loader
|
|
159
|
+
*
|
|
160
|
+
* Load skills from files, directories, and packages.
|
|
161
|
+
*/
|
|
162
|
+
/**
|
|
163
|
+
* Load skills from the project's .gerbil/skills/ directory
|
|
164
|
+
*
|
|
165
|
+
* This is the standard location for project-specific skills.
|
|
166
|
+
* Creates the directory structure if it doesn't exist.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```ts
|
|
170
|
+
* // Load from .gerbil/skills/ in current working directory
|
|
171
|
+
* const loaded = await loadProjectSkills();
|
|
172
|
+
* console.log(`Loaded ${loaded.length} project skills`);
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
async function loadProjectSkills(cwd = process.cwd()) {
|
|
176
|
+
const path$1 = await import("node:path");
|
|
177
|
+
const fs$1 = await import("node:fs");
|
|
178
|
+
const gerbilDir = path$1.join(cwd, ".gerbil");
|
|
179
|
+
const skillsDir = path$1.join(gerbilDir, "skills");
|
|
180
|
+
if (!fs$1.existsSync(skillsDir)) return [];
|
|
181
|
+
return loadSkills(skillsDir);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Load all skills from a directory
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```ts
|
|
188
|
+
* // Load all *.skill.ts and *.skill.js files
|
|
189
|
+
* const loaded = await loadSkills("./skills");
|
|
190
|
+
* console.log(`Loaded ${loaded.length} skills`);
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
async function loadSkills(dir, options = {}) {
|
|
194
|
+
const { patterns = ["*.skill.ts", "*.skill.js"] } = options;
|
|
195
|
+
const loaded = [];
|
|
196
|
+
try {
|
|
197
|
+
const fs$1 = await import("node:fs");
|
|
198
|
+
const path$1 = await import("node:path");
|
|
199
|
+
const resolvedDir = path$1.resolve(dir);
|
|
200
|
+
if (!fs$1.existsSync(resolvedDir)) throw new Error(`Skills directory not found: ${dir}`);
|
|
201
|
+
const files = fs$1.readdirSync(resolvedDir);
|
|
202
|
+
for (const file of files) if (patterns.some((pattern) => {
|
|
203
|
+
return (/* @__PURE__ */ new RegExp(`^${pattern.replace(/\*/g, ".*").replace(/\./g, "\\.")}$`)).test(file);
|
|
204
|
+
})) {
|
|
205
|
+
const skill = await loadSkill(path$1.join(resolvedDir, file));
|
|
206
|
+
if (skill) loaded.push(skill.definition.name);
|
|
207
|
+
}
|
|
208
|
+
} catch (error) {
|
|
209
|
+
if (error.code === "MODULE_NOT_FOUND") throw new Error(`Skills directory not found: ${dir}`);
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
return loaded;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Load a single skill from a file
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```ts
|
|
219
|
+
* const skill = await loadSkill("./skills/sentiment.skill.ts");
|
|
220
|
+
* if (skill) {
|
|
221
|
+
* const result = await skill({ text: "Hello" });
|
|
222
|
+
* }
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
async function loadSkill(filePath) {
|
|
226
|
+
try {
|
|
227
|
+
const resolvedPath = (await import("node:path")).resolve(filePath);
|
|
228
|
+
const skill = (await import(pathToFileURL(resolvedPath).href)).default;
|
|
229
|
+
if (!skill?.definition) return null;
|
|
230
|
+
registerSkillWithSource(skill, resolvedPath);
|
|
231
|
+
return skill;
|
|
232
|
+
} catch (_error) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Load skills from an npm package
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```ts
|
|
241
|
+
* // Load skills from a package
|
|
242
|
+
* const loaded = await loadSkillPackage("gerbil-skills-extra");
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
async function loadSkillPackage(packageName) {
|
|
246
|
+
try {
|
|
247
|
+
const module = await import(packageName);
|
|
248
|
+
const loaded = [];
|
|
249
|
+
for (const [_key, value] of Object.entries(module)) if (isSkill(value)) {
|
|
250
|
+
const skill = value;
|
|
251
|
+
registerSkillWithSource(skill, `npm:${packageName}`);
|
|
252
|
+
loaded.push(skill.definition.name);
|
|
253
|
+
}
|
|
254
|
+
if (module.default && typeof module.default === "object") {
|
|
255
|
+
for (const [_key, value] of Object.entries(module.default)) if (isSkill(value)) {
|
|
256
|
+
const skill = value;
|
|
257
|
+
registerSkillWithSource(skill, `npm:${packageName}`);
|
|
258
|
+
loaded.push(skill.definition.name);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return loaded;
|
|
262
|
+
} catch (error) {
|
|
263
|
+
throw new Error(`Failed to load skill package "${packageName}": ${error}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Check if a value is a Skill
|
|
268
|
+
*/
|
|
269
|
+
function isSkill(value) {
|
|
270
|
+
return typeof value === "function" && typeof value.definition === "object" && typeof value.definition.name === "string" && typeof value.definition.run === "function";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/skills/builtin/analyze-screenshot.ts
|
|
275
|
+
/**
|
|
276
|
+
* Analyze Screenshot Skill
|
|
277
|
+
*
|
|
278
|
+
* Analyze a UI screenshot for design, accessibility, or QA purposes.
|
|
279
|
+
*/
|
|
280
|
+
const AnalyzeScreenshotInput = z.object({
|
|
281
|
+
image: z.string(),
|
|
282
|
+
type: z.enum([
|
|
283
|
+
"ui-review",
|
|
284
|
+
"accessibility",
|
|
285
|
+
"suggestions",
|
|
286
|
+
"qa",
|
|
287
|
+
"ux-audit",
|
|
288
|
+
"mobile-check"
|
|
289
|
+
]).default("ui-review"),
|
|
290
|
+
focus: z.array(z.string()).optional()
|
|
291
|
+
});
|
|
292
|
+
const analysisPrompts = {
|
|
293
|
+
"ui-review": `Review this UI screenshot as a design expert. Analyze:
|
|
294
|
+
- Visual hierarchy and layout
|
|
295
|
+
- Typography and readability
|
|
296
|
+
- Color scheme and contrast
|
|
297
|
+
- Spacing and alignment
|
|
298
|
+
- Component consistency
|
|
299
|
+
- Overall design quality
|
|
300
|
+
|
|
301
|
+
Provide specific observations and suggestions.`,
|
|
302
|
+
accessibility: `Analyze this screenshot for accessibility issues. Check for:
|
|
303
|
+
- Color contrast ratios
|
|
304
|
+
- Text size and readability
|
|
305
|
+
- Touch target sizes
|
|
306
|
+
- Visual affordances for interactive elements
|
|
307
|
+
- Potential issues for users with visual impairments
|
|
308
|
+
- Missing alt text indicators
|
|
309
|
+
|
|
310
|
+
List specific issues found and how to fix them.`,
|
|
311
|
+
suggestions: `As a senior UX designer, review this UI and suggest improvements:
|
|
312
|
+
- Modern design patterns that could be applied
|
|
313
|
+
- Usability improvements
|
|
314
|
+
- Visual polish opportunities
|
|
315
|
+
- User experience enhancements
|
|
316
|
+
- Ways to make it more engaging
|
|
317
|
+
|
|
318
|
+
Be specific and actionable.`,
|
|
319
|
+
qa: `Perform QA analysis on this screenshot. Look for:
|
|
320
|
+
- Visual bugs or glitches
|
|
321
|
+
- Alignment and spacing issues
|
|
322
|
+
- Broken or missing elements
|
|
323
|
+
- Inconsistencies with design standards
|
|
324
|
+
- Text overflow or truncation issues
|
|
325
|
+
- Responsive design problems
|
|
326
|
+
|
|
327
|
+
Report each issue with its location and severity.`,
|
|
328
|
+
"ux-audit": `Conduct a UX audit of this interface:
|
|
329
|
+
- User flow clarity
|
|
330
|
+
- Call-to-action visibility
|
|
331
|
+
- Information architecture
|
|
332
|
+
- Cognitive load assessment
|
|
333
|
+
- Error prevention
|
|
334
|
+
- User feedback mechanisms
|
|
335
|
+
|
|
336
|
+
Provide a structured audit report.`,
|
|
337
|
+
"mobile-check": `Evaluate this UI for mobile usability:
|
|
338
|
+
- Touch target sizes (minimum 44x44px)
|
|
339
|
+
- Thumb-zone accessibility
|
|
340
|
+
- Content priority on small screens
|
|
341
|
+
- Gesture affordances
|
|
342
|
+
- Loading indicators
|
|
343
|
+
- Mobile-specific patterns
|
|
344
|
+
|
|
345
|
+
Identify mobile-specific issues and recommendations.`
|
|
346
|
+
};
|
|
347
|
+
const analyzeScreenshot = defineSkill({
|
|
348
|
+
name: "analyze-screenshot",
|
|
349
|
+
description: "Analyze a UI screenshot for design, accessibility, or QA",
|
|
350
|
+
version: "1.0.0",
|
|
351
|
+
model: "ministral-3b",
|
|
352
|
+
input: AnalyzeScreenshotInput,
|
|
353
|
+
maxTokens: 1200,
|
|
354
|
+
temperature: .3,
|
|
355
|
+
async run({ input, gerbil }) {
|
|
356
|
+
const { image, type = "ui-review", focus } = input;
|
|
357
|
+
if (!gerbil.supportsVision()) throw new Error(`Current model doesn't support vision. Load a vision model like "ministral-3b" first.`);
|
|
358
|
+
let prompt = analysisPrompts[type];
|
|
359
|
+
if (focus && focus.length > 0) prompt += `\n\nPay special attention to: ${focus.join(", ")}.`;
|
|
360
|
+
return (await gerbil.generate(prompt, {
|
|
361
|
+
images: [{ source: image }],
|
|
362
|
+
maxTokens: this.maxTokens,
|
|
363
|
+
temperature: this.temperature
|
|
364
|
+
})).text;
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
//#endregion
|
|
369
|
+
//#region src/skills/builtin/announce.ts
|
|
370
|
+
/**
|
|
371
|
+
* Announce Skill
|
|
372
|
+
*
|
|
373
|
+
* Generate and speak an announcement using AI.
|
|
374
|
+
* Useful for scripts that need to give voice updates.
|
|
375
|
+
*/
|
|
376
|
+
const AnnounceInput = z.object({
|
|
377
|
+
message: z.string(),
|
|
378
|
+
style: z.enum([
|
|
379
|
+
"casual",
|
|
380
|
+
"formal",
|
|
381
|
+
"excited",
|
|
382
|
+
"calm",
|
|
383
|
+
"urgent"
|
|
384
|
+
]).default("casual"),
|
|
385
|
+
voice: z.enum([
|
|
386
|
+
"af_heart",
|
|
387
|
+
"af_bella",
|
|
388
|
+
"af_nicole",
|
|
389
|
+
"am_fenrir",
|
|
390
|
+
"am_michael",
|
|
391
|
+
"bf_emma",
|
|
392
|
+
"bm_george"
|
|
393
|
+
]).default("af_heart"),
|
|
394
|
+
speed: z.number().min(.5).max(2).default(1),
|
|
395
|
+
textOnly: z.boolean().default(false)
|
|
396
|
+
});
|
|
397
|
+
const stylePrompts = {
|
|
398
|
+
casual: "Rephrase this as a casual, friendly announcement. Keep it natural and conversational.",
|
|
399
|
+
formal: "Rephrase this as a professional, formal announcement. Be clear and dignified.",
|
|
400
|
+
excited: "Rephrase this as an excited, enthusiastic announcement! Add energy and positivity.",
|
|
401
|
+
calm: "Rephrase this as a calm, soothing announcement. Be gentle and reassuring.",
|
|
402
|
+
urgent: "Rephrase this as an urgent announcement. Be direct and emphasize importance."
|
|
403
|
+
};
|
|
404
|
+
function saveWav$3(filename, audio, sampleRate) {
|
|
405
|
+
const buffer = Buffer.alloc(44 + audio.length * 2);
|
|
406
|
+
buffer.write("RIFF", 0);
|
|
407
|
+
buffer.writeUInt32LE(36 + audio.length * 2, 4);
|
|
408
|
+
buffer.write("WAVE", 8);
|
|
409
|
+
buffer.write("fmt ", 12);
|
|
410
|
+
buffer.writeUInt32LE(16, 16);
|
|
411
|
+
buffer.writeUInt16LE(1, 20);
|
|
412
|
+
buffer.writeUInt16LE(1, 22);
|
|
413
|
+
buffer.writeUInt32LE(sampleRate, 24);
|
|
414
|
+
buffer.writeUInt32LE(sampleRate * 2, 28);
|
|
415
|
+
buffer.writeUInt16LE(2, 32);
|
|
416
|
+
buffer.writeUInt16LE(16, 34);
|
|
417
|
+
buffer.write("data", 36);
|
|
418
|
+
buffer.writeUInt32LE(audio.length * 2, 40);
|
|
419
|
+
for (let i = 0; i < audio.length; i++) {
|
|
420
|
+
const s$1 = Math.max(-1, Math.min(1, audio[i]));
|
|
421
|
+
buffer.writeInt16LE(Math.round(s$1 * 32767), 44 + i * 2);
|
|
422
|
+
}
|
|
423
|
+
writeFileSync$1(filename, buffer);
|
|
424
|
+
}
|
|
425
|
+
const announce = defineSkill({
|
|
426
|
+
name: "announce",
|
|
427
|
+
description: "Generate and speak an AI-crafted announcement",
|
|
428
|
+
version: "1.0.0",
|
|
429
|
+
input: AnnounceInput,
|
|
430
|
+
temperature: .7,
|
|
431
|
+
maxTokens: 150,
|
|
432
|
+
async run({ input, gerbil }) {
|
|
433
|
+
const { message, style = "casual", voice = "af_heart", speed = 1, textOnly = false } = input;
|
|
434
|
+
const announcementText = (await gerbil.generate(message, {
|
|
435
|
+
system: `${stylePrompts[style]}
|
|
436
|
+
Keep it brief (1-2 sentences max).
|
|
437
|
+
Output only the announcement text, nothing else.`,
|
|
438
|
+
temperature: this.temperature,
|
|
439
|
+
maxTokens: this.maxTokens
|
|
440
|
+
})).text.trim();
|
|
441
|
+
if (textOnly) return announcementText;
|
|
442
|
+
const speechResult = await gerbil.speak(announcementText, {
|
|
443
|
+
voice,
|
|
444
|
+
speed
|
|
445
|
+
});
|
|
446
|
+
const tempFile = join$1(tmpdir$1(), `gerbil-announce-${Date.now()}.wav`);
|
|
447
|
+
saveWav$3(tempFile, speechResult.audio, speechResult.sampleRate);
|
|
448
|
+
try {
|
|
449
|
+
const platform = process.platform;
|
|
450
|
+
if (platform === "darwin") execSync$1(`afplay "${tempFile}"`, { stdio: "inherit" });
|
|
451
|
+
else if (platform === "linux") try {
|
|
452
|
+
execSync$1(`aplay "${tempFile}"`, { stdio: "inherit" });
|
|
453
|
+
} catch {
|
|
454
|
+
execSync$1(`paplay "${tempFile}"`, { stdio: "inherit" });
|
|
455
|
+
}
|
|
456
|
+
else if (platform === "win32") execSync$1(`powershell -c "(New-Object Media.SoundPlayer '${tempFile}').PlaySync()"`, { stdio: "inherit" });
|
|
457
|
+
} finally {
|
|
458
|
+
try {
|
|
459
|
+
unlinkSync$1(tempFile);
|
|
460
|
+
} catch {}
|
|
461
|
+
}
|
|
462
|
+
return announcementText;
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
//#endregion
|
|
467
|
+
//#region src/skills/builtin/caption-image.ts
|
|
468
|
+
/**
|
|
469
|
+
* Caption Image Skill
|
|
470
|
+
*
|
|
471
|
+
* Generate captions, alt text, or social media descriptions for images.
|
|
472
|
+
*/
|
|
473
|
+
const CaptionImageInput = z.object({
|
|
474
|
+
image: z.string(),
|
|
475
|
+
type: z.enum([
|
|
476
|
+
"alt-text",
|
|
477
|
+
"caption",
|
|
478
|
+
"social-media",
|
|
479
|
+
"seo",
|
|
480
|
+
"artistic",
|
|
481
|
+
"technical"
|
|
482
|
+
]).default("caption"),
|
|
483
|
+
tone: z.enum([
|
|
484
|
+
"professional",
|
|
485
|
+
"casual",
|
|
486
|
+
"playful",
|
|
487
|
+
"formal",
|
|
488
|
+
"descriptive"
|
|
489
|
+
]).default("professional"),
|
|
490
|
+
maxLength: z.enum([
|
|
491
|
+
"short",
|
|
492
|
+
"medium",
|
|
493
|
+
"long"
|
|
494
|
+
]).default("medium"),
|
|
495
|
+
platform: z.enum([
|
|
496
|
+
"twitter",
|
|
497
|
+
"instagram",
|
|
498
|
+
"linkedin",
|
|
499
|
+
"facebook"
|
|
500
|
+
]).optional()
|
|
501
|
+
});
|
|
502
|
+
const captionPrompts = {
|
|
503
|
+
"alt-text": `Generate accessible alt text for this image.
|
|
504
|
+
- Describe the essential content concisely
|
|
505
|
+
- Include relevant details for screen reader users
|
|
506
|
+
- Avoid starting with "Image of" or "Picture of"
|
|
507
|
+
- Be factual and objective`,
|
|
508
|
+
caption: `Write a caption for this image.
|
|
509
|
+
- Capture the essence of the image
|
|
510
|
+
- Make it engaging and informative
|
|
511
|
+
- Consider the context and mood`,
|
|
512
|
+
"social-media": `Create a social media caption for this image.
|
|
513
|
+
- Make it engaging and shareable
|
|
514
|
+
- Include relevant emoji where appropriate
|
|
515
|
+
- Suggest hashtags if applicable
|
|
516
|
+
- Optimize for engagement`,
|
|
517
|
+
seo: `Write SEO-optimized alt text and description for this image.
|
|
518
|
+
- Include relevant keywords naturally
|
|
519
|
+
- Be descriptive but concise
|
|
520
|
+
- Focus on searchable terms
|
|
521
|
+
- Maintain readability`,
|
|
522
|
+
artistic: `Write an artistic, evocative caption for this image.
|
|
523
|
+
- Capture the mood and emotion
|
|
524
|
+
- Use creative language
|
|
525
|
+
- Tell a micro-story if appropriate
|
|
526
|
+
- Be memorable and unique`,
|
|
527
|
+
technical: `Write a technical description of this image.
|
|
528
|
+
- Describe components and elements precisely
|
|
529
|
+
- Use appropriate technical terminology
|
|
530
|
+
- Note measurements or specifications if visible
|
|
531
|
+
- Be objective and detailed`
|
|
532
|
+
};
|
|
533
|
+
const lengthGuides = {
|
|
534
|
+
short: 100,
|
|
535
|
+
medium: 200,
|
|
536
|
+
long: 400
|
|
537
|
+
};
|
|
538
|
+
const platformGuides = {
|
|
539
|
+
twitter: "Keep under 280 characters. Make it punchy and tweetable.",
|
|
540
|
+
instagram: "Include relevant hashtags. Can be longer and more storytelling.",
|
|
541
|
+
linkedin: "Keep it professional. Focus on value and insights.",
|
|
542
|
+
facebook: "Can be conversational. Ask questions to encourage engagement."
|
|
543
|
+
};
|
|
544
|
+
const captionImage = defineSkill({
|
|
545
|
+
name: "caption-image",
|
|
546
|
+
description: "Generate captions, alt text, or social media descriptions for images",
|
|
547
|
+
version: "1.0.0",
|
|
548
|
+
model: "ministral-3b",
|
|
549
|
+
input: CaptionImageInput,
|
|
550
|
+
temperature: .7,
|
|
551
|
+
async run({ input, gerbil }) {
|
|
552
|
+
const { image, type = "caption", tone = "professional", maxLength = "medium", platform } = input;
|
|
553
|
+
if (!gerbil.supportsVision()) throw new Error(`Current model doesn't support vision. Load a vision model like "ministral-3b" first.`);
|
|
554
|
+
let prompt = captionPrompts[type];
|
|
555
|
+
prompt += `\n\nTone: ${tone}`;
|
|
556
|
+
if (platform && type === "social-media") prompt += `\n\nPlatform: ${platform}. ${platformGuides[platform]}`;
|
|
557
|
+
return (await gerbil.generate(prompt, {
|
|
558
|
+
images: [{ source: image }],
|
|
559
|
+
maxTokens: lengthGuides[maxLength],
|
|
560
|
+
temperature: this.temperature
|
|
561
|
+
})).text;
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
//#endregion
|
|
566
|
+
//#region src/skills/builtin/commit.ts
|
|
567
|
+
/**
|
|
568
|
+
* Commit Skill
|
|
569
|
+
*
|
|
570
|
+
* Generate git commit messages from staged changes.
|
|
571
|
+
*/
|
|
572
|
+
const CommitInput = z.object({
|
|
573
|
+
diff: z.string().optional(),
|
|
574
|
+
type: z.enum([
|
|
575
|
+
"conventional",
|
|
576
|
+
"simple",
|
|
577
|
+
"detailed"
|
|
578
|
+
]).default("conventional"),
|
|
579
|
+
maxLength: z.number().default(72)
|
|
580
|
+
});
|
|
581
|
+
async function getGitDiff() {
|
|
582
|
+
try {
|
|
583
|
+
const { execSync: execSync$2 } = await import("node:child_process");
|
|
584
|
+
return execSync$2("git diff --staged", { encoding: "utf-8" });
|
|
585
|
+
} catch {
|
|
586
|
+
return "";
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function getSystemPrompt(type) {
|
|
590
|
+
switch (type) {
|
|
591
|
+
case "conventional": return `Generate a git commit message following the Conventional Commits format.
|
|
592
|
+
Use one of these types: feat, fix, docs, style, refactor, perf, test, chore.
|
|
593
|
+
Format: type(scope): description
|
|
594
|
+
Keep it under 72 characters.
|
|
595
|
+
No period at the end.
|
|
596
|
+
Only output the commit message, nothing else.`;
|
|
597
|
+
case "detailed": return `Generate a detailed git commit message with a subject line and body.
|
|
598
|
+
Subject: under 72 chars, imperative mood
|
|
599
|
+
Body: explain what and why
|
|
600
|
+
Only output the commit message, nothing else.`;
|
|
601
|
+
default: return `Generate a concise git commit message.
|
|
602
|
+
Use imperative mood (e.g., "Add", "Fix", "Update").
|
|
603
|
+
Keep it under 72 characters.
|
|
604
|
+
No period at the end.
|
|
605
|
+
Only output the commit message, nothing else.`;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
const commit = defineSkill({
|
|
609
|
+
name: "commit",
|
|
610
|
+
description: "Generate a git commit message from staged changes",
|
|
611
|
+
version: "1.0.0",
|
|
612
|
+
input: CommitInput,
|
|
613
|
+
temperature: .3,
|
|
614
|
+
maxTokens: 100,
|
|
615
|
+
async run({ input, gerbil }) {
|
|
616
|
+
const { diff, type = "conventional", maxLength = 72 } = input;
|
|
617
|
+
const actualDiff = diff || await getGitDiff();
|
|
618
|
+
if (!actualDiff || actualDiff.trim().length === 0) throw new Error("No changes staged. Run `git add` first.");
|
|
619
|
+
let message = (await gerbil.generate(`Generate a commit message for the following diff:\n\n${actualDiff}`, {
|
|
620
|
+
system: getSystemPrompt(type),
|
|
621
|
+
maxTokens: this.maxTokens,
|
|
622
|
+
temperature: this.temperature
|
|
623
|
+
})).text.trim();
|
|
624
|
+
message = message.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
|
|
625
|
+
message = message.replace(/<\/?think>/g, "").trim();
|
|
626
|
+
message = message.replace(/^["']|["']$/g, "");
|
|
627
|
+
message = message.split("\n")[0];
|
|
628
|
+
if (message.length > maxLength) message = `${message.substring(0, maxLength - 3)}...`;
|
|
629
|
+
return message;
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
//#endregion
|
|
634
|
+
//#region src/skills/builtin/compare-images.ts
|
|
635
|
+
/**
|
|
636
|
+
* Compare Images Skill
|
|
637
|
+
*
|
|
638
|
+
* Compare two images and describe their differences.
|
|
639
|
+
*/
|
|
640
|
+
const CompareImagesInput = z.object({
|
|
641
|
+
image1: z.string(),
|
|
642
|
+
image2: z.string(),
|
|
643
|
+
type: z.enum([
|
|
644
|
+
"visual-diff",
|
|
645
|
+
"design-comparison",
|
|
646
|
+
"before-after",
|
|
647
|
+
"a-b-test",
|
|
648
|
+
"version-diff"
|
|
649
|
+
]).default("visual-diff"),
|
|
650
|
+
focus: z.array(z.string()).optional()
|
|
651
|
+
});
|
|
652
|
+
const comparisonPrompts = {
|
|
653
|
+
"visual-diff": `Compare these two images and identify all visual differences.
|
|
654
|
+
- List specific elements that differ
|
|
655
|
+
- Note position, color, size, and content changes
|
|
656
|
+
- Identify additions and removals
|
|
657
|
+
- Rate the significance of each change`,
|
|
658
|
+
"design-comparison": `Compare these two designs as a design expert.
|
|
659
|
+
- Analyze layout differences
|
|
660
|
+
- Compare typography and colors
|
|
661
|
+
- Evaluate which design is more effective
|
|
662
|
+
- Suggest which elements work better in each`,
|
|
663
|
+
"before-after": `Analyze these before and after images.
|
|
664
|
+
- Describe what changed
|
|
665
|
+
- Evaluate if the changes are improvements
|
|
666
|
+
- Note any unintended consequences
|
|
667
|
+
- Summarize the overall transformation`,
|
|
668
|
+
"a-b-test": `Analyze these two variants for A/B testing purposes.
|
|
669
|
+
- Identify the key differences being tested
|
|
670
|
+
- Predict which might perform better and why
|
|
671
|
+
- Note potential user experience impacts
|
|
672
|
+
- Suggest what metrics to measure`,
|
|
673
|
+
"version-diff": `Compare these two versions of the UI.
|
|
674
|
+
- Document all changes between versions
|
|
675
|
+
- Categorize changes (bug fix, feature, style)
|
|
676
|
+
- Identify breaking changes
|
|
677
|
+
- Note any regressions`
|
|
678
|
+
};
|
|
679
|
+
const compareImages = defineSkill({
|
|
680
|
+
name: "compare-images",
|
|
681
|
+
description: "Compare two images and describe their differences",
|
|
682
|
+
version: "1.0.0",
|
|
683
|
+
model: "ministral-3b",
|
|
684
|
+
input: CompareImagesInput,
|
|
685
|
+
maxTokens: 1500,
|
|
686
|
+
temperature: .3,
|
|
687
|
+
async run({ input, gerbil }) {
|
|
688
|
+
const { image1, image2, type = "visual-diff", focus } = input;
|
|
689
|
+
if (!gerbil.supportsVision()) throw new Error(`Current model doesn't support vision. Load a vision model like "ministral-3b" first.`);
|
|
690
|
+
let prompt = `I'm showing you two images for comparison.\n\n${comparisonPrompts[type]}`;
|
|
691
|
+
if (focus && focus.length > 0) prompt += `\n\nFocus specifically on: ${focus.join(", ")}.`;
|
|
692
|
+
prompt += "\n\nThe first image is shown first, followed by the second image.";
|
|
693
|
+
return (await gerbil.generate(prompt, {
|
|
694
|
+
images: [{ source: image1 }, { source: image2 }],
|
|
695
|
+
maxTokens: this.maxTokens,
|
|
696
|
+
temperature: this.temperature
|
|
697
|
+
})).text;
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
//#endregion
|
|
702
|
+
//#region src/skills/builtin/describe-image.ts
|
|
703
|
+
/**
|
|
704
|
+
* Describe Image Skill
|
|
705
|
+
*
|
|
706
|
+
* Describe an image using vision AI.
|
|
707
|
+
*/
|
|
708
|
+
const DescribeImageInput = z.object({
|
|
709
|
+
image: z.string(),
|
|
710
|
+
focus: z.enum([
|
|
711
|
+
"general",
|
|
712
|
+
"details",
|
|
713
|
+
"text",
|
|
714
|
+
"objects",
|
|
715
|
+
"scene",
|
|
716
|
+
"colors"
|
|
717
|
+
]).default("general"),
|
|
718
|
+
format: z.enum([
|
|
719
|
+
"paragraph",
|
|
720
|
+
"bullets",
|
|
721
|
+
"structured"
|
|
722
|
+
]).default("paragraph"),
|
|
723
|
+
detail: z.enum([
|
|
724
|
+
"brief",
|
|
725
|
+
"normal",
|
|
726
|
+
"comprehensive"
|
|
727
|
+
]).default("normal")
|
|
728
|
+
});
|
|
729
|
+
const focusPrompts = {
|
|
730
|
+
general: "Describe this image comprehensively, covering the main subject, context, and notable elements.",
|
|
731
|
+
details: "Describe this image in detail, including colors, textures, lighting, and small elements.",
|
|
732
|
+
text: "Extract and describe any text visible in this image. Include the text content and its context.",
|
|
733
|
+
objects: "List and describe all objects you can identify in this image, including their positions.",
|
|
734
|
+
scene: "Describe the scene, setting, and atmosphere of this image. What story does it tell?",
|
|
735
|
+
colors: "Analyze the color palette and visual composition of this image."
|
|
736
|
+
};
|
|
737
|
+
const detailLengths = {
|
|
738
|
+
brief: 150,
|
|
739
|
+
normal: 400,
|
|
740
|
+
comprehensive: 800
|
|
741
|
+
};
|
|
742
|
+
const describeImage = defineSkill({
|
|
743
|
+
name: "describe-image",
|
|
744
|
+
description: "Describe an image using vision AI",
|
|
745
|
+
version: "1.0.0",
|
|
746
|
+
model: "ministral-3b",
|
|
747
|
+
input: DescribeImageInput,
|
|
748
|
+
temperature: .5,
|
|
749
|
+
async run({ input, gerbil }) {
|
|
750
|
+
const { image, focus = "general", format = "paragraph", detail = "normal" } = input;
|
|
751
|
+
if (!gerbil.supportsVision()) throw new Error(`Current model doesn't support vision. Load a vision model like "ministral-3b" first.`);
|
|
752
|
+
const prompt = `${focusPrompts[focus]}\n\n${{
|
|
753
|
+
paragraph: "Write your description as natural flowing paragraphs.",
|
|
754
|
+
bullets: "Format your description as clear bullet points.",
|
|
755
|
+
structured: "Structure your description with sections: Overview, Main Elements, Details, Observations."
|
|
756
|
+
}[format]}`;
|
|
757
|
+
return (await gerbil.generate(prompt, {
|
|
758
|
+
images: [{ source: image }],
|
|
759
|
+
maxTokens: detailLengths[detail],
|
|
760
|
+
temperature: this.temperature
|
|
761
|
+
})).text;
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
//#endregion
|
|
766
|
+
//#region src/skills/builtin/explain.ts
|
|
767
|
+
/**
|
|
768
|
+
* Explain Skill
|
|
769
|
+
*
|
|
770
|
+
* Explain code or concepts at various levels.
|
|
771
|
+
* Optionally speaks the explanation aloud.
|
|
772
|
+
*/
|
|
773
|
+
const ExplainInput = z.object({
|
|
774
|
+
content: z.string(),
|
|
775
|
+
level: z.enum([
|
|
776
|
+
"beginner",
|
|
777
|
+
"intermediate",
|
|
778
|
+
"expert"
|
|
779
|
+
]).default("intermediate"),
|
|
780
|
+
language: z.string().optional(),
|
|
781
|
+
speak: z.boolean().default(false),
|
|
782
|
+
voice: z.enum([
|
|
783
|
+
"af_heart",
|
|
784
|
+
"af_bella",
|
|
785
|
+
"bf_emma",
|
|
786
|
+
"am_fenrir"
|
|
787
|
+
]).default("af_heart")
|
|
788
|
+
});
|
|
789
|
+
const levelGuide = {
|
|
790
|
+
beginner: "Explain like I'm new to programming. Use simple terms and analogies.",
|
|
791
|
+
intermediate: "Explain for someone with programming experience.",
|
|
792
|
+
expert: "Explain with technical depth, including implementation details."
|
|
793
|
+
};
|
|
794
|
+
function saveWav$2(filename, audio, sampleRate) {
|
|
795
|
+
const buffer = Buffer.alloc(44 + audio.length * 2);
|
|
796
|
+
buffer.write("RIFF", 0);
|
|
797
|
+
buffer.writeUInt32LE(36 + audio.length * 2, 4);
|
|
798
|
+
buffer.write("WAVE", 8);
|
|
799
|
+
buffer.write("fmt ", 12);
|
|
800
|
+
buffer.writeUInt32LE(16, 16);
|
|
801
|
+
buffer.writeUInt16LE(1, 20);
|
|
802
|
+
buffer.writeUInt16LE(1, 22);
|
|
803
|
+
buffer.writeUInt32LE(sampleRate, 24);
|
|
804
|
+
buffer.writeUInt32LE(sampleRate * 2, 28);
|
|
805
|
+
buffer.writeUInt16LE(2, 32);
|
|
806
|
+
buffer.writeUInt16LE(16, 34);
|
|
807
|
+
buffer.write("data", 36);
|
|
808
|
+
buffer.writeUInt32LE(audio.length * 2, 40);
|
|
809
|
+
for (let i = 0; i < audio.length; i++) {
|
|
810
|
+
const s$1 = Math.max(-1, Math.min(1, audio[i]));
|
|
811
|
+
buffer.writeInt16LE(Math.round(s$1 * 32767), 44 + i * 2);
|
|
812
|
+
}
|
|
813
|
+
writeFileSync(filename, buffer);
|
|
814
|
+
}
|
|
815
|
+
function playAudio$1(audioFile) {
|
|
816
|
+
const platform = process.platform;
|
|
817
|
+
if (platform === "darwin") execSync(`afplay "${audioFile}"`, { stdio: "inherit" });
|
|
818
|
+
else if (platform === "linux") try {
|
|
819
|
+
execSync(`aplay "${audioFile}"`, { stdio: "inherit" });
|
|
820
|
+
} catch {
|
|
821
|
+
execSync(`paplay "${audioFile}"`, { stdio: "inherit" });
|
|
822
|
+
}
|
|
823
|
+
else if (platform === "win32") execSync(`powershell -c "(New-Object Media.SoundPlayer '${audioFile}').PlaySync()"`, { stdio: "inherit" });
|
|
824
|
+
}
|
|
825
|
+
const explain = defineSkill({
|
|
826
|
+
name: "explain",
|
|
827
|
+
description: "Explain code or concepts at various levels (optionally speak aloud)",
|
|
828
|
+
version: "1.0.0",
|
|
829
|
+
input: ExplainInput,
|
|
830
|
+
temperature: .5,
|
|
831
|
+
maxTokens: 500,
|
|
832
|
+
async run({ input, gerbil }) {
|
|
833
|
+
const { content, level = "intermediate", language, speak: speak$1 = false, voice = "af_heart" } = input;
|
|
834
|
+
let systemPrompt = `You are a patient teacher.
|
|
835
|
+
${levelGuide[level]}`;
|
|
836
|
+
if (language) systemPrompt += `\nThis is ${language} code.`;
|
|
837
|
+
systemPrompt += "\nBe clear and concise.";
|
|
838
|
+
const explanation = (await gerbil.generate(`Explain this:\n\n${content}`, {
|
|
839
|
+
system: systemPrompt,
|
|
840
|
+
maxTokens: this.maxTokens,
|
|
841
|
+
temperature: this.temperature
|
|
842
|
+
})).text;
|
|
843
|
+
if (speak$1) {
|
|
844
|
+
const sentences = explanation.split(/(?<=[.!?])\s+/).filter((s$1) => s$1.trim());
|
|
845
|
+
for (const sentence of sentences) {
|
|
846
|
+
if (!sentence.trim()) continue;
|
|
847
|
+
const speechResult = await gerbil.speak(sentence, { voice });
|
|
848
|
+
const tempFile = join(tmpdir(), `gerbil-explain-${Date.now()}.wav`);
|
|
849
|
+
saveWav$2(tempFile, speechResult.audio, speechResult.sampleRate);
|
|
850
|
+
try {
|
|
851
|
+
playAudio$1(tempFile);
|
|
852
|
+
} finally {
|
|
853
|
+
try {
|
|
854
|
+
unlinkSync(tempFile);
|
|
855
|
+
} catch {}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return explanation;
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
//#endregion
|
|
864
|
+
//#region src/skills/builtin/extract.ts
|
|
865
|
+
/**
|
|
866
|
+
* Extract Skill
|
|
867
|
+
*
|
|
868
|
+
* Extract structured data from content.
|
|
869
|
+
*/
|
|
870
|
+
const ExtractInput = z.object({
|
|
871
|
+
content: z.string(),
|
|
872
|
+
schema: z.any(),
|
|
873
|
+
context: z.string().optional()
|
|
874
|
+
});
|
|
875
|
+
const extract = defineSkill({
|
|
876
|
+
name: "extract",
|
|
877
|
+
description: "Extract structured data from content",
|
|
878
|
+
version: "1.0.0",
|
|
879
|
+
input: ExtractInput,
|
|
880
|
+
temperature: .3,
|
|
881
|
+
async run({ input, gerbil }) {
|
|
882
|
+
const { content, schema, context } = input;
|
|
883
|
+
let prompt = "Extract structured data from the following content.";
|
|
884
|
+
if (context) prompt += `\nContext: ${context}`;
|
|
885
|
+
prompt += `\n\nContent:\n${content}`;
|
|
886
|
+
return gerbil.json(prompt, { schema });
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
//#endregion
|
|
891
|
+
//#region src/skills/builtin/extract-from-image.ts
|
|
892
|
+
/**
|
|
893
|
+
* Extract from Image Skill
|
|
894
|
+
*
|
|
895
|
+
* Extract text, code, data, or structured information from images.
|
|
896
|
+
*/
|
|
897
|
+
const ExtractFromImageInput = z.object({
|
|
898
|
+
image: z.string(),
|
|
899
|
+
extract: z.enum([
|
|
900
|
+
"text",
|
|
901
|
+
"code",
|
|
902
|
+
"data",
|
|
903
|
+
"table",
|
|
904
|
+
"diagram",
|
|
905
|
+
"form",
|
|
906
|
+
"receipt"
|
|
907
|
+
]).default("text"),
|
|
908
|
+
outputFormat: z.enum([
|
|
909
|
+
"raw",
|
|
910
|
+
"json",
|
|
911
|
+
"markdown",
|
|
912
|
+
"csv"
|
|
913
|
+
]).default("raw"),
|
|
914
|
+
language: z.string().optional()
|
|
915
|
+
});
|
|
916
|
+
const extractionPrompts = {
|
|
917
|
+
text: `Extract all visible text from this image.
|
|
918
|
+
- Preserve the original formatting and structure as much as possible
|
|
919
|
+
- Include headings, paragraphs, and any labels
|
|
920
|
+
- Note any text that's unclear or partially visible
|
|
921
|
+
- Maintain the reading order`,
|
|
922
|
+
code: `Extract the code visible in this image.
|
|
923
|
+
- Preserve exact syntax, indentation, and formatting
|
|
924
|
+
- Include comments if visible
|
|
925
|
+
- Note any parts that are unclear
|
|
926
|
+
- Format as a proper code block`,
|
|
927
|
+
data: `Extract all data, numbers, and structured information from this image.
|
|
928
|
+
- Include labels and their associated values
|
|
929
|
+
- Preserve numerical precision
|
|
930
|
+
- Note units and currencies
|
|
931
|
+
- Identify any patterns or relationships`,
|
|
932
|
+
table: `Extract the table data from this image.
|
|
933
|
+
- Identify all columns and rows
|
|
934
|
+
- Preserve cell alignment where meaningful
|
|
935
|
+
- Handle merged cells appropriately
|
|
936
|
+
- Include headers and any totals`,
|
|
937
|
+
diagram: `Describe and extract information from this diagram or flowchart.
|
|
938
|
+
- List all nodes/boxes and their labels
|
|
939
|
+
- Describe connections and their directions
|
|
940
|
+
- Note any annotations or legends
|
|
941
|
+
- Explain the flow or relationships`,
|
|
942
|
+
form: `Extract form fields and their values from this image.
|
|
943
|
+
- List each field label and its value
|
|
944
|
+
- Note required fields if indicated
|
|
945
|
+
- Include dropdown selections
|
|
946
|
+
- Preserve the form structure`,
|
|
947
|
+
receipt: `Extract receipt/invoice information from this image.
|
|
948
|
+
- Vendor/store name
|
|
949
|
+
- Date and time
|
|
950
|
+
- Line items with quantities and prices
|
|
951
|
+
- Subtotals, taxes, and total
|
|
952
|
+
- Payment method if shown`
|
|
953
|
+
};
|
|
954
|
+
const formatInstructions = {
|
|
955
|
+
raw: "Output the extracted content as plain text.",
|
|
956
|
+
json: "Output the extracted content as valid JSON with appropriate structure.",
|
|
957
|
+
markdown: "Format the output as Markdown with proper headings and formatting.",
|
|
958
|
+
csv: "Output tabular data in CSV format with proper quoting."
|
|
959
|
+
};
|
|
960
|
+
const extractFromImage = defineSkill({
|
|
961
|
+
name: "extract-from-image",
|
|
962
|
+
description: "Extract text, code, tables, or data from images",
|
|
963
|
+
version: "1.0.0",
|
|
964
|
+
model: "ministral-3b",
|
|
965
|
+
input: ExtractFromImageInput,
|
|
966
|
+
maxTokens: 2e3,
|
|
967
|
+
temperature: .1,
|
|
968
|
+
async run({ input, gerbil }) {
|
|
969
|
+
const { image, extract: extract$1 = "text", outputFormat = "raw", language } = input;
|
|
970
|
+
if (!gerbil.supportsVision()) throw new Error(`Current model doesn't support vision. Load a vision model like "ministral-3b" first.`);
|
|
971
|
+
let prompt = extractionPrompts[extract$1];
|
|
972
|
+
if (language && extract$1 === "code") prompt += `\n\nThe code appears to be ${language}.`;
|
|
973
|
+
prompt += `\n\n${formatInstructions[outputFormat]}`;
|
|
974
|
+
return (await gerbil.generate(prompt, {
|
|
975
|
+
images: [{ source: image }],
|
|
976
|
+
maxTokens: this.maxTokens,
|
|
977
|
+
temperature: this.temperature
|
|
978
|
+
})).text;
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
//#endregion
|
|
983
|
+
//#region src/skills/builtin/read-aloud.ts
|
|
984
|
+
/**
|
|
985
|
+
* Read Aloud Skill
|
|
986
|
+
*
|
|
987
|
+
* Read text or file content aloud using TTS.
|
|
988
|
+
* Supports streaming for long content.
|
|
989
|
+
*/
|
|
990
|
+
const ReadAloudInput = z.object({
|
|
991
|
+
content: z.string(),
|
|
992
|
+
isFile: z.boolean().optional(),
|
|
993
|
+
voice: z.enum([
|
|
994
|
+
"af_heart",
|
|
995
|
+
"af_bella",
|
|
996
|
+
"af_nicole",
|
|
997
|
+
"am_fenrir",
|
|
998
|
+
"am_michael",
|
|
999
|
+
"bf_emma",
|
|
1000
|
+
"bm_george"
|
|
1001
|
+
]).default("af_heart"),
|
|
1002
|
+
speed: z.number().min(.5).max(2).default(1),
|
|
1003
|
+
maxLength: z.number().default(5e3),
|
|
1004
|
+
summarizeIfLong: z.boolean().default(false)
|
|
1005
|
+
});
|
|
1006
|
+
function saveWav$1(filename, audio, sampleRate) {
|
|
1007
|
+
const buffer = Buffer.alloc(44 + audio.length * 2);
|
|
1008
|
+
buffer.write("RIFF", 0);
|
|
1009
|
+
buffer.writeUInt32LE(36 + audio.length * 2, 4);
|
|
1010
|
+
buffer.write("WAVE", 8);
|
|
1011
|
+
buffer.write("fmt ", 12);
|
|
1012
|
+
buffer.writeUInt32LE(16, 16);
|
|
1013
|
+
buffer.writeUInt16LE(1, 20);
|
|
1014
|
+
buffer.writeUInt16LE(1, 22);
|
|
1015
|
+
buffer.writeUInt32LE(sampleRate, 24);
|
|
1016
|
+
buffer.writeUInt32LE(sampleRate * 2, 28);
|
|
1017
|
+
buffer.writeUInt16LE(2, 32);
|
|
1018
|
+
buffer.writeUInt16LE(16, 34);
|
|
1019
|
+
buffer.write("data", 36);
|
|
1020
|
+
buffer.writeUInt32LE(audio.length * 2, 40);
|
|
1021
|
+
for (let i = 0; i < audio.length; i++) {
|
|
1022
|
+
const s$1 = Math.max(-1, Math.min(1, audio[i]));
|
|
1023
|
+
buffer.writeInt16LE(Math.round(s$1 * 32767), 44 + i * 2);
|
|
1024
|
+
}
|
|
1025
|
+
writeFileSync$1(filename, buffer);
|
|
1026
|
+
}
|
|
1027
|
+
function playAudio(audioFile) {
|
|
1028
|
+
const platform = process.platform;
|
|
1029
|
+
if (platform === "darwin") execSync$1(`afplay "${audioFile}"`, { stdio: "inherit" });
|
|
1030
|
+
else if (platform === "linux") try {
|
|
1031
|
+
execSync$1(`aplay "${audioFile}"`, { stdio: "inherit" });
|
|
1032
|
+
} catch {
|
|
1033
|
+
execSync$1(`paplay "${audioFile}"`, { stdio: "inherit" });
|
|
1034
|
+
}
|
|
1035
|
+
else if (platform === "win32") execSync$1(`powershell -c "(New-Object Media.SoundPlayer '${audioFile}').PlaySync()"`, { stdio: "inherit" });
|
|
1036
|
+
}
|
|
1037
|
+
const readAloud = defineSkill({
|
|
1038
|
+
name: "read-aloud",
|
|
1039
|
+
description: "Read text or file content aloud using TTS",
|
|
1040
|
+
version: "1.0.0",
|
|
1041
|
+
input: ReadAloudInput,
|
|
1042
|
+
async run({ input, gerbil }) {
|
|
1043
|
+
const { content, isFile, voice = "af_heart", speed = 1, maxLength = 5e3, summarizeIfLong = false } = input;
|
|
1044
|
+
const shouldReadFile = isFile ?? existsSync$1(content);
|
|
1045
|
+
let textToRead;
|
|
1046
|
+
if (shouldReadFile) {
|
|
1047
|
+
if (!existsSync$1(content)) throw new Error(`File not found: ${content}`);
|
|
1048
|
+
textToRead = readFileSync$1(content, "utf-8");
|
|
1049
|
+
} else textToRead = content;
|
|
1050
|
+
if (textToRead.length > maxLength) if (summarizeIfLong) textToRead = (await gerbil.generate(`Summarize this content in a way that's good for reading aloud (2-3 paragraphs max):\n\n${textToRead.slice(0, 1e4)}`, {
|
|
1051
|
+
system: "You are a skilled narrator. Create a clear, spoken-word friendly summary.",
|
|
1052
|
+
maxTokens: 500,
|
|
1053
|
+
temperature: .3
|
|
1054
|
+
})).text;
|
|
1055
|
+
else textToRead = textToRead.slice(0, maxLength) + "...";
|
|
1056
|
+
const sentences = textToRead.split(/(?<=[.!?])\s+/).filter((s$1) => s$1.trim());
|
|
1057
|
+
let totalDuration = 0;
|
|
1058
|
+
for (const sentence of sentences) {
|
|
1059
|
+
if (!sentence.trim()) continue;
|
|
1060
|
+
const result = await gerbil.speak(sentence, {
|
|
1061
|
+
voice,
|
|
1062
|
+
speed
|
|
1063
|
+
});
|
|
1064
|
+
totalDuration += result.duration;
|
|
1065
|
+
const tempFile = join$1(tmpdir$1(), `gerbil-read-${Date.now()}.wav`);
|
|
1066
|
+
saveWav$1(tempFile, result.audio, result.sampleRate);
|
|
1067
|
+
try {
|
|
1068
|
+
playAudio(tempFile);
|
|
1069
|
+
} finally {
|
|
1070
|
+
try {
|
|
1071
|
+
unlinkSync$1(tempFile);
|
|
1072
|
+
} catch {}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
return `Read ${textToRead.length} characters from ${shouldReadFile ? `file "${content}"` : "text"} (${totalDuration.toFixed(1)}s audio)`;
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
//#endregion
|
|
1080
|
+
//#region src/skills/builtin/review.ts
|
|
1081
|
+
/**
|
|
1082
|
+
* Review Skill
|
|
1083
|
+
*
|
|
1084
|
+
* Code review with configurable focus areas.
|
|
1085
|
+
*/
|
|
1086
|
+
const ReviewInput = z.object({
|
|
1087
|
+
code: z.string(),
|
|
1088
|
+
focus: z.array(z.enum([
|
|
1089
|
+
"security",
|
|
1090
|
+
"performance",
|
|
1091
|
+
"style",
|
|
1092
|
+
"bugs",
|
|
1093
|
+
"all"
|
|
1094
|
+
])).default(["all"]),
|
|
1095
|
+
format: z.enum([
|
|
1096
|
+
"inline",
|
|
1097
|
+
"summary",
|
|
1098
|
+
"detailed"
|
|
1099
|
+
]).default("summary")
|
|
1100
|
+
});
|
|
1101
|
+
const review = defineSkill({
|
|
1102
|
+
name: "review",
|
|
1103
|
+
description: "Code review with configurable focus areas",
|
|
1104
|
+
version: "1.0.0",
|
|
1105
|
+
input: ReviewInput,
|
|
1106
|
+
temperature: .3,
|
|
1107
|
+
maxTokens: 600,
|
|
1108
|
+
async run({ input, gerbil }) {
|
|
1109
|
+
const { code, focus = ["all"], format = "summary" } = input;
|
|
1110
|
+
let systemPrompt = `You are a senior code reviewer.
|
|
1111
|
+
Review the code for:`;
|
|
1112
|
+
if (focus.includes("all")) systemPrompt += "\n- Security vulnerabilities\n- Performance issues\n- Code style and readability\n- Potential bugs";
|
|
1113
|
+
else {
|
|
1114
|
+
if (focus.includes("security")) systemPrompt += "\n- Security vulnerabilities";
|
|
1115
|
+
if (focus.includes("performance")) systemPrompt += "\n- Performance issues";
|
|
1116
|
+
if (focus.includes("style")) systemPrompt += "\n- Code style and readability";
|
|
1117
|
+
if (focus.includes("bugs")) systemPrompt += "\n- Potential bugs";
|
|
1118
|
+
}
|
|
1119
|
+
if (format === "summary") systemPrompt += "\n\nProvide a brief summary of issues found.";
|
|
1120
|
+
else if (format === "detailed") systemPrompt += "\n\nProvide detailed feedback with suggestions.";
|
|
1121
|
+
else systemPrompt += "\n\nProvide inline-style comments.";
|
|
1122
|
+
return (await gerbil.generate(`Review this code:\n\n${code}`, {
|
|
1123
|
+
system: systemPrompt,
|
|
1124
|
+
maxTokens: this.maxTokens,
|
|
1125
|
+
temperature: this.temperature
|
|
1126
|
+
})).text;
|
|
1127
|
+
}
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
//#endregion
|
|
1131
|
+
//#region src/skills/builtin/speak.ts
|
|
1132
|
+
/**
|
|
1133
|
+
* Speak Skill
|
|
1134
|
+
*
|
|
1135
|
+
* Convert text to speech using on-device TTS.
|
|
1136
|
+
*/
|
|
1137
|
+
const SpeakInput = z.object({
|
|
1138
|
+
text: z.string(),
|
|
1139
|
+
voice: z.enum([
|
|
1140
|
+
"af_heart",
|
|
1141
|
+
"af_bella",
|
|
1142
|
+
"af_nicole",
|
|
1143
|
+
"af_sarah",
|
|
1144
|
+
"am_fenrir",
|
|
1145
|
+
"am_michael",
|
|
1146
|
+
"bf_emma",
|
|
1147
|
+
"bf_isabella",
|
|
1148
|
+
"bm_george",
|
|
1149
|
+
"bm_lewis"
|
|
1150
|
+
]).default("af_heart"),
|
|
1151
|
+
speed: z.number().min(.5).max(2).default(1),
|
|
1152
|
+
output: z.string().optional()
|
|
1153
|
+
});
|
|
1154
|
+
/**
|
|
1155
|
+
* Save Float32Array as WAV file
|
|
1156
|
+
*/
|
|
1157
|
+
function saveWav(filename, audio, sampleRate) {
|
|
1158
|
+
const buffer = Buffer.alloc(44 + audio.length * 2);
|
|
1159
|
+
buffer.write("RIFF", 0);
|
|
1160
|
+
buffer.writeUInt32LE(36 + audio.length * 2, 4);
|
|
1161
|
+
buffer.write("WAVE", 8);
|
|
1162
|
+
buffer.write("fmt ", 12);
|
|
1163
|
+
buffer.writeUInt32LE(16, 16);
|
|
1164
|
+
buffer.writeUInt16LE(1, 20);
|
|
1165
|
+
buffer.writeUInt16LE(1, 22);
|
|
1166
|
+
buffer.writeUInt32LE(sampleRate, 24);
|
|
1167
|
+
buffer.writeUInt32LE(sampleRate * 2, 28);
|
|
1168
|
+
buffer.writeUInt16LE(2, 32);
|
|
1169
|
+
buffer.writeUInt16LE(16, 34);
|
|
1170
|
+
buffer.write("data", 36);
|
|
1171
|
+
buffer.writeUInt32LE(audio.length * 2, 40);
|
|
1172
|
+
for (let i = 0; i < audio.length; i++) {
|
|
1173
|
+
const s$1 = Math.max(-1, Math.min(1, audio[i]));
|
|
1174
|
+
buffer.writeInt16LE(Math.round(s$1 * 32767), 44 + i * 2);
|
|
1175
|
+
}
|
|
1176
|
+
writeFileSync$1(filename, buffer);
|
|
1177
|
+
}
|
|
1178
|
+
const speak = defineSkill({
|
|
1179
|
+
name: "speak",
|
|
1180
|
+
description: "Convert text to speech using on-device TTS (Kokoro-82M)",
|
|
1181
|
+
version: "1.0.0",
|
|
1182
|
+
input: SpeakInput,
|
|
1183
|
+
async run({ input, gerbil }) {
|
|
1184
|
+
const { text, voice = "af_heart", speed = 1, output } = input;
|
|
1185
|
+
const result = await gerbil.speak(text, {
|
|
1186
|
+
voice,
|
|
1187
|
+
speed
|
|
1188
|
+
});
|
|
1189
|
+
if (output) {
|
|
1190
|
+
saveWav(output, result.audio, result.sampleRate);
|
|
1191
|
+
return `Saved ${result.duration.toFixed(1)}s of audio to ${output}`;
|
|
1192
|
+
}
|
|
1193
|
+
const tempFile = join$1(tmpdir$1(), `gerbil-speak-${Date.now()}.wav`);
|
|
1194
|
+
saveWav(tempFile, result.audio, result.sampleRate);
|
|
1195
|
+
try {
|
|
1196
|
+
const platform = process.platform;
|
|
1197
|
+
if (platform === "darwin") execSync$1(`afplay "${tempFile}"`, { stdio: "inherit" });
|
|
1198
|
+
else if (platform === "linux") try {
|
|
1199
|
+
execSync$1(`aplay "${tempFile}"`, { stdio: "inherit" });
|
|
1200
|
+
} catch {
|
|
1201
|
+
try {
|
|
1202
|
+
execSync$1(`paplay "${tempFile}"`, { stdio: "inherit" });
|
|
1203
|
+
} catch {
|
|
1204
|
+
execSync$1(`play "${tempFile}"`, { stdio: "inherit" });
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
else if (platform === "win32") execSync$1(`powershell -c "(New-Object Media.SoundPlayer '${tempFile}').PlaySync()"`, { stdio: "inherit" });
|
|
1208
|
+
} finally {
|
|
1209
|
+
try {
|
|
1210
|
+
unlinkSync$1(tempFile);
|
|
1211
|
+
} catch {}
|
|
1212
|
+
}
|
|
1213
|
+
return `Spoke ${result.duration.toFixed(1)}s of audio (voice: ${voice}, speed: ${speed}x)`;
|
|
1214
|
+
}
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
//#endregion
|
|
1218
|
+
//#region src/skills/builtin/summarize.ts
|
|
1219
|
+
/**
|
|
1220
|
+
* Summarize Skill
|
|
1221
|
+
*
|
|
1222
|
+
* Summarize content in various lengths and formats.
|
|
1223
|
+
*/
|
|
1224
|
+
const SummarizeInput = z.object({
|
|
1225
|
+
content: z.string(),
|
|
1226
|
+
length: z.enum([
|
|
1227
|
+
"short",
|
|
1228
|
+
"medium",
|
|
1229
|
+
"long"
|
|
1230
|
+
]).default("medium"),
|
|
1231
|
+
format: z.enum(["paragraph", "bullets"]).default("paragraph"),
|
|
1232
|
+
focus: z.array(z.string()).optional()
|
|
1233
|
+
});
|
|
1234
|
+
const lengthGuide = {
|
|
1235
|
+
short: "2-3 sentences",
|
|
1236
|
+
medium: "1 paragraph (4-6 sentences)",
|
|
1237
|
+
long: "2-3 paragraphs"
|
|
1238
|
+
};
|
|
1239
|
+
const maxTokensGuide = {
|
|
1240
|
+
short: 100,
|
|
1241
|
+
medium: 200,
|
|
1242
|
+
long: 400
|
|
1243
|
+
};
|
|
1244
|
+
const summarize = defineSkill({
|
|
1245
|
+
name: "summarize",
|
|
1246
|
+
description: "Summarize content in various lengths and formats",
|
|
1247
|
+
version: "1.0.0",
|
|
1248
|
+
input: SummarizeInput,
|
|
1249
|
+
temperature: .3,
|
|
1250
|
+
async run({ input, gerbil }) {
|
|
1251
|
+
const { content, length = "medium", format = "paragraph", focus } = input;
|
|
1252
|
+
let systemPrompt = `You are a summarization expert.
|
|
1253
|
+
Summarize the content in ${lengthGuide[length]}.`;
|
|
1254
|
+
if (format === "bullets") systemPrompt += "\nUse bullet points.";
|
|
1255
|
+
if (focus && focus.length > 0) systemPrompt += `\nFocus on: ${focus.join(", ")}.`;
|
|
1256
|
+
systemPrompt += "\nOnly output the summary, nothing else.";
|
|
1257
|
+
return (await gerbil.generate(`Summarize this:\n\n${content}`, {
|
|
1258
|
+
system: systemPrompt,
|
|
1259
|
+
maxTokens: maxTokensGuide[length],
|
|
1260
|
+
temperature: this.temperature
|
|
1261
|
+
})).text;
|
|
1262
|
+
}
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
//#endregion
|
|
1266
|
+
//#region src/skills/builtin/test.ts
|
|
1267
|
+
/**
|
|
1268
|
+
* Test Skill
|
|
1269
|
+
*
|
|
1270
|
+
* Generate tests for code.
|
|
1271
|
+
*/
|
|
1272
|
+
const TestInput = z.object({
|
|
1273
|
+
code: z.string(),
|
|
1274
|
+
framework: z.enum([
|
|
1275
|
+
"jest",
|
|
1276
|
+
"vitest",
|
|
1277
|
+
"mocha",
|
|
1278
|
+
"playwright"
|
|
1279
|
+
]).default("vitest"),
|
|
1280
|
+
style: z.enum([
|
|
1281
|
+
"unit",
|
|
1282
|
+
"integration",
|
|
1283
|
+
"e2e"
|
|
1284
|
+
]).default("unit")
|
|
1285
|
+
});
|
|
1286
|
+
const test = defineSkill({
|
|
1287
|
+
name: "test",
|
|
1288
|
+
description: "Generate tests for code",
|
|
1289
|
+
version: "1.0.0",
|
|
1290
|
+
input: TestInput,
|
|
1291
|
+
temperature: .3,
|
|
1292
|
+
maxTokens: 800,
|
|
1293
|
+
async run({ input, gerbil }) {
|
|
1294
|
+
const { code, framework, style } = input;
|
|
1295
|
+
const systemPrompt = `You are a test engineer.
|
|
1296
|
+
Generate ${style} tests using ${framework} for the provided code.
|
|
1297
|
+
Include edge cases and error scenarios.
|
|
1298
|
+
Only output the test code, no explanations.`;
|
|
1299
|
+
return (await gerbil.generate(`Generate tests for:\n\n${code}`, {
|
|
1300
|
+
system: systemPrompt,
|
|
1301
|
+
maxTokens: this.maxTokens,
|
|
1302
|
+
temperature: this.temperature
|
|
1303
|
+
})).text;
|
|
1304
|
+
}
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
//#endregion
|
|
1308
|
+
//#region src/skills/builtin/title.ts
|
|
1309
|
+
/**
|
|
1310
|
+
* Title Skill
|
|
1311
|
+
*
|
|
1312
|
+
* Generate titles for content.
|
|
1313
|
+
*/
|
|
1314
|
+
const TitleInput = z.object({
|
|
1315
|
+
content: z.string(),
|
|
1316
|
+
style: z.enum([
|
|
1317
|
+
"professional",
|
|
1318
|
+
"clickbait",
|
|
1319
|
+
"seo",
|
|
1320
|
+
"simple"
|
|
1321
|
+
]).default("professional"),
|
|
1322
|
+
maxLength: z.number().default(60)
|
|
1323
|
+
});
|
|
1324
|
+
const styleGuide = {
|
|
1325
|
+
professional: "Clear, informative, and professional",
|
|
1326
|
+
clickbait: "Engaging and curiosity-inducing",
|
|
1327
|
+
seo: "SEO-optimized with relevant keywords",
|
|
1328
|
+
simple: "Simple and straightforward"
|
|
1329
|
+
};
|
|
1330
|
+
const title = defineSkill({
|
|
1331
|
+
name: "title",
|
|
1332
|
+
description: "Generate titles for content",
|
|
1333
|
+
version: "1.0.0",
|
|
1334
|
+
input: TitleInput,
|
|
1335
|
+
temperature: .7,
|
|
1336
|
+
maxTokens: 30,
|
|
1337
|
+
async run({ input, gerbil }) {
|
|
1338
|
+
const { content, style = "professional", maxLength = 60 } = input;
|
|
1339
|
+
const systemPrompt = `Generate a title for the content.
|
|
1340
|
+
Style: ${styleGuide[style]}
|
|
1341
|
+
Max length: ${maxLength} characters
|
|
1342
|
+
Only output the title, nothing else.`;
|
|
1343
|
+
let generatedTitle = (await gerbil.generate(`Generate a title for:\n\n${content.substring(0, 1e3)}`, {
|
|
1344
|
+
system: systemPrompt,
|
|
1345
|
+
maxTokens: this.maxTokens,
|
|
1346
|
+
temperature: this.temperature
|
|
1347
|
+
})).text.trim();
|
|
1348
|
+
generatedTitle = generatedTitle.replace(/^["']|["']$/g, "");
|
|
1349
|
+
if (generatedTitle.length > maxLength) generatedTitle = `${generatedTitle.substring(0, maxLength - 3)}...`;
|
|
1350
|
+
return generatedTitle;
|
|
1351
|
+
}
|
|
1352
|
+
});
|
|
1353
|
+
|
|
1354
|
+
//#endregion
|
|
1355
|
+
//#region src/skills/builtin/transcribe.ts
|
|
1356
|
+
/**
|
|
1357
|
+
* Transcribe Skill
|
|
1358
|
+
*
|
|
1359
|
+
* Convert audio to text using on-device STT (Whisper).
|
|
1360
|
+
*/
|
|
1361
|
+
const TranscribeInput = z.object({
|
|
1362
|
+
audio: z.string(),
|
|
1363
|
+
model: z.enum([
|
|
1364
|
+
"whisper-tiny.en",
|
|
1365
|
+
"whisper-base.en",
|
|
1366
|
+
"whisper-small.en",
|
|
1367
|
+
"whisper-large-v3-turbo"
|
|
1368
|
+
]).default("whisper-tiny.en"),
|
|
1369
|
+
language: z.string().optional(),
|
|
1370
|
+
timestamps: z.boolean().default(false),
|
|
1371
|
+
output: z.string().optional()
|
|
1372
|
+
});
|
|
1373
|
+
const transcribe = defineSkill({
|
|
1374
|
+
name: "transcribe",
|
|
1375
|
+
description: "Transcribe audio to text using on-device STT (Whisper)",
|
|
1376
|
+
version: "1.0.0",
|
|
1377
|
+
input: TranscribeInput,
|
|
1378
|
+
async run({ input, gerbil }) {
|
|
1379
|
+
const { audio, model = "whisper-tiny.en", language, timestamps = false, output } = input;
|
|
1380
|
+
const filePath = resolve(audio);
|
|
1381
|
+
if (!existsSync$1(filePath)) throw new Error(`Audio file not found: ${filePath}`);
|
|
1382
|
+
const audioData = new Uint8Array(readFileSync$1(filePath));
|
|
1383
|
+
await gerbil.loadSTT(model);
|
|
1384
|
+
const result = await gerbil.transcribe(audioData, {
|
|
1385
|
+
language,
|
|
1386
|
+
timestamps
|
|
1387
|
+
});
|
|
1388
|
+
let text;
|
|
1389
|
+
if (timestamps && result.segments) text = result.segments.map((seg) => `[${seg.start.toFixed(1)}s - ${seg.end.toFixed(1)}s] ${seg.text}`).join("\n");
|
|
1390
|
+
else text = result.text;
|
|
1391
|
+
if (output) {
|
|
1392
|
+
writeFileSync$1(output, text);
|
|
1393
|
+
return `Transcribed ${result.duration.toFixed(1)}s of audio to ${output}`;
|
|
1394
|
+
}
|
|
1395
|
+
return text;
|
|
1396
|
+
}
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
//#endregion
|
|
1400
|
+
//#region src/skills/builtin/translate.ts
|
|
1401
|
+
/**
|
|
1402
|
+
* Translate Skill
|
|
1403
|
+
*
|
|
1404
|
+
* Translate text between languages.
|
|
1405
|
+
*/
|
|
1406
|
+
const TranslateInput = z.object({
|
|
1407
|
+
text: z.string(),
|
|
1408
|
+
from: z.string().optional(),
|
|
1409
|
+
to: z.string(),
|
|
1410
|
+
preserveFormatting: z.boolean().default(true)
|
|
1411
|
+
});
|
|
1412
|
+
const translate = defineSkill({
|
|
1413
|
+
name: "translate",
|
|
1414
|
+
description: "Translate text between languages",
|
|
1415
|
+
version: "1.0.0",
|
|
1416
|
+
input: TranslateInput,
|
|
1417
|
+
temperature: .3,
|
|
1418
|
+
async run({ input, gerbil }) {
|
|
1419
|
+
const { text, from, to, preserveFormatting } = input;
|
|
1420
|
+
let systemPrompt = `You are a professional translator.
|
|
1421
|
+
Translate the text to ${to}.`;
|
|
1422
|
+
if (from) systemPrompt += `\nThe source language is ${from}.`;
|
|
1423
|
+
if (preserveFormatting) systemPrompt += "\nPreserve the original formatting (paragraphs, lists, etc.).";
|
|
1424
|
+
systemPrompt += "\nOnly output the translation, nothing else.";
|
|
1425
|
+
return (await gerbil.generate(`Translate:\n\n${text}`, {
|
|
1426
|
+
system: systemPrompt,
|
|
1427
|
+
maxTokens: Math.ceil(text.length / 2),
|
|
1428
|
+
temperature: this.temperature
|
|
1429
|
+
})).text;
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
//#endregion
|
|
1434
|
+
export { defineSkill as C, listSkills as D, hasSkill as E, removeSkill as O, clearSkills as S, getSkillInfo as T, analyzeScreenshot as _, summarize as a, loadSkillPackage as b, readAloud as c, explain as d, describeImage as f, announce as g, captionImage as h, test as i, useSkill as k, extractFromImage as l, commit as m, transcribe as n, speak as o, compareImages as p, title as r, review as s, translate as t, extract as u, loadProjectSkills as v, getAllSkillInfo as w, loadSkills as x, loadSkill as y };
|
|
1435
|
+
//# sourceMappingURL=skills-DulrOPeP.mjs.map
|