openpets 1.0.5 → 1.0.7
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/dist/data/api.json +6658 -0
- package/dist/src/core/ai-client-base/index.d.ts +47 -0
- package/dist/src/core/ai-client-base/index.d.ts.map +1 -0
- package/dist/src/core/ai-client-base/index.js +168 -0
- package/dist/src/core/ai-client-base/index.js.map +1 -0
- package/dist/src/core/browser.d.ts +10 -0
- package/dist/src/core/browser.d.ts.map +1 -0
- package/{browser.ts → dist/src/core/browser.js} +4 -4
- package/dist/src/core/browser.js.map +1 -0
- package/dist/src/core/build-pet.d.ts +2 -0
- package/dist/src/core/build-pet.d.ts.map +1 -0
- package/dist/src/core/build-pet.js +392 -0
- package/dist/src/core/build-pet.js.map +1 -0
- package/dist/src/core/cli.d.ts +3 -0
- package/dist/src/core/cli.d.ts.map +1 -0
- package/dist/src/core/cli.js +244 -0
- package/dist/src/core/cli.js.map +1 -0
- package/dist/src/core/config-manager.d.ts +13 -0
- package/dist/src/core/config-manager.d.ts.map +1 -0
- package/dist/src/core/config-manager.js +59 -0
- package/dist/src/core/config-manager.js.map +1 -0
- package/dist/src/core/deploy-pet.d.ts +2 -0
- package/dist/src/core/deploy-pet.d.ts.map +1 -0
- package/dist/src/core/deploy-pet.js +66 -0
- package/dist/src/core/deploy-pet.js.map +1 -0
- package/dist/src/core/index.d.ts +12 -0
- package/dist/src/core/index.d.ts.map +1 -0
- package/dist/src/core/index.js +12 -0
- package/dist/src/core/index.js.map +1 -0
- package/dist/src/core/local-cache.d.ts +69 -0
- package/dist/src/core/local-cache.d.ts.map +1 -0
- package/dist/src/core/local-cache.js +212 -0
- package/dist/src/core/local-cache.js.map +1 -0
- package/dist/src/core/logger.d.ts.map +1 -0
- package/{logger.js → dist/src/core/logger.js} +8 -9
- package/dist/src/core/logger.js.map +1 -0
- package/dist/src/core/mautrix-bridge.d.ts +93 -0
- package/dist/src/core/mautrix-bridge.d.ts.map +1 -0
- package/dist/src/core/mautrix-bridge.js +1046 -0
- package/dist/src/core/mautrix-bridge.js.map +1 -0
- package/dist/src/core/mcp-factory.d.ts +12 -0
- package/dist/src/core/mcp-factory.d.ts.map +1 -0
- package/dist/src/core/mcp-factory.js +143 -0
- package/dist/src/core/mcp-factory.js.map +1 -0
- package/dist/src/core/mcp-server.d.ts +3 -0
- package/dist/src/core/mcp-server.d.ts.map +1 -0
- package/dist/src/core/mcp-server.js +55 -0
- package/dist/src/core/mcp-server.js.map +1 -0
- package/dist/src/core/migrate-plugin.d.ts +15 -0
- package/dist/src/core/migrate-plugin.d.ts.map +1 -0
- package/dist/src/core/migrate-plugin.js +181 -0
- package/dist/src/core/migrate-plugin.js.map +1 -0
- package/dist/src/core/pets-registry.d.ts +47 -0
- package/dist/src/core/pets-registry.d.ts.map +1 -0
- package/dist/src/core/pets-registry.js +109 -0
- package/dist/src/core/pets-registry.js.map +1 -0
- package/dist/src/core/plugin-factory.d.ts +58 -0
- package/dist/src/core/plugin-factory.d.ts.map +1 -0
- package/dist/src/core/plugin-factory.js +212 -0
- package/dist/src/core/plugin-factory.js.map +1 -0
- package/dist/src/core/prompt-utils.d.ts +14 -0
- package/dist/src/core/prompt-utils.d.ts.map +1 -0
- package/dist/src/core/prompt-utils.js +106 -0
- package/dist/src/core/prompt-utils.js.map +1 -0
- package/dist/src/core/schema-helpers.d.ts +30 -0
- package/dist/src/core/schema-helpers.d.ts.map +1 -0
- package/dist/src/core/schema-helpers.js +46 -0
- package/dist/src/core/schema-helpers.js.map +1 -0
- package/dist/src/core/search-pets.d.ts +29 -0
- package/dist/src/core/search-pets.d.ts.map +1 -0
- package/dist/src/core/search-pets.js +196 -0
- package/dist/src/core/search-pets.js.map +1 -0
- package/dist/src/core/types.d.ts +63 -0
- package/dist/src/core/types.d.ts.map +1 -0
- package/dist/src/core/types.js +2 -0
- package/dist/src/core/types.js.map +1 -0
- package/dist/src/core/validate-pet.d.ts +40 -0
- package/dist/src/core/validate-pet.d.ts.map +1 -0
- package/dist/src/core/validate-pet.js +650 -0
- package/dist/src/core/validate-pet.js.map +1 -0
- package/package.json +15 -28
- package/ai-client-base/index.ts +0 -229
- package/build-pet.ts +0 -429
- package/cli.ts +0 -268
- package/config-manager.ts +0 -82
- package/deploy-pet.ts +0 -91
- package/index.ts +0 -10
- package/local-cache.ts +0 -280
- package/logger.ts +0 -143
- package/mcp-factory.ts +0 -180
- package/mcp-server.ts +0 -69
- package/migrate-plugin.ts +0 -220
- package/pets-registry.ts +0 -160
- package/plugin-factory.ts +0 -300
- package/prompt-utils.ts +0 -130
- package/schema-helpers.ts +0 -59
- package/search-pets.ts +0 -267
- package/types.ts +0 -68
- package/validate-pet.ts +0 -749
- /package/{logger.d.ts → dist/src/core/logger.d.ts} +0 -0
|
@@ -0,0 +1,1046 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { exec } from "child_process";
|
|
3
|
+
import { promisify } from "util";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
export function createBridgeConfig(bridgeType, bridgeUrl, sharedSecret) {
|
|
8
|
+
return {
|
|
9
|
+
baseUrl: bridgeUrl || "",
|
|
10
|
+
sharedSecret: sharedSecret || "",
|
|
11
|
+
enabled: !!(bridgeUrl && sharedSecret),
|
|
12
|
+
bridgeType
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function createBridgeFetcher(config) {
|
|
16
|
+
return async (endpoint, options = {}) => {
|
|
17
|
+
if (!config.enabled) {
|
|
18
|
+
throw new Error(`Mautrix ${config.bridgeType} bridge not configured. Set MAUTRIX_BRIDGE_URL and MAUTRIX_SHARED_SECRET.`);
|
|
19
|
+
}
|
|
20
|
+
const url = `${config.baseUrl}/_matrix/provision${endpoint}`;
|
|
21
|
+
const response = await fetch(url, {
|
|
22
|
+
...options,
|
|
23
|
+
headers: {
|
|
24
|
+
"Authorization": `Bearer ${config.sharedSecret}`,
|
|
25
|
+
"Content-Type": "application/json",
|
|
26
|
+
...options.headers
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
31
|
+
throw new Error(`Bridge API error: ${response.status} - ${error.error || error.errcode || JSON.stringify(error)}`);
|
|
32
|
+
}
|
|
33
|
+
return response.json();
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function createBridgeStatusTool(options) {
|
|
37
|
+
const { config, logger, serviceName, serviceNameLower } = options;
|
|
38
|
+
const fetchBridge = createBridgeFetcher(config);
|
|
39
|
+
return {
|
|
40
|
+
name: `${serviceNameLower}-bridge-status`,
|
|
41
|
+
description: `Check the status of the mautrix-${serviceNameLower} Matrix bridge connection. Use this to see if the bridge is configured and if you're logged in to ${serviceName} through it.`,
|
|
42
|
+
schema: z.object({
|
|
43
|
+
user_id: z.string().optional().describe("Matrix user ID to check status for (defaults to configured user)")
|
|
44
|
+
}),
|
|
45
|
+
async execute(args) {
|
|
46
|
+
if (!config.enabled) {
|
|
47
|
+
return JSON.stringify({
|
|
48
|
+
success: false,
|
|
49
|
+
error: `Mautrix ${serviceNameLower} bridge not configured`,
|
|
50
|
+
help: {
|
|
51
|
+
message: `Set MAUTRIX_BRIDGE_URL and MAUTRIX_SHARED_SECRET to enable Matrix bridge integration`,
|
|
52
|
+
example: {
|
|
53
|
+
MAUTRIX_BRIDGE_URL: `https://${serviceNameLower}-bridge.example.com`,
|
|
54
|
+
MAUTRIX_SHARED_SECRET: "your-shared-secret"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}, null, 2);
|
|
58
|
+
}
|
|
59
|
+
logger.info("Checking bridge status", { user_id: args.user_id });
|
|
60
|
+
try {
|
|
61
|
+
const params = args.user_id ? `?user_id=${encodeURIComponent(args.user_id)}` : "";
|
|
62
|
+
const status = await fetchBridge(`/v1/whoami${params}`);
|
|
63
|
+
const remoteAccount = status[serviceNameLower];
|
|
64
|
+
return JSON.stringify({
|
|
65
|
+
success: true,
|
|
66
|
+
data: {
|
|
67
|
+
bridge_url: config.baseUrl,
|
|
68
|
+
bridge_type: config.bridgeType,
|
|
69
|
+
matrix_user: status.mxid,
|
|
70
|
+
[`${serviceNameLower}_account`]: remoteAccount || null,
|
|
71
|
+
logged_in: status.logged_in,
|
|
72
|
+
status: status.logged_in ? `Connected to ${serviceName}` : `Not logged in to ${serviceName}`
|
|
73
|
+
}
|
|
74
|
+
}, null, 2);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
logger.error("Error checking bridge status", { error: error instanceof Error ? error.message : String(error) });
|
|
78
|
+
return JSON.stringify({
|
|
79
|
+
success: false,
|
|
80
|
+
error: error instanceof Error ? error.message : String(error)
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export function createBridgeLogoutTool(options) {
|
|
87
|
+
const { config, logger, serviceName, serviceNameLower } = options;
|
|
88
|
+
const fetchBridge = createBridgeFetcher(config);
|
|
89
|
+
return {
|
|
90
|
+
name: `${serviceNameLower}-bridge-logout`,
|
|
91
|
+
description: `Log out from ${serviceName} through the mautrix bridge. This will disconnect your ${serviceName} account from the Matrix bridge.`,
|
|
92
|
+
schema: z.object({
|
|
93
|
+
user_id: z.string().optional().describe("Matrix user ID to log out (defaults to configured user)")
|
|
94
|
+
}),
|
|
95
|
+
async execute(args) {
|
|
96
|
+
if (!config.enabled) {
|
|
97
|
+
return JSON.stringify({
|
|
98
|
+
success: false,
|
|
99
|
+
error: `Mautrix ${serviceNameLower} bridge not configured. Set MAUTRIX_BRIDGE_URL and MAUTRIX_SHARED_SECRET.`
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
logger.info("Logging out from bridge", { user_id: args.user_id });
|
|
103
|
+
try {
|
|
104
|
+
const body = {};
|
|
105
|
+
if (args.user_id)
|
|
106
|
+
body.user_id = args.user_id;
|
|
107
|
+
await fetchBridge("/v1/logout", {
|
|
108
|
+
method: "POST",
|
|
109
|
+
body: JSON.stringify(body)
|
|
110
|
+
});
|
|
111
|
+
return JSON.stringify({
|
|
112
|
+
success: true,
|
|
113
|
+
message: `Successfully logged out from ${serviceName} through the bridge`
|
|
114
|
+
}, null, 2);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
logger.error("Error logging out", { error: error instanceof Error ? error.message : String(error) });
|
|
118
|
+
return JSON.stringify({
|
|
119
|
+
success: false,
|
|
120
|
+
error: error instanceof Error ? error.message : String(error)
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
export function createDiscordBridgeTools(options) {
|
|
127
|
+
const { config, logger } = options;
|
|
128
|
+
const fetchBridge = createBridgeFetcher(config);
|
|
129
|
+
const baseOptions = { ...options, serviceName: "Discord", serviceNameLower: "discord" };
|
|
130
|
+
const qrLoginTool = {
|
|
131
|
+
name: "discord-bridge-login-qr",
|
|
132
|
+
description: "Start QR code login flow for Discord through the mautrix bridge. This will generate a QR code that you can scan with the Discord mobile app to authenticate.",
|
|
133
|
+
schema: z.object({
|
|
134
|
+
user_id: z.string().optional().describe("Matrix user ID to log in (defaults to configured user)")
|
|
135
|
+
}),
|
|
136
|
+
async execute(args) {
|
|
137
|
+
if (!config.enabled) {
|
|
138
|
+
return JSON.stringify({
|
|
139
|
+
success: false,
|
|
140
|
+
error: "Mautrix discord bridge not configured. Set MAUTRIX_BRIDGE_URL and MAUTRIX_SHARED_SECRET."
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
logger.info("Starting QR login flow", { user_id: args.user_id });
|
|
144
|
+
try {
|
|
145
|
+
const body = {};
|
|
146
|
+
if (args.user_id)
|
|
147
|
+
body.user_id = args.user_id;
|
|
148
|
+
const result = await fetchBridge("/v1/login/qr", {
|
|
149
|
+
method: "POST",
|
|
150
|
+
body: JSON.stringify(body)
|
|
151
|
+
});
|
|
152
|
+
return JSON.stringify({
|
|
153
|
+
success: true,
|
|
154
|
+
data: {
|
|
155
|
+
status: result.status,
|
|
156
|
+
qr_code: result.qr,
|
|
157
|
+
instructions: [
|
|
158
|
+
"1. Open the Discord mobile app",
|
|
159
|
+
"2. Go to Settings > Scan QR Code",
|
|
160
|
+
"3. Scan the QR code displayed above",
|
|
161
|
+
"4. Approve the login request on your phone",
|
|
162
|
+
"Note: If you encounter a CAPTCHA, you'll need to use token login instead"
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
}, null, 2);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
logger.error("Error starting QR login", { error: error instanceof Error ? error.message : String(error) });
|
|
169
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
170
|
+
return JSON.stringify({
|
|
171
|
+
success: false,
|
|
172
|
+
error: errorMsg,
|
|
173
|
+
fallback: errorMsg.includes("CAPTCHA") ? {
|
|
174
|
+
message: "CAPTCHA detected. Use token login instead.",
|
|
175
|
+
command: "Use discord-bridge-login-token with your Discord token"
|
|
176
|
+
} : undefined
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const tokenLoginTool = {
|
|
182
|
+
name: "discord-bridge-login-token",
|
|
183
|
+
description: "Log in to Discord through the mautrix bridge using a Discord token. Use this if QR login fails due to CAPTCHA.",
|
|
184
|
+
schema: z.object({
|
|
185
|
+
token: z.string().describe("Discord token (user token or bot token)"),
|
|
186
|
+
token_type: z.enum(["user", "bot"]).default("user").describe("Type of token: 'user' for personal account, 'bot' for bot account"),
|
|
187
|
+
user_id: z.string().optional().describe("Matrix user ID to log in (defaults to configured user)")
|
|
188
|
+
}),
|
|
189
|
+
async execute(args) {
|
|
190
|
+
if (!config.enabled) {
|
|
191
|
+
return JSON.stringify({
|
|
192
|
+
success: false,
|
|
193
|
+
error: "Mautrix discord bridge not configured. Set MAUTRIX_BRIDGE_URL and MAUTRIX_SHARED_SECRET."
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
logger.info("Starting token login", { token_type: args.token_type, user_id: args.user_id });
|
|
197
|
+
try {
|
|
198
|
+
const body = { token: args.token, type: args.token_type };
|
|
199
|
+
if (args.user_id)
|
|
200
|
+
body.user_id = args.user_id;
|
|
201
|
+
const result = await fetchBridge("/v1/login/token", {
|
|
202
|
+
method: "POST",
|
|
203
|
+
body: JSON.stringify(body)
|
|
204
|
+
});
|
|
205
|
+
return JSON.stringify({
|
|
206
|
+
success: true,
|
|
207
|
+
data: {
|
|
208
|
+
status: result.status || "Login successful",
|
|
209
|
+
token_type: args.token_type,
|
|
210
|
+
message: args.token_type === "bot"
|
|
211
|
+
? "Bot account connected. Add the bot to servers using OAuth2 URL."
|
|
212
|
+
: "User account connected. You can now access your Discord servers through the bridge."
|
|
213
|
+
}
|
|
214
|
+
}, null, 2);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
logger.error("Error with token login", { error: error instanceof Error ? error.message : String(error) });
|
|
218
|
+
return JSON.stringify({
|
|
219
|
+
success: false,
|
|
220
|
+
error: error instanceof Error ? error.message : String(error),
|
|
221
|
+
help: {
|
|
222
|
+
user_token: {
|
|
223
|
+
steps: [
|
|
224
|
+
"1. Log into Discord in a browser (private window recommended)",
|
|
225
|
+
"2. Open Developer Tools (F12 or Cmd+Shift+I)",
|
|
226
|
+
"3. Go to Network tab, filter by 'api'",
|
|
227
|
+
"4. Reload the page and click any successful request",
|
|
228
|
+
"5. Find 'Authorization' header in Request Headers",
|
|
229
|
+
"6. Copy the token value (without 'Bearer')"
|
|
230
|
+
]
|
|
231
|
+
},
|
|
232
|
+
bot_token: {
|
|
233
|
+
steps: [
|
|
234
|
+
"1. Go to https://discord.com/developers/applications",
|
|
235
|
+
"2. Create or select your application",
|
|
236
|
+
"3. Go to 'Bot' section",
|
|
237
|
+
"4. Click 'Reset Token' and copy the new token"
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
const guildsTool = {
|
|
246
|
+
name: "discord-bridge-guilds",
|
|
247
|
+
description: "List Discord guilds/servers accessible through the mautrix bridge.",
|
|
248
|
+
schema: z.object({
|
|
249
|
+
user_id: z.string().optional().describe("Matrix user ID (defaults to configured user)")
|
|
250
|
+
}),
|
|
251
|
+
async execute(args) {
|
|
252
|
+
if (!config.enabled) {
|
|
253
|
+
return JSON.stringify({
|
|
254
|
+
success: false,
|
|
255
|
+
error: "Mautrix discord bridge not configured. Set MAUTRIX_BRIDGE_URL and MAUTRIX_SHARED_SECRET."
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
logger.info("Listing guilds through bridge", { user_id: args.user_id });
|
|
259
|
+
try {
|
|
260
|
+
const params = args.user_id ? `?user_id=${encodeURIComponent(args.user_id)}` : "";
|
|
261
|
+
const guilds = await fetchBridge(`/v1/guilds${params}`);
|
|
262
|
+
return JSON.stringify({
|
|
263
|
+
success: true,
|
|
264
|
+
data: {
|
|
265
|
+
guilds: guilds.map(g => ({
|
|
266
|
+
id: g.id,
|
|
267
|
+
name: g.name,
|
|
268
|
+
icon: g.icon ? `https://cdn.discordapp.com/icons/${g.id}/${g.icon}.png` : null
|
|
269
|
+
})),
|
|
270
|
+
count: guilds.length
|
|
271
|
+
}
|
|
272
|
+
}, null, 2);
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
logger.error("Error listing guilds", { error: error instanceof Error ? error.message : String(error) });
|
|
276
|
+
return JSON.stringify({
|
|
277
|
+
success: false,
|
|
278
|
+
error: error instanceof Error ? error.message : String(error)
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
const connectGuildTool = {
|
|
284
|
+
name: "discord-bridge-connect-guild",
|
|
285
|
+
description: "Bridge a Discord guild/server to Matrix. This creates Matrix rooms for the Discord channels.",
|
|
286
|
+
schema: z.object({
|
|
287
|
+
guild_id: z.string().describe("Discord guild ID to bridge"),
|
|
288
|
+
user_id: z.string().optional().describe("Matrix user ID (defaults to configured user)")
|
|
289
|
+
}),
|
|
290
|
+
async execute(args) {
|
|
291
|
+
if (!config.enabled) {
|
|
292
|
+
return JSON.stringify({
|
|
293
|
+
success: false,
|
|
294
|
+
error: "Mautrix discord bridge not configured. Set MAUTRIX_BRIDGE_URL and MAUTRIX_SHARED_SECRET."
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
logger.info("Connecting guild to bridge", { guild_id: args.guild_id, user_id: args.user_id });
|
|
298
|
+
try {
|
|
299
|
+
const body = { guild_id: args.guild_id };
|
|
300
|
+
if (args.user_id)
|
|
301
|
+
body.user_id = args.user_id;
|
|
302
|
+
const result = await fetchBridge("/v1/guilds/bridge", {
|
|
303
|
+
method: "POST",
|
|
304
|
+
body: JSON.stringify(body)
|
|
305
|
+
});
|
|
306
|
+
return JSON.stringify({
|
|
307
|
+
success: true,
|
|
308
|
+
data: {
|
|
309
|
+
guild_id: args.guild_id,
|
|
310
|
+
matrix_space: result.room_id,
|
|
311
|
+
channels_bridged: result.channels,
|
|
312
|
+
message: "Guild connected. Discord channels are now bridged to Matrix rooms."
|
|
313
|
+
}
|
|
314
|
+
}, null, 2);
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
logger.error("Error connecting guild", { error: error instanceof Error ? error.message : String(error) });
|
|
318
|
+
return JSON.stringify({
|
|
319
|
+
success: false,
|
|
320
|
+
error: error instanceof Error ? error.message : String(error)
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
return [
|
|
326
|
+
createBridgeStatusTool(baseOptions),
|
|
327
|
+
qrLoginTool,
|
|
328
|
+
tokenLoginTool,
|
|
329
|
+
createBridgeLogoutTool(baseOptions),
|
|
330
|
+
guildsTool,
|
|
331
|
+
connectGuildTool
|
|
332
|
+
];
|
|
333
|
+
}
|
|
334
|
+
export function createTelegramBridgeTools(options) {
|
|
335
|
+
const { config, logger } = options;
|
|
336
|
+
const fetchBridge = createBridgeFetcher(config);
|
|
337
|
+
const baseOptions = { ...options, serviceName: "Telegram", serviceNameLower: "telegram" };
|
|
338
|
+
const phoneLoginTool = {
|
|
339
|
+
name: "telegram-bridge-login-phone",
|
|
340
|
+
description: "Start phone number login flow for Telegram through the mautrix bridge. You will receive a code via Telegram.",
|
|
341
|
+
schema: z.object({
|
|
342
|
+
phone: z.string().describe("Phone number in international format (e.g., +1234567890)"),
|
|
343
|
+
user_id: z.string().optional().describe("Matrix user ID to log in (defaults to configured user)")
|
|
344
|
+
}),
|
|
345
|
+
async execute(args) {
|
|
346
|
+
if (!config.enabled) {
|
|
347
|
+
return JSON.stringify({
|
|
348
|
+
success: false,
|
|
349
|
+
error: "Mautrix telegram bridge not configured. Set MAUTRIX_BRIDGE_URL and MAUTRIX_SHARED_SECRET."
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
logger.info("Starting phone login flow", { user_id: args.user_id });
|
|
353
|
+
try {
|
|
354
|
+
const body = { phone: args.phone };
|
|
355
|
+
if (args.user_id)
|
|
356
|
+
body.user_id = args.user_id;
|
|
357
|
+
const result = await fetchBridge("/v1/login/request_code", {
|
|
358
|
+
method: "POST",
|
|
359
|
+
body: JSON.stringify(body)
|
|
360
|
+
});
|
|
361
|
+
return JSON.stringify({
|
|
362
|
+
success: true,
|
|
363
|
+
data: {
|
|
364
|
+
status: result.status || "code_requested",
|
|
365
|
+
next_step: result.next_step || "enter_code",
|
|
366
|
+
instructions: [
|
|
367
|
+
"1. You will receive a code via Telegram (in-app notification or SMS)",
|
|
368
|
+
"2. Use telegram-bridge-login-code to submit the code",
|
|
369
|
+
"3. If 2FA is enabled, you'll need to provide your password afterwards"
|
|
370
|
+
]
|
|
371
|
+
}
|
|
372
|
+
}, null, 2);
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
logger.error("Error starting phone login", { error: error instanceof Error ? error.message : String(error) });
|
|
376
|
+
return JSON.stringify({
|
|
377
|
+
success: false,
|
|
378
|
+
error: error instanceof Error ? error.message : String(error)
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
const codeLoginTool = {
|
|
384
|
+
name: "telegram-bridge-login-code",
|
|
385
|
+
description: "Submit the verification code received via Telegram to complete login.",
|
|
386
|
+
schema: z.object({
|
|
387
|
+
code: z.string().describe("Verification code received via Telegram"),
|
|
388
|
+
user_id: z.string().optional().describe("Matrix user ID to log in (defaults to configured user)")
|
|
389
|
+
}),
|
|
390
|
+
async execute(args) {
|
|
391
|
+
if (!config.enabled) {
|
|
392
|
+
return JSON.stringify({
|
|
393
|
+
success: false,
|
|
394
|
+
error: "Mautrix telegram bridge not configured. Set MAUTRIX_BRIDGE_URL and MAUTRIX_SHARED_SECRET."
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
logger.info("Submitting verification code", { user_id: args.user_id });
|
|
398
|
+
try {
|
|
399
|
+
const body = { code: args.code };
|
|
400
|
+
if (args.user_id)
|
|
401
|
+
body.user_id = args.user_id;
|
|
402
|
+
const result = await fetchBridge("/v1/login/send_code", {
|
|
403
|
+
method: "POST",
|
|
404
|
+
body: JSON.stringify(body)
|
|
405
|
+
});
|
|
406
|
+
if (result.next_step === "2fa" || result.status === "2fa_required") {
|
|
407
|
+
return JSON.stringify({
|
|
408
|
+
success: true,
|
|
409
|
+
data: {
|
|
410
|
+
status: "2fa_required",
|
|
411
|
+
next_step: "password",
|
|
412
|
+
message: "Two-factor authentication is enabled. Use telegram-bridge-login-2fa to submit your password."
|
|
413
|
+
}
|
|
414
|
+
}, null, 2);
|
|
415
|
+
}
|
|
416
|
+
return JSON.stringify({
|
|
417
|
+
success: true,
|
|
418
|
+
data: {
|
|
419
|
+
status: result.status || "Login successful",
|
|
420
|
+
message: "Successfully logged in to Telegram through the bridge."
|
|
421
|
+
}
|
|
422
|
+
}, null, 2);
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
logger.error("Error submitting code", { error: error instanceof Error ? error.message : String(error) });
|
|
426
|
+
return JSON.stringify({
|
|
427
|
+
success: false,
|
|
428
|
+
error: error instanceof Error ? error.message : String(error)
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
const twoFactorTool = {
|
|
434
|
+
name: "telegram-bridge-login-2fa",
|
|
435
|
+
description: "Submit 2FA password to complete Telegram login when two-factor authentication is enabled.",
|
|
436
|
+
schema: z.object({
|
|
437
|
+
password: z.string().describe("Your Telegram 2FA password"),
|
|
438
|
+
user_id: z.string().optional().describe("Matrix user ID to log in (defaults to configured user)")
|
|
439
|
+
}),
|
|
440
|
+
async execute(args) {
|
|
441
|
+
if (!config.enabled) {
|
|
442
|
+
return JSON.stringify({
|
|
443
|
+
success: false,
|
|
444
|
+
error: "Mautrix telegram bridge not configured. Set MAUTRIX_BRIDGE_URL and MAUTRIX_SHARED_SECRET."
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
logger.info("Submitting 2FA password", { user_id: args.user_id });
|
|
448
|
+
try {
|
|
449
|
+
const body = { password: args.password };
|
|
450
|
+
if (args.user_id)
|
|
451
|
+
body.user_id = args.user_id;
|
|
452
|
+
const result = await fetchBridge("/v1/login/send_password", {
|
|
453
|
+
method: "POST",
|
|
454
|
+
body: JSON.stringify(body)
|
|
455
|
+
});
|
|
456
|
+
return JSON.stringify({
|
|
457
|
+
success: true,
|
|
458
|
+
data: {
|
|
459
|
+
status: result.status || "Login successful",
|
|
460
|
+
message: "Successfully logged in to Telegram with 2FA."
|
|
461
|
+
}
|
|
462
|
+
}, null, 2);
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
logger.error("Error submitting 2FA password", { error: error instanceof Error ? error.message : String(error) });
|
|
466
|
+
return JSON.stringify({
|
|
467
|
+
success: false,
|
|
468
|
+
error: error instanceof Error ? error.message : String(error)
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
const botTokenTool = {
|
|
474
|
+
name: "telegram-bridge-login-bot",
|
|
475
|
+
description: "Log in to Telegram through the mautrix bridge using a bot token. More limited than user accounts but provides privacy benefits.",
|
|
476
|
+
schema: z.object({
|
|
477
|
+
token: z.string().describe("Telegram bot token from @BotFather"),
|
|
478
|
+
user_id: z.string().optional().describe("Matrix user ID to log in (defaults to configured user)")
|
|
479
|
+
}),
|
|
480
|
+
async execute(args) {
|
|
481
|
+
if (!config.enabled) {
|
|
482
|
+
return JSON.stringify({
|
|
483
|
+
success: false,
|
|
484
|
+
error: "Mautrix telegram bridge not configured. Set MAUTRIX_BRIDGE_URL and MAUTRIX_SHARED_SECRET."
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
logger.info("Starting bot token login", { user_id: args.user_id });
|
|
488
|
+
try {
|
|
489
|
+
const body = { token: args.token };
|
|
490
|
+
if (args.user_id)
|
|
491
|
+
body.user_id = args.user_id;
|
|
492
|
+
const result = await fetchBridge("/v1/login/bot_token", {
|
|
493
|
+
method: "POST",
|
|
494
|
+
body: JSON.stringify(body)
|
|
495
|
+
});
|
|
496
|
+
return JSON.stringify({
|
|
497
|
+
success: true,
|
|
498
|
+
data: {
|
|
499
|
+
status: result.status || "Login successful",
|
|
500
|
+
message: "Bot account connected. Note: Bot accounts have limited functionality compared to user accounts."
|
|
501
|
+
},
|
|
502
|
+
limitations: [
|
|
503
|
+
"Cannot join groups without being added",
|
|
504
|
+
"Cannot initiate conversations with users",
|
|
505
|
+
"Cannot access message history before joining"
|
|
506
|
+
]
|
|
507
|
+
}, null, 2);
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
logger.error("Error with bot token login", { error: error instanceof Error ? error.message : String(error) });
|
|
511
|
+
return JSON.stringify({
|
|
512
|
+
success: false,
|
|
513
|
+
error: error instanceof Error ? error.message : String(error),
|
|
514
|
+
help: {
|
|
515
|
+
steps: [
|
|
516
|
+
"1. Open Telegram and search for @BotFather",
|
|
517
|
+
"2. Send /newbot to create a new bot or /token to get existing bot's token",
|
|
518
|
+
"3. Copy the token (format: 123456789:ABCdefGHIjklMNOpqrSTUvwxYZ)"
|
|
519
|
+
]
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
const chatsTool = {
|
|
526
|
+
name: "telegram-bridge-chats",
|
|
527
|
+
description: "List Telegram chats accessible through the mautrix bridge.",
|
|
528
|
+
schema: z.object({
|
|
529
|
+
user_id: z.string().optional().describe("Matrix user ID (defaults to configured user)")
|
|
530
|
+
}),
|
|
531
|
+
async execute(args) {
|
|
532
|
+
if (!config.enabled) {
|
|
533
|
+
return JSON.stringify({
|
|
534
|
+
success: false,
|
|
535
|
+
error: "Mautrix telegram bridge not configured. Set MAUTRIX_BRIDGE_URL and MAUTRIX_SHARED_SECRET."
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
logger.info("Listing chats through bridge", { user_id: args.user_id });
|
|
539
|
+
try {
|
|
540
|
+
const params = args.user_id ? `?user_id=${encodeURIComponent(args.user_id)}` : "";
|
|
541
|
+
const chats = await fetchBridge(`/v1/chats${params}`);
|
|
542
|
+
return JSON.stringify({
|
|
543
|
+
success: true,
|
|
544
|
+
data: {
|
|
545
|
+
chats: chats.map(c => ({
|
|
546
|
+
id: c.id,
|
|
547
|
+
name: c.name,
|
|
548
|
+
type: c.type || "unknown"
|
|
549
|
+
})),
|
|
550
|
+
count: chats.length
|
|
551
|
+
}
|
|
552
|
+
}, null, 2);
|
|
553
|
+
}
|
|
554
|
+
catch (error) {
|
|
555
|
+
logger.error("Error listing chats", { error: error instanceof Error ? error.message : String(error) });
|
|
556
|
+
return JSON.stringify({
|
|
557
|
+
success: false,
|
|
558
|
+
error: error instanceof Error ? error.message : String(error)
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
const bridgeChatTool = {
|
|
564
|
+
name: "telegram-bridge-connect-chat",
|
|
565
|
+
description: "Bridge a Telegram chat to Matrix. This creates a Matrix room for the Telegram chat.",
|
|
566
|
+
schema: z.object({
|
|
567
|
+
chat_id: z.union([z.string(), z.number()]).describe("Telegram chat ID to bridge"),
|
|
568
|
+
user_id: z.string().optional().describe("Matrix user ID (defaults to configured user)")
|
|
569
|
+
}),
|
|
570
|
+
async execute(args) {
|
|
571
|
+
if (!config.enabled) {
|
|
572
|
+
return JSON.stringify({
|
|
573
|
+
success: false,
|
|
574
|
+
error: "Mautrix telegram bridge not configured. Set MAUTRIX_BRIDGE_URL and MAUTRIX_SHARED_SECRET."
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
logger.info("Connecting chat to bridge", { chat_id: args.chat_id, user_id: args.user_id });
|
|
578
|
+
try {
|
|
579
|
+
const body = { chat_id: args.chat_id };
|
|
580
|
+
if (args.user_id)
|
|
581
|
+
body.user_id = args.user_id;
|
|
582
|
+
const result = await fetchBridge("/v1/pm/" + args.chat_id, {
|
|
583
|
+
method: "POST",
|
|
584
|
+
body: JSON.stringify(body)
|
|
585
|
+
});
|
|
586
|
+
return JSON.stringify({
|
|
587
|
+
success: true,
|
|
588
|
+
data: {
|
|
589
|
+
chat_id: args.chat_id,
|
|
590
|
+
matrix_room: result.room_id,
|
|
591
|
+
message: "Chat connected. Telegram messages will now be bridged to the Matrix room."
|
|
592
|
+
}
|
|
593
|
+
}, null, 2);
|
|
594
|
+
}
|
|
595
|
+
catch (error) {
|
|
596
|
+
logger.error("Error connecting chat", { error: error instanceof Error ? error.message : String(error) });
|
|
597
|
+
return JSON.stringify({
|
|
598
|
+
success: false,
|
|
599
|
+
error: error instanceof Error ? error.message : String(error)
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
return [
|
|
605
|
+
createBridgeStatusTool(baseOptions),
|
|
606
|
+
phoneLoginTool,
|
|
607
|
+
codeLoginTool,
|
|
608
|
+
twoFactorTool,
|
|
609
|
+
botTokenTool,
|
|
610
|
+
createBridgeLogoutTool(baseOptions),
|
|
611
|
+
chatsTool,
|
|
612
|
+
bridgeChatTool
|
|
613
|
+
];
|
|
614
|
+
}
|
|
615
|
+
export const BRIDGE_ENV_VARS = {
|
|
616
|
+
required: [],
|
|
617
|
+
optional: [
|
|
618
|
+
{
|
|
619
|
+
name: "MAUTRIX_BRIDGE_URL",
|
|
620
|
+
description: "URL of your mautrix bridge instance (e.g., https://bridge.example.com). Enables Matrix bridge integration."
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
name: "MAUTRIX_SHARED_SECRET",
|
|
624
|
+
description: "Shared secret for mautrix provisioning API. Found in bridge config under provisioning.shared_secret"
|
|
625
|
+
}
|
|
626
|
+
]
|
|
627
|
+
};
|
|
628
|
+
const DEFAULT_PORTS = {
|
|
629
|
+
telegram: 29317,
|
|
630
|
+
discord: 29334,
|
|
631
|
+
whatsapp: 29318,
|
|
632
|
+
signal: 29328,
|
|
633
|
+
slack: 29335
|
|
634
|
+
};
|
|
635
|
+
function getDefaultDataDir(bridgeType) {
|
|
636
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "~";
|
|
637
|
+
return join(homeDir, ".mautrix", bridgeType);
|
|
638
|
+
}
|
|
639
|
+
function getContainerName(bridgeType) {
|
|
640
|
+
return `mautrix-${bridgeType}`;
|
|
641
|
+
}
|
|
642
|
+
function getImageName(bridgeType, tag) {
|
|
643
|
+
return `dock.mau.dev/mautrix/${bridgeType}:${tag}`;
|
|
644
|
+
}
|
|
645
|
+
export function createDockerBridgeTools(options) {
|
|
646
|
+
const { bridgeType, logger, dataDir, imageTag = "latest" } = options;
|
|
647
|
+
const defaultDataDir = dataDir || getDefaultDataDir(bridgeType);
|
|
648
|
+
const containerName = getContainerName(bridgeType);
|
|
649
|
+
const imageName = getImageName(bridgeType, imageTag);
|
|
650
|
+
const defaultPort = DEFAULT_PORTS[bridgeType];
|
|
651
|
+
const initBridgeTool = {
|
|
652
|
+
name: `${bridgeType}-docker-init`,
|
|
653
|
+
description: `Initialize mautrix-${bridgeType} bridge by pulling the Docker image and generating initial configuration. This is the first step in setting up the bridge.`,
|
|
654
|
+
schema: z.object({
|
|
655
|
+
data_dir: z.string().optional().describe(`Directory to store bridge data (default: ~/.mautrix/${bridgeType})`),
|
|
656
|
+
image_tag: z.string().optional().default("latest").describe("Docker image tag to use (e.g., 'latest', 'v0.15.0')")
|
|
657
|
+
}),
|
|
658
|
+
async execute(args) {
|
|
659
|
+
const targetDir = args.data_dir || defaultDataDir;
|
|
660
|
+
const tag = args.image_tag || imageTag;
|
|
661
|
+
const image = getImageName(bridgeType, tag);
|
|
662
|
+
logger.info("Initializing bridge", { bridgeType, targetDir, image });
|
|
663
|
+
try {
|
|
664
|
+
if (!existsSync(targetDir)) {
|
|
665
|
+
mkdirSync(targetDir, { recursive: true });
|
|
666
|
+
logger.info("Created data directory", { targetDir });
|
|
667
|
+
}
|
|
668
|
+
logger.info("Pulling Docker image", { image });
|
|
669
|
+
const { stdout: pullOutput } = await execAsync(`docker pull ${image}`);
|
|
670
|
+
logger.info("Generating initial configuration");
|
|
671
|
+
const { stdout: configOutput, stderr: configStderr } = await execAsync(`docker run --rm -v "${targetDir}:/data:z" ${image}`, { timeout: 60000 });
|
|
672
|
+
const configPath = join(targetDir, "config.yaml");
|
|
673
|
+
const configExists = existsSync(configPath);
|
|
674
|
+
return JSON.stringify({
|
|
675
|
+
success: true,
|
|
676
|
+
data: {
|
|
677
|
+
bridge_type: bridgeType,
|
|
678
|
+
data_dir: targetDir,
|
|
679
|
+
image: image,
|
|
680
|
+
config_generated: configExists,
|
|
681
|
+
config_path: configPath,
|
|
682
|
+
next_steps: [
|
|
683
|
+
`1. Edit the configuration file at ${configPath}`,
|
|
684
|
+
"2. Configure your Matrix homeserver address (avoid using 'localhost' inside Docker)",
|
|
685
|
+
"3. Set up database connection (SQLite works for testing, PostgreSQL recommended for production)",
|
|
686
|
+
"4. Configure Telegram API credentials (api_id and api_hash from my.telegram.org)",
|
|
687
|
+
`5. Run ${bridgeType}-docker-generate-registration to create the appservice registration`,
|
|
688
|
+
`6. Register the bridge with your homeserver`,
|
|
689
|
+
`7. Run ${bridgeType}-docker-start to start the bridge`
|
|
690
|
+
],
|
|
691
|
+
output: configOutput || configStderr
|
|
692
|
+
}
|
|
693
|
+
}, null, 2);
|
|
694
|
+
}
|
|
695
|
+
catch (error) {
|
|
696
|
+
logger.error("Error initializing bridge", { error: error instanceof Error ? error.message : String(error) });
|
|
697
|
+
return JSON.stringify({
|
|
698
|
+
success: false,
|
|
699
|
+
error: error instanceof Error ? error.message : String(error),
|
|
700
|
+
help: {
|
|
701
|
+
docker_not_found: "Ensure Docker is installed and running",
|
|
702
|
+
permission_denied: "You may need to run with sudo or add your user to the docker group"
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
const generateRegistrationTool = {
|
|
709
|
+
name: `${bridgeType}-docker-generate-registration`,
|
|
710
|
+
description: `Generate the appservice registration file for mautrix-${bridgeType}. Run this after configuring config.yaml.`,
|
|
711
|
+
schema: z.object({
|
|
712
|
+
data_dir: z.string().optional().describe(`Directory containing bridge data (default: ~/.mautrix/${bridgeType})`)
|
|
713
|
+
}),
|
|
714
|
+
async execute(args) {
|
|
715
|
+
const targetDir = args.data_dir || defaultDataDir;
|
|
716
|
+
logger.info("Generating registration", { bridgeType, targetDir });
|
|
717
|
+
try {
|
|
718
|
+
const { stdout, stderr } = await execAsync(`docker run --rm -v "${targetDir}:/data:z" ${imageName}`, { timeout: 60000 });
|
|
719
|
+
const registrationPath = join(targetDir, "registration.yaml");
|
|
720
|
+
const registrationExists = existsSync(registrationPath);
|
|
721
|
+
return JSON.stringify({
|
|
722
|
+
success: true,
|
|
723
|
+
data: {
|
|
724
|
+
bridge_type: bridgeType,
|
|
725
|
+
registration_generated: registrationExists,
|
|
726
|
+
registration_path: registrationPath,
|
|
727
|
+
next_steps: registrationExists ? [
|
|
728
|
+
`1. Copy ${registrationPath} to your Matrix homeserver's appservice directory`,
|
|
729
|
+
"2. Add the registration to your homeserver config (e.g., Synapse's app_service_config_files)",
|
|
730
|
+
"3. Restart your Matrix homeserver",
|
|
731
|
+
`4. Run ${bridgeType}-docker-start to start the bridge`
|
|
732
|
+
] : [
|
|
733
|
+
"Registration file not found. Check the configuration and try again."
|
|
734
|
+
],
|
|
735
|
+
output: stdout || stderr
|
|
736
|
+
}
|
|
737
|
+
}, null, 2);
|
|
738
|
+
}
|
|
739
|
+
catch (error) {
|
|
740
|
+
logger.error("Error generating registration", { error: error instanceof Error ? error.message : String(error) });
|
|
741
|
+
return JSON.stringify({
|
|
742
|
+
success: false,
|
|
743
|
+
error: error instanceof Error ? error.message : String(error)
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
const startBridgeTool = {
|
|
749
|
+
name: `${bridgeType}-docker-start`,
|
|
750
|
+
description: `Start the mautrix-${bridgeType} bridge Docker container.`,
|
|
751
|
+
schema: z.object({
|
|
752
|
+
data_dir: z.string().optional().describe(`Directory containing bridge data (default: ~/.mautrix/${bridgeType})`),
|
|
753
|
+
port: z.number().optional().describe(`Port to expose the bridge on (default: ${defaultPort})`),
|
|
754
|
+
network: z.string().optional().describe("Docker network to connect to (e.g., 'synapse-net' for same network as homeserver)"),
|
|
755
|
+
detach: z.boolean().optional().default(true).describe("Run container in background (default: true)")
|
|
756
|
+
}),
|
|
757
|
+
async execute(args) {
|
|
758
|
+
const targetDir = args.data_dir || defaultDataDir;
|
|
759
|
+
const port = args.port || defaultPort;
|
|
760
|
+
const detachFlag = args.detach !== false ? "-d" : "";
|
|
761
|
+
logger.info("Starting bridge container", { bridgeType, targetDir, port });
|
|
762
|
+
try {
|
|
763
|
+
try {
|
|
764
|
+
await execAsync(`docker rm -f ${containerName}`);
|
|
765
|
+
}
|
|
766
|
+
catch {
|
|
767
|
+
// Container doesn't exist, that's fine
|
|
768
|
+
}
|
|
769
|
+
let cmd = `docker run ${detachFlag} --restart unless-stopped --name ${containerName} -v "${targetDir}:/data:z"`;
|
|
770
|
+
if (args.network) {
|
|
771
|
+
cmd += ` --network=${args.network}`;
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
cmd += ` -p ${port}:${port}`;
|
|
775
|
+
}
|
|
776
|
+
cmd += ` ${imageName}`;
|
|
777
|
+
const { stdout, stderr } = await execAsync(cmd);
|
|
778
|
+
const { stdout: statusOutput } = await execAsync(`docker ps --filter name=${containerName} --format "{{.Status}}"`);
|
|
779
|
+
return JSON.stringify({
|
|
780
|
+
success: true,
|
|
781
|
+
data: {
|
|
782
|
+
bridge_type: bridgeType,
|
|
783
|
+
container_name: containerName,
|
|
784
|
+
status: statusOutput.trim() || "starting",
|
|
785
|
+
port: args.network ? "via network" : port,
|
|
786
|
+
network: args.network || "bridge (default)",
|
|
787
|
+
next_steps: [
|
|
788
|
+
`Check logs with: docker logs ${containerName}`,
|
|
789
|
+
`Or use ${bridgeType}-docker-logs tool`,
|
|
790
|
+
"Connect to the bridge using the provisioning API or bot commands"
|
|
791
|
+
],
|
|
792
|
+
container_id: stdout.trim()
|
|
793
|
+
}
|
|
794
|
+
}, null, 2);
|
|
795
|
+
}
|
|
796
|
+
catch (error) {
|
|
797
|
+
logger.error("Error starting bridge", { error: error instanceof Error ? error.message : String(error) });
|
|
798
|
+
return JSON.stringify({
|
|
799
|
+
success: false,
|
|
800
|
+
error: error instanceof Error ? error.message : String(error)
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
const stopBridgeTool = {
|
|
806
|
+
name: `${bridgeType}-docker-stop`,
|
|
807
|
+
description: `Stop the mautrix-${bridgeType} bridge Docker container.`,
|
|
808
|
+
schema: z.object({
|
|
809
|
+
remove: z.boolean().optional().default(false).describe("Also remove the container after stopping")
|
|
810
|
+
}),
|
|
811
|
+
async execute(args) {
|
|
812
|
+
logger.info("Stopping bridge container", { bridgeType, remove: args.remove });
|
|
813
|
+
try {
|
|
814
|
+
await execAsync(`docker stop ${containerName}`);
|
|
815
|
+
if (args.remove) {
|
|
816
|
+
await execAsync(`docker rm ${containerName}`);
|
|
817
|
+
}
|
|
818
|
+
return JSON.stringify({
|
|
819
|
+
success: true,
|
|
820
|
+
data: {
|
|
821
|
+
bridge_type: bridgeType,
|
|
822
|
+
container_name: containerName,
|
|
823
|
+
action: args.remove ? "stopped and removed" : "stopped",
|
|
824
|
+
message: `Bridge container ${args.remove ? "stopped and removed" : "stopped"}. Use ${bridgeType}-docker-start to restart.`
|
|
825
|
+
}
|
|
826
|
+
}, null, 2);
|
|
827
|
+
}
|
|
828
|
+
catch (error) {
|
|
829
|
+
logger.error("Error stopping bridge", { error: error instanceof Error ? error.message : String(error) });
|
|
830
|
+
return JSON.stringify({
|
|
831
|
+
success: false,
|
|
832
|
+
error: error instanceof Error ? error.message : String(error)
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
const restartBridgeTool = {
|
|
838
|
+
name: `${bridgeType}-docker-restart`,
|
|
839
|
+
description: `Restart the mautrix-${bridgeType} bridge Docker container.`,
|
|
840
|
+
schema: z.object({}),
|
|
841
|
+
async execute() {
|
|
842
|
+
logger.info("Restarting bridge container", { bridgeType });
|
|
843
|
+
try {
|
|
844
|
+
await execAsync(`docker restart ${containerName}`);
|
|
845
|
+
const { stdout: statusOutput } = await execAsync(`docker ps --filter name=${containerName} --format "{{.Status}}"`);
|
|
846
|
+
return JSON.stringify({
|
|
847
|
+
success: true,
|
|
848
|
+
data: {
|
|
849
|
+
bridge_type: bridgeType,
|
|
850
|
+
container_name: containerName,
|
|
851
|
+
status: statusOutput.trim(),
|
|
852
|
+
message: "Bridge container restarted successfully"
|
|
853
|
+
}
|
|
854
|
+
}, null, 2);
|
|
855
|
+
}
|
|
856
|
+
catch (error) {
|
|
857
|
+
logger.error("Error restarting bridge", { error: error instanceof Error ? error.message : String(error) });
|
|
858
|
+
return JSON.stringify({
|
|
859
|
+
success: false,
|
|
860
|
+
error: error instanceof Error ? error.message : String(error)
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
const statusTool = {
|
|
866
|
+
name: `${bridgeType}-docker-status`,
|
|
867
|
+
description: `Check the status of the mautrix-${bridgeType} Docker container.`,
|
|
868
|
+
schema: z.object({}),
|
|
869
|
+
async execute() {
|
|
870
|
+
logger.info("Checking bridge container status", { bridgeType });
|
|
871
|
+
try {
|
|
872
|
+
const { stdout: containerInfo } = await execAsync(`docker inspect ${containerName} --format '{"state":"{{.State.Status}}","running":{{.State.Running}},"started":"{{.State.StartedAt}}","health":"{{if .State.Health}}{{.State.Health.Status}}{{else}}no healthcheck{{end}}"}'`).catch(() => ({ stdout: "" }));
|
|
873
|
+
if (!containerInfo.trim()) {
|
|
874
|
+
return JSON.stringify({
|
|
875
|
+
success: true,
|
|
876
|
+
data: {
|
|
877
|
+
bridge_type: bridgeType,
|
|
878
|
+
container_name: containerName,
|
|
879
|
+
exists: false,
|
|
880
|
+
status: "not found",
|
|
881
|
+
message: `Container ${containerName} does not exist. Use ${bridgeType}-docker-init to set up the bridge.`
|
|
882
|
+
}
|
|
883
|
+
}, null, 2);
|
|
884
|
+
}
|
|
885
|
+
const info = JSON.parse(containerInfo);
|
|
886
|
+
const { stdout: portsInfo } = await execAsync(`docker port ${containerName} 2>/dev/null || echo ""`).catch(() => ({ stdout: "" }));
|
|
887
|
+
return JSON.stringify({
|
|
888
|
+
success: true,
|
|
889
|
+
data: {
|
|
890
|
+
bridge_type: bridgeType,
|
|
891
|
+
container_name: containerName,
|
|
892
|
+
exists: true,
|
|
893
|
+
state: info.state,
|
|
894
|
+
running: info.running,
|
|
895
|
+
started_at: info.started,
|
|
896
|
+
health: info.health,
|
|
897
|
+
ports: portsInfo.trim() || "none exposed"
|
|
898
|
+
}
|
|
899
|
+
}, null, 2);
|
|
900
|
+
}
|
|
901
|
+
catch (error) {
|
|
902
|
+
logger.error("Error checking bridge status", { error: error instanceof Error ? error.message : String(error) });
|
|
903
|
+
return JSON.stringify({
|
|
904
|
+
success: false,
|
|
905
|
+
error: error instanceof Error ? error.message : String(error)
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
const logsTool = {
|
|
911
|
+
name: `${bridgeType}-docker-logs`,
|
|
912
|
+
description: `View logs from the mautrix-${bridgeType} Docker container.`,
|
|
913
|
+
schema: z.object({
|
|
914
|
+
lines: z.number().optional().default(50).describe("Number of log lines to show (default: 50)"),
|
|
915
|
+
follow: z.boolean().optional().default(false).describe("Follow log output (not recommended for tool use)")
|
|
916
|
+
}),
|
|
917
|
+
async execute(args) {
|
|
918
|
+
logger.info("Fetching bridge logs", { bridgeType, lines: args.lines });
|
|
919
|
+
try {
|
|
920
|
+
const { stdout, stderr } = await execAsync(`docker logs --tail ${args.lines} ${containerName} 2>&1`, { timeout: 30000 });
|
|
921
|
+
const logs = stdout || stderr;
|
|
922
|
+
return JSON.stringify({
|
|
923
|
+
success: true,
|
|
924
|
+
data: {
|
|
925
|
+
bridge_type: bridgeType,
|
|
926
|
+
container_name: containerName,
|
|
927
|
+
lines_requested: args.lines,
|
|
928
|
+
logs: logs.split("\n").slice(-args.lines)
|
|
929
|
+
}
|
|
930
|
+
}, null, 2);
|
|
931
|
+
}
|
|
932
|
+
catch (error) {
|
|
933
|
+
logger.error("Error fetching logs", { error: error instanceof Error ? error.message : String(error) });
|
|
934
|
+
return JSON.stringify({
|
|
935
|
+
success: false,
|
|
936
|
+
error: error instanceof Error ? error.message : String(error)
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
const updateBridgeTool = {
|
|
942
|
+
name: `${bridgeType}-docker-update`,
|
|
943
|
+
description: `Update the mautrix-${bridgeType} Docker image to the latest version and restart the container.`,
|
|
944
|
+
schema: z.object({
|
|
945
|
+
data_dir: z.string().optional().describe(`Directory containing bridge data (default: ~/.mautrix/${bridgeType})`),
|
|
946
|
+
image_tag: z.string().optional().default("latest").describe("Docker image tag to pull")
|
|
947
|
+
}),
|
|
948
|
+
async execute(args) {
|
|
949
|
+
const targetDir = args.data_dir || defaultDataDir;
|
|
950
|
+
const tag = args.image_tag || imageTag;
|
|
951
|
+
const image = getImageName(bridgeType, tag);
|
|
952
|
+
logger.info("Updating bridge", { bridgeType, image });
|
|
953
|
+
try {
|
|
954
|
+
logger.info("Pulling latest image");
|
|
955
|
+
const { stdout: pullOutput } = await execAsync(`docker pull ${image}`);
|
|
956
|
+
const wasRunning = await execAsync(`docker ps -q --filter name=${containerName}`)
|
|
957
|
+
.then(r => !!r.stdout.trim())
|
|
958
|
+
.catch(() => false);
|
|
959
|
+
if (wasRunning) {
|
|
960
|
+
logger.info("Stopping current container");
|
|
961
|
+
await execAsync(`docker stop ${containerName}`);
|
|
962
|
+
await execAsync(`docker rm ${containerName}`);
|
|
963
|
+
logger.info("Starting updated container");
|
|
964
|
+
await execAsync(`docker run -d --restart unless-stopped --name ${containerName} -v "${targetDir}:/data:z" -p ${defaultPort}:${defaultPort} ${image}`);
|
|
965
|
+
}
|
|
966
|
+
return JSON.stringify({
|
|
967
|
+
success: true,
|
|
968
|
+
data: {
|
|
969
|
+
bridge_type: bridgeType,
|
|
970
|
+
image: image,
|
|
971
|
+
updated: true,
|
|
972
|
+
restarted: wasRunning,
|
|
973
|
+
message: wasRunning
|
|
974
|
+
? "Bridge updated and restarted with new image"
|
|
975
|
+
: "Image pulled. Container was not running. Use start command to run with new image."
|
|
976
|
+
}
|
|
977
|
+
}, null, 2);
|
|
978
|
+
}
|
|
979
|
+
catch (error) {
|
|
980
|
+
logger.error("Error updating bridge", { error: error instanceof Error ? error.message : String(error) });
|
|
981
|
+
return JSON.stringify({
|
|
982
|
+
success: false,
|
|
983
|
+
error: error instanceof Error ? error.message : String(error)
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
const configTool = {
|
|
989
|
+
name: `${bridgeType}-docker-config`,
|
|
990
|
+
description: `View or get path to the mautrix-${bridgeType} bridge configuration file.`,
|
|
991
|
+
schema: z.object({
|
|
992
|
+
data_dir: z.string().optional().describe(`Directory containing bridge data (default: ~/.mautrix/${bridgeType})`),
|
|
993
|
+
show_content: z.boolean().optional().default(false).describe("Show the actual config content (may contain secrets)")
|
|
994
|
+
}),
|
|
995
|
+
async execute(args) {
|
|
996
|
+
const targetDir = args.data_dir || defaultDataDir;
|
|
997
|
+
const configPath = join(targetDir, "config.yaml");
|
|
998
|
+
logger.info("Getting bridge config", { bridgeType, configPath });
|
|
999
|
+
try {
|
|
1000
|
+
const configExists = existsSync(configPath);
|
|
1001
|
+
if (!configExists) {
|
|
1002
|
+
return JSON.stringify({
|
|
1003
|
+
success: false,
|
|
1004
|
+
error: "Configuration file not found",
|
|
1005
|
+
help: {
|
|
1006
|
+
message: `Run ${bridgeType}-docker-init first to generate the configuration`,
|
|
1007
|
+
expected_path: configPath
|
|
1008
|
+
}
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
const result = {
|
|
1012
|
+
success: true,
|
|
1013
|
+
data: {
|
|
1014
|
+
bridge_type: bridgeType,
|
|
1015
|
+
config_path: configPath,
|
|
1016
|
+
exists: true
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
if (args.show_content) {
|
|
1020
|
+
const content = readFileSync(configPath, "utf-8");
|
|
1021
|
+
result.data.content = content;
|
|
1022
|
+
}
|
|
1023
|
+
return JSON.stringify(result, null, 2);
|
|
1024
|
+
}
|
|
1025
|
+
catch (error) {
|
|
1026
|
+
logger.error("Error reading config", { error: error instanceof Error ? error.message : String(error) });
|
|
1027
|
+
return JSON.stringify({
|
|
1028
|
+
success: false,
|
|
1029
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
};
|
|
1034
|
+
return [
|
|
1035
|
+
initBridgeTool,
|
|
1036
|
+
generateRegistrationTool,
|
|
1037
|
+
startBridgeTool,
|
|
1038
|
+
stopBridgeTool,
|
|
1039
|
+
restartBridgeTool,
|
|
1040
|
+
statusTool,
|
|
1041
|
+
logsTool,
|
|
1042
|
+
updateBridgeTool,
|
|
1043
|
+
configTool
|
|
1044
|
+
];
|
|
1045
|
+
}
|
|
1046
|
+
//# sourceMappingURL=mautrix-bridge.js.map
|