weixin-mcp 1.3.0 → 1.3.1
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/dist/accounts.d.ts +8 -0
- package/dist/accounts.js +132 -0
- package/dist/cli.js +15 -7
- package/dist/login.js +2 -1
- package/package.json +1 -1
- package/src/accounts.ts +132 -0
- package/src/cli.ts +15 -7
- package/src/login.ts +2 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account management commands:
|
|
3
|
+
* npx weixin-mcp accounts list — list all accounts (default)
|
|
4
|
+
* npx weixin-mcp accounts remove <id> — remove a specific account
|
|
5
|
+
* npx weixin-mcp accounts clean — remove duplicate accounts (same userId), keep newest
|
|
6
|
+
* npx weixin-mcp accounts use <id> — print export WEIXIN_ACCOUNT_ID=<id>
|
|
7
|
+
*/
|
|
8
|
+
export declare function manageAccounts(args: string[]): Promise<void>;
|
package/dist/accounts.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account management commands:
|
|
3
|
+
* npx weixin-mcp accounts list — list all accounts (default)
|
|
4
|
+
* npx weixin-mcp accounts remove <id> — remove a specific account
|
|
5
|
+
* npx weixin-mcp accounts clean — remove duplicate accounts (same userId), keep newest
|
|
6
|
+
* npx weixin-mcp accounts use <id> — print export WEIXIN_ACCOUNT_ID=<id>
|
|
7
|
+
*/
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { ACCOUNTS_DIR } from "./paths.js";
|
|
11
|
+
function listFiles() {
|
|
12
|
+
try {
|
|
13
|
+
return fs
|
|
14
|
+
.readdirSync(ACCOUNTS_DIR)
|
|
15
|
+
.filter((f) => f.endsWith(".json") && !f.endsWith(".sync.json") && !f.endsWith(".cursor.json"));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function loadAccount(file) {
|
|
22
|
+
const accountId = file.replace(".json", "");
|
|
23
|
+
const data = JSON.parse(fs.readFileSync(path.join(ACCOUNTS_DIR, file), "utf-8"));
|
|
24
|
+
return { ...data, accountId };
|
|
25
|
+
}
|
|
26
|
+
function removeAccount(accountId) {
|
|
27
|
+
const filePath = path.join(ACCOUNTS_DIR, `${accountId}.json`);
|
|
28
|
+
const cursorPath = path.join(ACCOUNTS_DIR, `${accountId}.cursor.json`);
|
|
29
|
+
let removed = false;
|
|
30
|
+
if (fs.existsSync(filePath)) {
|
|
31
|
+
fs.unlinkSync(filePath);
|
|
32
|
+
removed = true;
|
|
33
|
+
}
|
|
34
|
+
if (fs.existsSync(cursorPath)) {
|
|
35
|
+
fs.unlinkSync(cursorPath);
|
|
36
|
+
}
|
|
37
|
+
return removed;
|
|
38
|
+
}
|
|
39
|
+
export async function manageAccounts(args) {
|
|
40
|
+
const subcommand = args[0] ?? "list";
|
|
41
|
+
if (subcommand === "list") {
|
|
42
|
+
const files = listFiles();
|
|
43
|
+
if (files.length === 0) {
|
|
44
|
+
console.log("No accounts found. Run: npx weixin-mcp login");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const active = process.env.WEIXIN_ACCOUNT_ID ?? files[0].replace(".json", "");
|
|
48
|
+
console.log(`Accounts (${files.length}):\n`);
|
|
49
|
+
for (const file of files) {
|
|
50
|
+
const acc = loadAccount(file);
|
|
51
|
+
const isActive = acc.accountId === active;
|
|
52
|
+
const savedAt = acc.savedAt ? new Date(acc.savedAt).toLocaleString() : "unknown";
|
|
53
|
+
console.log(`${isActive ? "● " : " "}${acc.accountId}`);
|
|
54
|
+
const displayId = acc.userId?.replace(/@im\.wechat(@im\.wechat)+$/, "@im.wechat") ?? "(unknown)";
|
|
55
|
+
console.log(` User: ${displayId}`);
|
|
56
|
+
console.log(` Saved: ${savedAt}`);
|
|
57
|
+
}
|
|
58
|
+
if (files.length > 1) {
|
|
59
|
+
console.log(`\nActive: ${active} (set WEIXIN_ACCOUNT_ID to change)`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if (subcommand === "remove") {
|
|
63
|
+
const accountId = args[1];
|
|
64
|
+
if (!accountId) {
|
|
65
|
+
console.error("Usage: accounts remove <accountId>");
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
if (removeAccount(accountId)) {
|
|
69
|
+
console.log(`✅ Removed: ${accountId}`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.error(`❌ Account not found: ${accountId}`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (subcommand === "clean") {
|
|
77
|
+
const files = listFiles();
|
|
78
|
+
const byUserId = new Map();
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
const acc = loadAccount(file);
|
|
81
|
+
// Normalize userId: strip duplicate @im.wechat suffix
|
|
82
|
+
const rawId = acc.userId ?? acc.accountId;
|
|
83
|
+
const key = rawId.replace(/@im\.wechat(@im\.wechat)+$/, "@im.wechat");
|
|
84
|
+
if (!byUserId.has(key))
|
|
85
|
+
byUserId.set(key, []);
|
|
86
|
+
byUserId.get(key).push(acc);
|
|
87
|
+
}
|
|
88
|
+
let removed = 0;
|
|
89
|
+
for (const [userId, accounts] of byUserId) {
|
|
90
|
+
if (accounts.length <= 1)
|
|
91
|
+
continue;
|
|
92
|
+
// Sort by savedAt desc — keep newest
|
|
93
|
+
accounts.sort((a, b) => {
|
|
94
|
+
const ta = a.savedAt ? new Date(a.savedAt).getTime() : 0;
|
|
95
|
+
const tb = b.savedAt ? new Date(b.savedAt).getTime() : 0;
|
|
96
|
+
return tb - ta;
|
|
97
|
+
});
|
|
98
|
+
const [keep, ...remove] = accounts;
|
|
99
|
+
console.log(`UserId: ${userId}`);
|
|
100
|
+
console.log(` ✓ Keep: ${keep.accountId} (${keep.savedAt ?? "?"})`);
|
|
101
|
+
for (const acc of remove) {
|
|
102
|
+
removeAccount(acc.accountId);
|
|
103
|
+
console.log(` ✗ Removed: ${acc.accountId} (${acc.savedAt ?? "?"})`);
|
|
104
|
+
removed++;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (removed === 0) {
|
|
108
|
+
console.log("✅ No duplicates found.");
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
console.log(`\n✅ Cleaned ${removed} duplicate account(s).`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else if (subcommand === "use") {
|
|
115
|
+
const accountId = args[1];
|
|
116
|
+
if (!accountId) {
|
|
117
|
+
console.error("Usage: accounts use <accountId>");
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
const filePath = path.join(ACCOUNTS_DIR, `${accountId}.json`);
|
|
121
|
+
if (!fs.existsSync(filePath)) {
|
|
122
|
+
console.error(`❌ Account not found: ${accountId}`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
console.log(`export WEIXIN_ACCOUNT_ID="${accountId}"`);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
console.error(`Unknown accounts subcommand: ${subcommand}`);
|
|
129
|
+
console.error("Usage: accounts [list|remove <id>|clean|use <id>]");
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -41,6 +41,10 @@ else if (command === "logs") {
|
|
|
41
41
|
const { showLogs } = await import("./daemon.js");
|
|
42
42
|
showLogs(follow);
|
|
43
43
|
}
|
|
44
|
+
else if (command === "accounts") {
|
|
45
|
+
const { manageAccounts } = await import("./accounts.js");
|
|
46
|
+
await manageAccounts(process.argv.slice(3)); // [subcommand, ...args]
|
|
47
|
+
}
|
|
44
48
|
else if (command === undefined || command === "serve") {
|
|
45
49
|
// Default: stdio MCP server (for Claude Desktop integration)
|
|
46
50
|
await import("./index.js");
|
|
@@ -51,13 +55,17 @@ else {
|
|
|
51
55
|
Usage: npx weixin-mcp [command]
|
|
52
56
|
|
|
53
57
|
Commands:
|
|
54
|
-
(no args)
|
|
55
|
-
login
|
|
56
|
-
status
|
|
57
|
-
start [--port n]
|
|
58
|
-
stop
|
|
59
|
-
restart
|
|
60
|
-
logs [-f]
|
|
58
|
+
(no args) Start stdio MCP server (Claude Desktop mode)
|
|
59
|
+
login QR code login
|
|
60
|
+
status Show account and daemon status
|
|
61
|
+
start [--port n] Start HTTP MCP daemon in background (default: 3001)
|
|
62
|
+
stop Stop daemon
|
|
63
|
+
restart Restart daemon
|
|
64
|
+
logs [-f] Show daemon logs (-f to follow)
|
|
65
|
+
accounts [list] List all accounts
|
|
66
|
+
accounts remove <id> Remove an account
|
|
67
|
+
accounts clean Remove duplicate accounts (same userId), keep newest
|
|
68
|
+
accounts use <id> Print export command for switching accounts
|
|
61
69
|
`);
|
|
62
70
|
process.exit(1);
|
|
63
71
|
}
|
package/dist/login.js
CHANGED
|
@@ -76,7 +76,8 @@ export async function main() {
|
|
|
76
76
|
// Use bot_id or a random ID as account identifier
|
|
77
77
|
const accountId = status.ilink_bot_id?.replace("@", "-").replace(".", "-")
|
|
78
78
|
?? crypto.randomBytes(6).toString("hex") + "-im-bot";
|
|
79
|
-
|
|
79
|
+
// userId from API already contains @im.wechat suffix — don't append again
|
|
80
|
+
saveAccount(accountId, token, baseUrl, userId ?? undefined);
|
|
80
81
|
console.log(`\n🎉 Logged in! Account: ${accountId}`);
|
|
81
82
|
console.log(` UserId: ${userId ?? "(unknown)"}`);
|
|
82
83
|
console.log("\nYou can now start the MCP server:");
|
package/package.json
CHANGED
package/src/accounts.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account management commands:
|
|
3
|
+
* npx weixin-mcp accounts list — list all accounts (default)
|
|
4
|
+
* npx weixin-mcp accounts remove <id> — remove a specific account
|
|
5
|
+
* npx weixin-mcp accounts clean — remove duplicate accounts (same userId), keep newest
|
|
6
|
+
* npx weixin-mcp accounts use <id> — print export WEIXIN_ACCOUNT_ID=<id>
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { ACCOUNTS_DIR } from "./paths.js";
|
|
12
|
+
|
|
13
|
+
interface AccountData {
|
|
14
|
+
token?: string;
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
userId?: string;
|
|
17
|
+
savedAt?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function listFiles(): string[] {
|
|
21
|
+
try {
|
|
22
|
+
return fs
|
|
23
|
+
.readdirSync(ACCOUNTS_DIR)
|
|
24
|
+
.filter((f) => f.endsWith(".json") && !f.endsWith(".sync.json") && !f.endsWith(".cursor.json"));
|
|
25
|
+
} catch {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function loadAccount(file: string): AccountData & { accountId: string } {
|
|
31
|
+
const accountId = file.replace(".json", "");
|
|
32
|
+
const data = JSON.parse(fs.readFileSync(path.join(ACCOUNTS_DIR, file), "utf-8")) as AccountData;
|
|
33
|
+
return { ...data, accountId };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function removeAccount(accountId: string) {
|
|
37
|
+
const filePath = path.join(ACCOUNTS_DIR, `${accountId}.json`);
|
|
38
|
+
const cursorPath = path.join(ACCOUNTS_DIR, `${accountId}.cursor.json`);
|
|
39
|
+
let removed = false;
|
|
40
|
+
if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); removed = true; }
|
|
41
|
+
if (fs.existsSync(cursorPath)) { fs.unlinkSync(cursorPath); }
|
|
42
|
+
return removed;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function manageAccounts(args: string[]) {
|
|
46
|
+
const subcommand = args[0] ?? "list";
|
|
47
|
+
|
|
48
|
+
if (subcommand === "list") {
|
|
49
|
+
const files = listFiles();
|
|
50
|
+
if (files.length === 0) {
|
|
51
|
+
console.log("No accounts found. Run: npx weixin-mcp login");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const active = process.env.WEIXIN_ACCOUNT_ID ?? files[0].replace(".json", "");
|
|
55
|
+
console.log(`Accounts (${files.length}):\n`);
|
|
56
|
+
for (const file of files) {
|
|
57
|
+
const acc = loadAccount(file);
|
|
58
|
+
const isActive = acc.accountId === active;
|
|
59
|
+
const savedAt = acc.savedAt ? new Date(acc.savedAt).toLocaleString() : "unknown";
|
|
60
|
+
console.log(`${isActive ? "● " : " "}${acc.accountId}`);
|
|
61
|
+
const displayId = acc.userId?.replace(/@im\.wechat(@im\.wechat)+$/, "@im.wechat") ?? "(unknown)";
|
|
62
|
+
console.log(` User: ${displayId}`);
|
|
63
|
+
console.log(` Saved: ${savedAt}`);
|
|
64
|
+
}
|
|
65
|
+
if (files.length > 1) {
|
|
66
|
+
console.log(`\nActive: ${active} (set WEIXIN_ACCOUNT_ID to change)`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
} else if (subcommand === "remove") {
|
|
70
|
+
const accountId = args[1];
|
|
71
|
+
if (!accountId) { console.error("Usage: accounts remove <accountId>"); process.exit(1); }
|
|
72
|
+
if (removeAccount(accountId)) {
|
|
73
|
+
console.log(`✅ Removed: ${accountId}`);
|
|
74
|
+
} else {
|
|
75
|
+
console.error(`❌ Account not found: ${accountId}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
} else if (subcommand === "clean") {
|
|
80
|
+
const files = listFiles();
|
|
81
|
+
const byUserId = new Map<string, Array<AccountData & { accountId: string }>>();
|
|
82
|
+
|
|
83
|
+
for (const file of files) {
|
|
84
|
+
const acc = loadAccount(file);
|
|
85
|
+
// Normalize userId: strip duplicate @im.wechat suffix
|
|
86
|
+
const rawId = acc.userId ?? acc.accountId;
|
|
87
|
+
const key = rawId.replace(/@im\.wechat(@im\.wechat)+$/, "@im.wechat");
|
|
88
|
+
if (!byUserId.has(key)) byUserId.set(key, []);
|
|
89
|
+
byUserId.get(key)!.push(acc);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let removed = 0;
|
|
93
|
+
for (const [userId, accounts] of byUserId) {
|
|
94
|
+
if (accounts.length <= 1) continue;
|
|
95
|
+
// Sort by savedAt desc — keep newest
|
|
96
|
+
accounts.sort((a, b) => {
|
|
97
|
+
const ta = a.savedAt ? new Date(a.savedAt).getTime() : 0;
|
|
98
|
+
const tb = b.savedAt ? new Date(b.savedAt).getTime() : 0;
|
|
99
|
+
return tb - ta;
|
|
100
|
+
});
|
|
101
|
+
const [keep, ...remove] = accounts;
|
|
102
|
+
console.log(`UserId: ${userId}`);
|
|
103
|
+
console.log(` ✓ Keep: ${keep.accountId} (${keep.savedAt ?? "?"})`);
|
|
104
|
+
for (const acc of remove) {
|
|
105
|
+
removeAccount(acc.accountId);
|
|
106
|
+
console.log(` ✗ Removed: ${acc.accountId} (${acc.savedAt ?? "?"})`);
|
|
107
|
+
removed++;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (removed === 0) {
|
|
112
|
+
console.log("✅ No duplicates found.");
|
|
113
|
+
} else {
|
|
114
|
+
console.log(`\n✅ Cleaned ${removed} duplicate account(s).`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
} else if (subcommand === "use") {
|
|
118
|
+
const accountId = args[1];
|
|
119
|
+
if (!accountId) { console.error("Usage: accounts use <accountId>"); process.exit(1); }
|
|
120
|
+
const filePath = path.join(ACCOUNTS_DIR, `${accountId}.json`);
|
|
121
|
+
if (!fs.existsSync(filePath)) {
|
|
122
|
+
console.error(`❌ Account not found: ${accountId}`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
console.log(`export WEIXIN_ACCOUNT_ID="${accountId}"`);
|
|
126
|
+
|
|
127
|
+
} else {
|
|
128
|
+
console.error(`Unknown accounts subcommand: ${subcommand}`);
|
|
129
|
+
console.error("Usage: accounts [list|remove <id>|clean|use <id>]");
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -43,6 +43,10 @@ if (command === "login") {
|
|
|
43
43
|
const { showLogs } = await import("./daemon.js");
|
|
44
44
|
showLogs(follow);
|
|
45
45
|
|
|
46
|
+
} else if (command === "accounts") {
|
|
47
|
+
const { manageAccounts } = await import("./accounts.js");
|
|
48
|
+
await manageAccounts(process.argv.slice(3)); // [subcommand, ...args]
|
|
49
|
+
|
|
46
50
|
} else if (command === undefined || command === "serve") {
|
|
47
51
|
// Default: stdio MCP server (for Claude Desktop integration)
|
|
48
52
|
await import("./index.js");
|
|
@@ -53,13 +57,17 @@ if (command === "login") {
|
|
|
53
57
|
Usage: npx weixin-mcp [command]
|
|
54
58
|
|
|
55
59
|
Commands:
|
|
56
|
-
(no args)
|
|
57
|
-
login
|
|
58
|
-
status
|
|
59
|
-
start [--port n]
|
|
60
|
-
stop
|
|
61
|
-
restart
|
|
62
|
-
logs [-f]
|
|
60
|
+
(no args) Start stdio MCP server (Claude Desktop mode)
|
|
61
|
+
login QR code login
|
|
62
|
+
status Show account and daemon status
|
|
63
|
+
start [--port n] Start HTTP MCP daemon in background (default: 3001)
|
|
64
|
+
stop Stop daemon
|
|
65
|
+
restart Restart daemon
|
|
66
|
+
logs [-f] Show daemon logs (-f to follow)
|
|
67
|
+
accounts [list] List all accounts
|
|
68
|
+
accounts remove <id> Remove an account
|
|
69
|
+
accounts clean Remove duplicate accounts (same userId), keep newest
|
|
70
|
+
accounts use <id> Print export command for switching accounts
|
|
63
71
|
`);
|
|
64
72
|
process.exit(1);
|
|
65
73
|
}
|
package/src/login.ts
CHANGED
|
@@ -104,7 +104,8 @@ export async function main() {
|
|
|
104
104
|
// Use bot_id or a random ID as account identifier
|
|
105
105
|
const accountId = status.ilink_bot_id?.replace("@", "-").replace(".", "-")
|
|
106
106
|
?? crypto.randomBytes(6).toString("hex") + "-im-bot";
|
|
107
|
-
|
|
107
|
+
// userId from API already contains @im.wechat suffix — don't append again
|
|
108
|
+
saveAccount(accountId, token, baseUrl, userId ?? undefined);
|
|
108
109
|
console.log(`\n🎉 Logged in! Account: ${accountId}`);
|
|
109
110
|
console.log(` UserId: ${userId ?? "(unknown)"}`);
|
|
110
111
|
console.log("\nYou can now start the MCP server:");
|