@dikacode/telegram-mcp 1.0.0 → 1.0.1

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.md CHANGED
@@ -11,7 +11,6 @@
11
11
 
12
12
  <p align="center">
13
13
  <a href="https://www.npmjs.com/package/@dikacode/telegram-mcp"><img alt="npm" src="https://img.shields.io/npm/v/@dikacode/telegram-mcp?style=flat-square&color=5eead4&label=npm"></a>
14
- <a href="https://www.npmjs.com/package/@dikacode/telegram-mcp"><img alt="downloads" src="https://img.shields.io/npm/dm/@dikacode/telegram-mcp?style=flat-square&color=38bdf8"></a>
15
14
  <a href="https://github.com/dikaglorya/telegram-mcp/actions/workflows/ci.yml"><img alt="ci" src="https://github.com/dikaglorya/telegram-mcp/actions/workflows/ci.yml/badge.svg"></a>
16
15
  <img alt="node" src="https://img.shields.io/badge/node-%E2%89%A5%2020-0ea5e9?style=flat-square">
17
16
  <img alt="tools" src="https://img.shields.io/badge/tools-69-a78bfa?style=flat-square">
@@ -78,6 +77,7 @@ The server speaks MCP over stdio, so the last command is only a manual check. Th
78
77
  - [Architecture](#architecture)
79
78
  - [Requirements](#requirements)
80
79
  - [Install](#install)
80
+ - [Updating](#updating)
81
81
  - [Credentials](#credentials)
82
82
  - [First login — from chat](#first-login--from-chat)
83
83
  - [Connect your agent](#connect-your-agent)
@@ -150,7 +150,7 @@ telegram-mcp
150
150
  }
151
151
  ```
152
152
 
153
- Pin a version if you want reproducible installs: `npx -y @dikacode/telegram-mcp@1.0.0`.
153
+ Pin a version if you want reproducible installs: `npx -y @dikacode/telegram-mcp@1.0.1`.
154
154
 
155
155
  ### 2. Clone and link
156
156
 
@@ -170,7 +170,7 @@ npm link # optional: makes `telegram-mcp` available on PATH
170
170
  Download the tarball attached to the latest release and install it globally:
171
171
 
172
172
  ```bash
173
- npm install -g https://github.com/dikaglorya/telegram-mcp/releases/download/v1.0.0/dikacode-telegram-mcp-1.0.0.tgz
173
+ npm install -g https://github.com/dikaglorya/telegram-mcp/releases/download/v1.0.1/dikacode-telegram-mcp-1.0.1.tgz
174
174
  telegram-mcp
175
175
  ```
176
176
 
@@ -186,6 +186,38 @@ npx -y github:dikaglorya/telegram-mcp
186
186
 
187
187
  npm fetches the repository, installs dependencies, runs the `prepare` build, and starts the launcher.
188
188
 
189
+ ## Updating
190
+
191
+ How you installed it decides whether you have to do anything.
192
+
193
+ | Installed with | Behaviour |
194
+ | --- | --- |
195
+ | `npx -y @dikacode/telegram-mcp` (unpinned) | **Automatic.** npm resolves `latest` on every start, so the next client restart picks up a new release. |
196
+ | `npx -y @dikacode/telegram-mcp@1.0.1` (pinned) | **Never.** That is the point of pinning. |
197
+ | `npm install -g @dikacode/telegram-mcp` | **Manual**, one command: `npm update -g @dikacode/telegram-mcp` |
198
+ | Release tarball | **Manual.** Reinstall from the newer tarball. |
199
+ | `npx -y github:dikaglorya/telegram-mcp` | **Not automatic.** npm caches the fetched repo; clear the cache or pin a ref (`#main`) to move forward. |
200
+
201
+ Check what you are running:
202
+
203
+ ```bash
204
+ telegram-mcp --version
205
+ npm outdated -g @dikacode/telegram-mcp # global installs only
206
+ ```
207
+
208
+ The server also tells you. When a newer release exists it prints one line to
209
+ stderr at startup, after the MCP transport is already serving:
210
+
211
+ ```
212
+ [telegram-mcp] update available: 1.0.1 -> 1.0.2. Run `npm install -g @dikacode/telegram-mcp@latest`, or set TELEGRAM_UPDATE_CHECK=0 to silence this.
213
+ ```
214
+
215
+ That check is a single request to the npm registry with an 800 ms timeout,
216
+ cached for 24 hours, and it never blocks startup or writes to stdout. If you
217
+ want zero outbound traffic that is not Telegram itself, set
218
+ `TELEGRAM_UPDATE_CHECK=0`. `telegram_doctor` shows the version it knows about
219
+ without touching the network.
220
+
189
221
  ## Credentials
190
222
 
191
223
  1. Open [my.telegram.org/apps](https://my.telegram.org/apps) and log in with your phone number.
@@ -477,6 +509,8 @@ All settings are environment variables. The server also loads a `.env` next to t
477
509
  | `TELEGRAM_EXPORT_DIR` | `~/.telegram-mcp/exports` | History exports. |
478
510
  | `TELEGRAM_AUDIT_LOG` | `~/.telegram-mcp/audit.log` | Call log. |
479
511
  | `TELEGRAM_MAX_DOWNLOAD_BYTES` | `0` (unlimited) | Hard cap for one download. A non-numeric value is refused rather than treated as unlimited. |
512
+ | `TELEGRAM_UPDATE_CHECK` | `true` | Set to `0` to stop the startup check for a newer npm release. |
513
+ | `TELEGRAM_UPDATE_CHECK_TIMEOUT_MS` | `800` | Timeout for that check, capped at 10 s. |
480
514
  | `TELEGRAM_MCP_NODE` | current Node | Launcher override, for exotic Node installs. |
481
515
 
482
516
  `~` and `~\` are both expanded. Relative paths resolve against the current working directory.
@@ -494,6 +528,8 @@ All settings are environment variables. The server also loads a `.env` next to t
494
528
  | `Cannot find module .../dist/index.js` | The package was installed without building. Run `npm run build`, or use the release tarball. |
495
529
  | Login works, then the next run asks again | `TELEGRAM_SESSION_PATH` changed between runs. Keep it stable, or leave it unset. |
496
530
  | Window/terminal closes when started by hand | Expected: the server speaks MCP on stdio. Start it through a client, or use `npm run login` for the standalone CLI. |
531
+ | `update available: …` on every start | A newer release is on npm. Run the upgrade command it prints, or set `TELEGRAM_UPDATE_CHECK=0` if you pin versions on purpose. |
532
+ | `telegram-mcp --version` prints nothing | You are on `1.0.0`, which predates the flag. Upgrade to `1.0.1` or newer. |
497
533
 
498
534
  ## Development
499
535
 
@@ -503,7 +539,7 @@ The repository ships with a unit suite and a real MCP harness that spawns the bu
503
539
  npm install
504
540
  npm run build
505
541
 
506
- npm run test:unit # 64 offline checks
542
+ npm run test:unit # 72 offline checks
507
543
  npm run test:mcp # read-only end-to-end pass
508
544
  npm run test:mcp:write # + self-cleaning writes
509
545
  npm run test:destructive # + disposable chats (opt-in, flood-limited)
@@ -556,7 +592,8 @@ The TypeScript is built with `tsc` to `dist/`. There is no bundler; the publishe
556
592
 
557
593
  ## Release
558
594
 
559
- **v1.0.0** is the first public release.
595
+ **v1.0.1** is the current release, published to npm as
596
+ [`@dikacode/telegram-mcp`](https://www.npmjs.com/package/@dikacode/telegram-mcp).
560
597
 
561
598
  - 69 MCP tools, all registered with strict input schemas.
562
599
  - In-chat login, including two-step verification.
@@ -564,6 +601,7 @@ The TypeScript is built with `tsc` to `dist/`. There is no bundler; the publishe
564
601
  - Read-back verification for state-changing tools.
565
602
  - Optional allowlist, audited calls, redacted secrets.
566
603
  - Cross-platform launcher; no hardcoded paths.
604
+ - `--version` / `--help`, and a startup notice when a newer release exists.
567
605
  - End-to-end harness with paging, policy probes and artifact cleanup.
568
606
 
569
607
  See [CHANGELOG.md](https://github.com/dikaglorya/telegram-mcp/blob/main/CHANGELOG.md) for the full release notes. Release artifacts are on the [releases page](https://github.com/dikaglorya/telegram-mcp/releases).
package/dist/config.js CHANGED
@@ -11,8 +11,7 @@ import fs from "node:fs";
11
11
  import os from "node:os";
12
12
  import path from "node:path";
13
13
  import { lockDir, lockFile } from "./perms.js";
14
- export const SERVER_NAME = "telegram-mcp";
15
- export const SERVER_VERSION = "1.0.0";
14
+ export { SERVER_NAME, SERVER_VERSION } from "./version.js";
16
15
  export class ConfigError extends Error {
17
16
  constructor(message) {
18
17
  super(message);
package/dist/index.js CHANGED
@@ -2,6 +2,9 @@
2
2
  /**
3
3
  * Entrypoint. Loads .env, builds the MCP server, serves over stdio.
4
4
  * stdout = MCP protocol only; diagnostics go to stderr.
5
+ *
6
+ * Exception: `--version` / `--help` are plain CLI invocations. They answer on
7
+ * stdout and exit before the transport is created, so nothing is corrupted.
5
8
  */
6
9
  import "./stream-guard.js";
7
10
  import "./env.js";
@@ -9,6 +12,41 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
9
12
  import { buildServer } from "./server.js";
10
13
  import { ensureDirs, getConfig, redact } from "./config.js";
11
14
  import { shutdownLogin } from "./login-tools.js";
15
+ import { PACKAGE_NAME, SERVER_NAME, SERVER_VERSION } from "./version.js";
16
+ import { warnIfOutdated } from "./update-check.js";
17
+ const HELP = `${SERVER_NAME} ${SERVER_VERSION}
18
+
19
+ An MCP server for a personal Telegram account, spoken over stdio.
20
+
21
+ Usage:
22
+ telegram-mcp serve MCP on stdio (what an MCP client runs)
23
+ telegram-mcp --version, -v print the version and exit
24
+ telegram-mcp --help, -h print this help and exit
25
+
26
+ Setup:
27
+ 1. Get api_id and api_hash from https://my.telegram.org/apps
28
+ 2. Provide them as TELEGRAM_API_ID / TELEGRAM_API_HASH, or let the agent call
29
+ telegram_setup
30
+ 3. Log in with telegram_login_start (the agent can do this for you)
31
+
32
+ Updating:
33
+ npx -y ${PACKAGE_NAME}@latest # npx, always current
34
+ npm install -g ${PACKAGE_NAME}@latest # global install
35
+
36
+ Docs: https://github.com/dikaglorya/telegram-mcp
37
+ `;
38
+ function handleCliFlags(argv) {
39
+ const flags = new Set(argv.slice(2));
40
+ if (flags.has("--version") || flags.has("-v")) {
41
+ process.stdout.write(`${SERVER_VERSION}\n`);
42
+ return true;
43
+ }
44
+ if (flags.has("--help") || flags.has("-h")) {
45
+ process.stdout.write(HELP);
46
+ return true;
47
+ }
48
+ return false;
49
+ }
12
50
  async function main() {
13
51
  // A missing .env must NOT be fatal: the user needs telegram_setup (and the
14
52
  // doctor) to be reachable so they can fix it from chat. Only a genuinely
@@ -28,6 +66,9 @@ async function main() {
28
66
  process.stderr.write(config
29
67
  ? `[telegram-mcp] serving over stdio (allowAll=${config.allowAllChats}, allowlist=${config.allowlist.length})\n`
30
68
  : `[telegram-mcp] serving over stdio (unconfigured — telegram_setup required)\n`);
69
+ // After the transport is live, so a slow or unreachable registry can never
70
+ // delay the first tool call.
71
+ warnIfOutdated();
31
72
  const shutdown = async (signal) => {
32
73
  process.stderr.write(`[telegram-mcp] ${signal}, shutting down\n`);
33
74
  try {
@@ -42,7 +83,9 @@ async function main() {
42
83
  process.on("SIGINT", () => void shutdown("SIGINT"));
43
84
  process.on("SIGTERM", () => void shutdown("SIGTERM"));
44
85
  }
45
- main().catch((err) => {
46
- process.stderr.write(`[telegram-mcp] fatal: ${redact(err instanceof Error ? err.stack ?? err.message : String(err))}\n`);
47
- process.exit(1);
48
- });
86
+ if (!handleCliFlags(process.argv)) {
87
+ main().catch((err) => {
88
+ process.stderr.write(`[telegram-mcp] fatal: ${redact(err instanceof Error ? err.stack ?? err.message : String(err))}\n`);
89
+ process.exit(1);
90
+ });
91
+ }
@@ -23,6 +23,8 @@ import { getClient } from "./telegram.js";
23
23
  import { lockFile } from "./perms.js";
24
24
  import { normalizeError } from "./errors.js";
25
25
  import { ok, guard } from "./util.js";
26
+ import { PACKAGE_NAME, SERVER_VERSION } from "./version.js";
27
+ import { getUpdateInfo } from "./update-check.js";
26
28
  /** Keys this module owns when writing `.env`; everything else is preserved. */
27
29
  const MANAGED_KEYS = [
28
30
  "TELEGRAM_API_ID",
@@ -131,10 +133,11 @@ function buildEnvBody(values) {
131
133
  export function registerSetupTools(server) {
132
134
  server.registerTool("telegram_doctor", {
133
135
  title: "Telegram health check",
134
- description: "Read-only health check: config, state dir, session presence, session file permissions " +
135
- "(auto-repaired to 0600), and live authorization. Returns { healthy, checks[], account }. " +
136
+ description: "Read-only health check: config, installed version, state dir, session presence, session file " +
137
+ "permissions (auto-repaired to 0600), and live authorization. Returns { healthy, checks[], account }. " +
136
138
  "A missing or unauthorized session is reported as a failed check, not an error — call " +
137
- "telegram_login_start to fix it. Never returns the api_hash or the session string.",
139
+ "telegram_login_start to fix it. An available update is informational and never fails the check. " +
140
+ "Never returns the api_hash or the session string.",
138
141
  inputSchema: {},
139
142
  annotations: { readOnlyHint: true, openWorldHint: false },
140
143
  }, async () => guard(async () => {
@@ -153,6 +156,23 @@ export function registerSetupTools(server) {
153
156
  catch (err) {
154
157
  checks.push({ name: "config", pass: false, detail: normalizeError(err).message });
155
158
  }
159
+ // 2. version — always informational, never a failure. An available
160
+ // update is not a health problem, so it must not flip `healthy` to
161
+ // false and send an agent chasing a working install. Cache-only:
162
+ // a health check must not wait on the network.
163
+ try {
164
+ const info = await getUpdateInfo({ allowNetwork: false });
165
+ checks.push({
166
+ name: "version",
167
+ pass: true,
168
+ detail: info.updateAvailable
169
+ ? `${info.current} (${info.latest} available — \`npm install -g ${PACKAGE_NAME}@latest\`)`
170
+ : `${info.current} (latest known)`,
171
+ });
172
+ }
173
+ catch {
174
+ checks.push({ name: "version", pass: true, detail: SERVER_VERSION });
175
+ }
156
176
  if (!config) {
157
177
  // Without credentials there is no session path to inspect and no client
158
178
  // to connect: report the remaining checks as failed-with-reason rather
@@ -0,0 +1,151 @@
1
+ /**
2
+ * "A newer version exists" notice.
3
+ *
4
+ * Why it exists: the package is distributed through npx, where the version is
5
+ * resolved on every run, and through `npm i -g`, where it is not. A user who
6
+ * installed globally has no way to learn that a fix shipped unless they think
7
+ * to run `npm outdated -g`. This module prints one line to stderr when the
8
+ * registry has something newer.
9
+ *
10
+ * Design constraints, all deliberate:
11
+ * - stderr only. stdout belongs to the JSON-RPC transport; a stray line there
12
+ * would make the agent drop the server.
13
+ * - never fatal, never blocking. The check runs after the server is already
14
+ * serving; a timeout, an offline machine or a broken proxy just means no
15
+ * notice.
16
+ * - cached for a day, so restarting the client five times does not mean five
17
+ * registry requests.
18
+ * - opt out with TELEGRAM_UPDATE_CHECK=0. This is a local-first tool and a
19
+ * user is entitled to zero outbound traffic that is not Telegram itself.
20
+ */
21
+ import fs from "node:fs";
22
+ import os from "node:os";
23
+ import path from "node:path";
24
+ import { PACKAGE_NAME, SERVER_VERSION, VERSION_IS_KNOWN, compareVersions } from "./version.js";
25
+ const DEFAULT_TIMEOUT_MS = 800;
26
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
27
+ function enabled() {
28
+ const raw = process.env.TELEGRAM_UPDATE_CHECK;
29
+ if (raw === undefined || raw.trim() === "")
30
+ return true;
31
+ return !/^(0|false|no|off)$/i.test(raw.trim());
32
+ }
33
+ function timeoutMs() {
34
+ const raw = process.env.TELEGRAM_UPDATE_CHECK_TIMEOUT_MS;
35
+ if (!raw)
36
+ return DEFAULT_TIMEOUT_MS;
37
+ const n = Number(raw);
38
+ if (!Number.isFinite(n) || n <= 0)
39
+ return DEFAULT_TIMEOUT_MS;
40
+ return Math.min(n, 10_000);
41
+ }
42
+ /**
43
+ * Cache location. Deliberately independent of `getConfig()`: the notice must
44
+ * work before api_id/api_hash are set, and it must not create the state dir
45
+ * just to write a version number.
46
+ */
47
+ function cachePath() {
48
+ const configured = process.env.TELEGRAM_STATE_DIR?.trim();
49
+ const base = configured
50
+ ? path.resolve(configured.replace(/^~(?=$|[\\/])/, os.homedir()))
51
+ : path.join(os.homedir(), ".telegram-mcp");
52
+ return path.join(base, "update-check.json");
53
+ }
54
+ function readCache() {
55
+ try {
56
+ const parsed = JSON.parse(fs.readFileSync(cachePath(), "utf8"));
57
+ if (typeof parsed.latest !== "string" || typeof parsed.checkedAt !== "string")
58
+ return null;
59
+ if (Date.now() - Date.parse(parsed.checkedAt) > CACHE_TTL_MS)
60
+ return null;
61
+ return { latest: parsed.latest, checkedAt: parsed.checkedAt };
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ }
67
+ function writeCache(latest) {
68
+ try {
69
+ const file = cachePath();
70
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
71
+ const tmp = `${file}.${process.pid}.tmp`;
72
+ fs.writeFileSync(tmp, JSON.stringify({ checkedAt: new Date().toISOString(), latest }, null, 2), { encoding: "utf8", mode: 0o600 });
73
+ fs.renameSync(tmp, file);
74
+ }
75
+ catch {
76
+ /* a cache we cannot write is not a failure — it just means we ask again */
77
+ }
78
+ }
79
+ /** Ask the registry for `dist-tags.latest`. Returns null on any problem. */
80
+ async function fetchLatest() {
81
+ try {
82
+ const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`, {
83
+ headers: { accept: "application/json" },
84
+ signal: AbortSignal.timeout(timeoutMs()),
85
+ });
86
+ if (!res.ok)
87
+ return null;
88
+ const body = (await res.json());
89
+ return typeof body.version === "string" && body.version.trim() ? body.version.trim() : null;
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ }
95
+ /**
96
+ * Resolve the current state. `allowNetwork: false` reads only the cache, which
97
+ * is what `telegram_doctor` uses so a health check never waits on the network.
98
+ */
99
+ export async function getUpdateInfo(opts = {}) {
100
+ const current = SERVER_VERSION;
101
+ // Without a trustworthy local version, "newer" has no meaning: skip the
102
+ // notice rather than telling every user to upgrade to something.
103
+ if (!VERSION_IS_KNOWN)
104
+ return { current, latest: current, updateAvailable: false, source: "unknown" };
105
+ if (!enabled())
106
+ return { current, latest: current, updateAvailable: false, source: "disabled" };
107
+ const cached = readCache();
108
+ if (cached) {
109
+ return {
110
+ current,
111
+ latest: cached.latest,
112
+ updateAvailable: compareVersions(cached.latest, current) > 0,
113
+ source: "cache",
114
+ };
115
+ }
116
+ if (opts.allowNetwork === false) {
117
+ return { current, latest: current, updateAvailable: false, source: "unknown" };
118
+ }
119
+ const latest = await fetchLatest();
120
+ if (!latest)
121
+ return { current, latest: current, updateAvailable: false, source: "unknown" };
122
+ writeCache(latest);
123
+ return {
124
+ current,
125
+ latest,
126
+ updateAvailable: compareVersions(latest, current) > 0,
127
+ source: "registry",
128
+ };
129
+ }
130
+ /** The single line users see. Shaped to be actionable, not just informative. */
131
+ export function formatNotice(info) {
132
+ const upgrade = process.env.npm_config_global === "true"
133
+ ? `npm install -g ${PACKAGE_NAME}@latest`
134
+ : `npx -y ${PACKAGE_NAME}@latest`;
135
+ return (`[telegram-mcp] update available: ${info.current} -> ${info.latest}. ` +
136
+ `Run \`${upgrade}\`, or set TELEGRAM_UPDATE_CHECK=0 to silence this.\n`);
137
+ }
138
+ /**
139
+ * Fire-and-forget: called after the server is already serving, so a slow
140
+ * registry can never delay the first tool call.
141
+ */
142
+ export function warnIfOutdated() {
143
+ void getUpdateInfo()
144
+ .then((info) => {
145
+ if (info.updateAvailable)
146
+ process.stderr.write(formatNotice(info));
147
+ })
148
+ .catch(() => {
149
+ /* the notice is best-effort by definition */
150
+ });
151
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The one place the version number comes from.
3
+ *
4
+ * `package.json` is the source of truth: npm writes the version it publishes,
5
+ * so reading it back means the MCP `serverInfo`, `--version` and the update
6
+ * check can never drift from the artifact that was actually shipped.
7
+ *
8
+ * Resolution walks up from this file, which covers every layout:
9
+ * <root>/dist/version.js (published / built)
10
+ * <root>/version.ts (tsx, dev)
11
+ * <root>/node_modules/@dikacode/telegram-mcp/dist/version.js
12
+ */
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+ export const SERVER_NAME = "telegram-mcp";
17
+ export const PACKAGE_NAME = "@dikacode/telegram-mcp";
18
+ /**
19
+ * Last resort when package.json is unreadable. Deliberately not a plausible
20
+ * version: a guess here would make the update check nag about a release the
21
+ * user may already be running, so an unknown version disables the notice.
22
+ */
23
+ const FALLBACK_VERSION = "0.0.0-unknown";
24
+ function readVersion() {
25
+ let dir = path.dirname(fileURLToPath(import.meta.url));
26
+ for (let i = 0; i < 6; i++) {
27
+ try {
28
+ const raw = fs.readFileSync(path.join(dir, "package.json"), "utf8");
29
+ const parsed = JSON.parse(raw);
30
+ if (typeof parsed.version === "string" && parsed.version.trim()) {
31
+ return parsed.version.trim();
32
+ }
33
+ }
34
+ catch {
35
+ /* keep walking: this directory is not the package root */
36
+ }
37
+ const parent = path.dirname(dir);
38
+ if (parent === dir)
39
+ break;
40
+ dir = parent;
41
+ }
42
+ return FALLBACK_VERSION;
43
+ }
44
+ export const SERVER_VERSION = readVersion();
45
+ /** True when package.json could not be read, so the version is not trustworthy. */
46
+ export const VERSION_IS_KNOWN = SERVER_VERSION !== "0.0.0-unknown";
47
+ /** Dotted-numeric comparison, prerelease-aware, with no dependency. */
48
+ export function compareVersions(a, b) {
49
+ const split = (v) => {
50
+ const cleaned = v.trim().replace(/^v/i, "").split("+")[0];
51
+ const dash = cleaned.indexOf("-");
52
+ const core = dash === -1 ? cleaned : cleaned.slice(0, dash);
53
+ const pre = dash === -1 ? "" : cleaned.slice(dash + 1);
54
+ const nums = core.split(".").map((part) => {
55
+ const n = Number.parseInt(part, 10);
56
+ return Number.isFinite(n) ? n : 0;
57
+ });
58
+ while (nums.length < 3)
59
+ nums.push(0);
60
+ return { nums, pre };
61
+ };
62
+ const left = split(a);
63
+ const right = split(b);
64
+ for (let i = 0; i < 3; i++) {
65
+ if (left.nums[i] !== right.nums[i])
66
+ return left.nums[i] < right.nums[i] ? -1 : 1;
67
+ }
68
+ // Same core: a release outranks any prerelease of it.
69
+ if (!left.pre && right.pre)
70
+ return 1;
71
+ if (left.pre && !right.pre)
72
+ return -1;
73
+ if (left.pre === right.pre)
74
+ return 0;
75
+ return left.pre < right.pre ? -1 : 1;
76
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dikacode/telegram-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Local MCP server that connects CLI agents (OpenCode, Codex CLI, Claude Code, ...) to a personal Telegram account over MTProto.",
5
5
  "type": "module",
6
6
  "bin": {