volute 0.26.0 → 0.27.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 (127) hide show
  1. package/README.md +13 -13
  2. package/dist/{activity-events-ZMBAKLUF.js → activity-events-BBIEA2F4.js} +2 -3
  3. package/dist/api.d.ts +363 -168
  4. package/dist/{archive-4ZQYK5MN.js → archive-UA4BDFXQ.js} +2 -2
  5. package/dist/{auth-4TV573WE.js → auth-D3OT2ARB.js} +3 -3
  6. package/dist/bridge-FQHZL3MC.js +206 -0
  7. package/dist/chat-MHJ3L6JQ.js +58 -0
  8. package/dist/{chunk-PHU4DEAJ.js → chunk-2WPW7OT6.js} +3 -3
  9. package/dist/{chunk-5Y3PBKW6.js → chunk-2YP2TVDT.js} +138 -56
  10. package/dist/{chunk-USNBKHYG.js → chunk-4WXYUOAK.js} +4 -6
  11. package/dist/{chunk-YJA7P64S.js → chunk-AW7PFDVN.js} +5 -5
  12. package/dist/{chunk-OZFKBXD6.js → chunk-EHYDTZTF.js} +6 -6
  13. package/dist/{chunk-LX22GRG7.js → chunk-GIE6CSN5.js} +11 -8
  14. package/dist/{chunk-WBHMQ5OZ.js → chunk-H7OZRFJB.js} +192 -12
  15. package/dist/{chunk-ON3FF5JA.js → chunk-HDN7MNGD.js} +3 -3
  16. package/dist/chunk-IAYBDWVG.js +477 -0
  17. package/dist/{chunk-TZKJLDQN.js → chunk-IKRVFPWU.js} +14 -9
  18. package/dist/{chunk-WGOGUMPO.js → chunk-JGFVMROS.js} +13 -6
  19. package/dist/{chunk-3TV4GLFO.js → chunk-JKOWNZ4P.js} +3 -3
  20. package/dist/{chunk-NWI2425I.js → chunk-K5NAC55T.js} +1 -1
  21. package/dist/{chunk-HFCBO2GL.js → chunk-KDGS53OS.js} +4 -4
  22. package/dist/chunk-KTLFDYPT.js +61 -0
  23. package/dist/{chunk-V63B7DX3.js → chunk-LAC664WU.js} +7 -4
  24. package/dist/{chunk-3CFRE2VC.js → chunk-OQZH4PBB.js} +337 -1061
  25. package/dist/{chunk-2VO7453N.js → chunk-PHSAT7YL.js} +30 -54
  26. package/dist/{chunk-XOXLRRR2.js → chunk-RKQEHRBB.js} +4 -3
  27. package/dist/chunk-T6HKBWXZ.js +23 -0
  28. package/dist/{chunk-UTL75LP6.js → chunk-USUXRNVD.js} +22 -22
  29. package/dist/{chunk-J2CO4WEV.js → chunk-VIVMW2H2.js} +4 -4
  30. package/dist/{chunk-KTJGZ7M7.js → chunk-XBLSAVJF.js} +1 -1
  31. package/dist/cli.js +31 -36
  32. package/dist/{cloud-sync-NI2K3C7G.js → cloud-sync-T7M3ESC3.js} +15 -14
  33. package/dist/connectors/discord-bridge.js +158 -0
  34. package/dist/connectors/slack-bridge.js +119 -0
  35. package/dist/connectors/telegram-bridge.js +133 -0
  36. package/dist/conversations-M2K4253F.js +55 -0
  37. package/dist/create-D7J73A6H.js +45 -0
  38. package/dist/{create-4YBRTTJS.js → create-QWV73WXD.js} +1 -1
  39. package/dist/{daemon-client-Z7FAJ6JW.js → daemon-client-I42FK2BF.js} +2 -2
  40. package/dist/{daemon-restart-BJZ3O4U4.js → daemon-restart-M2QTYMEG.js} +7 -7
  41. package/dist/daemon.js +1758 -1024
  42. package/dist/db-IC4J52XQ.js +8 -0
  43. package/dist/{delete-27OYNK25.js → delete-4JYGD4VN.js} +1 -1
  44. package/dist/down-LVBXEULC.js +14 -0
  45. package/dist/{env-M336ONDP.js → env-YJMUMFIY.js} +2 -2
  46. package/dist/{export-HP4G5DQC.js → export-BOJQWBMA.js} +4 -4
  47. package/dist/{file-HUDKTRAS.js → file-CR36YUPD.js} +4 -4
  48. package/dist/{history-B64GTFTD.js → history-XKRTAFS2.js} +5 -5
  49. package/dist/{import-XIB7UV4S.js → import-SRTQXBGH.js} +4 -4
  50. package/dist/join-J4QU42DL.js +66 -0
  51. package/dist/list-R73GENNL.js +40 -0
  52. package/dist/{log-PBFNILJ4.js → log-ABYNVYJ3.js} +4 -4
  53. package/dist/{login-B5E7N7MY.js → login-3QZNR2DF.js} +4 -4
  54. package/dist/{login-6U7U6BNG.js → login-XX37I52P.js} +2 -2
  55. package/dist/{logout-XSJRYS3U.js → logout-T53VKCPU.js} +4 -4
  56. package/dist/{logout-UKD5LA37.js → logout-W4KOOBIT.js} +2 -2
  57. package/dist/{logs-3CART7O7.js → logs-U35JR2KE.js} +5 -5
  58. package/dist/{merge-VK2HSKMA.js → merge-LNSMSAOF.js} +4 -4
  59. package/dist/message-delivery-LDXLGERA.js +25 -0
  60. package/dist/migrate-registry-to-db-XC7T5B7P.js +110 -0
  61. package/dist/{mind-HZ3QSDDJ.js → mind-DI33C74K.js} +25 -25
  62. package/dist/{mind-activity-tracker-4G6FURY2.js → mind-activity-tracker-EN6XNXPF.js} +3 -4
  63. package/dist/mind-manager-M6EMUW5I.js +18 -0
  64. package/dist/{mind-sleep-DTV7L44D.js → mind-sleep-BTSWQNAC.js} +4 -4
  65. package/dist/{mind-wake-PFN4FN3T.js → mind-wake-SBAKIDVP.js} +4 -4
  66. package/dist/{notes-37FW2UR2.js → notes-XCER3I7M.js} +11 -21
  67. package/dist/{package-VZWLXPHV.js → package-7WY6VKU3.js} +1 -1
  68. package/dist/{pages-DIIT5HMQ.js → pages-6EBS6CBR.js} +2 -2
  69. package/dist/{publish-HQV7YREB.js → publish-66UB2ZFY.js} +5 -5
  70. package/dist/{pull-2MB4SK3C.js → pull-XCHJTM5M.js} +4 -4
  71. package/dist/read-36UFXN3G.js +46 -0
  72. package/dist/{register-EFND67FQ.js → register-6B2CXTYM.js} +2 -2
  73. package/dist/{registry-D2BSQ2X5.js → registry-NDNOOYG4.js} +15 -9
  74. package/dist/{restart-CCK7D6TV.js → restart-6ESL3NBO.js} +5 -5
  75. package/dist/{sandbox-EHGFF52K.js → sandbox-TGBX22DS.js} +3 -3
  76. package/dist/{schedule-6F7ELB2M.js → schedule-QTJMFATP.js} +5 -5
  77. package/dist/{seed-E5OQGWX3.js → seed-SSUCYYDF.js} +2 -2
  78. package/dist/{send-IH6XZKPC.js → send-ZNCJDSRP.js} +25 -19
  79. package/dist/{service-LLBV3R7M.js → service-6LIN3F3K.js} +4 -4
  80. package/dist/{setup-F6TWFYGQ.js → setup-JG4QAEBV.js} +12 -12
  81. package/dist/{setup-YGAAIKKZ.js → setup-JHL5ZEST.js} +2 -2
  82. package/dist/{shared-UMO4S7CC.js → shared-ML5I4Q2A.js} +4 -4
  83. package/dist/{skill-42LGFBQC.js → skill-AUAQTSP5.js} +5 -5
  84. package/dist/skills/dreaming/references/INSTALL.md +2 -2
  85. package/dist/skills/orientation/SKILL.md +3 -3
  86. package/dist/skills/volute-mind/SKILL.md +32 -30
  87. package/dist/sleep-manager-MWYHM5HV.js +29 -0
  88. package/dist/split-TKJ5OT3P.js +63 -0
  89. package/dist/{sprout-QL74KR2X.js → sprout-IJVVKSJ2.js} +6 -7
  90. package/dist/{start-O5JQASRC.js → start-EUJSS5R4.js} +2 -2
  91. package/dist/{status-FZBEBM7Q.js → status-77YEPHMW.js} +5 -5
  92. package/dist/{status-WXD4HXRL.js → status-7GA4SM4Y.js} +4 -4
  93. package/dist/{status-LV34BG6G.js → status-THLOBLWG.js} +2 -2
  94. package/dist/{stop-2SOG5NYF.js → stop-3XAITBBF.js} +5 -5
  95. package/dist/{tailscale-AJ4VL5XK.js → tailscale-NY5MUMY3.js} +1 -1
  96. package/dist/up-NKSMXBWR.js +17 -0
  97. package/dist/{update-5VUDAI3D.js → update-PTSH22AZ.js} +9 -9
  98. package/dist/{update-check-F5Z3ALXX.js → update-check-64FWC4Y2.js} +2 -2
  99. package/dist/{upgrade-QCCO33BK.js → upgrade-HA47CS4C.js} +12 -5
  100. package/dist/variant-7TGZHOU3.js +41 -0
  101. package/dist/{version-notify-USFZBWMG.js → version-notify-5Z4MNR6M.js} +26 -30
  102. package/dist/web-assets/assets/index-CI5wgghI.css +1 -0
  103. package/dist/web-assets/assets/index-is5CvJWH.js +75 -0
  104. package/dist/web-assets/favicon.png +0 -0
  105. package/dist/web-assets/index.html +2 -2
  106. package/drizzle/0017_minds.sql +16 -0
  107. package/drizzle/meta/_journal.json +7 -0
  108. package/package.json +1 -1
  109. package/templates/_base/.init/.config/prompts.json +2 -2
  110. package/templates/_base/home/VOLUTE.md +5 -5
  111. package/templates/_base/src/lib/startup.ts +2 -2
  112. package/dist/channel-ZVZV42UD.js +0 -260
  113. package/dist/chunk-B2CPS4QU.js +0 -283
  114. package/dist/chunk-SIAG3QMM.js +0 -42
  115. package/dist/chunk-WSLPZF72.js +0 -173
  116. package/dist/connector-G722WXAU.js +0 -147
  117. package/dist/connectors/discord.js +0 -177
  118. package/dist/connectors/slack.js +0 -181
  119. package/dist/connectors/telegram.js +0 -187
  120. package/dist/down-7UKFMJJZ.js +0 -14
  121. package/dist/message-delivery-MS5JYPZX.js +0 -25
  122. package/dist/mind-manager-VVK67AY3.js +0 -19
  123. package/dist/sleep-manager-EE4NRN2Q.js +0 -29
  124. package/dist/up-SDMCSVI3.js +0 -17
  125. package/dist/variant-WWLDY6D5.js +0 -207
  126. package/dist/web-assets/assets/index-CUQ31ieL.js +0 -69
  127. package/dist/web-assets/assets/index-CW8NSl1o.css +0 -1
@@ -1,173 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- stateDir
4
- } from "./chunk-B2CPS4QU.js";
5
-
6
- // src/lib/slugify.ts
7
- function slugify(text) {
8
- return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
9
- }
10
- function buildVoluteSlug(opts) {
11
- if (opts.convType === "channel" && opts.convName) {
12
- return `volute:#${opts.convName}`;
13
- }
14
- const isDM = opts.participants.length === 2;
15
- if (isDM) {
16
- const other = opts.participants.find((p) => p.username !== opts.mindUsername);
17
- const otherSlug = other ? slugify(other.username) : "";
18
- return otherSlug ? `volute:@${otherSlug}` : `volute:${opts.conversationId}`;
19
- }
20
- return opts.convTitle ? `volute:${slugify(opts.convTitle)}` : `volute:${opts.conversationId}`;
21
- }
22
-
23
- // src/connectors/sdk.ts
24
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
25
- import { join, resolve } from "path";
26
- function loadEnv() {
27
- const mindPort = process.env.VOLUTE_MIND_PORT;
28
- const mindName = process.env.VOLUTE_MIND_NAME;
29
- if (!mindPort || !mindName) {
30
- console.error("Missing required env vars: VOLUTE_MIND_PORT, VOLUTE_MIND_NAME");
31
- process.exit(1);
32
- }
33
- const mindDir = process.env.VOLUTE_MIND_DIR;
34
- const daemonUrl = process.env.VOLUTE_DAEMON_URL;
35
- const daemonToken = process.env.VOLUTE_DAEMON_TOKEN;
36
- const baseUrl = daemonUrl ? `${daemonUrl}/api/minds/${encodeURIComponent(mindName)}` : `http://127.0.0.1:${mindPort}`;
37
- return { mindPort, mindName, mindDir, baseUrl, daemonUrl, daemonToken };
38
- }
39
- function loadFollowedChannels(env, platform) {
40
- if (!env.mindDir) return [];
41
- const configPath = resolve(env.mindDir, "home/.config/volute.json");
42
- if (!existsSync(configPath)) return [];
43
- try {
44
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
45
- const platformConfig = config[platform];
46
- return platformConfig?.channels ?? platformConfig?.chats ?? [];
47
- } catch (err) {
48
- console.warn(`Failed to load mind config: ${err}`);
49
- return [];
50
- }
51
- }
52
- function splitMessage(text, maxLength) {
53
- const chunks = [];
54
- while (text.length > maxLength) {
55
- let splitAt = text.lastIndexOf("\n", maxLength);
56
- if (splitAt < maxLength / 2) splitAt = maxLength;
57
- chunks.push(text.slice(0, splitAt));
58
- text = text.slice(splitAt).replace(/^\n/, "");
59
- }
60
- if (text) chunks.push(text);
61
- return chunks;
62
- }
63
- function getHeaders(env) {
64
- const headers = { "Content-Type": "application/json" };
65
- if (env.daemonUrl && env.daemonToken) {
66
- headers.Authorization = `Bearer ${env.daemonToken}`;
67
- headers.Origin = env.daemonUrl;
68
- }
69
- return headers;
70
- }
71
- function onShutdown(cleanup) {
72
- const handler = () => {
73
- Promise.resolve(cleanup()).then(
74
- () => process.exit(0),
75
- (err) => {
76
- console.error(`Shutdown error: ${err}`);
77
- process.exit(1);
78
- }
79
- );
80
- };
81
- process.on("SIGINT", handler);
82
- process.on("SIGTERM", handler);
83
- }
84
- function reportTyping(env, channel, sender, active) {
85
- fetch(`${env.baseUrl}/typing`, {
86
- method: "POST",
87
- headers: getHeaders(env),
88
- body: JSON.stringify({ channel, sender, active })
89
- }).catch((err) => {
90
- console.warn(`[typing] failed to report for ${sender} on ${channel}: ${err}`);
91
- });
92
- }
93
- async function sendToMind(env, payload) {
94
- const url = `${env.baseUrl}/message`;
95
- try {
96
- const res = await fetch(url, {
97
- method: "POST",
98
- headers: getHeaders(env),
99
- body: JSON.stringify(payload)
100
- });
101
- if (!res.ok) {
102
- const body = await res.text().catch(() => "");
103
- console.error(`Mind returned ${res.status}: ${body}`);
104
- return { ok: false, error: `Mind returned ${res.status}` };
105
- }
106
- return { ok: true };
107
- } catch (err) {
108
- console.error(`Failed to forward message: ${err}`);
109
- const isConnRefused = err instanceof TypeError && err.cause?.code === "ECONNREFUSED";
110
- return { ok: false, error: isConnRefused ? "Mind is not running" : "Failed to reach mind" };
111
- }
112
- }
113
- function buildChannelSlug(platform, meta) {
114
- if (meta.isDM) {
115
- if (meta.recipients && meta.recipients.length > 0) {
116
- const sorted = meta.recipients.map(slugify).sort();
117
- return `${platform}:@${sorted.join(",")}`;
118
- }
119
- if (meta.senderName) {
120
- return `${platform}:@${slugify(meta.senderName)}`;
121
- }
122
- }
123
- if (meta.channelName && meta.serverName) {
124
- return `${platform}:${slugify(meta.serverName)}/${slugify(meta.channelName)}`;
125
- }
126
- if (meta.channelName) {
127
- return `${platform}:${slugify(meta.channelName)}`;
128
- }
129
- if (meta.platformId) {
130
- return `${platform}:${meta.platformId}`;
131
- }
132
- return `${platform}:unknown`;
133
- }
134
- function readChannelMap(mindName) {
135
- const filePath = join(stateDir(mindName), "channels.json");
136
- if (!existsSync(filePath)) return {};
137
- try {
138
- return JSON.parse(readFileSync(filePath, "utf-8"));
139
- } catch (err) {
140
- console.error(`[sdk] failed to parse ${filePath}:`, err);
141
- return {};
142
- }
143
- }
144
- function writeChannelEntry(mindName, slug, entry) {
145
- const dir = stateDir(mindName);
146
- mkdirSync(dir, { recursive: true });
147
- const filePath = join(dir, "channels.json");
148
- const map = readChannelMap(mindName);
149
- map[slug] = entry;
150
- writeFileSync(filePath, JSON.stringify(map, null, 2) + "\n");
151
- }
152
- function resolveChannelId(mindName, slug) {
153
- const map = readChannelMap(mindName);
154
- if (map[slug]) {
155
- return map[slug].platformId;
156
- }
157
- const colonIndex = slug.indexOf(":");
158
- return colonIndex >= 0 ? slug.slice(colonIndex + 1) : slug;
159
- }
160
-
161
- export {
162
- slugify,
163
- buildVoluteSlug,
164
- loadEnv,
165
- loadFollowedChannels,
166
- splitMessage,
167
- onShutdown,
168
- reportTyping,
169
- sendToMind,
170
- buildChannelSlug,
171
- writeChannelEntry,
172
- resolveChannelId
173
- };
@@ -1,147 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- promptLine
4
- } from "./chunk-SSI47XP2.js";
5
- import {
6
- daemonFetch
7
- } from "./chunk-WGOGUMPO.js";
8
- import {
9
- getClient,
10
- urlOf
11
- } from "./chunk-4RQBJWQX.js";
12
- import {
13
- resolveMindName
14
- } from "./chunk-NAOW2CLO.js";
15
- import {
16
- parseArgs
17
- } from "./chunk-D424ZQGI.js";
18
- import "./chunk-B2CPS4QU.js";
19
- import "./chunk-K3NQKI34.js";
20
-
21
- // src/commands/connector.ts
22
- async function run(args) {
23
- const subcommand = args[0];
24
- switch (subcommand) {
25
- case "connect":
26
- await connectConnector(args.slice(1));
27
- break;
28
- case "disconnect":
29
- await disconnectConnector(args.slice(1));
30
- break;
31
- case "--help":
32
- case "-h":
33
- case void 0:
34
- printUsage();
35
- break;
36
- default:
37
- printUsage();
38
- process.exit(1);
39
- }
40
- }
41
- function printUsage() {
42
- console.log(`Usage:
43
- volute mind connect <type> [--mind <name>]
44
- volute mind disconnect <type> [--mind <name>]`);
45
- }
46
- async function connectConnector(args) {
47
- const { positional, flags } = parseArgs(args, {
48
- mind: { type: "string" }
49
- });
50
- const mindName = resolveMindName(flags);
51
- const type = positional[0];
52
- if (!type) {
53
- console.error("Usage: volute mind connect <type> [--mind <name>]");
54
- process.exit(1);
55
- }
56
- const client = getClient();
57
- const connectorUrl = urlOf(
58
- client.api.minds[":name"].connectors[":type"].$url({
59
- param: { name: mindName, type }
60
- })
61
- );
62
- let res = await daemonFetch(connectorUrl, { method: "POST" });
63
- if (!res.ok) {
64
- const body = await res.json().catch(() => ({ error: "Unknown error" }));
65
- if (body.error === "missing_env" && "missing" in body) {
66
- const { missing, connectorName } = body;
67
- if (!process.stdin.isTTY) {
68
- console.error(`Missing required environment variables for ${connectorName}:`);
69
- for (const v of missing) {
70
- console.error(` ${v.name} \u2014 ${v.description}`);
71
- }
72
- console.error(`
73
- Set them with: volute env set <KEY> --mind ${mindName}`);
74
- process.exit(1);
75
- }
76
- console.error(`${connectorName} connector requires some environment variables.
77
- `);
78
- for (const v of missing) {
79
- const value = await promptLine(`${v.description}
80
- Enter value for ${v.name}: `);
81
- if (!value) {
82
- console.error(`No value provided for ${v.name}. Aborting.`);
83
- process.exit(1);
84
- }
85
- const envRes = await daemonFetch(
86
- urlOf(
87
- client.api.minds[":name"].env[":key"].$url({
88
- param: { name: mindName, key: v.name }
89
- })
90
- ),
91
- {
92
- method: "PUT",
93
- headers: { "Content-Type": "application/json" },
94
- body: JSON.stringify({ value })
95
- }
96
- );
97
- if (!envRes.ok) {
98
- const errBody = await envRes.json().catch(() => ({}));
99
- console.error(`Failed to set ${v.name}: ${errBody.error ?? `HTTP ${envRes.status}`}`);
100
- process.exit(1);
101
- }
102
- }
103
- console.log("Environment variables saved.\n");
104
- res = await daemonFetch(connectorUrl, { method: "POST" });
105
- if (!res.ok) {
106
- const retryBody = await res.json().catch(() => ({ error: "Unknown error" }));
107
- console.error(
108
- `Failed to start ${type} connector: ${retryBody.error}`
109
- );
110
- process.exit(1);
111
- }
112
- } else {
113
- console.error(`Failed to start ${type} connector: ${body.error}`);
114
- process.exit(1);
115
- }
116
- }
117
- console.log(`${type} connector for ${mindName} started.`);
118
- }
119
- async function disconnectConnector(args) {
120
- const { positional, flags } = parseArgs(args, {
121
- mind: { type: "string" }
122
- });
123
- const mindName = resolveMindName(flags);
124
- const type = positional[0];
125
- if (!type) {
126
- console.error("Usage: volute mind disconnect <type> [--mind <name>]");
127
- process.exit(1);
128
- }
129
- const client = getClient();
130
- const res = await daemonFetch(
131
- urlOf(
132
- client.api.minds[":name"].connectors[":type"].$url({
133
- param: { name: mindName, type }
134
- })
135
- ),
136
- { method: "DELETE" }
137
- );
138
- if (!res.ok) {
139
- const body = await res.json().catch(() => ({ error: "Unknown error" }));
140
- console.error(`Failed to stop ${type} connector: ${body.error}`);
141
- process.exit(1);
142
- }
143
- console.log(`${type} connector for ${mindName} stopped.`);
144
- }
145
- export {
146
- run
147
- };
@@ -1,177 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- buildChannelSlug,
4
- loadEnv,
5
- loadFollowedChannels,
6
- reportTyping,
7
- sendToMind,
8
- slugify,
9
- writeChannelEntry
10
- } from "../chunk-WSLPZF72.js";
11
- import "../chunk-B2CPS4QU.js";
12
- import "../chunk-K3NQKI34.js";
13
-
14
- // src/connectors/discord.ts
15
- import { ChannelType, Client, Events, GatewayIntentBits, Partials } from "discord.js";
16
- var TYPING_INTERVAL_MS = 8e3;
17
- var env = loadEnv();
18
- var token = process.env.DISCORD_TOKEN;
19
- if (!token) {
20
- console.error("Missing required env var: DISCORD_TOKEN");
21
- process.exit(1);
22
- }
23
- var followedChannelNames = loadFollowedChannels(env, "discord");
24
- var followedChannelIds = /* @__PURE__ */ new Set();
25
- var client = new Client({
26
- intents: [
27
- GatewayIntentBits.Guilds,
28
- GatewayIntentBits.GuildMessages,
29
- GatewayIntentBits.MessageContent,
30
- GatewayIntentBits.DirectMessages,
31
- GatewayIntentBits.GuildMessageTyping,
32
- GatewayIntentBits.DirectMessageTyping
33
- ],
34
- partials: [Partials.Channel]
35
- });
36
- function shutdown() {
37
- client.destroy();
38
- process.exit(0);
39
- }
40
- process.on("SIGINT", shutdown);
41
- process.on("SIGTERM", shutdown);
42
- client.once(Events.ClientReady, (c) => {
43
- console.log(`Connected to Discord as ${c.user.tag}`);
44
- console.log(`Bridging to mind: ${env.mindName} via ${env.baseUrl}/message`);
45
- if (followedChannelNames.length > 0) {
46
- for (const guild of c.guilds.cache.values()) {
47
- for (const ch of guild.channels.cache.values()) {
48
- if (ch.type !== ChannelType.GuildText) continue;
49
- if (followedChannelNames.includes(ch.name)) {
50
- followedChannelIds.add(ch.id);
51
- console.log(`Following #${ch.name} (${ch.id}) in ${guild.name}`);
52
- }
53
- }
54
- }
55
- if (followedChannelIds.size === 0) {
56
- console.warn(`No channels found matching: ${followedChannelNames.join(", ")}`);
57
- }
58
- }
59
- });
60
- client.on(Events.MessageCreate, async (message) => {
61
- if (message.author.bot) return;
62
- const isDM = !message.guild;
63
- const isMentioned = !isDM && message.mentions.has(client.user);
64
- const isFollowedChannel = !isDM && followedChannelIds.has(message.channelId);
65
- if (!isDM && !isMentioned && !isFollowedChannel) return;
66
- let text = message.content;
67
- if (isMentioned) {
68
- text = text.replace(new RegExp(`<@!?${client.user.id}>`, "g"), "").trim();
69
- }
70
- const content = [];
71
- if (text) content.push({ type: "text", text });
72
- for (const attachment of message.attachments.values()) {
73
- if (!attachment.contentType?.startsWith("image/")) continue;
74
- try {
75
- const res = await fetch(attachment.url);
76
- const buffer = Buffer.from(await res.arrayBuffer());
77
- content.push({
78
- type: "image",
79
- media_type: attachment.contentType,
80
- data: buffer.toString("base64")
81
- });
82
- } catch (err) {
83
- console.error(`Failed to download attachment: ${err}`);
84
- }
85
- }
86
- if (content.length === 0) return;
87
- const senderName = message.author.displayName || message.author.username;
88
- const channelName = !isDM && "name" in message.channel ? message.channel.name : void 0;
89
- const channelKey = isDM ? buildChannelSlug("discord", {
90
- isDM: true,
91
- recipients: [message.author.username]
92
- }) : buildChannelSlug("discord", {
93
- channelName: channelName ?? message.channelId,
94
- serverName: message.guild?.name
95
- });
96
- try {
97
- writeChannelEntry(env.mindName, channelKey, {
98
- platformId: message.channelId,
99
- platform: "discord",
100
- name: channelName ? `#${channelName}` : void 0,
101
- server: message.guild?.name,
102
- type: isDM ? "dm" : "channel"
103
- });
104
- } catch (err) {
105
- console.error(`[discord] failed to write channel entry for ${channelKey}:`, err);
106
- }
107
- const participantCount = isDM ? 2 : message.guild?.memberCount;
108
- const payload = {
109
- content,
110
- channel: channelKey,
111
- sender: senderName,
112
- platform: "Discord",
113
- ...isDM ? { isDM: true } : {},
114
- ...channelName ? { channelName } : {},
115
- ...message.guild?.name ? { serverName: message.guild.name } : {},
116
- ...participantCount ? { participantCount } : {}
117
- };
118
- reportTyping(env, channelKey, senderName, false);
119
- if (isFollowedChannel && !isMentioned) {
120
- const result = await sendToMind(env, payload);
121
- if (!result.ok)
122
- message.reply(result.error ?? "Failed to process message").catch((err) => {
123
- console.warn(`[discord] failed to send error reply: ${err}`);
124
- });
125
- return;
126
- }
127
- await handleDiscordMessage(message, payload);
128
- });
129
- client.on(Events.TypingStart, (typing) => {
130
- if (typing.user.bot) return;
131
- const sender = typing.user.displayName || typing.user.username || typing.user.id || "unknown";
132
- const typingChannel = typing.guild ? `discord:${slugify(typing.guild.name)}/${slugify("name" in typing.channel ? String(typing.channel.name) : typing.channel.id)}` : `discord:@${slugify(typing.user.username ?? typing.user.id)}`;
133
- reportTyping(env, typingChannel, sender, true);
134
- });
135
- async function handleDiscordMessage(message, payload) {
136
- const channel = message.channel;
137
- if (!("sendTyping" in channel)) return;
138
- const typingInterval = setInterval(() => {
139
- channel.sendTyping().catch((err) => {
140
- console.warn(`[discord] sendTyping failed: ${err}`);
141
- });
142
- }, TYPING_INTERVAL_MS);
143
- channel.sendTyping().catch((err) => {
144
- console.warn(`[discord] sendTyping failed: ${err}`);
145
- });
146
- try {
147
- const result = await sendToMind(env, payload);
148
- if (!result.ok)
149
- message.reply(result.error ?? "Failed to process message").catch((err) => {
150
- console.warn(`[discord] failed to send error reply: ${err}`);
151
- });
152
- } finally {
153
- clearInterval(typingInterval);
154
- }
155
- }
156
- async function loginWithRetry() {
157
- try {
158
- await client.login(token);
159
- } catch (err) {
160
- const msg = err instanceof Error ? err.message : String(err);
161
- const match = msg.match(/resets at (.+)/);
162
- if (match) {
163
- const resetAt = new Date(match[1]);
164
- const waitMs = resetAt.getTime() - Date.now();
165
- if (waitMs > 0) {
166
- console.error(`Session limit hit, waiting until ${resetAt.toISOString()}...`);
167
- await new Promise((r) => setTimeout(r, waitMs + 5e3));
168
- return loginWithRetry();
169
- }
170
- }
171
- throw err;
172
- }
173
- }
174
- loginWithRetry().catch((err) => {
175
- console.error("Failed to connect to Discord:", err);
176
- process.exit(1);
177
- });
@@ -1,181 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- buildChannelSlug,
4
- loadEnv,
5
- loadFollowedChannels,
6
- onShutdown,
7
- sendToMind,
8
- writeChannelEntry
9
- } from "../chunk-WSLPZF72.js";
10
- import "../chunk-B2CPS4QU.js";
11
- import "../chunk-K3NQKI34.js";
12
-
13
- // src/connectors/slack.ts
14
- import { App } from "@slack/bolt";
15
- var env = loadEnv();
16
- var botToken = process.env.SLACK_BOT_TOKEN;
17
- var appToken = process.env.SLACK_APP_TOKEN;
18
- if (!botToken || !appToken) {
19
- console.error("Missing required env vars: SLACK_BOT_TOKEN, SLACK_APP_TOKEN");
20
- process.exit(1);
21
- }
22
- var followedChannelNames = loadFollowedChannels(env, "slack");
23
- var followedChannelIds = /* @__PURE__ */ new Set();
24
- var app = new App({
25
- token: botToken,
26
- socketMode: true,
27
- appToken
28
- });
29
- var botUserId;
30
- var serverName;
31
- app.message(async ({ message }) => {
32
- if (message.subtype) return;
33
- if (!("user" in message) || !("text" in message)) return;
34
- if ("bot_id" in message && message.bot_id) return;
35
- const isDM = message.channel_type === "im" || message.channel_type === "mpim";
36
- const isMentioned = !isDM && botUserId && message.text?.includes(`<@${botUserId}>`);
37
- const isFollowedChannel = !isDM && followedChannelIds.has(message.channel);
38
- if (!isDM && !isMentioned && !isFollowedChannel) return;
39
- let text = message.text ?? "";
40
- if (isMentioned && botUserId) {
41
- text = text.replace(new RegExp(`<@${botUserId}>`, "g"), "").trim();
42
- }
43
- const content = [];
44
- if (text) content.push({ type: "text", text });
45
- if ("files" in message && message.files) {
46
- for (const file of message.files) {
47
- if (!file.mimetype?.startsWith("image/") || !file.url_private) continue;
48
- try {
49
- const res = await fetch(file.url_private, {
50
- headers: { Authorization: `Bearer ${botToken}` }
51
- });
52
- if (!res.ok) {
53
- console.error(`Failed to download attachment: HTTP ${res.status}`);
54
- continue;
55
- }
56
- const buffer = Buffer.from(await res.arrayBuffer());
57
- content.push({
58
- type: "image",
59
- media_type: file.mimetype,
60
- data: buffer.toString("base64")
61
- });
62
- } catch (err) {
63
- console.error(`Failed to download attachment: ${err}`);
64
- }
65
- }
66
- }
67
- if (content.length === 0) return;
68
- let channelName;
69
- let numMembers;
70
- if (message.channel_type !== "im") {
71
- try {
72
- const info = await app.client.conversations.info({
73
- channel: message.channel
74
- });
75
- channelName = info.channel?.name;
76
- numMembers = info.channel?.num_members;
77
- } catch (err) {
78
- console.warn(`Failed to get channel info: ${err}`);
79
- }
80
- }
81
- let senderName = message.user;
82
- let senderUsername = message.user;
83
- try {
84
- const userInfo = await app.client.users.info({
85
- user: message.user
86
- });
87
- senderName = userInfo.user?.profile?.display_name || userInfo.user?.profile?.real_name || message.user;
88
- senderUsername = userInfo.user?.name ?? message.user;
89
- } catch (err) {
90
- console.warn(`Failed to get user info: ${err}`);
91
- }
92
- const channelKey = isDM ? buildChannelSlug("slack", {
93
- isDM: true,
94
- senderName: senderUsername
95
- }) : buildChannelSlug("slack", {
96
- channelName: channelName ?? message.channel,
97
- serverName
98
- });
99
- try {
100
- writeChannelEntry(env.mindName, channelKey, {
101
- platformId: message.channel,
102
- platform: "slack",
103
- name: channelName ? `#${channelName}` : void 0,
104
- server: serverName,
105
- type: isDM ? "dm" : "channel"
106
- });
107
- } catch (err) {
108
- console.error(`[slack] failed to write channel entry for ${channelKey}:`, err);
109
- }
110
- const participantCount = message.channel_type === "im" ? 2 : numMembers;
111
- const payload = {
112
- content,
113
- channel: channelKey,
114
- sender: senderName,
115
- platform: "Slack",
116
- ...isDM ? { isDM: true } : {},
117
- ...channelName ? { channelName } : {},
118
- ...serverName ? { serverName } : {},
119
- ...participantCount ? { participantCount } : {}
120
- };
121
- if (isFollowedChannel && !isMentioned) {
122
- const result2 = await sendToMind(env, payload);
123
- if (!result2.ok)
124
- app.client.chat.postMessage({
125
- channel: message.channel,
126
- text: result2.error ?? "Failed to process message"
127
- }).catch((err) => {
128
- console.warn(`[slack] failed to send error reply: ${err}`);
129
- });
130
- return;
131
- }
132
- const result = await sendToMind(env, payload);
133
- if (!result.ok)
134
- app.client.chat.postMessage({ channel: message.channel, text: result.error ?? "Failed to process message" }).catch((err) => {
135
- console.warn(`[slack] failed to send error reply: ${err}`);
136
- });
137
- });
138
- async function start() {
139
- await app.start();
140
- const auth = await app.client.auth.test();
141
- if (!auth.user_id) {
142
- throw new Error("auth.test succeeded but returned no user_id");
143
- }
144
- botUserId = auth.user_id;
145
- serverName = auth.team;
146
- console.log(
147
- `Connected to Slack as bot user ${botUserId}${serverName ? ` in ${serverName}` : ""}`
148
- );
149
- console.log(`Bridging to mind: ${env.mindName} via ${env.baseUrl}/message`);
150
- if (followedChannelNames.length > 0) {
151
- try {
152
- let cursor;
153
- do {
154
- const result = await app.client.conversations.list({
155
- types: "public_channel,private_channel",
156
- limit: 200,
157
- ...cursor ? { cursor } : {}
158
- });
159
- for (const ch of result.channels ?? []) {
160
- if (followedChannelNames.includes(ch.name)) {
161
- followedChannelIds.add(ch.id);
162
- console.log(`Following #${ch.name} (${ch.id})`);
163
- }
164
- }
165
- cursor = result.response_metadata?.next_cursor || void 0;
166
- } while (cursor);
167
- } catch (err) {
168
- console.error(`Failed to resolve channel names: ${err}`);
169
- }
170
- if (followedChannelIds.size === 0 && followedChannelNames.length > 0) {
171
- console.warn(`No channels found matching: ${followedChannelNames.join(", ")}`);
172
- }
173
- }
174
- }
175
- onShutdown(async () => {
176
- await app.stop();
177
- });
178
- start().catch((err) => {
179
- console.error("Failed to start Slack connector:", err);
180
- process.exit(1);
181
- });