zubo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/.github/workflows/ci.yml +35 -0
  2. package/README.md +149 -0
  3. package/bun.lock +216 -0
  4. package/desktop/README.md +57 -0
  5. package/desktop/package.json +12 -0
  6. package/desktop/src-tauri/Cargo.toml +25 -0
  7. package/desktop/src-tauri/build.rs +3 -0
  8. package/desktop/src-tauri/icons/README.md +17 -0
  9. package/desktop/src-tauri/icons/icon.png +0 -0
  10. package/desktop/src-tauri/src/main.rs +189 -0
  11. package/desktop/src-tauri/tauri.conf.json +68 -0
  12. package/docs/ROADMAP.md +490 -0
  13. package/migrations/001_init.sql +9 -0
  14. package/migrations/002_memory.sql +33 -0
  15. package/migrations/003_cron.sql +24 -0
  16. package/migrations/004_usage.sql +12 -0
  17. package/migrations/005_secrets.sql +8 -0
  18. package/migrations/006_agents.sql +1 -0
  19. package/migrations/007_workflows.sql +22 -0
  20. package/migrations/008_proactive.sql +24 -0
  21. package/migrations/009_uploads.sql +9 -0
  22. package/migrations/010_observability.sql +22 -0
  23. package/migrations/011_api_keys.sql +7 -0
  24. package/migrations/012_indexes.sql +5 -0
  25. package/migrations/013_budget.sql +11 -0
  26. package/migrations/014_usage_session_idx.sql +2 -0
  27. package/package.json +39 -0
  28. package/site/404.html +156 -0
  29. package/site/CNAME +1 -0
  30. package/site/docs/agents.html +294 -0
  31. package/site/docs/api.html +446 -0
  32. package/site/docs/channels.html +345 -0
  33. package/site/docs/cli.html +238 -0
  34. package/site/docs/config.html +1034 -0
  35. package/site/docs/index.html +433 -0
  36. package/site/docs/integrations.html +381 -0
  37. package/site/docs/memory.html +254 -0
  38. package/site/docs/security.html +375 -0
  39. package/site/docs/skills.html +322 -0
  40. package/site/docs.css +412 -0
  41. package/site/index.html +638 -0
  42. package/site/install.sh +98 -0
  43. package/site/logo.svg +1 -0
  44. package/site/og-image.png +0 -0
  45. package/site/robots.txt +4 -0
  46. package/site/script.js +361 -0
  47. package/site/sitemap.xml +63 -0
  48. package/site/skills.html +532 -0
  49. package/site/style.css +1686 -0
  50. package/src/agent/agents.ts +159 -0
  51. package/src/agent/compaction.ts +53 -0
  52. package/src/agent/context.ts +18 -0
  53. package/src/agent/delegate.ts +118 -0
  54. package/src/agent/loop.ts +318 -0
  55. package/src/agent/prompts.ts +111 -0
  56. package/src/agent/session.ts +87 -0
  57. package/src/agent/teams.ts +116 -0
  58. package/src/agent/workflow-executor.ts +192 -0
  59. package/src/agent/workflow.ts +175 -0
  60. package/src/channels/adapter.ts +21 -0
  61. package/src/channels/dashboard.html.ts +2969 -0
  62. package/src/channels/discord.ts +137 -0
  63. package/src/channels/optional-deps.d.ts +17 -0
  64. package/src/channels/router.ts +199 -0
  65. package/src/channels/signal.ts +133 -0
  66. package/src/channels/slack.ts +101 -0
  67. package/src/channels/telegram.ts +102 -0
  68. package/src/channels/utils.ts +18 -0
  69. package/src/channels/webchat.ts +1797 -0
  70. package/src/channels/whatsapp.ts +119 -0
  71. package/src/config/loader.ts +22 -0
  72. package/src/config/paths.ts +43 -0
  73. package/src/config/schema.ts +121 -0
  74. package/src/db/connection.ts +20 -0
  75. package/src/db/export.ts +148 -0
  76. package/src/db/migrations.ts +42 -0
  77. package/src/index.ts +261 -0
  78. package/src/llm/claude.ts +193 -0
  79. package/src/llm/factory.ts +115 -0
  80. package/src/llm/failover.ts +101 -0
  81. package/src/llm/openai-compat.ts +409 -0
  82. package/src/llm/provider.ts +83 -0
  83. package/src/llm/smart-router.ts +241 -0
  84. package/src/logs.ts +53 -0
  85. package/src/memory/chunker.ts +58 -0
  86. package/src/memory/document-parser.ts +115 -0
  87. package/src/memory/embedder.ts +235 -0
  88. package/src/memory/engine.ts +170 -0
  89. package/src/memory/fts-index.ts +55 -0
  90. package/src/memory/hybrid-search.ts +72 -0
  91. package/src/memory/store.ts +56 -0
  92. package/src/memory/vector-index.ts +72 -0
  93. package/src/model.ts +118 -0
  94. package/src/registry/cli.ts +43 -0
  95. package/src/registry/client.ts +54 -0
  96. package/src/registry/installer.ts +67 -0
  97. package/src/scheduler/briefing.ts +71 -0
  98. package/src/scheduler/cron.ts +258 -0
  99. package/src/scheduler/heartbeat.ts +58 -0
  100. package/src/scheduler/memory-triggers.ts +100 -0
  101. package/src/scheduler/natural-cron.ts +163 -0
  102. package/src/scheduler/proactive.ts +25 -0
  103. package/src/scheduler/recipes.ts +110 -0
  104. package/src/secrets/store.ts +64 -0
  105. package/src/setup.ts +413 -0
  106. package/src/skills.ts +293 -0
  107. package/src/start.ts +373 -0
  108. package/src/status.ts +165 -0
  109. package/src/tools/builtin/connect-service.ts +205 -0
  110. package/src/tools/builtin/cron.ts +126 -0
  111. package/src/tools/builtin/datetime.ts +36 -0
  112. package/src/tools/builtin/delegate-task.ts +81 -0
  113. package/src/tools/builtin/delegate.ts +42 -0
  114. package/src/tools/builtin/diagnose.ts +41 -0
  115. package/src/tools/builtin/google-oauth.ts +379 -0
  116. package/src/tools/builtin/manage-agents.ts +149 -0
  117. package/src/tools/builtin/manage-skills.ts +294 -0
  118. package/src/tools/builtin/manage-teams.ts +89 -0
  119. package/src/tools/builtin/manage-triggers.ts +94 -0
  120. package/src/tools/builtin/manage-workflows.ts +119 -0
  121. package/src/tools/builtin/memory-search.ts +38 -0
  122. package/src/tools/builtin/memory-write.ts +30 -0
  123. package/src/tools/builtin/run-workflow.ts +36 -0
  124. package/src/tools/builtin/secrets.ts +122 -0
  125. package/src/tools/builtin/skill-registry.ts +75 -0
  126. package/src/tools/builtin-integrations/api-helpers.ts +26 -0
  127. package/src/tools/builtin-integrations/github/github_issues/SKILL.md +56 -0
  128. package/src/tools/builtin-integrations/github/github_issues/handler.ts +108 -0
  129. package/src/tools/builtin-integrations/github/github_prs/SKILL.md +57 -0
  130. package/src/tools/builtin-integrations/github/github_prs/handler.ts +113 -0
  131. package/src/tools/builtin-integrations/github/github_repos/SKILL.md +37 -0
  132. package/src/tools/builtin-integrations/github/github_repos/handler.ts +88 -0
  133. package/src/tools/builtin-integrations/google/gmail/SKILL.md +51 -0
  134. package/src/tools/builtin-integrations/google/gmail/handler.ts +125 -0
  135. package/src/tools/builtin-integrations/google/google_calendar/SKILL.md +35 -0
  136. package/src/tools/builtin-integrations/google/google_calendar/handler.ts +105 -0
  137. package/src/tools/builtin-integrations/google/google_docs/SKILL.md +35 -0
  138. package/src/tools/builtin-integrations/google/google_docs/handler.ts +108 -0
  139. package/src/tools/builtin-integrations/google/google_drive/SKILL.md +39 -0
  140. package/src/tools/builtin-integrations/google/google_drive/handler.ts +106 -0
  141. package/src/tools/builtin-integrations/google/google_sheets/SKILL.md +36 -0
  142. package/src/tools/builtin-integrations/google/google_sheets/handler.ts +116 -0
  143. package/src/tools/builtin-integrations/jira/jira_boards/SKILL.md +21 -0
  144. package/src/tools/builtin-integrations/jira/jira_boards/handler.ts +74 -0
  145. package/src/tools/builtin-integrations/jira/jira_issues/SKILL.md +28 -0
  146. package/src/tools/builtin-integrations/jira/jira_issues/handler.ts +140 -0
  147. package/src/tools/builtin-integrations/linear/linear_issues/SKILL.md +30 -0
  148. package/src/tools/builtin-integrations/linear/linear_issues/handler.ts +75 -0
  149. package/src/tools/builtin-integrations/linear/linear_projects/SKILL.md +21 -0
  150. package/src/tools/builtin-integrations/linear/linear_projects/handler.ts +43 -0
  151. package/src/tools/builtin-integrations/notion/notion_databases/SKILL.md +39 -0
  152. package/src/tools/builtin-integrations/notion/notion_databases/handler.ts +83 -0
  153. package/src/tools/builtin-integrations/notion/notion_pages/SKILL.md +43 -0
  154. package/src/tools/builtin-integrations/notion/notion_pages/handler.ts +130 -0
  155. package/src/tools/builtin-integrations/notion/notion_search/SKILL.md +27 -0
  156. package/src/tools/builtin-integrations/notion/notion_search/handler.ts +69 -0
  157. package/src/tools/builtin-integrations/slack/slack_messages/SKILL.md +42 -0
  158. package/src/tools/builtin-integrations/slack/slack_messages/handler.ts +72 -0
  159. package/src/tools/builtin-integrations/twitter/twitter_posts/SKILL.md +24 -0
  160. package/src/tools/builtin-integrations/twitter/twitter_posts/handler.ts +133 -0
  161. package/src/tools/builtin-skills/file-read/SKILL.md +26 -0
  162. package/src/tools/builtin-skills/file-read/handler.ts +66 -0
  163. package/src/tools/builtin-skills/file-write/SKILL.md +30 -0
  164. package/src/tools/builtin-skills/file-write/handler.ts +64 -0
  165. package/src/tools/builtin-skills/http-request/SKILL.md +34 -0
  166. package/src/tools/builtin-skills/http-request/handler.ts +87 -0
  167. package/src/tools/builtin-skills/shell/SKILL.md +26 -0
  168. package/src/tools/builtin-skills/shell/handler.ts +96 -0
  169. package/src/tools/builtin-skills/url-fetch/SKILL.md +26 -0
  170. package/src/tools/builtin-skills/url-fetch/handler.ts +37 -0
  171. package/src/tools/builtin-skills/web-search/SKILL.md +26 -0
  172. package/src/tools/builtin-skills/web-search/handler.ts +50 -0
  173. package/src/tools/executor.ts +205 -0
  174. package/src/tools/integration-installer.ts +106 -0
  175. package/src/tools/permissions.ts +45 -0
  176. package/src/tools/registry.ts +39 -0
  177. package/src/tools/sandbox-runner.ts +56 -0
  178. package/src/tools/sandbox.ts +82 -0
  179. package/src/tools/skill-installer.ts +52 -0
  180. package/src/tools/skill-loader.ts +259 -0
  181. package/src/types/optional-deps.d.ts +23 -0
  182. package/src/util/auth.ts +121 -0
  183. package/src/util/costs.ts +59 -0
  184. package/src/util/error-buffer.ts +32 -0
  185. package/src/util/google-tokens.ts +180 -0
  186. package/src/util/logger.ts +73 -0
  187. package/src/util/perf-collector.ts +35 -0
  188. package/src/util/rate-limiter.ts +70 -0
  189. package/src/util/tokens.ts +17 -0
  190. package/src/voice/stt.ts +57 -0
  191. package/src/voice/tts.ts +103 -0
  192. package/tests/agent/session.test.ts +109 -0
  193. package/tests/agent-loop.test.ts +54 -0
  194. package/tests/auth.test.ts +89 -0
  195. package/tests/channels.test.ts +67 -0
  196. package/tests/compaction.test.ts +44 -0
  197. package/tests/config.test.ts +51 -0
  198. package/tests/costs.test.ts +19 -0
  199. package/tests/cron.test.ts +55 -0
  200. package/tests/db/export.test.ts +219 -0
  201. package/tests/executor.test.ts +144 -0
  202. package/tests/export.test.ts +137 -0
  203. package/tests/helpers/mock-llm.ts +34 -0
  204. package/tests/helpers/test-db.ts +74 -0
  205. package/tests/integration/chat-flow.test.ts +48 -0
  206. package/tests/integrations.test.ts +97 -0
  207. package/tests/memory/engine.test.ts +114 -0
  208. package/tests/memory-engine.test.ts +57 -0
  209. package/tests/permissions.test.ts +21 -0
  210. package/tests/rate-limiter.test.ts +70 -0
  211. package/tests/registry.test.ts +67 -0
  212. package/tests/router.test.ts +36 -0
  213. package/tests/session.test.ts +58 -0
  214. package/tests/skill-loader.test.ts +44 -0
  215. package/tests/tokens.test.ts +30 -0
  216. package/tests/tools/executor.test.ts +130 -0
  217. package/tests/util/auth.test.ts +75 -0
  218. package/tests/util/rate-limiter.test.ts +73 -0
  219. package/tests/voice.test.ts +60 -0
  220. package/tests/webchat.test.ts +88 -0
  221. package/tests/workflow.test.ts +38 -0
  222. package/tsconfig.json +16 -0
@@ -0,0 +1,294 @@
1
+ import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync, rmSync } from "fs";
2
+ import { join } from "path";
3
+ import { paths } from "../../config/paths";
4
+ import { registerTool, unregisterTool, getTool } from "../registry";
5
+ import { parseSkillMd, parseSkillExport, paramsToJsonSchema } from "../skill-loader";
6
+ import { logger } from "../../util/logger";
7
+
8
+ export function registerManageSkillsTool() {
9
+ registerTool({
10
+ definition: {
11
+ name: "manage_skills",
12
+ description:
13
+ 'Create, list, or remove skills (custom tools) at runtime. Use this when the user asks you to build, create, or make a new tool, skill, or utility — including phrases like "build a skill that...", "make me a tool to...", "create a skill for...". Also use for listing installed skills or removing one. Created skills are available immediately without restarting.',
14
+ input_schema: {
15
+ type: "object",
16
+ properties: {
17
+ action: {
18
+ type: "string",
19
+ enum: ["create", "list", "remove"],
20
+ description: "The action to perform.",
21
+ },
22
+ name: {
23
+ type: "string",
24
+ description: "Skill name (lowercase, underscores only). Required for create and remove.",
25
+ },
26
+ description: {
27
+ type: "string",
28
+ description: "What the skill does. Required for create.",
29
+ },
30
+ input_schema: {
31
+ type: "object",
32
+ description:
33
+ "JSON Schema for the tool's input parameters. Should have type, properties, and required fields. Use this OR params (simplified format).",
34
+ },
35
+ params: {
36
+ type: "object",
37
+ description:
38
+ 'Simplified parameter definitions. Each key is a param name with {type, description, required} fields. E.g. { "query": { "type": "string", "required": true } }. Use this OR input_schema.',
39
+ },
40
+ handler_code: {
41
+ type: "string",
42
+ description:
43
+ 'TypeScript code for handler.ts. Must export a default async function that takes Record<string, unknown> and returns a Promise<string>. Required for create. Example: \'export default async function (input: Record<string, unknown>): Promise<string> { return JSON.stringify({ result: "hello" }); }\'',
44
+ },
45
+ },
46
+ required: ["action"],
47
+ },
48
+ },
49
+ execute: async (input) => {
50
+ const action = input.action as string;
51
+
52
+ switch (action) {
53
+ case "create":
54
+ return await createSkill(input);
55
+ case "list":
56
+ return listSkills();
57
+ case "remove":
58
+ return removeSkill(input);
59
+ default:
60
+ return JSON.stringify({ error: `Unknown action: ${action}` });
61
+ }
62
+ },
63
+ });
64
+ }
65
+
66
+ async function createSkill(input: Record<string, unknown>): Promise<string> {
67
+ const name = input.name as string;
68
+ const description = input.description as string;
69
+ const inputSchema = input.input_schema as Record<string, unknown> | undefined;
70
+ const params = input.params as Record<string, { type: string; description?: string; required?: boolean }> | undefined;
71
+ const handlerCode = input.handler_code as string;
72
+
73
+ if (!name || !/^[a-z0-9_]+$/.test(name)) {
74
+ return JSON.stringify({ error: "Invalid name. Must match [a-z0-9_]+" });
75
+ }
76
+ if (!description) {
77
+ return JSON.stringify({ error: "Description is required." });
78
+ }
79
+ if (!handlerCode) {
80
+ return JSON.stringify({ error: "handler_code is required." });
81
+ }
82
+
83
+ // Resolve the final input schema — accept either params shorthand or full input_schema
84
+ let resolvedSchema: Record<string, unknown>;
85
+ if (params) {
86
+ resolvedSchema = paramsToJsonSchema(params);
87
+ } else if (inputSchema) {
88
+ resolvedSchema = inputSchema;
89
+ } else {
90
+ return JSON.stringify({ error: "Either params or input_schema is required." });
91
+ }
92
+
93
+ // Check for existing tool (built-in or skill)
94
+ if (getTool(name)) {
95
+ return JSON.stringify({ error: `A tool named "${name}" already exists.` });
96
+ }
97
+
98
+ const destDir = join(paths.skills, name);
99
+
100
+ // Build params block for the skill config
101
+ const escParam = (s: string) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
102
+ let paramsLiteral = "{}";
103
+ if (params) {
104
+ const lines = Object.entries(params).map(([key, def]) => {
105
+ const parts = [`type: "${def.type || "string"}"`];
106
+ if (def.description) parts.push(`description: "${escParam(def.description)}"`);
107
+ if (def.required) parts.push("required: true");
108
+ return ` ${key}: { ${parts.join(", ")} }`;
109
+ });
110
+ paramsLiteral = `{\n${lines.join(",\n")}\n }`;
111
+ } else if (inputSchema) {
112
+ // Convert full JSON Schema back to params shorthand for the file
113
+ const props = (inputSchema as any).properties || {};
114
+ const req = (inputSchema as any).required || [];
115
+ const lines = Object.entries(props).map(([key, def]: [string, any]) => {
116
+ const parts = [`type: "${escParam(Array.isArray(def.type) ? def.type[0] : (def.type || "string"))}"`];
117
+ if (def.description) parts.push(`description: "${escParam(def.description)}"`);
118
+ if (req.includes(key)) parts.push("required: true");
119
+ return ` ${key}: { ${parts.join(", ")} }`;
120
+ });
121
+ paramsLiteral = lines.length ? `{\n${lines.join(",\n")}\n }` : "{}";
122
+ }
123
+
124
+ // Generate single-file handler.ts with skill config prepended
125
+ const esc = (s: string) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
126
+ const skillHeader = `export const skill = {
127
+ name: "${esc(name)}",
128
+ description: "${esc(description)}",
129
+ params: ${paramsLiteral}
130
+ };
131
+
132
+ `;
133
+
134
+ const fullHandler = skillHeader + handlerCode;
135
+
136
+ // Write files
137
+ mkdirSync(destDir, { recursive: true });
138
+ writeFileSync(join(destDir, "handler.ts"), fullHandler);
139
+
140
+ // Hot-reload: dynamically import and register
141
+ try {
142
+ const handlerPath = join(destDir, "handler.ts");
143
+ const mod = await import(handlerPath);
144
+ const handler = mod.default;
145
+
146
+ if (typeof handler !== "function") {
147
+ return JSON.stringify({
148
+ error: "handler_code must export a default function.",
149
+ files_written: true,
150
+ });
151
+ }
152
+
153
+ registerTool({
154
+ definition: {
155
+ name,
156
+ description,
157
+ input_schema: resolvedSchema,
158
+ },
159
+ execute: handler,
160
+ });
161
+
162
+ logger.info(`Skill "${name}" created and registered at runtime`);
163
+ return JSON.stringify({
164
+ success: true,
165
+ name,
166
+ message: `Skill "${name}" created and available immediately.`,
167
+ });
168
+ } catch (err: any) {
169
+ logger.error(`Failed to hot-load skill "${name}": ${err.message}`);
170
+ return JSON.stringify({
171
+ error: `Files written but failed to load handler: ${err.message}`,
172
+ files_written: true,
173
+ });
174
+ }
175
+ }
176
+
177
+ function listSkills(): string {
178
+ if (!existsSync(paths.skills)) {
179
+ return JSON.stringify({ skills: [] });
180
+ }
181
+
182
+ let entries: string[];
183
+ try {
184
+ entries = readdirSync(paths.skills);
185
+ } catch {
186
+ return JSON.stringify({ skills: [] });
187
+ }
188
+
189
+ const skills: { name: string; description: string; status: string }[] = [];
190
+
191
+ for (const entry of entries) {
192
+ const dirPath = join(paths.skills, entry);
193
+ const skillMdPath = join(dirPath, "SKILL.md");
194
+ const handlerPath = join(dirPath, "handler.ts");
195
+
196
+ const hasHandler = existsSync(handlerPath);
197
+ const hasSkillMd = existsSync(skillMdPath);
198
+
199
+ if (!hasHandler && !hasSkillMd) continue;
200
+
201
+ let name = entry;
202
+ let description = "";
203
+ let status = "error";
204
+
205
+ if (hasSkillMd) {
206
+ const mdContent = readFileSync(skillMdPath, "utf-8");
207
+ const parsed = parseSkillMd(mdContent, dirPath);
208
+ name = parsed?.name ?? entry;
209
+ description = parsed?.description?.split("\n")[0] ?? "";
210
+ status = parsed && hasHandler ? "ok" : "error";
211
+ } else if (hasHandler) {
212
+ try {
213
+ const content = readFileSync(handlerPath, "utf-8");
214
+ const nameMatch = content.match(/name\s*:\s*["']([^"']+)["']/);
215
+ const descMatch = content.match(/description\s*:\s*["']([^"']+)["']/);
216
+ if (nameMatch) name = nameMatch[1];
217
+ if (descMatch) description = descMatch[1];
218
+ status = "ok";
219
+ } catch {
220
+ status = "error";
221
+ }
222
+ }
223
+
224
+ skills.push({ name, description, status });
225
+ }
226
+
227
+ return JSON.stringify({ skills, count: skills.length });
228
+ }
229
+
230
+ function removeSkill(input: Record<string, unknown>): string {
231
+ const name = input.name as string;
232
+ if (!name) {
233
+ return JSON.stringify({ error: "Skill name is required for remove." });
234
+ }
235
+
236
+ // Find the skill folder (folder name might differ from tool name)
237
+ if (!existsSync(paths.skills)) {
238
+ return JSON.stringify({ error: "No skills directory found." });
239
+ }
240
+
241
+ let entries: string[];
242
+ try {
243
+ entries = readdirSync(paths.skills);
244
+ } catch {
245
+ return JSON.stringify({ error: "Could not read skills directory." });
246
+ }
247
+
248
+ let targetFolder: string | null = null;
249
+ for (const entry of entries) {
250
+ const dirPath = join(paths.skills, entry);
251
+ const skillMdPath = join(dirPath, "SKILL.md");
252
+ const handlerPath = join(dirPath, "handler.ts");
253
+
254
+ if (entry === name) {
255
+ targetFolder = entry;
256
+ break;
257
+ }
258
+
259
+ if (existsSync(skillMdPath)) {
260
+ const mdContent = readFileSync(skillMdPath, "utf-8");
261
+ const parsed = parseSkillMd(mdContent, dirPath);
262
+ if (parsed?.name === name) {
263
+ targetFolder = entry;
264
+ break;
265
+ }
266
+ } else if (existsSync(handlerPath)) {
267
+ try {
268
+ const content = readFileSync(handlerPath, "utf-8");
269
+ const nameMatch = content.match(/name\s*:\s*["']([^"']+)["']/);
270
+ if (nameMatch && nameMatch[1] === name) {
271
+ targetFolder = entry;
272
+ break;
273
+ }
274
+ } catch {
275
+ // skip
276
+ }
277
+ }
278
+ }
279
+
280
+ if (!targetFolder) {
281
+ return JSON.stringify({ error: `Skill "${name}" not found.` });
282
+ }
283
+
284
+ const dirPath = join(paths.skills, targetFolder);
285
+ rmSync(dirPath, { recursive: true, force: true });
286
+ unregisterTool(name);
287
+
288
+ logger.info(`Skill "${name}" removed`);
289
+ return JSON.stringify({
290
+ success: true,
291
+ name,
292
+ message: `Skill "${name}" removed.`,
293
+ });
294
+ }
@@ -0,0 +1,89 @@
1
+ import { registerTool } from "../registry";
2
+ import { loadTeams, getTeam, createTeam, removeTeam, type AgentTeam } from "../../agent/teams";
3
+
4
+ export function registerManageTeamsTool() {
5
+ registerTool({
6
+ definition: {
7
+ name: "manage_teams",
8
+ description:
9
+ "Create, list, view, and remove agent teams. Teams group agents together with an optional default workflow.",
10
+ input_schema: {
11
+ type: "object",
12
+ properties: {
13
+ action: {
14
+ type: "string",
15
+ enum: ["create", "list", "view", "remove"],
16
+ description: "Action to perform",
17
+ },
18
+ name: {
19
+ type: "string",
20
+ description: "Team name",
21
+ },
22
+ description: {
23
+ type: "string",
24
+ description: "Team description (for create)",
25
+ },
26
+ agents: {
27
+ type: "array",
28
+ items: { type: "string" },
29
+ description: "Agent names in the team (for create)",
30
+ },
31
+ default_workflow: {
32
+ type: "string",
33
+ description: "Default workflow for this team (for create)",
34
+ },
35
+ },
36
+ required: ["action"],
37
+ },
38
+ },
39
+ async execute(input) {
40
+ const action = input.action as string;
41
+
42
+ switch (action) {
43
+ case "list": {
44
+ const teams = loadTeams();
45
+ if (teams.length === 0) return "No teams defined.";
46
+ return JSON.stringify(
47
+ teams.map((t) => ({
48
+ name: t.name,
49
+ description: t.description,
50
+ agents: t.agents,
51
+ defaultWorkflow: t.defaultWorkflow,
52
+ }))
53
+ );
54
+ }
55
+
56
+ case "view": {
57
+ const name = input.name as string;
58
+ if (!name) return JSON.stringify({ error: "name is required" });
59
+ const team = getTeam(name);
60
+ if (!team) return JSON.stringify({ error: `Team "${name}" not found` });
61
+ return JSON.stringify(team);
62
+ }
63
+
64
+ case "create": {
65
+ const name = input.name as string;
66
+ if (!name) return JSON.stringify({ error: "name is required" });
67
+ const team: AgentTeam = {
68
+ name,
69
+ description: (input.description as string) ?? "",
70
+ agents: (input.agents as string[]) ?? [],
71
+ defaultWorkflow: input.default_workflow as string | undefined,
72
+ };
73
+ const path = createTeam(team);
74
+ return JSON.stringify({ created: true, name, path });
75
+ }
76
+
77
+ case "remove": {
78
+ const name = input.name as string;
79
+ if (!name) return JSON.stringify({ error: "name is required" });
80
+ const removed = removeTeam(name);
81
+ return JSON.stringify({ removed, name });
82
+ }
83
+
84
+ default:
85
+ return JSON.stringify({ error: `Unknown action: ${action}` });
86
+ }
87
+ },
88
+ });
89
+ }
@@ -0,0 +1,94 @@
1
+ import { registerTool } from "../registry";
2
+ import { getDb } from "../../db/connection";
3
+
4
+ export function registerManageTriggersTool() {
5
+ registerTool({
6
+ definition: {
7
+ name: "manage_triggers",
8
+ description:
9
+ "Create, list, and remove proactive memory triggers. Triggers fire based on memory patterns and schedule.",
10
+ input_schema: {
11
+ type: "object",
12
+ properties: {
13
+ action: {
14
+ type: "string",
15
+ enum: ["create", "list", "remove"],
16
+ description: "Action to perform",
17
+ },
18
+ pattern: {
19
+ type: "string",
20
+ description: "Memory search pattern that triggers the action",
21
+ },
22
+ trigger_action: {
23
+ type: "string",
24
+ description: "What to do when the trigger fires (task for the agent)",
25
+ },
26
+ schedule: {
27
+ type: "string",
28
+ enum: ["once", "daily", "weekly", "monthly"],
29
+ description: "How often to check/fire (default: once)",
30
+ },
31
+ trigger_id: {
32
+ type: "number",
33
+ description: "Trigger ID (for remove)",
34
+ },
35
+ },
36
+ required: ["action"],
37
+ },
38
+ },
39
+ async execute(input) {
40
+ const action = input.action as string;
41
+ const db = getDb();
42
+
43
+ switch (action) {
44
+ case "list": {
45
+ try {
46
+ const triggers = db.query(
47
+ "SELECT * FROM memory_triggers ORDER BY id"
48
+ ).all();
49
+ return JSON.stringify(triggers);
50
+ } catch {
51
+ return JSON.stringify({ error: "Triggers table not available" });
52
+ }
53
+ }
54
+
55
+ case "create": {
56
+ const pattern = input.pattern as string;
57
+ const triggerAction = input.trigger_action as string;
58
+ const schedule = (input.schedule as string) ?? "once";
59
+
60
+ if (!pattern) return JSON.stringify({ error: "pattern is required" });
61
+ if (!triggerAction) return JSON.stringify({ error: "trigger_action is required" });
62
+
63
+ try {
64
+ const result = db.prepare(
65
+ "INSERT INTO memory_triggers (pattern, action, schedule) VALUES (?, ?, ?)"
66
+ ).run(pattern, triggerAction, schedule);
67
+ return JSON.stringify({
68
+ created: true,
69
+ id: Number(result.lastInsertRowid),
70
+ pattern,
71
+ schedule,
72
+ });
73
+ } catch (err: any) {
74
+ return JSON.stringify({ error: err.message });
75
+ }
76
+ }
77
+
78
+ case "remove": {
79
+ const triggerId = input.trigger_id as number;
80
+ if (!triggerId) return JSON.stringify({ error: "trigger_id is required" });
81
+ try {
82
+ db.prepare("DELETE FROM memory_triggers WHERE id = ?").run(triggerId);
83
+ return JSON.stringify({ removed: true, id: triggerId });
84
+ } catch (err: any) {
85
+ return JSON.stringify({ error: err.message });
86
+ }
87
+ }
88
+
89
+ default:
90
+ return JSON.stringify({ error: `Unknown action: ${action}` });
91
+ }
92
+ },
93
+ });
94
+ }
@@ -0,0 +1,119 @@
1
+ import { registerTool } from "../registry";
2
+ import {
3
+ loadWorkflowDefinitions,
4
+ getWorkflowDefinition,
5
+ createWorkflowDefinition,
6
+ removeWorkflowDefinition,
7
+ type WorkflowDefinition,
8
+ type WorkflowStep,
9
+ } from "../../agent/workflow";
10
+
11
+ export function registerManageWorkflowsTool() {
12
+ registerTool({
13
+ definition: {
14
+ name: "manage_workflows",
15
+ description:
16
+ "Create, list, view, and remove multi-agent workflows. Workflows orchestrate multiple agents in a dependency graph.",
17
+ input_schema: {
18
+ type: "object",
19
+ properties: {
20
+ action: {
21
+ type: "string",
22
+ enum: ["create", "list", "view", "remove"],
23
+ description: "Action to perform",
24
+ },
25
+ name: {
26
+ type: "string",
27
+ description: "Workflow name",
28
+ },
29
+ description: {
30
+ type: "string",
31
+ description: "Workflow description (for create)",
32
+ },
33
+ agents: {
34
+ type: "array",
35
+ items: { type: "string" },
36
+ description: "Agent names used in this workflow (for create)",
37
+ },
38
+ steps: {
39
+ type: "array",
40
+ items: {
41
+ type: "object",
42
+ properties: {
43
+ name: { type: "string" },
44
+ agent: { type: "string" },
45
+ task: { type: "string" },
46
+ dependsOn: { type: "array", items: { type: "string" } },
47
+ outputVar: { type: "string" },
48
+ },
49
+ },
50
+ description: "Workflow steps (for create)",
51
+ },
52
+ },
53
+ required: ["action"],
54
+ },
55
+ },
56
+ async execute(input) {
57
+ const action = input.action as string;
58
+
59
+ switch (action) {
60
+ case "list": {
61
+ const workflows = loadWorkflowDefinitions();
62
+ if (workflows.length === 0) return "No workflows defined.";
63
+ return JSON.stringify(
64
+ workflows.map((w) => ({
65
+ name: w.name,
66
+ description: w.description,
67
+ stepCount: w.steps.length,
68
+ agents: w.agents,
69
+ }))
70
+ );
71
+ }
72
+
73
+ case "view": {
74
+ const name = input.name as string;
75
+ if (!name) return JSON.stringify({ error: "name is required" });
76
+ const def = getWorkflowDefinition(name);
77
+ if (!def) return JSON.stringify({ error: `Workflow "${name}" not found` });
78
+ return JSON.stringify(def);
79
+ }
80
+
81
+ case "create": {
82
+ const name = input.name as string;
83
+ const description = (input.description as string) ?? "";
84
+ const agents = (input.agents as string[]) ?? [];
85
+ const steps = (input.steps as WorkflowStep[]) ?? [];
86
+
87
+ if (!name) return JSON.stringify({ error: "name is required" });
88
+ if (!steps.length) return JSON.stringify({ error: "at least one step is required" });
89
+
90
+ const def: WorkflowDefinition = {
91
+ name,
92
+ description,
93
+ agents,
94
+ steps: steps.map((s) => ({
95
+ name: s.name ?? "unnamed",
96
+ agent: s.agent ?? "main",
97
+ task: s.task ?? "",
98
+ dependsOn: s.dependsOn ?? [],
99
+ outputVar: s.outputVar,
100
+ })),
101
+ };
102
+
103
+ const path = createWorkflowDefinition(def);
104
+ return JSON.stringify({ created: true, name, path });
105
+ }
106
+
107
+ case "remove": {
108
+ const name = input.name as string;
109
+ if (!name) return JSON.stringify({ error: "name is required" });
110
+ const removed = removeWorkflowDefinition(name);
111
+ return JSON.stringify({ removed, name });
112
+ }
113
+
114
+ default:
115
+ return JSON.stringify({ error: `Unknown action: ${action}` });
116
+ }
117
+ },
118
+ });
119
+ }
@@ -0,0 +1,38 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { registerTool } from "../registry";
3
+ import { searchMemoryAsync } from "../../memory/engine";
4
+
5
+ export function registerMemorySearchTool(db: Database) {
6
+ registerTool({
7
+ definition: {
8
+ name: "memory_search",
9
+ description:
10
+ "Search persistent memory for previously stored information. Use this when the user asks about something you might have saved before, or when you need to recall facts about the user.",
11
+ input_schema: {
12
+ type: "object",
13
+ properties: {
14
+ query: {
15
+ type: "string",
16
+ description:
17
+ "Search query. Use natural language — both keyword and semantic search are used.",
18
+ },
19
+ },
20
+ required: ["query"],
21
+ },
22
+ },
23
+ execute: async (input) => {
24
+ const query = input.query as string;
25
+ const results = await searchMemoryAsync(db, query, 5);
26
+
27
+ if (results.length === 0) {
28
+ return "No relevant memories found.";
29
+ }
30
+
31
+ const formatted = results
32
+ .map((r, i) => `[${i + 1}] ${r.content}`)
33
+ .join("\n\n");
34
+
35
+ return `Found ${results.length} relevant memories:\n\n${formatted}`;
36
+ },
37
+ });
38
+ }
@@ -0,0 +1,30 @@
1
+ import { registerTool } from "../registry";
2
+ import { writeAndIndexMemory } from "../../memory/engine";
3
+ import { getDb } from "../../db/connection";
4
+
5
+ export function registerMemoryWriteTool() {
6
+ registerTool({
7
+ definition: {
8
+ name: "memory_write",
9
+ description:
10
+ "Save an important fact or piece of information to persistent memory. Use this when the user shares personal details, preferences, or any information worth remembering across conversations.",
11
+ input_schema: {
12
+ type: "object",
13
+ properties: {
14
+ content: {
15
+ type: "string",
16
+ description:
17
+ "The information to remember. Be specific and factual. Example: 'User's name is Thomas. He lives in Berlin.'",
18
+ },
19
+ },
20
+ required: ["content"],
21
+ },
22
+ },
23
+ execute: async (input) => {
24
+ const content = input.content as string;
25
+ const db = getDb();
26
+ await writeAndIndexMemory(db, content);
27
+ return `Saved to memory: "${content.slice(0, 100)}"`;
28
+ },
29
+ });
30
+ }