@dikacode/telegram-mcp 1.0.0 → 1.0.2
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 +68 -6
- package/dist/cli/doctor.js +69 -19
- package/dist/cli/login.js +46 -14
- package/dist/cli/update.js +293 -0
- package/dist/config.js +1 -2
- package/dist/index.js +111 -10
- package/dist/setup-tools.js +23 -3
- package/dist/ui.js +237 -0
- package/dist/update-check.js +151 -0
- package/dist/version.js +76 -0
- package/package.json +2 -1
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.
|
|
153
|
+
Pin a version if you want reproducible installs: `npx -y @dikacode/telegram-mcp@1.0.2`.
|
|
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.
|
|
173
|
+
npm install -g https://github.com/dikaglorya/telegram-mcp/releases/download/v1.0.2/dikacode-telegram-mcp-1.0.2.tgz
|
|
174
174
|
telegram-mcp
|
|
175
175
|
```
|
|
176
176
|
|
|
@@ -186,6 +186,59 @@ 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
|
+
One command, whichever way it was installed:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
telegram-mcp update
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
It works out how *this* copy got here and runs the right thing — `npm install
|
|
198
|
+
-g @latest` for a global install (reusing a custom `--prefix`), `git pull
|
|
199
|
+
--ff-only` plus a rebuild for a checkout, and a plain restart instruction for an
|
|
200
|
+
npx run, where nothing on disk needs changing. `--check` reports and installs
|
|
201
|
+
nothing.
|
|
202
|
+
|
|
203
|
+
```text
|
|
204
|
+
telegram-mcp 1.0.2 · update
|
|
205
|
+
|
|
206
|
+
installed 1.0.1 /home/you/.local/lib/node_modules/@dikacode/telegram-mcp
|
|
207
|
+
latest 1.0.2
|
|
208
|
+
install global npm package
|
|
209
|
+
|
|
210
|
+
▲ 1.0.1 -> 1.0.2 available
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
| Installed with | Behaviour |
|
|
214
|
+
| --- | --- |
|
|
215
|
+
| `npx -y @dikacode/telegram-mcp` (unpinned) | **Automatic.** npm resolves `latest` on every start, so the next client restart picks up a new release. |
|
|
216
|
+
| `npx -y @dikacode/telegram-mcp@1.0.2` (pinned) | **Never.** That is the point of pinning. |
|
|
217
|
+
| `npm install -g @dikacode/telegram-mcp` | **`telegram-mcp update`**, or `npm update -g @dikacode/telegram-mcp`. |
|
|
218
|
+
| Release tarball | **Manual.** Reinstall from the newer tarball. |
|
|
219
|
+
| `npx -y github:dikaglorya/telegram-mcp` | **Not automatic.** npm caches the fetched repo; clear the cache or pin a ref (`#main`) to move forward. |
|
|
220
|
+
|
|
221
|
+
Check what you are running:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
telegram-mcp --version
|
|
225
|
+
telegram-mcp update --check # newer release available? installs nothing
|
|
226
|
+
npm outdated -g @dikacode/telegram-mcp # global installs only
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The server also tells you. When a newer release exists it prints one line to
|
|
230
|
+
stderr at startup, after the MCP transport is already serving:
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
[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.
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
That check is a single request to the npm registry with an 800 ms timeout,
|
|
237
|
+
cached for 24 hours, and it never blocks startup or writes to stdout. If you
|
|
238
|
+
want zero outbound traffic that is not Telegram itself, set
|
|
239
|
+
`TELEGRAM_UPDATE_CHECK=0`. `telegram_doctor` shows the version it knows about
|
|
240
|
+
without touching the network.
|
|
241
|
+
|
|
189
242
|
## Credentials
|
|
190
243
|
|
|
191
244
|
1. Open [my.telegram.org/apps](https://my.telegram.org/apps) and log in with your phone number.
|
|
@@ -477,6 +530,8 @@ All settings are environment variables. The server also loads a `.env` next to t
|
|
|
477
530
|
| `TELEGRAM_EXPORT_DIR` | `~/.telegram-mcp/exports` | History exports. |
|
|
478
531
|
| `TELEGRAM_AUDIT_LOG` | `~/.telegram-mcp/audit.log` | Call log. |
|
|
479
532
|
| `TELEGRAM_MAX_DOWNLOAD_BYTES` | `0` (unlimited) | Hard cap for one download. A non-numeric value is refused rather than treated as unlimited. |
|
|
533
|
+
| `TELEGRAM_UPDATE_CHECK` | `true` | Set to `0` to stop the startup check for a newer npm release. |
|
|
534
|
+
| `TELEGRAM_UPDATE_CHECK_TIMEOUT_MS` | `800` | Timeout for that check, capped at 10 s. |
|
|
480
535
|
| `TELEGRAM_MCP_NODE` | current Node | Launcher override, for exotic Node installs. |
|
|
481
536
|
|
|
482
537
|
`~` and `~\` are both expanded. Relative paths resolve against the current working directory.
|
|
@@ -494,6 +549,9 @@ All settings are environment variables. The server also loads a `.env` next to t
|
|
|
494
549
|
| `Cannot find module .../dist/index.js` | The package was installed without building. Run `npm run build`, or use the release tarball. |
|
|
495
550
|
| Login works, then the next run asks again | `TELEGRAM_SESSION_PATH` changed between runs. Keep it stable, or leave it unset. |
|
|
496
551
|
| 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. |
|
|
552
|
+
| `update available: …` on every start | A newer release is on npm. Run `telegram-mcp update`, or set `TELEGRAM_UPDATE_CHECK=0` if you pin versions on purpose. |
|
|
553
|
+
| `telegram-mcp update` finds no upgrade command | The copy lives in a `node_modules` tree npm does not manage as a global prefix (npx cache, or a project dependency). Restart it as `npx -y @dikacode/telegram-mcp@latest`, or upgrade the project dependency. |
|
|
554
|
+
| `telegram-mcp --version` prints nothing | You are on `1.0.0`, which predates the flag. Upgrade to `1.0.1` or newer. |
|
|
497
555
|
|
|
498
556
|
## Development
|
|
499
557
|
|
|
@@ -503,7 +561,7 @@ The repository ships with a unit suite and a real MCP harness that spawns the bu
|
|
|
503
561
|
npm install
|
|
504
562
|
npm run build
|
|
505
563
|
|
|
506
|
-
npm run test:unit #
|
|
564
|
+
npm run test:unit # 72 offline checks
|
|
507
565
|
npm run test:mcp # read-only end-to-end pass
|
|
508
566
|
npm run test:mcp:write # + self-cleaning writes
|
|
509
567
|
npm run test:destructive # + disposable chats (opt-in, flood-limited)
|
|
@@ -529,7 +587,7 @@ The harness also supports `--json`, `--only=tool_a,tool_b`, `--cleanup`, `--recl
|
|
|
529
587
|
```
|
|
530
588
|
telegram-mcp/
|
|
531
589
|
├── bin/telegram-mcp.js portable launcher (finds Node, then dist/index.js)
|
|
532
|
-
├── cli/ standalone login and
|
|
590
|
+
├── cli/ standalone login, doctor and update commands
|
|
533
591
|
├── assets/ animated README art (SVG, no scripts)
|
|
534
592
|
├── harness/ unit checks + end-to-end MCP harness
|
|
535
593
|
├── index.ts stdio entrypoint
|
|
@@ -556,7 +614,8 @@ The TypeScript is built with `tsc` to `dist/`. There is no bundler; the publishe
|
|
|
556
614
|
|
|
557
615
|
## Release
|
|
558
616
|
|
|
559
|
-
**v1.0.
|
|
617
|
+
**v1.0.2** is the current release, published to npm as
|
|
618
|
+
[`@dikacode/telegram-mcp`](https://www.npmjs.com/package/@dikacode/telegram-mcp).
|
|
560
619
|
|
|
561
620
|
- 69 MCP tools, all registered with strict input schemas.
|
|
562
621
|
- In-chat login, including two-step verification.
|
|
@@ -564,6 +623,9 @@ The TypeScript is built with `tsc` to `dist/`. There is no bundler; the publishe
|
|
|
564
623
|
- Read-back verification for state-changing tools.
|
|
565
624
|
- Optional allowlist, audited calls, redacted secrets.
|
|
566
625
|
- Cross-platform launcher; no hardcoded paths.
|
|
626
|
+
- `--version` / `--help`, and a startup notice when a newer release exists.
|
|
627
|
+
- `telegram-mcp update` upgrades whichever layout is installed; `--check`
|
|
628
|
+
reports without touching anything.
|
|
567
629
|
- End-to-end harness with paging, policy probes and artifact cleanup.
|
|
568
630
|
|
|
569
631
|
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/cli/doctor.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Doctor: validate config, session presence, permissions, live connectivity.
|
|
4
|
-
*
|
|
4
|
+
* Never prints secrets.
|
|
5
|
+
*
|
|
6
|
+
* Output is a framed report on stdout when run as a CLI — this is a human
|
|
7
|
+
* command, not an MCP server, so stdout is free. Under `--quiet` (or when
|
|
8
|
+
* piped) it prints one line per check with no colour, which is what CI wants.
|
|
5
9
|
*/
|
|
6
10
|
import "../env.js";
|
|
7
11
|
import fs from "node:fs";
|
|
@@ -9,8 +13,14 @@ import path from "node:path";
|
|
|
9
13
|
import { ensureDirs, getConfig, hasSession, readSessionString, redact } from "../config.js";
|
|
10
14
|
import { lockFile } from "../perms.js";
|
|
11
15
|
import { getClient, disconnect } from "../telegram.js";
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
import { banner, colorEnabled, gap, paint, sym, theme } from "../ui.js";
|
|
17
|
+
const out = (text) => void process.stdout.write(text);
|
|
18
|
+
const rows = [];
|
|
19
|
+
function record(pass, label, detail = "") {
|
|
20
|
+
rows.push({ pass, label, detail });
|
|
21
|
+
}
|
|
22
|
+
function icon(pass) {
|
|
23
|
+
return pass ? theme.success(sym.ok) : theme.fail(sym.fail);
|
|
14
24
|
}
|
|
15
25
|
async function main() {
|
|
16
26
|
let failures = 0;
|
|
@@ -19,49 +29,55 @@ async function main() {
|
|
|
19
29
|
config = getConfig();
|
|
20
30
|
}
|
|
21
31
|
catch (err) {
|
|
22
|
-
|
|
32
|
+
record(false, "config", err instanceof Error ? err.message : String(err));
|
|
33
|
+
report();
|
|
23
34
|
process.exit(1);
|
|
24
35
|
}
|
|
25
36
|
ensureDirs(config);
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
record(true, "config", `api_id=${config.apiId}, allowAll=${config.allowAllChats}, allowlist=${config.allowlist.length}`);
|
|
38
|
+
record(fs.existsSync(path.dirname(config.sessionPath)), "state dir", path.dirname(config.sessionPath));
|
|
28
39
|
const has = hasSession(config);
|
|
29
|
-
|
|
40
|
+
record(has, "session present", config.sessionPath);
|
|
30
41
|
if (!has)
|
|
31
42
|
failures++;
|
|
32
43
|
if (has) {
|
|
33
44
|
if (process.platform === "win32") {
|
|
34
|
-
|
|
45
|
+
record(true, "session permissions", "skipped on Windows (inherits user profile ACLs)");
|
|
35
46
|
}
|
|
36
47
|
else {
|
|
37
48
|
try {
|
|
38
49
|
const mode = fs.statSync(config.sessionPath).mode & 0o777;
|
|
39
50
|
const good = (mode & 0o077) === 0;
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
if (good) {
|
|
52
|
+
record(true, "session permissions", `mode=${mode.toString(8)}`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
42
55
|
lockFile(config.sessionPath);
|
|
43
|
-
|
|
56
|
+
const fixed = (fs.statSync(config.sessionPath).mode & 0o777 & 0o077) === 0;
|
|
57
|
+
record(fixed, "session permissions", fixed ? "repaired to 600" : `mode=${mode.toString(8)}`);
|
|
58
|
+
if (!fixed)
|
|
59
|
+
failures++;
|
|
44
60
|
}
|
|
45
61
|
}
|
|
46
62
|
catch (err) {
|
|
47
|
-
|
|
63
|
+
record(false, "session permissions", err instanceof Error ? err.message : String(err));
|
|
48
64
|
failures++;
|
|
49
65
|
}
|
|
50
66
|
}
|
|
51
67
|
try {
|
|
52
|
-
|
|
68
|
+
record(readSessionString(config).length > 0, "session readable");
|
|
53
69
|
}
|
|
54
70
|
catch (err) {
|
|
55
|
-
|
|
71
|
+
record(false, "session readable", err instanceof Error ? err.message : String(err));
|
|
56
72
|
failures++;
|
|
57
73
|
}
|
|
58
74
|
try {
|
|
59
75
|
const client = await getClient();
|
|
60
76
|
const authorized = await client.isUserAuthorized();
|
|
61
|
-
|
|
77
|
+
record(authorized, "telegram authorized");
|
|
62
78
|
if (authorized) {
|
|
63
79
|
const me = (await client.getMe());
|
|
64
|
-
|
|
80
|
+
record(true, "account", `id=${String(me.id ?? "")} username=${me.username ?? "none"}`);
|
|
65
81
|
}
|
|
66
82
|
else {
|
|
67
83
|
failures++;
|
|
@@ -69,14 +85,48 @@ async function main() {
|
|
|
69
85
|
await disconnect();
|
|
70
86
|
}
|
|
71
87
|
catch (err) {
|
|
72
|
-
|
|
88
|
+
record(false, "telegram connectivity", redact(err instanceof Error ? err.message : String(err)));
|
|
73
89
|
failures++;
|
|
74
90
|
}
|
|
75
91
|
}
|
|
76
|
-
|
|
92
|
+
report(failures);
|
|
77
93
|
process.exit(failures === 0 ? 0 : 1);
|
|
78
94
|
}
|
|
95
|
+
function report(failures) {
|
|
96
|
+
const plain = !colorEnabled() || process.argv.includes("--quiet");
|
|
97
|
+
if (!plain) {
|
|
98
|
+
banner({ subtitle: "health check", compact: true });
|
|
99
|
+
gap();
|
|
100
|
+
}
|
|
101
|
+
const width = Math.max(...rows.map((r) => r.label.length));
|
|
102
|
+
for (const row of rows) {
|
|
103
|
+
if (plain) {
|
|
104
|
+
out(`[${row.pass ? "PASS" : "FAIL"}] ${row.label}${row.detail ? ` - ${row.detail}` : ""}\n`);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const detail = row.detail ? ` ${theme.muted(row.detail)}` : "";
|
|
108
|
+
// padEnd measures code units; the icon is one glyph, so this aligns.
|
|
109
|
+
out(` ${icon(row.pass)} ${theme.label(row.label.padEnd(width))}${detail}\n`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (failures === undefined)
|
|
113
|
+
return;
|
|
114
|
+
if (plain) {
|
|
115
|
+
out(failures === 0 ? "\nall checks passed.\n" : `\n${failures} check(s) failed.\n`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
gap();
|
|
119
|
+
out(failures === 0
|
|
120
|
+
? ` ${theme.success(`${sym.ok} ${paint.bold("all checks passed")}`)}\n`
|
|
121
|
+
: ` ${theme.fail(`${sym.fail} ${failures} check(s) failed`)}\n`);
|
|
122
|
+
if (failures !== 0 && hasHint()) {
|
|
123
|
+
out(` ${theme.muted(`${sym.bullet} run ${paint.bold("telegram-mcp login")} to issue a fresh session`)}\n`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function hasHint() {
|
|
127
|
+
return rows.some((r) => !r.pass && /session|authorized/i.test(r.label));
|
|
128
|
+
}
|
|
79
129
|
main().catch((err) => {
|
|
80
|
-
process.stderr.write(`[doctor] fatal: ${redact(err instanceof Error ? err.message : String(err))}\n`);
|
|
130
|
+
process.stderr.write(`${theme.fail(`[doctor] fatal: ${redact(err instanceof Error ? err.message : String(err))}`)}\n`);
|
|
81
131
|
process.exit(1);
|
|
82
132
|
});
|
package/dist/cli/login.js
CHANGED
|
@@ -15,6 +15,7 @@ import { TelegramClient } from "telegram";
|
|
|
15
15
|
import { StringSession } from "telegram/sessions/index.js";
|
|
16
16
|
import { ensureDirs, getConfig, hasSession, readSessionString, writeSessionString, redact, } from "../config.js";
|
|
17
17
|
import { createLogger } from "../logger.js";
|
|
18
|
+
import { banner, gap, hint, nextSteps, paint, spinner, step, success, sym, theme, warn } from "../ui.js";
|
|
18
19
|
function ask(question) {
|
|
19
20
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
20
21
|
return new Promise((resolve) => rl.question(question, (answer) => {
|
|
@@ -22,6 +23,9 @@ function ask(question) {
|
|
|
22
23
|
resolve(answer.trim());
|
|
23
24
|
}));
|
|
24
25
|
}
|
|
26
|
+
function askPlain(question) {
|
|
27
|
+
return ask(`${theme.accent(`${sym.arrow} `)}${theme.label(question)} `);
|
|
28
|
+
}
|
|
25
29
|
/**
|
|
26
30
|
* Ask a question without echoing the answer. readline's `terminal` mode masks
|
|
27
31
|
* the prompt on POSIX and Windows alike; it falls back to a plain read when
|
|
@@ -57,6 +61,9 @@ function askSecret(question) {
|
|
|
57
61
|
async function main() {
|
|
58
62
|
const config = getConfig();
|
|
59
63
|
ensureDirs(config);
|
|
64
|
+
banner({ subtitle: "interactive login", compact: true });
|
|
65
|
+
gap();
|
|
66
|
+
step(1, 3, "Checking the existing session");
|
|
60
67
|
// Re-running login silently mints a NEW auth key for the same account. Telegram
|
|
61
68
|
// then treats the previous key as AUTH_KEY_DUPLICATED and every client holding
|
|
62
69
|
// it stops working. Refuse to overwrite a session that still works.
|
|
@@ -65,25 +72,35 @@ async function main() {
|
|
|
65
72
|
connectionRetries: 2,
|
|
66
73
|
baseLogger: createLogger(),
|
|
67
74
|
});
|
|
75
|
+
const check = spinner("verifying the saved session");
|
|
68
76
|
try {
|
|
69
77
|
await probe.connect();
|
|
70
78
|
const ok = await probe.isUserAuthorized();
|
|
71
79
|
if (ok) {
|
|
72
80
|
const me = (await probe.getMe());
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
check.stop(`${theme.success(sym.ok)} already authorized as ${theme.label(String(me.username ?? me.id ?? ""))}`);
|
|
82
|
+
gap();
|
|
83
|
+
hint(`session kept at ${config.sessionPath}`);
|
|
84
|
+
hint(`pass ${theme.label("--force")} to overwrite it with a fresh auth key`);
|
|
76
85
|
await probe.disconnect().catch(() => undefined);
|
|
77
86
|
return;
|
|
78
87
|
}
|
|
88
|
+
check.stop(`${theme.warn(`${sym.warn} saved session is no longer authorized`)}`);
|
|
79
89
|
}
|
|
80
90
|
catch {
|
|
91
|
+
check.stop(`${theme.warn(`${sym.warn} saved session is unusable — issuing a fresh one`)}`);
|
|
81
92
|
/* dead or duplicated session — fall through and replace it */
|
|
82
93
|
}
|
|
83
94
|
await probe.disconnect().catch(() => undefined);
|
|
84
|
-
process.stderr.write(`[login] existing session is unusable, issuing a fresh one\n`);
|
|
85
95
|
}
|
|
86
|
-
|
|
96
|
+
else {
|
|
97
|
+
hint(hasSession(config) ? `overwriting the session because of ${theme.label("--force")}` : "no session on disk yet");
|
|
98
|
+
}
|
|
99
|
+
gap();
|
|
100
|
+
step(2, 3, "Signing in to Telegram");
|
|
101
|
+
hint(`the session will be stored at ${config.sessionPath}`);
|
|
102
|
+
if (!process.stdin.isTTY)
|
|
103
|
+
hint(`stdin is not a terminal — prompts may not echo correctly`);
|
|
87
104
|
const existing = hasSession(config) ? readSessionString(config) : "";
|
|
88
105
|
const session = new StringSession(existing);
|
|
89
106
|
const client = new TelegramClient(session, config.apiId, config.apiHash, {
|
|
@@ -97,28 +114,40 @@ async function main() {
|
|
|
97
114
|
phoneNumber: async () => {
|
|
98
115
|
const stored = process.env.TELEGRAM_PHONE?.trim();
|
|
99
116
|
if (stored) {
|
|
100
|
-
|
|
117
|
+
hint(`using ${theme.label("TELEGRAM_PHONE")} from .env`);
|
|
101
118
|
return stored;
|
|
102
119
|
}
|
|
103
|
-
return
|
|
120
|
+
return askPlain("Phone number (international, e.g. +15551234567)");
|
|
121
|
+
},
|
|
122
|
+
phoneCode: async () => {
|
|
123
|
+
hint("Telegram is sending a code to your other devices");
|
|
124
|
+
return askPlain("Login code");
|
|
125
|
+
},
|
|
126
|
+
password: async () => {
|
|
127
|
+
hint("two-step verification is on for this account");
|
|
128
|
+
return askSecret("2FA password (leave blank if none)");
|
|
104
129
|
},
|
|
105
|
-
phoneCode: async () => ask("Login code from Telegram: "),
|
|
106
|
-
password: async () => askSecret("2FA password (leave blank if none)"),
|
|
107
130
|
onError: (err) => {
|
|
108
131
|
// The update loop keeps pinging after we disconnect; those late
|
|
109
132
|
// rejections are noise, not auth failures.
|
|
110
133
|
const msg = err instanceof Error ? err.message : String(err);
|
|
111
134
|
if (msg === "TIMEOUT" || msg.includes("TIMEOUT"))
|
|
112
135
|
return;
|
|
113
|
-
|
|
136
|
+
warn(`auth error: ${redact(msg)}`);
|
|
114
137
|
},
|
|
115
138
|
});
|
|
116
139
|
const me = (await client.getMe());
|
|
117
140
|
const serialized = client.session.save();
|
|
118
141
|
writeSessionString(config, serialized);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
142
|
+
gap();
|
|
143
|
+
step(3, 3, "Saving");
|
|
144
|
+
success(`authorized as ${paint.bold(String(me.username ?? me.id ?? ""))}`);
|
|
145
|
+
hint(`session saved to ${config.sessionPath} ${theme.muted("(0600)")}`);
|
|
146
|
+
nextSteps([
|
|
147
|
+
`start your MCP client — it spawns ${theme.label("telegram-mcp")} from the config you already added`,
|
|
148
|
+
`or run ${theme.label("telegram-mcp doctor")} to confirm everything is healthy`,
|
|
149
|
+
]);
|
|
150
|
+
gap();
|
|
122
151
|
}
|
|
123
152
|
finally {
|
|
124
153
|
// gramJS fires the update-ping loop the moment we connect. Disconnecting
|
|
@@ -136,6 +165,9 @@ async function main() {
|
|
|
136
165
|
}
|
|
137
166
|
main().catch((err) => {
|
|
138
167
|
const msg = err instanceof Error ? err.message : String(err);
|
|
139
|
-
process.stderr.write(
|
|
168
|
+
process.stderr.write(`${theme.fail(`${sym.fail} login failed: ${redact(msg)}`)}\n`);
|
|
169
|
+
if (/not set|invalid/i.test(msg)) {
|
|
170
|
+
process.stderr.write(` ${theme.muted(`${sym.bullet} get api_id/api_hash from `)}${theme.underline("https://my.telegram.org/apps")}\n`);
|
|
171
|
+
}
|
|
140
172
|
process.exit(1);
|
|
141
173
|
});
|