@deadragdoll/tellymcp 0.0.8 → 0.0.10
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/.env.example.client +2 -0
- package/.env.example.gateway +2 -0
- package/README-ru.md +21 -1
- package/README.md +5 -1
- package/TOOLS.md +3 -1
- package/VERSION.md +58 -0
- package/config/templates/env.both.template +1 -0
- package/config/templates/env.client.template +1 -0
- package/config/templates/env.gateway.template +1 -0
- package/dist/services/features/telegram-mcp/approval.service.js +1 -1
- package/dist/services/features/telegram-mcp/browser.service.js +1 -1
- package/dist/services/features/telegram-mcp/collaboration.service.js +1 -1
- package/dist/services/features/telegram-mcp/gateway-rmq.service.js +3 -2
- package/dist/services/features/telegram-mcp/gateway-socket.service.js +8 -2
- package/dist/services/features/telegram-mcp/inbox.service.js +1 -1
- package/dist/services/features/telegram-mcp/mcp-http.service.js +1 -1
- package/dist/services/features/telegram-mcp/notify.service.js +1 -1
- package/dist/services/features/telegram-mcp/pair.service.js +1 -1
- package/dist/services/features/telegram-mcp/runtime.service.js +27 -7
- package/dist/services/features/telegram-mcp/session-context.service.js +1 -1
- package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +2 -1
- package/dist/services/features/telegram-mcp/src/app/config/env.js +4 -0
- package/dist/services/features/telegram-mcp/src/app/http.js +8 -1
- package/dist/services/features/telegram-mcp/src/app/providers/mcp/server.js +2 -1
- package/dist/services/features/telegram-mcp/src/app/webapp/assets.js +201 -60
- package/dist/services/features/telegram-mcp/src/shared/i18n/index.js +46 -0
- package/dist/services/features/telegram-mcp/src/shared/i18n/resources/en.js +555 -0
- package/dist/services/features/telegram-mcp/src/shared/i18n/resources/ru.js +555 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/redis/stateStore.js +9 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +1227 -567
- package/dist/services/features/telegram-mcp/standalone-http.service.js +1 -1
- package/dist/services/features/telegram-mcp/tools-sync.service.js +1 -1
- package/package.json +2 -1
|
@@ -100,6 +100,21 @@ body {
|
|
|
100
100
|
border-color: rgba(87, 193, 255, 0.8);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
.btn.active {
|
|
104
|
+
border-color: rgba(87, 193, 255, 0.85);
|
|
105
|
+
background: linear-gradient(180deg, rgba(20, 54, 77, 0.98) 0%, rgba(14, 39, 57, 1) 100%);
|
|
106
|
+
color: #d9f3ff;
|
|
107
|
+
box-shadow: inset 0 0 0 1px rgba(87, 193, 255, 0.16);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.btn.toggle {
|
|
111
|
+
border-color: rgba(87, 193, 255, 0.42);
|
|
112
|
+
background: linear-gradient(180deg, rgba(31, 38, 52, 0.98) 0%, rgba(21, 27, 38, 1) 100%);
|
|
113
|
+
color: #cfe9ff;
|
|
114
|
+
box-shadow: inset 0 0 0 1px rgba(87, 193, 255, 0.08);
|
|
115
|
+
font-weight: 600;
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
.statusbar {
|
|
104
119
|
position: fixed;
|
|
105
120
|
left: 0;
|
|
@@ -127,11 +142,32 @@ body {
|
|
|
127
142
|
gap: 8px 16px;
|
|
128
143
|
}
|
|
129
144
|
|
|
145
|
+
.status-toggle {
|
|
146
|
+
min-width: 72px;
|
|
147
|
+
padding: 4px 8px;
|
|
148
|
+
border-radius: 9px;
|
|
149
|
+
font-size: 12px;
|
|
150
|
+
line-height: 1.1;
|
|
151
|
+
}
|
|
152
|
+
|
|
130
153
|
.session-label {
|
|
131
|
-
|
|
154
|
+
display: inline-flex;
|
|
155
|
+
align-items: center;
|
|
156
|
+
min-height: 24px;
|
|
157
|
+
padding: 3px 8px;
|
|
158
|
+
border-radius: 999px;
|
|
159
|
+
border: 1px solid rgba(76, 217, 100, 0.42);
|
|
160
|
+
background: rgba(26, 42, 31, 0.92);
|
|
161
|
+
color: #d6ffe0;
|
|
132
162
|
font-weight: 600;
|
|
133
163
|
}
|
|
134
164
|
|
|
165
|
+
.session-label.error {
|
|
166
|
+
border-color: rgba(255, 116, 116, 0.5);
|
|
167
|
+
background: rgba(57, 23, 26, 0.94);
|
|
168
|
+
color: #ffd8d8;
|
|
169
|
+
}
|
|
170
|
+
|
|
135
171
|
.ok {
|
|
136
172
|
color: var(--success);
|
|
137
173
|
}
|
|
@@ -146,6 +182,13 @@ body {
|
|
|
146
182
|
font: 13px/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
147
183
|
}
|
|
148
184
|
|
|
185
|
+
.terminal.unwrap {
|
|
186
|
+
white-space: pre;
|
|
187
|
+
word-break: normal;
|
|
188
|
+
overflow-x: auto;
|
|
189
|
+
overflow-y: auto;
|
|
190
|
+
}
|
|
191
|
+
|
|
149
192
|
.ansi-bold {
|
|
150
193
|
font-weight: 700;
|
|
151
194
|
}
|
|
@@ -194,6 +237,8 @@ const state = {
|
|
|
194
237
|
timer: null,
|
|
195
238
|
actionBusy: false,
|
|
196
239
|
pollIntervalMs: 2000,
|
|
240
|
+
wrapEnabled: true,
|
|
241
|
+
recoverPromise: null,
|
|
197
242
|
};
|
|
198
243
|
|
|
199
244
|
const elements = {
|
|
@@ -201,6 +246,7 @@ const elements = {
|
|
|
201
246
|
status: document.querySelector("[data-role=status]"),
|
|
202
247
|
updated: document.querySelector("[data-role=updated]"),
|
|
203
248
|
interrupt: document.querySelector("[data-role=interrupt]"),
|
|
249
|
+
wrap: document.querySelector("[data-role=wrap]"),
|
|
204
250
|
type: document.querySelector("[data-role=type]"),
|
|
205
251
|
esc: document.querySelector("[data-role=escape]"),
|
|
206
252
|
tab: document.querySelector("[data-role=tab]"),
|
|
@@ -213,16 +259,80 @@ const elements = {
|
|
|
213
259
|
};
|
|
214
260
|
|
|
215
261
|
function setStatus(text, isError = false) {
|
|
216
|
-
|
|
217
|
-
elements.status.textContent = text + " - " + sessionName;
|
|
262
|
+
elements.status.textContent = text;
|
|
218
263
|
elements.status.classList.toggle("error", isError);
|
|
219
264
|
elements.status.classList.toggle("ok", !isError);
|
|
265
|
+
elements.session.classList.toggle("error", isError);
|
|
266
|
+
elements.session.classList.toggle("ok", !isError);
|
|
220
267
|
}
|
|
221
268
|
|
|
222
269
|
function setUpdated(text) {
|
|
223
270
|
elements.updated.textContent = text;
|
|
224
271
|
}
|
|
225
272
|
|
|
273
|
+
function createHttpError(message, status) {
|
|
274
|
+
const error = new Error(message);
|
|
275
|
+
error.status = status;
|
|
276
|
+
return error;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function shouldRecoverWebAppSession(error) {
|
|
280
|
+
return (
|
|
281
|
+
error &&
|
|
282
|
+
typeof error === "object" &&
|
|
283
|
+
(error.status === 401 || error.status === 403)
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function applyTmuxAvailability(hasTarget) {
|
|
288
|
+
elements.interrupt.disabled = !hasTarget;
|
|
289
|
+
elements.type.disabled = !hasTarget;
|
|
290
|
+
elements.esc.disabled = !hasTarget;
|
|
291
|
+
elements.tab.disabled = !hasTarget;
|
|
292
|
+
elements.slash.disabled = !hasTarget;
|
|
293
|
+
elements.del.disabled = !hasTarget;
|
|
294
|
+
elements.up.disabled = !hasTarget;
|
|
295
|
+
elements.down.disabled = !hasTarget;
|
|
296
|
+
elements.enter.disabled = !hasTarget;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function getWrapPreferenceKey() {
|
|
300
|
+
return "telegram-mcp-live-wrap";
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function applyWrapMode(enabled) {
|
|
304
|
+
state.wrapEnabled = enabled;
|
|
305
|
+
elements.terminal.classList.toggle("unwrap", !enabled);
|
|
306
|
+
elements.wrap.classList.toggle("active", enabled);
|
|
307
|
+
elements.wrap.setAttribute("aria-pressed", enabled ? "true" : "false");
|
|
308
|
+
elements.wrap.textContent = enabled ? "Unwrap" : "Wrap";
|
|
309
|
+
elements.wrap.title = enabled
|
|
310
|
+
? "Wrapping enabled. Tap to switch to horizontal scroll."
|
|
311
|
+
: "Horizontal scroll enabled. Tap to wrap lines.";
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function loadWrapPreference() {
|
|
315
|
+
try {
|
|
316
|
+
const stored = window.localStorage.getItem(getWrapPreferenceKey());
|
|
317
|
+
if (stored === "off") {
|
|
318
|
+
applyWrapMode(false);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
} catch (_error) {
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
applyWrapMode(true);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function toggleWrapMode() {
|
|
328
|
+
const next = !state.wrapEnabled;
|
|
329
|
+
applyWrapMode(next);
|
|
330
|
+
try {
|
|
331
|
+
window.localStorage.setItem(getWrapPreferenceKey(), next ? "on" : "off");
|
|
332
|
+
} catch (_error) {
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
226
336
|
function formatCapturedAt(value) {
|
|
227
337
|
if (!value) {
|
|
228
338
|
return "never";
|
|
@@ -234,12 +344,10 @@ function formatCapturedAt(value) {
|
|
|
234
344
|
}
|
|
235
345
|
|
|
236
346
|
return new Intl.DateTimeFormat(undefined, {
|
|
237
|
-
year: "numeric",
|
|
238
|
-
month: "2-digit",
|
|
239
|
-
day: "2-digit",
|
|
240
347
|
hour: "2-digit",
|
|
241
348
|
minute: "2-digit",
|
|
242
349
|
second: "2-digit",
|
|
350
|
+
hour12: false,
|
|
243
351
|
}).format(date);
|
|
244
352
|
}
|
|
245
353
|
|
|
@@ -561,6 +669,49 @@ async function bootstrap() {
|
|
|
561
669
|
return response.json();
|
|
562
670
|
}
|
|
563
671
|
|
|
672
|
+
function applyBootstrapPayload(bootstrapPayload) {
|
|
673
|
+
state.token = bootstrapPayload.token;
|
|
674
|
+
state.sessionId = bootstrapPayload.session_id;
|
|
675
|
+
state.pollIntervalMs = bootstrapPayload.poll_interval_ms || state.pollIntervalMs;
|
|
676
|
+
elements.session.textContent =
|
|
677
|
+
bootstrapPayload.session_label || bootstrapPayload.session_id;
|
|
678
|
+
elements.session.hidden = false;
|
|
679
|
+
|
|
680
|
+
const hasTmuxTarget = Boolean(bootstrapPayload.tmux_target);
|
|
681
|
+
applyTmuxAvailability(hasTmuxTarget);
|
|
682
|
+
setStatus(hasTmuxTarget ? "Live" : "No tmux target", !hasTmuxTarget);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
async function recoverWebAppSession() {
|
|
686
|
+
if (state.recoverPromise) {
|
|
687
|
+
return state.recoverPromise;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
state.recoverPromise = (async () => {
|
|
691
|
+
setStatus("Reconnecting...", true);
|
|
692
|
+
const bootstrapPayload = await bootstrap();
|
|
693
|
+
applyBootstrapPayload(bootstrapPayload);
|
|
694
|
+
return bootstrapPayload;
|
|
695
|
+
})().finally(() => {
|
|
696
|
+
state.recoverPromise = null;
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
return state.recoverPromise;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
async function withRecoveredSession(operation) {
|
|
703
|
+
try {
|
|
704
|
+
return await operation();
|
|
705
|
+
} catch (error) {
|
|
706
|
+
if (!shouldRecoverWebAppSession(error)) {
|
|
707
|
+
throw error;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
await recoverWebAppSession();
|
|
711
|
+
return operation();
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
564
715
|
async function fetchVisibleBuffer() {
|
|
565
716
|
const response = await fetch(config.basePath + "/api/view", {
|
|
566
717
|
method: "GET",
|
|
@@ -571,7 +722,7 @@ async function fetchVisibleBuffer() {
|
|
|
571
722
|
|
|
572
723
|
if (!response.ok) {
|
|
573
724
|
const text = await response.text();
|
|
574
|
-
throw
|
|
725
|
+
throw createHttpError(text || "Failed to fetch visible buffer.", response.status);
|
|
575
726
|
}
|
|
576
727
|
|
|
577
728
|
return response.json();
|
|
@@ -584,21 +735,22 @@ async function sendAction(action) {
|
|
|
584
735
|
|
|
585
736
|
state.actionBusy = true;
|
|
586
737
|
try {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
if (!response.ok) {
|
|
597
|
-
const text = await response.text();
|
|
598
|
-
throw new Error(text || "Failed to send action.");
|
|
599
|
-
}
|
|
738
|
+
await withRecoveredSession(async () => {
|
|
739
|
+
const response = await fetch(config.basePath + "/api/action", {
|
|
740
|
+
method: "POST",
|
|
741
|
+
headers: {
|
|
742
|
+
"content-type": "application/json",
|
|
743
|
+
authorization: "Bearer " + state.token,
|
|
744
|
+
},
|
|
745
|
+
body: JSON.stringify({ action }),
|
|
746
|
+
});
|
|
600
747
|
|
|
601
|
-
|
|
748
|
+
if (!response.ok) {
|
|
749
|
+
const text = await response.text();
|
|
750
|
+
throw createHttpError(text || "Failed to send action.", response.status);
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
await withRecoveredSession(refreshVisibleBuffer);
|
|
602
754
|
} finally {
|
|
603
755
|
state.actionBusy = false;
|
|
604
756
|
}
|
|
@@ -611,22 +763,26 @@ async function sendTextInput(text) {
|
|
|
611
763
|
|
|
612
764
|
state.actionBusy = true;
|
|
613
765
|
try {
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
if (!response.ok) {
|
|
624
|
-
const text = await response.text();
|
|
625
|
-
throw new Error(text || "Failed to send text.");
|
|
626
|
-
}
|
|
766
|
+
await withRecoveredSession(async () => {
|
|
767
|
+
const response = await fetch(config.basePath + "/api/action", {
|
|
768
|
+
method: "POST",
|
|
769
|
+
headers: {
|
|
770
|
+
"content-type": "application/json",
|
|
771
|
+
authorization: "Bearer " + state.token,
|
|
772
|
+
},
|
|
773
|
+
body: JSON.stringify({ action: "text", text }),
|
|
774
|
+
});
|
|
627
775
|
|
|
776
|
+
if (!response.ok) {
|
|
777
|
+
const textResponse = await response.text();
|
|
778
|
+
throw createHttpError(
|
|
779
|
+
textResponse || "Failed to send text.",
|
|
780
|
+
response.status,
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
});
|
|
628
784
|
setStatus("Text sent");
|
|
629
|
-
await refreshVisibleBuffer
|
|
785
|
+
await withRecoveredSession(refreshVisibleBuffer);
|
|
630
786
|
} finally {
|
|
631
787
|
state.actionBusy = false;
|
|
632
788
|
}
|
|
@@ -646,9 +802,9 @@ function confirmInterrupt() {
|
|
|
646
802
|
}
|
|
647
803
|
|
|
648
804
|
async function refreshVisibleBuffer() {
|
|
649
|
-
const payload = await fetchVisibleBuffer
|
|
805
|
+
const payload = await withRecoveredSession(fetchVisibleBuffer);
|
|
650
806
|
elements.terminal.innerHTML = renderAnsiToHtml(payload.content || "");
|
|
651
|
-
setUpdated(
|
|
807
|
+
setUpdated(formatCapturedAt(payload.captured_at));
|
|
652
808
|
}
|
|
653
809
|
|
|
654
810
|
function stopPolling() {
|
|
@@ -672,6 +828,10 @@ function startPolling() {
|
|
|
672
828
|
}
|
|
673
829
|
|
|
674
830
|
function bindUi() {
|
|
831
|
+
elements.wrap.addEventListener("click", () => {
|
|
832
|
+
toggleWrapMode();
|
|
833
|
+
});
|
|
834
|
+
|
|
675
835
|
elements.interrupt.addEventListener("click", () => {
|
|
676
836
|
confirmInterrupt()
|
|
677
837
|
.then((ok) => {
|
|
@@ -748,32 +908,12 @@ async function applyLaunchMode() {
|
|
|
748
908
|
async function main() {
|
|
749
909
|
try {
|
|
750
910
|
await applyLaunchMode();
|
|
911
|
+
loadWrapPreference();
|
|
751
912
|
bindUi();
|
|
752
913
|
setStatus("Authorizing Mini App...");
|
|
753
914
|
const bootstrapPayload = await bootstrap();
|
|
754
|
-
|
|
755
|
-
state.sessionId = bootstrapPayload.session_id;
|
|
756
|
-
state.pollIntervalMs = bootstrapPayload.poll_interval_ms || state.pollIntervalMs;
|
|
757
|
-
elements.session.textContent =
|
|
758
|
-
bootstrapPayload.session_label || bootstrapPayload.session_id;
|
|
759
|
-
|
|
760
|
-
if (!bootstrapPayload.tmux_target) {
|
|
761
|
-
elements.interrupt.disabled = true;
|
|
762
|
-
elements.type.disabled = true;
|
|
763
|
-
elements.esc.disabled = true;
|
|
764
|
-
elements.tab.disabled = true;
|
|
765
|
-
elements.slash.disabled = true;
|
|
766
|
-
elements.del.disabled = true;
|
|
767
|
-
elements.up.disabled = true;
|
|
768
|
-
elements.down.disabled = true;
|
|
769
|
-
elements.enter.disabled = true;
|
|
770
|
-
setStatus("No tmux target", true);
|
|
771
|
-
}
|
|
772
|
-
|
|
915
|
+
applyBootstrapPayload(bootstrapPayload);
|
|
773
916
|
await refreshVisibleBuffer();
|
|
774
|
-
if (bootstrapPayload.tmux_target) {
|
|
775
|
-
setStatus("Connected");
|
|
776
|
-
}
|
|
777
917
|
startPolling();
|
|
778
918
|
} catch (error) {
|
|
779
919
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -818,7 +958,8 @@ function renderWebAppHtml(input) {
|
|
|
818
958
|
<span class="session-label" data-role="session" hidden>Live View</span>
|
|
819
959
|
</div>
|
|
820
960
|
<div class="status-right">
|
|
821
|
-
<
|
|
961
|
+
<button class="btn toggle status-toggle active" data-role="wrap" type="button" aria-pressed="true">Wrap</button>
|
|
962
|
+
<span data-role="updated">never</span>
|
|
822
963
|
</div>
|
|
823
964
|
</div>
|
|
824
965
|
</div>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeLocale = normalizeLocale;
|
|
4
|
+
exports.translate = translate;
|
|
5
|
+
const i18next_1 = require("i18next");
|
|
6
|
+
const en_1 = require("./resources/en");
|
|
7
|
+
const ru_1 = require("./resources/ru");
|
|
8
|
+
const instance = (0, i18next_1.createInstance)();
|
|
9
|
+
void instance.init({
|
|
10
|
+
lng: "en",
|
|
11
|
+
fallbackLng: "en",
|
|
12
|
+
ns: ["common", "menu"],
|
|
13
|
+
defaultNS: "common",
|
|
14
|
+
initAsync: false,
|
|
15
|
+
interpolation: {
|
|
16
|
+
escapeValue: false,
|
|
17
|
+
},
|
|
18
|
+
returnNull: false,
|
|
19
|
+
returnEmptyString: false,
|
|
20
|
+
resources: {
|
|
21
|
+
en: {
|
|
22
|
+
common: en_1.enCommon,
|
|
23
|
+
menu: en_1.enMenu,
|
|
24
|
+
},
|
|
25
|
+
ru: {
|
|
26
|
+
common: ru_1.ruCommon,
|
|
27
|
+
menu: ru_1.ruMenu,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
function normalizeLocale(input) {
|
|
32
|
+
const normalized = input?.trim().toLowerCase() ?? "";
|
|
33
|
+
if (normalized === "ru" || normalized.startsWith("ru-")) {
|
|
34
|
+
return "ru";
|
|
35
|
+
}
|
|
36
|
+
if (normalized === "en" || normalized.startsWith("en-")) {
|
|
37
|
+
return "en";
|
|
38
|
+
}
|
|
39
|
+
return "en";
|
|
40
|
+
}
|
|
41
|
+
function translate(locale, key, options) {
|
|
42
|
+
return instance.t(key, {
|
|
43
|
+
lng: normalizeLocale(locale),
|
|
44
|
+
...(options ?? {}),
|
|
45
|
+
});
|
|
46
|
+
}
|