@deadragdoll/tellymcp 0.0.2 → 0.0.4

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/README-ru.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # TellyMCP
2
2
 
3
- [English](README.md) | [Русский](README-ru.md) | [Standalone Guide](STANDALONE.md) | [Standalone RU](STANDALONE-ru.md)
3
+ [English](README.md) | [Русский](README-ru.md) | [Standalone Guide](STANDALONE.md) | [Standalone RU](STANDALONE-ru.md) | [Release Notes](VERSION.md)
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/%40deadragdoll%2Ftellymcp)](https://www.npmjs.com/package/@deadragdoll/tellymcp)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/%40deadragdoll%2Ftellymcp)](https://www.npmjs.com/package/@deadragdoll/tellymcp)
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # TellyMCP
2
2
 
3
- [English](README.md) | [Русский](README-ru.md) | [Standalone Guide](STANDALONE.md) | [Standalone RU](STANDALONE-ru.md)
3
+ [English](README.md) | [Русский](README-ru.md) | [Standalone Guide](STANDALONE.md) | [Standalone RU](STANDALONE-ru.md) | [Release Notes](VERSION.md)
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/%40deadragdoll%2Ftellymcp)](https://www.npmjs.com/package/@deadragdoll/tellymcp)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/%40deadragdoll%2Ftellymcp)](https://www.npmjs.com/package/@deadragdoll/tellymcp)
@@ -60,6 +60,7 @@ Current tools:
60
60
  - a Telegram bot token from BotFather
61
61
  - for `gateway` / `both`: Postgres
62
62
  - optional for durable fanout on gateway: RabbitMQ
63
+ - for `browser_*` tools: Playwright Chromium browser binaries
63
64
 
64
65
  ## tmux is strongly recommended
65
66
 
@@ -145,6 +146,12 @@ Typical local MCP endpoint in `client` mode:
145
146
 
146
147
  - `http://127.0.0.1:8787/mcp`
147
148
 
149
+ If you plan to use `browser_*` tools, install Chromium once:
150
+
151
+ ```bash
152
+ tellymcp browser install
153
+ ```
154
+
148
155
  Detailed step-by-step guide:
149
156
 
150
157
  - [STANDALONE.md](STANDALONE.md)
@@ -519,6 +526,8 @@ Recommended local dev settings:
519
526
  - `BROWSER_ADDRESS=http://localhost:5173`
520
527
  - start your SPA dev server on `0.0.0.0:5173`
521
528
  - open it through `browser_open`
529
+ - install browser binaries once with `npx playwright install chromium`
530
+ - install browser binaries once with `tellymcp browser install`
522
531
 
523
532
  Recommended Docker settings:
524
533
 
package/STANDALONE-ru.md CHANGED
@@ -20,6 +20,7 @@
20
20
  2. `tmux`
21
21
  3. Redis
22
22
  4. Telegram bot token из BotFather
23
+ 5. опционально для `browser_*` tools: Playwright Chromium browser binaries
23
24
 
24
25
  Зачем это нужно:
25
26
 
@@ -46,6 +47,12 @@ redis-cli ping
46
47
  - `tmux -V` показывает версию
47
48
  - `redis-cli ping` возвращает `PONG`
48
49
 
50
+ Если планируешь использовать browser automation tools, один раз выполни ещё:
51
+
52
+ ```bash
53
+ tellymcp browser install
54
+ ```
55
+
49
56
  ## Шаг 2. Создать Telegram-бота
50
57
 
51
58
  В Telegram:
@@ -156,6 +163,7 @@ tellymcp doctor --env .env
156
163
  - `.env`
157
164
  - Redis
158
165
  - локальный MCP bind
166
+ - Playwright Chromium, если browser tools включены
159
167
 
160
168
  ## Шаг 8. Запустить сервис
161
169
 
package/STANDALONE.md CHANGED
@@ -20,6 +20,7 @@ Before installation, make sure you have:
20
20
  2. `tmux`
21
21
  3. Redis
22
22
  4. a Telegram bot token from BotFather
23
+ 5. optional for `browser_*` tools: Playwright Chromium browser binaries
23
24
 
24
25
  Why these matter:
25
26
 
@@ -46,6 +47,12 @@ Expected:
46
47
  - `tmux -V` prints a version
47
48
  - `redis-cli ping` returns `PONG`
48
49
 
50
+ If you plan to use browser automation tools later, also run:
51
+
52
+ ```bash
53
+ tellymcp browser install
54
+ ```
55
+
49
56
  ## Step 2. Create a Telegram bot
50
57
 
51
58
  In Telegram:
@@ -156,6 +163,7 @@ In standalone `client` mode it should verify:
156
163
  - `.env`
157
164
  - Redis
158
165
  - local MCP bind
166
+ - Playwright Chromium when browser tools are enabled
159
167
 
160
168
  ## Step 8. Run the service
161
169
 
package/TOOLS.md CHANGED
@@ -30,6 +30,12 @@ Browser tools:
30
30
  - `browser_screenshot`
31
31
  - `browser_close`
32
32
 
33
+ Browser runtime rule:
34
+
35
+ - Browser tools use Playwright Chromium.
36
+ - If browser tools fail because the Playwright browser runtime is missing, install it with `tellymcp browser install`.
37
+ - Do not stop at the installation error itself. Install the browser runtime first, then retry the browser tool.
38
+
33
39
  Collaboration tools:
34
40
 
35
41
  - `send_partner_note`
@@ -702,6 +708,9 @@ Input:
702
708
 
703
709
  Notes:
704
710
 
711
+ - Browser tools require Playwright Chromium browser binaries.
712
+ - If the runtime is missing, run `tellymcp browser install`, then retry `browser_open`.
713
+
705
714
  - the server always uses `TELEGRAM_INBOX_BATCH_SIZE`
706
715
  - the agent should not try to choose its own batch size
707
716
 
package/VERSION.md ADDED
@@ -0,0 +1,93 @@
1
+ # TellyMCP Release Notes
2
+
3
+ Public, user-facing release notes for published versions of `@deadragdoll/tellymcp`.
4
+
5
+ For detailed engineering history, refactors, and internal development notes, see [CHANGELOG.md](CHANGELOG.md).
6
+
7
+ ## 0.0.3
8
+
9
+ ### Added
10
+ - Standalone CLI workflow:
11
+ - `tellymcp init <client|gateway|both>`
12
+ - `tellymcp run`
13
+ - `tellymcp doctor`
14
+ - `tellymcp mcp --help`
15
+ - Standalone and public installation guides:
16
+ - [STANDALONE.md](STANDALONE.md)
17
+ - [STANDALONE-ru.md](STANDALONE-ru.md)
18
+ - Browser runtime helper:
19
+ - `tellymcp browser install`
20
+ - Public README set for GitHub and npm:
21
+ - [README.md](README.md)
22
+ - [README-ru.md](README-ru.md)
23
+ - Human-readable release notes in this file.
24
+
25
+ ### Changed
26
+ - Default installation path is now npm-first:
27
+ - `npm install -g @deadragdoll/tellymcp`
28
+ - Standalone client mode is documented first, before gateway/both deployment.
29
+ - `tmux` is now documented as a strongly recommended prerequisite for the full experience:
30
+ - Live View
31
+ - nudges
32
+ - direct terminal control from Telegram
33
+ - Environment examples were split into dedicated client and gateway variants.
34
+ - Package build/publish flow now validates itself before packing/publishing.
35
+
36
+ ### Collaboration
37
+ - Project collaboration works across local and remote sessions.
38
+ - `Collab` now includes:
39
+ - `Broadcast`
40
+ - `History`
41
+ - `Delete`
42
+ - `Ask` and `Share` semantics were clarified:
43
+ - `Ask` tells the selected session to do the work and reply back
44
+ - `Share` tells the current session to send something to the selected session
45
+
46
+ ### Live View
47
+ - Telegram Mini App Live View supports:
48
+ - fullscreen/expand launch policy
49
+ - bottom toolbar layout
50
+ - `Esc`
51
+ - `Tab`
52
+ - `Ctrl+C`
53
+ - `Backspace`
54
+ - `Up`
55
+ - `Down`
56
+ - `Enter`
57
+ - Live approval flow was added for remote project sessions.
58
+
59
+ ### Browser
60
+ - Browser tools use Playwright Chromium.
61
+ - Headless mode is the recommended default for remote and SSH-based environments.
62
+ - `doctor` now helps detect missing browser runtime and connectivity issues.
63
+
64
+ ### Compatibility
65
+ - Gateway and clients now compare:
66
+ - package version
67
+ - protocol version
68
+ - capabilities
69
+ - `TOOLS.md` sync now detects outdated or missing local instructions and asks the session to refresh them.
70
+
71
+ ### Removed
72
+ - Legacy Go/HTTP tmux proxy path was removed.
73
+ - Direct product path is now local `tmux` only.
74
+
75
+ ## Next entry template
76
+
77
+ Copy this block for the next published version:
78
+
79
+ ```md
80
+ ## x.y.z
81
+
82
+ ### Added
83
+ - ...
84
+
85
+ ### Changed
86
+ - ...
87
+
88
+ ### Fixed
89
+ - ...
90
+
91
+ ### Removed
92
+ - ...
93
+ ```
package/dist/cli.js CHANGED
@@ -11,8 +11,10 @@ const node_net_1 = __importDefault(require("node:net"));
11
11
  const dotenv_1 = require("dotenv");
12
12
  const picocolors_1 = __importDefault(require("picocolors"));
13
13
  const ws_1 = __importDefault(require("ws"));
14
+ const versionHandshake_1 = require("./services/features/telegram-mcp/src/shared/lib/version/versionHandshake");
14
15
  const distDir = __dirname;
15
16
  const packageRoot = node_path_1.default.resolve(distDir, "..");
17
+ const cliPackageVersion = (0, versionHandshake_1.getTellyMcpPackageVersion)(__dirname);
16
18
  function getTmuxStatus() {
17
19
  const result = (0, node_child_process_1.spawnSync)("tmux", ["-V"], {
18
20
  encoding: "utf8",
@@ -27,7 +29,7 @@ function getTmuxStatus() {
27
29
  return { found: false };
28
30
  }
29
31
  function printBanner(title, subtitle) {
30
- process.stdout.write(`${picocolors_1.default.bold(picocolors_1.default.cyan("TellyMCP"))} ${picocolors_1.default.dim(title)}\n`);
32
+ process.stdout.write(`${picocolors_1.default.bold(picocolors_1.default.cyan("TellyMCP"))} ${picocolors_1.default.bold(picocolors_1.default.white(`v${cliPackageVersion}`))} ${picocolors_1.default.dim(title)}\n`);
31
33
  if (subtitle) {
32
34
  process.stdout.write(`${picocolors_1.default.dim(subtitle)}\n`);
33
35
  }
@@ -50,6 +52,34 @@ function getTmuxInstallHints() {
50
52
  "Arch: sudo pacman -S tmux",
51
53
  ];
52
54
  }
55
+ async function getPlaywrightBrowserStatus(browserEnabled) {
56
+ if (!browserEnabled) {
57
+ return { enabled: false };
58
+ }
59
+ try {
60
+ const playwright = await import("playwright");
61
+ const executablePath = playwright.chromium.executablePath();
62
+ if (executablePath && (0, node_fs_1.existsSync)(executablePath)) {
63
+ return {
64
+ enabled: true,
65
+ installed: true,
66
+ executablePath,
67
+ };
68
+ }
69
+ return {
70
+ enabled: true,
71
+ installed: false,
72
+ message: "Chromium browser binaries are missing.",
73
+ };
74
+ }
75
+ catch (error) {
76
+ return {
77
+ enabled: true,
78
+ installed: false,
79
+ message: error instanceof Error ? error.message : String(error),
80
+ };
81
+ }
82
+ }
53
83
  function printHelp() {
54
84
  const tmux = getTmuxStatus();
55
85
  printBanner("CLI", "Telegram Human-in-the-Loop MCP server");
@@ -58,6 +88,7 @@ function printHelp() {
58
88
  " tellymcp run [--env <file>]",
59
89
  " tellymcp run --env=<file>",
60
90
  " tellymcp doctor [--env <file>]",
91
+ " tellymcp browser install",
61
92
  " tellymcp mcp [--url <url>] [--bearer <token>] [--format claude|legacy]",
62
93
  " tellymcp help",
63
94
  ]);
@@ -67,6 +98,7 @@ function printHelp() {
67
98
  " tellymcp run",
68
99
  " tellymcp run --env .env.client",
69
100
  " tellymcp doctor --env .env.client",
101
+ " tellymcp browser install",
70
102
  " tellymcp mcp --help",
71
103
  ]);
72
104
  if (tmux.found) {
@@ -146,6 +178,17 @@ function printMcpHelp() {
146
178
  " tellymcp mcp --url https://builder.undoo.ru/api/mcp --format legacy",
147
179
  ]);
148
180
  }
181
+ function printBrowserHelp() {
182
+ printBanner("browser helper", "Manage Playwright browser binaries used by browser_* tools");
183
+ printSection("Usage", [
184
+ " tellymcp browser install",
185
+ ]);
186
+ printSection("What this command does", [
187
+ " Installs the bundled Playwright Chromium browser.",
188
+ " Uses the Playwright dependency shipped with TellyMCP.",
189
+ " Avoids generic npx warnings about missing local project dependencies.",
190
+ ]);
191
+ }
149
192
  function fail(message) {
150
193
  process.stderr.write(`${message}\n`);
151
194
  process.exit(1);
@@ -439,6 +482,7 @@ async function runDoctor(args) {
439
482
  const gatewayWsUrl = parsed.GATEWAY_WS_URL?.trim();
440
483
  const publicWebappUrl = parsed.WEBAPP_PUBLIC_URL?.trim();
441
484
  const mcpBearerToken = parsed.MCP_HTTP_BEARER_TOKEN?.trim();
485
+ const browserEnabled = (parsed.BROWSER_ENABLED || "true").trim().toLowerCase() !== "false";
442
486
  const externalHealthUrl = deriveGatewayHealthUrlFromPublicUrl(publicGatewayUrl || "") ??
443
487
  deriveGatewayHealthUrlFromPublicUrl(publicWebappUrl || "");
444
488
  printSection("env", [
@@ -455,6 +499,19 @@ async function runDoctor(args) {
455
499
  ]);
456
500
  const checks = [];
457
501
  const capabilities = [];
502
+ const playwrightStatus = await getPlaywrightBrowserStatus(browserEnabled);
503
+ if (!playwrightStatus.enabled) {
504
+ checks.push(`${picocolors_1.default.dim(" SKIP")} playwright: browser tools are disabled`);
505
+ capabilities.push(`${picocolors_1.default.dim(" SKIP")} browser tools: disabled`);
506
+ }
507
+ else if (playwrightStatus.installed) {
508
+ checks.push(`${picocolors_1.default.green(" OK")} playwright chromium: ${playwrightStatus.executablePath}`);
509
+ capabilities.push(`${picocolors_1.default.green(" OK")} browser tools: available`);
510
+ }
511
+ else {
512
+ checks.push(`${picocolors_1.default.red(" ERROR")} playwright chromium: ${playwrightStatus.message}`);
513
+ capabilities.push(`${picocolors_1.default.red(" ERROR")} browser tools: browsers are not installed`);
514
+ }
458
515
  const redisHost = (parsed.REDIS_HOST || "127.0.0.1").trim();
459
516
  const redisPort = Number(parsed.REDIS_PORT || 6379);
460
517
  const redisCheck = await checkTcpPort(redisHost, redisPort);
@@ -560,6 +617,39 @@ async function runDoctor(args) {
560
617
  else {
561
618
  printSection("notes", [`${picocolors_1.default.green(" OK")} No obvious local config issues detected.`]);
562
619
  }
620
+ if (browserEnabled && (!playwrightStatus.enabled || !playwrightStatus.installed)) {
621
+ printSection("playwright", [
622
+ `${picocolors_1.default.yellow(" ACTION")} Install browser binaries before using browser_* tools:`,
623
+ " tellymcp browser install",
624
+ ]);
625
+ }
626
+ }
627
+ function runBrowserCommand(args) {
628
+ const [subcommand] = args;
629
+ if (!subcommand || subcommand === "--help" || subcommand === "-h") {
630
+ printBrowserHelp();
631
+ return;
632
+ }
633
+ if (subcommand !== "install") {
634
+ fail("Supported browser subcommands: install");
635
+ }
636
+ const cliPath = node_path_1.default.join(packageRoot, "node_modules", "playwright", "cli.js");
637
+ if (!(0, node_fs_1.existsSync)(cliPath)) {
638
+ fail(`Missing bundled Playwright CLI: ${cliPath}`);
639
+ }
640
+ printBanner("browser install", "Installing bundled Playwright Chromium");
641
+ const child = (0, node_child_process_1.spawn)(process.execPath, [cliPath, "install", "chromium"], {
642
+ cwd: process.cwd(),
643
+ stdio: "inherit",
644
+ env: process.env,
645
+ });
646
+ child.on("exit", (code, signal) => {
647
+ if (signal) {
648
+ process.kill(process.pid, signal);
649
+ return;
650
+ }
651
+ process.exit(code ?? 0);
652
+ });
563
653
  }
564
654
  function runRuntime(args) {
565
655
  const envPath = resolveRunEnvPath(args);
@@ -578,6 +668,7 @@ function runRuntime(args) {
578
668
  if (!(0, node_fs_1.existsSync)(servicesPath)) {
579
669
  fail(`Missing compiled services: ${servicesPath}`);
580
670
  }
671
+ printBanner("run", "Starting packaged runtime");
581
672
  const tmux = getTmuxStatus();
582
673
  if (tmux.found) {
583
674
  process.stdout.write(`${picocolors_1.default.green("tmux detected:")} ${tmux.version}\n`);
@@ -612,7 +703,7 @@ function runRuntime(args) {
612
703
  }
613
704
  async function main(argv) {
614
705
  const [rawCommand, firstArg, secondArg] = argv;
615
- const command = rawCommand === "init" || rawCommand === "run" || rawCommand === "help" || rawCommand === "mcp" || rawCommand === "doctor"
706
+ const command = rawCommand === "init" || rawCommand === "run" || rawCommand === "help" || rawCommand === "mcp" || rawCommand === "doctor" || rawCommand === "browser"
616
707
  ? rawCommand
617
708
  : "help";
618
709
  if (command === "help" || !rawCommand || rawCommand === "--help" || rawCommand === "-h") {
@@ -631,6 +722,10 @@ async function main(argv) {
631
722
  await runDoctor(argv.slice(1));
632
723
  return;
633
724
  }
725
+ if (command === "browser") {
726
+ runBrowserCommand(argv.slice(1));
727
+ return;
728
+ }
634
729
  runRuntime(argv.slice(1));
635
730
  }
636
731
  void main(process.argv.slice(2));
@@ -554,7 +554,7 @@ const TelegramMcpGatewaySocketService = {
554
554
  const action = typeof request.payload?.action === "string"
555
555
  ? request.payload.action
556
556
  : "";
557
- if (!["up", "down", "enter", "slash", "delete", "tab", "escape"].includes(action)) {
557
+ if (!["up", "down", "enter", "slash", "delete", "tab", "escape", "interrupt"].includes(action)) {
558
558
  throw new Error("Unsupported action");
559
559
  }
560
560
  const sessionId = request.local_session_id.trim();
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TELEGRAM_MCP_RUNTIME_SERVICE_NAME = void 0;
4
4
  const runtime_1 = require("./src/app/bootstrap/runtime");
5
+ const versionHandshake_1 = require("./src/shared/lib/version/versionHandshake");
5
6
  exports.TELEGRAM_MCP_RUNTIME_SERVICE_NAME = "telegramMcp.runtime";
6
7
  const TelegramMcpRuntimeService = {
7
8
  name: exports.TELEGRAM_MCP_RUNTIME_SERVICE_NAME,
@@ -17,11 +18,17 @@ const TelegramMcpRuntimeService = {
17
18
  },
18
19
  },
19
20
  async started() {
20
- this.logger.info("Starting telegram_mcp runtime service");
21
+ this.logger.info("Starting telegram_mcp runtime service", {
22
+ packageVersion: (0, versionHandshake_1.getTellyMcpPackageVersion)(__dirname),
23
+ protocolVersion: versionHandshake_1.TELLYMCP_PROTOCOL_VERSION,
24
+ });
21
25
  this.runtime = await (0, runtime_1.createAppRuntime)({
22
26
  callBroker: (actionName, params, options) => this.broker.call(actionName, params, options),
23
27
  });
24
- this.logger.info("telegram_mcp runtime service is ready");
28
+ this.logger.info("telegram_mcp runtime service is ready", {
29
+ packageVersion: (0, versionHandshake_1.getTellyMcpPackageVersion)(__dirname),
30
+ protocolVersion: versionHandshake_1.TELLYMCP_PROTOCOL_VERSION,
31
+ });
25
32
  },
26
33
  async stopped() {
27
34
  if (!this.runtime) {
@@ -77,6 +77,8 @@ async function createAppRuntime(input) {
77
77
  logger.info("Telegram transport ready");
78
78
  await telegramTransport.recoverPendingInboxNudges();
79
79
  logger.info("Startup inbox nudge recovery completed");
80
+ await telegramTransport.sendStartupNotifications();
81
+ logger.info("Startup Telegram notifications completed");
80
82
  const gatewayHttpService = new gatewayHttpService_1.GatewayHttpService(config, input.callBroker);
81
83
  return {
82
84
  config,
@@ -428,7 +428,7 @@ function createMcpHttpHandler(runtime, input) {
428
428
  typeof Reflect.get(body, "action") === "string"
429
429
  ? String(Reflect.get(body, "action"))
430
430
  : "";
431
- if (!["up", "down", "enter", "slash", "delete", "tab", "escape"].includes(action)) {
431
+ if (!["up", "down", "enter", "slash", "delete", "tab", "escape", "interrupt"].includes(action)) {
432
432
  writeText(res, 400, "Unsupported action");
433
433
  return;
434
434
  }
@@ -168,6 +168,7 @@ const elements = {
168
168
  session: document.querySelector("[data-role=session]"),
169
169
  status: document.querySelector("[data-role=status]"),
170
170
  updated: document.querySelector("[data-role=updated]"),
171
+ interrupt: document.querySelector("[data-role=interrupt]"),
171
172
  esc: document.querySelector("[data-role=escape]"),
172
173
  tab: document.querySelector("[data-role=tab]"),
173
174
  slash: document.querySelector("[data-role=slash]"),
@@ -597,6 +598,10 @@ function startPolling() {
597
598
  }
598
599
 
599
600
  function bindUi() {
601
+ elements.interrupt.addEventListener("click", () => {
602
+ sendAction("interrupt").catch((error) => setStatus(error.message || String(error), true));
603
+ });
604
+
600
605
  elements.esc.addEventListener("click", () => {
601
606
  sendAction("escape").catch((error) => setStatus(error.message || String(error), true));
602
607
  });
@@ -664,6 +669,7 @@ async function main() {
664
669
  bootstrapPayload.session_label || bootstrapPayload.session_id;
665
670
 
666
671
  if (!bootstrapPayload.tmux_target) {
672
+ elements.interrupt.disabled = true;
667
673
  elements.esc.disabled = true;
668
674
  elements.tab.disabled = true;
669
675
  elements.slash.disabled = true;
@@ -704,6 +710,7 @@ function renderWebAppHtml(input) {
704
710
  <body>
705
711
  <div class="app">
706
712
  <div class="toolbar">
713
+ <button class="btn compact danger" data-role="interrupt" type="button">Ctrl+C</button>
707
714
  <button class="btn compact" data-role="escape" type="button">Esc</button>
708
715
  <button class="btn compact" data-role="tab" type="button">Tab</button>
709
716
  <button class="btn compact" data-role="slash" type="button">/</button>
@@ -17,6 +17,7 @@ const collabSemantics_1 = require("./collabSemantics");
17
17
  const collabUi_1 = require("./collabUi");
18
18
  const proxyFetch_1 = require("./proxyFetch");
19
19
  const client_1 = require("../tmux/client");
20
+ const versionHandshake_1 = require("../../lib/version/versionHandshake");
20
21
  const LOCAL_INDEX_FILE_NAME = "LOCAL_INDEX.md";
21
22
  function trimTrailingSlashes(value) {
22
23
  return value.replace(/\/+$/u, "");
@@ -25,6 +26,14 @@ function normalizeBasePath(value) {
25
26
  const trimmed = trimTrailingSlashes(value.trim());
26
27
  return trimmed.startsWith("/") ? trimmed || "/" : `/${trimmed || ""}`;
27
28
  }
29
+ function joinHttpPath(prefix, suffix) {
30
+ const normalizedPrefix = prefix ? normalizeBasePath(prefix) : "";
31
+ const normalizedSuffix = normalizeBasePath(suffix);
32
+ if (!normalizedPrefix || normalizedPrefix === "/") {
33
+ return normalizedSuffix;
34
+ }
35
+ return `${normalizedPrefix}${normalizedSuffix}`.replace(/\/{2,}/gu, "/");
36
+ }
28
37
  function resolveWebAppPublicBaseUrl(config) {
29
38
  if (!config.webapp.publicUrl) {
30
39
  return null;
@@ -755,6 +764,92 @@ class TelegramTransport {
755
764
  recoveredSessions: recoveredCount,
756
765
  });
757
766
  }
767
+ async sendStartupNotifications() {
768
+ const packageVersion = (0, versionHandshake_1.getTellyMcpPackageVersion)(__dirname);
769
+ const sessions = await this.sessionStore.listSessions();
770
+ const groupedRecipients = new Map();
771
+ for (const session of sessions) {
772
+ const binding = await this.bindingStore.getBinding(session.sessionId);
773
+ if (!binding) {
774
+ continue;
775
+ }
776
+ const key = `${binding.telegramChatId}:${binding.telegramUserId}`;
777
+ const current = groupedRecipients.get(key);
778
+ if (current) {
779
+ current.sessionIds.push(session.sessionId);
780
+ current.sessionLabels.push(session.label ?? session.sessionId);
781
+ continue;
782
+ }
783
+ groupedRecipients.set(key, {
784
+ binding: {
785
+ telegramChatId: binding.telegramChatId,
786
+ telegramUserId: binding.telegramUserId,
787
+ },
788
+ sessionIds: [session.sessionId],
789
+ sessionLabels: [session.label ?? session.sessionId],
790
+ });
791
+ }
792
+ if (groupedRecipients.size === 0) {
793
+ this.logger.info("Skipping startup notifications because no Telegram sessions are paired");
794
+ return;
795
+ }
796
+ const runtimePort = this.config.distributed.mode === "gateway" || this.config.distributed.mode === "both"
797
+ ? Number(process.env.PORT || this.config.mcp.httpPort)
798
+ : this.config.mcp.httpPort;
799
+ const rootPrefix = this.config.distributed.mode === "gateway" || this.config.distributed.mode === "both"
800
+ ? normalizeBasePath(process.env.ROOT_PREFIX || "/api")
801
+ : "";
802
+ const localMcpPath = this.config.distributed.mode === "gateway" || this.config.distributed.mode === "both"
803
+ ? joinHttpPath(rootPrefix, this.config.mcp.httpPath)
804
+ : this.config.mcp.httpPath;
805
+ const localWebappPath = this.config.distributed.mode === "gateway" || this.config.distributed.mode === "both"
806
+ ? joinHttpPath(rootPrefix, this.config.webapp.basePath)
807
+ : this.config.webapp.basePath;
808
+ const localMcpUrl = `http://${this.config.mcp.httpHost}:${runtimePort}${localMcpPath}`;
809
+ const localWebappUrl = `http://${this.config.mcp.httpHost}:${runtimePort}${localWebappPath}`;
810
+ for (const recipientGroup of groupedRecipients.values()) {
811
+ const primarySessionId = recipientGroup.sessionIds[0];
812
+ if (!primarySessionId) {
813
+ continue;
814
+ }
815
+ const uniqueSessionLabels = Array.from(new Set(recipientGroup.sessionLabels)).sort();
816
+ const startupMessage = [
817
+ "✅ TellyMCP запущен.",
818
+ `Версия: ${packageVersion}`,
819
+ `Протокол: ${versionHandshake_1.TELLYMCP_PROTOCOL_VERSION}`,
820
+ `Режим: ${this.config.distributed.mode}`,
821
+ ...(this.config.telegram.botUsername
822
+ ? [`Бот: @${this.config.telegram.botUsername.replace(/^@/u, "")}`]
823
+ : []),
824
+ `Сессии: ${uniqueSessionLabels.join(", ")}`,
825
+ `MCP: ${localMcpUrl}`,
826
+ ...(this.config.webapp.enabled ? [`WebApp: ${localWebappUrl}`] : []),
827
+ ...(this.config.distributed.gatewayPublicUrl
828
+ ? [`Gateway: ${this.config.distributed.gatewayPublicUrl}`]
829
+ : []),
830
+ ...(this.config.distributed.gatewayWsUrl
831
+ ? [`Gateway WS: ${this.config.distributed.gatewayWsUrl}`]
832
+ : []),
833
+ `Browser: ${this.config.browser.enabled ? (this.config.browser.headless ? "enabled, headless" : "enabled, headed") : "disabled"}`,
834
+ "Напиши /menu, чтобы открыть меню сессий.",
835
+ ].join("\n");
836
+ try {
837
+ await this.sendNotification({
838
+ sessionId: primarySessionId,
839
+ sessionLabel: "TellyMCP",
840
+ recipient: recipientGroup.binding,
841
+ message: startupMessage,
842
+ });
843
+ }
844
+ catch (error) {
845
+ this.logger.warn("Failed to deliver Telegram startup notification", {
846
+ telegramChatId: recipientGroup.binding.telegramChatId,
847
+ telegramUserId: recipientGroup.binding.telegramUserId,
848
+ error: error instanceof Error ? error.message : String(error),
849
+ });
850
+ }
851
+ }
852
+ }
758
853
  async sendRequest(input) {
759
854
  const text = (0, messageFormat_1.formatTelegramMessage)(input, {
760
855
  maxQuestionChars: this.config.telegram.maxQuestionChars,
@@ -253,7 +253,9 @@ async function sendAllowedTmuxAction(config, target, action) {
253
253
  ? "Tab"
254
254
  : action === "escape"
255
255
  ? "Escape"
256
- : "Enter";
256
+ : action === "interrupt"
257
+ ? "C-c"
258
+ : "Enter";
257
259
  await execFileAsync("tmux", buildTmuxArgs(config, ["send-keys", "-t", target, key]));
258
260
  }
259
261
  async function sendTmuxLiteralLine(config, target, text) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deadragdoll/tellymcp",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "TellyMCP - Telegram Human-in-the-Loop MCP Server",
5
5
  "main": "dist/services/features/telegram-mcp/runtime.service.js",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  "README-ru.md",
15
15
  "STANDALONE.md",
16
16
  "STANDALONE-ru.md",
17
+ "VERSION.md",
17
18
  "CHANGELOG.md",
18
19
  "TOOLS.md",
19
20
  ".env.example.client",
@@ -34,6 +35,8 @@
34
35
  },
35
36
  "scripts": {
36
37
  "postinstall": "node ./scripts/postinstall.js",
38
+ "prepack": "yarn build",
39
+ "prepublishOnly": "yarn build && yarn lint && yarn test",
37
40
  "mcp:local": "codex mcp add telegramHuman --url http://127.0.0.1:8787/mcp",
38
41
  "dev:gw": "TS_NODE_TRANSPILE_ONLY=1 TS_NODE_EXPERIMENTAL_RESOLVER=1 NODE_OPTIONS=--openssl-legacy-provider ts-node ./node_modules/moleculer/bin/moleculer-runner.js src/services --config src/moleculer.config.ts --envfile ./.env-dev --hot --mask \"**/*.service.ts\"",
39
42
  "dev:builder": "TS_NODE_TRANSPILE_ONLY=1 TS_NODE_EXPERIMENTAL_RESOLVER=1 NODE_OPTIONS=--openssl-legacy-provider ts-node ./node_modules/moleculer/bin/moleculer-runner.js src/services --config src/moleculer.config.ts --envfile ./.env-builder --hot --mask \"**/*.service.ts\"",
@@ -53,6 +53,11 @@ if (tmux.found) {
53
53
  }
54
54
  }
55
55
 
56
+ line();
57
+ line(`${pc.yellow("INFO")} Browser tools need Playwright browser binaries.`);
58
+ line("If you plan to use browser_* tools, run:");
59
+ line(` ${pc.bold("tellymcp browser install")}`);
60
+
56
61
  line();
57
62
  line(`Check your local setup: ${pc.bold("tellymcp doctor")}`);
58
63
  line(`General help: ${pc.bold("tellymcp help")}`);