verybot 0.1.3

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.

Potentially problematic release.


This version of verybot might be problematic. Click here for more details.

Files changed (244) hide show
  1. package/dist/brain/agent-registry.d.ts +75 -0
  2. package/dist/brain/agent-registry.js +124 -0
  3. package/dist/brain/agent.d.ts +146 -0
  4. package/dist/brain/agent.js +680 -0
  5. package/dist/brain/channel-store.d.ts +27 -0
  6. package/dist/brain/channel-store.js +78 -0
  7. package/dist/brain/compaction.d.ts +37 -0
  8. package/dist/brain/compaction.js +214 -0
  9. package/dist/brain/context.d.ts +33 -0
  10. package/dist/brain/context.js +77 -0
  11. package/dist/brain/delegation-store.d.ts +33 -0
  12. package/dist/brain/delegation-store.js +106 -0
  13. package/dist/brain/loop.d.ts +21 -0
  14. package/dist/brain/loop.js +161 -0
  15. package/dist/brain/mcp-adapter.d.ts +39 -0
  16. package/dist/brain/mcp-adapter.js +227 -0
  17. package/dist/brain/memory-extractor.d.ts +26 -0
  18. package/dist/brain/memory-extractor.js +82 -0
  19. package/dist/brain/providers.d.ts +10 -0
  20. package/dist/brain/providers.js +69 -0
  21. package/dist/brain/queue.d.ts +18 -0
  22. package/dist/brain/queue.js +84 -0
  23. package/dist/brain/run-tools.d.ts +47 -0
  24. package/dist/brain/run-tools.js +84 -0
  25. package/dist/brain/session-key.d.ts +23 -0
  26. package/dist/brain/session-key.js +41 -0
  27. package/dist/brain/session-state.d.ts +36 -0
  28. package/dist/brain/session-state.js +51 -0
  29. package/dist/brain/session-store.d.ts +50 -0
  30. package/dist/brain/session-store.js +207 -0
  31. package/dist/brain/session.d.ts +32 -0
  32. package/dist/brain/session.js +75 -0
  33. package/dist/brain/utils.d.ts +4 -0
  34. package/dist/brain/utils.js +26 -0
  35. package/dist/brain/worker-coordinator.d.ts +25 -0
  36. package/dist/brain/worker-coordinator.js +83 -0
  37. package/dist/channels/commands.d.ts +35 -0
  38. package/dist/channels/commands.js +65 -0
  39. package/dist/channels/discord/channel.d.ts +18 -0
  40. package/dist/channels/discord/channel.js +154 -0
  41. package/dist/channels/discord/markdown.d.ts +19 -0
  42. package/dist/channels/discord/markdown.js +62 -0
  43. package/dist/channels/manager.d.ts +29 -0
  44. package/dist/channels/manager.js +100 -0
  45. package/dist/channels/slack/channel.d.ts +26 -0
  46. package/dist/channels/slack/channel.js +207 -0
  47. package/dist/channels/slack/markdown.d.ts +19 -0
  48. package/dist/channels/slack/markdown.js +62 -0
  49. package/dist/channels/specs.d.ts +21 -0
  50. package/dist/channels/specs.js +96 -0
  51. package/dist/channels/telegram/channel.d.ts +18 -0
  52. package/dist/channels/telegram/channel.js +156 -0
  53. package/dist/channels/telegram/markdown.d.ts +17 -0
  54. package/dist/channels/telegram/markdown.js +66 -0
  55. package/dist/channels/types.d.ts +26 -0
  56. package/dist/channels/types.js +1 -0
  57. package/dist/channels/whatsapp/channel.d.ts +23 -0
  58. package/dist/channels/whatsapp/channel.js +242 -0
  59. package/dist/channels/whatsapp/markdown.d.ts +20 -0
  60. package/dist/channels/whatsapp/markdown.js +51 -0
  61. package/dist/cli/config.d.ts +5 -0
  62. package/dist/cli/config.js +78 -0
  63. package/dist/cli/index.d.ts +5 -0
  64. package/dist/cli/index.js +13 -0
  65. package/dist/computer/browser/actions.d.ts +31 -0
  66. package/dist/computer/browser/actions.js +148 -0
  67. package/dist/computer/browser/manager.d.ts +55 -0
  68. package/dist/computer/browser/manager.js +496 -0
  69. package/dist/computer/browser/profile-badge.d.ts +13 -0
  70. package/dist/computer/browser/profile-badge.js +67 -0
  71. package/dist/computer/browser/screenshot.d.ts +5 -0
  72. package/dist/computer/browser/screenshot.js +21 -0
  73. package/dist/computer/browser/snapshot.d.ts +30 -0
  74. package/dist/computer/browser/snapshot.js +242 -0
  75. package/dist/computer/browser/tools.d.ts +5 -0
  76. package/dist/computer/browser/tools.js +167 -0
  77. package/dist/computer/desktop/adapter.d.ts +25 -0
  78. package/dist/computer/desktop/adapter.js +11 -0
  79. package/dist/computer/desktop/macos.d.ts +24 -0
  80. package/dist/computer/desktop/macos.js +223 -0
  81. package/dist/computer/desktop/tools.d.ts +25 -0
  82. package/dist/computer/desktop/tools.js +114 -0
  83. package/dist/config/agent-config.d.ts +41 -0
  84. package/dist/config/agent-config.js +14 -0
  85. package/dist/config/model-catalog.d.ts +22 -0
  86. package/dist/config/model-catalog.js +99 -0
  87. package/dist/config/store.d.ts +25 -0
  88. package/dist/config/store.js +143 -0
  89. package/dist/config.d.ts +103 -0
  90. package/dist/config.js +224 -0
  91. package/dist/control-ui/assets/index-BANXNUyt.js +143 -0
  92. package/dist/control-ui/assets/index-BSUFrP9R.css +1 -0
  93. package/dist/control-ui/assets/noto-sans-cyrillic-ext-wght-normal-DSNfmdVt.woff2 +0 -0
  94. package/dist/control-ui/assets/noto-sans-cyrillic-wght-normal-B2hlT84T.woff2 +0 -0
  95. package/dist/control-ui/assets/noto-sans-devanagari-wght-normal-Cv-Vwajv.woff2 +0 -0
  96. package/dist/control-ui/assets/noto-sans-greek-ext-wght-normal-12T8GTDR.woff2 +0 -0
  97. package/dist/control-ui/assets/noto-sans-greek-wght-normal-Ymb6dZNd.woff2 +0 -0
  98. package/dist/control-ui/assets/noto-sans-latin-ext-wght-normal-W1qJv59z.woff2 +0 -0
  99. package/dist/control-ui/assets/noto-sans-latin-wght-normal-BYSzYMf3.woff2 +0 -0
  100. package/dist/control-ui/assets/noto-sans-vietnamese-wght-normal-DLTJy58D.woff2 +0 -0
  101. package/dist/control-ui/index.html +14 -0
  102. package/dist/control-ui/vite.svg +1 -0
  103. package/dist/events.d.ts +2 -0
  104. package/dist/events.js +11 -0
  105. package/dist/gateway/broadcast.d.ts +5 -0
  106. package/dist/gateway/broadcast.js +33 -0
  107. package/dist/gateway/methods/chat.d.ts +24 -0
  108. package/dist/gateway/methods/chat.js +19 -0
  109. package/dist/gateway/methods/config.d.ts +13 -0
  110. package/dist/gateway/methods/config.js +14 -0
  111. package/dist/gateway/methods/models.d.ts +10 -0
  112. package/dist/gateway/methods/models.js +14 -0
  113. package/dist/gateway/methods/prompt-templates.d.ts +23 -0
  114. package/dist/gateway/methods/prompt-templates.js +82 -0
  115. package/dist/gateway/methods/scheduler.d.ts +62 -0
  116. package/dist/gateway/methods/scheduler.js +129 -0
  117. package/dist/gateway/methods/sessions.d.ts +26 -0
  118. package/dist/gateway/methods/sessions.js +54 -0
  119. package/dist/gateway/methods/skills.d.ts +35 -0
  120. package/dist/gateway/methods/skills.js +202 -0
  121. package/dist/gateway/methods/system.d.ts +12 -0
  122. package/dist/gateway/methods/system.js +39 -0
  123. package/dist/gateway/methods/tasks.d.ts +21 -0
  124. package/dist/gateway/methods/tasks.js +46 -0
  125. package/dist/gateway/methods/teams.d.ts +70 -0
  126. package/dist/gateway/methods/teams.js +374 -0
  127. package/dist/gateway/methods/tools.d.ts +6 -0
  128. package/dist/gateway/methods/tools.js +7 -0
  129. package/dist/gateway/methods/whatsapp.d.ts +19 -0
  130. package/dist/gateway/methods/whatsapp.js +35 -0
  131. package/dist/gateway/rpc.d.ts +38 -0
  132. package/dist/gateway/rpc.js +75 -0
  133. package/dist/gateway/server.d.ts +4 -0
  134. package/dist/gateway/server.js +133 -0
  135. package/dist/index.d.ts +1 -0
  136. package/dist/index.js +212 -0
  137. package/dist/integrations/github.d.ts +7 -0
  138. package/dist/integrations/github.js +133 -0
  139. package/dist/integrations/mcp.d.ts +7 -0
  140. package/dist/integrations/mcp.js +106 -0
  141. package/dist/integrations/registry.d.ts +43 -0
  142. package/dist/integrations/registry.js +258 -0
  143. package/dist/integrations/scanner.d.ts +10 -0
  144. package/dist/integrations/scanner.js +122 -0
  145. package/dist/integrations/twitter.d.ts +10 -0
  146. package/dist/integrations/twitter.js +120 -0
  147. package/dist/integrations/types.d.ts +72 -0
  148. package/dist/integrations/types.js +1 -0
  149. package/dist/logger.d.ts +16 -0
  150. package/dist/logger.js +104 -0
  151. package/dist/markdown/chunk.d.ts +9 -0
  152. package/dist/markdown/chunk.js +52 -0
  153. package/dist/markdown/ir.d.ts +37 -0
  154. package/dist/markdown/ir.js +529 -0
  155. package/dist/markdown/render.d.ts +22 -0
  156. package/dist/markdown/render.js +148 -0
  157. package/dist/markdown/table-render.d.ts +43 -0
  158. package/dist/markdown/table-render.js +219 -0
  159. package/dist/markdown/tables.d.ts +17 -0
  160. package/dist/markdown/tables.js +27 -0
  161. package/dist/memory/embedding.d.ts +16 -0
  162. package/dist/memory/embedding.js +66 -0
  163. package/dist/memory/extractor.d.ts +6 -0
  164. package/dist/memory/extractor.js +72 -0
  165. package/dist/memory/search.d.ts +15 -0
  166. package/dist/memory/search.js +57 -0
  167. package/dist/memory/store.d.ts +34 -0
  168. package/dist/memory/store.js +328 -0
  169. package/dist/memory/types.d.ts +9 -0
  170. package/dist/memory/types.js +2 -0
  171. package/dist/paths.d.ts +20 -0
  172. package/dist/paths.js +29 -0
  173. package/dist/prompt-templates/builtins.d.ts +2 -0
  174. package/dist/prompt-templates/builtins.js +72 -0
  175. package/dist/prompt-templates/store.d.ts +39 -0
  176. package/dist/prompt-templates/store.js +174 -0
  177. package/dist/prompt-templates/types.d.ts +10 -0
  178. package/dist/prompt-templates/types.js +1 -0
  179. package/dist/scheduler/connected-channels.d.ts +24 -0
  180. package/dist/scheduler/connected-channels.js +57 -0
  181. package/dist/scheduler/scheduler.d.ts +22 -0
  182. package/dist/scheduler/scheduler.js +132 -0
  183. package/dist/scheduler/store.d.ts +27 -0
  184. package/dist/scheduler/store.js +205 -0
  185. package/dist/scheduler/types.d.ts +29 -0
  186. package/dist/scheduler/types.js +1 -0
  187. package/dist/security/command-validator.d.ts +22 -0
  188. package/dist/security/command-validator.js +160 -0
  189. package/dist/security/docker-sandbox.d.ts +48 -0
  190. package/dist/security/docker-sandbox.js +218 -0
  191. package/dist/security/env-filter.d.ts +8 -0
  192. package/dist/security/env-filter.js +41 -0
  193. package/dist/skills/loader.d.ts +33 -0
  194. package/dist/skills/loader.js +132 -0
  195. package/dist/skills/prompt.d.ts +6 -0
  196. package/dist/skills/prompt.js +17 -0
  197. package/dist/skills/read-tool.d.ts +7 -0
  198. package/dist/skills/read-tool.js +24 -0
  199. package/dist/skills/scanner.d.ts +6 -0
  200. package/dist/skills/scanner.js +73 -0
  201. package/dist/skills/types.d.ts +15 -0
  202. package/dist/skills/types.js +1 -0
  203. package/dist/tasks/store.d.ts +47 -0
  204. package/dist/tasks/store.js +193 -0
  205. package/dist/tasks/types.d.ts +75 -0
  206. package/dist/tasks/types.js +32 -0
  207. package/dist/teams/store.d.ts +78 -0
  208. package/dist/teams/store.js +420 -0
  209. package/dist/teams/types.d.ts +23 -0
  210. package/dist/teams/types.js +1 -0
  211. package/dist/tools/bash.d.ts +16 -0
  212. package/dist/tools/bash.js +62 -0
  213. package/dist/tools/channel-history.d.ts +10 -0
  214. package/dist/tools/channel-history.js +43 -0
  215. package/dist/tools/delegate.d.ts +16 -0
  216. package/dist/tools/delegate.js +216 -0
  217. package/dist/tools/fs.d.ts +4 -0
  218. package/dist/tools/fs.js +335 -0
  219. package/dist/tools/integration-toggle.d.ts +14 -0
  220. package/dist/tools/integration-toggle.js +47 -0
  221. package/dist/tools/memory.d.ts +13 -0
  222. package/dist/tools/memory.js +65 -0
  223. package/dist/tools/registry.d.ts +6 -0
  224. package/dist/tools/registry.js +9 -0
  225. package/dist/tools/schedule.d.ts +8 -0
  226. package/dist/tools/schedule.js +219 -0
  227. package/dist/tools/speak.d.ts +10 -0
  228. package/dist/tools/speak.js +56 -0
  229. package/dist/tools/tasks.d.ts +29 -0
  230. package/dist/tools/tasks.js +92 -0
  231. package/dist/tools/teams.d.ts +7 -0
  232. package/dist/tools/teams.js +180 -0
  233. package/dist/tools/web-fetch.d.ts +3 -0
  234. package/dist/tools/web-fetch.js +22 -0
  235. package/dist/tts/edge.d.ts +10 -0
  236. package/dist/tts/edge.js +60 -0
  237. package/dist/tts/speak.d.ts +12 -0
  238. package/dist/tts/speak.js +81 -0
  239. package/dist/tts/transcribe.d.ts +5 -0
  240. package/dist/tts/transcribe.js +40 -0
  241. package/dist/utils.d.ts +5 -0
  242. package/dist/utils.js +22 -0
  243. package/package.json +90 -0
  244. package/verybot.js +2 -0
@@ -0,0 +1,129 @@
1
+ import { broadcast, onClientClose } from "../broadcast.js";
2
+ const VALID_STATUSES = new Set(["active", "paused", "completed", "failed"]);
3
+ /** Validate that `teamId` belongs to a real team. */
4
+ function validateTeamId(agent, teamId) {
5
+ if (!teamId)
6
+ throw new Error("Missing teamId");
7
+ const teams = agent.getTeams();
8
+ if (!teams.some((t) => t.id === teamId)) {
9
+ throw new Error(`Unknown team: "${teamId}"`);
10
+ }
11
+ }
12
+ export function schedulerMethods(getAgent, getScheduleStore, getConnectedChannels, ctx) {
13
+ return {
14
+ /** Connect a WS client to a team's scheduler. Returns session history + connection key. */
15
+ "scheduler.connect": async (params) => {
16
+ const { teamId } = params;
17
+ validateTeamId(getAgent(), teamId);
18
+ const connectedChannels = getConnectedChannels();
19
+ // Register this WS as a connected channel — send fn broadcasts via WS events
20
+ const connectionKey = connectedChannels.connect(teamId, {
21
+ channelType: "gateway",
22
+ channelId: `ws:${teamId}`,
23
+ send: async (text) => {
24
+ broadcast("schedulerMessage", { teamId, role: "assistant", content: text });
25
+ },
26
+ });
27
+ // Auto-disconnect when the WS closes (prevents stale entries)
28
+ if (ctx?.ws) {
29
+ onClientClose(ctx.ws, () => {
30
+ connectedChannels.disconnect(teamId, connectionKey);
31
+ });
32
+ }
33
+ // Return existing session history
34
+ const session = getAgent().getSchedulerSession(teamId);
35
+ const messages = session?.getMessages() ?? [];
36
+ return { connectionKey, messages };
37
+ },
38
+ /** Disconnect a WS client from a team's scheduler. */
39
+ "scheduler.disconnect": async (params) => {
40
+ const { teamId, connectionKey } = params;
41
+ if (!teamId || !connectionKey)
42
+ throw new Error("Missing teamId or connectionKey");
43
+ getConnectedChannels().disconnect(teamId, connectionKey);
44
+ return { status: "ok" };
45
+ },
46
+ /** Send a message to the team's scheduler session. */
47
+ "scheduler.send": async (params) => {
48
+ const { teamId, message } = params;
49
+ if (!message)
50
+ throw new Error("Missing message");
51
+ validateTeamId(getAgent(), teamId);
52
+ // Broadcast user message to OTHER clients (sender already has it via optimistic UI)
53
+ const excludeWs = ctx?.ws;
54
+ broadcast("schedulerMessage", { teamId, role: "user", content: message, senderInfo: "Web UI" }, excludeWs);
55
+ const reply = await getAgent().handleSchedulerMessage(teamId, message, "Web UI");
56
+ // Broadcast reply to OTHER clients (sender gets it via RPC response)
57
+ broadcast("schedulerMessage", { teamId, role: "assistant", content: reply }, excludeWs);
58
+ return { status: "ok", reply };
59
+ },
60
+ /** Get scheduler session history for a team. */
61
+ "scheduler.history": async (params) => {
62
+ const { teamId } = params;
63
+ validateTeamId(getAgent(), teamId);
64
+ const session = getAgent().getSchedulerSession(teamId);
65
+ return { messages: session?.getMessages() ?? [] };
66
+ },
67
+ /** List schedules for a team. */
68
+ "scheduler.list": async (params) => {
69
+ const { teamId, status } = params;
70
+ validateTeamId(getAgent(), teamId);
71
+ if (status && !VALID_STATUSES.has(status)) {
72
+ throw new Error(`Invalid status: "${status}". Must be one of: active, paused, completed, failed`);
73
+ }
74
+ const store = getScheduleStore();
75
+ const schedules = store.listByTeam(teamId, status);
76
+ return { schedules };
77
+ },
78
+ // --- Direct schedule CRUD (bypasses LLM) ---
79
+ /** Pause an active schedule. */
80
+ "scheduler.pause": async (params) => {
81
+ const { teamId, scheduleId } = params;
82
+ if (!scheduleId)
83
+ throw new Error("Missing scheduleId");
84
+ validateTeamId(getAgent(), teamId);
85
+ const store = getScheduleStore();
86
+ const schedule = store.getById(scheduleId);
87
+ if (!schedule || schedule.teamId !== teamId) {
88
+ throw new Error(`Schedule "${scheduleId}" not found for this team`);
89
+ }
90
+ if (schedule.status !== "active") {
91
+ throw new Error(`Schedule is not active (status: ${schedule.status})`);
92
+ }
93
+ store.update(scheduleId, { status: "paused" });
94
+ return { status: "ok" };
95
+ },
96
+ /** Resume a paused schedule. */
97
+ "scheduler.resume": async (params) => {
98
+ const { teamId, scheduleId } = params;
99
+ if (!scheduleId)
100
+ throw new Error("Missing scheduleId");
101
+ validateTeamId(getAgent(), teamId);
102
+ const store = getScheduleStore();
103
+ const schedule = store.getById(scheduleId);
104
+ if (!schedule || schedule.teamId !== teamId) {
105
+ throw new Error(`Schedule "${scheduleId}" not found for this team`);
106
+ }
107
+ if (schedule.status !== "paused") {
108
+ throw new Error(`Schedule is not paused (status: ${schedule.status})`);
109
+ }
110
+ const nextRun = store.computeNextRun(schedule);
111
+ store.update(scheduleId, { status: "active", nextRun });
112
+ return { status: "ok" };
113
+ },
114
+ /** Delete a schedule. */
115
+ "scheduler.delete": async (params) => {
116
+ const { teamId, scheduleId } = params;
117
+ if (!scheduleId)
118
+ throw new Error("Missing scheduleId");
119
+ validateTeamId(getAgent(), teamId);
120
+ const store = getScheduleStore();
121
+ const schedule = store.getById(scheduleId);
122
+ if (!schedule || schedule.teamId !== teamId) {
123
+ throw new Error(`Schedule "${scheduleId}" not found for this team`);
124
+ }
125
+ store.delete(scheduleId);
126
+ return { status: "ok" };
127
+ },
128
+ };
129
+ }
@@ -0,0 +1,26 @@
1
+ import type { Agent } from "../../brain/agent.js";
2
+ export declare function sessionMethods(getAgent: () => Agent): {
3
+ "sessions.list": () => Promise<{
4
+ sessions: import("../../brain/session-store.js").SessionIndexEntry[];
5
+ ts: number;
6
+ }>;
7
+ "sessions.get": (params: {
8
+ sessionKey: string;
9
+ }) => Promise<{
10
+ messages: {
11
+ role: "system" | "user" | "assistant" | "tool";
12
+ content: string;
13
+ }[];
14
+ }>;
15
+ "sessions.rename": (params: {
16
+ sessionKey: string;
17
+ title: string;
18
+ }) => Promise<{
19
+ status: string;
20
+ }>;
21
+ "sessions.clear": (params: {
22
+ sessionKey: string;
23
+ }) => Promise<{
24
+ status: string;
25
+ }>;
26
+ };
@@ -0,0 +1,54 @@
1
+ import { logger } from "../../logger.js";
2
+ export function sessionMethods(getAgent) {
3
+ return {
4
+ "sessions.list": async () => {
5
+ const agent = getAgent();
6
+ const sessions = agent.getStore().list();
7
+ // Enrich sessions with team name + color from the team store
8
+ const teams = agent.getTeams();
9
+ const teamById = new Map(teams.map((t) => [t.id, t]));
10
+ const enriched = sessions.map((s) => {
11
+ const team = s.teamId ? teamById.get(s.teamId) : undefined;
12
+ if (!team)
13
+ return s;
14
+ return {
15
+ ...s,
16
+ ...(team.name && { teamName: team.name }),
17
+ ...(team.color && { teamColor: team.color }),
18
+ };
19
+ });
20
+ return { sessions: enriched, ts: Date.now() };
21
+ },
22
+ "sessions.get": async (params) => {
23
+ if (!params?.sessionKey || typeof params.sessionKey !== "string") {
24
+ throw new Error("sessionKey is required and must be a string");
25
+ }
26
+ const session = await getAgent().getStore().load(params.sessionKey);
27
+ if (!session)
28
+ return { messages: [] };
29
+ const messages = session.getMessages().map((m) => ({
30
+ role: m.role,
31
+ content: typeof m.content === "string" ? m.content : JSON.stringify(m.content),
32
+ }));
33
+ return { messages };
34
+ },
35
+ "sessions.rename": async (params) => {
36
+ if (!params?.sessionKey || typeof params.sessionKey !== "string") {
37
+ throw new Error("sessionKey is required and must be a string");
38
+ }
39
+ if (!params?.title || typeof params.title !== "string") {
40
+ throw new Error("title is required and must be a string");
41
+ }
42
+ const ok = getAgent().getStore().rename(params.sessionKey, params.title);
43
+ if (!ok)
44
+ return { status: "not_found" };
45
+ logger.info(`[${params.sessionKey}] Session renamed to: ${params.title}`);
46
+ return { status: "ok" };
47
+ },
48
+ "sessions.clear": async (params) => {
49
+ logger.info(`[${params.sessionKey}] Session clear requested`);
50
+ await getAgent().clearSession(params.sessionKey);
51
+ return { status: "ok" };
52
+ },
53
+ };
54
+ }
@@ -0,0 +1,35 @@
1
+ import type { SkillManager } from "../../skills/loader.js";
2
+ export declare function skillMethods(getSkillManager: () => SkillManager): {
3
+ /** List all skills (without content). */
4
+ "skills.list": () => Promise<{
5
+ skills: {
6
+ name: string;
7
+ description: string;
8
+ icon: string | undefined;
9
+ tools: string[];
10
+ }[];
11
+ }>;
12
+ /** Get a single skill by name (includes content). */
13
+ "skills.get": (params: unknown) => Promise<{
14
+ skill: {
15
+ name: string;
16
+ description: string;
17
+ icon: string | undefined;
18
+ tools: string[];
19
+ content: string;
20
+ };
21
+ }>;
22
+ /** Create or update a skill on disk. Supports rename via `originalName`. */
23
+ "skills.save": (params: unknown) => Promise<{
24
+ status: string;
25
+ }>;
26
+ /** Delete a skill directory from disk. */
27
+ "skills.delete": (params: unknown) => Promise<{
28
+ status: string;
29
+ }>;
30
+ /** Import skills from ~/.claude/ (plugins cache + user skills). */
31
+ "skills.importClaude": () => Promise<{
32
+ imported: string[];
33
+ skipped: string[];
34
+ }>;
35
+ };
@@ -0,0 +1,202 @@
1
+ import { mkdir, writeFile, rm, readdir, stat, cp } from "fs/promises";
2
+ import { basename, resolve, join } from "path";
3
+ import { homedir } from "os";
4
+ import { SKILLS_DIR } from "../../paths.js";
5
+ import { emit } from "../../events.js";
6
+ import { logger } from "../../logger.js";
7
+ /* ------------------------------------------------------------------ */
8
+ /* Validation helpers */
9
+ /* ------------------------------------------------------------------ */
10
+ /** Alphanumeric, hyphens, underscores, max 64 chars. */
11
+ const VALID_SKILL_NAME = /^[a-zA-Z0-9][a-zA-Z0-9 _-]{0,63}$/;
12
+ /** Resolve a safe skill directory path, rejecting traversal attempts. */
13
+ function safeSkillDir(name) {
14
+ const sanitized = basename(name.trim());
15
+ if (!sanitized || sanitized === "." || sanitized === "..") {
16
+ throw new Error("Invalid skill name");
17
+ }
18
+ if (!VALID_SKILL_NAME.test(sanitized)) {
19
+ throw new Error("Skill name must be alphanumeric (hyphens/underscores allowed, max 64 chars)");
20
+ }
21
+ const target = resolve(SKILLS_DIR, sanitized);
22
+ const parent = resolve(SKILLS_DIR);
23
+ if (!target.startsWith(`${parent}/`)) {
24
+ throw new Error("Invalid skill name");
25
+ }
26
+ return target;
27
+ }
28
+ /** Escape a string for safe YAML value output. */
29
+ function yamlEscape(val) {
30
+ if (/[:\n\r#"'\[\]{}|>&*!%@`]/.test(val) || val.trim() !== val) {
31
+ return `"${val.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
32
+ }
33
+ return val;
34
+ }
35
+ /** Build YAML frontmatter string from skill metadata. */
36
+ function buildFrontmatter(p) {
37
+ const lines = ["---"];
38
+ lines.push(`name: ${yamlEscape(p.name)}`);
39
+ lines.push(`description: ${yamlEscape(p.description)}`);
40
+ if (p.icon)
41
+ lines.push(`icon: ${yamlEscape(p.icon)}`);
42
+ if (p.tools.length > 0)
43
+ lines.push(`tools: [${p.tools.map(yamlEscape).join(", ")}]`);
44
+ lines.push("---");
45
+ return lines.join("\n");
46
+ }
47
+ /* ------------------------------------------------------------------ */
48
+ /* RPC methods */
49
+ /* ------------------------------------------------------------------ */
50
+ export function skillMethods(getSkillManager) {
51
+ return {
52
+ /** List all skills (without content). */
53
+ "skills.list": async () => {
54
+ const entries = getSkillManager().entries;
55
+ return {
56
+ skills: entries.map((e) => ({
57
+ name: e.name,
58
+ description: e.description,
59
+ icon: e.icon,
60
+ tools: e.tools,
61
+ })),
62
+ };
63
+ },
64
+ /** Get a single skill by name (includes content). */
65
+ "skills.get": async (params) => {
66
+ const { name } = (params ?? {});
67
+ if (!name || typeof name !== "string")
68
+ throw new Error("name is required");
69
+ const entry = getSkillManager().entries.find((e) => e.name === name);
70
+ if (!entry)
71
+ throw new Error(`Skill not found: ${name}`);
72
+ return {
73
+ skill: {
74
+ name: entry.name,
75
+ description: entry.description,
76
+ icon: entry.icon,
77
+ tools: entry.tools,
78
+ content: entry.content,
79
+ },
80
+ };
81
+ },
82
+ /** Create or update a skill on disk. Supports rename via `originalName`. */
83
+ "skills.save": async (params) => {
84
+ const p = (params ?? {});
85
+ if (!p.name || typeof p.name !== "string")
86
+ throw new Error("name is required");
87
+ if (!p.content || typeof p.content !== "string")
88
+ throw new Error("content is required");
89
+ const name = p.name.trim();
90
+ if (name.length === 0)
91
+ throw new Error("name cannot be empty");
92
+ const skillDir = safeSkillDir(name);
93
+ // Check uniqueness (case-insensitive, matching scanner dedup logic)
94
+ const originalName = typeof p.originalName === "string" ? p.originalName.trim() : "";
95
+ const isRename = originalName && originalName !== name;
96
+ const isCreate = !originalName;
97
+ if (isCreate || isRename) {
98
+ const conflict = getSkillManager().entries.find((e) => e.name.toLowerCase() === name.toLowerCase());
99
+ if (conflict)
100
+ throw new Error(`A skill named "${conflict.name}" already exists`);
101
+ }
102
+ // If renamed, delete the old skill directory first
103
+ if (isRename) {
104
+ const oldDir = safeSkillDir(originalName);
105
+ await rm(oldDir, { recursive: true, force: true });
106
+ }
107
+ const description = typeof p.description === "string" ? p.description.replace(/[\r\n]/g, " ") : "";
108
+ const icon = typeof p.icon === "string" && p.icon.length > 0 ? p.icon : undefined;
109
+ const tools = Array.isArray(p.tools) ? p.tools.filter((t) => typeof t === "string") : [];
110
+ const frontmatter = buildFrontmatter({ name, description, icon, tools });
111
+ const fileContent = `${frontmatter}\n${p.content}`;
112
+ await mkdir(skillDir, { recursive: true });
113
+ await writeFile(`${skillDir}/SKILL.md`, fileContent, "utf-8");
114
+ // Rescan so in-memory entries are consistent before broadcasting
115
+ await getSkillManager().scan();
116
+ emit("skillChange", { action: "saved", name });
117
+ return { status: "ok" };
118
+ },
119
+ /** Delete a skill directory from disk. */
120
+ "skills.delete": async (params) => {
121
+ const { name } = (params ?? {});
122
+ if (!name || typeof name !== "string")
123
+ throw new Error("name is required");
124
+ const skillDir = safeSkillDir(name);
125
+ await rm(skillDir, { recursive: true, force: true });
126
+ // Rescan so in-memory entries are consistent before broadcasting
127
+ await getSkillManager().scan();
128
+ emit("skillChange", { action: "deleted", name });
129
+ return { status: "ok" };
130
+ },
131
+ /** Import skills from ~/.claude/ (plugins cache + user skills). */
132
+ "skills.importClaude": async () => {
133
+ const claudeDir = join(homedir(), ".claude");
134
+ const found = await findSkillFiles(claudeDir);
135
+ if (found.length === 0)
136
+ return { imported: [], skipped: [] };
137
+ const existing = new Set(getSkillManager().entries.map((e) => e.name.toLowerCase()));
138
+ const imported = [];
139
+ const skipped = [];
140
+ for (const skillDir of found) {
141
+ const name = basename(skillDir);
142
+ if (existing.has(name.toLowerCase())) {
143
+ skipped.push(name);
144
+ continue;
145
+ }
146
+ try {
147
+ const destDir = resolve(SKILLS_DIR, name);
148
+ await cp(skillDir, destDir, { recursive: true });
149
+ imported.push(name);
150
+ existing.add(name.toLowerCase());
151
+ }
152
+ catch (err) {
153
+ logger.warn(`Failed to import Claude skill "${name}": ${err instanceof Error ? err.message : err}`);
154
+ skipped.push(name);
155
+ }
156
+ }
157
+ if (imported.length > 0) {
158
+ await getSkillManager().scan();
159
+ emit("skillChange", { action: "saved", name: imported.join(", ") });
160
+ }
161
+ return { imported, skipped };
162
+ },
163
+ };
164
+ }
165
+ /**
166
+ * Recursively find directories containing SKILL.md under a root path.
167
+ * Returns the parent directories (skill dirs to copy).
168
+ */
169
+ async function findSkillFiles(root) {
170
+ const results = [];
171
+ async function walk(dir, depth) {
172
+ const MAX_DEPTH = 8;
173
+ if (depth > MAX_DEPTH)
174
+ return;
175
+ let entries;
176
+ try {
177
+ entries = await readdir(dir);
178
+ }
179
+ catch {
180
+ return;
181
+ }
182
+ for (const entry of entries) {
183
+ if (entry === "node_modules" || entry.startsWith("."))
184
+ continue;
185
+ const full = join(dir, entry);
186
+ const s = await stat(full).catch(() => null);
187
+ if (!s?.isDirectory())
188
+ continue;
189
+ // Check if this directory has a SKILL.md
190
+ const skillStat = await stat(join(full, "SKILL.md")).catch(() => null);
191
+ if (skillStat?.isFile()) {
192
+ results.push(full);
193
+ }
194
+ else {
195
+ // No SKILL.md here, recurse deeper
196
+ await walk(full, depth + 1);
197
+ }
198
+ }
199
+ }
200
+ await walk(root, 0);
201
+ return results;
202
+ }
@@ -0,0 +1,12 @@
1
+ import { type LogEntry } from "../../logger.js";
2
+ import type { RpcContext } from "../rpc.js";
3
+ export declare function systemMethods(restart: () => Promise<void>, ctx?: RpcContext): {
4
+ /** Hot-restart all backend components. WebSocket connections stay alive. */
5
+ "system.restart": () => Promise<{
6
+ status: string;
7
+ }>;
8
+ /** Return buffered log history and subscribe the caller to real-time log events. */
9
+ "system.logs.subscribe": () => Promise<{
10
+ logs: LogEntry[];
11
+ }>;
12
+ };
@@ -0,0 +1,39 @@
1
+ import { emit } from "../../events.js";
2
+ import { onClientClose } from "../broadcast.js";
3
+ import { logger, getLogBuffer, addLogSubscriber } from "../../logger.js";
4
+ export function systemMethods(restart, ctx) {
5
+ return {
6
+ /** Hot-restart all backend components. WebSocket connections stay alive. */
7
+ "system.restart": async () => {
8
+ logger.info("Hot restart requested via RPC");
9
+ emit("system", { action: "restarting" });
10
+ // Run restart in background so the RPC response is sent first
11
+ setTimeout(async () => {
12
+ try {
13
+ await restart();
14
+ emit("system", { action: "restarted" });
15
+ logger.info("Hot restart complete");
16
+ }
17
+ catch (err) {
18
+ logger.error(`Hot restart failed: ${err}`);
19
+ emit("system", { action: "restart_failed", error: String(err) });
20
+ }
21
+ }, 0);
22
+ return { status: "restarting" };
23
+ },
24
+ /** Return buffered log history and subscribe the caller to real-time log events. */
25
+ "system.logs.subscribe": async () => {
26
+ const ws = ctx?.ws;
27
+ if (ws) {
28
+ const send = (entry) => {
29
+ if (ws.readyState === ws.OPEN) {
30
+ ws.send(JSON.stringify({ type: "event", event: "log", payload: entry }));
31
+ }
32
+ };
33
+ const unsub = addLogSubscriber(send);
34
+ onClientClose(ws, unsub);
35
+ }
36
+ return { logs: getLogBuffer() };
37
+ },
38
+ };
39
+ }
@@ -0,0 +1,21 @@
1
+ import type { TaskStore } from "../../tasks/store.js";
2
+ export declare function taskMethods(taskStore: TaskStore): {
3
+ "tasks.list": (params: unknown) => Promise<{
4
+ tasks: import("../../tasks/types.js").Task[];
5
+ }>;
6
+ "tasks.create": (params: unknown) => Promise<{
7
+ task: import("../../tasks/types.js").Task;
8
+ }>;
9
+ "tasks.update": (params: unknown) => Promise<{
10
+ task: import("../../tasks/types.js").Task;
11
+ }>;
12
+ "tasks.delete": (params: unknown) => Promise<{
13
+ status: string;
14
+ }>;
15
+ "tasks.reorder": (params: unknown) => Promise<{
16
+ status: string;
17
+ }>;
18
+ "tasks.archiveDone": (params: unknown) => Promise<{
19
+ archived: number;
20
+ }>;
21
+ };
@@ -0,0 +1,46 @@
1
+ import { emit } from "../../events.js";
2
+ import { CreateTaskSchema, UpdateTaskSchema, ListTasksSchema, ReorderTasksSchema } from "../../tasks/types.js";
3
+ export function taskMethods(taskStore) {
4
+ return {
5
+ "tasks.list": async (params) => {
6
+ const filter = ListTasksSchema.parse(params ?? {});
7
+ return { tasks: taskStore.list(filter) };
8
+ },
9
+ "tasks.create": async (params) => {
10
+ const input = CreateTaskSchema.parse(params);
11
+ const task = taskStore.create(input);
12
+ emit("taskChange", { action: "created", task });
13
+ return { task };
14
+ },
15
+ "tasks.update": async (params) => {
16
+ const { id, ...updates } = UpdateTaskSchema.parse(params);
17
+ const task = taskStore.update(id, updates);
18
+ if (!task)
19
+ throw new Error(`Task not found: ${id}`);
20
+ emit("taskChange", { action: "updated", task });
21
+ return { task };
22
+ },
23
+ "tasks.delete": async (params) => {
24
+ const { id } = params ?? {};
25
+ if (!id)
26
+ throw new Error("Missing task id");
27
+ const deleted = taskStore.delete(id);
28
+ if (!deleted)
29
+ throw new Error(`Task not found: ${id}`);
30
+ emit("taskChange", { action: "deleted", id });
31
+ return { status: "ok" };
32
+ },
33
+ "tasks.reorder": async (params) => {
34
+ const { status, orderedIds } = ReorderTasksSchema.parse(params);
35
+ taskStore.reorder(status, orderedIds);
36
+ emit("taskChange", { action: "reordered", status });
37
+ return { status: "ok" };
38
+ },
39
+ "tasks.archiveDone": async (params) => {
40
+ const { teamId } = params ?? {};
41
+ const count = taskStore.archiveDone(teamId);
42
+ emit("taskChange", { action: "archived", count });
43
+ return { archived: count };
44
+ },
45
+ };
46
+ }
@@ -0,0 +1,70 @@
1
+ import type { TeamStore } from "../../teams/store.js";
2
+ import type { TeamConfig } from "../../config/agent-config.js";
3
+ import type { MemoryStore } from "../../memory/store.js";
4
+ import type { EmbeddingProvider } from "../../memory/embedding.js";
5
+ export declare function teamMethods(teamStore: TeamStore, memoryStore?: MemoryStore | null, embeddingProvider?: EmbeddingProvider | null): {
6
+ /** List all teams with their agents. */
7
+ "teams.list": () => Promise<{
8
+ teams: {
9
+ agents: import("../../teams/types.js").AgentRow[];
10
+ id: string;
11
+ name: string;
12
+ color: string;
13
+ workspace: string;
14
+ createdAt: number;
15
+ updatedAt: number;
16
+ }[];
17
+ }>;
18
+ /** Return teams in the TeamConfig[] format used by the UI. */
19
+ "teams.configs": () => Promise<{
20
+ teams: TeamConfig[];
21
+ }>;
22
+ /**
23
+ * Save a single team (create or update) including its agents.
24
+ * Accepts a full TeamConfig; handles agent diff internally.
25
+ */
26
+ "teams.save": (params: unknown) => Promise<{
27
+ teams: TeamConfig[];
28
+ }>;
29
+ /** Create a new team. */
30
+ "teams.create": (params: unknown) => Promise<{
31
+ team: import("../../teams/types.js").Team;
32
+ }>;
33
+ /** Update a team (rename / recolor / workspace / variables). */
34
+ "teams.update": (params: unknown) => Promise<{
35
+ team: import("../../teams/types.js").Team;
36
+ }>;
37
+ /** Delete a team and all its agents. */
38
+ "teams.delete": (params: unknown) => Promise<{
39
+ teams: TeamConfig[];
40
+ }>;
41
+ /** Add an agent to a team. */
42
+ "teams.createAgent": (params: unknown) => Promise<{
43
+ agent: import("../../teams/types.js").AgentRow;
44
+ }>;
45
+ /** Update an agent's configuration. */
46
+ "teams.updateAgent": (params: unknown) => Promise<{
47
+ agent: import("../../teams/types.js").AgentRow;
48
+ }>;
49
+ /** Remove an agent from a team. */
50
+ "teams.deleteAgent": (params: unknown) => Promise<{
51
+ status: string;
52
+ }>;
53
+ /** List memories for a team (paginated). */
54
+ "teams.memories.list": (params: unknown) => Promise<{
55
+ entries: import("../../memory/types.js").MemoryEntry[];
56
+ total: number;
57
+ }>;
58
+ /** Search memories scoped to a team (returns global + team). */
59
+ "teams.memories.search": (params: unknown) => Promise<{
60
+ facts: string[];
61
+ }>;
62
+ /** Manually add a memory for a team. */
63
+ "teams.memories.add": (params: unknown) => Promise<{
64
+ saved: boolean;
65
+ }>;
66
+ /** Delete a single memory by ID (team-scoped for authorization). */
67
+ "teams.memories.delete": (params: unknown) => Promise<{
68
+ status: string;
69
+ }>;
70
+ };