flingit 0.0.19 → 0.0.22

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 (58) hide show
  1. package/dist/cli/commands/dev.d.ts.map +1 -1
  2. package/dist/cli/commands/dev.js +9 -0
  3. package/dist/cli/commands/dev.js.map +1 -1
  4. package/dist/cli/commands/plugin.d.ts +3 -10
  5. package/dist/cli/commands/plugin.d.ts.map +1 -1
  6. package/dist/cli/commands/plugin.js +103 -209
  7. package/dist/cli/commands/plugin.js.map +1 -1
  8. package/dist/cli/commands/push.d.ts +5 -0
  9. package/dist/cli/commands/push.d.ts.map +1 -1
  10. package/dist/cli/commands/push.js +21 -33
  11. package/dist/cli/commands/push.js.map +1 -1
  12. package/dist/cli/deploy/bundler.d.ts +19 -9
  13. package/dist/cli/deploy/bundler.d.ts.map +1 -1
  14. package/dist/cli/deploy/bundler.js +168 -69
  15. package/dist/cli/deploy/bundler.js.map +1 -1
  16. package/dist/cli/utils/config.d.ts +12 -4
  17. package/dist/cli/utils/config.d.ts.map +1 -1
  18. package/dist/cli/utils/config.js +49 -6
  19. package/dist/cli/utils/config.js.map +1 -1
  20. package/dist/runtime/discord.d.ts +20 -58
  21. package/dist/runtime/discord.d.ts.map +1 -1
  22. package/dist/runtime/discord.js +22 -59
  23. package/dist/runtime/discord.js.map +1 -1
  24. package/dist/runtime/slack.d.ts +83 -0
  25. package/dist/runtime/slack.d.ts.map +1 -0
  26. package/dist/runtime/slack.js +87 -0
  27. package/dist/runtime/slack.js.map +1 -0
  28. package/dist/shared/discord-types.d.ts +23 -24
  29. package/dist/shared/discord-types.d.ts.map +1 -1
  30. package/dist/shared/plugin-metadata.d.ts +28 -0
  31. package/dist/shared/plugin-metadata.d.ts.map +1 -0
  32. package/dist/shared/plugin-metadata.js +65 -0
  33. package/dist/shared/plugin-metadata.js.map +1 -0
  34. package/dist/shared/slack-types.d.ts +61 -0
  35. package/dist/shared/slack-types.d.ts.map +1 -0
  36. package/dist/shared/slack-types.js +8 -0
  37. package/dist/shared/slack-types.js.map +1 -0
  38. package/dist/worker-runtime/discord.d.ts +80 -27
  39. package/dist/worker-runtime/discord.d.ts.map +1 -1
  40. package/dist/worker-runtime/discord.js +163 -108
  41. package/dist/worker-runtime/discord.js.map +1 -1
  42. package/dist/worker-runtime/index.d.ts +1 -1
  43. package/dist/worker-runtime/index.d.ts.map +1 -1
  44. package/dist/worker-runtime/index.js +0 -4
  45. package/dist/worker-runtime/index.js.map +1 -1
  46. package/dist/worker-runtime/plugin-common.d.ts +27 -0
  47. package/dist/worker-runtime/plugin-common.d.ts.map +1 -0
  48. package/dist/worker-runtime/plugin-common.js +91 -0
  49. package/dist/worker-runtime/plugin-common.js.map +1 -0
  50. package/dist/worker-runtime/slack.d.ts +67 -0
  51. package/dist/worker-runtime/slack.d.ts.map +1 -0
  52. package/dist/worker-runtime/slack.js +135 -0
  53. package/dist/worker-runtime/slack.js.map +1 -0
  54. package/package.json +8 -1
  55. package/templates/default/skills/fling/.hash +1 -1
  56. package/templates/default/skills/fling/DISCORD.md +112 -122
  57. package/templates/default/skills/fling/SKILL.md +7 -1
  58. package/templates/default/skills/fling/SLACK.md +214 -0
@@ -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,33 @@ 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", [
32
+ { name: "branch", type: "string", description: "Branch to deploy", required: false },
33
+ ], async (interaction, options) => {
34
+ const branch = options.getString("branch") ?? "main";
35
+ await discord.reply(interaction, {
36
+ content: `Deploying ${branch}...`
37
+ });
38
+
39
+ // Do work after the initial reply...
40
+ const result = await runDeploy(branch);
41
+
42
+ // Send a followup message (no 3-second limit)
43
+ await discord.followup(interaction, {
44
+ content: `Deployed ${branch}: ${result}`
45
+ });
62
46
  });
63
47
 
64
- // 3. Send messages proactively (e.g., from webhooks or cron)
48
+ // 2. Send messages proactively (e.g., from webhooks or cron)
65
49
  app.post("/api/notify", async (c) => {
66
50
  const { channelId, text } = await c.req.json();
67
51
 
@@ -82,67 +66,82 @@ All methods are imported from `"flingit/plugin/discord"`:
82
66
  import { discord } from "flingit/plugin/discord";
83
67
  ```
84
68
 
85
- ### discord.defineCommands(commands)
69
+ ### discord.onCommand(name, description, handler)
70
+ ### discord.onCommand(name, description, options, handler)
86
71
 
87
- Register slash commands. **Must be exported** as a top-level `export const commands` for `fling push` to detect them.
72
+ 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.
88
73
 
89
74
  ```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
- ]);
75
+ // Simple command (no parameters)
76
+ discord.onCommand("ping", "Check if the bot is alive", async (interaction, options) => {
77
+ await discord.reply(interaction, "Pong!");
78
+ });
79
+
80
+ // Command with parameters — Discord shows autocomplete & validation
81
+ discord.onCommand("deploy", "Deploy a branch", [
82
+ { name: "branch", type: "string", description: "Branch to deploy" },
83
+ { name: "force", type: "boolean", description: "Force deploy", required: false },
84
+ ], async (interaction, options) => {
85
+ const branch = options.getString("branch") ?? "main";
86
+ const force = options.getBoolean("force") ?? false;
87
+ await discord.reply(interaction, `Deploying ${branch}${force ? " (force)" : ""}...`);
88
+ });
101
89
  ```
102
90
 
103
- **Command names:** 1-32 chars, lowercase letters, numbers, and hyphens only.
91
+ **Supported option types:**
92
+
93
+ | Type | Discord type | Value |
94
+ |------|-------------|-------|
95
+ | `"string"` | STRING (3) | `options.getString("name")` |
96
+ | `"integer"` | INTEGER (4) | `options.getNumber("name")` |
97
+ | `"boolean"` | BOOLEAN (5) | `options.getBoolean("name")` |
98
+ | `"number"` | NUMBER (10) | `options.getNumber("name")` |
104
99
 
105
- **Option types:** `"string"`, `"integer"`, `"boolean"`, `"user"`, `"channel"`, `"role"`
100
+ **Option properties:**
101
+ - `name` — Parameter name (required)
102
+ - `type` — One of `"string"`, `"integer"`, `"boolean"`, `"number"` (required)
103
+ - `description` — Shown in Discord's UI (required)
104
+ - `required` — Defaults to `true`. Set `false` for optional parameters.
105
+ - `choices` — Enum-style choices: `[{ name: "Production", value: "prod" }, { name: "Staging", value: "staging" }]`
106
106
 
107
- Options can have predefined choices:
107
+ **Choices example:**
108
108
 
109
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
- }
110
+ discord.onCommand("env", "Switch environment", [
111
+ {
112
+ name: "target",
113
+ type: "string",
114
+ description: "Target environment",
115
+ choices: [
116
+ { name: "Production", value: "prod" },
117
+ { name: "Staging", value: "staging" },
118
+ { name: "Development", value: "dev" },
119
+ ],
120
+ },
121
+ ], async (interaction, options) => {
122
+ const target = options.getString("target")!;
123
+ await discord.reply(interaction, `Switching to ${target}`);
124
+ });
120
125
  ```
121
126
 
122
- ### discord.verifyInteraction(c)
127
+ The platform verifies Discord's cryptographic signature before forwarding to your code — you don't need to handle that.
123
128
 
124
- Parse an incoming Discord interaction from a Hono request context. Call this in your `POST /discord` handler.
129
+ ### discord.onEvent(handler)
130
+
131
+ Register a fallback handler for interactions that don't match any `onCommand`. Use for advanced cases.
125
132
 
126
133
  ```typescript
127
- app.post("/discord", async (c) => {
128
- const interaction = await discord.verifyInteraction(c);
129
-
130
- // interaction.data.name = command name
131
- // interaction.data.options = command arguments
132
- // interaction.member.user = who ran the command (in servers)
133
- // interaction.guild_id = which server
134
- // interaction.channel_id = which channel
134
+ discord.onEvent(async (interaction) => {
135
+ // Called for any interaction not handled by onCommand
135
136
  });
136
137
  ```
137
138
 
138
- The platform verifies Discord's cryptographic signature before forwarding to your code — you don't need to handle that.
139
-
140
- ### discord.replyToInteraction(interaction, options)
139
+ ### discord.reply(interaction, options)
141
140
 
142
141
  Reply to a slash command. **Must be called within 3 seconds** of receiving the interaction.
143
142
 
144
143
  ```typescript
145
- await discord.replyToInteraction(interaction, {
144
+ discord.reply(interaction, {
146
145
  content: "Hello!", // Text content (max 2000 chars)
147
146
  ephemeral: true, // Only visible to command user (optional)
148
147
  embeds: [{ // Rich embeds (optional, max 10)
@@ -153,16 +152,16 @@ await discord.replyToInteraction(interaction, {
153
152
  });
154
153
  ```
155
154
 
156
- ### discord.sendFollowup(interaction, options)
155
+ ### discord.followup(interaction, options)
157
156
 
158
157
  Send additional messages after the initial reply. No 3-second time limit.
159
158
 
160
159
  ```typescript
161
- await discord.replyToInteraction(interaction, { content: "Working on it..." });
160
+ discord.reply(interaction, { content: "Working on it..." });
162
161
 
163
162
  // ... do async work ...
164
163
 
165
- const msg = await discord.sendFollowup(interaction, {
164
+ const msg = await discord.followup(interaction, {
166
165
  content: "Done! Here are the results.",
167
166
  embeds: [{ title: "Results", description: resultText }]
168
167
  });
@@ -243,23 +242,21 @@ Content max: 2000 characters. Embeds max: 10 per message.
243
242
 
244
243
  ## Reading Command Options
245
244
 
246
- Access option values from `interaction.data.options`:
245
+ The `options` helper provides typed accessors for command options. Declare options in the `onCommand` call so Discord shows parameter hints and validation:
247
246
 
248
247
  ```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
- }
261
-
262
- return c.json({ ok: true });
248
+ discord.onCommand("deploy", "Deploy a branch", [
249
+ { name: "branch", type: "string", description: "Branch to deploy" },
250
+ { name: "force", type: "boolean", description: "Force deploy", required: false },
251
+ { name: "count", type: "integer", description: "Number of instances", required: false },
252
+ ], async (interaction, options) => {
253
+ const branch = options.getString("branch") ?? "main";
254
+ const force = options.getBoolean("force") ?? false;
255
+ const count = options.getNumber("count") ?? 1;
256
+
257
+ await discord.reply(interaction, {
258
+ content: `Deploying ${branch}${force ? " (force)" : ""} (×${count})...`
259
+ });
263
260
  });
264
261
  ```
265
262
 
@@ -268,9 +265,7 @@ app.post("/discord", async (c) => {
268
265
  Access who ran the command and where:
269
266
 
270
267
  ```typescript
271
- app.post("/discord", async (c) => {
272
- const interaction = await discord.verifyInteraction(c);
273
-
268
+ discord.onCommand("info", "Show context info", async (interaction) => {
274
269
  // Who ran it
275
270
  const user = interaction.member?.user; // In servers
276
271
  // user.id, user.username
@@ -289,14 +284,12 @@ app.post("/discord", async (c) => {
289
284
 
290
285
  ```bash
291
286
  # 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
287
+ npm exec fling plugin install discord # Connect Discord (OAuth), auto-claims server
295
288
 
296
289
  # Status
297
290
  npm exec fling plugin permissions discord # Show connection status + servers
298
291
 
299
- # Deployment (registers slash commands automatically)
292
+ # Deployment
300
293
  npm exec fling push
301
294
 
302
295
  # Teardown
@@ -305,24 +298,21 @@ npm exec fling plugin remove discord # Disconnect, release all servers
305
298
 
306
299
  ## How It Works
307
300
 
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.
301
+ 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.
302
+ 2. **`discord.onCommand(name, description, handler)`** registers a named command handler with a description.
303
+ 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.
304
+ 4. When a user runs a slash command, Discord sends it to the platform, which routes it to your worker.
305
+ 5. Your handler receives the interaction and calls `discord.reply()` to respond.
306
+ 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
307
 
314
308
  ## Important Constraints
315
309
 
316
310
  1. **Discord features only work in deployed workers** — They throw errors locally. Use `fling push` to deploy, then test in Discord.
317
311
 
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.
312
+ 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
313
 
324
- 5. **Channels must be in claimed servers** — `sendMessage()`, `editMessage()`, and `addReaction()` only work in channels belonging to servers claimed by your project.
314
+ 3. **Channels must be in claimed servers** — `sendMessage()`, `editMessage()`, and `addReaction()` only work in channels belonging to servers claimed by your project.
325
315
 
326
- 6. **One project per server** — A Discord server can only be claimed by one Fling project at a time.
316
+ 4. **One project per server** — A Discord server can only be claimed by one Fling project at a time.
327
317
 
328
- 7. **Plugin must be installed first** — Run `fling plugin install discord` before using any Discord features. Check with `fling plugin permissions discord`.
318
+ 5. **Plugin must be installed first** — Run `fling plugin install discord` before using any Discord features. Check with `fling plugin permissions discord`.
@@ -215,6 +215,8 @@ When the user's request is complete and working locally, offer to deploy it. Bu
215
215
  To deploy:
216
216
  **Run `npm exec fling push` directly** - you have bash access, don't ask the user to run commands.
217
217
 
218
+ After each deploy, tell the user what the deployed URL is.
219
+
218
220
  ### After First Deployment
219
221
 
220
222
  After your first `fling push`, consider asking the user if they want a custom URL.
@@ -251,7 +253,7 @@ Slugs must be:
251
253
 
252
254
  ## Security
253
255
 
254
- If the user does not want their app to be published because of security issues, or asks about that, offer implementing proper backend-supported security. In particular, suggest these two auth methods first:
256
+ If the user does not want their app to be deployed because of security issues, or asks about that, offer implementing proper backend-supported security. In particular, suggest these two auth methods first:
255
257
 
256
258
  - Login with Google, with a filter on which emails/domains are allowed
257
259
  - Simple password-based auth, where the backend has a list of allowed passwords (hashed)
@@ -344,3 +346,7 @@ This approach:
344
346
  **For large assets:** Serve them from the frontend (`public/` folder) instead.
345
347
 
346
348
  See API.md for detailed API reference, EXAMPLES.md for common patterns, and FEEDBACK.md for collecting user feedback.
349
+
350
+ ## Updates
351
+
352
+ When the fling CLI mentions that there is a new version available, strongly suggest to the user to update, becasue it might contain bug fixes or new features.
@@ -0,0 +1,214 @@
1
+ # Slack Skill
2
+
3
+ Build Slack chatops bots with Fling. Respond to @mentions, send messages, and react to events — all from your Fling project.
4
+
5
+ ## Setup
6
+
7
+ Slack requires a one-time setup before use:
8
+
9
+ ```bash
10
+ npm exec fling plugin install slack # Connect Slack workspace (opens browser)
11
+ npm exec fling push # Deploy code
12
+ ```
13
+
14
+ Run these commands yourself — you have bash access.
15
+
16
+ ## Quick Reference
17
+
18
+ ```typescript
19
+ import { app } from "flingit";
20
+ import { slack } from "flingit/plugin/slack";
21
+
22
+ // 1. Handle @mentions of your bot
23
+ slack.onMention(async (event) => {
24
+ // Parse the mention text (remove the bot mention prefix)
25
+ const text = event.text.replace(/<@[A-Z0-9]+>\s*/, "").trim();
26
+
27
+ if (text.startsWith("deploy")) {
28
+ const branch = text.split(" ")[1] ?? "main";
29
+ await slack.sendMessage({
30
+ channelId: event.channel,
31
+ threadTs: event.ts,
32
+ text: `Deploying ${branch}...`,
33
+ });
34
+ } else {
35
+ await slack.sendMessage({
36
+ channelId: event.channel,
37
+ threadTs: event.ts,
38
+ text: "Hello! How can I help?",
39
+ });
40
+ }
41
+ });
42
+
43
+ // 2. Send messages proactively (e.g., from webhooks or cron)
44
+ app.post("/api/notify", async (c) => {
45
+ const { channelId, text } = await c.req.json();
46
+
47
+ await slack.sendMessage({
48
+ channelId,
49
+ text
50
+ });
51
+
52
+ return c.json({ sent: true });
53
+ });
54
+ ```
55
+
56
+ ## API Reference
57
+
58
+ All methods are imported from `"flingit/plugin/slack"`:
59
+
60
+ ```typescript
61
+ import { slack } from "flingit/plugin/slack";
62
+ ```
63
+
64
+ ### slack.onMention(handler)
65
+
66
+ Register a handler for @mentions of your bot. Triggered when someone mentions your Fling app in a channel.
67
+
68
+ ```typescript
69
+ slack.onMention(async (event) => {
70
+ // event.channel = which channel
71
+ // event.user = who mentioned the bot
72
+ // event.text = full mention text (includes <@BOT_ID>)
73
+ // event.ts = message timestamp
74
+ // event.thread_ts = thread timestamp (if in thread)
75
+
76
+ await slack.sendMessage({
77
+ channelId: event.channel,
78
+ threadTs: event.ts, // Reply in thread
79
+ text: "Got it!",
80
+ });
81
+ });
82
+ ```
83
+
84
+ The platform verifies Slack's HMAC-SHA256 signature before forwarding to your code — you don't need to handle that.
85
+
86
+ ### slack.onEvent(handler)
87
+
88
+ Register a fallback handler for raw Slack events that aren't handled by `onMention`. Use this for advanced cases.
89
+
90
+ ```typescript
91
+ slack.onEvent(async (event) => {
92
+ console.log("Raw event:", event.type);
93
+ });
94
+ ```
95
+
96
+ ### slack.sendMessage(options)
97
+
98
+ Send a message to any channel. Use this for notifications, alerts, or proactive messages.
99
+
100
+ ```typescript
101
+ const msg = await slack.sendMessage({
102
+ channelId: "C123456789",
103
+ text: "Deployment complete!",
104
+ blocks: [{
105
+ type: "section",
106
+ text: { type: "mrkdwn", text: "*Deploy Report*\nv2.1.0 is now live" }
107
+ }]
108
+ });
109
+ // Returns: { channelId, ts, text }
110
+ ```
111
+
112
+ To reply in a thread:
113
+
114
+ ```typescript
115
+ await slack.sendMessage({
116
+ channelId: "C123456789",
117
+ text: "Thread reply here",
118
+ threadTs: "1234567890.123456" // Parent message timestamp
119
+ });
120
+ ```
121
+
122
+ ### slack.editMessage(channelId, ts, options)
123
+
124
+ Edit a previously sent message.
125
+
126
+ ```typescript
127
+ const msg = await slack.sendMessage({
128
+ channelId: channel,
129
+ text: "Deploying..."
130
+ });
131
+
132
+ // Later, update it
133
+ await slack.editMessage(channel, msg.ts, {
134
+ text: "Deploy complete!"
135
+ });
136
+ ```
137
+
138
+ ### slack.addReaction(channelId, ts, emoji)
139
+
140
+ Add an emoji reaction to a message.
141
+
142
+ ```typescript
143
+ await slack.addReaction(channelId, ts, "white_check_mark");
144
+ await slack.addReaction(channelId, ts, "rocket");
145
+ ```
146
+
147
+ Note: Use emoji names without colons (e.g., `"thumbsup"` not `":thumbsup:"`).
148
+
149
+ ## Block Kit
150
+
151
+ Slack uses Block Kit for rich message formatting. Common block types:
152
+
153
+ ```typescript
154
+ // Section block with markdown
155
+ {
156
+ type: "section",
157
+ text: { type: "mrkdwn", text: "*Bold* and _italic_" }
158
+ }
159
+
160
+ // Section with fields
161
+ {
162
+ type: "section",
163
+ fields: [
164
+ { type: "mrkdwn", text: "*Status:*\nActive" },
165
+ { type: "mrkdwn", text: "*Region:*\nus-east-1" }
166
+ ]
167
+ }
168
+
169
+ // Divider
170
+ { type: "divider" }
171
+
172
+ // Context (small text)
173
+ {
174
+ type: "context",
175
+ elements: [
176
+ { type: "mrkdwn", text: "Deployed by <@U123> at 3:42 PM" }
177
+ ]
178
+ }
179
+ ```
180
+
181
+ For more block types, see the Slack Block Kit documentation.
182
+
183
+ ## CLI Commands
184
+
185
+ ```bash
186
+ # Setup
187
+ npm exec fling plugin install slack # Connect Slack (OAuth), auto-claims workspace
188
+
189
+ # Status
190
+ npm exec fling plugin permissions slack # Show connection status + workspaces
191
+
192
+ # Deployment
193
+ npm exec fling push
194
+
195
+ # Teardown
196
+ npm exec fling plugin remove slack # Disconnect, release all workspaces
197
+ ```
198
+
199
+ ## How It Works
200
+
201
+ 1. **`fling plugin install slack`** opens a browser for Slack OAuth. You authorize the Fling app for your workspace, and it's automatically claimed for your project.
202
+ 2. **`slack.onMention(handler)`** registers a handler that receives `app_mention` events when someone @mentions your bot in a channel.
203
+ 3. The platform verifies Slack's HMAC-SHA256 signature, looks up the owning project, and forwards the event to your worker.
204
+ 4. Your handler receives the event and uses `slack.sendMessage()` to respond (typically replying in-thread).
205
+
206
+ ## Important Constraints
207
+
208
+ 1. **Slack features only work in deployed workers** — They throw errors locally. Use `fling push` to deploy, then test in Slack.
209
+
210
+ 2. **Mention-based interaction** — Users interact with your bot by @mentioning it in a channel. Parse the mention text to understand intent.
211
+
212
+ 3. **One project per workspace** — A Slack workspace can only be claimed by one Fling project at a time.
213
+
214
+ 4. **Plugin must be installed first** — Run `fling plugin install slack` before using any Slack features. Check with `fling plugin permissions slack`.