cc-jandi 1.0.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.
@@ -0,0 +1,75 @@
1
+ import { BaseWebhookService } from './base/BaseWebhookService.js';
2
+ import { JandiColors, MessageType } from '../types/common.js';
3
+ export class IncomingWebhookService extends BaseWebhookService {
4
+ static DEFAULT_URL = 'https://wh.jandi.com/connect-api/webhook';
5
+ buildUrl(config) {
6
+ return IncomingWebhookService.getWebhookUrl(config);
7
+ }
8
+ static getWebhookUrl(config) {
9
+ if (config.url) {
10
+ return config.url;
11
+ }
12
+ return `${this.DEFAULT_URL}/${config.token}`;
13
+ }
14
+ static async sendMessage(config, message) {
15
+ const url = this.getWebhookUrl(config);
16
+ return this.sendRequest(url, message);
17
+ }
18
+ static async validateToken(token, customUrl) {
19
+ const testMessage = {
20
+ body: 'Token validation test message',
21
+ connectColor: JandiColors.GRAY
22
+ };
23
+ const config = { token, url: customUrl };
24
+ try {
25
+ const result = await this.sendMessage(config, testMessage);
26
+ if (result.success) {
27
+ return {
28
+ success: true,
29
+ message: 'Token is valid'
30
+ };
31
+ }
32
+ else {
33
+ return {
34
+ success: false,
35
+ error: `Token validation failed: ${result.error}`
36
+ };
37
+ }
38
+ }
39
+ catch (error) {
40
+ return {
41
+ success: false,
42
+ error: `Token validation error: ${error}`
43
+ };
44
+ }
45
+ }
46
+ static createBasicMessage(body) {
47
+ return { body };
48
+ }
49
+ static createRichMessage(body, color, connectInfo) {
50
+ return {
51
+ body,
52
+ connectColor: color || JandiColors.DEFAULT,
53
+ connectInfo
54
+ };
55
+ }
56
+ static createStatusMessage(body, type, additionalInfo) {
57
+ const colorMap = {
58
+ [MessageType.INFO]: JandiColors.BLUE,
59
+ [MessageType.SUCCESS]: JandiColors.GREEN,
60
+ [MessageType.WARNING]: JandiColors.YELLOW,
61
+ [MessageType.ERROR]: JandiColors.RED
62
+ };
63
+ const message = {
64
+ body,
65
+ connectColor: colorMap[type]
66
+ };
67
+ if (additionalInfo) {
68
+ message.connectInfo = [{
69
+ title: type.toUpperCase(),
70
+ description: additionalInfo
71
+ }];
72
+ }
73
+ return message;
74
+ }
75
+ }
@@ -0,0 +1,97 @@
1
+ import { BaseWebhookService } from './base/BaseWebhookService.js';
2
+ import { JandiColors } from '../types/common.js';
3
+ export class TeamIncomingWebhookService extends BaseWebhookService {
4
+ static DEFAULT_URL = 'https://wh.jandi.com/connect-api/team-webhook';
5
+ static MAX_EMAIL_COUNT = 100;
6
+ buildUrl(config) {
7
+ return TeamIncomingWebhookService.getWebhookUrl(config);
8
+ }
9
+ static getWebhookUrl(config) {
10
+ if (config.url) {
11
+ return config.url;
12
+ }
13
+ return `${this.DEFAULT_URL}/${config.teamId}/${config.token}`;
14
+ }
15
+ static validateEmails(email) {
16
+ const emails = email.split(',').map(e => e.trim()).filter(e => e.length > 0);
17
+ if (emails.length === 0) {
18
+ return { valid: false, error: 'At least one email address is required' };
19
+ }
20
+ if (emails.length > this.MAX_EMAIL_COUNT) {
21
+ return {
22
+ valid: false,
23
+ error: `Maximum ${this.MAX_EMAIL_COUNT} email addresses allowed (provided: ${emails.length})`
24
+ };
25
+ }
26
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
27
+ const invalidEmails = emails.filter(e => !emailRegex.test(e));
28
+ if (invalidEmails.length > 0) {
29
+ return {
30
+ valid: false,
31
+ error: `Invalid email format: ${invalidEmails.join(', ')}`
32
+ };
33
+ }
34
+ return { valid: true };
35
+ }
36
+ static async sendMessage(config, message) {
37
+ const emailValidation = this.validateEmails(message.email);
38
+ if (!emailValidation.valid) {
39
+ return {
40
+ success: false,
41
+ error: emailValidation.error
42
+ };
43
+ }
44
+ const url = this.getWebhookUrl(config);
45
+ // Jandi Team Incoming Webhook uses 'to' field instead of 'email'
46
+ const payload = {
47
+ body: message.body,
48
+ connectColor: message.connectColor,
49
+ connectInfo: message.connectInfo,
50
+ to: message.email
51
+ };
52
+ return this.sendRequest(url, payload);
53
+ }
54
+ static async validateToken(config) {
55
+ const testMessage = {
56
+ body: 'Team webhook token validation test',
57
+ connectColor: JandiColors.GRAY,
58
+ email: ''
59
+ };
60
+ // For validation, we send a minimal request to check if the endpoint responds
61
+ const url = this.getWebhookUrl(config);
62
+ try {
63
+ const result = await this.sendRequest(url, {
64
+ body: testMessage.body,
65
+ connectColor: testMessage.connectColor
66
+ });
67
+ // Even if the message fails due to missing email, a 400 vs 401/403 tells us token validity
68
+ if (result.success) {
69
+ return { success: true, message: 'Team webhook token is valid' };
70
+ }
71
+ else if (result.errorCode === 40000) {
72
+ return { success: false, error: `Team webhook token validation failed: ${result.error}` };
73
+ }
74
+ else {
75
+ // Non-auth errors might mean the token is valid but request was incomplete
76
+ return { success: true, message: 'Team webhook token appears valid (endpoint reachable)' };
77
+ }
78
+ }
79
+ catch (error) {
80
+ return {
81
+ success: false,
82
+ error: `Team token validation error: ${error}`
83
+ };
84
+ }
85
+ }
86
+ static createBasicMessage(body, email) {
87
+ return { body, email };
88
+ }
89
+ static createRichMessage(body, email, color, connectInfo) {
90
+ return {
91
+ body,
92
+ email,
93
+ connectColor: color || JandiColors.DEFAULT,
94
+ connectInfo
95
+ };
96
+ }
97
+ }
@@ -0,0 +1,90 @@
1
+ import axios from 'axios';
2
+ export class BaseWebhookService {
3
+ static HEADERS = {
4
+ 'Accept': 'application/vnd.tosslab.jandi-v2+json',
5
+ 'Content-Type': 'application/json'
6
+ };
7
+ static MAX_MESSAGE_LENGTH = 5000;
8
+ static MAX_DATA_SIZE = 256 * 1024; // 256KB
9
+ static REQUEST_TIMEOUT = 10000; // 10 seconds
10
+ static MAX_RETRIES = 3;
11
+ static RETRY_DELAYS = [5000, 15000, 30000]; // ms — Jandi rate limit is 60 req/min
12
+ static sleep(ms) {
13
+ return new Promise(resolve => setTimeout(resolve, ms));
14
+ }
15
+ static validateMessageLimits(message) {
16
+ if (message.body.length > this.MAX_MESSAGE_LENGTH) {
17
+ return {
18
+ valid: false,
19
+ error: `Message body exceeds maximum length of ${this.MAX_MESSAGE_LENGTH} characters (current: ${message.body.length})`
20
+ };
21
+ }
22
+ const dataSize = JSON.stringify(message).length;
23
+ if (dataSize > this.MAX_DATA_SIZE) {
24
+ return {
25
+ valid: false,
26
+ error: `Message data exceeds maximum size of ${this.MAX_DATA_SIZE} bytes (current: ${dataSize})`
27
+ };
28
+ }
29
+ return { valid: true };
30
+ }
31
+ static handleJandiError(error) {
32
+ if (error.response?.status === 429) {
33
+ return {
34
+ error: 'Rate limit exceeded. Jandi allows 60 requests/min and 500 requests/10min. Please wait and try again.',
35
+ errorCode: 42900,
36
+ rateLimited: true
37
+ };
38
+ }
39
+ if (error.response?.status === 400) {
40
+ const data = error.response.data;
41
+ if (data?.code === 40000) {
42
+ return {
43
+ error: 'Invalid webhook token format or inactive/deleted webhook',
44
+ errorCode: 40000
45
+ };
46
+ }
47
+ return {
48
+ error: 'Invalid request data format',
49
+ errorCode: 40000
50
+ };
51
+ }
52
+ return {
53
+ error: error.message || 'Unknown error occurred'
54
+ };
55
+ }
56
+ static async sendRequest(url, message) {
57
+ const validation = this.validateMessageLimits(message);
58
+ if (!validation.valid) {
59
+ return { success: false, error: validation.error };
60
+ }
61
+ let lastResult = { success: false, error: 'Max retries exceeded' };
62
+ for (let attempt = 0; attempt <= this.MAX_RETRIES; attempt++) {
63
+ if (attempt > 0) {
64
+ await this.sleep(this.RETRY_DELAYS[attempt - 1]);
65
+ }
66
+ try {
67
+ await axios.post(url, message, {
68
+ headers: this.HEADERS,
69
+ timeout: this.REQUEST_TIMEOUT
70
+ });
71
+ return { success: true, message: 'Message sent successfully' };
72
+ }
73
+ catch (error) {
74
+ const axiosError = error;
75
+ const errorInfo = this.handleJandiError(axiosError);
76
+ lastResult = {
77
+ success: false,
78
+ error: errorInfo.error,
79
+ errorCode: errorInfo.errorCode,
80
+ rateLimited: errorInfo.rateLimited
81
+ };
82
+ // Only retry on rate limit errors
83
+ if (!errorInfo.rateLimited) {
84
+ return lastResult;
85
+ }
86
+ }
87
+ }
88
+ return lastResult;
89
+ }
90
+ }
@@ -0,0 +1,125 @@
1
+ import * as dotenv from 'dotenv';
2
+ dotenv.config();
3
+ export class ConfigService {
4
+ static tokens = new Map();
5
+ static teamTokens = new Map();
6
+ static outgoingTokens = new Map();
7
+ static initialize() {
8
+ // Load default incoming token
9
+ const defaultToken = process.env.JANDI_TOKEN;
10
+ if (defaultToken) {
11
+ this.addToken('default', defaultToken);
12
+ }
13
+ // Load named incoming tokens (JANDI_TOKEN_DEV, JANDI_TOKEN_PROD, etc.)
14
+ Object.keys(process.env).forEach(key => {
15
+ if (key.startsWith('JANDI_TOKEN_') && key !== 'JANDI_TOKEN') {
16
+ const alias = key.replace('JANDI_TOKEN_', '').toLowerCase();
17
+ const token = process.env[key];
18
+ if (token) {
19
+ this.addToken(alias, token);
20
+ }
21
+ }
22
+ });
23
+ // Load custom URLs for incoming webhooks
24
+ Object.keys(process.env).forEach(key => {
25
+ if (key.startsWith('JANDI_URL_')) {
26
+ const alias = key.replace('JANDI_URL_', '').toLowerCase();
27
+ const url = process.env[key];
28
+ if (url && this.tokens.has(alias)) {
29
+ const config = this.tokens.get(alias);
30
+ config.url = url;
31
+ }
32
+ }
33
+ });
34
+ // Load team incoming tokens (JANDI_TEAM_ID_* + JANDI_TEAM_TOKEN_*)
35
+ const teamAliases = new Set();
36
+ Object.keys(process.env).forEach(key => {
37
+ if (key.startsWith('JANDI_TEAM_ID_')) {
38
+ teamAliases.add(key.replace('JANDI_TEAM_ID_', '').toLowerCase());
39
+ }
40
+ if (key.startsWith('JANDI_TEAM_TOKEN_')) {
41
+ teamAliases.add(key.replace('JANDI_TEAM_TOKEN_', '').toLowerCase());
42
+ }
43
+ });
44
+ teamAliases.forEach(alias => {
45
+ const teamId = process.env[`JANDI_TEAM_ID_${alias.toUpperCase()}`];
46
+ const token = process.env[`JANDI_TEAM_TOKEN_${alias.toUpperCase()}`];
47
+ if (teamId && token) {
48
+ const config = { teamId, token, alias };
49
+ const customUrl = process.env[`JANDI_TEAM_URL_${alias.toUpperCase()}`];
50
+ if (customUrl) {
51
+ config.url = customUrl;
52
+ }
53
+ this.teamTokens.set(alias, config);
54
+ }
55
+ });
56
+ // Load outgoing verification tokens (JANDI_OUTGOING_TOKEN_*)
57
+ Object.keys(process.env).forEach(key => {
58
+ if (key.startsWith('JANDI_OUTGOING_TOKEN_')) {
59
+ const alias = key.replace('JANDI_OUTGOING_TOKEN_', '').toLowerCase();
60
+ const token = process.env[key];
61
+ if (token) {
62
+ this.outgoingTokens.set(alias, token);
63
+ }
64
+ }
65
+ });
66
+ }
67
+ // --- Incoming Token Management ---
68
+ static addToken(alias, token, url) {
69
+ this.tokens.set(alias, {
70
+ token,
71
+ url,
72
+ alias
73
+ });
74
+ }
75
+ static getToken(alias) {
76
+ return this.tokens.get(alias) || null;
77
+ }
78
+ static getAllTokens() {
79
+ return new Map(this.tokens);
80
+ }
81
+ static hasToken(alias) {
82
+ return this.tokens.has(alias);
83
+ }
84
+ static removeToken(alias) {
85
+ return this.tokens.delete(alias);
86
+ }
87
+ static getTokenByValue(tokenValue) {
88
+ for (const [, config] of this.tokens) {
89
+ if (config.token === tokenValue) {
90
+ return config;
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+ static validateTokenFormat(token) {
96
+ const tokenRegex = /^[a-f0-9]{32}$/i;
97
+ return tokenRegex.test(token);
98
+ }
99
+ static listTokenAliases() {
100
+ return Array.from(this.tokens.keys());
101
+ }
102
+ // --- Team Incoming Token Management ---
103
+ static getTeamToken(alias) {
104
+ return this.teamTokens.get(alias) || null;
105
+ }
106
+ static hasTeamToken(alias) {
107
+ return this.teamTokens.has(alias);
108
+ }
109
+ static listTeamTokenAliases() {
110
+ return Array.from(this.teamTokens.keys());
111
+ }
112
+ static getAllTeamTokens() {
113
+ return new Map(this.teamTokens);
114
+ }
115
+ // --- Outgoing Token Management ---
116
+ static getOutgoingToken(alias) {
117
+ return this.outgoingTokens.get(alias) || null;
118
+ }
119
+ static hasOutgoingToken(alias) {
120
+ return this.outgoingTokens.has(alias);
121
+ }
122
+ static listOutgoingTokenAliases() {
123
+ return Array.from(this.outgoingTokens.keys());
124
+ }
125
+ }
@@ -0,0 +1,4 @@
1
+ export { BaseWebhookService } from './base/BaseWebhookService.js';
2
+ export { IncomingWebhookService } from './IncomingWebhookService.js';
3
+ export { TeamIncomingWebhookService } from './TeamIncomingWebhookService.js';
4
+ export { ConfigService } from './configService.js';
@@ -0,0 +1,232 @@
1
+ import { MCPTool } from "mcp-framework";
2
+ import { z } from "zod";
3
+ class GenerateWebhookScriptTool extends MCPTool {
4
+ name = "generate_webhook_script";
5
+ description = "Generate a script to send messages via Jandi webhooks. Supports both Incoming and Team Incoming webhook types in Python, Node.js, curl, and bash.";
6
+ schema = {
7
+ language: {
8
+ type: z.enum(['python', 'nodejs', 'curl', 'bash']),
9
+ description: "Programming language for the generated script",
10
+ },
11
+ webhookType: {
12
+ type: z.enum(['incoming', 'team-incoming']).optional(),
13
+ description: "Webhook type: 'incoming' (channel message) or 'team-incoming' (personal message). Default is 'incoming'",
14
+ },
15
+ token: {
16
+ type: z.string(),
17
+ description: "Jandi webhook token (32-character hexadecimal string)",
18
+ },
19
+ message: {
20
+ type: z.string(),
21
+ description: "The message content to send",
22
+ },
23
+ color: {
24
+ type: z.string().optional(),
25
+ description: "Hex color code for the message (e.g., '#FF0000')",
26
+ },
27
+ title: {
28
+ type: z.string().optional(),
29
+ description: "Title for the message attachment",
30
+ },
31
+ description: {
32
+ type: z.string().optional(),
33
+ description: "Description for the message attachment",
34
+ },
35
+ imageUrl: {
36
+ type: z.string().optional(),
37
+ description: "Image URL for the message attachment",
38
+ },
39
+ teamId: {
40
+ type: z.string().optional(),
41
+ description: "Jandi team ID (required for team-incoming webhook type)",
42
+ },
43
+ email: {
44
+ type: z.string().optional(),
45
+ description: "Comma-separated recipient emails (required for team-incoming webhook type)",
46
+ },
47
+ };
48
+ getBaseUrl(input) {
49
+ const type = input.webhookType || 'incoming';
50
+ if (type === 'team-incoming') {
51
+ return `https://wh.jandi.com/connect-api/team-webhook/${input.teamId}/${input.token}`;
52
+ }
53
+ return `https://wh.jandi.com/connect-api/webhook/${input.token}`;
54
+ }
55
+ buildPayload(input) {
56
+ const hasAttachment = input.color || input.title || input.description || input.imageUrl;
57
+ const data = { body: input.message };
58
+ if (input.webhookType === 'team-incoming' && input.email) {
59
+ data.to = input.email;
60
+ }
61
+ if (hasAttachment) {
62
+ if (input.color)
63
+ data.connectColor = input.color;
64
+ if (input.title || input.description || input.imageUrl) {
65
+ const info = {};
66
+ if (input.title)
67
+ info.title = input.title;
68
+ if (input.description)
69
+ info.description = input.description;
70
+ if (input.imageUrl)
71
+ info.imageUrl = input.imageUrl;
72
+ data.connectInfo = [info];
73
+ }
74
+ }
75
+ return data;
76
+ }
77
+ generatePythonScript(input) {
78
+ const url = this.getBaseUrl(input);
79
+ const payload = this.buildPayload(input);
80
+ const payloadStr = JSON.stringify(payload, null, 8).replace(/^/gm, ' ').trim();
81
+ return `#!/usr/bin/env python3
82
+ import requests
83
+ import json
84
+
85
+ def send_jandi_message():
86
+ url = "${url}"
87
+
88
+ headers = {
89
+ "Accept": "application/vnd.tosslab.jandi-v2+json",
90
+ "Content-Type": "application/json"
91
+ }
92
+
93
+ data = ${payloadStr}
94
+
95
+ try:
96
+ response = requests.post(url, headers=headers, data=json.dumps(data))
97
+ if response.status_code == 200:
98
+ print("Message sent successfully!")
99
+ else:
100
+ print(f"Error: {response.status_code} - {response.text}")
101
+ except Exception as e:
102
+ print(f"Error sending message: {e}")
103
+
104
+ if __name__ == "__main__":
105
+ send_jandi_message()
106
+ `;
107
+ }
108
+ generateNodejsScript(input) {
109
+ const url = this.getBaseUrl(input);
110
+ const payload = this.buildPayload(input);
111
+ const payloadStr = JSON.stringify(payload, null, 4);
112
+ return `const axios = require('axios');
113
+
114
+ async function sendJandiMessage() {
115
+ const url = '${url}';
116
+
117
+ const headers = {
118
+ 'Accept': 'application/vnd.tosslab.jandi-v2+json',
119
+ 'Content-Type': 'application/json'
120
+ };
121
+
122
+ const data = ${payloadStr};
123
+
124
+ try {
125
+ const response = await axios.post(url, data, { headers });
126
+ console.log('Message sent successfully!');
127
+ } catch (error) {
128
+ console.error('Error sending message:', error.response?.data || error.message);
129
+ }
130
+ }
131
+
132
+ sendJandiMessage();
133
+ `;
134
+ }
135
+ generateCurlScript(input) {
136
+ const url = this.getBaseUrl(input);
137
+ const payload = this.buildPayload(input);
138
+ const jsonData = JSON.stringify(payload);
139
+ return `#!/bin/bash
140
+ curl -X POST "${url}" \\
141
+ -H "Accept: application/vnd.tosslab.jandi-v2+json" \\
142
+ -H "Content-Type: application/json" \\
143
+ -d '${jsonData}'
144
+ `;
145
+ }
146
+ generateBashScript(input) {
147
+ const type = input.webhookType || 'incoming';
148
+ const payload = this.buildPayload(input);
149
+ const jsonData = JSON.stringify(payload).replace(/"/g, '\\"');
150
+ let urlSetup;
151
+ if (type === 'team-incoming') {
152
+ urlSetup = `TEAM_ID="${input.teamId}"
153
+ TOKEN="${input.token}"
154
+ URL="https://wh.jandi.com/connect-api/team-webhook/$TEAM_ID/$TOKEN"`;
155
+ }
156
+ else {
157
+ urlSetup = `TOKEN="${input.token}"
158
+ URL="https://wh.jandi.com/connect-api/webhook/$TOKEN"`;
159
+ }
160
+ return `#!/bin/bash
161
+
162
+ # Jandi ${type === 'team-incoming' ? 'Team Incoming' : 'Incoming'} Webhook Script
163
+ ${urlSetup}
164
+
165
+ JSON_DATA="${jsonData}"
166
+
167
+ # Send message
168
+ curl -X POST "$URL" \\
169
+ -H "Accept: application/vnd.tosslab.jandi-v2+json" \\
170
+ -H "Content-Type: application/json" \\
171
+ -d "$JSON_DATA"
172
+ `;
173
+ }
174
+ async execute(input) {
175
+ try {
176
+ const type = input.webhookType || 'incoming';
177
+ if (type === 'team-incoming') {
178
+ if (!input.teamId) {
179
+ return { success: false, error: "teamId is required for team-incoming webhook type" };
180
+ }
181
+ if (!input.email) {
182
+ return { success: false, error: "email is required for team-incoming webhook type" };
183
+ }
184
+ }
185
+ let script;
186
+ let fileExtension;
187
+ let executionInstructions;
188
+ switch (input.language) {
189
+ case 'python':
190
+ script = this.generatePythonScript(input);
191
+ fileExtension = '.py';
192
+ executionInstructions = 'python3 script.py (requires: pip install requests)';
193
+ break;
194
+ case 'nodejs':
195
+ script = this.generateNodejsScript(input);
196
+ fileExtension = '.js';
197
+ executionInstructions = 'node script.js (requires: npm install axios)';
198
+ break;
199
+ case 'curl':
200
+ script = this.generateCurlScript(input);
201
+ fileExtension = '.sh';
202
+ executionInstructions = 'bash script.sh (requires: curl)';
203
+ break;
204
+ case 'bash':
205
+ script = this.generateBashScript(input);
206
+ fileExtension = '.sh';
207
+ executionInstructions = 'bash script.sh (requires: curl)';
208
+ break;
209
+ }
210
+ return {
211
+ success: true,
212
+ data: {
213
+ language: input.language,
214
+ webhookType: type,
215
+ fileExtension,
216
+ executionInstructions,
217
+ script,
218
+ message: `Successfully generated ${input.language} script for Jandi ${type} webhook`,
219
+ features: {
220
+ hasColor: !!input.color,
221
+ hasAttachment: !!(input.title || input.description || input.imageUrl),
222
+ hasRecipients: !!input.email
223
+ }
224
+ }
225
+ };
226
+ }
227
+ catch (error) {
228
+ return { success: false, error: `Error generating script: ${error}` };
229
+ }
230
+ }
231
+ }
232
+ export default GenerateWebhookScriptTool;
@@ -0,0 +1,48 @@
1
+ import { MCPTool } from "mcp-framework";
2
+ import { z } from "zod";
3
+ import { IncomingWebhookService } from "../../services/IncomingWebhookService.js";
4
+ import { resolveIncomingToken } from "../../utils/resolveToken.js";
5
+ class SendMessageTool extends MCPTool {
6
+ name = "send_message";
7
+ description = "Send a basic text message to Jandi channel via Incoming Webhook";
8
+ schema = {
9
+ message: {
10
+ type: z.string(),
11
+ description: "The message text to send to Jandi",
12
+ },
13
+ token: {
14
+ type: z.string().optional(),
15
+ description: "Jandi webhook token (32-character hexadecimal string). If not provided, will use tokenAlias or default token",
16
+ },
17
+ tokenAlias: {
18
+ type: z.string().optional(),
19
+ description: "Token alias from configuration (e.g., 'default', 'dev', 'prod'). If not provided, will use 'default'",
20
+ },
21
+ };
22
+ async execute(input) {
23
+ try {
24
+ const resolved = resolveIncomingToken(input);
25
+ if (!resolved.success) {
26
+ return { success: false, error: resolved.error };
27
+ }
28
+ const message = IncomingWebhookService.createBasicMessage(input.message);
29
+ const result = await IncomingWebhookService.sendMessage(resolved.config, message);
30
+ if (result.success) {
31
+ return {
32
+ success: true,
33
+ data: {
34
+ message: "Message sent successfully to Jandi",
35
+ tokenUsed: resolved.config.alias || 'direct'
36
+ }
37
+ };
38
+ }
39
+ else {
40
+ return { success: false, error: result.error };
41
+ }
42
+ }
43
+ catch (error) {
44
+ return { success: false, error: `Unexpected error: ${error}` };
45
+ }
46
+ }
47
+ }
48
+ export default SendMessageTool;