synabun 1.3.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 (187) hide show
  1. package/CHANGELOG.md +665 -0
  2. package/LICENSE +191 -0
  3. package/README.md +853 -0
  4. package/hooks/claude-code/post-plan.mjs +391 -0
  5. package/hooks/claude-code/post-remember.mjs +239 -0
  6. package/hooks/claude-code/pre-compact.mjs +241 -0
  7. package/hooks/claude-code/pre-websearch.mjs +90 -0
  8. package/hooks/claude-code/prompt-submit.mjs +861 -0
  9. package/hooks/claude-code/session-start.mjs +494 -0
  10. package/hooks/claude-code/shared.mjs +368 -0
  11. package/hooks/claude-code/stop.mjs +646 -0
  12. package/hooks/claude-code/test-user-learning.mjs +531 -0
  13. package/mcp-server/package.json +38 -0
  14. package/mcp-server/run.mjs +10 -0
  15. package/mcp-server/src/config.ts +70 -0
  16. package/mcp-server/src/http.ts +93 -0
  17. package/mcp-server/src/index.ts +203 -0
  18. package/mcp-server/src/preload-http.ts +25 -0
  19. package/mcp-server/src/preload.ts +29 -0
  20. package/mcp-server/src/scripts/migrate-qdrant-to-sqlite.ts +506 -0
  21. package/mcp-server/src/services/categories.ts +429 -0
  22. package/mcp-server/src/services/discord.ts +702 -0
  23. package/mcp-server/src/services/file-checksums.ts +33 -0
  24. package/mcp-server/src/services/local-embeddings.ts +77 -0
  25. package/mcp-server/src/services/neural-interface.ts +344 -0
  26. package/mcp-server/src/services/sqlite.ts +823 -0
  27. package/mcp-server/src/tools/browser-advanced.ts +123 -0
  28. package/mcp-server/src/tools/browser-interact.ts +255 -0
  29. package/mcp-server/src/tools/browser-navigate.ts +77 -0
  30. package/mcp-server/src/tools/browser-observe.ts +1330 -0
  31. package/mcp-server/src/tools/browser.ts +107 -0
  32. package/mcp-server/src/tools/card-tools.ts +193 -0
  33. package/mcp-server/src/tools/card.ts +22 -0
  34. package/mcp-server/src/tools/category.ts +379 -0
  35. package/mcp-server/src/tools/discord-channel.ts +274 -0
  36. package/mcp-server/src/tools/discord-guild.ts +188 -0
  37. package/mcp-server/src/tools/discord-member.ts +158 -0
  38. package/mcp-server/src/tools/discord-message.ts +231 -0
  39. package/mcp-server/src/tools/discord-onboarding.ts +228 -0
  40. package/mcp-server/src/tools/discord-role.ts +209 -0
  41. package/mcp-server/src/tools/discord-thread.ts +110 -0
  42. package/mcp-server/src/tools/discord-webhook.ts +176 -0
  43. package/mcp-server/src/tools/discord.ts +40 -0
  44. package/mcp-server/src/tools/forget.ts +38 -0
  45. package/mcp-server/src/tools/git-tools.ts +141 -0
  46. package/mcp-server/src/tools/git.ts +13 -0
  47. package/mcp-server/src/tools/image-tools.ts +98 -0
  48. package/mcp-server/src/tools/image.ts +17 -0
  49. package/mcp-server/src/tools/leonardo-browser-tools.ts +281 -0
  50. package/mcp-server/src/tools/leonardo.ts +28 -0
  51. package/mcp-server/src/tools/loop.ts +348 -0
  52. package/mcp-server/src/tools/memories.ts +118 -0
  53. package/mcp-server/src/tools/recall.ts +323 -0
  54. package/mcp-server/src/tools/reflect.ts +145 -0
  55. package/mcp-server/src/tools/remember.ts +115 -0
  56. package/mcp-server/src/tools/response.ts +26 -0
  57. package/mcp-server/src/tools/restore.ts +34 -0
  58. package/mcp-server/src/tools/sync.ts +114 -0
  59. package/mcp-server/src/tools/tictactoe-tools.ts +98 -0
  60. package/mcp-server/src/tools/tictactoe.ts +13 -0
  61. package/mcp-server/src/tools/utils.ts +25 -0
  62. package/mcp-server/src/tools/whiteboard-tools.ts +260 -0
  63. package/mcp-server/src/tools/whiteboard.ts +22 -0
  64. package/mcp-server/src/tui.ts +940 -0
  65. package/mcp-server/src/types.ts +85 -0
  66. package/mcp-server/tsconfig.json +19 -0
  67. package/neural-interface/package.json +27 -0
  68. package/neural-interface/public/apple-touch-icon.png +0 -0
  69. package/neural-interface/public/category-logos/claude.svg +1 -0
  70. package/neural-interface/public/category-logos/gemini-color.svg +1 -0
  71. package/neural-interface/public/category-logos/openai.svg +1 -0
  72. package/neural-interface/public/category-logos/synabun.png +0 -0
  73. package/neural-interface/public/claude-chat.css +878 -0
  74. package/neural-interface/public/claude-chat.html +92 -0
  75. package/neural-interface/public/claude-chat.js +1168 -0
  76. package/neural-interface/public/favicon-16x16.png +0 -0
  77. package/neural-interface/public/favicon-32x32.png +0 -0
  78. package/neural-interface/public/gallery.svg +6 -0
  79. package/neural-interface/public/icon-192.png +0 -0
  80. package/neural-interface/public/icon-512.png +0 -0
  81. package/neural-interface/public/index-legacy.html +14677 -0
  82. package/neural-interface/public/index.html +212 -0
  83. package/neural-interface/public/index2d-legacy.html +8193 -0
  84. package/neural-interface/public/index2d.html +127 -0
  85. package/neural-interface/public/invite.html +351 -0
  86. package/neural-interface/public/leonardoai.svg +1 -0
  87. package/neural-interface/public/logo-synabun.png +0 -0
  88. package/neural-interface/public/logoHD.png +0 -0
  89. package/neural-interface/public/manifest.json +14 -0
  90. package/neural-interface/public/offline.html +299 -0
  91. package/neural-interface/public/onboarding.html +2335 -0
  92. package/neural-interface/public/openclaw-logo-text.png +0 -0
  93. package/neural-interface/public/pin-svgrepo-com.svg +25 -0
  94. package/neural-interface/public/sessionicon.svg +8 -0
  95. package/neural-interface/public/shared/ansi-parser.js +305 -0
  96. package/neural-interface/public/shared/api.js +1043 -0
  97. package/neural-interface/public/shared/colors.js +153 -0
  98. package/neural-interface/public/shared/constants.js +141 -0
  99. package/neural-interface/public/shared/html-shell.js +777 -0
  100. package/neural-interface/public/shared/html-term-renderer.js +1022 -0
  101. package/neural-interface/public/shared/i18n.js +147 -0
  102. package/neural-interface/public/shared/registry.js +82 -0
  103. package/neural-interface/public/shared/state.js +86 -0
  104. package/neural-interface/public/shared/storage.js +150 -0
  105. package/neural-interface/public/shared/styles.css +21212 -0
  106. package/neural-interface/public/shared/ui-automation-studio.js +3573 -0
  107. package/neural-interface/public/shared/ui-bookmarks.js +231 -0
  108. package/neural-interface/public/shared/ui-claude-panel.js +6333 -0
  109. package/neural-interface/public/shared/ui-command-runner.js +534 -0
  110. package/neural-interface/public/shared/ui-cost-widget.js +602 -0
  111. package/neural-interface/public/shared/ui-detail.js +1454 -0
  112. package/neural-interface/public/shared/ui-explorer.js +1356 -0
  113. package/neural-interface/public/shared/ui-file-explorer.js +2750 -0
  114. package/neural-interface/public/shared/ui-games.js +411 -0
  115. package/neural-interface/public/shared/ui-help.js +107 -0
  116. package/neural-interface/public/shared/ui-image-gallery.js +461 -0
  117. package/neural-interface/public/shared/ui-invite.js +519 -0
  118. package/neural-interface/public/shared/ui-keybinds.js +452 -0
  119. package/neural-interface/public/shared/ui-layouts.js +297 -0
  120. package/neural-interface/public/shared/ui-link.js +731 -0
  121. package/neural-interface/public/shared/ui-loading.js +255 -0
  122. package/neural-interface/public/shared/ui-menubar.js +499 -0
  123. package/neural-interface/public/shared/ui-multiselect.js +93 -0
  124. package/neural-interface/public/shared/ui-navbar.js +222 -0
  125. package/neural-interface/public/shared/ui-notifications.js +287 -0
  126. package/neural-interface/public/shared/ui-panels.js +309 -0
  127. package/neural-interface/public/shared/ui-resume.js +653 -0
  128. package/neural-interface/public/shared/ui-search.js +145 -0
  129. package/neural-interface/public/shared/ui-sessions.js +531 -0
  130. package/neural-interface/public/shared/ui-settings.js +5987 -0
  131. package/neural-interface/public/shared/ui-sidebar.js +1905 -0
  132. package/neural-interface/public/shared/ui-skills.js +2805 -0
  133. package/neural-interface/public/shared/ui-stats.js +38 -0
  134. package/neural-interface/public/shared/ui-sync.js +201 -0
  135. package/neural-interface/public/shared/ui-terminal.js +6385 -0
  136. package/neural-interface/public/shared/ui-tooltip.js +123 -0
  137. package/neural-interface/public/shared/ui-trash.js +439 -0
  138. package/neural-interface/public/shared/ui-tutorial-draw.js +875 -0
  139. package/neural-interface/public/shared/ui-tutorial-steps.js +2276 -0
  140. package/neural-interface/public/shared/ui-tutorial.js +1117 -0
  141. package/neural-interface/public/shared/ui-update.js +244 -0
  142. package/neural-interface/public/shared/ui-whiteboard.js +3262 -0
  143. package/neural-interface/public/shared/ui-workspaces.js +722 -0
  144. package/neural-interface/public/shared/utils.js +360 -0
  145. package/neural-interface/public/shared/vterm-buffer.js +1102 -0
  146. package/neural-interface/public/sw.js +52 -0
  147. package/neural-interface/public/synabun.png +0 -0
  148. package/neural-interface/public/toolbar-album.svg +8 -0
  149. package/neural-interface/public/toolbar-question.svg +6 -0
  150. package/neural-interface/public/toolbar-sessions.svg +6 -0
  151. package/neural-interface/public/variant/2d/background.js +113 -0
  152. package/neural-interface/public/variant/2d/context-menu.js +133 -0
  153. package/neural-interface/public/variant/2d/gfx.js +45 -0
  154. package/neural-interface/public/variant/2d/graph.js +1635 -0
  155. package/neural-interface/public/variant/2d/hulls.js +207 -0
  156. package/neural-interface/public/variant/2d/lasso.js +135 -0
  157. package/neural-interface/public/variant/2d/layout.js +594 -0
  158. package/neural-interface/public/variant/2d/main.js +521 -0
  159. package/neural-interface/public/variant/2d/minimap.js +312 -0
  160. package/neural-interface/public/variant/2d/settings-gfx.js +98 -0
  161. package/neural-interface/public/variant/3d/background.js +884 -0
  162. package/neural-interface/public/variant/3d/camera.js +661 -0
  163. package/neural-interface/public/variant/3d/gfx.js +116 -0
  164. package/neural-interface/public/variant/3d/graph.js +2399 -0
  165. package/neural-interface/public/variant/3d/main.js +500 -0
  166. package/neural-interface/public/variant/3d/settings-gfx.js +228 -0
  167. package/neural-interface/scripts/rebuild-pty.js +100 -0
  168. package/neural-interface/server.js +15000 -0
  169. package/neural-interface/templates/CLAUDE-template.md +211 -0
  170. package/package.json +53 -0
  171. package/postinstall.js +135 -0
  172. package/setup.js +260 -0
  173. package/skills/leonardo/SKILL.md +159 -0
  174. package/skills/leonardo/icon.svg +1 -0
  175. package/skills/leonardo/modules/browser-guide.md +378 -0
  176. package/skills/leonardo/modules/image-prompter.md +277 -0
  177. package/skills/leonardo/modules/model-advisor.md +100 -0
  178. package/skills/leonardo/modules/prompt-library.md +110 -0
  179. package/skills/leonardo/modules/style-guide.md +186 -0
  180. package/skills/leonardo/modules/video-prompter.md +311 -0
  181. package/skills/synabun/SKILL.md +185 -0
  182. package/skills/synabun/icon.png +0 -0
  183. package/skills/synabun/modules/audit.md +374 -0
  184. package/skills/synabun/modules/changelog.md +194 -0
  185. package/skills/synabun/modules/idea.md +144 -0
  186. package/skills/synabun/modules/memorize.md +188 -0
  187. package/skills/synabun/synabunicon.png +0 -0
@@ -0,0 +1,228 @@
1
+ import { z } from 'zod';
2
+ import * as discord from '../services/discord.js';
3
+ import { text } from './response.js';
4
+
5
+ export const discordOnboardingSchema = {
6
+ action: z.enum(['get', 'set_welcome', 'set_rules', 'set_verification', 'set_onboarding'] as const)
7
+ .describe('Action: get (current config), set_welcome (welcome screen), set_rules (rules channel), set_verification (level), set_onboarding (prompts & defaults).'),
8
+ guild_id: z.string().optional()
9
+ .describe('Guild ID. Defaults to DISCORD_GUILD_ID env var.'),
10
+ description: z.string().optional()
11
+ .describe('Welcome screen description text (set_welcome).'),
12
+ welcome_channels: z.array(z.record(z.unknown())).optional()
13
+ .describe('Welcome screen channels: [{channel_id, description, emoji_name?}] (set_welcome).'),
14
+ rules_channel: z.string().optional()
15
+ .describe('Channel name or ID for rules (set_rules).'),
16
+ system_channel: z.string().optional()
17
+ .describe('Channel name or ID for system messages (set_rules).'),
18
+ verification_level: z.enum(['none', 'low', 'medium', 'high', 'very_high'] as const).optional()
19
+ .describe('Verification level (set_verification). none=unrestricted, low=email, medium=5min, high=10min, very_high=phone.'),
20
+ prompts: z.array(z.record(z.unknown())).optional()
21
+ .describe('Onboarding prompts array (set_onboarding). Each: {title, options: [{title, description?, emoji_name?, channel_ids?, role_ids?}], single_select?, required?, in_onboarding?}'),
22
+ default_channels: z.array(z.string()).optional()
23
+ .describe('Default channel IDs for new members (set_onboarding).'),
24
+ enabled: z.boolean().optional()
25
+ .describe('Enable/disable onboarding (set_onboarding).'),
26
+ };
27
+
28
+ export const discordOnboardingDescription =
29
+ 'Discord server onboarding and setup. Actions: get, set_welcome, set_rules, set_verification, set_onboarding.';
30
+
31
+ // ── Get ────────────────────────────────────────────────────────
32
+
33
+ async function handleGet(guildId: string) {
34
+ // Fetch guild info, welcome screen, and onboarding in sequence
35
+ const guildRes = await discord.getGuild(guildId);
36
+ const welcomeRes = await discord.getWelcomeScreen(guildId);
37
+ const onboardingRes = await discord.getOnboarding(guildId);
38
+
39
+ const lines: string[] = ['**Server Setup Status:**\n'];
40
+
41
+ // Guild settings
42
+ if (guildRes.ok && guildRes.data) {
43
+ const g = guildRes.data as {
44
+ verification_level: number;
45
+ rules_channel_id?: string;
46
+ system_channel_id?: string;
47
+ features: string[];
48
+ };
49
+ const verificationNames = ['None', 'Low (email)', 'Medium (5min)', 'High (10min)', 'Very High (phone)'];
50
+ lines.push(`Verification: ${verificationNames[g.verification_level] || g.verification_level}`);
51
+ lines.push(`Rules channel: ${g.rules_channel_id || 'not set'}`);
52
+ lines.push(`System channel: ${g.system_channel_id || 'not set'}`);
53
+ const hasWelcome = g.features.includes('WELCOME_SCREEN_ENABLED');
54
+ lines.push(`Welcome screen: ${hasWelcome ? 'enabled' : 'disabled'}`);
55
+ const hasCommunity = g.features.includes('COMMUNITY');
56
+ lines.push(`Community: ${hasCommunity ? 'enabled' : 'disabled'}`);
57
+ lines.push('');
58
+ }
59
+
60
+ // Welcome screen
61
+ if (welcomeRes.ok && welcomeRes.data) {
62
+ const w = welcomeRes.data as {
63
+ description?: string;
64
+ welcome_channels: { channel_id: string; description: string; emoji_name?: string }[];
65
+ };
66
+ lines.push('**Welcome Screen:**');
67
+ lines.push(`Description: ${w.description || '(none)'}`);
68
+ if (w.welcome_channels.length > 0) {
69
+ for (const ch of w.welcome_channels) {
70
+ lines.push(` <#${ch.channel_id}> — ${ch.description}${ch.emoji_name ? ` ${ch.emoji_name}` : ''}`);
71
+ }
72
+ } else {
73
+ lines.push(' (no channels configured)');
74
+ }
75
+ lines.push('');
76
+ } else {
77
+ lines.push('**Welcome Screen:** not configured or requires Community\n');
78
+ }
79
+
80
+ // Onboarding
81
+ if (onboardingRes.ok && onboardingRes.data) {
82
+ const o = onboardingRes.data as {
83
+ guild_id: string;
84
+ prompts: { title: string; options: { title: string }[]; single_select: boolean; required: boolean }[];
85
+ default_channel_ids: string[];
86
+ enabled: boolean;
87
+ };
88
+ lines.push('**Onboarding:**');
89
+ lines.push(`Enabled: ${o.enabled}`);
90
+ lines.push(`Default channels: ${o.default_channel_ids.length > 0 ? o.default_channel_ids.map(id => `<#${id}>`).join(', ') : 'none'}`);
91
+ if (o.prompts.length > 0) {
92
+ lines.push(`Prompts (${o.prompts.length}):`);
93
+ for (const p of o.prompts) {
94
+ const opts = p.options.map(opt => opt.title).join(', ');
95
+ lines.push(` "${p.title}" (${p.options.length} options${p.required ? ', required' : ''}${p.single_select ? ', single' : ', multi'}): ${opts}`);
96
+ }
97
+ } else {
98
+ lines.push('Prompts: none');
99
+ }
100
+ } else {
101
+ lines.push('**Onboarding:** not configured');
102
+ }
103
+
104
+ return text(lines.join('\n'));
105
+ }
106
+
107
+ // ── Set Welcome ────────────────────────────────────────────────
108
+
109
+ async function handleSetWelcome(guildId: string, args: {
110
+ description?: string;
111
+ welcome_channels?: Record<string, unknown>[];
112
+ }) {
113
+ const data: Record<string, unknown> = { enabled: true };
114
+ if (args.description !== undefined) data.description = args.description;
115
+ if (args.welcome_channels) data.welcome_channels = args.welcome_channels;
116
+
117
+ const res = await discord.setWelcomeScreen(guildId, data);
118
+ if (res.error) return text(res.error);
119
+
120
+ return text(`Updated welcome screen.${args.description ? ` Description: "${args.description}"` : ''}${args.welcome_channels ? ` Channels: ${args.welcome_channels.length}` : ''}`);
121
+ }
122
+
123
+ // ── Set Rules ──────────────────────────────────────────────────
124
+
125
+ async function handleSetRules(guildId: string, args: {
126
+ rules_channel?: string;
127
+ system_channel?: string;
128
+ }) {
129
+ if (!args.rules_channel && !args.system_channel) {
130
+ return text('Provide rules_channel and/or system_channel.');
131
+ }
132
+
133
+ const data: Record<string, unknown> = {};
134
+
135
+ if (args.rules_channel) {
136
+ const ch = await discord.resolveChannel(args.rules_channel, guildId);
137
+ if ('error' in ch) return text(`Rules channel: ${ch.error}`);
138
+ data.rules_channel_id = ch.id;
139
+ }
140
+
141
+ if (args.system_channel) {
142
+ const ch = await discord.resolveChannel(args.system_channel, guildId);
143
+ if ('error' in ch) return text(`System channel: ${ch.error}`);
144
+ data.system_channel_id = ch.id;
145
+ }
146
+
147
+ const res = await discord.editGuildSettings(guildId, data);
148
+ if (res.error) return text(res.error);
149
+
150
+ const changes = Object.entries(data).map(([k, v]) => `${k}: ${v}`).join(', ');
151
+ return text(`Updated guild settings: ${changes}`);
152
+ }
153
+
154
+ // ── Set Verification ───────────────────────────────────────────
155
+
156
+ async function handleSetVerification(guildId: string, args: { verification_level?: string }) {
157
+ if (!args.verification_level) {
158
+ return text('verification_level is required.');
159
+ }
160
+
161
+ const level = discord.VERIFICATION_LEVELS[args.verification_level];
162
+ if (level === undefined) {
163
+ return text(`Unknown verification level "${args.verification_level}". Use: none, low, medium, high, very_high.`);
164
+ }
165
+
166
+ const res = await discord.editGuildSettings(guildId, { verification_level: level });
167
+ if (res.error) return text(res.error);
168
+
169
+ return text(`Set verification level to ${args.verification_level} (${level}).`);
170
+ }
171
+
172
+ // ── Set Onboarding ─────────────────────────────────────────────
173
+
174
+ async function handleSetOnboarding(guildId: string, args: {
175
+ prompts?: Record<string, unknown>[];
176
+ default_channels?: string[];
177
+ enabled?: boolean;
178
+ }) {
179
+ // GET current onboarding first, then merge
180
+ const currentRes = await discord.getOnboarding(guildId);
181
+ const current = (currentRes.ok && currentRes.data)
182
+ ? currentRes.data as Record<string, unknown>
183
+ : { prompts: [], default_channel_ids: [], enabled: false };
184
+
185
+ const data: Record<string, unknown> = { ...current };
186
+ if (args.prompts !== undefined) data.prompts = args.prompts;
187
+ if (args.default_channels !== undefined) data.default_channel_ids = args.default_channels;
188
+ if (args.enabled !== undefined) data.enabled = args.enabled;
189
+
190
+ const res = await discord.setOnboarding(guildId, data);
191
+ if (res.error) return text(res.error);
192
+
193
+ const changes: string[] = [];
194
+ if (args.enabled !== undefined) changes.push(`enabled: ${args.enabled}`);
195
+ if (args.prompts) changes.push(`prompts: ${args.prompts.length}`);
196
+ if (args.default_channels) changes.push(`default channels: ${args.default_channels.length}`);
197
+
198
+ return text(`Updated onboarding: ${changes.join(', ')}`);
199
+ }
200
+
201
+ // ── Main dispatcher ────────────────────────────────────────────
202
+
203
+ export async function handleDiscordOnboarding(args: {
204
+ action: string;
205
+ guild_id?: string;
206
+ description?: string;
207
+ welcome_channels?: Record<string, unknown>[];
208
+ rules_channel?: string;
209
+ system_channel?: string;
210
+ verification_level?: string;
211
+ prompts?: Record<string, unknown>[];
212
+ default_channels?: string[];
213
+ enabled?: boolean;
214
+ }) {
215
+ const resolved = await discord.resolveGuildId(args.guild_id);
216
+ if ('error' in resolved) return text(resolved.error);
217
+ const guildId = resolved.guildId;
218
+
219
+ switch (args.action) {
220
+ case 'get': return handleGet(guildId);
221
+ case 'set_welcome': return handleSetWelcome(guildId, args);
222
+ case 'set_rules': return handleSetRules(guildId, args);
223
+ case 'set_verification': return handleSetVerification(guildId, args);
224
+ case 'set_onboarding': return handleSetOnboarding(guildId, args);
225
+ default:
226
+ return text(`Unknown action "${args.action}". Use: get, set_welcome, set_rules, set_verification, set_onboarding.`);
227
+ }
228
+ }
@@ -0,0 +1,209 @@
1
+ import { z } from 'zod';
2
+ import * as discord from '../services/discord.js';
3
+ import { text } from './response.js';
4
+
5
+ export const discordRoleSchema = {
6
+ action: z.enum(['create', 'edit', 'delete', 'list', 'assign', 'remove'] as const)
7
+ .describe('Action: create, edit, delete, list, assign (to member), remove (from member).'),
8
+ guild_id: z.string().optional()
9
+ .describe('Guild ID. Defaults to DISCORD_GUILD_ID env var.'),
10
+ role: z.string().optional()
11
+ .describe('Role name or ID (required for edit/delete/assign/remove).'),
12
+ name: z.string().optional()
13
+ .describe('Role name (create required, edit optional).'),
14
+ color: z.string().optional()
15
+ .describe('Hex color e.g. "#ff0000" (create/edit).'),
16
+ hoist: z.boolean().optional()
17
+ .describe('Display separately in sidebar (create/edit).'),
18
+ mentionable: z.boolean().optional()
19
+ .describe('Allow @mention by anyone (create/edit).'),
20
+ permissions: z.string().optional()
21
+ .describe('Permission flags, comma-separated (create/edit). E.g. "SEND_MESSAGES,MANAGE_CHANNELS".'),
22
+ position: z.number().optional()
23
+ .describe('Sort position (edit only).'),
24
+ member: z.string().optional()
25
+ .describe('User name or ID (assign/remove actions).'),
26
+ reason: z.string().optional()
27
+ .describe('Audit log reason.'),
28
+ };
29
+
30
+ export const discordRoleDescription =
31
+ 'Discord role management. Actions: create, edit, delete, list, assign (role to member), remove (role from member).';
32
+
33
+ // ── Create ─────────────────────────────────────────────────────
34
+
35
+ async function handleCreate(guildId: string, args: {
36
+ name?: string; color?: string; hoist?: boolean;
37
+ mentionable?: boolean; permissions?: string;
38
+ }) {
39
+ if (!args.name) {
40
+ return text('name is required for create action.');
41
+ }
42
+
43
+ const data: Record<string, unknown> = { name: args.name };
44
+
45
+ if (args.color) {
46
+ const hex = args.color.replace('#', '');
47
+ data.color = parseInt(hex, 16);
48
+ }
49
+ if (args.hoist !== undefined) data.hoist = args.hoist;
50
+ if (args.mentionable !== undefined) data.mentionable = args.mentionable;
51
+ if (args.permissions) {
52
+ try {
53
+ data.permissions = discord.resolvePermissions(args.permissions);
54
+ } catch (err) {
55
+ return text((err as Error).message);
56
+ }
57
+ }
58
+
59
+ const res = await discord.createRole(guildId, data);
60
+ if (res.error) return text(res.error);
61
+
62
+ const role = res.data as { id: string; name: string; color: number };
63
+ const colorStr = role.color ? ` #${role.color.toString(16).padStart(6, '0')}` : '';
64
+ return text(`Created role @${role.name} (${role.id})${colorStr}`);
65
+ }
66
+
67
+ // ── Edit ───────────────────────────────────────────────────────
68
+
69
+ async function handleEdit(guildId: string, args: {
70
+ role?: string; name?: string; color?: string; hoist?: boolean;
71
+ mentionable?: boolean; permissions?: string; position?: number;
72
+ }) {
73
+ if (!args.role) {
74
+ return text('role is required for edit action.');
75
+ }
76
+
77
+ const resolved = await discord.resolveRole(args.role, guildId);
78
+ if ('error' in resolved) return text(resolved.error);
79
+
80
+ const data: Record<string, unknown> = {};
81
+ if (args.name !== undefined) data.name = args.name;
82
+ if (args.color !== undefined) {
83
+ const hex = args.color.replace('#', '');
84
+ data.color = parseInt(hex, 16);
85
+ }
86
+ if (args.hoist !== undefined) data.hoist = args.hoist;
87
+ if (args.mentionable !== undefined) data.mentionable = args.mentionable;
88
+ if (args.permissions) {
89
+ try {
90
+ data.permissions = discord.resolvePermissions(args.permissions);
91
+ } catch (err) {
92
+ return text((err as Error).message);
93
+ }
94
+ }
95
+
96
+ if (Object.keys(data).length === 0 && args.position === undefined) {
97
+ return text('No changes specified.');
98
+ }
99
+
100
+ const res = await discord.editRole(guildId, resolved.id, data);
101
+ if (res.error) return text(res.error);
102
+
103
+ return text(`Updated role @${resolved.name} (${resolved.id}).`);
104
+ }
105
+
106
+ // ── Delete ─────────────────────────────────────────────────────
107
+
108
+ async function handleDelete(guildId: string, args: { role?: string }) {
109
+ if (!args.role) {
110
+ return text('role is required for delete action.');
111
+ }
112
+
113
+ const resolved = await discord.resolveRole(args.role, guildId);
114
+ if ('error' in resolved) return text(resolved.error);
115
+
116
+ const res = await discord.deleteRole(guildId, resolved.id);
117
+ if (res.error) return text(res.error);
118
+
119
+ return text(`Deleted role @${resolved.name} (${resolved.id}).`);
120
+ }
121
+
122
+ // ── List ───────────────────────────────────────────────────────
123
+
124
+ async function handleList(guildId: string) {
125
+ const roles = await discord.getGuildRoles(guildId);
126
+ if ('error' in roles) return text((roles as { error: string }).error);
127
+
128
+ const list = (roles as { id: string; name: string; color: number; position: number; managed?: boolean; mentionable?: boolean; hoist?: boolean; permissions: string }[])
129
+ .sort((a, b) => b.position - a.position);
130
+
131
+ const lines = list.map(r => {
132
+ const color = r.color ? ` #${r.color.toString(16).padStart(6, '0')}` : '';
133
+ const flags: string[] = [];
134
+ if (r.hoist) flags.push('hoisted');
135
+ if (r.mentionable) flags.push('mentionable');
136
+ if (r.managed) flags.push('managed');
137
+ const flagStr = flags.length > 0 ? ` [${flags.join(', ')}]` : '';
138
+ return ` @${r.name} (${r.id})${color}${flagStr}`;
139
+ });
140
+
141
+ return text(`Roles (${list.length}):\n${lines.join('\n')}`);
142
+ }
143
+
144
+ // ── Assign ─────────────────────────────────────────────────────
145
+
146
+ async function handleAssign(guildId: string, args: { role?: string; member?: string }) {
147
+ if (!args.role) return text('role is required for assign action.');
148
+ if (!args.member) return text('member is required for assign action.');
149
+
150
+ const roleResolved = await discord.resolveRole(args.role, guildId);
151
+ if ('error' in roleResolved) return text(roleResolved.error);
152
+
153
+ const userResolved = await discord.resolveUser(args.member, guildId);
154
+ if ('error' in userResolved) return text(userResolved.error);
155
+
156
+ const res = await discord.addMemberRole(guildId, userResolved.id, roleResolved.id);
157
+ if (res.error) return text(res.error);
158
+
159
+ return text(`Assigned @${roleResolved.name} to ${userResolved.name}.`);
160
+ }
161
+
162
+ // ── Remove ─────────────────────────────────────────────────────
163
+
164
+ async function handleRemove(guildId: string, args: { role?: string; member?: string }) {
165
+ if (!args.role) return text('role is required for remove action.');
166
+ if (!args.member) return text('member is required for remove action.');
167
+
168
+ const roleResolved = await discord.resolveRole(args.role, guildId);
169
+ if ('error' in roleResolved) return text(roleResolved.error);
170
+
171
+ const userResolved = await discord.resolveUser(args.member, guildId);
172
+ if ('error' in userResolved) return text(userResolved.error);
173
+
174
+ const res = await discord.removeMemberRole(guildId, userResolved.id, roleResolved.id);
175
+ if (res.error) return text(res.error);
176
+
177
+ return text(`Removed @${roleResolved.name} from ${userResolved.name}.`);
178
+ }
179
+
180
+ // ── Main dispatcher ────────────────────────────────────────────
181
+
182
+ export async function handleDiscordRole(args: {
183
+ action: string;
184
+ guild_id?: string;
185
+ role?: string;
186
+ name?: string;
187
+ color?: string;
188
+ hoist?: boolean;
189
+ mentionable?: boolean;
190
+ permissions?: string;
191
+ position?: number;
192
+ member?: string;
193
+ reason?: string;
194
+ }) {
195
+ const resolved = await discord.resolveGuildId(args.guild_id);
196
+ if ('error' in resolved) return text(resolved.error);
197
+ const guildId = resolved.guildId;
198
+
199
+ switch (args.action) {
200
+ case 'create': return handleCreate(guildId, args);
201
+ case 'edit': return handleEdit(guildId, args);
202
+ case 'delete': return handleDelete(guildId, args);
203
+ case 'list': return handleList(guildId);
204
+ case 'assign': return handleAssign(guildId, args);
205
+ case 'remove': return handleRemove(guildId, args);
206
+ default:
207
+ return text(`Unknown action "${args.action}". Use: create, edit, delete, list, assign, remove.`);
208
+ }
209
+ }
@@ -0,0 +1,110 @@
1
+ import { z } from 'zod';
2
+ import * as discord from '../services/discord.js';
3
+ import { text } from './response.js';
4
+
5
+ export const discordThreadSchema = {
6
+ action: z.enum(['create', 'archive', 'unarchive', 'lock', 'delete'] as const)
7
+ .describe('Action: create, archive, unarchive, lock, delete.'),
8
+ guild_id: z.string().optional()
9
+ .describe('Guild ID for channel name resolution. Defaults to DISCORD_GUILD_ID env var.'),
10
+ channel: z.string().optional()
11
+ .describe('Parent channel name or ID (create only).'),
12
+ thread: z.string().optional()
13
+ .describe('Thread ID (required for archive/unarchive/lock/delete).'),
14
+ name: z.string().optional()
15
+ .describe('Thread name (create required).'),
16
+ message_id: z.string().optional()
17
+ .describe('Create thread from this message (create only). Omit for standalone thread.'),
18
+ auto_archive: z.number().optional()
19
+ .describe('Auto-archive duration in minutes: 60, 1440, 4320, or 10080 (create only).'),
20
+ private: z.boolean().optional()
21
+ .describe('Create private thread (create only, default false).'),
22
+ };
23
+
24
+ export const discordThreadDescription =
25
+ 'Discord thread management. Actions: create (from message or standalone), archive, unarchive, lock, delete.';
26
+
27
+ // ── Create ─────────────────────────────────────────────────────
28
+
29
+ async function handleCreate(args: {
30
+ channel?: string; name?: string; message_id?: string;
31
+ auto_archive?: number; private?: boolean; guild_id?: string;
32
+ }) {
33
+ if (!args.channel) return text('channel is required for create action.');
34
+ if (!args.name) return text('name is required for create action.');
35
+
36
+ let channelId = args.channel;
37
+ if (!/^\d{17,20}$/.test(channelId)) {
38
+ const resolved = await discord.resolveGuildId(args.guild_id);
39
+ if ('error' in resolved) return text(resolved.error);
40
+ const ch = await discord.resolveChannel(args.channel, resolved.guildId);
41
+ if ('error' in ch) return text(ch.error);
42
+ channelId = ch.id;
43
+ }
44
+
45
+ const data: Record<string, unknown> = {
46
+ name: args.name,
47
+ auto_archive_duration: args.auto_archive || 1440,
48
+ };
49
+
50
+ let res: discord.DiscordResponse;
51
+
52
+ if (args.message_id) {
53
+ // Create thread from message
54
+ res = await discord.createThreadFromMessage(channelId, args.message_id, data);
55
+ } else {
56
+ // Standalone thread
57
+ data.type = args.private ? 12 : 11; // 11 = public, 12 = private
58
+ res = await discord.createThread(channelId, data);
59
+ }
60
+
61
+ if (res.error) return text(res.error);
62
+
63
+ const thread = res.data as { id: string; name: string };
64
+ return text(`Created thread "${thread.name}" (${thread.id})${args.message_id ? ` from message ${args.message_id}` : ''}.`);
65
+ }
66
+
67
+ // ── Archive / Unarchive / Lock ─────────────────────────────────
68
+
69
+ async function handleModify(args: { thread?: string }, updates: Record<string, unknown>, actionName: string) {
70
+ if (!args.thread) return text(`thread ID is required for ${actionName} action.`);
71
+
72
+ const res = await discord.modifyThread(args.thread, updates);
73
+ if (res.error) return text(res.error);
74
+
75
+ return text(`Thread ${args.thread} ${actionName}d.`);
76
+ }
77
+
78
+ // ── Delete ─────────────────────────────────────────────────────
79
+
80
+ async function handleDelete(args: { thread?: string }) {
81
+ if (!args.thread) return text('thread ID is required for delete action.');
82
+
83
+ const res = await discord.deleteThread(args.thread);
84
+ if (res.error) return text(res.error);
85
+
86
+ return text(`Deleted thread ${args.thread}.`);
87
+ }
88
+
89
+ // ── Main dispatcher ────────────────────────────────────────────
90
+
91
+ export async function handleDiscordThread(args: {
92
+ action: string;
93
+ guild_id?: string;
94
+ channel?: string;
95
+ thread?: string;
96
+ name?: string;
97
+ message_id?: string;
98
+ auto_archive?: number;
99
+ private?: boolean;
100
+ }) {
101
+ switch (args.action) {
102
+ case 'create': return handleCreate(args);
103
+ case 'archive': return handleModify(args, { archived: true }, 'archive');
104
+ case 'unarchive': return handleModify(args, { archived: false }, 'unarchive');
105
+ case 'lock': return handleModify(args, { locked: true }, 'lock');
106
+ case 'delete': return handleDelete(args);
107
+ default:
108
+ return text(`Unknown action "${args.action}". Use: create, archive, unarchive, lock, delete.`);
109
+ }
110
+ }