@unlinearity/cli-wechat-bridge 1.2.1 → 1.2.3

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 (54) hide show
  1. package/README.md +146 -73
  2. package/README_EN.md +544 -0
  3. package/bin/telegram-check-update.mjs +10 -0
  4. package/bin/telegram-claude.mjs +10 -0
  5. package/bin/telegram-codex.mjs +10 -0
  6. package/bin/telegram-daemon.mjs +10 -0
  7. package/bin/telegram-opencode.mjs +10 -0
  8. package/bin/telegram-pi.mjs +10 -0
  9. package/bin/telegram-setup.mjs +10 -0
  10. package/dist/bridge/adapter-control.js +19 -12
  11. package/dist/bridge/bridge-adapters.claude.js +219 -115
  12. package/dist/bridge/bridge-adapters.codex.js +532 -99
  13. package/dist/bridge/bridge-adapters.core.js +84 -7
  14. package/dist/bridge/bridge-adapters.opencode.js +202 -55
  15. package/dist/bridge/bridge-adapters.pi.js +23 -18
  16. package/dist/bridge/bridge-adapters.shared.js +9 -9
  17. package/dist/bridge/bridge-session-resume.js +13 -6
  18. package/dist/bridge/bridge-state.js +3 -1
  19. package/dist/bridge/bridge-utils.js +94 -144
  20. package/dist/bridge/claude-hooks.js +60 -3
  21. package/dist/bridge/claude-native-controls.js +19 -12
  22. package/dist/bridge/language-control.js +33 -0
  23. package/dist/bridge/native-terminal-control.js +26 -10
  24. package/dist/bridge/opencode-v2-compat.js +11 -3
  25. package/dist/bridge/wechat-bridge.js +154 -72
  26. package/dist/channels/channel-driver-factory.js +56 -0
  27. package/dist/channels/local/local-channel-port.js +40 -0
  28. package/dist/channels/local/local-driver.js +217 -0
  29. package/dist/channels/telegram/setup-cli.js +12 -0
  30. package/dist/channels/telegram/setup.js +163 -0
  31. package/dist/channels/telegram/telegram-channel-port.js +47 -0
  32. package/dist/channels/telegram/telegram-config.js +66 -0
  33. package/dist/channels/telegram/telegram-driver.js +106 -0
  34. package/dist/channels/telegram/telegram-message.js +41 -0
  35. package/dist/channels/telegram/telegram-transport.js +378 -0
  36. package/dist/channels/wechat/wechat-driver.js +11 -2
  37. package/dist/channels/wecom/wecom-driver.js +8 -1
  38. package/dist/companion/local-companion-start.js +180 -61
  39. package/dist/companion/local-companion.js +27 -7
  40. package/dist/companion/pi-tui-bridge-extension.js +10 -4
  41. package/dist/core/bridge-control.js +3 -0
  42. package/dist/core/bridge-defer.js +3 -2
  43. package/dist/core/bridge-event-forwarder.js +3 -0
  44. package/dist/core/channel-types.js +13 -1
  45. package/dist/daemon/wechat-daemon.js +239 -126
  46. package/dist/i18n/index.js +25 -11
  47. package/dist/i18n/messages-en.js +245 -1
  48. package/dist/i18n/messages-zh.js +245 -1
  49. package/dist/runtime/legacy-adapter-runtime.js +7 -4
  50. package/dist/wechat/channel-config.js +3 -2
  51. package/package.json +14 -4
  52. package/skills/codex-bridge/SKILL.md +35 -0
  53. package/skills/codex-bridge/agents/openai.yaml +4 -0
  54. package/skills/codex-bridge/scripts/install.mjs +28 -0
@@ -40,20 +40,23 @@ export class LegacyAdapterRuntime {
40
40
  throw new Error("Plan mode is not supported by this companion.");
41
41
  return this.adapter.setPlanMode(enabled);
42
42
  }
43
+ async updateLocale(locale) {
44
+ return this.adapter.updateLocale?.(locale) ?? true;
45
+ }
43
46
  async interrupt() {
44
47
  return await this.adapter.interrupt();
45
48
  }
46
49
  async reset() {
47
50
  await this.adapter.reset();
48
51
  }
49
- async resolveApproval(action) {
50
- return await this.adapter.resolveApproval(action);
52
+ async resolveApproval(action, requestId) {
53
+ return await this.adapter.resolveApproval(action, requestId);
51
54
  }
52
55
  async resolveAllApprovals(action) {
53
56
  return await this.adapter.resolveAllApprovals(action);
54
57
  }
55
- async submitUserInput(answers) {
56
- return await this.adapter.submitUserInput(answers);
58
+ async submitUserInput(answers, requestId) {
59
+ return await this.adapter.submitUserInput(answers, requestId);
57
60
  }
58
61
  async dispose() {
59
62
  await this.adapter.dispose();
@@ -9,7 +9,7 @@ import fs from "node:fs";
9
9
  import os from "node:os";
10
10
  import path from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
- import { initLocaleFromEnv } from "../i18n/index.js";
12
+ import { initLocaleFromEnv, readLocalePreference } from "../i18n/index.js";
13
13
  const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
14
14
  const PROJECT_DIR = path.resolve(MODULE_DIR, "..", "..");
15
15
  export const DEFAULT_BASE_URL = process.env.WECHAT_ILINK_BASE_URL?.trim() || "https://ilinkai.weixin.qq.com";
@@ -57,7 +57,8 @@ export const WORKSPACES_DIR = path.join(CHANNEL_DATA_DIR, "workspaces");
57
57
  export const INBOUND_MESSAGE_CLAIMS_DIR = path.join(CHANNEL_DATA_DIR, "inbound-message-claims");
58
58
  export const INBOUND_ATTACHMENTS_DIR = path.join(CHANNEL_DATA_DIR, "inbound-attachments");
59
59
  export const EMOJI_BINDINGS_FILE = path.join(CHANNEL_DATA_DIR, "emoji-bindings.json");
60
- initLocaleFromEnv();
60
+ export const LANGUAGE_FILE = path.join(CHANNEL_DATA_DIR, "language.json");
61
+ initLocaleFromEnv(process.env, readLocalePreference(LANGUAGE_FILE) ?? "zh");
61
62
  const LEGACY_GLOBAL_CHANNEL_DATA_DIR = path.join(os.homedir(), ".claude", "channels", "wechat");
62
63
  const LEGACY_REPO_CHANNEL_DATA_DIR = path.join(PROJECT_DIR, "~", ".claude", "channels", "wechat");
63
64
  const LEGACY_ENV_CHANNEL_DATA_DIR = process.env.CLAUDE_WECHAT_CHANNEL_DATA_DIR?.trim()
package/package.json CHANGED
@@ -1,18 +1,21 @@
1
1
  {
2
2
  "name": "@unlinearity/cli-wechat-bridge",
3
- "version": "1.2.1",
4
- "description": "CLI bridge for Codex, Claude Code, OpenCode, and Pi over WeChat and WeCom",
3
+ "version": "1.2.3",
4
+ "description": "CLI bridge for Codex, Claude Code, OpenCode, and Pi over WeChat, WeCom, and Telegram",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "bin/",
8
8
  "dist/",
9
9
  "scripts/ensure-node-pty-permissions.mjs",
10
10
  "README.md",
11
- "LICENSE.txt"
11
+ "README_EN.md",
12
+ "LICENSE.txt",
13
+ "skills/codex-bridge/"
12
14
  ],
13
15
  "keywords": [
14
16
  "wechat",
15
17
  "wecom",
18
+ "telegram",
16
19
  "cli",
17
20
  "bridge",
18
21
  "codex",
@@ -48,7 +51,14 @@
48
51
  "wecom-pi": "bin/wecom-pi.mjs",
49
52
  "wecom-daemon": "bin/wecom-daemon.mjs",
50
53
  "wecom-setup": "bin/wecom-setup.mjs",
51
- "wecom-check-update": "bin/wecom-check-update.mjs"
54
+ "wecom-check-update": "bin/wecom-check-update.mjs",
55
+ "telegram-codex": "bin/telegram-codex.mjs",
56
+ "telegram-claude": "bin/telegram-claude.mjs",
57
+ "telegram-opencode": "bin/telegram-opencode.mjs",
58
+ "telegram-pi": "bin/telegram-pi.mjs",
59
+ "telegram-daemon": "bin/telegram-daemon.mjs",
60
+ "telegram-setup": "bin/telegram-setup.mjs",
61
+ "telegram-check-update": "bin/telegram-check-update.mjs"
52
62
  },
53
63
  "dependencies": {
54
64
  "@modelcontextprotocol/sdk": "^1.30.0",
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: codex-bridge
3
+ description: Attach a messaging channel (WeChat, WeCom, or Telegram) to the current Codex conversation through the shared Codex app-server daemon, or manage the attached bridge. Use for $codex-bridge requests from Codex TUI sessions; this skill does not apply to Claude Code, OpenCode, Pi, or embedded (desktop app) Codex sessions.
4
+ ---
5
+
6
+ # Codex Bridge
7
+
8
+ Attach the CLI WeChat Bridge to the **current** Codex conversation. The bridge joins the shared Codex app-server daemon (`~/.codex/app-server-control/app-server-control.sock`, codex >= 0.162 default mode) as a second client and rejoins this session's live thread: channel messages become turns in this same thread, replies and approvals flow back to the channel, and the local Codex session keeps working unchanged.
9
+
10
+ Keep the invoking Codex conversation and its context. The launcher prefix selects the channel — `wechat-codex`, `wecom-codex`, or `telegram-codex`; default to WeChat unless the user names another channel.
11
+
12
+ ## Attach the current conversation
13
+
14
+ 1. Resolve the launcher: the installed package's `wechat-codex` / `wecom-codex` / `telegram-codex` (find it with `npm root -g`; primary package `cli-wechat-bridge`, mirror `@unlinearity/cli-wechat-bridge`), or the source-checkout equivalents.
15
+ 2. Run the attach command from the project directory (or pass `--cwd`):
16
+ ```bash
17
+ <channel>-codex --cwd <project> --attach-current-session # prefix: wechat | wecom | telegram
18
+ ```
19
+ It starts a background bridge that discovers the newest **live** daemon session in `<project>` and binds to it. Success prints `Attached Codex daemon thread <id>`. Attach with an explicit thread via `--codex-thread-id <id>` when several live sessions share the directory.
20
+ 3. Verify before claiming success: the launcher printed `Attached Codex daemon thread`, or `~/.cli-bridge/workspaces/<workspace-key>/bridge-state.json` has a `sharedThreadId`. A message that reaches this conversation and whose reply returns to the channel is the full proof.
21
+ 4. Do not run plain `wechat-codex` as a substitute: that opens a new bridge-managed visible client and does NOT attach this conversation. Never report "connected" based on a login, a status read, or a new session.
22
+
23
+ ## Boundaries — report these honestly
24
+
25
+ - `No shared Codex app-server daemon socket at ...`: codex < 0.162 or no shared background server. Upgrade codex and retry.
26
+ - `No live Codex daemon session found for <project>`: no daemon-attached session in this directory. Open codex normally (>= 0.162) in the project and retry.
27
+ - `thread ... already has an active writer`: the target session is **embedded** (desktop app, `--no-daemon`, `--oss`, `--profile`). Embedded sessions cannot be attached; say so instead of falling back to opening another session.
28
+ - A daemon (`wechat-daemon`/`wecom-daemon`/`telegram-daemon`) owning the workspace blocks attach (it holds the bridge lock); stop it first or use its slots.
29
+
30
+ ## Status, switching, and shutdown
31
+
32
+ - Status: `~/.cli-bridge/workspaces/<workspace-key>/bridge-state.json` (`sharedThreadId`, status) and `~/.cli-bridge/bridge.log`.
33
+ - Switch to another conversation: run the attach command again from that session's project directory; it replaces the previous attach bridge for the workspace.
34
+ - Shutdown: send `/shutdown` from the channel, or stop the bridge process holding `~/.cli-bridge/bridge.lock.json`.
35
+ - Attach does not kill or restart anything: the shared daemon, the owning TUI, and all turns stay intact; the bridge is a guest client and turns it injects appear live in the local session.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Codex Bridge"
3
+ short_description: "把当前 Codex 会话接入消息通道(微信 / 企业微信 / Telegram):加入共享 app-server daemon,rejoin 活线程,消息与回复双向流转"
4
+ default_prompt: "使用 $codex-bridge 把当前 Codex 会话接入消息通道。"
@@ -0,0 +1,28 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (c) 2026 UNLINEARITY <unlinearity@gmail.com>
3
+ // Source: https://github.com/UNLINEARITY/CLI-WeChat-Bridge
4
+ // This file is part of CLI-WeChat-Bridge. Modifications and derivative works
5
+ // must be released under AGPL-3.0-or-later with full source code; see
6
+ // LICENSE.txt. Network services built on it must offer source to users.
7
+ import fs from "node:fs";
8
+ import os from "node:os";
9
+ import path from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+
12
+ export function installCodexBridgeSkill({
13
+ sourceDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."),
14
+ codexHome = process.env.CODEX_HOME || path.join(os.homedir(), ".codex"),
15
+ } = {}) {
16
+ const targetDir = path.join(codexHome, "skills", "codex-bridge");
17
+ for (const relative of ["SKILL.md", "agents/openai.yaml", "scripts/install.mjs"]) {
18
+ const source = path.join(sourceDir, relative);
19
+ const target = path.join(targetDir, relative);
20
+ fs.mkdirSync(path.dirname(target), { recursive: true });
21
+ if (path.resolve(source) !== path.resolve(target)) fs.copyFileSync(source, target);
22
+ }
23
+ return targetDir;
24
+ }
25
+
26
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
27
+ console.log(JSON.stringify({ installed: installCodexBridgeSkill() }));
28
+ }