flingit 0.0.18 → 0.0.20

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 (55) hide show
  1. package/dist/cli/commands/plugin.d.ts +3 -10
  2. package/dist/cli/commands/plugin.d.ts.map +1 -1
  3. package/dist/cli/commands/plugin.js +103 -209
  4. package/dist/cli/commands/plugin.js.map +1 -1
  5. package/dist/cli/commands/push.d.ts +5 -0
  6. package/dist/cli/commands/push.d.ts.map +1 -1
  7. package/dist/cli/commands/push.js +21 -33
  8. package/dist/cli/commands/push.js.map +1 -1
  9. package/dist/cli/deploy/bundler.d.ts +14 -10
  10. package/dist/cli/deploy/bundler.d.ts.map +1 -1
  11. package/dist/cli/deploy/bundler.js +44 -70
  12. package/dist/cli/deploy/bundler.js.map +1 -1
  13. package/dist/cli/utils/config.d.ts +12 -4
  14. package/dist/cli/utils/config.d.ts.map +1 -1
  15. package/dist/cli/utils/config.js +49 -6
  16. package/dist/cli/utils/config.js.map +1 -1
  17. package/dist/runtime/discord.d.ts +18 -59
  18. package/dist/runtime/discord.d.ts.map +1 -1
  19. package/dist/runtime/discord.js +20 -60
  20. package/dist/runtime/discord.js.map +1 -1
  21. package/dist/runtime/slack.d.ts +83 -0
  22. package/dist/runtime/slack.d.ts.map +1 -0
  23. package/dist/runtime/slack.js +87 -0
  24. package/dist/runtime/slack.js.map +1 -0
  25. package/dist/shared/discord-types.d.ts +0 -24
  26. package/dist/shared/discord-types.d.ts.map +1 -1
  27. package/dist/shared/plugin-metadata.d.ts +28 -0
  28. package/dist/shared/plugin-metadata.d.ts.map +1 -0
  29. package/dist/shared/plugin-metadata.js +65 -0
  30. package/dist/shared/plugin-metadata.js.map +1 -0
  31. package/dist/shared/slack-types.d.ts +61 -0
  32. package/dist/shared/slack-types.d.ts.map +1 -0
  33. package/dist/shared/slack-types.js +8 -0
  34. package/dist/shared/slack-types.js.map +1 -0
  35. package/dist/worker-runtime/discord.d.ts +75 -27
  36. package/dist/worker-runtime/discord.d.ts.map +1 -1
  37. package/dist/worker-runtime/discord.js +156 -108
  38. package/dist/worker-runtime/discord.js.map +1 -1
  39. package/dist/worker-runtime/index.d.ts +1 -1
  40. package/dist/worker-runtime/index.d.ts.map +1 -1
  41. package/dist/worker-runtime/index.js +0 -4
  42. package/dist/worker-runtime/index.js.map +1 -1
  43. package/dist/worker-runtime/plugin-common.d.ts +27 -0
  44. package/dist/worker-runtime/plugin-common.d.ts.map +1 -0
  45. package/dist/worker-runtime/plugin-common.js +91 -0
  46. package/dist/worker-runtime/plugin-common.js.map +1 -0
  47. package/dist/worker-runtime/slack.d.ts +67 -0
  48. package/dist/worker-runtime/slack.d.ts.map +1 -0
  49. package/dist/worker-runtime/slack.js +135 -0
  50. package/dist/worker-runtime/slack.js.map +1 -0
  51. package/package.json +10 -2
  52. package/templates/default/skills/fling/.hash +1 -1
  53. package/templates/default/skills/fling/DISCORD.md +69 -126
  54. package/templates/default/skills/fling/SKILL.md +13 -0
  55. package/templates/default/skills/fling/SLACK.md +214 -0
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Shared helpers for plugin worker-runtime modules.
3
+ *
4
+ * Extracts the common plumbing (service binding fetch, identity tokens,
5
+ * HMAC signing, error parsing) so each plugin module only contains
6
+ * plugin-specific logic.
7
+ */
8
+ import { __getEnv } from "./index.js";
9
+ /**
10
+ * Create a pluginFetch function bound to a specific service binding name.
11
+ *
12
+ * @param bindingName - The env binding name (e.g., "DISCORD_PLUGIN")
13
+ */
14
+ export function createPluginFetch(bindingName) {
15
+ return function pluginFetch(path, init) {
16
+ const env = __getEnv();
17
+ const binding = env[bindingName];
18
+ if (binding === null || binding === undefined || typeof binding["fetch"] !== "function") {
19
+ throw new Error(`${bindingName} service binding not available. ` +
20
+ "Re-deploy your project with 'fling push' to get the latest bindings.");
21
+ }
22
+ return binding.fetch(`https://plugin${path}`, init);
23
+ };
24
+ }
25
+ // ═══════════════════════════════════════════════════════
26
+ // HMAC + IDENTITY TOKENS
27
+ // ═══════════════════════════════════════════════════════
28
+ export async function hmacSha256(secret, message) {
29
+ const encoder = new TextEncoder();
30
+ const key = await crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
31
+ const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(message));
32
+ return Array.from(new Uint8Array(signature))
33
+ .map((b) => b.toString(16).padStart(2, "0"))
34
+ .join("");
35
+ }
36
+ export async function generateIdentityToken() {
37
+ const env = __getEnv();
38
+ const projectId = env["FLING_PROJECT_ID"];
39
+ const workerSecret = env["FLING_WORKER_SECRET"];
40
+ const projectIdStr = typeof projectId === "string" ? projectId :
41
+ typeof projectId === "number" ? String(projectId) : "";
42
+ const secretStr = typeof workerSecret === "string" ? workerSecret : "";
43
+ if (!secretStr) {
44
+ throw new Error("FLING_WORKER_SECRET not available. Re-deploy your project with 'fling push' to get the latest configuration.");
45
+ }
46
+ const timestamp = Date.now();
47
+ const payload = `${projectIdStr}.${timestamp}`;
48
+ const signature = await hmacSha256(secretStr, payload);
49
+ return `${payload}.${signature}`;
50
+ }
51
+ let cachedIdentityToken = null;
52
+ const TOKEN_CACHE_TTL_MS = 30_000;
53
+ export async function getProjectHeaders() {
54
+ const now = Date.now();
55
+ if (!cachedIdentityToken || cachedIdentityToken.expiresAt <= now) {
56
+ const token = await generateIdentityToken();
57
+ cachedIdentityToken = { token, expiresAt: now + TOKEN_CACHE_TTL_MS };
58
+ }
59
+ return {
60
+ "X-Fling-Identity": cachedIdentityToken.token,
61
+ "Content-Type": "application/json",
62
+ };
63
+ }
64
+ // ═══════════════════════════════════════════════════════
65
+ // ERROR RESPONSE PARSING
66
+ // ═══════════════════════════════════════════════════════
67
+ /**
68
+ * Parse an error response and throw with a descriptive message.
69
+ * Handles both JSON and non-JSON error bodies gracefully.
70
+ */
71
+ export async function throwResponseError(response, defaultMessage) {
72
+ const text = await response.text();
73
+ try {
74
+ const json = JSON.parse(text);
75
+ throw new Error(json.error ?? defaultMessage);
76
+ }
77
+ catch (e) {
78
+ if (e instanceof SyntaxError) {
79
+ throw new Error(`${defaultMessage} (HTTP ${response.status}): ${text}`);
80
+ }
81
+ throw e;
82
+ }
83
+ }
84
+ /**
85
+ * Reset the identity token cache. Exposed for testing only.
86
+ * @internal
87
+ */
88
+ export function __resetTokenCache() {
89
+ cachedIdentityToken = null;
90
+ }
91
+ //# sourceMappingURL=plugin-common.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-common.js","sourceRoot":"","sources":["../../src/worker-runtime/plugin-common.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Fling Worker Runtime - Slack Integration
3
+ *
4
+ * Worker-compatible Slack runtime for Cloudflare Workers deployment.
5
+ * This module provides Slack-specific exports for the worker environment.
6
+ *
7
+ * Used via the "flingit/plugin/slack" subpath export.
8
+ */
9
+ export type { SlackEvent, SlackBlock, SlackMessage, SendMessageOptions, EditMessageOptions, } from "../shared/slack-types.js";
10
+ import type { SlackEvent, SlackMessage, SendMessageOptions, EditMessageOptions } from "../shared/slack-types.js";
11
+ /**
12
+ * An app mention event from Slack.
13
+ */
14
+ export interface MentionEvent {
15
+ type: "app_mention";
16
+ channel: string;
17
+ user: string;
18
+ text: string;
19
+ ts: string;
20
+ thread_ts?: string;
21
+ team?: string;
22
+ }
23
+ type MentionHandler = (event: MentionEvent) => Promise<void> | void;
24
+ type GenericEventHandler = (event: SlackEvent) => Promise<void> | void;
25
+ /**
26
+ * Internal: Route an event to the appropriate handler.
27
+ */
28
+ export declare function __handleSlackEvent(event: SlackEvent): Promise<void>;
29
+ /**
30
+ * Common entry point for the bundler-generated worker.
31
+ */
32
+ export declare function __handlePluginRequest(request: Request): Promise<Response>;
33
+ export declare const slack: {
34
+ /**
35
+ * Register a handler for @mentions of your bot.
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * slack.onMention(async (event) => {
40
+ * await slack.sendMessage({
41
+ * channelId: event.channel,
42
+ * threadTs: event.ts,
43
+ * text: "Hey! How can I help?",
44
+ * });
45
+ * });
46
+ * ```
47
+ */
48
+ onMention(handler: MentionHandler): void;
49
+ /**
50
+ * Register a fallback handler for events that aren't handled by other handlers.
51
+ * Use this for advanced cases where you need to handle raw events.
52
+ */
53
+ onEvent(handler: GenericEventHandler): void;
54
+ /**
55
+ * Send a message to a Slack channel.
56
+ */
57
+ sendMessage(options: SendMessageOptions): Promise<SlackMessage>;
58
+ /**
59
+ * Edit a message.
60
+ */
61
+ editMessage(channelId: string, ts: string, options: EditMessageOptions): Promise<SlackMessage>;
62
+ /**
63
+ * Add a reaction to a message.
64
+ */
65
+ addReaction(channelId: string, ts: string, emoji: string): Promise<void>;
66
+ };
67
+ //# sourceMappingURL=slack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/worker-runtime/slack.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,YAAY,EACV,UAAU,EACV,UAAU,EACV,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EACV,UAAU,EACV,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAMlC;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAgBD,KAAK,cAAc,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACpE,KAAK,mBAAmB,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAMvE;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAczE;AAMD;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAa/E;AAED,eAAO,MAAM,KAAK;IAChB;;;;;;;;;;;;;OAaG;uBACgB,cAAc,GAAG,IAAI;IAIxC;;;OAGG;qBACc,mBAAmB,GAAG,IAAI;IAI3C;;OAEG;yBACwB,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC;IAmBrE;;OAEG;2BAEU,MAAM,MACb,MAAM,WACD,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC;IAiBxB;;OAEG;2BAC0B,MAAM,MAAM,MAAM,SAAS,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAa/E,CAAC"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Fling Worker Runtime - Slack Integration
3
+ *
4
+ * Worker-compatible Slack runtime for Cloudflare Workers deployment.
5
+ * This module provides Slack-specific exports for the worker environment.
6
+ *
7
+ * Used via the "flingit/plugin/slack" subpath export.
8
+ */
9
+ import { getPluginMeta } from "../shared/plugin-metadata.js";
10
+ import { createPluginFetch, getProjectHeaders, throwResponseError } from "./plugin-common.js";
11
+ // ═══════════════════════════════════════════════════════
12
+ // INTERNAL HELPERS
13
+ // ═══════════════════════════════════════════════════════
14
+ // Service binding name from plugin metadata (single source of truth)
15
+ const meta = getPluginMeta("slack");
16
+ if (!meta)
17
+ throw new Error("slack plugin not found in metadata registry");
18
+ const pluginFetch = createPluginFetch(meta.serviceBinding);
19
+ // Registered handlers
20
+ let _mentionHandler = null;
21
+ let _fallbackHandler = null;
22
+ /**
23
+ * Internal: Route an event to the appropriate handler.
24
+ */
25
+ export async function __handleSlackEvent(event) {
26
+ // Handle app mentions
27
+ if (event.type === "event_callback") {
28
+ const innerEvent = event.event;
29
+ if (innerEvent?.type === "app_mention" && _mentionHandler) {
30
+ await _mentionHandler(innerEvent);
31
+ return;
32
+ }
33
+ }
34
+ // Fallback handler
35
+ if (_fallbackHandler) {
36
+ await _fallbackHandler(event);
37
+ }
38
+ }
39
+ // ═══════════════════════════════════════════════════════
40
+ // PUBLIC API
41
+ // ═══════════════════════════════════════════════════════
42
+ /**
43
+ * Common entry point for the bundler-generated worker.
44
+ */
45
+ export async function __handlePluginRequest(request) {
46
+ const event = await request.json();
47
+ try {
48
+ await __handleSlackEvent(event);
49
+ return new Response(JSON.stringify({ ok: true }), {
50
+ headers: { "Content-Type": "application/json" },
51
+ });
52
+ }
53
+ catch (error) {
54
+ console.error("Slack event handler error:", error);
55
+ return new Response(JSON.stringify({ ok: true }), {
56
+ headers: { "Content-Type": "application/json" },
57
+ });
58
+ }
59
+ }
60
+ export const slack = {
61
+ /**
62
+ * Register a handler for @mentions of your bot.
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * slack.onMention(async (event) => {
67
+ * await slack.sendMessage({
68
+ * channelId: event.channel,
69
+ * threadTs: event.ts,
70
+ * text: "Hey! How can I help?",
71
+ * });
72
+ * });
73
+ * ```
74
+ */
75
+ onMention(handler) {
76
+ _mentionHandler = handler;
77
+ },
78
+ /**
79
+ * Register a fallback handler for events that aren't handled by other handlers.
80
+ * Use this for advanced cases where you need to handle raw events.
81
+ */
82
+ onEvent(handler) {
83
+ _fallbackHandler = handler;
84
+ },
85
+ /**
86
+ * Send a message to a Slack channel.
87
+ */
88
+ async sendMessage(options) {
89
+ const response = await pluginFetch("/proxy/messages", {
90
+ method: "POST",
91
+ headers: await getProjectHeaders(),
92
+ body: JSON.stringify({
93
+ channelId: options.channelId,
94
+ text: options.text,
95
+ blocks: options.blocks,
96
+ threadTs: options.threadTs,
97
+ }),
98
+ });
99
+ if (!response.ok) {
100
+ await throwResponseError(response, "Failed to send message");
101
+ }
102
+ return response.json();
103
+ },
104
+ /**
105
+ * Edit a message.
106
+ */
107
+ async editMessage(channelId, ts, options) {
108
+ const response = await pluginFetch(`/proxy/messages/${channelId}/${ts}`, {
109
+ method: "PATCH",
110
+ headers: await getProjectHeaders(),
111
+ body: JSON.stringify({
112
+ text: options.text,
113
+ blocks: options.blocks,
114
+ }),
115
+ });
116
+ if (!response.ok) {
117
+ await throwResponseError(response, "Failed to edit message");
118
+ }
119
+ return response.json();
120
+ },
121
+ /**
122
+ * Add a reaction to a message.
123
+ */
124
+ async addReaction(channelId, ts, emoji) {
125
+ const encodedEmoji = encodeURIComponent(emoji);
126
+ const response = await pluginFetch(`/proxy/reactions/${channelId}/${ts}/${encodedEmoji}`, {
127
+ method: "PUT",
128
+ headers: await getProjectHeaders(),
129
+ });
130
+ if (!response.ok) {
131
+ await throwResponseError(response, "Failed to add reaction");
132
+ }
133
+ },
134
+ };
135
+ //# sourceMappingURL=slack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack.js","sourceRoot":"","sources":["../../src/worker-runtime/slack.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAmC9F,0DAA0D;AAC1D,mBAAmB;AACnB,0DAA0D;AAE1D,qEAAqE;AACrE,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;AACpC,IAAI,CAAC,IAAI;IAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;AAC1E,MAAM,WAAW,GAAG,iBAAiB,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AAU3D,sBAAsB;AACtB,IAAI,eAAe,GAA0B,IAAI,CAAC;AAClD,IAAI,gBAAgB,GAA+B,IAAI,CAAC;AAExD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,KAAiB;IACxD,sBAAsB;IACtB,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB,EAAE,CAAC;QACpC,MAAM,UAAU,GAAI,KAAuC,CAAC,KAAK,CAAC;QAClE,IAAI,UAAU,EAAE,IAAI,KAAK,aAAa,IAAI,eAAe,EAAE,CAAC;YAC1D,MAAM,eAAe,CAAC,UAAqC,CAAC,CAAC;YAC7D,OAAO;QACT,CAAC;IACH,CAAC;IAED,mBAAmB;IACnB,IAAI,gBAAgB,EAAE,CAAC;QACrB,MAAM,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;AACH,CAAC;AAED,0DAA0D;AAC1D,aAAa;AACb,0DAA0D;AAE1D;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,OAAgB;IAC1D,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,IAAI,EAAgB,CAAC;IACjD,IAAI,CAAC;QACH,MAAM,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAChC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE;YAChD,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAChD,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;QACnD,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE;YAChD,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAChD,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,OAAuB;QAC/B,eAAe,GAAG,OAAO,CAAC;IAC5B,CAAC;IAED;;;OAGG;IACH,OAAO,CAAC,OAA4B;QAClC,gBAAgB,GAAG,OAAO,CAAC;IAC7B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CAAC,OAA2B;QAC3C,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,iBAAiB,EAAE;YACpD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,MAAM,iBAAiB,EAAE;YAClC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;aAC3B,CAAC;SACH,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,kBAAkB,CAAC,QAAQ,EAAE,wBAAwB,CAAC,CAAC;QAC/D,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,EAA2B,CAAC;IAClD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CACf,SAAiB,EACjB,EAAU,EACV,OAA2B;QAE3B,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,mBAAmB,SAAS,IAAI,EAAE,EAAE,EAAE;YACvE,MAAM,EAAE,OAAO;YACf,OAAO,EAAE,MAAM,iBAAiB,EAAE;YAClC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB,CAAC;SACH,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,kBAAkB,CAAC,QAAQ,EAAE,wBAAwB,CAAC,CAAC;QAC/D,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,EAA2B,CAAC;IAClD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CAAC,SAAiB,EAAE,EAAU,EAAE,KAAa;QAC5D,MAAM,YAAY,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAE/C,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,oBAAoB,SAAS,IAAI,EAAE,IAAI,YAAY,EAAE,EAAE;YACxF,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,MAAM,iBAAiB,EAAE;SACnC,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,kBAAkB,CAAC,QAAQ,EAAE,wBAAwB,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;CAEF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flingit",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "description": "Personal Software Platform - Build and deploy personal tools through conversation",
5
5
  "type": "module",
6
6
  "engines": {
@@ -24,6 +24,12 @@
24
24
  "import": "./dist/runtime/discord.js",
25
25
  "default": "./dist/runtime/discord.js"
26
26
  },
27
+ "./plugin/slack": {
28
+ "workerd": "./dist/worker-runtime/slack.js",
29
+ "types": "./dist/runtime/slack.d.ts",
30
+ "import": "./dist/runtime/slack.js",
31
+ "default": "./dist/runtime/slack.js"
32
+ },
27
33
  "./worker-runtime": {
28
34
  "types": "./dist/worker-runtime/index.d.ts",
29
35
  "import": "./dist/worker-runtime/index.js"
@@ -67,9 +73,11 @@
67
73
  "dotenv": "^17.2.3",
68
74
  "esbuild": "^0.27.2",
69
75
  "hono": "^4.11.7",
70
- "tsx": "^4.19.0"
76
+ "tsx": "^4.19.0",
77
+ "zod": "^3.25.76"
71
78
  },
72
79
  "devDependencies": {
80
+ "@cloudflare/workers-types": "^4.0.0",
73
81
  "@eslint/js": "^9.39.2",
74
82
  "@types/better-sqlite3": "^7.6.12",
75
83
  "@types/node": "^24.10.9",
@@ -1 +1 @@
1
- 48c43a6cab25d1a064e710509399ca76
1
+ 0862078e056a789ecb1426fcebd51516
@@ -1,15 +1,14 @@
1
1
  # Discord Skill
2
2
 
3
- Build Discord chatops bots with Fling. Add slash commands, send messages, handle interactions, and react to events — all from your Fling project.
3
+ Build Discord chatops bots with Fling. Handle slash commands, send messages, handle interactions, and react to events — all from your Fling project.
4
4
 
5
5
  ## Setup
6
6
 
7
7
  Discord requires a one-time setup before use:
8
8
 
9
9
  ```bash
10
- npm exec fling plugin install discord # Connect Discord account (opens browser)
11
- npm exec fling plugin discord add-server # Claim a server for your project
12
- npm exec fling push # Deploy code + register slash commands
10
+ npm exec fling plugin install discord # Connect Discord account + add bot to a server (opens browser)
11
+ npm exec fling push # Deploy code
13
12
  ```
14
13
 
15
14
  Run these commands yourself — you have bash access.
@@ -20,48 +19,31 @@ Run these commands yourself — you have bash access.
20
19
  import { app } from "flingit";
21
20
  import { discord } from "flingit/plugin/discord";
22
21
 
23
- // 1. Define slash commands (synced to Discord on `fling push`)
24
- export const commands = discord.defineCommands([
25
- { name: "ping", description: "Check bot latency" },
26
- {
27
- name: "deploy",
28
- description: "Deploy a branch",
29
- options: [
30
- { name: "branch", type: "string", description: "Branch name", required: true }
31
- ]
32
- }
33
- ]);
34
-
35
- // 2. Handle slash commands on POST /discord
36
- app.post("/discord", async (c) => {
37
- const interaction = await discord.verifyInteraction(c);
38
-
39
- if (interaction.data?.name === "ping") {
40
- await discord.replyToInteraction(interaction, {
41
- content: "Pong!",
42
- ephemeral: true // Only visible to the user who ran the command
43
- });
44
- }
45
-
46
- if (interaction.data?.name === "deploy") {
47
- const branch = interaction.data.options?.[0]?.value as string;
48
- await discord.replyToInteraction(interaction, {
49
- content: `Deploying ${branch}...`
50
- });
51
-
52
- // Do work after the initial reply...
53
- const result = await runDeploy(branch);
54
-
55
- // Send a followup message (no 3-second limit)
56
- await discord.sendFollowup(interaction, {
57
- content: `Deployed ${branch}: ${result}`
58
- });
59
- }
60
-
61
- return c.json({ ok: true });
22
+ // 1. Handle slash commands using onCommand(name, description, handler)
23
+ // Commands are auto-registered with Discord on `fling push`
24
+ discord.onCommand("ping", "Check if the bot is alive", async (interaction) => {
25
+ await discord.reply(interaction, {
26
+ content: "Pong!",
27
+ ephemeral: true // Only visible to the user who ran the command
28
+ });
29
+ });
30
+
31
+ discord.onCommand("deploy", "Deploy a branch", async (interaction, options) => {
32
+ const branch = options.getString("branch") ?? "main";
33
+ await discord.reply(interaction, {
34
+ content: `Deploying ${branch}...`
35
+ });
36
+
37
+ // Do work after the initial reply...
38
+ const result = await runDeploy(branch);
39
+
40
+ // Send a followup message (no 3-second limit)
41
+ await discord.followup(interaction, {
42
+ content: `Deployed ${branch}: ${result}`
43
+ });
62
44
  });
63
45
 
64
- // 3. Send messages proactively (e.g., from webhooks or cron)
46
+ // 2. Send messages proactively (e.g., from webhooks or cron)
65
47
  app.post("/api/notify", async (c) => {
66
48
  const { channelId, text } = await c.req.json();
67
49
 
@@ -82,67 +64,41 @@ All methods are imported from `"flingit/plugin/discord"`:
82
64
  import { discord } from "flingit/plugin/discord";
83
65
  ```
84
66
 
85
- ### discord.defineCommands(commands)
86
-
87
- Register slash commands. **Must be exported** as a top-level `export const commands` for `fling push` to detect them.
88
-
89
- ```typescript
90
- export const commands = discord.defineCommands([
91
- { name: "status", description: "Show system status" },
92
- {
93
- name: "lookup",
94
- description: "Look up a user",
95
- options: [
96
- { name: "email", type: "string", description: "User email", required: true },
97
- { name: "verbose", type: "boolean", description: "Show full details" }
98
- ]
99
- }
100
- ]);
101
- ```
102
-
103
- **Command names:** 1-32 chars, lowercase letters, numbers, and hyphens only.
104
-
105
- **Option types:** `"string"`, `"integer"`, `"boolean"`, `"user"`, `"channel"`, `"role"`
106
-
107
- Options can have predefined choices:
108
-
109
- ```typescript
110
- {
111
- name: "env",
112
- type: "string",
113
- description: "Target environment",
114
- required: true,
115
- choices: [
116
- { name: "Production", value: "prod" },
117
- { name: "Staging", value: "staging" }
118
- ]
119
- }
120
- ```
121
-
122
- ### discord.verifyInteraction(c)
67
+ ### discord.onCommand(name, description, handler)
123
68
 
124
- Parse an incoming Discord interaction from a Hono request context. Call this in your `POST /discord` handler.
69
+ Register a handler for a specific slash command. Commands are **automatically registered** with Discord when you run `fling push` — no need to use the Discord Developer Portal.
125
70
 
126
71
  ```typescript
127
- app.post("/discord", async (c) => {
128
- const interaction = await discord.verifyInteraction(c);
129
-
72
+ discord.onCommand("ping", "Check if the bot is alive", async (interaction, options) => {
130
73
  // interaction.data.name = command name
131
- // interaction.data.options = command arguments
74
+ // options.getString("param") = get a string option
75
+ // options.getNumber("count") = get a number option
76
+ // options.getBoolean("force") = get a boolean option
132
77
  // interaction.member.user = who ran the command (in servers)
133
78
  // interaction.guild_id = which server
134
79
  // interaction.channel_id = which channel
80
+ await discord.reply(interaction, "Pong!");
135
81
  });
136
82
  ```
137
83
 
138
84
  The platform verifies Discord's cryptographic signature before forwarding to your code — you don't need to handle that.
139
85
 
140
- ### discord.replyToInteraction(interaction, options)
86
+ ### discord.onEvent(handler)
87
+
88
+ Register a fallback handler for interactions that don't match any `onCommand`. Use for advanced cases.
89
+
90
+ ```typescript
91
+ discord.onEvent(async (interaction) => {
92
+ // Called for any interaction not handled by onCommand
93
+ });
94
+ ```
95
+
96
+ ### discord.reply(interaction, options)
141
97
 
142
98
  Reply to a slash command. **Must be called within 3 seconds** of receiving the interaction.
143
99
 
144
100
  ```typescript
145
- await discord.replyToInteraction(interaction, {
101
+ discord.reply(interaction, {
146
102
  content: "Hello!", // Text content (max 2000 chars)
147
103
  ephemeral: true, // Only visible to command user (optional)
148
104
  embeds: [{ // Rich embeds (optional, max 10)
@@ -153,16 +109,16 @@ await discord.replyToInteraction(interaction, {
153
109
  });
154
110
  ```
155
111
 
156
- ### discord.sendFollowup(interaction, options)
112
+ ### discord.followup(interaction, options)
157
113
 
158
114
  Send additional messages after the initial reply. No 3-second time limit.
159
115
 
160
116
  ```typescript
161
- await discord.replyToInteraction(interaction, { content: "Working on it..." });
117
+ discord.reply(interaction, { content: "Working on it..." });
162
118
 
163
119
  // ... do async work ...
164
120
 
165
- const msg = await discord.sendFollowup(interaction, {
121
+ const msg = await discord.followup(interaction, {
166
122
  content: "Done! Here are the results.",
167
123
  embeds: [{ title: "Results", description: resultText }]
168
124
  });
@@ -243,23 +199,17 @@ Content max: 2000 characters. Embeds max: 10 per message.
243
199
 
244
200
  ## Reading Command Options
245
201
 
246
- Access option values from `interaction.data.options`:
202
+ The `options` helper provides typed accessors for command options:
247
203
 
248
204
  ```typescript
249
- app.post("/discord", async (c) => {
250
- const interaction = await discord.verifyInteraction(c);
251
-
252
- if (interaction.data?.name === "deploy") {
253
- const options = interaction.data.options ?? [];
254
- const branch = options.find(o => o.name === "branch")?.value as string;
255
- const force = options.find(o => o.name === "force")?.value as boolean ?? false;
256
-
257
- await discord.replyToInteraction(interaction, {
258
- content: `Deploying ${branch}${force ? " (force)" : ""}...`
259
- });
260
- }
205
+ discord.onCommand("deploy", "Deploy a branch", async (interaction, options) => {
206
+ const branch = options.getString("branch") ?? "main";
207
+ const force = options.getBoolean("force") ?? false;
208
+ const count = options.getNumber("count") ?? 1;
261
209
 
262
- return c.json({ ok: true });
210
+ await discord.reply(interaction, {
211
+ content: `Deploying ${branch}${force ? " (force)" : ""}...`
212
+ });
263
213
  });
264
214
  ```
265
215
 
@@ -268,9 +218,7 @@ app.post("/discord", async (c) => {
268
218
  Access who ran the command and where:
269
219
 
270
220
  ```typescript
271
- app.post("/discord", async (c) => {
272
- const interaction = await discord.verifyInteraction(c);
273
-
221
+ discord.onCommand("info", "Show context info", async (interaction) => {
274
222
  // Who ran it
275
223
  const user = interaction.member?.user; // In servers
276
224
  // user.id, user.username
@@ -289,14 +237,12 @@ app.post("/discord", async (c) => {
289
237
 
290
238
  ```bash
291
239
  # Setup
292
- npm exec fling plugin install discord # Connect Discord (OAuth)
293
- npm exec fling plugin discord add-server # Claim a server
294
- npm exec fling plugin discord remove-server # Release a server
240
+ npm exec fling plugin install discord # Connect Discord (OAuth), auto-claims server
295
241
 
296
242
  # Status
297
243
  npm exec fling plugin permissions discord # Show connection status + servers
298
244
 
299
- # Deployment (registers slash commands automatically)
245
+ # Deployment
300
246
  npm exec fling push
301
247
 
302
248
  # Teardown
@@ -305,24 +251,21 @@ npm exec fling plugin remove discord # Disconnect, release all servers
305
251
 
306
252
  ## How It Works
307
253
 
308
- 1. **`fling plugin install discord`** opens a browser for Discord OAuth. You authorize Fling to see your servers and add the bot.
309
- 2. **`fling plugin discord add-server`** claims a Discord server for your project. Each server can only be claimed by one project.
310
- 3. **`export const commands = discord.defineCommands([...])`** in your code defines slash commands. `fling push` extracts them and registers them with Discord in all claimed servers.
311
- 4. When a user runs a slash command, Discord sends it to the platform, which routes it to your worker's `POST /discord` endpoint.
312
- 5. Your code calls `discord.verifyInteraction(c)` to parse it and `discord.replyToInteraction()` to respond.
254
+ 1. **`fling plugin install discord`** opens a browser for Discord OAuth. You authorize Fling, select a server to add the bot to, and that server is automatically claimed for your project.
255
+ 2. **`discord.onCommand(name, description, handler)`** registers a named command handler with a description.
256
+ 3. **`fling push`** bundles your code, deploys it, then automatically registers your slash commands with Discord's API for each claimed server. Commands appear instantly in Discord's slash command picker.
257
+ 4. When a user runs a slash command, Discord sends it to the platform, which routes it to your worker.
258
+ 5. Your handler receives the interaction and calls `discord.reply()` to respond.
259
+ 6. To add the bot to additional servers, re-run `fling plugin install discord` (remove and reinstall to re-OAuth), then `fling push` again to register commands in the new server.
313
260
 
314
261
  ## Important Constraints
315
262
 
316
263
  1. **Discord features only work in deployed workers** — They throw errors locally. Use `fling push` to deploy, then test in Discord.
317
264
 
318
- 2. **Reply within 3 seconds** — `replyToInteraction()` must be called within 3 seconds. For slow operations, reply immediately with "Working..." then use `sendFollowup()` for the result.
319
-
320
- 3. **Commands must be exported** — `fling push` looks for `export const commands = discord.defineCommands(...)`. If the export is missing, commands won't be registered.
321
-
322
- 4. **Handle interactions at POST /discord** — The platform routes all interactions to this exact path on your worker.
265
+ 2. **Reply within 3 seconds** — `discord.reply()` must be called within 3 seconds. For slow operations, reply immediately with "Working..." then use `discord.followup()` for the result.
323
266
 
324
- 5. **Channels must be in claimed servers** — `sendMessage()`, `editMessage()`, and `addReaction()` only work in channels belonging to servers claimed by your project.
267
+ 3. **Channels must be in claimed servers** — `sendMessage()`, `editMessage()`, and `addReaction()` only work in channels belonging to servers claimed by your project.
325
268
 
326
- 6. **One project per server** — A Discord server can only be claimed by one Fling project at a time.
269
+ 4. **One project per server** — A Discord server can only be claimed by one Fling project at a time.
327
270
 
328
- 7. **Plugin must be installed first** — Run `fling plugin install discord` before using any Discord features. Check with `fling plugin permissions discord`.
271
+ 5. **Plugin must be installed first** — Run `fling plugin install discord` before using any Discord features. Check with `fling plugin permissions discord`.