mcp-lab-agent 2.1.4 → 2.1.6
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.
- package/README.md +190 -224
- package/dist/index.js +436 -20
- package/dist/index.js.map +1 -1
- package/learning-hub/README.md +66 -0
- package/learning-hub/package.json +17 -0
- package/learning-hub/src/dashboard.html +73 -0
- package/learning-hub/src/server.js +129 -0
- package/learning-hub/src/store.js +114 -0
- package/package.json +8 -5
- package/slack-bot/.env.example +17 -2
- package/slack-bot/CREDENTIALS.md +23 -0
- package/slack-bot/README.md +71 -16
- package/slack-bot/TROUBLESHOOTING.md +73 -0
- package/slack-bot/check-config.js +80 -37
- package/slack-bot/setup.js +14 -8
- package/slack-bot/src/config.js +18 -8
- package/slack-bot/src/index.js +46 -12
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Troubleshooting — QA Lab Slack Bot
|
|
2
|
+
|
|
3
|
+
## Bot não responde quando envio mensagem
|
|
4
|
+
|
|
5
|
+
### 1. Bot foi convidado para o canal?
|
|
6
|
+
|
|
7
|
+
**Muito comum.** O bot precisa estar no canal para receber menções.
|
|
8
|
+
|
|
9
|
+
- No canal: digite `/invite @NomeDoSeuBot`
|
|
10
|
+
- Ou mencione em DM: abra DM com o bot e teste `@Bot analise o projeto`
|
|
11
|
+
|
|
12
|
+
### 2. Configuração no Slack (api.slack.com)
|
|
13
|
+
|
|
14
|
+
Seguindo a [documentação oficial](https://docs.slack.dev/app-management/quickstart-app-settings):
|
|
15
|
+
|
|
16
|
+
#### Se usa **Socket Mode** (PC corporativo, sem ngrok):
|
|
17
|
+
|
|
18
|
+
1. **Socket Mode** → Enable Socket Mode → ON
|
|
19
|
+
2. **Basic Information** → App-Level Tokens → Generate → scope: `connections:write` → copie (`xapp-...`)
|
|
20
|
+
3. **Event Subscriptions** → Enable Events → Subscribe to bot events → `app_mention`
|
|
21
|
+
4. **OAuth & Permissions** → Bot Token Scopes: `app_mentions:read`, `chat:write`, `channels:read`, `channels:history`
|
|
22
|
+
5. **Install App** → **Reinstall to Workspace** (obrigatório após alterar scopes ou eventos)
|
|
23
|
+
|
|
24
|
+
#### Se usa **HTTP** (ngrok):
|
|
25
|
+
|
|
26
|
+
1. **Event Subscriptions** → Enable Events → Request URL: `https://SEU_NGROK.ngrok.io/slack/events`
|
|
27
|
+
2. Subscribe to bot events: `app_mention`
|
|
28
|
+
3. **Basic Information** → App Credentials → Signing Secret (Show)
|
|
29
|
+
|
|
30
|
+
### 3. Config correta no mcp.json
|
|
31
|
+
|
|
32
|
+
O `~/.cursor/mcp.json` precisa ter a seção `qa-lab-agent.slack`:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"qa-lab-agent": {
|
|
37
|
+
"slack": {
|
|
38
|
+
"botToken": "xoxb-...",
|
|
39
|
+
"appToken": "xapp-...",
|
|
40
|
+
"useLocal": true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- `botToken` — OAuth & Permissions → OAuth Tokens → Bot User OAuth Token (começa com `xoxb-`)
|
|
47
|
+
- `appToken` — Basic Information → App-Level Tokens (scope `connections:write`, começa com `xapp-`) — só para Socket Mode
|
|
48
|
+
- `useLocal: true` — analisa o projeto local (pasta atual) em vez de clonar um repo
|
|
49
|
+
|
|
50
|
+
### 4. Rodar o diagnóstico
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
cd slack-bot
|
|
54
|
+
npm run check
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Corrija qualquer item marcado com ❌.
|
|
58
|
+
|
|
59
|
+
### 5. Conferir se o bot está rodando
|
|
60
|
+
|
|
61
|
+
Ao subir com `npm start`, deve aparecer:
|
|
62
|
+
|
|
63
|
+
- Socket Mode: `QA Lab Slack Bot rodando em Socket Mode (sem URL pública necessária)`
|
|
64
|
+
- HTTP: `QA Lab Slack Bot rodando em http://localhost:3000`
|
|
65
|
+
|
|
66
|
+
Se der erro ao iniciar, leia a mensagem — geralmente indica token inválido ou faltando.
|
|
67
|
+
|
|
68
|
+
### 6. Firewall / proxy corporativo
|
|
69
|
+
|
|
70
|
+
Em redes corporativas, às vezes o WebSocket (Socket Mode) é bloqueado. Tente:
|
|
71
|
+
|
|
72
|
+
- Socket Mode primeiro (não precisa de URL pública)
|
|
73
|
+
- Se não funcionar, use ngrok em outra máquina e configure HTTP
|
|
@@ -3,54 +3,74 @@
|
|
|
3
3
|
* Verifica se a config do Slack está correta.
|
|
4
4
|
* Rode: node check-config.js
|
|
5
5
|
*/
|
|
6
|
+
import { config } from "dotenv";
|
|
6
7
|
import { readFileSync, existsSync } from "node:fs";
|
|
7
8
|
import path from "node:path";
|
|
8
9
|
import { fileURLToPath } from "node:url";
|
|
9
10
|
|
|
10
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
// check-config.js fica em slack-bot/, então __dirname já é a pasta slack-bot
|
|
13
|
+
const SLACK_BOT_DIR = __dirname;
|
|
14
|
+
// Carrega .env na mesma ordem do config.js (QA_LAB_ENV override, cwd, slack-bot)
|
|
15
|
+
const envPaths = [
|
|
16
|
+
process.env.QA_LAB_ENV,
|
|
17
|
+
path.join(process.cwd(), ".env"),
|
|
18
|
+
path.join(SLACK_BOT_DIR, ".env"),
|
|
19
|
+
].filter(Boolean);
|
|
20
|
+
for (const p of envPaths) {
|
|
21
|
+
if (p && existsSync(p)) {
|
|
22
|
+
config({ path: p });
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function getMcpJsonPath() {
|
|
27
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
28
|
+
return home ? path.join(home, ".cursor", "mcp.json") : null;
|
|
19
29
|
}
|
|
20
|
-
|
|
30
|
+
const mcpPath = process.env.QA_LAB_MCP_CONFIG || getMcpJsonPath();
|
|
21
31
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
console.log("
|
|
27
|
-
|
|
32
|
+
console.log("\n🔧 QA Lab Slack Bot - Diagnóstico\n");
|
|
33
|
+
console.log("1. Origens de config (mcp.json ou .env):");
|
|
34
|
+
let mcp = null;
|
|
35
|
+
if (mcpPath && existsSync(mcpPath)) {
|
|
36
|
+
console.log(" ✅ mcp.json:", mcpPath);
|
|
37
|
+
try {
|
|
38
|
+
mcp = JSON.parse(readFileSync(mcpPath, "utf8"));
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.log(" ❌ Erro ao ler mcp.json:", e.message);
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
console.log(" ⊘ mcp.json não encontrado (use .env na pasta do slack-bot ou cwd)");
|
|
28
44
|
}
|
|
29
45
|
|
|
30
|
-
const slack = mcp?.["qa-lab-agent"]?.slack;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
console.log("
|
|
34
|
-
console.log(
|
|
35
|
-
process.exit(1);
|
|
46
|
+
const slack = mcp?.["qa-lab-agent"]?.slack || {};
|
|
47
|
+
const hasMcpSlack = !!mcp?.["qa-lab-agent"]?.slack;
|
|
48
|
+
if (!hasMcpSlack) {
|
|
49
|
+
console.log(" ⚠️ Seção 'qa-lab-agent.slack' não encontrada no mcp.json");
|
|
50
|
+
console.log(" Usando .env como fallback (SLACK_BOT_TOKEN, SLACK_APP_TOKEN ou SLACK_SIGNING_SECRET)");
|
|
36
51
|
}
|
|
37
|
-
console.log(" ✅ Config slack encontrada");
|
|
52
|
+
if (hasMcpSlack) console.log(" ✅ Config slack encontrada no mcp.json");
|
|
38
53
|
|
|
39
54
|
console.log("\n2. botToken:");
|
|
40
|
-
const token = slack.botToken || slack.SLACK_BOT_TOKEN;
|
|
55
|
+
const token = slack.botToken || slack.SLACK_BOT_TOKEN || process.env.SLACK_BOT_TOKEN;
|
|
41
56
|
if (!token) {
|
|
42
57
|
console.log(" ❌ Ausente. Adicione 'botToken' ou 'SLACK_BOT_TOKEN'");
|
|
43
58
|
} else if (!token.startsWith("xoxb-")) {
|
|
44
59
|
console.log(" ⚠️ Deve começar com 'xoxb-'. Você usou o Client Secret?");
|
|
45
|
-
console.log("
|
|
60
|
+
console.log(" Onde: OAuth & Permissions → OAuth Tokens → Bot User OAuth Token");
|
|
46
61
|
} else {
|
|
47
62
|
console.log(" ✅ OK (xoxb-...)");
|
|
48
63
|
}
|
|
49
64
|
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
const appToken = slack.appToken || slack.slack_app_token || slack.SLACK_APP_TOKEN || process.env.SLACK_APP_TOKEN;
|
|
66
|
+
const useSocketMode = !!(appToken && appToken.startsWith("xapp-"));
|
|
67
|
+
|
|
68
|
+
console.log("\n3. signingSecret (só para modo HTTP):");
|
|
69
|
+
const secret = slack.signingSecret || slack.SLACK_SIGNING_SECRET || process.env.SLACK_SIGNING_SECRET;
|
|
70
|
+
if (useSocketMode) {
|
|
71
|
+
console.log(" ⊘ Não necessário (você está usando Socket Mode)");
|
|
72
|
+
} else if (!secret) {
|
|
73
|
+
console.log(" ❌ Ausente. Adicione 'signingSecret' ou use appToken para Socket Mode");
|
|
54
74
|
console.log(" Onde: Basic Information → App Credentials → Signing Secret (Show)");
|
|
55
75
|
} else if (secret === "..." || secret.length < 20) {
|
|
56
76
|
console.log(" ⚠️ Parece placeholder ou inválido. Use o valor real do Signing Secret.");
|
|
@@ -58,17 +78,40 @@ if (!secret) {
|
|
|
58
78
|
console.log(" ✅ OK");
|
|
59
79
|
}
|
|
60
80
|
|
|
61
|
-
console.log("\n4.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
console.log("
|
|
81
|
+
console.log("\n4. appToken (slack_app_token) para Socket Mode:");
|
|
82
|
+
if (appToken) {
|
|
83
|
+
if (appToken.startsWith("xapp-")) {
|
|
84
|
+
console.log(" ✅ OK (xapp-...) — bot funcionará sem URL pública");
|
|
85
|
+
} else {
|
|
86
|
+
console.log(" ⚠️ Deve começar com 'xapp-'. Onde: Basic Information → App-Level Tokens (scope: connections:write)");
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
console.log(" ⊘ Não configurado. Onde: Basic Information → App-Level Tokens (scope: connections:write)");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log("\n5. Resumo do modo:");
|
|
93
|
+
if (useSocketMode) {
|
|
94
|
+
console.log(" ✅ Socket Mode (appToken xapp-) — não precisa de URL pública");
|
|
95
|
+
console.log(" • Em api.slack.com: Socket Mode → Enable");
|
|
96
|
+
console.log(" • Basic Information → App-Level Tokens → Generate com scope connections:write");
|
|
97
|
+
} else {
|
|
98
|
+
console.log(" HTTP (Event Subscriptions):");
|
|
99
|
+
console.log(" • Request URL: https://SEU_DOMINIO/slack/events");
|
|
100
|
+
console.log(" • Se local: ngrok http 3000");
|
|
101
|
+
console.log(" • Bot event: app_mention");
|
|
102
|
+
console.log(" 💡 Para PC corporativo: use appToken (Socket Mode) e não precisa de ngrok!");
|
|
103
|
+
}
|
|
65
104
|
|
|
66
|
-
console.log("\
|
|
67
|
-
console.log(" •
|
|
105
|
+
console.log("\n6. Bot no canal:");
|
|
106
|
+
console.log(" • /invite @NomeDoBot (obrigatório!) ou mencione em DM");
|
|
68
107
|
|
|
69
|
-
if (token &&
|
|
70
|
-
console.log("\n✅ Config
|
|
108
|
+
if (token && token.startsWith("xoxb-") && (useSocketMode || secret)) {
|
|
109
|
+
console.log("\n✅ Config OK. Rode: npm start");
|
|
110
|
+
console.log(" Modo:", useSocketMode ? "Socket (qualquer ambiente)" : "HTTP (ngrok)");
|
|
71
111
|
} else {
|
|
72
|
-
console.log("\n❌ Corrija os itens acima
|
|
112
|
+
console.log("\n❌ Corrija os itens acima.");
|
|
113
|
+
if (!token) console.log(" Dica: botToken ou SLACK_BOT_TOKEN obrigatório.");
|
|
114
|
+
if (!useSocketMode && !secret) console.log(" Dica: use appToken (Socket) OU signingSecret (HTTP).");
|
|
115
|
+
console.log(" Onde obter: slack-bot/CREDENTIALS.md | https://docs.slack.dev/app-management/quickstart-app-settings");
|
|
73
116
|
}
|
|
74
117
|
console.log("");
|
package/slack-bot/setup.js
CHANGED
|
@@ -14,21 +14,27 @@ console.log("\n🔧 QA Lab Slack Bot - Setup\n");
|
|
|
14
14
|
|
|
15
15
|
if (!existsSync(envPath)) {
|
|
16
16
|
copyFileSync(path.join(__dirname, ".env.example"), envPath);
|
|
17
|
-
console.log("✅ .env criado. Edite e preencha
|
|
18
|
-
console.log("
|
|
19
|
-
console.log("
|
|
17
|
+
console.log("✅ .env criado. Edite e preencha:\n");
|
|
18
|
+
console.log(" OBRIGATÓRIO: SLACK_BOT_TOKEN (xoxb-...)\n");
|
|
19
|
+
console.log(" Escolha UM modo:\n");
|
|
20
|
+
console.log(" • Socket Mode (qualquer ambiente): SLACK_APP_TOKEN (xapp-...)");
|
|
21
|
+
console.log(" • HTTP (ngrok): SLACK_SIGNING_SECRET\n");
|
|
20
22
|
process.exit(0);
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
const env = readFileSync(envPath, "utf8");
|
|
24
26
|
const hasToken = /SLACK_BOT_TOKEN=xoxb-/.test(env);
|
|
27
|
+
const hasAppToken = /SLACK_APP_TOKEN=xapp-/.test(env);
|
|
25
28
|
const hasSecret = /SLACK_SIGNING_SECRET=.{20,}/.test(env);
|
|
26
29
|
|
|
27
|
-
if (!hasToken
|
|
28
|
-
console.log("⚠️ .env existe mas
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
if (!hasToken) {
|
|
31
|
+
console.log("⚠️ .env existe mas falta SLACK_BOT_TOKEN (xoxb-...)\n");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
if (!hasAppToken && !hasSecret) {
|
|
35
|
+
console.log("⚠️ Configure um modo de conexão:");
|
|
36
|
+
console.log(" • Socket Mode (recomendado): SLACK_APP_TOKEN=xapp-...");
|
|
37
|
+
console.log(" • HTTP: SLACK_SIGNING_SECRET=...\n");
|
|
32
38
|
process.exit(1);
|
|
33
39
|
}
|
|
34
40
|
|
package/slack-bot/src/config.js
CHANGED
|
@@ -7,7 +7,19 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
7
7
|
const ROOT = path.resolve(__dirname, "../..");
|
|
8
8
|
const SLACK_BOT_DIR = path.dirname(__dirname);
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// Carrega .env de múltiplos locais (funciona em pessoal, corporativo, npx, etc.)
|
|
11
|
+
// Ordem: cwd primeiro (override), depois package dir, depois QA_LAB_ENV
|
|
12
|
+
const envPaths = [
|
|
13
|
+
process.env.QA_LAB_ENV,
|
|
14
|
+
path.join(process.cwd(), ".env"),
|
|
15
|
+
path.join(SLACK_BOT_DIR, ".env"),
|
|
16
|
+
].filter(Boolean);
|
|
17
|
+
for (const p of envPaths) {
|
|
18
|
+
if (p && existsSync(p)) {
|
|
19
|
+
config({ path: p });
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
11
23
|
|
|
12
24
|
function getMcpJsonPath() {
|
|
13
25
|
const home = process.env.HOME || process.env.USERPROFILE;
|
|
@@ -33,6 +45,7 @@ function getSlackConfigFromMcp() {
|
|
|
33
45
|
return {
|
|
34
46
|
id: slack.id || slack.channelId,
|
|
35
47
|
botToken: slack.botToken || slack.SLACK_BOT_TOKEN,
|
|
48
|
+
appToken: slack.appToken || slack.slack_app_token || slack.SLACK_APP_TOKEN,
|
|
36
49
|
signingSecret: slack.signingSecret || slack.SLACK_SIGNING_SECRET,
|
|
37
50
|
repo: slack.repo || slack.REPO_URL,
|
|
38
51
|
branch: slack.branch || slack.REPO_BRANCH || "main",
|
|
@@ -54,13 +67,10 @@ function loadSlackConfig() {
|
|
|
54
67
|
|
|
55
68
|
function getSlackTokens() {
|
|
56
69
|
const fromMcp = getSlackConfigFromMcp();
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
token: process.env.SLACK_BOT_TOKEN,
|
|
62
|
-
signingSecret: process.env.SLACK_SIGNING_SECRET,
|
|
63
|
-
};
|
|
70
|
+
const token = fromMcp?.botToken || process.env.SLACK_BOT_TOKEN;
|
|
71
|
+
const signingSecret = fromMcp?.signingSecret || process.env.SLACK_SIGNING_SECRET;
|
|
72
|
+
const appToken = fromMcp?.appToken || process.env.SLACK_APP_TOKEN;
|
|
73
|
+
return { token, signingSecret, appToken };
|
|
64
74
|
}
|
|
65
75
|
|
|
66
76
|
function getRepoForChannel() {
|
package/slack-bot/src/index.js
CHANGED
|
@@ -4,28 +4,62 @@
|
|
|
4
4
|
* Recebe @mentions no Slack, executa mcp-lab-agent e posta relatório.
|
|
5
5
|
*
|
|
6
6
|
* Config: ~/.cursor/mcp.json (qa-lab-agent.slack) ou .env
|
|
7
|
+
*
|
|
8
|
+
* Dois modos:
|
|
9
|
+
* 1) Socket Mode (recomendado para PC corporativo): botToken + appToken (slack_app_token)
|
|
10
|
+
* Não precisa de URL pública. Funciona atrás de firewall.
|
|
11
|
+
* 2) HTTP (Event Subscriptions): botToken + signingSecret
|
|
12
|
+
* Precisa de URL pública (ngrok) em api.slack.com.
|
|
7
13
|
*/
|
|
8
14
|
import { App } from "@slack/bolt";
|
|
9
15
|
import { getSlackTokens } from "./config.js";
|
|
10
16
|
import { registerAppMention } from "./handlers/app-mention.js";
|
|
11
17
|
|
|
12
|
-
const { token, signingSecret } = getSlackTokens();
|
|
18
|
+
const { token, signingSecret, appToken } = getSlackTokens();
|
|
19
|
+
|
|
20
|
+
const useSocketMode = !!(appToken && appToken.startsWith("xapp-"));
|
|
21
|
+
|
|
22
|
+
if (!token || !token.startsWith("xoxb-")) {
|
|
23
|
+
console.error(
|
|
24
|
+
"Configure botToken (xoxb-...) no ~/.cursor/mcp.json ou .env (SLACK_BOT_TOKEN).\n" +
|
|
25
|
+
"Onde: api.slack.com → sua app → OAuth & Permissions → Bot User OAuth Token"
|
|
26
|
+
);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
13
29
|
|
|
14
|
-
if (
|
|
15
|
-
|
|
30
|
+
if (useSocketMode) {
|
|
31
|
+
// Socket Mode: funciona sem URL pública (ideal para PC corporativo)
|
|
32
|
+
if (!appToken.startsWith("xapp-")) {
|
|
33
|
+
console.error("appToken (slack_app_token) deve começar com 'xapp-' para Socket Mode.");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
} else if (!signingSecret) {
|
|
37
|
+
console.error(
|
|
38
|
+
"Modo HTTP: configure signingSecret no ~/.cursor/mcp.json ou .env (SLACK_SIGNING_SECRET).\n" +
|
|
39
|
+
"Modo Socket: adicione appToken (xapp-...) para funcionar sem URL pública."
|
|
40
|
+
);
|
|
16
41
|
process.exit(1);
|
|
17
42
|
}
|
|
18
43
|
|
|
19
|
-
const
|
|
44
|
+
const appOptions = {
|
|
20
45
|
token,
|
|
21
|
-
|
|
22
|
-
}
|
|
46
|
+
...(useSocketMode
|
|
47
|
+
? { socketMode: true, appToken }
|
|
48
|
+
: { signingSecret }),
|
|
49
|
+
};
|
|
23
50
|
|
|
51
|
+
const app = new App(appOptions);
|
|
24
52
|
registerAppMention(app);
|
|
25
53
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
console.log(
|
|
29
|
-
console.log("
|
|
30
|
-
|
|
31
|
-
|
|
54
|
+
if (useSocketMode) {
|
|
55
|
+
await app.start();
|
|
56
|
+
console.log("QA Lab Slack Bot rodando em Socket Mode (sem URL pública necessária)");
|
|
57
|
+
console.log("Mencione o bot em um canal para usar.");
|
|
58
|
+
} else {
|
|
59
|
+
const port = process.env.PORT || 3000;
|
|
60
|
+
await app.start(port);
|
|
61
|
+
console.log(`QA Lab Slack Bot rodando em http://localhost:${port}`);
|
|
62
|
+
console.log("Configure Event Subscriptions em api.slack.com:");
|
|
63
|
+
console.log(` Request URL: https://SEU_DOMINIO/slack/events`);
|
|
64
|
+
console.log(" Bot event: app_mention");
|
|
65
|
+
}
|