@deadragdoll/tellymcp 0.0.14 → 0.0.16

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 (44) hide show
  1. package/.env.example.client +19 -39
  2. package/.env.example.gateway +40 -64
  3. package/CHANGELOG.md +31 -0
  4. package/README-ru.md +67 -6
  5. package/README.md +67 -6
  6. package/TOOLS.md +38 -1
  7. package/config/templates/env.both.template +4 -5
  8. package/config/templates/env.client.template +4 -17
  9. package/config/templates/env.gateway.template +4 -29
  10. package/dist/cli.js +232 -53
  11. package/dist/configureServer.js +966 -0
  12. package/dist/envMigration.js +316 -0
  13. package/dist/moleculer.config.js +1 -3
  14. package/dist/services/features/telegram-mcp/approval.service.js +1 -1
  15. package/dist/services/features/telegram-mcp/collaboration.service.js +2 -2
  16. package/dist/services/features/telegram-mcp/ensuredb.service.js +1 -1
  17. package/dist/services/features/telegram-mcp/gateway.service.js +1 -1
  18. package/dist/services/features/telegram-mcp/mcp-server.service.js +2 -0
  19. package/dist/services/features/telegram-mcp/session-context.service.js +25 -1
  20. package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +70 -66
  21. package/dist/services/features/telegram-mcp/src/app/config/env.js +10 -36
  22. package/dist/services/features/telegram-mcp/src/app/config/environmentContract.js +66 -0
  23. package/dist/services/features/telegram-mcp/src/entities/request/model/schema.js +28 -2
  24. package/dist/services/features/telegram-mcp/src/features/browser/model/browserService.js +2 -2
  25. package/dist/services/features/telegram-mcp/src/features/collaboration/model/gatewaySessionsService.js +5 -5
  26. package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayClientAccess.js +1 -1
  27. package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayCollaborationBackend.js +4 -4
  28. package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +0 -1
  29. package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/remoteConsoleActionClient.js +4 -3
  30. package/dist/services/features/telegram-mcp/src/features/notify/model/notifyService.js +4 -4
  31. package/dist/services/features/telegram-mcp/src/features/session-context/model/getRuntimeDiagnosticsTool.js +30 -0
  32. package/dist/services/features/telegram-mcp/src/features/session-context/model/sessionContextService.js +169 -7
  33. package/dist/services/features/telegram-mcp/src/shared/integrations/memory/processLocalStateStore.js +260 -0
  34. package/dist/services/features/telegram-mcp/src/shared/integrations/object-storage/minioExchangeStore.js +1 -1
  35. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +1 -1
  36. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConsoleRegistry.js +2 -2
  37. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMessageFlow.js +1 -1
  38. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectState.js +2 -2
  39. package/dist/services/features/telegram-mcp/src/shared/lib/gatewayScope.js +5 -5
  40. package/dist/services/features/telegram-mcp/src/shared/lib/project-identity/projectIdentity.js +10 -0
  41. package/docs/STANDALONE-ru.md +30 -4
  42. package/docs/STANDALONE.md +30 -4
  43. package/package.json +1 -1
  44. package/scripts/postinstall.js +36 -1
@@ -0,0 +1,260 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProcessLocalStateStore = void 0;
4
+ const redactSecrets_1 = require("../../lib/redact-secrets/redactSecrets");
5
+ function principalKey(principal) {
6
+ return `${principal.telegramChatId}:${principal.telegramUserId}`;
7
+ }
8
+ function xchangeKey(sessionId, filePath) {
9
+ return `${sessionId}\u0000${filePath}`;
10
+ }
11
+ class ProcessLocalStateStore {
12
+ pairCodes = new Map();
13
+ bindings = new Map();
14
+ principalSessions = new Map();
15
+ principalActiveSessions = new Map();
16
+ locales = new Map();
17
+ authorizedPrincipals = new Map();
18
+ requests = new Map();
19
+ requestQueue = [];
20
+ activeRequestId = null;
21
+ menuPayloads = new Map();
22
+ xchangeFileMetas = new Map();
23
+ browserAttachments = new Map();
24
+ browserRecordings = new Map();
25
+ projectMenuViews = new Map();
26
+ outgoingNotices = new Map();
27
+ gatewayClientUuid;
28
+ constructor(input = {}) {
29
+ this.gatewayClientUuid = input.gatewayClientUuid?.trim() || null;
30
+ this.onGatewayClientUuidChange = input.onGatewayClientUuidChange;
31
+ }
32
+ onGatewayClientUuidChange;
33
+ async createPairCode(record, ttlSeconds) {
34
+ const existing = this.readExpiring(this.pairCodes, record.code);
35
+ if (existing)
36
+ return false;
37
+ this.pairCodes.set(record.code, { value: record, expiresAt: Date.now() + ttlSeconds * 1000 });
38
+ return true;
39
+ }
40
+ async consumePairCode(code) {
41
+ const record = this.readExpiring(this.pairCodes, code);
42
+ this.pairCodes.delete(code);
43
+ return record;
44
+ }
45
+ async getBinding(sessionId) {
46
+ return this.bindings.get(sessionId) ?? null;
47
+ }
48
+ async setBinding(binding) {
49
+ const previous = this.bindings.get(binding.sessionId);
50
+ if (previous)
51
+ this.detachSession(previous, binding.sessionId);
52
+ this.bindings.set(binding.sessionId, binding);
53
+ const key = principalKey(binding);
54
+ const sessions = this.principalSessions.get(key) ?? new Set();
55
+ sessions.add(binding.sessionId);
56
+ this.principalSessions.set(key, sessions);
57
+ this.principalActiveSessions.set(key, binding.sessionId);
58
+ }
59
+ async clearBinding(sessionId) {
60
+ const binding = this.bindings.get(sessionId);
61
+ this.bindings.delete(sessionId);
62
+ if (binding)
63
+ this.detachSession(binding, sessionId);
64
+ }
65
+ async getActiveSessionIdForPrincipal(principal) {
66
+ return this.principalActiveSessions.get(principalKey(principal)) ?? null;
67
+ }
68
+ async getActiveSessionIdForTelegramUser(telegramUserId) {
69
+ for (const [sessionId, binding] of this.bindings) {
70
+ if (binding.telegramUserId === telegramUserId) {
71
+ return this.principalActiveSessions.get(principalKey(binding)) ?? sessionId;
72
+ }
73
+ }
74
+ return null;
75
+ }
76
+ async clearActiveSessionIdForPrincipal(principal) {
77
+ this.principalActiveSessions.delete(principalKey(principal));
78
+ }
79
+ async setActiveSessionIdForPrincipal(principal, sessionId) {
80
+ const key = principalKey(principal);
81
+ const sessions = this.principalSessions.get(key) ?? new Set();
82
+ sessions.add(sessionId);
83
+ this.principalSessions.set(key, sessions);
84
+ this.principalActiveSessions.set(key, sessionId);
85
+ }
86
+ async listBoundSessionIdsForPrincipal(principal) {
87
+ return Array.from(this.principalSessions.get(principalKey(principal)) ?? []);
88
+ }
89
+ async listBoundPrincipals() {
90
+ const principals = new Map();
91
+ for (const binding of this.bindings.values()) {
92
+ principals.set(principalKey(binding), {
93
+ telegramChatId: binding.telegramChatId,
94
+ telegramUserId: binding.telegramUserId,
95
+ });
96
+ }
97
+ return Array.from(principals.values());
98
+ }
99
+ async resetRuntimeState() {
100
+ this.activeRequestId = null;
101
+ this.requestQueue.length = 0;
102
+ }
103
+ async getActive() {
104
+ return this.activeRequestId ? this.requests.get(this.activeRequestId) ?? null : null;
105
+ }
106
+ async createPending(request) {
107
+ this.requests.set(request.requestId, request);
108
+ this.activeRequestId = request.requestId;
109
+ }
110
+ async updatePending(request) {
111
+ this.requests.set(request.requestId, request);
112
+ }
113
+ async resolvePending(requestId, resolution) {
114
+ const current = this.requests.get(requestId);
115
+ if (current) {
116
+ this.requests.set(requestId, {
117
+ ...current,
118
+ status: resolution.status,
119
+ ...(resolution.answer ? { answer: (0, redactSecrets_1.redactSecrets)(resolution.answer) } : {}),
120
+ ...(resolution.receivedAt ? { receivedAt: resolution.receivedAt } : {}),
121
+ ...(resolution.fallbackUsed ? { fallbackIfTimeout: resolution.fallbackUsed } : {}),
122
+ });
123
+ }
124
+ this.activeRequestId = null;
125
+ }
126
+ async enqueue(request) {
127
+ this.requests.set(request.requestId, request);
128
+ this.requestQueue.push(request.requestId);
129
+ }
130
+ async dequeueNext() {
131
+ const requestId = this.requestQueue.shift();
132
+ return requestId ? this.requests.get(requestId) ?? null : null;
133
+ }
134
+ async createMenuPayload(record, ttlSeconds) {
135
+ this.menuPayloads.set(record.key, { value: record, expiresAt: Date.now() + ttlSeconds * 1000 });
136
+ }
137
+ async getMenuPayload(key) {
138
+ return this.readExpiring(this.menuPayloads, key);
139
+ }
140
+ async getUserLocale(telegramUserId) {
141
+ return this.locales.get(telegramUserId) ?? null;
142
+ }
143
+ async setUserLocale(telegramUserId, locale) {
144
+ this.locales.set(telegramUserId, locale);
145
+ }
146
+ async isAdminAuthorized(principal) {
147
+ return this.authorizedPrincipals.has(principalKey(principal));
148
+ }
149
+ async setAdminAuthorized(principal) {
150
+ this.authorizedPrincipals.set(principalKey(principal), principal);
151
+ }
152
+ async clearAdminAuthorized(principal) {
153
+ this.authorizedPrincipals.delete(principalKey(principal));
154
+ }
155
+ async listAdminAuthorizedPrincipals() {
156
+ return Array.from(this.authorizedPrincipals.values());
157
+ }
158
+ async setXchangeFileMeta(meta) {
159
+ this.xchangeFileMetas.set(xchangeKey(meta.sessionId, meta.filePath), meta);
160
+ }
161
+ async listXchangeFileMetas(sessionId) {
162
+ return Array.from(this.xchangeFileMetas.values())
163
+ .filter((meta) => meta.sessionId === sessionId)
164
+ .sort((left, right) => right.uploadedAt.localeCompare(left.uploadedAt));
165
+ }
166
+ async getXchangeFileMeta(sessionId, filePath) {
167
+ return this.xchangeFileMetas.get(xchangeKey(sessionId, filePath)) ?? null;
168
+ }
169
+ async deleteXchangeFileMeta(sessionId, filePath) {
170
+ return this.xchangeFileMetas.delete(xchangeKey(sessionId, filePath));
171
+ }
172
+ async pruneAll() {
173
+ const deletedKeys = this.countEntries();
174
+ this.pairCodes.clear();
175
+ this.bindings.clear();
176
+ this.principalSessions.clear();
177
+ this.principalActiveSessions.clear();
178
+ this.locales.clear();
179
+ this.authorizedPrincipals.clear();
180
+ this.requests.clear();
181
+ this.requestQueue.length = 0;
182
+ this.activeRequestId = null;
183
+ this.menuPayloads.clear();
184
+ this.xchangeFileMetas.clear();
185
+ this.browserAttachments.clear();
186
+ this.browserRecordings.clear();
187
+ this.projectMenuViews.clear();
188
+ this.outgoingNotices.clear();
189
+ this.gatewayClientUuid = null;
190
+ return { deletedKeys };
191
+ }
192
+ async getGatewayClientUuid() { return this.gatewayClientUuid; }
193
+ async setGatewayClientUuid(clientUuid) {
194
+ this.gatewayClientUuid = clientUuid;
195
+ this.onGatewayClientUuidChange?.(clientUuid);
196
+ }
197
+ async getBrowserAttachment(sessionId) {
198
+ return this.browserAttachments.get(sessionId) ?? null;
199
+ }
200
+ async setBrowserAttachment(record) {
201
+ this.browserAttachments.set(record.sessionId, record);
202
+ }
203
+ async clearBrowserAttachment(sessionId) { this.browserAttachments.delete(sessionId); }
204
+ async getBrowserRecording(sessionId) {
205
+ return this.browserRecordings.get(sessionId) ?? null;
206
+ }
207
+ async setBrowserRecording(record) {
208
+ this.browserRecordings.set(record.sessionId, record);
209
+ }
210
+ async clearBrowserRecording(sessionId) { this.browserRecordings.delete(sessionId); }
211
+ async setProjectMenuViewState(state) {
212
+ this.projectMenuViews.set(xchangeKey(state.projectUuid, state.sessionId), state);
213
+ }
214
+ async listProjectMenuViewStates(projectUuid) {
215
+ return Array.from(this.projectMenuViews.values()).filter((state) => state.projectUuid === projectUuid);
216
+ }
217
+ async deleteProjectMenuViewState(sessionId, projectUuid) {
218
+ return this.projectMenuViews.delete(xchangeKey(projectUuid, sessionId));
219
+ }
220
+ async setOutgoingDeliveryNotice(notice) {
221
+ this.outgoingNotices.set(notice.deliveryUuid, notice);
222
+ }
223
+ async listOutgoingDeliveryNotices() {
224
+ return Array.from(this.outgoingNotices.values());
225
+ }
226
+ async deleteOutgoingDeliveryNotice(deliveryUuid) {
227
+ return this.outgoingNotices.delete(deliveryUuid);
228
+ }
229
+ readExpiring(store, key) {
230
+ const record = store.get(key);
231
+ if (!record)
232
+ return null;
233
+ if (record.expiresAt <= Date.now()) {
234
+ store.delete(key);
235
+ return null;
236
+ }
237
+ return record.value;
238
+ }
239
+ detachSession(principal, sessionId) {
240
+ const key = principalKey(principal);
241
+ const sessions = this.principalSessions.get(key);
242
+ sessions?.delete(sessionId);
243
+ if (!sessions?.size)
244
+ this.principalSessions.delete(key);
245
+ if (this.principalActiveSessions.get(key) === sessionId) {
246
+ const next = sessions?.values().next().value;
247
+ if (next)
248
+ this.principalActiveSessions.set(key, next);
249
+ else
250
+ this.principalActiveSessions.delete(key);
251
+ }
252
+ }
253
+ countEntries() {
254
+ return this.pairCodes.size + this.bindings.size + this.locales.size +
255
+ this.authorizedPrincipals.size + this.requests.size + this.menuPayloads.size +
256
+ this.xchangeFileMetas.size + this.browserAttachments.size + this.browserRecordings.size +
257
+ this.projectMenuViews.size + this.outgoingNotices.size + (this.gatewayClientUuid ? 1 : 0);
258
+ }
259
+ }
260
+ exports.ProcessLocalStateStore = ProcessLocalStateStore;
@@ -29,7 +29,7 @@ class MinioExchangeStore {
29
29
  terminalConfig;
30
30
  exchangeDirName;
31
31
  logger;
32
- constructor(_callBroker, _bindingStore, terminalConfig, exchangeDirName, _vfsScope, logger, _distributedMode = "client", _gatewayPublicUrl, _gatewayAuthToken) {
32
+ constructor(terminalConfig, exchangeDirName, logger) {
33
33
  this.terminalConfig = terminalConfig;
34
34
  this.exchangeDirName = exchangeDirName;
35
35
  this.logger = logger;
@@ -218,7 +218,7 @@ class TelegramTransport {
218
218
  }
219
219
  isAdminAuthEnabled() {
220
220
  return (this.isAdminBotProfile() &&
221
- Boolean(this.config.distributed.gatewayToken?.trim()));
221
+ Boolean(this.config.distributed.gatewayScopeToken?.trim()));
222
222
  }
223
223
  isAdminBotProfile() {
224
224
  return this.config.distributed.mode === "gateway";
@@ -26,8 +26,8 @@ class TransportConsoleRegistry {
26
26
  ...(input?.principal
27
27
  ? { telegram_user_id: input.principal.telegramUserId }
28
28
  : {}),
29
- ...(this.host.config.distributed.gatewayToken
30
- ? { gateway_token: this.host.config.distributed.gatewayToken }
29
+ ...(this.host.config.distributed.gatewayScopeToken
30
+ ? { gateway_token: this.host.config.distributed.gatewayScopeToken }
31
31
  : {}),
32
32
  });
33
33
  return Array.isArray(response.sessions) ? response.sessions : [];
@@ -73,7 +73,7 @@ class TransportMessageFlow {
73
73
  await this.host.replyText(ctx, await this.host.tForContext(ctx, "menu:admin.auth.disabled"), { sessionId: "gateway-auth", kind: "transport" });
74
74
  return true;
75
75
  }
76
- if (authToken !== this.host.config.distributed.gatewayToken) {
76
+ if (authToken !== this.host.config.distributed.gatewayScopeToken) {
77
77
  await this.host.replyText(ctx, await this.host.tForContext(ctx, "menu:admin.auth.invalid"), { sessionId: "gateway-auth", kind: "transport" });
78
78
  return true;
79
79
  }
@@ -46,8 +46,8 @@ class TransportProjectState {
46
46
  this.host.config.telegram.botUsername ||
47
47
  "telegram-mcp client",
48
48
  bot_username: this.host.config.telegram.botUsername,
49
- ...(this.host.config.distributed.gatewayToken
50
- ? { gateway_token: this.host.config.distributed.gatewayToken }
49
+ ...(this.host.config.distributed.gatewayScopeToken
50
+ ? { gateway_token: this.host.config.distributed.gatewayScopeToken }
51
51
  : {}),
52
52
  meta: {
53
53
  telegram_chat_id: principal.telegramChatId,
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.normalizeGatewayToken = normalizeGatewayToken;
4
- exports.gatewayTokenToScopeKey = gatewayTokenToScopeKey;
4
+ exports.gatewayScopeTokenToScopeKey = gatewayScopeTokenToScopeKey;
5
5
  exports.resolveGatewayScopeKey = resolveGatewayScopeKey;
6
6
  const node_crypto_1 = require("node:crypto");
7
7
  function normalizeGatewayToken(value) {
@@ -11,13 +11,13 @@ function normalizeGatewayToken(value) {
11
11
  const token = value.trim();
12
12
  return token ? token : null;
13
13
  }
14
- function gatewayTokenToScopeKey(token) {
14
+ function gatewayScopeTokenToScopeKey(token) {
15
15
  return (0, node_crypto_1.createHash)("sha256").update(token).digest("hex");
16
16
  }
17
17
  function resolveGatewayScopeKey(input) {
18
- const gatewayToken = normalizeGatewayToken(input.gateway_token);
19
- if (gatewayToken) {
20
- return gatewayTokenToScopeKey(gatewayToken);
18
+ const gatewayScopeToken = normalizeGatewayToken(input.gateway_token);
19
+ if (gatewayScopeToken) {
20
+ return gatewayScopeTokenToScopeKey(gatewayScopeToken);
21
21
  }
22
22
  const scopeKey = normalizeGatewayToken(input.scope_key);
23
23
  return scopeKey;
@@ -117,6 +117,10 @@ function readSessionMarkerState(inputCwd, logger) {
117
117
  parsed.last_notified_tools_hash.trim()
118
118
  ? { lastNotifiedToolsHash: parsed.last_notified_tools_hash.trim() }
119
119
  : {}),
120
+ ...(typeof parsed.gateway_client_uuid === "string" &&
121
+ parsed.gateway_client_uuid.trim()
122
+ ? { gatewayClientUuid: parsed.gateway_client_uuid.trim() }
123
+ : {}),
120
124
  ...(typeof parsed.updated_at === "string" && parsed.updated_at.trim()
121
125
  ? { updatedAt: parsed.updated_at.trim() }
122
126
  : {}),
@@ -166,6 +170,12 @@ function writeSessionMarkerState(input) {
166
170
  : current?.lastNotifiedToolsHash
167
171
  ? { last_notified_tools_hash: current.lastNotifiedToolsHash }
168
172
  : {}),
173
+ ...(typeof input.gatewayClientUuid === "string" &&
174
+ input.gatewayClientUuid.trim()
175
+ ? { gateway_client_uuid: input.gatewayClientUuid.trim() }
176
+ : current?.gatewayClientUuid
177
+ ? { gateway_client_uuid: current.gatewayClientUuid }
178
+ : {}),
169
179
  created_at: now,
170
180
  updated_at: now,
171
181
  ...(current ? { first_seen_local_session_id: current.localSessionId } : {}),
@@ -24,7 +24,18 @@
24
24
  ## 1. Установка
25
25
 
26
26
  ```bash
27
- npm install -g @deadragdoll/tellymcp
27
+ sudo apt install -y python3 make g++
28
+ npm config set ignore-scripts false
29
+ npm install -g @deadragdoll/tellymcp --foreground-scripts
30
+ ```
31
+
32
+ На Linux native addon `node-pty` собирается локально; для Linux ARM64 это
33
+ обязательно, потому что зависимость не поставляет подходящий готовый binary.
34
+ Если после предыдущей установки `pty.node` отсутствует, выполни:
35
+
36
+ ```bash
37
+ npm uninstall -g @deadragdoll/tellymcp
38
+ npm install -g @deadragdoll/tellymcp@latest --foreground-scripts
28
39
  ```
29
40
 
30
41
  Опционально:
@@ -64,7 +75,7 @@ DB_PASSWORD=
64
75
  DB_NAME=
65
76
  GATEWAY_PUBLIC_URL=https://your-domain.example/api/gateway
66
77
  GATEWAY_WS_URL=wss://your-domain.example/api/gateway/ws
67
- GATEWAY_TOKEN=change_me_gateway_token
78
+ GATEWAY_SCOPE_TOKEN=change_me_scope_token
68
79
  GATEWAY_AUTH_TOKEN=put_strong_shared_transport_token_here
69
80
  ROOT_PREFIX=/api
70
81
  PORT=8080
@@ -83,13 +94,16 @@ DISTRIBUTED_MODE=gateway
83
94
  DISTRIBUTED_MODE=client
84
95
  GATEWAY_PUBLIC_URL=https://your-domain.example/api/gateway
85
96
  GATEWAY_WS_URL=wss://your-domain.example/api/gateway/ws
86
- GATEWAY_TOKEN=change_me_gateway_token
97
+ GATEWAY_SCOPE_TOKEN=change_me_scope_token
87
98
  GATEWAY_AUTH_TOKEN=put_strong_shared_transport_token_here
88
99
  GATEWAY_USER_UUID=put_owner_uuid_here
89
100
  ```
90
101
 
102
+ На машине agent/client Redis не нужен. Временное runtime-состояние хранится
103
+ локально, а стабильный gateway client UUID — в `.mcpsession.json`.
104
+
91
105
  Используйте один и тот же стойкий `GATEWAY_AUTH_TOKEN` на gateway и всех клиентах.
92
- Не смешивайте его с `GATEWAY_TOKEN`: последний разделяет данные gateway по scope,
106
+ Не смешивайте его с `GATEWAY_SCOPE_TOKEN`: последний разделяет данные gateway по scope,
93
107
  но не аутентифицирует HTTP- или WebSocket-транспорт. Сгенерируйте токен один раз,
94
108
  например командой `openssl rand -hex 32`, и не используйте пример значения выше.
95
109
 
@@ -192,10 +206,22 @@ http://127.0.0.1:8787/mcp
192
206
 
193
207
  ## 9. Проверки
194
208
 
209
+ Настройка dotenv через локальный web-конфигуратор:
210
+
211
+ ```bash
212
+ tellymcp configure
213
+ ```
214
+
195
215
  ```bash
196
216
  tellymcp doctor --env .env
197
217
  ```
198
218
 
219
+ Перед запуском старый env можно нормализовать командой:
220
+
221
+ ```bash
222
+ tellymcp migrate-env ./old.env > ./.migrated-env
223
+ ```
224
+
199
225
  Разрушительная очистка:
200
226
 
201
227
  ```bash
@@ -24,7 +24,18 @@ The recommended topology is:
24
24
  ## 1. Install
25
25
 
26
26
  ```bash
27
- npm install -g @deadragdoll/tellymcp
27
+ sudo apt install -y python3 make g++
28
+ npm config set ignore-scripts false
29
+ npm install -g @deadragdoll/tellymcp --foreground-scripts
30
+ ```
31
+
32
+ Linux installs compile the native `node-pty` addon locally; this is required on
33
+ Linux ARM64 because the dependency does not ship a matching prebuilt binary.
34
+ If `pty.node` is missing after an earlier install, run:
35
+
36
+ ```bash
37
+ npm uninstall -g @deadragdoll/tellymcp
38
+ npm install -g @deadragdoll/tellymcp@latest --foreground-scripts
28
39
  ```
29
40
 
30
41
  Optional:
@@ -64,7 +75,7 @@ DB_PASSWORD=
64
75
  DB_NAME=
65
76
  GATEWAY_PUBLIC_URL=https://your-domain.example/api/gateway
66
77
  GATEWAY_WS_URL=wss://your-domain.example/api/gateway/ws
67
- GATEWAY_TOKEN=change_me_gateway_token
78
+ GATEWAY_SCOPE_TOKEN=change_me_scope_token
68
79
  GATEWAY_AUTH_TOKEN=put_strong_shared_transport_token_here
69
80
  ROOT_PREFIX=/api
70
81
  PORT=8080
@@ -83,13 +94,16 @@ Minimum client settings:
83
94
  DISTRIBUTED_MODE=client
84
95
  GATEWAY_PUBLIC_URL=https://your-domain.example/api/gateway
85
96
  GATEWAY_WS_URL=wss://your-domain.example/api/gateway/ws
86
- GATEWAY_TOKEN=change_me_gateway_token
97
+ GATEWAY_SCOPE_TOKEN=change_me_scope_token
87
98
  GATEWAY_AUTH_TOKEN=put_strong_shared_transport_token_here
88
99
  GATEWAY_USER_UUID=put_owner_uuid_here
89
100
  ```
90
101
 
102
+ The agent/client machine does not need Redis. Its transient runtime state is
103
+ local, while the stable gateway client UUID is stored in `.mcpsession.json`.
104
+
91
105
  Use the same strong `GATEWAY_AUTH_TOKEN` on the gateway and every client. Keep it
92
- separate from `GATEWAY_TOKEN`, which scopes gateway data but does not authenticate
106
+ separate from `GATEWAY_SCOPE_TOKEN`, which scopes gateway data but does not authenticate
93
107
  the HTTP or WebSocket transport. Generate it once, for example with
94
108
  `openssl rand -hex 32`, and do not use the illustrative value above.
95
109
 
@@ -192,10 +206,22 @@ Use the MCP HTTP endpoint exposed by `tellymcp run`.
192
206
 
193
207
  ## 9. Health Checks
194
208
 
209
+ Guided dotenv setup:
210
+
211
+ ```bash
212
+ tellymcp configure
213
+ ```
214
+
195
215
  ```bash
196
216
  tellymcp doctor --env .env
197
217
  ```
198
218
 
219
+ Normalize a legacy env before startup:
220
+
221
+ ```bash
222
+ tellymcp migrate-env ./old.env > ./.migrated-env
223
+ ```
224
+
199
225
  Destructive cleanup:
200
226
 
201
227
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deadragdoll/tellymcp",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "description": "TellyMCP - Telegram control plane for MCP-connected coding agents",
5
5
  "main": "dist/services/features/telegram-mcp/runtime.service.js",
6
6
  "bin": {
@@ -15,11 +15,46 @@ function line(value = "") {
15
15
  process.stdout.write(`${value}\n`);
16
16
  }
17
17
 
18
+ function checkNativePty() {
19
+ try {
20
+ const nodePty = require("node-pty");
21
+ return typeof nodePty.spawn === "function"
22
+ ? { available: true }
23
+ : {
24
+ available: false,
25
+ message: "node-pty loaded without a spawn function.",
26
+ };
27
+ } catch (error) {
28
+ const message = error instanceof Error ? error.message : String(error);
29
+ return {
30
+ available: false,
31
+ message: message.split("\n", 1)[0] || "node-pty failed to load.",
32
+ };
33
+ }
34
+ }
35
+
18
36
  line();
19
37
  line(`${pc.bold(pc.cyan("TellyMCP"))} ${pc.dim("installed")}`);
20
38
  line();
21
39
 
22
- line(`${pc.green("OK")} Built-in PTY terminal runtime is enabled.`);
40
+ const nativePty = checkNativePty();
41
+ if (nativePty.available) {
42
+ line(`${pc.green("OK")} Built-in PTY native module loaded.`);
43
+ } else {
44
+ line(`${pc.red("ERROR")} Built-in PTY native module is unavailable.`);
45
+ line(` ${nativePty.message}`);
46
+ line(
47
+ ` Platform: ${process.platform}-${process.arch}, Node ${process.versions.node}`,
48
+ );
49
+ if (process.platform === "linux") {
50
+ line(" Debian/Ubuntu prerequisites: sudo apt install -y python3 make g++");
51
+ }
52
+ line(" Ensure scripts are enabled: npm config set ignore-scripts false");
53
+ line(" Remove broken install: npm uninstall -g @deadragdoll/tellymcp");
54
+ line(
55
+ " Reinstall: npm install -g @deadragdoll/tellymcp@latest --foreground-scripts",
56
+ );
57
+ }
23
58
 
24
59
  line();
25
60
  line(`${pc.yellow("INFO")} Browser tools need Playwright browser binaries.`);