clawgram 2.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,381 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createConfigBackup = createConfigBackup;
7
+ exports.updateConfigFileDirectly = updateConfigFileDirectly;
8
+ const node_fs_1 = require("node:fs");
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const json5_1 = __importDefault(require("json5"));
11
+ const constants_1 = require("./constants");
12
+ function formatBackupTimestamp(date) {
13
+ const year = String(date.getUTCFullYear());
14
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
15
+ const day = String(date.getUTCDate()).padStart(2, "0");
16
+ const hours = String(date.getUTCHours()).padStart(2, "0");
17
+ const minutes = String(date.getUTCMinutes()).padStart(2, "0");
18
+ const seconds = String(date.getUTCSeconds()).padStart(2, "0");
19
+ return `${year}${month}${day}-${hours}${minutes}${seconds}`;
20
+ }
21
+ function buildConfigBackupPath(configPath) {
22
+ const dir = node_path_1.default.dirname(configPath);
23
+ const fileName = node_path_1.default.basename(configPath);
24
+ const suffix = `${formatBackupTimestamp(new Date())}-clawgram-auth`;
25
+ return node_path_1.default.join(dir, `${fileName}.bak-${suffix}`);
26
+ }
27
+ function isPlainObject(value) {
28
+ return typeof value === "object" && value !== null && !Array.isArray(value);
29
+ }
30
+ function buildAccountPayload(auth) {
31
+ return {
32
+ enabled: true,
33
+ apiId: auth.apiId,
34
+ apiHash: auth.apiHash,
35
+ sessionString: auth.sessionString,
36
+ };
37
+ }
38
+ function buildAccountConfigFragment(auth) {
39
+ return {
40
+ ...buildAccountPayload(auth),
41
+ allowFrom: ["*"],
42
+ groups: {
43
+ "*": {
44
+ enabled: true,
45
+ groupPolicy: "mention",
46
+ allowFrom: ["*"],
47
+ },
48
+ },
49
+ };
50
+ }
51
+ function applyAuthToConfig(config, accountId, auth) {
52
+ const channels = config.channels && typeof config.channels === "object" ? config.channels : {};
53
+ const channelConfig = channels[constants_1.CHANNEL_ID] && typeof channels[constants_1.CHANNEL_ID] === "object" ? channels[constants_1.CHANNEL_ID] : {};
54
+ const accounts = channelConfig.accounts && typeof channelConfig.accounts === "object" ? channelConfig.accounts : {};
55
+ const existingAccount = accounts[accountId] && typeof accounts[accountId] === "object" ? accounts[accountId] : {};
56
+ return {
57
+ ...config,
58
+ channels: {
59
+ ...channels,
60
+ [constants_1.CHANNEL_ID]: {
61
+ ...channelConfig,
62
+ accounts: {
63
+ ...accounts,
64
+ [accountId]: {
65
+ ...existingAccount,
66
+ ...buildAccountPayload(auth),
67
+ enabled: existingAccount.enabled ?? true,
68
+ allowFrom: existingAccount.allowFrom ?? ["*"],
69
+ groups: existingAccount.groups ?? {
70
+ "*": {
71
+ enabled: true,
72
+ groupPolicy: "mention",
73
+ allowFrom: ["*"],
74
+ },
75
+ },
76
+ },
77
+ },
78
+ },
79
+ },
80
+ };
81
+ }
82
+ function detectTextFormat(raw) {
83
+ const eol = raw.includes("\r\n") ? "\r\n" : "\n";
84
+ const indentMatch = raw.match(/^[ \t]+(?=(?:\"|')?[A-Za-z0-9_$-]+(?:\"|')?\s*:)/m);
85
+ return {
86
+ eol,
87
+ indentUnit: indentMatch?.[0] || " ",
88
+ };
89
+ }
90
+ function getLineStart(raw, index) {
91
+ const lineStart = raw.lastIndexOf("\n", index - 1);
92
+ return lineStart === -1 ? 0 : lineStart + 1;
93
+ }
94
+ function getLineIndent(raw, index) {
95
+ const lineStart = getLineStart(raw, index);
96
+ let cursor = lineStart;
97
+ while (cursor < raw.length && (raw[cursor] === " " || raw[cursor] === "\t")) {
98
+ cursor += 1;
99
+ }
100
+ return raw.slice(lineStart, cursor);
101
+ }
102
+ function skipTrivia(raw, start) {
103
+ let index = start;
104
+ while (index < raw.length) {
105
+ const char = raw[index];
106
+ if (char === " " || char === "\t" || char === "\n" || char === "\r") {
107
+ index += 1;
108
+ continue;
109
+ }
110
+ if (char === "/" && raw[index + 1] === "/") {
111
+ index += 2;
112
+ while (index < raw.length && raw[index] !== "\n") {
113
+ index += 1;
114
+ }
115
+ continue;
116
+ }
117
+ if (char === "/" && raw[index + 1] === "*") {
118
+ index += 2;
119
+ while (index + 1 < raw.length && !(raw[index] === "*" && raw[index + 1] === "/")) {
120
+ index += 1;
121
+ }
122
+ index = Math.min(index + 2, raw.length);
123
+ continue;
124
+ }
125
+ break;
126
+ }
127
+ return index;
128
+ }
129
+ function readQuotedString(raw, start) {
130
+ const quote = raw[start];
131
+ let index = start + 1;
132
+ while (index < raw.length) {
133
+ const char = raw[index];
134
+ if (char === "\\") {
135
+ if (index + 1 >= raw.length) {
136
+ throw new Error("Unterminated escape sequence in config string.");
137
+ }
138
+ index += 2;
139
+ continue;
140
+ }
141
+ if (char === quote) {
142
+ return {
143
+ value: json5_1.default.parse(raw.slice(start, index + 1)),
144
+ end: index + 1,
145
+ };
146
+ }
147
+ index += 1;
148
+ }
149
+ throw new Error("Unterminated string in config file.");
150
+ }
151
+ function readIdentifier(raw, start) {
152
+ const first = raw[start];
153
+ if (!/[A-Za-z_$]/.test(first)) {
154
+ return null;
155
+ }
156
+ let end = start + 1;
157
+ while (end < raw.length && /[A-Za-z0-9_$-]/.test(raw[end])) {
158
+ end += 1;
159
+ }
160
+ return {
161
+ value: raw.slice(start, end),
162
+ end,
163
+ };
164
+ }
165
+ function scanEnclosedValue(raw, start, openChar, closeChar) {
166
+ let depth = 1;
167
+ let index = start + 1;
168
+ while (index < raw.length) {
169
+ const char = raw[index];
170
+ if (char === "\"" || char === "'") {
171
+ index = readQuotedString(raw, index).end;
172
+ continue;
173
+ }
174
+ if (char === "/" && raw[index + 1] === "/") {
175
+ index = skipTrivia(raw, index);
176
+ continue;
177
+ }
178
+ if (char === "/" && raw[index + 1] === "*") {
179
+ index = skipTrivia(raw, index);
180
+ continue;
181
+ }
182
+ if (char === openChar) {
183
+ depth += 1;
184
+ index += 1;
185
+ continue;
186
+ }
187
+ if (char === closeChar) {
188
+ depth -= 1;
189
+ index += 1;
190
+ if (depth === 0) {
191
+ return index;
192
+ }
193
+ continue;
194
+ }
195
+ if (openChar === "{" && char === "[") {
196
+ index = scanEnclosedValue(raw, index, "[", "]");
197
+ continue;
198
+ }
199
+ if (openChar === "[" && char === "{") {
200
+ index = scanEnclosedValue(raw, index, "{", "}");
201
+ continue;
202
+ }
203
+ index += 1;
204
+ }
205
+ throw new Error("Unterminated structured value in config file.");
206
+ }
207
+ function scanValue(raw, start) {
208
+ const char = raw[start];
209
+ if (char === "{") {
210
+ return {
211
+ end: scanEnclosedValue(raw, start, "{", "}"),
212
+ kind: "object",
213
+ };
214
+ }
215
+ if (char === "[") {
216
+ return {
217
+ end: scanEnclosedValue(raw, start, "[", "]"),
218
+ kind: "array",
219
+ };
220
+ }
221
+ if (char === "\"" || char === "'") {
222
+ return {
223
+ end: readQuotedString(raw, start).end,
224
+ kind: "string",
225
+ };
226
+ }
227
+ let end = start;
228
+ while (end < raw.length) {
229
+ const current = raw[end];
230
+ if (current === "," ||
231
+ current === "}" ||
232
+ current === "]" ||
233
+ current === "\n" ||
234
+ current === "\r" ||
235
+ current === "\t" ||
236
+ current === " " ||
237
+ (current === "/" && (raw[end + 1] === "/" || raw[end + 1] === "*"))) {
238
+ break;
239
+ }
240
+ end += 1;
241
+ }
242
+ return {
243
+ end,
244
+ kind: "scalar",
245
+ };
246
+ }
247
+ function findObjectEnd(raw, objectStart) {
248
+ return scanEnclosedValue(raw, objectStart, "{", "}");
249
+ }
250
+ function listObjectProperties(raw, objectStart) {
251
+ const properties = [];
252
+ const objectEnd = findObjectEnd(raw, objectStart);
253
+ let cursor = skipTrivia(raw, objectStart + 1);
254
+ while (cursor < objectEnd) {
255
+ if (raw[cursor] === "}") {
256
+ break;
257
+ }
258
+ const keyToken = raw[cursor] === "\"" || raw[cursor] === "'"
259
+ ? readQuotedString(raw, cursor)
260
+ : readIdentifier(raw, cursor);
261
+ if (!keyToken) {
262
+ throw new Error(`Unable to parse config object key near index ${cursor}.`);
263
+ }
264
+ const afterKey = skipTrivia(raw, keyToken.end);
265
+ if (raw[afterKey] !== ":") {
266
+ throw new Error(`Expected ":" after config key "${keyToken.value}".`);
267
+ }
268
+ const valueStart = skipTrivia(raw, afterKey + 1);
269
+ const scannedValue = scanValue(raw, valueStart);
270
+ const afterValue = skipTrivia(raw, scannedValue.end);
271
+ const delimiter = raw[afterValue];
272
+ if (delimiter !== "," && delimiter !== "}") {
273
+ throw new Error(`Unexpected token after config key "${keyToken.value}".`);
274
+ }
275
+ properties.push({
276
+ key: keyToken.value,
277
+ keyStart: cursor,
278
+ valueStart,
279
+ valueEnd: scannedValue.end,
280
+ valueKind: scannedValue.kind,
281
+ delimiter,
282
+ });
283
+ if (delimiter === "}") {
284
+ break;
285
+ }
286
+ cursor = skipTrivia(raw, afterValue + 1);
287
+ }
288
+ return properties;
289
+ }
290
+ function findObjectProperty(raw, objectStart, key) {
291
+ return listObjectProperties(raw, objectStart).find((entry) => entry.key === key) ?? null;
292
+ }
293
+ function formatConfigValue(value, propertyIndent, format) {
294
+ const serialized = JSON.stringify(value, null, 2);
295
+ if (serialized === undefined) {
296
+ throw new Error("Unable to serialize config value.");
297
+ }
298
+ return serialized
299
+ .split("\n")
300
+ .map((line, index) => {
301
+ if (index === 0) {
302
+ return line;
303
+ }
304
+ const indentMatch = line.match(/^ +/);
305
+ const level = indentMatch ? Math.floor(indentMatch[0].length / 2) : 0;
306
+ return `${propertyIndent}${format.indentUnit.repeat(level)}${line.trimStart()}`;
307
+ })
308
+ .join(format.eol);
309
+ }
310
+ function replaceRange(raw, start, end, value) {
311
+ return `${raw.slice(0, start)}${value}${raw.slice(end)}`;
312
+ }
313
+ function insertObjectProperty(raw, objectStart, key, value, format) {
314
+ const objectEnd = findObjectEnd(raw, objectStart);
315
+ const parentIndent = getLineIndent(raw, objectStart);
316
+ const propertyIndent = `${parentIndent}${format.indentUnit}`;
317
+ const propertyText = `${JSON.stringify(key)}: ${formatConfigValue(value, propertyIndent, format)}`;
318
+ const properties = listObjectProperties(raw, objectStart);
319
+ if (properties.length === 0) {
320
+ const insertion = `${format.eol}${propertyIndent}${propertyText}${format.eol}${parentIndent}`;
321
+ return replaceRange(raw, objectEnd, objectEnd, insertion);
322
+ }
323
+ let insertAt = objectEnd;
324
+ while (insertAt > objectStart + 1 && /[ \t\r\n]/.test(raw[insertAt - 1])) {
325
+ insertAt -= 1;
326
+ }
327
+ const separator = properties[properties.length - 1]?.delimiter === "," ? "" : ",";
328
+ const insertion = `${separator}${format.eol}${propertyIndent}${propertyText}`;
329
+ return replaceRange(raw, insertAt, insertAt, insertion);
330
+ }
331
+ function replaceObjectPropertyValue(raw, property, value, format) {
332
+ const propertyIndent = getLineIndent(raw, property.keyStart);
333
+ const formattedValue = formatConfigValue(value, propertyIndent, format);
334
+ return replaceRange(raw, property.valueStart, property.valueEnd, formattedValue);
335
+ }
336
+ function buildUpdatedConfigText(raw, accountId, auth) {
337
+ const parsed = json5_1.default.parse(raw);
338
+ if (!isPlainObject(parsed)) {
339
+ throw new Error("OpenClaw config root must be an object.");
340
+ }
341
+ const updatedConfig = applyAuthToConfig(parsed, accountId, auth);
342
+ const updatedChannels = isPlainObject(updatedConfig.channels) ? updatedConfig.channels : {};
343
+ const format = detectTextFormat(raw);
344
+ const rootStart = skipTrivia(raw, 0);
345
+ if (raw[rootStart] !== "{") {
346
+ throw new Error("OpenClaw config file is not a JSON object.");
347
+ }
348
+ const channelsProperty = findObjectProperty(raw, rootStart, "channels");
349
+ if (!channelsProperty) {
350
+ return insertObjectProperty(raw, rootStart, "channels", updatedChannels, format);
351
+ }
352
+ return replaceObjectPropertyValue(raw, channelsProperty, updatedChannels, format);
353
+ }
354
+ async function writeConfigAtomically(configPath, raw) {
355
+ const tempPath = `${configPath}.tmp-${process.pid}-${Date.now()}`;
356
+ try {
357
+ await node_fs_1.promises.writeFile(tempPath, raw, "utf8");
358
+ await node_fs_1.promises.rename(tempPath, configPath);
359
+ }
360
+ catch (error) {
361
+ await node_fs_1.promises.unlink(tempPath).catch(() => undefined);
362
+ throw error;
363
+ }
364
+ }
365
+ async function createConfigBackup(configPath) {
366
+ const raw = await node_fs_1.promises.readFile(configPath, "utf8").catch(() => null);
367
+ if (raw === null) {
368
+ return null;
369
+ }
370
+ const backupPath = buildConfigBackupPath(configPath);
371
+ await node_fs_1.promises.writeFile(backupPath, raw, "utf8");
372
+ return backupPath;
373
+ }
374
+ async function updateConfigFileDirectly(configPath, accountId, auth) {
375
+ const raw = await node_fs_1.promises.readFile(configPath, "utf8");
376
+ const nextRaw = buildUpdatedConfigText(raw, accountId, auth);
377
+ if (nextRaw === raw) {
378
+ return;
379
+ }
380
+ await writeConfigAtomically(configPath, nextRaw);
381
+ }
@@ -0,0 +1,253 @@
1
+ {
2
+ "id": "clawgram",
3
+ "name": "Clawgram",
4
+ "description": "Clawgram — personal Telegram (MTProto userbot) channel for OpenClaw. Your AI assistant reads and responds as you.",
5
+ "version": "2.0.0",
6
+ "configSchema": {
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "properties": {}
10
+ },
11
+ "channels": [
12
+ "clawgram"
13
+ ],
14
+ "channelConfigs": {
15
+ "clawgram": {
16
+ "schema": {
17
+ "$schema": "http://json-schema.org/draft-07/schema#",
18
+ "type": "object",
19
+ "additionalProperties": false,
20
+ "properties": {
21
+ "name": {
22
+ "type": "string"
23
+ },
24
+ "enabled": {
25
+ "type": "boolean"
26
+ },
27
+ "allowFrom": {
28
+ "type": "array",
29
+ "items": {
30
+ "anyOf": [
31
+ {
32
+ "type": "string"
33
+ },
34
+ {
35
+ "type": "number"
36
+ }
37
+ ]
38
+ }
39
+ },
40
+ "groups": {
41
+ "type": "object",
42
+ "propertyNames": {
43
+ "type": "string"
44
+ },
45
+ "additionalProperties": {
46
+ "type": "object",
47
+ "additionalProperties": false,
48
+ "properties": {
49
+ "enabled": {
50
+ "type": "boolean"
51
+ },
52
+ "groupPolicy": {
53
+ "type": "string",
54
+ "enum": [
55
+ "open",
56
+ "mention"
57
+ ],
58
+ "default": "mention"
59
+ },
60
+ "allowFrom": {
61
+ "type": "array",
62
+ "items": {
63
+ "anyOf": [
64
+ {
65
+ "type": "string"
66
+ },
67
+ {
68
+ "type": "number"
69
+ }
70
+ ]
71
+ }
72
+ }
73
+ }
74
+ }
75
+ },
76
+ "accounts": {
77
+ "type": "object",
78
+ "propertyNames": {
79
+ "type": "string"
80
+ },
81
+ "additionalProperties": {
82
+ "type": "object",
83
+ "additionalProperties": false,
84
+ "properties": {
85
+ "enabled": {
86
+ "type": "boolean"
87
+ },
88
+ "apiId": {
89
+ "anyOf": [
90
+ {
91
+ "type": "integer",
92
+ "exclusiveMinimum": 0,
93
+ "maximum": 9007199254740991
94
+ },
95
+ {
96
+ "type": "string",
97
+ "pattern": "^[1-9][0-9]*$"
98
+ }
99
+ ]
100
+ },
101
+ "apiHash": {
102
+ "type": "string"
103
+ },
104
+ "sessionString": {
105
+ "type": "string"
106
+ },
107
+ "proxy": {
108
+ "type": "object",
109
+ "additionalProperties": false,
110
+ "properties": {
111
+ "ip": {
112
+ "type": "string",
113
+ "minLength": 1,
114
+ "pattern": "\\S"
115
+ },
116
+ "port": {
117
+ "type": "integer",
118
+ "minimum": 1,
119
+ "maximum": 65535
120
+ },
121
+ "socksType": {
122
+ "type": "integer",
123
+ "enum": [
124
+ 4,
125
+ 5
126
+ ]
127
+ },
128
+ "username": {
129
+ "type": "string"
130
+ },
131
+ "password": {
132
+ "type": "string"
133
+ },
134
+ "timeout": {
135
+ "type": "number",
136
+ "exclusiveMinimum": 0
137
+ }
138
+ },
139
+ "required": [
140
+ "ip",
141
+ "port",
142
+ "socksType"
143
+ ]
144
+ },
145
+ "allowFrom": {
146
+ "type": "array",
147
+ "items": {
148
+ "anyOf": [
149
+ {
150
+ "type": "string"
151
+ },
152
+ {
153
+ "type": "number"
154
+ }
155
+ ]
156
+ }
157
+ },
158
+ "groups": {
159
+ "type": "object",
160
+ "propertyNames": {
161
+ "type": "string"
162
+ },
163
+ "additionalProperties": {
164
+ "type": "object",
165
+ "additionalProperties": false,
166
+ "properties": {
167
+ "enabled": {
168
+ "type": "boolean"
169
+ },
170
+ "groupPolicy": {
171
+ "type": "string",
172
+ "enum": [
173
+ "open",
174
+ "mention"
175
+ ],
176
+ "default": "mention"
177
+ },
178
+ "allowFrom": {
179
+ "type": "array",
180
+ "items": {
181
+ "anyOf": [
182
+ {
183
+ "type": "string"
184
+ },
185
+ {
186
+ "type": "number"
187
+ }
188
+ ]
189
+ }
190
+ }
191
+ }
192
+ }
193
+ },
194
+ "readChats": {
195
+ "type": "array",
196
+ "items": {
197
+ "type": "string"
198
+ },
199
+ "description": "Chat ids the account may read history and membership for. Absent means no restriction; an empty array denies everything."
200
+ },
201
+ "joinsJournalPath": {
202
+ "type": "string",
203
+ "description": "Where joins observed for this account are journalled. Defaults to a per-account file under the OpenClaw state directory."
204
+ }
205
+ },
206
+ "required": [
207
+ "apiId",
208
+ "apiHash",
209
+ "sessionString"
210
+ ]
211
+ }
212
+ }
213
+ }
214
+ },
215
+ "uiHints": {
216
+ "accounts.*.proxy": {
217
+ "label": "SOCKS Proxy",
218
+ "help": "Optional native SOCKS4/SOCKS5 proxy for this account's MTProto connection. GramJS uses raw TCP sockets, so OpenClaw's proxy.proxyUrl does not cover it. This is a SOCKS proxy, not a Telegram MTProxy.",
219
+ "advanced": true
220
+ },
221
+ "accounts.*.proxy.ip": {
222
+ "label": "Proxy Host",
223
+ "help": "Proxy hostname or IP address.",
224
+ "placeholder": "proxy.example.com"
225
+ },
226
+ "accounts.*.proxy.port": {
227
+ "label": "Proxy Port",
228
+ "help": "Proxy TCP port (1-65535).",
229
+ "placeholder": "1080"
230
+ },
231
+ "accounts.*.proxy.socksType": {
232
+ "label": "SOCKS Version",
233
+ "help": "5 for SOCKS5, 4 for SOCKS4.",
234
+ "placeholder": "5"
235
+ },
236
+ "accounts.*.proxy.username": {
237
+ "label": "Proxy Username",
238
+ "help": "Optional username for proxies that require authentication."
239
+ },
240
+ "accounts.*.proxy.password": {
241
+ "label": "Proxy Password",
242
+ "help": "Optional password for proxies that require authentication.",
243
+ "sensitive": true
244
+ },
245
+ "accounts.*.proxy.timeout": {
246
+ "label": "Proxy Timeout",
247
+ "help": "Optional proxy connection timeout in seconds (GramJS default: 5).",
248
+ "placeholder": "10"
249
+ }
250
+ }
251
+ }
252
+ }
253
+ }
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "clawgram",
3
+ "version": "2.0.0",
4
+ "description": "Clawgram — personal Telegram (MTProto userbot) channel for OpenClaw. Your AI assistant reads and responds as you.",
5
+ "main": "./dist/index.js",
6
+ "scripts": {
7
+ "build": "tsc -p tsconfig.json",
8
+ "build:test": "tsc -p tsconfig.test.json",
9
+ "test": "npm run build:test && node test/ensure-compiled.mjs && node --test \"dist-test/test/*.test.js\"",
10
+ "clawgram-cli": "node dist/clawgram-cli.js",
11
+ "clawgram-cli:hello": "node dist/clawgram-cli.js --hello",
12
+ "clawgram-cli:auth": "node dist/clawgram-cli.js --auth"
13
+ },
14
+ "keywords": [
15
+ "clawgram",
16
+ "openclaw",
17
+ "telegram",
18
+ "userbot",
19
+ "mtproto",
20
+ "gramjs",
21
+ "plugin",
22
+ "channel"
23
+ ],
24
+ "license": "MIT",
25
+ "files": [
26
+ "dist",
27
+ "package.json",
28
+ "openclaw.plugin.json",
29
+ "README.md"
30
+ ],
31
+ "author": "Konstantin Dipezh (d3pre5s)",
32
+ "contributors": [
33
+ "eldaruma (original author of telegram-userbot)"
34
+ ],
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/d3pre5s/clawgram"
38
+ },
39
+ "homepage": "https://github.com/d3pre5s/clawgram#readme",
40
+ "bugs": {
41
+ "url": "https://github.com/d3pre5s/clawgram/issues"
42
+ },
43
+ "openclaw": {
44
+ "extensions": [
45
+ "./dist/index.js"
46
+ ],
47
+ "compat": {
48
+ "pluginApi": ">=2026.5.7",
49
+ "minGatewayVersion": "2026.5.7"
50
+ },
51
+ "build": {
52
+ "openclawVersion": "2026.5.7",
53
+ "pluginSdkVersion": "2026.5.7"
54
+ }
55
+ },
56
+ "devDependencies": {
57
+ "openclaw": "2026.5.7",
58
+ "typescript": "^5.9.3"
59
+ },
60
+ "peerDependencies": {
61
+ "openclaw": ">=2026.5.7"
62
+ },
63
+ "installDependencies": true,
64
+ "dependencies": {
65
+ "json5": "^2.2.3",
66
+ "telegram": "^2.26.22"
67
+ }
68
+ }