@deadragdoll/tellymcp 0.0.14 → 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 (44) hide show
  1. package/.env.example.client +19 -39
  2. package/.env.example.gateway +40 -64
  3. package/CHANGELOG.md +27 -0
  4. package/README-ru.md +66 -6
  5. package/README.md +66 -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 +225 -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/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 +29 -4
  42. package/docs/STANDALONE.md +29 -4
  43. package/package.json +1 -1
  44. package/scripts/postinstall.js +33 -1
@@ -19,8 +19,7 @@ REDIS_HOST=127.0.0.1
19
19
  REDIS_PORT=6379
20
20
  REDIS_DB=1
21
21
 
22
- MODE=reject
23
- PAIR_CODE_TTL_SECONDS=300
22
+ TELEGRAM_REQUEST_MODE=reject
24
23
 
25
24
  MCP_HTTP_HOST=127.0.0.1
26
25
  MCP_HTTP_PORT=8787
@@ -47,7 +46,7 @@ DISTRIBUTED_MODE=both
47
46
  GATEWAY_PUBLIC_URL=
48
47
  GATEWAY_WS_URL=
49
48
  GATEWAY_WS_PATH=/api/gateway/ws
50
- GATEWAY_TOKEN=
49
+ GATEWAY_SCOPE_TOKEN=
51
50
  GATEWAY_USER_UUID=
52
51
  GATEWAY_AUTH_TOKEN=
53
52
 
@@ -61,7 +60,7 @@ DB_PORT=5432
61
60
  DB_USER=
62
61
  DB_PASSWORD=
63
62
  DB_NAME=
64
- DB_SCHEME=mcp
63
+ DB_SCHEMA=mcp
65
64
 
66
65
  # RMQ_HOST=127.0.0.1
67
66
  # RMQ_PORT=5672
@@ -113,4 +112,4 @@ BROWSER_SLOW_MO_MS=0
113
112
  LOG_LEVEL=info
114
113
  LOG_FILE_ENABLED=false
115
114
  LOG_FILE_PATH=.tellymcp/log.jsonl
116
- ENABLE_LOGFEED=0
115
+ LOGFEED_ENABLED=0
@@ -4,12 +4,8 @@
4
4
  # TELLYMCP_SESSION_ID=NEW
5
5
  # TELLYMCP_SESSION_LABEL=NEW
6
6
 
7
- REDIS_HOST=127.0.0.1
8
- REDIS_PORT=6379
9
- REDIS_DB=1
10
-
11
- MODE=reject
12
- PAIR_CODE_TTL_SECONDS=300
7
+ # Client state is process-local. Redis is gateway-only.
8
+ TELEGRAM_REQUEST_MODE=reject
13
9
 
14
10
  MCP_HTTP_HOST=127.0.0.1
15
11
  MCP_HTTP_PORT=8787
@@ -22,7 +18,7 @@ DISTRIBUTED_MODE=client
22
18
  GATEWAY_PUBLIC_URL=
23
19
  GATEWAY_WS_URL=
24
20
  GATEWAY_WS_PATH=/api/gateway/ws
25
- GATEWAY_TOKEN=
21
+ GATEWAY_SCOPE_TOKEN=
26
22
  GATEWAY_USER_UUID=
27
23
  GATEWAY_AUTH_TOKEN=
28
24
 
@@ -44,15 +40,6 @@ TERMINAL_PROMPT_SCAN_INTERVAL_SECONDS=15
44
40
  TERMINAL_PROMPT_SCAN_COOLDOWN_SECONDS=120
45
41
  TERMINAL_PROMPT_SCAN_STRATEGY=strict
46
42
  TERMINAL_PROMPT_SCAN_MIN_SCORE=5
47
- WEBAPP_ENABLED=true
48
- WEBAPP_BASE_PATH=/webapp
49
- WEBAPP_PUBLIC_URL=
50
- WEBAPP_INITDATA_TTL_SECONDS=300
51
- WEBAPP_SESSION_TTL_SECONDS=900
52
- WEBAPP_LAUNCH_MODE=fullscreen
53
- WEBAPP_VISIBLE_SCREENS=10
54
- WEBAPP_ACTION_COOLDOWN_MS=150
55
-
56
43
  MCP_XCHANGE_DIR=.mcp-xchange
57
44
 
58
45
  BROWSER_ENABLED=true
@@ -76,4 +63,4 @@ NODE_ID=agent
76
63
  LOG_LEVEL=info
77
64
  LOG_FILE_ENABLED=false
78
65
  LOG_FILE_PATH=.tellymcp/log.jsonl
79
- ENABLE_LOGFEED=0
66
+ LOGFEED_ENABLED=0
@@ -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
@@ -43,7 +42,7 @@ DISTRIBUTED_MODE=gateway
43
42
  GATEWAY_PUBLIC_URL=
44
43
  GATEWAY_WS_URL=
45
44
  GATEWAY_WS_PATH=/api/gateway/ws
46
- GATEWAY_TOKEN=
45
+ GATEWAY_SCOPE_TOKEN=
47
46
  GATEWAY_AUTH_TOKEN=
48
47
 
49
48
  ROOT_PREFIX=/api
@@ -56,7 +55,7 @@ DB_PORT=5432
56
55
  DB_USER=
57
56
  DB_PASSWORD=
58
57
  DB_NAME=
59
- DB_SCHEME=mcp
58
+ DB_SCHEMA=mcp
60
59
 
61
60
  # RMQ_HOST=127.0.0.1
62
61
  # RMQ_PORT=5672
@@ -76,36 +75,12 @@ WEBAPP_ACTION_COOLDOWN_MS=150
76
75
 
77
76
  MCP_XCHANGE_DIR=.mcp-xchange
78
77
 
79
- TERMINAL_SHELL=bash
80
- TERMINAL_COLS=120
81
- TERMINAL_ROWS=40
82
- TERMINAL_SCROLLBACK_LINES=4000
83
-
84
- TERMINAL_NUDGE_ENABLED=true
85
- TERMINAL_NUDGE_DEBOUNCE_SECONDS=10
86
- TERMINAL_NUDGE_COOLDOWN_SECONDS=30
87
- TERMINAL_NUDGE_MESSAGE=получи newest telegram_message через list_xchange_records/get_xchange_record, выполни запрос и ответь человеку через notify_telegram; не останавливайся на анализе
88
- TERMINAL_PARTNER_NUDGE_MESSAGE=получи newest partner_note через list_xchange_records/get_xchange_record, выполни задачу в текущей консоли и обязательно отправь результат через send_partner_note или send_partner_file; только потом mark_xchange_record_read
89
- 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
90
- TERMINAL_CAPTURE_MODE=visible
91
- TERMINAL_CAPTURE_LINES=300
92
78
  TERMINAL_PROMPT_SCAN_ENABLED=false
93
79
  TERMINAL_PROMPT_SCAN_INTERVAL_SECONDS=15
94
80
  TERMINAL_PROMPT_SCAN_COOLDOWN_SECONDS=120
95
81
  TERMINAL_PROMPT_SCAN_STRATEGY=strict
96
82
  TERMINAL_PROMPT_SCAN_MIN_SCORE=5
97
- BROWSER_ENABLED=false
98
- BROWSER_HEADLESS=false
99
- BROWSER_DEVTOOLS=false
100
- # BROWSER_ADDRESS=http://localhost:5173
101
- BROWSER_TIMEOUT_MS=20000
102
- BROWSER_MAX_EVENTS=200
103
- BROWSER_WAIT_UNTIL=load
104
- # BROWSER_EXECUTABLE_PATH=
105
- # BROWSER_CHANNEL=chrome
106
- BROWSER_SLOW_MO_MS=0
107
-
108
83
  LOG_LEVEL=info
109
84
  LOG_FILE_ENABLED=false
110
85
  LOG_FILE_PATH=.tellymcp/log.jsonl
111
- ENABLE_LOGFEED=0
86
+ LOGFEED_ENABLED=0
package/dist/cli.js CHANGED
@@ -16,10 +16,44 @@ const ws_1 = __importDefault(require("ws"));
16
16
  const codexPluginInstaller_1 = require("./codexPluginInstaller");
17
17
  const versionHandshake_1 = require("./services/features/telegram-mcp/src/shared/lib/version/versionHandshake");
18
18
  const projectIdentity_1 = require("./services/features/telegram-mcp/src/shared/lib/project-identity/projectIdentity");
19
- 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");
20
21
  const distDir = __dirname;
21
22
  const packageRoot = node_path_1.default.resolve(distDir, "..");
22
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
+ }
23
57
  function printBanner(title, subtitle) {
24
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`);
25
59
  if (subtitle) {
@@ -66,6 +100,8 @@ function printHelp() {
66
100
  printBanner("CLI", "Telegram control plane for MCP-connected coding agents");
67
101
  printSection("Usage", [
68
102
  " tellymcp init <client|gateway|both> [directory]",
103
+ " tellymcp configure [--no-open]",
104
+ " tellymcp migrate-env <input.env> > .migrated-env",
69
105
  " tellymcp run [--env <file>]",
70
106
  " tellymcp run --env=<file>",
71
107
  " tellymcp run --env .env-client -s backendDev",
@@ -84,6 +120,8 @@ function printHelp() {
84
120
  printSection("Examples", [
85
121
  " tellymcp init client",
86
122
  " tellymcp init gateway ./gateway-node",
123
+ " tellymcp configure",
124
+ " tellymcp migrate-env ./old.env > ./new.env",
87
125
  " tellymcp run",
88
126
  " tellymcp run --env .env.client",
89
127
  " tellymcp run --env .env-client -s backendDev",
@@ -99,7 +137,7 @@ function printHelp() {
99
137
  " tellymcp oauth key",
100
138
  ]);
101
139
  printSection("terminal", [
102
- `${picocolors_1.default.green(" OK")} built-in PTY runtime`,
140
+ " built-in PTY runtime (validated by tellymcp doctor and tellymcp run)",
103
141
  " Live view, session nudges and browser flows use the built-in terminal runtime.",
104
142
  ]);
105
143
  }
@@ -110,7 +148,9 @@ function formatMarkerEnvPath(envPath, cwd) {
110
148
  const resolvedCwd = node_path_1.default.resolve(cwd);
111
149
  const resolvedEnvPath = node_path_1.default.resolve(envPath);
112
150
  const relativePath = node_path_1.default.relative(resolvedCwd, resolvedEnvPath);
113
- return relativePath && !relativePath.startsWith("..") && !node_path_1.default.isAbsolute(relativePath)
151
+ return relativePath &&
152
+ !relativePath.startsWith("..") &&
153
+ !node_path_1.default.isAbsolute(relativePath)
114
154
  ? relativePath
115
155
  : resolvedEnvPath;
116
156
  }
@@ -157,7 +197,7 @@ function loadCliEnv(args) {
157
197
  const sessionOverride = explicitSessionOverride ?? marker?.localSessionId ?? null;
158
198
  const sessionLabelOverride = explicitSessionOverride
159
199
  ? explicitSessionOverride
160
- : marker?.sessionLabel ?? sessionOverride ?? null;
200
+ : (marker?.sessionLabel ?? sessionOverride ?? null);
161
201
  if (!(0, node_fs_1.existsSync)(envPath)) {
162
202
  fail(`Missing env file: ${envPath}`);
163
203
  }
@@ -263,9 +303,7 @@ function runOAuthCommand(args) {
263
303
  }
264
304
  function printBrowserHelp() {
265
305
  printBanner("browser helper", "Manage Playwright browser binaries used by browser_* tools");
266
- printSection("Usage", [
267
- " tellymcp browser install",
268
- ]);
306
+ printSection("Usage", [" tellymcp browser install"]);
269
307
  printSection("What this command does", [
270
308
  " Installs the bundled Playwright Chromium browser.",
271
309
  " Uses the Playwright dependency shipped with TellyMCP.",
@@ -323,12 +361,67 @@ function loadTemplate(mode) {
323
361
  : "env.both.template";
324
362
  const templatePath = node_path_1.default.join(packageRoot, templateName);
325
363
  const nestedTemplatePath = node_path_1.default.join(packageRoot, "config", "templates", templateName);
326
- const resolvedTemplatePath = (0, node_fs_1.existsSync)(templatePath) ? templatePath : nestedTemplatePath;
364
+ const resolvedTemplatePath = (0, node_fs_1.existsSync)(templatePath)
365
+ ? templatePath
366
+ : nestedTemplatePath;
327
367
  if (!(0, node_fs_1.existsSync)(resolvedTemplatePath)) {
328
368
  fail(`Missing packaged template: ${templateName}`);
329
369
  }
330
370
  return (0, node_fs_1.readFileSync)(resolvedTemplatePath, "utf8");
331
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
+ }
332
425
  function initWorkspace(mode, directoryArg) {
333
426
  const targetDir = node_path_1.default.resolve(directoryArg ?? process.cwd());
334
427
  (0, node_fs_1.mkdirSync)(targetDir, { recursive: true });
@@ -354,6 +447,40 @@ function initWorkspace(mode, directoryArg) {
354
447
  " 3. tellymcp run",
355
448
  ]);
356
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
+ }
357
484
  function resolveRunEnvPath(args) {
358
485
  const [firstArg, secondArg] = args;
359
486
  const marker = getSessionMarkerForCwd(process.cwd());
@@ -424,9 +551,7 @@ function readFlagValue(args, flagName) {
424
551
  return null;
425
552
  }
426
553
  function printMcpConfig(args) {
427
- if (args.length === 0 ||
428
- args.includes("--help") ||
429
- args.includes("-h")) {
554
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
430
555
  printMcpHelp();
431
556
  return;
432
557
  }
@@ -554,11 +679,18 @@ async function checkWebSocketUrl(url, timeoutMs = 3000) {
554
679
  }
555
680
  async function runDoctor(args) {
556
681
  const { envPath, parsed } = loadCliEnv(args);
682
+ const nativePtyStatus = await checkNativePtyRuntime();
557
683
  printBanner("doctor", "Local installation diagnostics");
558
684
  printSection("terminal", [
559
- `${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}`,
560
689
  ` shell: ${parsed.TERMINAL_SHELL?.trim() || process.env.SHELL || "bash"}`,
561
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
+ : []),
562
694
  ]);
563
695
  const mode = (parsed.DISTRIBUTED_MODE || "client").trim();
564
696
  const httpHost = (parsed.MCP_HTTP_HOST || "0.0.0.0").trim();
@@ -617,10 +749,15 @@ async function runDoctor(args) {
617
749
  checks.push(`${picocolors_1.default.red(" ERROR")} playwright chromium: ${playwrightStatus.message}`);
618
750
  capabilities.push(`${picocolors_1.default.red(" ERROR")} browser tools: browsers are not installed`);
619
751
  }
620
- const redisHost = (parsed.REDIS_HOST || "127.0.0.1").trim();
621
- const redisPort = Number(parsed.REDIS_PORT || 6379);
622
- const redisCheck = await checkTcpPort(redisHost, redisPort);
623
- 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
+ }
624
761
  if (mode === "client") {
625
762
  const gatewayPublicUrl = parsed.GATEWAY_PUBLIC_URL?.trim();
626
763
  if (gatewayPublicUrl) {
@@ -720,9 +857,12 @@ async function runDoctor(args) {
720
857
  printSection("notes", notes);
721
858
  }
722
859
  else {
723
- 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
+ ]);
724
863
  }
725
- if (browserEnabled && (!playwrightStatus.enabled || !playwrightStatus.installed)) {
864
+ if (browserEnabled &&
865
+ (!playwrightStatus.enabled || !playwrightStatus.installed)) {
726
866
  printSection("playwright", [
727
867
  `${picocolors_1.default.yellow(" ACTION")} Install browser binaries before using browser_* tools:`,
728
868
  " tellymcp browser install",
@@ -742,7 +882,9 @@ function normalizeExtensionFlavor(rawValue) {
742
882
  return null;
743
883
  }
744
884
  function getBundledExtensionDir(flavor) {
745
- 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");
746
888
  }
747
889
  function getDefaultExtensionTargetDir(flavor) {
748
890
  return node_path_1.default.resolve(process.cwd(), flavor === "firefox" ? "tellymcp-firefox-attach" : "tellymcp-chrome-attach");
@@ -783,7 +925,7 @@ async function runSystemPrune(args) {
783
925
  const dbUser = parsed.DB_USER?.trim();
784
926
  const dbPassword = parsed.DB_PASSWORD?.trim();
785
927
  const dbName = parsed.DB_NAME?.trim();
786
- const dbSchema = (parsed.DB_SCHEME || "mcp").trim();
928
+ const dbSchema = (parsed.DB_SCHEMA || "mcp").trim();
787
929
  const xchangeDir = node_path_1.default.resolve(process.cwd(), parsed.MCP_XCHANGE_DIR || ".mcp-xchange");
788
930
  const sessionMarkerPath = node_path_1.default.resolve(process.cwd(), ".mcpsession.json");
789
931
  const sqliteDbPath = node_path_1.default.join(xchangeDir, "xchange.sqlite3");
@@ -791,35 +933,39 @@ async function runSystemPrune(args) {
791
933
  printSection("Target", [
792
934
  ` env: ${envPath}`,
793
935
  ` mode: ${mode}`,
794
- ` redis: ${redisHost}:${redisPort}/${redisDb}`,
936
+ ...(mode === "client"
937
+ ? [` state: process-local (Redis skipped)`]
938
+ : [` redis: ${redisHost}:${redisPort}/${redisDb}`]),
795
939
  ...(dbHost && dbUser && dbName
796
940
  ? [` postgres: ${dbHost}:${dbPort}/${dbName} schema ${dbSchema}`]
797
941
  : [` postgres: ${picocolors_1.default.dim("skipped (not configured)")}`]),
798
942
  ` xchange dir: ${xchangeDir}`,
799
943
  ` session marker: ${sessionMarkerPath}`,
800
944
  ]);
801
- const redis = new ioredis_1.default({
802
- host: redisHost,
803
- port: redisPort,
804
- db: redisDb,
805
- ...(redisUsername ? { username: redisUsername } : {}),
806
- ...(redisPassword ? { password: redisPassword } : {}),
807
- maxRetriesPerRequest: 1,
808
- enableReadyCheck: true,
809
- });
810
945
  let deletedRedisKeys = 0;
811
- try {
812
- let cursor = "0";
813
- do {
814
- const [nextCursor, keys] = await redis.scan(cursor, "MATCH", "telegram-mcp:*", "COUNT", 500);
815
- cursor = nextCursor;
816
- if (keys.length > 0) {
817
- deletedRedisKeys += await redis.del(...keys);
818
- }
819
- } while (cursor !== "0");
820
- }
821
- finally {
822
- 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
+ }
823
969
  }
824
970
  let truncatedTables = [];
825
971
  if (dbHost && dbUser && dbName) {
@@ -1013,8 +1159,19 @@ async function runRuntime(args) {
1013
1159
  sessionId: parsed.TELLYMCP_SESSION_ID,
1014
1160
  sessionLabel: parsed.TELLYMCP_SESSION_LABEL,
1015
1161
  });
1016
- if ((0, foregroundTerminalRuntime_1.isForegroundPtyClientMode)(parsed)) {
1017
- 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({
1018
1175
  envPath,
1019
1176
  packageRoot,
1020
1177
  printBanner,
@@ -1068,11 +1225,24 @@ async function runRuntime(args) {
1068
1225
  }
1069
1226
  async function main(argv) {
1070
1227
  const [rawCommand, firstArg, secondArg] = argv;
1071
- const command = rawCommand === "init" || rawCommand === "run" || rawCommand === "help" || rawCommand === "mcp" || rawCommand === "oauth" || rawCommand === "doctor" || rawCommand === "browser" || rawCommand === "system-prune"
1072
- || 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"
1073
1240
  ? rawCommand
1074
1241
  : "help";
1075
- if (command === "help" || !rawCommand || rawCommand === "--help" || rawCommand === "-h") {
1242
+ if (command === "help" ||
1243
+ !rawCommand ||
1244
+ rawCommand === "--help" ||
1245
+ rawCommand === "-h") {
1076
1246
  printHelp();
1077
1247
  return;
1078
1248
  }
@@ -1080,6 +1250,14 @@ async function main(argv) {
1080
1250
  initWorkspace(ensureMode(firstArg), secondArg);
1081
1251
  return;
1082
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
+ }
1083
1261
  if (command === "mcp") {
1084
1262
  printMcpConfig(argv.slice(1));
1085
1263
  return;