@deadragdoll/tellymcp 0.0.4 → 0.0.5
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/VERSION.md +22 -0
- package/dist/services/features/telegram-mcp/gateway-socket.service.js +13 -2
- package/dist/services/features/telegram-mcp/src/app/http.js +18 -2
- package/dist/services/features/telegram-mcp/src/app/webapp/assets.js +99 -7
- package/dist/services/features/telegram-mcp/src/app/webapp/tmux.js +2 -1
- package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +1 -0
- package/dist/services/features/telegram-mcp/src/shared/integrations/tmux/client.js +8 -2
- package/package.json +1 -1
package/VERSION.md
CHANGED
|
@@ -21,6 +21,15 @@ For detailed engineering history, refactors, and internal development notes, see
|
|
|
21
21
|
- [README.md](README.md)
|
|
22
22
|
- [README-ru.md](README-ru.md)
|
|
23
23
|
- Human-readable release notes in this file.
|
|
24
|
+
- Telegram startup notice:
|
|
25
|
+
- version
|
|
26
|
+
- protocol
|
|
27
|
+
- mode
|
|
28
|
+
- paired sessions
|
|
29
|
+
- MCP/WebApp/Gateway endpoints
|
|
30
|
+
- Live text input button:
|
|
31
|
+
- `[txt]`
|
|
32
|
+
- sends literal text to tmux without pressing `Enter`
|
|
24
33
|
|
|
25
34
|
### Changed
|
|
26
35
|
- Default installation path is now npm-first:
|
|
@@ -32,6 +41,7 @@ For detailed engineering history, refactors, and internal development notes, see
|
|
|
32
41
|
- direct terminal control from Telegram
|
|
33
42
|
- Environment examples were split into dedicated client and gateway variants.
|
|
34
43
|
- Package build/publish flow now validates itself before packing/publishing.
|
|
44
|
+
- CLI now shows package version directly in banners and startup output.
|
|
35
45
|
|
|
36
46
|
### Collaboration
|
|
37
47
|
- Project collaboration works across local and remote sessions.
|
|
@@ -55,6 +65,18 @@ For detailed engineering history, refactors, and internal development notes, see
|
|
|
55
65
|
- `Down`
|
|
56
66
|
- `Enter`
|
|
57
67
|
- Live approval flow was added for remote project sessions.
|
|
68
|
+
- Live toolbar now includes:
|
|
69
|
+
- `/`
|
|
70
|
+
- `↑`
|
|
71
|
+
- `↓`
|
|
72
|
+
- `Enter`
|
|
73
|
+
- `⌫`
|
|
74
|
+
- `[txt]`
|
|
75
|
+
- `Tab`
|
|
76
|
+
- `Esc`
|
|
77
|
+
- `Ctrl+C`
|
|
78
|
+
- `Ctrl+C` now asks for confirmation before sending an interrupt to the agent.
|
|
79
|
+
- Mobile toolbar layout now wraps cleanly into two rows instead of collapsing into a centered stack.
|
|
58
80
|
|
|
59
81
|
### Browser
|
|
60
82
|
- Browser tools use Playwright Chromium.
|
|
@@ -554,15 +554,26 @@ const TelegramMcpGatewaySocketService = {
|
|
|
554
554
|
const action = typeof request.payload?.action === "string"
|
|
555
555
|
? request.payload.action
|
|
556
556
|
: "";
|
|
557
|
-
|
|
557
|
+
const text = typeof request.payload?.text === "string"
|
|
558
|
+
? request.payload.text
|
|
559
|
+
: "";
|
|
560
|
+
if (!["up", "down", "enter", "slash", "delete", "tab", "escape", "interrupt", "text"].includes(action)) {
|
|
558
561
|
throw new Error("Unsupported action");
|
|
559
562
|
}
|
|
563
|
+
if (action === "text" && (!text || text.length > 4000)) {
|
|
564
|
+
throw new Error("Text payload is required and must be <= 4000 characters");
|
|
565
|
+
}
|
|
560
566
|
const sessionId = request.local_session_id.trim();
|
|
561
567
|
const session = await runtime.sessionStore.getSession(sessionId);
|
|
562
568
|
if (!session?.tmuxTarget) {
|
|
563
569
|
throw new Error("tmux target is not configured for this session");
|
|
564
570
|
}
|
|
565
|
-
|
|
571
|
+
if (action === "text") {
|
|
572
|
+
await (0, tmux_1.sendTmuxLiteralText)(runtime.config.tmux, session.tmuxTarget, text);
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
await (0, tmux_1.sendAllowedTmuxAction)(runtime.config.tmux, session.tmuxTarget, action);
|
|
576
|
+
}
|
|
566
577
|
return {
|
|
567
578
|
type: "live_response",
|
|
568
579
|
request_id: request.request_id,
|
|
@@ -428,10 +428,19 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
428
428
|
typeof Reflect.get(body, "action") === "string"
|
|
429
429
|
? String(Reflect.get(body, "action"))
|
|
430
430
|
: "";
|
|
431
|
-
|
|
431
|
+
const text = body &&
|
|
432
|
+
typeof body === "object" &&
|
|
433
|
+
typeof Reflect.get(body, "text") === "string"
|
|
434
|
+
? String(Reflect.get(body, "text"))
|
|
435
|
+
: "";
|
|
436
|
+
if (!["up", "down", "enter", "slash", "delete", "tab", "escape", "interrupt", "text"].includes(action)) {
|
|
432
437
|
writeText(res, 400, "Unsupported action");
|
|
433
438
|
return;
|
|
434
439
|
}
|
|
440
|
+
if (action === "text" && (!text || text.length > 4000)) {
|
|
441
|
+
writeText(res, 400, "Text payload is required and must be <= 4000 characters");
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
435
444
|
const nowMs = Date.now();
|
|
436
445
|
if (nowMs - webAppSession.lastActionAtMs <
|
|
437
446
|
runtime.config.webapp.actionCooldownMs) {
|
|
@@ -445,6 +454,7 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
445
454
|
clientUuid: relayTarget.clientUuid,
|
|
446
455
|
localSessionId: relayTarget.localSessionId,
|
|
447
456
|
action: action,
|
|
457
|
+
...(action === "text" ? { text } : {}),
|
|
448
458
|
});
|
|
449
459
|
webAppSessions.touchAction(webAppSession.token, nowMs);
|
|
450
460
|
writeJson(res, 200, {
|
|
@@ -477,12 +487,18 @@ function createMcpHttpHandler(runtime, input) {
|
|
|
477
487
|
return;
|
|
478
488
|
}
|
|
479
489
|
try {
|
|
480
|
-
|
|
490
|
+
if (action === "text") {
|
|
491
|
+
await (0, tmux_1.sendTmuxLiteralText)(runtime.config.tmux, session.tmuxTarget, text);
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
await (0, tmux_1.sendAllowedTmuxAction)(runtime.config.tmux, session.tmuxTarget, action);
|
|
495
|
+
}
|
|
481
496
|
webAppSessions.touchAction(webAppSession.token, nowMs);
|
|
482
497
|
runtime.logger.info("Telegram WebApp action sent to tmux", {
|
|
483
498
|
sessionId: webAppSession.sessionId,
|
|
484
499
|
telegramUserId: webAppSession.telegramUserId,
|
|
485
500
|
action,
|
|
501
|
+
...(action === "text" ? { textLength: text.length } : {}),
|
|
486
502
|
});
|
|
487
503
|
writeJson(res, 200, {
|
|
488
504
|
ok: true,
|
|
@@ -42,7 +42,7 @@ body {
|
|
|
42
42
|
bottom: calc(42px + env(safe-area-inset-bottom, 0px));
|
|
43
43
|
z-index: 30;
|
|
44
44
|
display: flex;
|
|
45
|
-
justify-content:
|
|
45
|
+
justify-content: flex-start;
|
|
46
46
|
flex-wrap: wrap;
|
|
47
47
|
gap: 8px;
|
|
48
48
|
padding: 10px 14px;
|
|
@@ -52,6 +52,11 @@ body {
|
|
|
52
52
|
backdrop-filter: blur(14px);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
.toolbar-spacer {
|
|
56
|
+
flex: 1 1 auto;
|
|
57
|
+
min-width: 12px;
|
|
58
|
+
}
|
|
59
|
+
|
|
55
60
|
.btn {
|
|
56
61
|
appearance: none;
|
|
57
62
|
border: 1px solid var(--border);
|
|
@@ -73,6 +78,28 @@ body {
|
|
|
73
78
|
.btn:disabled { cursor: not-allowed; opacity: 0.55; }
|
|
74
79
|
.btn.danger:hover { border-color: var(--danger); }
|
|
75
80
|
|
|
81
|
+
.btn.danger {
|
|
82
|
+
border-color: rgba(255, 116, 116, 0.55);
|
|
83
|
+
background: linear-gradient(180deg, rgba(78, 18, 24, 0.96) 0%, rgba(50, 14, 18, 0.98) 100%);
|
|
84
|
+
color: #ffd7d7;
|
|
85
|
+
box-shadow: inset 0 0 0 1px rgba(255, 116, 116, 0.12);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.btn.danger:hover {
|
|
89
|
+
border-color: rgba(255, 116, 116, 0.9);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.btn.primary {
|
|
93
|
+
border-color: rgba(87, 193, 255, 0.45);
|
|
94
|
+
background: linear-gradient(180deg, rgba(17, 45, 66, 0.96) 0%, rgba(12, 33, 48, 0.98) 100%);
|
|
95
|
+
color: #d9f3ff;
|
|
96
|
+
box-shadow: inset 0 0 0 1px rgba(87, 193, 255, 0.1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.btn.primary:hover {
|
|
100
|
+
border-color: rgba(87, 193, 255, 0.8);
|
|
101
|
+
}
|
|
102
|
+
|
|
76
103
|
.statusbar {
|
|
77
104
|
position: fixed;
|
|
78
105
|
left: 0;
|
|
@@ -138,6 +165,11 @@ body {
|
|
|
138
165
|
padding: 8px 10px;
|
|
139
166
|
}
|
|
140
167
|
|
|
168
|
+
.toolbar-spacer {
|
|
169
|
+
flex: 1 1 auto;
|
|
170
|
+
min-width: 12px;
|
|
171
|
+
}
|
|
172
|
+
|
|
141
173
|
.btn.compact {
|
|
142
174
|
min-width: 42px;
|
|
143
175
|
padding: 8px 10px;
|
|
@@ -169,6 +201,7 @@ const elements = {
|
|
|
169
201
|
status: document.querySelector("[data-role=status]"),
|
|
170
202
|
updated: document.querySelector("[data-role=updated]"),
|
|
171
203
|
interrupt: document.querySelector("[data-role=interrupt]"),
|
|
204
|
+
type: document.querySelector("[data-role=type]"),
|
|
172
205
|
esc: document.querySelector("[data-role=escape]"),
|
|
173
206
|
tab: document.querySelector("[data-role=tab]"),
|
|
174
207
|
slash: document.querySelector("[data-role=slash]"),
|
|
@@ -571,6 +604,47 @@ async function sendAction(action) {
|
|
|
571
604
|
}
|
|
572
605
|
}
|
|
573
606
|
|
|
607
|
+
async function sendTextInput(text) {
|
|
608
|
+
if (state.actionBusy || !state.token) {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
state.actionBusy = true;
|
|
613
|
+
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
|
+
}
|
|
627
|
+
|
|
628
|
+
setStatus("Text sent");
|
|
629
|
+
await refreshVisibleBuffer();
|
|
630
|
+
} finally {
|
|
631
|
+
state.actionBusy = false;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function confirmInterrupt() {
|
|
636
|
+
return new Promise((resolve) => {
|
|
637
|
+
if (tg && typeof tg.showConfirm === "function") {
|
|
638
|
+
tg.showConfirm("Send Ctrl+C to the tmux session? This can stop the running agent.", (ok) => {
|
|
639
|
+
resolve(Boolean(ok));
|
|
640
|
+
});
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
resolve(window.confirm("Send Ctrl+C to the tmux session? This can stop the running agent."));
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
|
|
574
648
|
async function refreshVisibleBuffer() {
|
|
575
649
|
const payload = await fetchVisibleBuffer();
|
|
576
650
|
elements.terminal.innerHTML = renderAnsiToHtml(payload.content || "");
|
|
@@ -599,7 +673,22 @@ function startPolling() {
|
|
|
599
673
|
|
|
600
674
|
function bindUi() {
|
|
601
675
|
elements.interrupt.addEventListener("click", () => {
|
|
602
|
-
|
|
676
|
+
confirmInterrupt()
|
|
677
|
+
.then((ok) => {
|
|
678
|
+
if (!ok) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
return sendAction("interrupt");
|
|
682
|
+
})
|
|
683
|
+
.catch((error) => setStatus(error.message || String(error), true));
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
elements.type.addEventListener("click", () => {
|
|
687
|
+
const value = window.prompt("Send text to tmux without Enter:", "");
|
|
688
|
+
if (value === null || value.length === 0) {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
sendTextInput(value).catch((error) => setStatus(error.message || String(error), true));
|
|
603
692
|
});
|
|
604
693
|
|
|
605
694
|
elements.esc.addEventListener("click", () => {
|
|
@@ -670,6 +759,7 @@ async function main() {
|
|
|
670
759
|
|
|
671
760
|
if (!bootstrapPayload.tmux_target) {
|
|
672
761
|
elements.interrupt.disabled = true;
|
|
762
|
+
elements.type.disabled = true;
|
|
673
763
|
elements.esc.disabled = true;
|
|
674
764
|
elements.tab.disabled = true;
|
|
675
765
|
elements.slash.disabled = true;
|
|
@@ -710,14 +800,16 @@ function renderWebAppHtml(input) {
|
|
|
710
800
|
<body>
|
|
711
801
|
<div class="app">
|
|
712
802
|
<div class="toolbar">
|
|
713
|
-
<button class="btn compact danger" data-role="interrupt" type="button">Ctrl+C</button>
|
|
714
|
-
<button class="btn compact" data-role="escape" type="button">Esc</button>
|
|
715
|
-
<button class="btn compact" data-role="tab" type="button">Tab</button>
|
|
716
803
|
<button class="btn compact" data-role="slash" type="button">/</button>
|
|
717
|
-
<button class="btn compact" data-role="delete" type="button">⌫</button>
|
|
718
804
|
<button class="btn compact" data-role="up" type="button">↑</button>
|
|
719
805
|
<button class="btn compact" data-role="down" type="button">↓</button>
|
|
720
|
-
<button class="btn compact" data-role="enter" type="button"
|
|
806
|
+
<button class="btn compact primary" data-role="enter" type="button">Enter</button>
|
|
807
|
+
<button class="btn compact" data-role="delete" type="button">⌫</button>
|
|
808
|
+
<button class="btn compact" data-role="type" type="button" title="Type text">🔤</button>
|
|
809
|
+
<button class="btn compact" data-role="tab" type="button">Tab</button>
|
|
810
|
+
<button class="btn compact" data-role="escape" type="button">Esc</button>
|
|
811
|
+
<span class="toolbar-spacer" aria-hidden="true"></span>
|
|
812
|
+
<button class="btn compact danger" data-role="interrupt" type="button">Ctrl+C</button>
|
|
721
813
|
</div>
|
|
722
814
|
<pre class="terminal" data-role="terminal">Waiting for tmux buffer…</pre>
|
|
723
815
|
<div class="statusbar">
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sendAllowedTmuxAction = exports.isTmuxUnavailableError = exports.getTmuxWindowHeight = exports.captureVisibleTmuxPane = exports.captureTmuxPaneRange = void 0;
|
|
3
|
+
exports.sendTmuxLiteralText = exports.sendAllowedTmuxAction = exports.isTmuxUnavailableError = exports.getTmuxWindowHeight = exports.captureVisibleTmuxPane = exports.captureTmuxPaneRange = void 0;
|
|
4
4
|
var client_1 = require("../../shared/integrations/tmux/client");
|
|
5
5
|
Object.defineProperty(exports, "captureTmuxPaneRange", { enumerable: true, get: function () { return client_1.captureTmuxPaneRange; } });
|
|
6
6
|
Object.defineProperty(exports, "captureVisibleTmuxPane", { enumerable: true, get: function () { return client_1.captureVisibleTmuxPane; } });
|
|
7
7
|
Object.defineProperty(exports, "getTmuxWindowHeight", { enumerable: true, get: function () { return client_1.getTmuxWindowHeight; } });
|
|
8
8
|
Object.defineProperty(exports, "isTmuxUnavailableError", { enumerable: true, get: function () { return client_1.isTmuxUnavailableError; } });
|
|
9
9
|
Object.defineProperty(exports, "sendAllowedTmuxAction", { enumerable: true, get: function () { return client_1.sendAllowedTmuxAction; } });
|
|
10
|
+
Object.defineProperty(exports, "sendTmuxLiteralText", { enumerable: true, get: function () { return client_1.sendTmuxLiteralText; } });
|
|
@@ -173,6 +173,7 @@ class GatewayHttpService {
|
|
|
173
173
|
requestType: "action",
|
|
174
174
|
payload: {
|
|
175
175
|
action: input.action,
|
|
176
|
+
...(input.action === "text" ? { text: input.text ?? "" } : {}),
|
|
176
177
|
},
|
|
177
178
|
}, { meta: { internal_call: true } });
|
|
178
179
|
const response = unwrapLiveRelayResult(rawResponse);
|
|
@@ -14,6 +14,7 @@ exports.getTmuxWindowHeight = getTmuxWindowHeight;
|
|
|
14
14
|
exports.captureTmuxPaneRange = captureTmuxPaneRange;
|
|
15
15
|
exports.captureVisibleTmuxPane = captureVisibleTmuxPane;
|
|
16
16
|
exports.sendAllowedTmuxAction = sendAllowedTmuxAction;
|
|
17
|
+
exports.sendTmuxLiteralText = sendTmuxLiteralText;
|
|
17
18
|
exports.sendTmuxLiteralLine = sendTmuxLiteralLine;
|
|
18
19
|
const node_child_process_1 = require("node:child_process");
|
|
19
20
|
const promises_1 = require("node:fs/promises");
|
|
@@ -258,8 +259,8 @@ async function sendAllowedTmuxAction(config, target, action) {
|
|
|
258
259
|
: "Enter";
|
|
259
260
|
await execFileAsync("tmux", buildTmuxArgs(config, ["send-keys", "-t", target, key]));
|
|
260
261
|
}
|
|
261
|
-
async function
|
|
262
|
-
const normalized = text.replace(/\r?\n/g, " ")
|
|
262
|
+
async function sendTmuxLiteralText(config, target, text) {
|
|
263
|
+
const normalized = text.replace(/\r?\n/g, " ");
|
|
263
264
|
const bufferName = `telegram-mcp-${Date.now().toString(36)}`;
|
|
264
265
|
if (normalized.length > 0) {
|
|
265
266
|
try {
|
|
@@ -276,6 +277,11 @@ async function sendTmuxLiteralLine(config, target, text) {
|
|
|
276
277
|
finally {
|
|
277
278
|
await execFileAsync("tmux", buildTmuxArgs(config, ["delete-buffer", "-b", bufferName])).catch(() => undefined);
|
|
278
279
|
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async function sendTmuxLiteralLine(config, target, text) {
|
|
283
|
+
await sendTmuxLiteralText(config, target, text);
|
|
284
|
+
if (text.length > 0) {
|
|
279
285
|
await delay(ENTER_AFTER_PASTE_DELAY_MS);
|
|
280
286
|
}
|
|
281
287
|
await execFileAsync("tmux", buildTmuxArgs(config, ["send-keys", "-t", target, SUBMIT_LINE_KEY]));
|