@unlinearity/cli-wechat-bridge 1.0.3 → 1.0.5
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.
- package/README.md +325 -221
- package/bin/wechat-daemon.mjs +5 -0
- package/dist/bridge/bridge-adapter-common.js +95 -0
- package/dist/bridge/bridge-adapters.claude.js +1 -1
- package/dist/bridge/bridge-adapters.codex.js +18 -3
- package/dist/bridge/bridge-adapters.core.js +51 -7
- package/dist/bridge/bridge-adapters.opencode.js +220 -22
- package/dist/bridge/bridge-adapters.shared.js +4 -97
- package/dist/bridge/bridge-controller.js +6 -2
- package/dist/bridge/bridge-final-reply.js +18 -3
- package/dist/bridge/bridge-state.js +9 -3
- package/dist/bridge/bridge-utils.js +81 -14
- package/dist/bridge/claude-hooks.js +2 -2
- package/dist/bridge/wechat-bridge.js +139 -31
- package/dist/companion/codex-remote-client.js +11 -4
- package/dist/companion/local-companion-link.js +56 -26
- package/dist/companion/local-companion-start.js +54 -6
- package/dist/companion/local-companion.js +6 -1
- package/dist/daemon/daemon-link.js +175 -0
- package/dist/daemon/wechat-daemon.js +1202 -0
- package/dist/utils/version-checker.js +1 -1
- package/dist/wechat/channel-config.js +126 -33
- package/dist/wechat/standalone-bot.js +5 -6
- package/dist/wechat/wechat-channel.js +21 -10
- package/dist/wechat/wechat-transport.js +336 -10
- package/package.json +12 -2
|
@@ -7,22 +7,79 @@ const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
|
7
7
|
const PROJECT_DIR = path.resolve(MODULE_DIR, "..", "..");
|
|
8
8
|
export const DEFAULT_BASE_URL = process.env.WECHAT_ILINK_BASE_URL?.trim() || "https://ilinkai.weixin.qq.com";
|
|
9
9
|
export const BOT_TYPE = "3";
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
: path.join(
|
|
13
|
-
|
|
10
|
+
export function resolveChannelDataDir(env = process.env, homeDir = os.homedir()) {
|
|
11
|
+
const configured = env.CLI_BRIDGE_DATA_DIR?.trim();
|
|
12
|
+
return configured ? path.resolve(configured) : path.join(homeDir, ".cli-bridge");
|
|
13
|
+
}
|
|
14
|
+
export const CHANNEL_DATA_DIR = resolveChannelDataDir();
|
|
14
15
|
export const CREDENTIALS_FILE = path.join(CHANNEL_DATA_DIR, "account.json");
|
|
15
16
|
export const SYNC_BUF_FILE = path.join(CHANNEL_DATA_DIR, "sync_buf.txt");
|
|
16
17
|
export const CONTEXT_CACHE_FILE = path.join(CHANNEL_DATA_DIR, "context_tokens.json");
|
|
17
18
|
export const BRIDGE_STATE_FILE = path.join(CHANNEL_DATA_DIR, "bridge-state.json");
|
|
18
19
|
export const BRIDGE_LOG_FILE = path.join(CHANNEL_DATA_DIR, "bridge.log");
|
|
19
20
|
export const BRIDGE_LOCK_FILE = path.join(CHANNEL_DATA_DIR, "bridge.lock.json");
|
|
21
|
+
export const DAEMON_ENDPOINT_FILE = path.join(CHANNEL_DATA_DIR, "daemon-endpoint.json");
|
|
20
22
|
export const CODEX_PANEL_ENDPOINT_FILE = path.join(CHANNEL_DATA_DIR, "codex-panel-endpoint.json");
|
|
21
23
|
export const WORKSPACES_DIR = path.join(CHANNEL_DATA_DIR, "workspaces");
|
|
22
|
-
export const INBOUND_MESSAGE_CLAIMS_DIR = path.join(
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
24
|
+
export const INBOUND_MESSAGE_CLAIMS_DIR = path.join(CHANNEL_DATA_DIR, "inbound-message-claims");
|
|
25
|
+
export const INBOUND_ATTACHMENTS_DIR = path.join(CHANNEL_DATA_DIR, "inbound-attachments");
|
|
26
|
+
const LEGACY_GLOBAL_CHANNEL_DATA_DIR = path.join(os.homedir(), ".claude", "channels", "wechat");
|
|
27
|
+
const LEGACY_REPO_CHANNEL_DATA_DIR = path.join(PROJECT_DIR, "~", ".claude", "channels", "wechat");
|
|
28
|
+
const LEGACY_ENV_CHANNEL_DATA_DIR = process.env.CLAUDE_WECHAT_CHANNEL_DATA_DIR?.trim()
|
|
29
|
+
? path.resolve(process.env.CLAUDE_WECHAT_CHANNEL_DATA_DIR.trim())
|
|
30
|
+
: "";
|
|
31
|
+
const LEGACY_CHANNEL_SOURCE_DIRS = [
|
|
32
|
+
LEGACY_ENV_CHANNEL_DATA_DIR,
|
|
33
|
+
LEGACY_GLOBAL_CHANNEL_DATA_DIR,
|
|
34
|
+
LEGACY_REPO_CHANNEL_DATA_DIR,
|
|
35
|
+
].filter(Boolean);
|
|
36
|
+
const LEGACY_CHANNEL_SOURCES = LEGACY_CHANNEL_SOURCE_DIRS.map((dataDir) => ({
|
|
37
|
+
dataDir,
|
|
38
|
+
}));
|
|
39
|
+
const LEGACY_MIGRATION_ITEMS = [
|
|
40
|
+
{
|
|
41
|
+
label: "credentials",
|
|
42
|
+
sourceName: "account.json",
|
|
43
|
+
targetName: "account.json",
|
|
44
|
+
kind: "file",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
label: "sync state",
|
|
48
|
+
sourceName: "sync_buf.txt",
|
|
49
|
+
targetName: "sync_buf.txt",
|
|
50
|
+
kind: "file",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
label: "context tokens",
|
|
54
|
+
sourceName: "context_tokens.json",
|
|
55
|
+
targetName: "context_tokens.json",
|
|
56
|
+
kind: "file",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
label: "update check cache",
|
|
60
|
+
sourceName: "update-check.json",
|
|
61
|
+
targetName: "update-check.json",
|
|
62
|
+
kind: "file",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
label: "workspace state",
|
|
66
|
+
sourceName: "workspaces",
|
|
67
|
+
targetName: "workspaces",
|
|
68
|
+
kind: "directory",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
label: "inbound attachments",
|
|
72
|
+
sourceName: "inbound-attachments",
|
|
73
|
+
targetName: "inbound-attachments",
|
|
74
|
+
kind: "directory",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
label: "legacy bridge log",
|
|
78
|
+
sourceName: "bridge.log",
|
|
79
|
+
targetName: "legacy-bridge.log",
|
|
80
|
+
kind: "file",
|
|
81
|
+
},
|
|
82
|
+
];
|
|
26
83
|
export function ensureChannelDataDir() {
|
|
27
84
|
fs.mkdirSync(CHANNEL_DATA_DIR, { recursive: true });
|
|
28
85
|
}
|
|
@@ -58,46 +115,82 @@ export function getWorkspaceChannelPaths(cwd) {
|
|
|
58
115
|
endpointFile: path.join(workspaceDir, "codex-panel-endpoint.json"),
|
|
59
116
|
};
|
|
60
117
|
}
|
|
118
|
+
export function getWorkspaceAdapterEndpointFile(cwd, adapter) {
|
|
119
|
+
return path.join(getWorkspaceChannelPaths(cwd).workspaceDir, `${adapter}-companion-endpoint.json`);
|
|
120
|
+
}
|
|
61
121
|
export function ensureWorkspaceChannelDir(cwd) {
|
|
62
122
|
ensureChannelDataDir();
|
|
63
123
|
const paths = getWorkspaceChannelPaths(cwd);
|
|
64
124
|
fs.mkdirSync(paths.workspaceDir, { recursive: true });
|
|
65
125
|
return paths;
|
|
66
126
|
}
|
|
67
|
-
function
|
|
68
|
-
|
|
69
|
-
|
|
127
|
+
function isSamePath(left, right) {
|
|
128
|
+
const normalizedLeft = path.resolve(left);
|
|
129
|
+
const normalizedRight = path.resolve(right);
|
|
130
|
+
return process.platform === "win32"
|
|
131
|
+
? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase()
|
|
132
|
+
: normalizedLeft === normalizedRight;
|
|
133
|
+
}
|
|
134
|
+
function legacySourceHasMigratableData(source) {
|
|
135
|
+
return LEGACY_MIGRATION_ITEMS.some((item) => {
|
|
136
|
+
const sourcePath = path.join(source.dataDir, item.sourceName);
|
|
137
|
+
if (!fs.existsSync(sourcePath)) {
|
|
70
138
|
return false;
|
|
71
139
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
140
|
+
try {
|
|
141
|
+
const stat = fs.statSync(sourcePath);
|
|
142
|
+
return item.kind === "directory" ? stat.isDirectory() : stat.isFile();
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
77
148
|
}
|
|
78
|
-
|
|
79
|
-
return (
|
|
80
|
-
|
|
81
|
-
!params.channelDataDirHadState);
|
|
149
|
+
function findLegacyChannelSource(channelDataDir = CHANNEL_DATA_DIR, legacySources = LEGACY_CHANNEL_SOURCES) {
|
|
150
|
+
return (legacySources.find((source) => !isSamePath(source.dataDir, channelDataDir) &&
|
|
151
|
+
legacySourceHasMigratableData(source)) ?? null);
|
|
82
152
|
}
|
|
83
|
-
export function migrateLegacyChannelFiles(log) {
|
|
153
|
+
export function migrateLegacyChannelFiles(log, options = {}) {
|
|
154
|
+
const channelDataDir = options.channelDataDir ?? CHANNEL_DATA_DIR;
|
|
155
|
+
const legacySources = (options.legacyDataDirs ?? LEGACY_CHANNEL_SOURCE_DIRS).map((dataDir) => ({ dataDir }));
|
|
84
156
|
const migrated = [];
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
157
|
+
const skippedExisting = [];
|
|
158
|
+
const legacySource = findLegacyChannelSource(channelDataDir, legacySources);
|
|
159
|
+
if (!legacySource) {
|
|
88
160
|
return migrated;
|
|
89
161
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
162
|
+
fs.mkdirSync(channelDataDir, { recursive: true });
|
|
163
|
+
for (const item of LEGACY_MIGRATION_ITEMS) {
|
|
164
|
+
const sourcePath = path.join(legacySource.dataDir, item.sourceName);
|
|
165
|
+
const targetPath = path.join(channelDataDir, item.targetName);
|
|
166
|
+
if (!fs.existsSync(sourcePath)) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (fs.existsSync(targetPath)) {
|
|
170
|
+
skippedExisting.push(item.label);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const stat = fs.statSync(sourcePath);
|
|
174
|
+
if (item.kind === "directory") {
|
|
175
|
+
if (!stat.isDirectory()) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
fs.cpSync(sourcePath, targetPath, { recursive: true });
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
if (!stat.isFile()) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
185
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
186
|
+
}
|
|
187
|
+
migrated.push(item.label);
|
|
98
188
|
}
|
|
99
189
|
if (migrated.length && log) {
|
|
100
|
-
|
|
190
|
+
const skippedText = skippedExisting.length
|
|
191
|
+
? ` Skipped existing: ${skippedExisting.join(", ")}.`
|
|
192
|
+
: "";
|
|
193
|
+
log(`Migrated legacy ${migrated.join(", ")} from ${legacySource.dataDir} to ${channelDataDir}.${skippedText}`);
|
|
101
194
|
}
|
|
102
195
|
return migrated;
|
|
103
196
|
}
|
|
@@ -8,15 +8,13 @@
|
|
|
8
8
|
* Usage:
|
|
9
9
|
* bun run src/wechat/standalone-bot.ts
|
|
10
10
|
*/
|
|
11
|
-
import fs from "node:fs";
|
|
12
|
-
import os from "node:os";
|
|
13
11
|
import crypto from "node:crypto";
|
|
12
|
+
import fs from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
14
|
import Anthropic from "@anthropic-ai/sdk";
|
|
15
|
+
import { CHANNEL_DATA_DIR, CREDENTIALS_FILE, migrateLegacyChannelFiles, SYNC_BUF_FILE, } from "./channel-config.js";
|
|
15
16
|
// Constants
|
|
16
|
-
const
|
|
17
|
-
const CREDENTIALS_FILE = `${HOME_DIR}/.claude/channels/wechat/account.json`;
|
|
18
|
-
const SYNC_BUF_FILE = `${HOME_DIR}/.claude/channels/wechat/sync_buf.txt`;
|
|
19
|
-
const LOG_DIR = `${HOME_DIR}/.claude/channels/wechat/logs`;
|
|
17
|
+
const LOG_DIR = path.join(CHANNEL_DATA_DIR, "logs");
|
|
20
18
|
const BOT_TYPE = "3";
|
|
21
19
|
const CHANNEL_VERSION = "0.1.0";
|
|
22
20
|
const LONG_POLL_TIMEOUT_MS = 35_000;
|
|
@@ -329,6 +327,7 @@ async function startPolling(account) {
|
|
|
329
327
|
async function main() {
|
|
330
328
|
log("=== WeChat + Claude 独立机器人 ===");
|
|
331
329
|
log("");
|
|
330
|
+
migrateLegacyChannelFiles(log);
|
|
332
331
|
// Load WeChat credentials
|
|
333
332
|
log(`正在加载微信凭据: ${CREDENTIALS_FILE}`);
|
|
334
333
|
const account = loadCredentials();
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
9
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
10
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
11
|
-
import { WeChatTransport, } from "./wechat-transport.js";
|
|
11
|
+
import { formatByteSize, WeChatTransport, } from "./wechat-transport.js";
|
|
12
12
|
const SERVER_NAME = "wechat";
|
|
13
13
|
const SERVER_VERSION = "0.3.0";
|
|
14
14
|
const DEFAULT_POLL_TIMEOUT_MS = 1_000;
|
|
@@ -176,15 +176,26 @@ async function fetchMessages(args) {
|
|
|
176
176
|
return formatFetchedMessages(result.messages);
|
|
177
177
|
}
|
|
178
178
|
function formatFetchedMessages(messages) {
|
|
179
|
-
const blocks = messages.map((message, index) =>
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
179
|
+
const blocks = messages.map((message, index) => {
|
|
180
|
+
const lines = [
|
|
181
|
+
`[${index + 1}]`,
|
|
182
|
+
`sender_id: ${message.senderId}`,
|
|
183
|
+
`sender: ${message.sender}`,
|
|
184
|
+
`session_id: ${message.sessionId || "(unknown)"}`,
|
|
185
|
+
`created_at: ${message.createdAt}`,
|
|
186
|
+
"text:",
|
|
187
|
+
message.text,
|
|
188
|
+
];
|
|
189
|
+
if (message.attachments.length > 0) {
|
|
190
|
+
lines.push("attachments:", ...message.attachments.map((attachment) => [
|
|
191
|
+
`- kind: ${attachment.kind}`,
|
|
192
|
+
` name: ${attachment.fileName}`,
|
|
193
|
+
` size: ${formatByteSize(attachment.sizeBytes)}`,
|
|
194
|
+
` path: ${attachment.path}`,
|
|
195
|
+
].join("\n")));
|
|
196
|
+
}
|
|
197
|
+
return lines.join("\n");
|
|
198
|
+
});
|
|
188
199
|
return [
|
|
189
200
|
`Fetched ${messages.length} new WeChat message${messages.length === 1 ? "" : "s"}.`,
|
|
190
201
|
"",
|