cli-wechat-bridge 1.1.3 → 1.1.4

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 (45) hide show
  1. package/README.md +98 -135
  2. package/bin/_run-entry.mjs +12 -0
  3. package/bin/wechat-claude-start.mjs +7 -2
  4. package/bin/wechat-claude.mjs +1 -1
  5. package/bin/wechat-codex-start.mjs +7 -2
  6. package/bin/wechat-codex.mjs +1 -1
  7. package/bin/wechat-opencode-start.mjs +7 -2
  8. package/bin/wechat-opencode.mjs +1 -1
  9. package/bin/wechat-pi-start.mjs +7 -2
  10. package/bin/wechat-pi.mjs +1 -1
  11. package/dist/bridge/bridge-adapters.claude.js +21 -1
  12. package/dist/bridge/bridge-adapters.codex.js +27 -4
  13. package/dist/bridge/bridge-adapters.core.js +76 -4
  14. package/dist/bridge/bridge-adapters.js +0 -3
  15. package/dist/bridge/bridge-adapters.opencode.js +130 -21
  16. package/dist/bridge/bridge-adapters.pi.js +12 -2
  17. package/dist/bridge/bridge-adapters.shared.js +13 -100
  18. package/dist/bridge/bridge-final-reply.js +17 -2
  19. package/dist/bridge/bridge-process-reaper.js +14 -4
  20. package/dist/bridge/bridge-state.js +41 -6
  21. package/dist/bridge/bridge-utils.js +37 -224
  22. package/dist/bridge/wechat-bridge.js +75 -130
  23. package/dist/bridge/wechat-forwarding.js +58 -0
  24. package/dist/companion/codex-remote-client.js +20 -6
  25. package/dist/companion/local-companion-link.js +7 -2
  26. package/dist/companion/local-companion-start.js +45 -13
  27. package/dist/companion/local-companion.js +6 -3
  28. package/dist/daemon/daemon-link.js +25 -3
  29. package/dist/daemon/wechat-daemon.js +148 -54
  30. package/dist/i18n/index.js +0 -3
  31. package/dist/i18n/messages-en.js +2 -3
  32. package/dist/i18n/messages-zh.js +2 -3
  33. package/dist/utils/atomic-file.js +15 -0
  34. package/dist/utils/doctor.js +23 -11
  35. package/dist/wechat/channel-config.js +0 -2
  36. package/dist/wechat/setup.js +5 -11
  37. package/dist/wechat/wechat-transport.js +14 -8
  38. package/package.json +2 -9
  39. package/bin/wechat-bridge-claude.mjs +0 -5
  40. package/bin/wechat-bridge-codex.mjs +0 -5
  41. package/bin/wechat-bridge-opencode.mjs +0 -5
  42. package/bin/wechat-bridge-pi.mjs +0 -5
  43. package/bin/wechat-bridge-shell.mjs +0 -5
  44. package/bin/wechat-bridge.mjs +0 -5
  45. package/dist/bridge/bridge-adapters.shell.js +0 -363
@@ -61,45 +61,40 @@ const INLINE_VOICE_EXTENSIONS = new Set([
61
61
  ".ogg",
62
62
  ".aac",
63
63
  ]);
64
+ // Directly executable / installer file types are never auto-uploaded from a
65
+ // path mentioned in a reply: handing these to WeChat contacts by accident is
66
+ // a real hazard. Everything else — source code, documents, archives, data
67
+ // files — stays auto-sendable so common workflows keep working.
64
68
  const INLINE_REFERENCE_ONLY_FILE_EXTENSIONS = new Set([
69
+ // Windows binaries and installers
70
+ ".exe",
71
+ ".dll",
72
+ ".sys",
73
+ ".msi",
74
+ ".msp",
75
+ ".scr",
76
+ ".com",
77
+ ".pif",
78
+ ".cpl",
79
+ ".ocx",
80
+ ".hta",
81
+ ".jar",
82
+ // Windows one-click script launchers
65
83
  ".bat",
66
- ".c",
67
- ".cc",
68
- ".cjs",
69
84
  ".cmd",
70
- ".cpp",
71
- ".cs",
72
- ".cts",
73
- ".cxx",
74
- ".go",
75
- ".h",
76
- ".hh",
77
- ".hpp",
78
- ".java",
79
- ".js",
80
- ".jsx",
81
- ".kt",
82
- ".kts",
83
- ".lua",
84
- ".m",
85
- ".mjs",
86
- ".mm",
87
- ".mts",
88
- ".php",
89
- ".pl",
90
85
  ".ps1",
91
- ".psd1",
92
- ".psm1",
93
- ".py",
94
- ".rb",
95
- ".rs",
96
- ".scala",
86
+ ".vbs",
87
+ ".vbe",
88
+ ".jse",
89
+ ".wsf",
90
+ ".wsh",
91
+ // Unix one-click script launchers and installable packages
97
92
  ".sh",
98
- ".swift",
99
- ".ts",
100
- ".tsx",
101
- ".vb",
102
- ".zsh",
93
+ ".bash",
94
+ ".deb",
95
+ ".rpm",
96
+ ".apk",
97
+ ".dmg",
103
98
  ]);
104
99
  const INLINE_MAAS_URL_RE = /https?:\/\/[^\s]*?\/([A-Za-z]:\\.+?(?:\.\s*[A-Za-z0-9]{2,8})+)(?:\?[^\n]*)?/g;
105
100
  const INLINE_WINDOWS_PATH_RE = /(^|[^\w])`?([A-Za-z]:\\(?:[^\\/:*?"<>|\r\n`]+\\)*[^\\/:*?"<>|\r\n`]+?(?:\.\s*[A-Za-z0-9]{2,8})+)`?(?=$|[^\w])/gm;
@@ -451,176 +446,6 @@ export function isHighRiskShellCommand(command) {
451
446
  }
452
447
  return HIGH_RISK_PATTERNS.some((pattern) => pattern.test(normalized));
453
448
  }
454
- const ALWAYS_INTERACTIVE_SHELL_COMMANDS = new Set([
455
- "ftp",
456
- "htop",
457
- "irb",
458
- "less",
459
- "more",
460
- "mongosh",
461
- "mysql",
462
- "nano",
463
- "nvim",
464
- "psql",
465
- "redis-cli",
466
- "screen",
467
- "sftp",
468
- "sqlite3",
469
- "ssh",
470
- "telnet",
471
- "tmux",
472
- "top",
473
- "vi",
474
- "vim",
475
- "watch",
476
- ]);
477
- function tokenizeShellCommand(command, maxTokens = 16) {
478
- const tokens = [];
479
- let current = "";
480
- let quote = null;
481
- for (const char of command.trim()) {
482
- if (quote) {
483
- if (char === quote) {
484
- quote = null;
485
- }
486
- else {
487
- current += char;
488
- }
489
- continue;
490
- }
491
- if (char === '"' || char === "'") {
492
- quote = char;
493
- continue;
494
- }
495
- if (/\s/u.test(char)) {
496
- if (current) {
497
- tokens.push(current);
498
- current = "";
499
- if (tokens.length >= maxTokens) {
500
- return tokens;
501
- }
502
- }
503
- continue;
504
- }
505
- current += char;
506
- }
507
- if (current) {
508
- tokens.push(current);
509
- }
510
- return tokens;
511
- }
512
- function normalizeShellExecutableToken(token) {
513
- const trimmed = token.trim().replace(/^["']|["']$/g, "");
514
- if (!trimmed) {
515
- return "";
516
- }
517
- return path.parse(trimmed).name.toLowerCase();
518
- }
519
- function findCommandFlagIndex(args, supportedFlags) {
520
- return args.findIndex((arg) => supportedFlags.includes(arg.toLowerCase()));
521
- }
522
- function hasScriptLikeArg(args) {
523
- return args.some((arg) => Boolean(arg) && !arg.startsWith("-") && !arg.startsWith("/"));
524
- }
525
- function hasAnyCommandFlag(args, supportedFlags) {
526
- return findCommandFlagIndex(args, supportedFlags) >= 0;
527
- }
528
- function buildInteractiveShellCommandMessage(executable, suggestion) {
529
- return `Interactive command "${executable}" is not supported in shell mode yet. This shell bridge currently only supports non-interactive commands and scripts. ${suggestion}`;
530
- }
531
- export function getInteractiveShellCommandRejectionMessage(command) {
532
- const tokens = tokenizeShellCommand(command);
533
- if (!tokens.length) {
534
- return null;
535
- }
536
- const firstToken = tokens[0];
537
- if (!firstToken) {
538
- return null;
539
- }
540
- const executable = normalizeShellExecutableToken(firstToken);
541
- const args = tokens.slice(1);
542
- const lowerArgs = args.map((arg) => arg.toLowerCase());
543
- if (!executable) {
544
- return null;
545
- }
546
- if (ALWAYS_INTERACTIVE_SHELL_COMMANDS.has(executable)) {
547
- return buildInteractiveShellCommandMessage(executable, "Run a non-interactive command or script instead.");
548
- }
549
- switch (executable) {
550
- case "python":
551
- case "python3":
552
- case "py": {
553
- if (!args.length) {
554
- return buildInteractiveShellCommandMessage(executable, 'Try "python script.py" or "python -c \\"...\\"" instead.');
555
- }
556
- if (lowerArgs.includes("-i") || lowerArgs.includes("--interactive")) {
557
- return buildInteractiveShellCommandMessage(executable, 'Try "python script.py" or "python -c \\"...\\"" instead.');
558
- }
559
- if (hasAnyCommandFlag(lowerArgs, ["-c", "-h", "--help", "-v", "--version"])) {
560
- return null;
561
- }
562
- const moduleFlagIndex = findCommandFlagIndex(lowerArgs, ["-m"]);
563
- if (moduleFlagIndex >= 0) {
564
- return moduleFlagIndex < args.length - 1
565
- ? null
566
- : buildInteractiveShellCommandMessage(executable, 'Try "python script.py" or "python -m module_name" instead.');
567
- }
568
- return hasScriptLikeArg(args)
569
- ? null
570
- : buildInteractiveShellCommandMessage(executable, 'Try "python script.py" or "python -c \\"...\\"" instead.');
571
- }
572
- case "node":
573
- if (!args.length || lowerArgs.includes("-i") || lowerArgs.includes("--interactive")) {
574
- return buildInteractiveShellCommandMessage(executable, 'Try "node script.js" or "node -e \\"...\\"" instead.');
575
- }
576
- if (hasAnyCommandFlag(lowerArgs, ["-e", "--eval", "-p", "--print", "-h", "--help", "-v", "--version"])) {
577
- return null;
578
- }
579
- return hasScriptLikeArg(args)
580
- ? null
581
- : buildInteractiveShellCommandMessage(executable, 'Try "node script.js" or "node -e \\"...\\"" instead.');
582
- case "cmd":
583
- if (lowerArgs.includes("/?")) {
584
- return null;
585
- }
586
- if (lowerArgs.includes("/k") || !lowerArgs.includes("/c")) {
587
- return buildInteractiveShellCommandMessage(executable, 'Try "cmd /c <command>" or run the command directly instead.');
588
- }
589
- return null;
590
- case "powershell":
591
- case "pwsh":
592
- if (!args.length ||
593
- lowerArgs.includes("-noexit") ||
594
- lowerArgs.includes("-nologo") && args.length === 1) {
595
- return buildInteractiveShellCommandMessage(executable, `Try "${executable} -Command \\"...\\"" or "${executable} -File script.ps1" instead.`);
596
- }
597
- if (hasAnyCommandFlag(lowerArgs, ["-c", "-command", "-enc", "-encodedcommand", "-f", "-file", "-h", "-help", "-v", "-version", "-?"])) {
598
- return null;
599
- }
600
- return hasScriptLikeArg(args)
601
- ? null
602
- : buildInteractiveShellCommandMessage(executable, `Try "${executable} -Command \\"...\\"" or "${executable} -File script.ps1" instead.`);
603
- case "bash":
604
- case "dash":
605
- case "ksh":
606
- case "sh":
607
- case "zsh":
608
- if (!args.length || lowerArgs.includes("-i")) {
609
- return buildInteractiveShellCommandMessage(executable, `Try "${executable} -c '...'" or "${executable} script.sh" instead.`);
610
- }
611
- if (findCommandFlagIndex(lowerArgs, ["-c", "-lc"]) >= 0) {
612
- return null;
613
- }
614
- if (hasAnyCommandFlag(lowerArgs, ["-h", "--help", "--version"])) {
615
- return null;
616
- }
617
- return hasScriptLikeArg(args)
618
- ? null
619
- : buildInteractiveShellCommandMessage(executable, `Try "${executable} -c '...'" or "${executable} script.sh" instead.`);
620
- default:
621
- return null;
622
- }
623
- }
624
449
  export function detectCliApproval(text) {
625
450
  const normalized = normalizeOutput(text);
626
451
  const compact = normalized.replace(/\s+/g, " ").trim();
@@ -807,27 +632,14 @@ export function formatMirroredUserInputMessage(adapter, text) {
807
632
  : "Local input";
808
633
  return `${label}:\n${truncatePreview(text, 500)}`;
809
634
  }
810
- export function formatFinalReplyMessage(adapter, text) {
811
- if (adapter === "claude" || adapter === "codex" || adapter === "opencode" || adapter === "pi") {
812
- return text;
813
- }
814
- // After the early return above, only "shell" remains.
815
- const label = adapter === "codex"
816
- ? "Codex"
817
- : adapter === "claude"
818
- ? "Claude"
819
- : adapter === "opencode"
820
- ? "OpenCode"
821
- : adapter === "pi"
822
- ? "Pi"
823
- : adapter;
824
- return `${label} final reply:\n${text}`;
635
+ export function formatFinalReplyMessage(_adapter, text) {
636
+ return text;
825
637
  }
826
638
  const OPENCODE_WORKING_NOTICE_RE = /^OpenCode is still working on:\s*$/i;
827
639
  const OPENCODE_TRANSIENT_NOTICE_RES = [
828
640
  /^Bridge error: opencode companion is not connected\./i,
829
641
  /^OpenCode companion is not connected(?: for bridge workspace)?:?$/i,
830
- /^Run "wechat-(?:bridge-opencode|opencode(?:-start)?)".*$/i,
642
+ /^Run "wechat-opencode".*$/i,
831
643
  /^OpenCode session switched to \S+ from the local terminal\.$/i,
832
644
  /^Local OpenCode input:\s*$/i,
833
645
  ];
@@ -922,7 +734,7 @@ export function sanitizeWechatFinalReplyText(adapter, text) {
922
734
  const resolved = tail || cleaned;
923
735
  return isOpenCodeReasoningResidue(resolved) ? "" : resolved;
924
736
  }
925
- function extractInlineWechatAttachments(text) {
737
+ export function extractInlineWechatAttachments(text) {
926
738
  const sanitized = text
927
739
  .replace(/\\\n\s*/g, "\\")
928
740
  .replace(/\.\s*\n?\s*([A-Za-z0-9]{2,8})(?=\?)/g, ".$1")
@@ -1010,8 +822,9 @@ function inferInlineWechatAttachmentKind(filePath) {
1010
822
  if (INLINE_VOICE_EXTENSIONS.has(extension)) {
1011
823
  return "voice";
1012
824
  }
1013
- // Keep ordinary local files auto-sendable, but avoid turning common
1014
- // source/script references in prose into unintended WeChat uploads.
825
+ // Ordinary local files stay auto-sendable; only directly executable /
826
+ // installer types are excluded so a path mentioned in prose cannot
827
+ // accidentally ship a runnable binary to a WeChat contact.
1015
828
  if (!extension || INLINE_REFERENCE_ONLY_FILE_EXTENSIONS.has(extension)) {
1016
829
  return null;
1017
830
  }
@@ -1053,7 +866,7 @@ export function formatApprovalMessage(pending, adapterState) {
1053
866
  ].join("\n");
1054
867
  }
1055
868
  return [
1056
- `${pending.source === "shell" ? "Shell" : "CLI"} approval is required.`,
869
+ "CLI approval is required.",
1057
870
  `adapter: ${adapterState.kind}`,
1058
871
  `summary: ${pending.summary}`,
1059
872
  `target: ${pending.commandPreview}`,