@deadragdoll/tellymcp 0.0.13 → 0.0.15

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 (86) hide show
  1. package/.env.example.client +23 -39
  2. package/.env.example.gateway +52 -61
  3. package/CHANGELOG.md +57 -0
  4. package/README-ru.md +80 -6
  5. package/README.md +80 -6
  6. package/TOOLS.md +219 -10
  7. package/config/templates/env.both.template +19 -7
  8. package/config/templates/env.client.template +9 -19
  9. package/config/templates/env.gateway.template +19 -31
  10. package/dist/cli.js +252 -47
  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/browser.service.js +18 -0
  16. package/dist/services/features/telegram-mcp/collaboration.service.js +2 -2
  17. package/dist/services/features/telegram-mcp/ensuredb.service.js +1 -1
  18. package/dist/services/features/telegram-mcp/file-content.service.js +94 -0
  19. package/dist/services/features/telegram-mcp/gateway-delivery.service.js +5 -1
  20. package/dist/services/features/telegram-mcp/gateway-socket.service.js +43 -11
  21. package/dist/services/features/telegram-mcp/gateway.service.js +1 -1
  22. package/dist/services/features/telegram-mcp/mcp-http.service.js +1 -0
  23. package/dist/services/features/telegram-mcp/mcp-server.service.js +20 -0
  24. package/dist/services/features/telegram-mcp/session-context.service.js +25 -1
  25. package/dist/services/features/telegram-mcp/src/app/bootstrap/runtime.js +79 -67
  26. package/dist/services/features/telegram-mcp/src/app/config/env.js +129 -38
  27. package/dist/services/features/telegram-mcp/src/app/config/environmentContract.js +66 -0
  28. package/dist/services/features/telegram-mcp/src/app/http.js +139 -99
  29. package/dist/services/features/telegram-mcp/src/app/oauthFacade.js +642 -0
  30. package/dist/services/features/telegram-mcp/src/app/webapp/assets.js +151 -170
  31. package/dist/services/features/telegram-mcp/src/app/webapp/auth.js +96 -99
  32. package/dist/services/features/telegram-mcp/src/entities/request/model/schema.js +88 -19
  33. package/dist/services/features/telegram-mcp/src/features/browser/model/browserAttachActiveTabTool.js +28 -0
  34. package/dist/services/features/telegram-mcp/src/features/browser/model/browserAttachTabTool.js +28 -0
  35. package/dist/services/features/telegram-mcp/src/features/browser/model/browserDetachTabTool.js +28 -0
  36. package/dist/services/features/telegram-mcp/src/features/browser/model/browserListAttachedInstancesTool.js +1 -1
  37. package/dist/services/features/telegram-mcp/src/features/browser/model/browserListTabsTool.js +1 -1
  38. package/dist/services/features/telegram-mcp/src/features/browser/model/browserService.js +412 -29
  39. package/dist/services/features/telegram-mcp/src/features/browser-attach/model/browserRecordingBundle.js +37 -3
  40. package/dist/services/features/telegram-mcp/src/features/browser-attach/model/firefoxAttachRegistry.js +7 -6
  41. package/dist/services/features/telegram-mcp/src/features/browser-attach/model/firefoxAttachServer.js +214 -37
  42. package/dist/services/features/telegram-mcp/src/features/browser-attach/model/types.js +186 -0
  43. package/dist/services/features/telegram-mcp/src/features/collaboration/model/collaborationService.js +2 -0
  44. package/dist/services/features/telegram-mcp/src/features/collaboration/model/gatewaySessionsService.js +5 -5
  45. package/dist/services/features/telegram-mcp/src/features/collaboration/model/sendPartnerFileService.js +6 -3
  46. package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayClientAccess.js +5 -2
  47. package/dist/services/features/telegram-mcp/src/features/distributed-client/model/gatewayCollaborationBackend.js +4 -4
  48. package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +33 -36
  49. package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/remoteConsoleActionClient.js +4 -3
  50. package/dist/services/features/telegram-mcp/src/features/file-content/model/getFileListTool.js +33 -0
  51. package/dist/services/features/telegram-mcp/src/features/file-content/model/getFileService.js +327 -0
  52. package/dist/services/features/telegram-mcp/src/features/file-content/model/getFileTool.js +81 -0
  53. package/dist/services/features/telegram-mcp/src/features/file-content/model/temporaryFileLinkStore.js +307 -0
  54. package/dist/services/features/telegram-mcp/src/features/file-content/model/workspaceFilePolicy.js +115 -0
  55. package/dist/services/features/telegram-mcp/src/features/notify/model/notifyService.js +9 -5
  56. package/dist/services/features/telegram-mcp/src/features/session-context/model/getRuntimeDiagnosticsTool.js +30 -0
  57. package/dist/services/features/telegram-mcp/src/features/session-context/model/sessionContextService.js +169 -7
  58. package/dist/services/features/telegram-mcp/src/shared/integrations/memory/processLocalStateStore.js +260 -0
  59. package/dist/services/features/telegram-mcp/src/shared/integrations/object-storage/minioExchangeStore.js +1 -1
  60. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +4 -1
  61. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportConsoleRegistry.js +2 -2
  62. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportFileHandoffActions.js +6 -3
  63. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportMessageFlow.js +1 -1
  64. package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transportProjectState.js +2 -2
  65. package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/client.js +29 -6
  66. package/dist/services/features/telegram-mcp/src/shared/integrations/terminal/ptyRegistry.js +100 -2
  67. package/dist/services/features/telegram-mcp/src/shared/lib/bodyLimits.js +63 -0
  68. package/dist/services/features/telegram-mcp/src/shared/lib/gatewayAuth.js +13 -0
  69. package/dist/services/features/telegram-mcp/src/shared/lib/gatewayScope.js +5 -5
  70. package/dist/services/features/telegram-mcp/src/shared/lib/project-identity/projectIdentity.js +10 -0
  71. package/dist/services/features/telegram-mcp/src/shared/lib/time/localTimestamp.js +21 -0
  72. package/docs/CHAT_CONNECTOR.md +134 -0
  73. package/docs/STANDALONE-ru.md +41 -3
  74. package/docs/STANDALONE.md +41 -3
  75. package/package.json +5 -3
  76. package/packages/chrome-attach-extension/dist/background.js +572 -163
  77. package/packages/chrome-attach-extension/dist/manifest.json +2 -1
  78. package/packages/chrome-attach-extension/dist/options.js +15 -2
  79. package/packages/chrome-attach-extension/dist/recorder-content.js +14 -1
  80. package/packages/chrome-attach-extension/dist/recorder-page.js +34 -18
  81. package/packages/firefox-attach-extension/dist/background.js +413 -33
  82. package/packages/firefox-attach-extension/dist/manifest.json +0 -12
  83. package/packages/firefox-attach-extension/dist/options.js +14 -1
  84. package/packages/firefox-attach-extension/dist/recorder-content.js +14 -1
  85. package/packages/firefox-attach-extension/dist/recorder-page.js +34 -18
  86. package/scripts/postinstall.js +33 -1
@@ -15,8 +15,7 @@ REDIS_HOST=127.0.0.1
15
15
  REDIS_PORT=6379
16
16
  REDIS_DB=1
17
17
 
18
- MODE=reject
19
- PAIR_CODE_TTL_SECONDS=300
18
+ TELEGRAM_REQUEST_MODE=reject
20
19
 
21
20
  MCP_HTTP_HOST=127.0.0.1
22
21
  MCP_HTTP_PORT=8787
@@ -25,12 +24,26 @@ MCP_HTTP_PATH=/mcp
25
24
  MCP_HTTP_ENABLE_DEBUG_ROUTES=false
26
25
  MCP_HTTP_ENABLE_PRUNE_ROUTE=false
27
26
 
27
+ # ChatGPT / Claude OAuth connector. Setting any OAuth value enables the facade.
28
+ # TELLYMCP_PUBLIC_URL=https://your-domain.example/api
29
+ # TELLYMCP_OAUTH_ISSUER=https://your-domain.example/api
30
+ # TELLYMCP_OAUTH_AUDIENCE=https://your-domain.example/api
31
+ # TELLYMCP_MAGIC_TOKEN=change_me_private_connector_token
32
+ # TELLYMCP_MAGIC_TOKEN_HASH=sha256:<hex>
33
+ # TELLYMCP_OAUTH_CLIENT_ID=tellymcp
34
+ # TELLYMCP_OAUTH_CLIENT_SECRET=
35
+ # TELLYMCP_ALLOWED_REDIRECT_URIS=https://chatgpt.com/connector/oauth/...,https://claude.ai/api/mcp/auth_callback
36
+ # TELLYMCP_OAUTH_PRIVATE_KEY_PEM="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
37
+ # TELLYMCP_AUTH_CODE_TTL_SECONDS=300
38
+ # TELLYMCP_OAUTH_SCOPES=tellymcp:read tellymcp:write
39
+ # TELLYMCP_OAUTH_KEY_ID=tellymcp-oauth
40
+
28
41
  DISTRIBUTED_MODE=gateway
29
42
  GATEWAY_PUBLIC_URL=
30
43
  GATEWAY_WS_URL=
31
44
  GATEWAY_WS_PATH=/api/gateway/ws
32
- GATEWAY_TOKEN=
33
- # GATEWAY_AUTH_TOKEN=
45
+ GATEWAY_SCOPE_TOKEN=
46
+ GATEWAY_AUTH_TOKEN=
34
47
 
35
48
  ROOT_PREFIX=/api
36
49
  PORT=8080
@@ -42,7 +55,7 @@ DB_PORT=5432
42
55
  DB_USER=
43
56
  DB_PASSWORD=
44
57
  DB_NAME=
45
- DB_SCHEME=mcp
58
+ DB_SCHEMA=mcp
46
59
 
47
60
  # RMQ_HOST=127.0.0.1
48
61
  # RMQ_PORT=5672
@@ -58,41 +71,16 @@ WEBAPP_INITDATA_TTL_SECONDS=300
58
71
  WEBAPP_SESSION_TTL_SECONDS=900
59
72
  WEBAPP_LAUNCH_MODE=fullscreen
60
73
  WEBAPP_VISIBLE_SCREENS=10
61
- WEBAPP_POLL_INTERVAL_MS=2000
62
74
  WEBAPP_ACTION_COOLDOWN_MS=150
63
75
 
64
76
  MCP_XCHANGE_DIR=.mcp-xchange
65
77
 
66
- TERMINAL_SHELL=bash
67
- TERMINAL_COLS=120
68
- TERMINAL_ROWS=40
69
- TERMINAL_SCROLLBACK_LINES=4000
70
-
71
- TERMINAL_NUDGE_ENABLED=true
72
- TERMINAL_NUDGE_DEBOUNCE_SECONDS=10
73
- TERMINAL_NUDGE_COOLDOWN_SECONDS=30
74
- TERMINAL_NUDGE_MESSAGE=получи newest telegram_message через list_xchange_records/get_xchange_record, выполни запрос и ответь человеку через notify_telegram; не останавливайся на анализе
75
- TERMINAL_PARTNER_NUDGE_MESSAGE=получи newest partner_note через list_xchange_records/get_xchange_record, выполни задачу в текущей консоли и обязательно отправь результат через send_partner_note или send_partner_file; только потом mark_xchange_record_read
76
- TERMINAL_PARTNER_REPLY_NUDGE_MESSAGE=получи newest partner_note через list_xchange_records/get_xchange_record; если newest note имеет kind=reply или не требует ответа, не отправляй новый send_partner_note/send_partner_file без явного нового запроса. Если этот reply завершает задачу от человека, сразу отправь финальный результат в Telegram через notify_telegram или send_file_to_telegram; только потом mark_xchange_record_read
77
- TERMINAL_CAPTURE_MODE=visible
78
- TERMINAL_CAPTURE_LINES=300
79
78
  TERMINAL_PROMPT_SCAN_ENABLED=false
80
79
  TERMINAL_PROMPT_SCAN_INTERVAL_SECONDS=15
81
80
  TERMINAL_PROMPT_SCAN_COOLDOWN_SECONDS=120
82
81
  TERMINAL_PROMPT_SCAN_STRATEGY=strict
83
82
  TERMINAL_PROMPT_SCAN_MIN_SCORE=5
84
- BROWSER_ENABLED=false
85
- BROWSER_HEADLESS=false
86
- BROWSER_DEVTOOLS=false
87
- # BROWSER_ADDRESS=http://localhost:5173
88
- BROWSER_TIMEOUT_MS=20000
89
- BROWSER_MAX_EVENTS=200
90
- BROWSER_WAIT_UNTIL=load
91
- # BROWSER_EXECUTABLE_PATH=
92
- # BROWSER_CHANNEL=chrome
93
- BROWSER_SLOW_MO_MS=0
94
-
95
83
  LOG_LEVEL=info
96
84
  LOG_FILE_ENABLED=false
97
85
  LOG_FILE_PATH=.tellymcp/log.jsonl
98
- ENABLE_LOGFEED=0
86
+ LOGFEED_ENABLED=0
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const node_fs_1 = require("node:fs");
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const node_child_process_1 = require("node:child_process");
10
+ const node_crypto_1 = require("node:crypto");
10
11
  const node_net_1 = __importDefault(require("node:net"));
11
12
  const dotenv_1 = require("dotenv");
12
13
  const ioredis_1 = __importDefault(require("ioredis"));
@@ -15,10 +16,44 @@ const ws_1 = __importDefault(require("ws"));
15
16
  const codexPluginInstaller_1 = require("./codexPluginInstaller");
16
17
  const versionHandshake_1 = require("./services/features/telegram-mcp/src/shared/lib/version/versionHandshake");
17
18
  const projectIdentity_1 = require("./services/features/telegram-mcp/src/shared/lib/project-identity/projectIdentity");
18
- const foregroundTerminalRuntime_1 = require("./services/features/telegram-mcp/src/features/foreground-terminal/model/foregroundTerminalRuntime");
19
+ const envMigration_1 = require("./envMigration");
20
+ const configureServer_1 = require("./configureServer");
19
21
  const distDir = __dirname;
20
22
  const packageRoot = node_path_1.default.resolve(distDir, "..");
21
23
  const cliPackageVersion = (0, versionHandshake_1.getTellyMcpPackageVersion)(__dirname);
24
+ function nativePtyRecoveryLines() {
25
+ return [
26
+ ...(process.platform === "linux"
27
+ ? [
28
+ "Install native build prerequisites (Debian/Ubuntu): sudo apt install -y python3 make g++",
29
+ ]
30
+ : []),
31
+ "Ensure npm lifecycle scripts are enabled: npm config set ignore-scripts false",
32
+ "Rebuild the global package: npm rebuild -g @deadragdoll/tellymcp --foreground-scripts",
33
+ ];
34
+ }
35
+ async function checkNativePtyRuntime() {
36
+ try {
37
+ const nodePty = await import("node-pty");
38
+ if (typeof nodePty.spawn !== "function") {
39
+ return {
40
+ available: false,
41
+ message: "node-pty loaded without a spawn function.",
42
+ };
43
+ }
44
+ return { available: true };
45
+ }
46
+ catch (error) {
47
+ const message = error instanceof Error ? error.message : String(error);
48
+ return {
49
+ available: false,
50
+ message: message.split("\n", 1)[0] || "node-pty failed to load.",
51
+ };
52
+ }
53
+ }
54
+ function isForegroundPtyClientMode(parsed) {
55
+ return (parsed.DISTRIBUTED_MODE || "client").trim() === "client";
56
+ }
22
57
  function printBanner(title, subtitle) {
23
58
  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`);
24
59
  if (subtitle) {
@@ -65,6 +100,8 @@ function printHelp() {
65
100
  printBanner("CLI", "Telegram control plane for MCP-connected coding agents");
66
101
  printSection("Usage", [
67
102
  " tellymcp init <client|gateway|both> [directory]",
103
+ " tellymcp configure [--no-open]",
104
+ " tellymcp migrate-env <input.env> > .migrated-env",
68
105
  " tellymcp run [--env <file>]",
69
106
  " tellymcp run --env=<file>",
70
107
  " tellymcp run --env .env-client -s backendDev",
@@ -77,11 +114,14 @@ function printHelp() {
77
114
  " tellymcp codex-plugin install",
78
115
  " tellymcp codex-plugin status",
79
116
  " tellymcp mcp [--url <url>] [--bearer <token>] [--format claude|legacy]",
117
+ " tellymcp oauth key",
80
118
  " tellymcp help",
81
119
  ]);
82
120
  printSection("Examples", [
83
121
  " tellymcp init client",
84
122
  " tellymcp init gateway ./gateway-node",
123
+ " tellymcp configure",
124
+ " tellymcp migrate-env ./old.env > ./new.env",
85
125
  " tellymcp run",
86
126
  " tellymcp run --env .env.client",
87
127
  " tellymcp run --env .env-client -s backendDev",
@@ -94,9 +134,10 @@ function printHelp() {
94
134
  " tellymcp codex-plugin install",
95
135
  " tellymcp codex-plugin status",
96
136
  " tellymcp mcp --help",
137
+ " tellymcp oauth key",
97
138
  ]);
98
139
  printSection("terminal", [
99
- `${picocolors_1.default.green(" OK")} built-in PTY runtime`,
140
+ " built-in PTY runtime (validated by tellymcp doctor and tellymcp run)",
100
141
  " Live view, session nudges and browser flows use the built-in terminal runtime.",
101
142
  ]);
102
143
  }
@@ -107,7 +148,9 @@ function formatMarkerEnvPath(envPath, cwd) {
107
148
  const resolvedCwd = node_path_1.default.resolve(cwd);
108
149
  const resolvedEnvPath = node_path_1.default.resolve(envPath);
109
150
  const relativePath = node_path_1.default.relative(resolvedCwd, resolvedEnvPath);
110
- return relativePath && !relativePath.startsWith("..") && !node_path_1.default.isAbsolute(relativePath)
151
+ return relativePath &&
152
+ !relativePath.startsWith("..") &&
153
+ !node_path_1.default.isAbsolute(relativePath)
111
154
  ? relativePath
112
155
  : resolvedEnvPath;
113
156
  }
@@ -154,7 +197,7 @@ function loadCliEnv(args) {
154
197
  const sessionOverride = explicitSessionOverride ?? marker?.localSessionId ?? null;
155
198
  const sessionLabelOverride = explicitSessionOverride
156
199
  ? explicitSessionOverride
157
- : marker?.sessionLabel ?? sessionOverride ?? null;
200
+ : (marker?.sessionLabel ?? sessionOverride ?? null);
158
201
  if (!(0, node_fs_1.existsSync)(envPath)) {
159
202
  fail(`Missing env file: ${envPath}`);
160
203
  }
@@ -238,11 +281,29 @@ function printMcpHelp() {
238
281
  " tellymcp mcp --url https://builder.undoo.ru/api/mcp --format legacy",
239
282
  ]);
240
283
  }
284
+ function runOAuthCommand(args) {
285
+ const [subcommand] = args;
286
+ if (!subcommand || subcommand === "--help" || subcommand === "-h") {
287
+ printBanner("OAuth helper", "Generate connector signing material");
288
+ printSection("Usage", [" tellymcp oauth key"]);
289
+ printSection("Output", [
290
+ " Prints a dotenv-ready TELLYMCP_OAUTH_PRIVATE_KEY_PEM value.",
291
+ " Store it only on the gateway and never expose it to chat clients.",
292
+ ]);
293
+ return;
294
+ }
295
+ if (subcommand !== "key") {
296
+ fail("Supported OAuth subcommands: key");
297
+ }
298
+ const privateKeyPem = (0, node_crypto_1.generateKeyPairSync)("rsa", { modulusLength: 2048 })
299
+ .privateKey.export({ type: "pkcs8", format: "pem" })
300
+ .toString()
301
+ .replace(/\n/gu, "\\n");
302
+ process.stdout.write(`TELLYMCP_OAUTH_PRIVATE_KEY_PEM="${privateKeyPem}"\n`);
303
+ }
241
304
  function printBrowserHelp() {
242
305
  printBanner("browser helper", "Manage Playwright browser binaries used by browser_* tools");
243
- printSection("Usage", [
244
- " tellymcp browser install",
245
- ]);
306
+ printSection("Usage", [" tellymcp browser install"]);
246
307
  printSection("What this command does", [
247
308
  " Installs the bundled Playwright Chromium browser.",
248
309
  " Uses the Playwright dependency shipped with TellyMCP.",
@@ -300,12 +361,67 @@ function loadTemplate(mode) {
300
361
  : "env.both.template";
301
362
  const templatePath = node_path_1.default.join(packageRoot, templateName);
302
363
  const nestedTemplatePath = node_path_1.default.join(packageRoot, "config", "templates", templateName);
303
- const resolvedTemplatePath = (0, node_fs_1.existsSync)(templatePath) ? templatePath : nestedTemplatePath;
364
+ const resolvedTemplatePath = (0, node_fs_1.existsSync)(templatePath)
365
+ ? templatePath
366
+ : nestedTemplatePath;
304
367
  if (!(0, node_fs_1.existsSync)(resolvedTemplatePath)) {
305
368
  fail(`Missing packaged template: ${templateName}`);
306
369
  }
307
370
  return (0, node_fs_1.readFileSync)(resolvedTemplatePath, "utf8");
308
371
  }
372
+ async function runConfigure(args) {
373
+ if (args.includes("--help") || args.includes("-h")) {
374
+ printBanner("configure", "Local web configurator for a TellyMCP dotenv file");
375
+ printSection("Usage", [
376
+ " tellymcp configure [--no-open]",
377
+ " tellymcp configure [--port 8790] [--no-open]",
378
+ ]);
379
+ printSection("Safety", [
380
+ " The configurator binds only to 127.0.0.1 on a random free port.",
381
+ " Its URL contains a one-time access token.",
382
+ " The browser downloads .env-client or .env-gateway after validation.",
383
+ ]);
384
+ return;
385
+ }
386
+ for (let index = 0; index < args.length; index += 1) {
387
+ const value = args[index];
388
+ if (value === "--no-open" || value?.startsWith("--port=")) {
389
+ continue;
390
+ }
391
+ if (value === "--port") {
392
+ index += 1;
393
+ continue;
394
+ }
395
+ fail("configure does not accept a role or output path; choose Client or Gateway in the browser wizard.");
396
+ }
397
+ const portArg = readFlagValue(args, "--port");
398
+ const port = portArg === null ? 0 : Number(portArg);
399
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
400
+ fail("--port must be an integer between 0 and 65535.");
401
+ }
402
+ printBanner("configure", "Choose Client or Gateway in the local browser wizard");
403
+ await (0, configureServer_1.startConfigureServer)({
404
+ templates: {
405
+ client: loadTemplate("client"),
406
+ gateway: loadTemplate("gateway"),
407
+ },
408
+ port,
409
+ open: !args.includes("--no-open"),
410
+ onListening(url) {
411
+ printSection("Configurator", [
412
+ ` ${picocolors_1.default.green("OPEN")} ${url}`,
413
+ " Keep this terminal open until the file is saved.",
414
+ " Press Ctrl+C to cancel.",
415
+ ]);
416
+ },
417
+ onDownloaded(filename) {
418
+ printSection("Download", [
419
+ ` ${picocolors_1.default.green("OK")} ${filename} sent to the browser`,
420
+ ` next: chmod 600 ${filename}`,
421
+ ]);
422
+ },
423
+ });
424
+ }
309
425
  function initWorkspace(mode, directoryArg) {
310
426
  const targetDir = node_path_1.default.resolve(directoryArg ?? process.cwd());
311
427
  (0, node_fs_1.mkdirSync)(targetDir, { recursive: true });
@@ -331,6 +447,40 @@ function initWorkspace(mode, directoryArg) {
331
447
  " 3. tellymcp run",
332
448
  ]);
333
449
  }
450
+ function runMigrateEnv(args) {
451
+ const [inputArg, extraArg] = args;
452
+ if (!inputArg || extraArg || inputArg === "--help" || inputArg === "-h") {
453
+ printBanner("migrate-env", "Normalize a legacy TellyMCP environment file");
454
+ printSection("Usage", [
455
+ " tellymcp migrate-env <input.env> > .migrated-env",
456
+ " The role is read from DISTRIBUTED_MODE or inferred from gateway-only keys.",
457
+ " Normalized dotenv is written to stdout; migration notes go to stderr.",
458
+ ]);
459
+ if (!inputArg || inputArg === "--help" || inputArg === "-h") {
460
+ return;
461
+ }
462
+ fail("migrate-env expects exactly one input env file.");
463
+ }
464
+ const inputPath = node_path_1.default.resolve(process.cwd(), inputArg);
465
+ if (!(0, node_fs_1.existsSync)(inputPath)) {
466
+ fail(`Missing input env file: ${inputPath}`);
467
+ }
468
+ const result = (0, envMigration_1.migrateEnvironmentContent)((0, node_fs_1.readFileSync)(inputPath, "utf8"));
469
+ process.stdout.write(result.content);
470
+ const notes = [
471
+ `migrate-env: role=${result.role}`,
472
+ `migrate-env: kept ${result.keptKeys.length} keys from ${inputPath}`,
473
+ ];
474
+ if (result.renamedKeys.length > 0) {
475
+ notes.push(`migrate-env: renamed ${result.renamedKeys
476
+ .map(({ from, to }) => `${from}->${to}`)
477
+ .join(", ")}`);
478
+ }
479
+ if (result.droppedKeys.length > 0) {
480
+ notes.push(`migrate-env: dropped ${result.droppedKeys.join(", ")}`);
481
+ }
482
+ process.stderr.write(`${notes.join("\n")}\n`);
483
+ }
334
484
  function resolveRunEnvPath(args) {
335
485
  const [firstArg, secondArg] = args;
336
486
  const marker = getSessionMarkerForCwd(process.cwd());
@@ -401,9 +551,7 @@ function readFlagValue(args, flagName) {
401
551
  return null;
402
552
  }
403
553
  function printMcpConfig(args) {
404
- if (args.length === 0 ||
405
- args.includes("--help") ||
406
- args.includes("-h")) {
554
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
407
555
  printMcpHelp();
408
556
  return;
409
557
  }
@@ -531,11 +679,18 @@ async function checkWebSocketUrl(url, timeoutMs = 3000) {
531
679
  }
532
680
  async function runDoctor(args) {
533
681
  const { envPath, parsed } = loadCliEnv(args);
682
+ const nativePtyStatus = await checkNativePtyRuntime();
534
683
  printBanner("doctor", "Local installation diagnostics");
535
684
  printSection("terminal", [
536
- `${picocolors_1.default.green(" OK")} built-in PTY runtime`,
685
+ nativePtyStatus.available
686
+ ? `${picocolors_1.default.green(" OK")} built-in PTY runtime: native module loaded`
687
+ : `${picocolors_1.default.red(" ERROR")} built-in PTY runtime: ${nativePtyStatus.message}`,
688
+ ` platform: ${process.platform}-${process.arch}, Node ${process.versions.node}`,
537
689
  ` shell: ${parsed.TERMINAL_SHELL?.trim() || process.env.SHELL || "bash"}`,
538
690
  ` size: ${parsed.TERMINAL_COLS?.trim() || "120"}x${parsed.TERMINAL_ROWS?.trim() || "40"}`,
691
+ ...(!nativePtyStatus.available
692
+ ? nativePtyRecoveryLines().map((line) => `${picocolors_1.default.yellow(" ACTION")} ${line}`)
693
+ : []),
539
694
  ]);
540
695
  const mode = (parsed.DISTRIBUTED_MODE || "client").trim();
541
696
  const httpHost = (parsed.MCP_HTTP_HOST || "0.0.0.0").trim();
@@ -594,10 +749,15 @@ async function runDoctor(args) {
594
749
  checks.push(`${picocolors_1.default.red(" ERROR")} playwright chromium: ${playwrightStatus.message}`);
595
750
  capabilities.push(`${picocolors_1.default.red(" ERROR")} browser tools: browsers are not installed`);
596
751
  }
597
- const redisHost = (parsed.REDIS_HOST || "127.0.0.1").trim();
598
- const redisPort = Number(parsed.REDIS_PORT || 6379);
599
- const redisCheck = await checkTcpPort(redisHost, redisPort);
600
- checks.push(`${redisCheck.ok ? picocolors_1.default.green(" OK") : picocolors_1.default.red(" ERROR")} redis: ${redisCheck.message}`);
752
+ if (mode === "client") {
753
+ checks.push(`${picocolors_1.default.green(" OK")} local state: process-local (Redis is not required)`);
754
+ }
755
+ else {
756
+ const redisHost = (parsed.REDIS_HOST || "127.0.0.1").trim();
757
+ const redisPort = Number(parsed.REDIS_PORT || 6379);
758
+ const redisCheck = await checkTcpPort(redisHost, redisPort);
759
+ checks.push(`${redisCheck.ok ? picocolors_1.default.green(" OK") : picocolors_1.default.red(" ERROR")} redis: ${redisCheck.message}`);
760
+ }
601
761
  if (mode === "client") {
602
762
  const gatewayPublicUrl = parsed.GATEWAY_PUBLIC_URL?.trim();
603
763
  if (gatewayPublicUrl) {
@@ -697,9 +857,12 @@ async function runDoctor(args) {
697
857
  printSection("notes", notes);
698
858
  }
699
859
  else {
700
- printSection("notes", [`${picocolors_1.default.green(" OK")} No obvious local config issues detected.`]);
860
+ printSection("notes", [
861
+ `${picocolors_1.default.green(" OK")} No obvious local config issues detected.`,
862
+ ]);
701
863
  }
702
- if (browserEnabled && (!playwrightStatus.enabled || !playwrightStatus.installed)) {
864
+ if (browserEnabled &&
865
+ (!playwrightStatus.enabled || !playwrightStatus.installed)) {
703
866
  printSection("playwright", [
704
867
  `${picocolors_1.default.yellow(" ACTION")} Install browser binaries before using browser_* tools:`,
705
868
  " tellymcp browser install",
@@ -719,7 +882,9 @@ function normalizeExtensionFlavor(rawValue) {
719
882
  return null;
720
883
  }
721
884
  function getBundledExtensionDir(flavor) {
722
- return node_path_1.default.join(packageRoot, "packages", flavor === "firefox" ? "firefox-attach-extension" : "chrome-attach-extension", "dist");
885
+ return node_path_1.default.join(packageRoot, "packages", flavor === "firefox"
886
+ ? "firefox-attach-extension"
887
+ : "chrome-attach-extension", "dist");
723
888
  }
724
889
  function getDefaultExtensionTargetDir(flavor) {
725
890
  return node_path_1.default.resolve(process.cwd(), flavor === "firefox" ? "tellymcp-firefox-attach" : "tellymcp-chrome-attach");
@@ -760,7 +925,7 @@ async function runSystemPrune(args) {
760
925
  const dbUser = parsed.DB_USER?.trim();
761
926
  const dbPassword = parsed.DB_PASSWORD?.trim();
762
927
  const dbName = parsed.DB_NAME?.trim();
763
- const dbSchema = (parsed.DB_SCHEME || "mcp").trim();
928
+ const dbSchema = (parsed.DB_SCHEMA || "mcp").trim();
764
929
  const xchangeDir = node_path_1.default.resolve(process.cwd(), parsed.MCP_XCHANGE_DIR || ".mcp-xchange");
765
930
  const sessionMarkerPath = node_path_1.default.resolve(process.cwd(), ".mcpsession.json");
766
931
  const sqliteDbPath = node_path_1.default.join(xchangeDir, "xchange.sqlite3");
@@ -768,35 +933,39 @@ async function runSystemPrune(args) {
768
933
  printSection("Target", [
769
934
  ` env: ${envPath}`,
770
935
  ` mode: ${mode}`,
771
- ` redis: ${redisHost}:${redisPort}/${redisDb}`,
936
+ ...(mode === "client"
937
+ ? [` state: process-local (Redis skipped)`]
938
+ : [` redis: ${redisHost}:${redisPort}/${redisDb}`]),
772
939
  ...(dbHost && dbUser && dbName
773
940
  ? [` postgres: ${dbHost}:${dbPort}/${dbName} schema ${dbSchema}`]
774
941
  : [` postgres: ${picocolors_1.default.dim("skipped (not configured)")}`]),
775
942
  ` xchange dir: ${xchangeDir}`,
776
943
  ` session marker: ${sessionMarkerPath}`,
777
944
  ]);
778
- const redis = new ioredis_1.default({
779
- host: redisHost,
780
- port: redisPort,
781
- db: redisDb,
782
- ...(redisUsername ? { username: redisUsername } : {}),
783
- ...(redisPassword ? { password: redisPassword } : {}),
784
- maxRetriesPerRequest: 1,
785
- enableReadyCheck: true,
786
- });
787
945
  let deletedRedisKeys = 0;
788
- try {
789
- let cursor = "0";
790
- do {
791
- const [nextCursor, keys] = await redis.scan(cursor, "MATCH", "telegram-mcp:*", "COUNT", 500);
792
- cursor = nextCursor;
793
- if (keys.length > 0) {
794
- deletedRedisKeys += await redis.del(...keys);
795
- }
796
- } while (cursor !== "0");
797
- }
798
- finally {
799
- redis.disconnect();
946
+ if (mode !== "client") {
947
+ const redis = new ioredis_1.default({
948
+ host: redisHost,
949
+ port: redisPort,
950
+ db: redisDb,
951
+ ...(redisUsername ? { username: redisUsername } : {}),
952
+ ...(redisPassword ? { password: redisPassword } : {}),
953
+ maxRetriesPerRequest: 1,
954
+ enableReadyCheck: true,
955
+ });
956
+ try {
957
+ let cursor = "0";
958
+ do {
959
+ const [nextCursor, keys] = await redis.scan(cursor, "MATCH", "telegram-mcp:*", "COUNT", 500);
960
+ cursor = nextCursor;
961
+ if (keys.length > 0) {
962
+ deletedRedisKeys += await redis.del(...keys);
963
+ }
964
+ } while (cursor !== "0");
965
+ }
966
+ finally {
967
+ redis.disconnect();
968
+ }
800
969
  }
801
970
  let truncatedTables = [];
802
971
  if (dbHost && dbUser && dbName) {
@@ -990,8 +1159,19 @@ async function runRuntime(args) {
990
1159
  sessionId: parsed.TELLYMCP_SESSION_ID,
991
1160
  sessionLabel: parsed.TELLYMCP_SESSION_LABEL,
992
1161
  });
993
- if ((0, foregroundTerminalRuntime_1.isForegroundPtyClientMode)(parsed)) {
994
- await (0, foregroundTerminalRuntime_1.runForegroundPtyRuntime)({
1162
+ const nativePtyStatus = await checkNativePtyRuntime();
1163
+ if (!nativePtyStatus.available) {
1164
+ fail([
1165
+ `Built-in PTY runtime is unavailable on ${process.platform}-${process.arch} with Node ${process.versions.node}.`,
1166
+ nativePtyStatus.message,
1167
+ "",
1168
+ ...nativePtyRecoveryLines(),
1169
+ "Then rerun: tellymcp doctor --env <file>",
1170
+ ].join("\n"));
1171
+ }
1172
+ if (isForegroundPtyClientMode(parsed)) {
1173
+ const { runForegroundPtyRuntime } = await import("./services/features/telegram-mcp/src/features/foreground-terminal/model/foregroundTerminalRuntime.js");
1174
+ await runForegroundPtyRuntime({
995
1175
  envPath,
996
1176
  packageRoot,
997
1177
  printBanner,
@@ -1045,11 +1225,24 @@ async function runRuntime(args) {
1045
1225
  }
1046
1226
  async function main(argv) {
1047
1227
  const [rawCommand, firstArg, secondArg] = argv;
1048
- const command = rawCommand === "init" || rawCommand === "run" || rawCommand === "help" || rawCommand === "mcp" || rawCommand === "doctor" || rawCommand === "browser" || rawCommand === "system-prune"
1049
- || rawCommand === "codex-plugin" || rawCommand === "extension"
1228
+ const command = rawCommand === "init" ||
1229
+ rawCommand === "configure" ||
1230
+ rawCommand === "migrate-env" ||
1231
+ rawCommand === "run" ||
1232
+ rawCommand === "help" ||
1233
+ rawCommand === "mcp" ||
1234
+ rawCommand === "oauth" ||
1235
+ rawCommand === "doctor" ||
1236
+ rawCommand === "browser" ||
1237
+ rawCommand === "system-prune" ||
1238
+ rawCommand === "codex-plugin" ||
1239
+ rawCommand === "extension"
1050
1240
  ? rawCommand
1051
1241
  : "help";
1052
- if (command === "help" || !rawCommand || rawCommand === "--help" || rawCommand === "-h") {
1242
+ if (command === "help" ||
1243
+ !rawCommand ||
1244
+ rawCommand === "--help" ||
1245
+ rawCommand === "-h") {
1053
1246
  printHelp();
1054
1247
  return;
1055
1248
  }
@@ -1057,10 +1250,22 @@ async function main(argv) {
1057
1250
  initWorkspace(ensureMode(firstArg), secondArg);
1058
1251
  return;
1059
1252
  }
1253
+ if (command === "configure") {
1254
+ await runConfigure(argv.slice(1));
1255
+ return;
1256
+ }
1257
+ if (command === "migrate-env") {
1258
+ runMigrateEnv(argv.slice(1));
1259
+ return;
1260
+ }
1060
1261
  if (command === "mcp") {
1061
1262
  printMcpConfig(argv.slice(1));
1062
1263
  return;
1063
1264
  }
1265
+ if (command === "oauth") {
1266
+ runOAuthCommand(argv.slice(1));
1267
+ return;
1268
+ }
1064
1269
  if (command === "doctor") {
1065
1270
  await runDoctor(argv.slice(1));
1066
1271
  return;