@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.
Files changed (33) hide show
  1. package/.env.example.client +2 -0
  2. package/.env.example.gateway +2 -0
  3. package/README-ru.md +21 -1
  4. package/README.md +5 -1
  5. package/TOOLS.md +3 -1
  6. package/VERSION.md +58 -0
  7. package/config/templates/env.both.template +1 -0
  8. package/config/templates/env.client.template +1 -0
  9. package/config/templates/env.gateway.template +1 -0
  10. package/dist/services/features/telegram-mcp/approval.service.js +1 -1
  11. package/dist/services/features/telegram-mcp/browser.service.js +1 -1
  12. package/dist/services/features/telegram-mcp/collaboration.service.js +1 -1
  13. package/dist/services/features/telegram-mcp/gateway-rmq.service.js +3 -2
  14. package/dist/services/features/telegram-mcp/gateway-socket.service.js +8 -2
  15. package/dist/services/features/telegram-mcp/inbox.service.js +1 -1
  16. package/dist/services/features/telegram-mcp/mcp-http.service.js +1 -1
  17. package/dist/services/features/telegram-mcp/notify.service.js +1 -1
  18. package/dist/services/features/telegram-mcp/pair.service.js +1 -1
  19. package/dist/services/features/telegram-mcp/runtime.service.js +27 -7
  20. package/dist/services/features/telegram-mcp/session-context.service.js +1 -1
  21. package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +2 -1
  22. package/dist/services/features/telegram-mcp/src/app/config/env.js +4 -0
  23. package/dist/services/features/telegram-mcp/src/app/http.js +8 -1
  24. package/dist/services/features/telegram-mcp/src/app/providers/mcp/server.js +2 -1
  25. package/dist/services/features/telegram-mcp/src/app/webapp/assets.js +201 -60
  26. package/dist/services/features/telegram-mcp/src/shared/i18n/index.js +46 -0
  27. package/dist/services/features/telegram-mcp/src/shared/i18n/resources/en.js +555 -0
  28. package/dist/services/features/telegram-mcp/src/shared/i18n/resources/ru.js +555 -0
  29. package/dist/services/features/telegram-mcp/src/shared/integrations/redis/stateStore.js +9 -0
  30. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +1227 -567
  31. package/dist/services/features/telegram-mcp/standalone-http.service.js +1 -1
  32. package/dist/services/features/telegram-mcp/tools-sync.service.js +1 -1
  33. 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
- color: var(--text);
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
- const sessionName = elements.session.textContent || "Live View";
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 new Error(text || "Failed to fetch visible buffer.");
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
- const response = await fetch(config.basePath + "/api/action", {
588
- method: "POST",
589
- headers: {
590
- "content-type": "application/json",
591
- authorization: "Bearer " + state.token,
592
- },
593
- body: JSON.stringify({ action }),
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
- await refreshVisibleBuffer();
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
- const response = await fetch(config.basePath + "/api/action", {
615
- method: "POST",
616
- headers: {
617
- "content-type": "application/json",
618
- authorization: "Bearer " + state.token,
619
- },
620
- body: JSON.stringify({ action: "text", text }),
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("Updated: " + formatCapturedAt(payload.captured_at));
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
- state.token = bootstrapPayload.token;
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
- <span data-role="updated">Updated: never</span>
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
+ }