wechat-claude-sessions 1.0.0

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 (62) hide show
  1. package/CONTRIBUTING.md +52 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +264 -0
  4. package/README.md +245 -0
  5. package/SECURITY.md +45 -0
  6. package/dist/bindings.d.ts +4 -0
  7. package/dist/bindings.js +50 -0
  8. package/dist/bindings.js.map +1 -0
  9. package/dist/claude-config.d.ts +4 -0
  10. package/dist/claude-config.js +49 -0
  11. package/dist/claude-config.js.map +1 -0
  12. package/dist/cli.d.ts +2 -0
  13. package/dist/cli.js +345 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/daemon.d.ts +2 -0
  16. package/dist/daemon.js +771 -0
  17. package/dist/daemon.js.map +1 -0
  18. package/dist/i18n.d.ts +67 -0
  19. package/dist/i18n.js +249 -0
  20. package/dist/i18n.js.map +1 -0
  21. package/dist/ilink.d.ts +49 -0
  22. package/dist/ilink.js +533 -0
  23. package/dist/ilink.js.map +1 -0
  24. package/dist/inbox.d.ts +4 -0
  25. package/dist/inbox.js +75 -0
  26. package/dist/inbox.js.map +1 -0
  27. package/dist/launchd.d.ts +7 -0
  28. package/dist/launchd.js +71 -0
  29. package/dist/launchd.js.map +1 -0
  30. package/dist/monitoring.d.ts +4 -0
  31. package/dist/monitoring.js +33 -0
  32. package/dist/monitoring.js.map +1 -0
  33. package/dist/paths.d.ts +11 -0
  34. package/dist/paths.js +35 -0
  35. package/dist/paths.js.map +1 -0
  36. package/dist/pkg-root.d.ts +2 -0
  37. package/dist/pkg-root.js +11 -0
  38. package/dist/pkg-root.js.map +1 -0
  39. package/dist/server.d.ts +2 -0
  40. package/dist/server.js +364 -0
  41. package/dist/server.js.map +1 -0
  42. package/dist/session-numbers.d.ts +1 -0
  43. package/dist/session-numbers.js +44 -0
  44. package/dist/session-numbers.js.map +1 -0
  45. package/dist/sessions.d.ts +15 -0
  46. package/dist/sessions.js +135 -0
  47. package/dist/sessions.js.map +1 -0
  48. package/dist/tmux.d.ts +7 -0
  49. package/dist/tmux.js +47 -0
  50. package/dist/tmux.js.map +1 -0
  51. package/dist/types.d.ts +150 -0
  52. package/dist/types.js +2 -0
  53. package/dist/types.js.map +1 -0
  54. package/dist/utils.d.ts +12 -0
  55. package/dist/utils.js +88 -0
  56. package/dist/utils.js.map +1 -0
  57. package/dist/watch-inbox.d.ts +2 -0
  58. package/dist/watch-inbox.js +102 -0
  59. package/dist/watch-inbox.js.map +1 -0
  60. package/package.json +50 -0
  61. package/templates/com.wechat-claude.daemon.plist.template +31 -0
  62. package/templates/wechat.md +20 -0
@@ -0,0 +1,135 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { isMonitoring } from "./monitoring.js";
4
+ import { INBOX_DIR, SESSIONS_DIR, isProcessAlive } from "./paths.js";
5
+ import { assignSessionNumbers } from "./session-numbers.js";
6
+ // Derive a human-friendly session name from a working directory:
7
+ // "repo:branch" for a git repo, "repo/worktree" for a worktree, else basename.
8
+ export function detectSessionName(cwd) {
9
+ const worktreeMatch = cwd.match(/\.claude[/\\]worktrees[/\\]([^/\\]+)/);
10
+ if (worktreeMatch) {
11
+ const repoPath = cwd
12
+ .split(/\.claude[/\\]worktrees[/\\]/)[0]
13
+ .replace(/[/\\]$/, "");
14
+ return `${path.basename(repoPath)}/${worktreeMatch[1]}`;
15
+ }
16
+ const repoName = path.basename(cwd);
17
+ try {
18
+ let gitDir = path.join(cwd, ".git");
19
+ const gitStat = fs.statSync(gitDir);
20
+ if (!gitStat.isDirectory()) {
21
+ const content = fs.readFileSync(gitDir, "utf-8").trim();
22
+ const m = content.match(/gitdir:\s*(.+)/);
23
+ if (m)
24
+ gitDir = path.resolve(cwd, m[1]);
25
+ }
26
+ const head = fs.readFileSync(path.join(gitDir, "HEAD"), "utf-8").trim();
27
+ const branchMatch = head.match(/ref:\s*refs\/heads\/(.+)/);
28
+ if (branchMatch)
29
+ return `${repoName}:${branchMatch[1]}`;
30
+ }
31
+ catch { }
32
+ return repoName || "session";
33
+ }
34
+ export function writeSessionFile(info) {
35
+ fs.writeFileSync(path.join(SESSIONS_DIR, `${info.id}.json`), JSON.stringify(info));
36
+ }
37
+ // Live sessions, cleaning up files for dead processes. Sorted by pid.
38
+ export function listSessions() {
39
+ const sessions = [];
40
+ try {
41
+ for (const file of fs.readdirSync(SESSIONS_DIR)) {
42
+ if (!file.endsWith(".json"))
43
+ continue;
44
+ try {
45
+ const info = JSON.parse(fs.readFileSync(path.join(SESSIONS_DIR, file), "utf-8"));
46
+ if (isProcessAlive(info.pid)) {
47
+ sessions.push(info);
48
+ }
49
+ else {
50
+ fs.unlinkSync(path.join(SESSIONS_DIR, file));
51
+ try {
52
+ fs.unlinkSync(path.join(INBOX_DIR, file));
53
+ }
54
+ catch { }
55
+ }
56
+ }
57
+ catch { }
58
+ }
59
+ }
60
+ catch { }
61
+ return sessions.sort((a, b) => a.pid - b.pid);
62
+ }
63
+ // Listing/selection order: monitored sessions first, stable pid order within
64
+ // each group. /sessions numbering and "/s <number>" both use this order.
65
+ export function sortedSessions() {
66
+ return listSessions().sort((a, b) => {
67
+ const ma = isMonitoring(a.id) ? 1 : 0;
68
+ const mb = isMonitoring(b.id) ? 1 : 0;
69
+ if (ma !== mb)
70
+ return mb - ma;
71
+ return a.pid - b.pid;
72
+ });
73
+ }
74
+ // Where a plain (non-command) message goes: prefer monitored sessions, then
75
+ // the most recently active among them.
76
+ export function getDefaultTarget(sessions) {
77
+ const monitored = sessions.filter((s) => isMonitoring(s.id));
78
+ const pool = monitored.length > 0 ? monitored : sessions;
79
+ if (pool.length === 0)
80
+ return undefined;
81
+ return pool.reduce((a, b) => (a.lastActive > b.lastActive ? a : b));
82
+ }
83
+ // Resolve a selector (stable number, pid, exact name, or fuzzy substring) to a
84
+ // single session. On an ambiguous name, prefer monitored then most-recent.
85
+ export function findSession(selector) {
86
+ const sessions = sortedSessions();
87
+ const num = parseInt(selector, 10);
88
+ if (!isNaN(num) && String(num) === selector) {
89
+ const numbers = assignSessionNumbers(sessions.map((s) => s.id));
90
+ const byNum = sessions.find((s) => numbers[s.id] === num);
91
+ if (byNum)
92
+ return byNum;
93
+ }
94
+ const byPid = sessions.find((s) => String(s.pid) === selector);
95
+ if (byPid)
96
+ return byPid;
97
+ const lower = selector.toLowerCase();
98
+ const exact = sessions.filter((s) => s.name.toLowerCase() === lower);
99
+ const pool = exact.length > 0
100
+ ? exact
101
+ : sessions.filter((s) => s.name.toLowerCase().includes(lower));
102
+ if (pool.length === 0)
103
+ return undefined;
104
+ if (pool.length === 1)
105
+ return pool[0];
106
+ const monitored = pool.filter((s) => isMonitoring(s.id));
107
+ const finalPool = monitored.length > 0 ? monitored : pool;
108
+ return finalPool.reduce((a, b) => (a.lastActive > b.lastActive ? a : b));
109
+ }
110
+ // All sessions matching a selector (number and pid are unique; a name can
111
+ // match several) — used by /close for batch operations.
112
+ export function matchSessions(selector) {
113
+ const sessions = sortedSessions();
114
+ const num = parseInt(selector, 10);
115
+ if (!isNaN(num) && String(num) === selector) {
116
+ const numbers = assignSessionNumbers(sessions.map((s) => s.id));
117
+ const byNum = sessions.filter((s) => numbers[s.id] === num);
118
+ if (byNum.length > 0)
119
+ return byNum;
120
+ }
121
+ const byPid = sessions.filter((s) => String(s.pid) === selector);
122
+ if (byPid.length > 0)
123
+ return byPid;
124
+ const lower = selector.toLowerCase();
125
+ const exact = sessions.filter((s) => s.name.toLowerCase() === lower);
126
+ if (exact.length > 0)
127
+ return exact;
128
+ return sessions.filter((s) => s.name.toLowerCase().includes(lower));
129
+ }
130
+ // Display label; appends #pid when the name is shared by multiple sessions.
131
+ export function sessionLabel(s, all) {
132
+ const dup = all.filter((o) => o.name === s.name).length > 1;
133
+ return dup ? `${s.name}#${s.pid}` : s.name;
134
+ }
135
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"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"}
package/dist/tmux.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export declare function hasTmux(): boolean;
2
+ export declare function tmuxSessionExists(name: string): boolean;
3
+ export declare function listTmuxSessions(): string[];
4
+ export declare function killTmuxSession(name: string): boolean;
5
+ export declare function newTmuxSession(name: string, cwd: string, shellCmd: string): string | null;
6
+ export declare function isSafeSessionName(name: string): boolean;
7
+ export declare function sanitizeForSessionName(s: string): string;
package/dist/tmux.js ADDED
@@ -0,0 +1,47 @@
1
+ import { spawnSync } from "node:child_process";
2
+ // All tmux interaction goes through argv arrays (never a shell string), so
3
+ // session names and directories from user input cannot inject shell commands.
4
+ export function hasTmux() {
5
+ const r = spawnSync("tmux", ["-V"], { stdio: "ignore" });
6
+ return r.status === 0;
7
+ }
8
+ export function tmuxSessionExists(name) {
9
+ const r = spawnSync("tmux", ["has-session", "-t", name], { stdio: "ignore" });
10
+ return r.status === 0;
11
+ }
12
+ export function listTmuxSessions() {
13
+ const r = spawnSync("tmux", ["ls", "-F", "#{session_name}"], {
14
+ stdio: ["ignore", "pipe", "ignore"],
15
+ });
16
+ if (r.status !== 0 || !r.stdout)
17
+ return [];
18
+ return r.stdout
19
+ .toString()
20
+ .split("\n")
21
+ .map((s) => s.trim())
22
+ .filter(Boolean);
23
+ }
24
+ export function killTmuxSession(name) {
25
+ const r = spawnSync("tmux", ["kill-session", "-t", name], { stdio: "ignore" });
26
+ return r.status === 0;
27
+ }
28
+ // Start a detached session running shellCmd (executed by tmux's own shell, so
29
+ // shellCmd itself must already be a safe shell string — see buildRunPrompt's
30
+ // file-based task passing). Returns null on success or an error string.
31
+ export function newTmuxSession(name, cwd, shellCmd) {
32
+ const r = spawnSync("tmux", ["new-session", "-d", "-s", name, "-c", cwd, shellCmd], { stdio: "ignore" });
33
+ if (r.error)
34
+ return String(r.error);
35
+ if (r.status !== 0)
36
+ return `tmux exited with ${r.status}`;
37
+ return null;
38
+ }
39
+ // A tmux session name safe to embed and to hand back to the user: no shell
40
+ // metacharacters, no spaces.
41
+ export function isSafeSessionName(name) {
42
+ return /^[A-Za-z0-9_.-]+$/.test(name);
43
+ }
44
+ export function sanitizeForSessionName(s) {
45
+ return s.replace(/[^A-Za-z0-9_.-]/g, "-").replace(/-+/g, "-").slice(0, 40) || "x";
46
+ }
47
+ //# sourceMappingURL=tmux.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tmux.js","sourceRoot":"","sources":["../src/tmux.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,2EAA2E;AAC3E,8EAA8E;AAE9E,MAAM,UAAU,OAAO;IACrB,MAAM,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IACzD,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC9E,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,MAAM,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,iBAAiB,CAAC,EAAE;QAC3D,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;KACpC,CAAC,CAAC;IACH,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAC3C,OAAO,CAAC,CAAC,MAAM;SACZ,QAAQ,EAAE;SACV,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC/E,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AACxB,CAAC;AAED,8EAA8E;AAC9E,6EAA6E;AAC7E,wEAAwE;AACxE,MAAM,UAAU,cAAc,CAC5B,IAAY,EACZ,GAAW,EACX,QAAgB;IAEhB,MAAM,CAAC,GAAG,SAAS,CACjB,MAAM,EACN,CAAC,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAC,EACtD,EAAE,KAAK,EAAE,QAAQ,EAAE,CACpB,CAAC;IACF,IAAI,CAAC,CAAC,KAAK;QAAE,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACpC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,oBAAoB,CAAC,CAAC,MAAM,EAAE,CAAC;IAC1D,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2EAA2E;AAC3E,6BAA6B;AAC7B,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,CAAS;IAC9C,OAAO,CAAC,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;AACpF,CAAC"}
@@ -0,0 +1,150 @@
1
+ export type QRCodeResponse = {
2
+ qrcode: string;
3
+ qrcode_img_content: string;
4
+ };
5
+ export type QRCodeStatus = "wait" | "scaned" | "confirmed" | "expired";
6
+ export type QRCodeStatusResponse = {
7
+ status: QRCodeStatus;
8
+ bot_token?: string;
9
+ ilink_bot_id?: string;
10
+ ilink_user_id?: string;
11
+ baseurl?: string;
12
+ };
13
+ export type BaseInfo = {
14
+ channel_version: string;
15
+ };
16
+ export type CDNMedia = {
17
+ encrypt_query_param: string;
18
+ aes_key: string;
19
+ encrypt_type: number;
20
+ };
21
+ export type TextItem = {
22
+ type: 1;
23
+ text_item: {
24
+ text: string;
25
+ };
26
+ };
27
+ export type ImageItem = {
28
+ type: 2;
29
+ image_item: {
30
+ media: CDNMedia;
31
+ thumb_media?: CDNMedia;
32
+ aeskey?: string;
33
+ url?: string;
34
+ thumb_height?: number;
35
+ thumb_width?: number;
36
+ };
37
+ };
38
+ export type VoiceItem = {
39
+ type: 3;
40
+ voice_item: {
41
+ media: CDNMedia;
42
+ encode_type: number;
43
+ bits_per_sample?: number;
44
+ sample_rate?: number;
45
+ playtime?: number;
46
+ text?: string;
47
+ };
48
+ };
49
+ export type FileItem = {
50
+ type: 4;
51
+ file_item: {
52
+ media: CDNMedia;
53
+ file_name: string;
54
+ md5: string;
55
+ len: string;
56
+ };
57
+ };
58
+ export type VideoItem = {
59
+ type: 5;
60
+ video_item: {
61
+ media: CDNMedia;
62
+ video_size: number;
63
+ play_length: number;
64
+ video_md5: string;
65
+ thumb_media?: CDNMedia;
66
+ thumb_size?: number;
67
+ thumb_height?: number;
68
+ thumb_width?: number;
69
+ };
70
+ };
71
+ export type MessageItem = TextItem | ImageItem | VoiceItem | FileItem | VideoItem;
72
+ export type WeixinMessage = {
73
+ seq: number;
74
+ message_id: string;
75
+ from_user_id: string;
76
+ to_user_id: string;
77
+ client_id: string;
78
+ create_time_ms: number;
79
+ update_time_ms: number;
80
+ delete_time_ms: number;
81
+ session_id: string;
82
+ group_id: string;
83
+ message_type: 1 | 2;
84
+ message_state: 0 | 1 | 2;
85
+ context_token: string;
86
+ item_list: MessageItem[];
87
+ };
88
+ export type GetUpdatesResponse = {
89
+ ret: number;
90
+ msgs: WeixinMessage[];
91
+ get_updates_buf: string;
92
+ longpolling_timeout_ms: number;
93
+ };
94
+ export type GetConfigResponse = {
95
+ ret: number;
96
+ typing_ticket: string;
97
+ };
98
+ export type SendTypingRequest = {
99
+ ilink_user_id: string;
100
+ typing_ticket: string;
101
+ status: 1 | 2;
102
+ base_info: BaseInfo;
103
+ };
104
+ export type SendMessageRequest = {
105
+ msg: {
106
+ from_user_id: string;
107
+ to_user_id: string;
108
+ client_id: string;
109
+ message_type: 2;
110
+ message_state: 2;
111
+ context_token: string;
112
+ item_list: MessageItem[];
113
+ };
114
+ base_info: BaseInfo;
115
+ };
116
+ export type GetUploadUrlRequest = {
117
+ filekey: string;
118
+ media_type: number;
119
+ to_user_id: string;
120
+ rawsize: number;
121
+ rawfilemd5: string;
122
+ filesize: number;
123
+ no_need_thumb: boolean;
124
+ aeskey: string;
125
+ base_info: BaseInfo;
126
+ };
127
+ export type GetUploadUrlResponse = {
128
+ upload_param?: string;
129
+ upload_full_url?: string;
130
+ };
131
+ export type UploadedMedia = {
132
+ downloadParam: string;
133
+ aesKeyHex: string;
134
+ fileSize: number;
135
+ ciphertextSize: number;
136
+ };
137
+ export type Session = {
138
+ botToken: string;
139
+ ilinkBotId: string;
140
+ ilinkUserId: string;
141
+ baseUrl: string;
142
+ };
143
+ export type PendingMessage = {
144
+ id: string;
145
+ fromUserId: string;
146
+ text: string;
147
+ contextToken: string;
148
+ timestamp: number;
149
+ rawItems: MessageItem[];
150
+ };
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ import { type Lang } from "./i18n.js";
2
+ import type { WeixinMessage } from "./types.js";
3
+ export declare function extractText(msg: WeixinMessage, lang?: Lang): string;
4
+ export declare function aesEcbPaddedSize(plaintextSize: number): number;
5
+ export declare function resolveCdnAesKey(aesKeyB64: string): Buffer;
6
+ export declare function decryptCdnMedia(ciphertext: Buffer, aesKeyB64: string, encryptType: number): Buffer;
7
+ export declare function imageExtension(buf: Buffer): string;
8
+ export declare function parseRunFlags(args: string): {
9
+ auto: boolean;
10
+ rest: string;
11
+ };
12
+ export declare function buildRunPrompt(task: string, fromUserId: string): string;
package/dist/utils.js ADDED
@@ -0,0 +1,88 @@
1
+ import crypto from "node:crypto";
2
+ import { marker } from "./i18n.js";
3
+ export function extractText(msg, lang = "zh") {
4
+ const parts = [];
5
+ for (const item of msg.item_list) {
6
+ if (item.type === 1)
7
+ parts.push(item.text_item.text);
8
+ else if (item.type === 3 && item.voice_item.text)
9
+ parts.push(marker("voice", lang, item.voice_item.text));
10
+ else if (item.type === 2)
11
+ parts.push(marker("image", lang));
12
+ else if (item.type === 4)
13
+ parts.push(marker("file", lang, item.file_item.file_name));
14
+ else if (item.type === 5)
15
+ parts.push(marker("video", lang));
16
+ }
17
+ return parts.join("\n");
18
+ }
19
+ export function aesEcbPaddedSize(plaintextSize) {
20
+ return Math.ceil((plaintextSize + 1) / 16) * 16;
21
+ }
22
+ // Incoming CDN media carries aes_key as base64. Depending on the sender it
23
+ // decodes to either the raw 16-byte key or the 32-char hex string of the key
24
+ // (our own uploads use the latter — see sendImage).
25
+ export function resolveCdnAesKey(aesKeyB64) {
26
+ const decoded = Buffer.from(aesKeyB64, "base64");
27
+ if (decoded.length === 16)
28
+ return decoded;
29
+ const asText = decoded.toString("utf-8");
30
+ if (/^[0-9a-fA-F]{32}$/.test(asText))
31
+ return Buffer.from(asText, "hex");
32
+ if (decoded.length > 16)
33
+ return decoded.subarray(0, 16);
34
+ throw new Error(`Unsupported aes_key length: ${decoded.length}`);
35
+ }
36
+ export function decryptCdnMedia(ciphertext, aesKeyB64, encryptType) {
37
+ if (encryptType === 0)
38
+ return ciphertext;
39
+ const key = resolveCdnAesKey(aesKeyB64);
40
+ const decipher = crypto.createDecipheriv("aes-128-ecb", key, null);
41
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
42
+ }
43
+ // Sniff a downloaded image's type so the saved file gets a usable extension.
44
+ export function imageExtension(buf) {
45
+ if (buf.length >= 8 && buf.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])))
46
+ return "png";
47
+ if (buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff)
48
+ return "jpg";
49
+ if (buf.length >= 4 && buf.subarray(0, 4).toString("ascii") === "GIF8")
50
+ return "gif";
51
+ if (buf.length >= 12 && buf.subarray(8, 12).toString("ascii") === "WEBP")
52
+ return "webp";
53
+ return "jpg";
54
+ }
55
+ // Parse leading flags of a "/run" argument string.
56
+ // Remote tasks run unattended, so skip-permissions is the default; "--safe"
57
+ // opts back into acceptEdits (bash commands then wait for confirmation).
58
+ // "-y" / "--yes" are still accepted as no-ops.
59
+ export function parseRunFlags(args) {
60
+ let auto = true;
61
+ let rest = args.trim();
62
+ while (true) {
63
+ const m = rest.match(/^(-y|--yes|--safe)(?:\s+|$)/);
64
+ if (!m)
65
+ break;
66
+ if (m[1] === "--safe")
67
+ auto = false;
68
+ rest = rest.slice(m[0].length).trim();
69
+ }
70
+ return { auto, rest };
71
+ }
72
+ // Build the prompt for a remotely-launched Claude session so the result
73
+ // comes back to WeChat instead of dying inside tmux.
74
+ export function buildRunPrompt(task, fromUserId) {
75
+ const shortTask = task.replace(/\s+/g, " ").slice(0, 12);
76
+ return [
77
+ "这个任务是用户通过微信远程发起的,请严格按以下步骤执行:",
78
+ "",
79
+ `第一步: 调用 wechat_set_session_name 工具把本 session 命名为 "run:${shortTask}",然后启动微信监控 — 调用 wechat_status 工具,按其输出的 watcher 命令启动一个 persistent Monitor(即 /wechat 命令的流程),这样用户可以在微信里继续跟进和补充指示。`,
80
+ "",
81
+ "第二步: 执行以下任务:",
82
+ task,
83
+ "",
84
+ `第三步: 任务完成后,必须调用 wechat_send_text 工具把结果摘要发送给用户 "${fromUserId}"。`,
85
+ "如果任务失败、被阻塞或需要用户决策,也必须通过 wechat_send_text 说明情况,并保持微信监控运行以等待用户回复。",
86
+ ].join("\n");
87
+ }
88
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ // Persistent inbox watcher for one Claude Code session.
3
+ //
4
+ // Usage: node dist/watch-inbox.js [sessionId]
5
+ //
6
+ // Prints one line per new inbox delivery (for the Claude Code Monitor tool)
7
+ // and maintains a heartbeat file so the daemon knows this session is
8
+ // actively monitored. Exits when the session's MCP server goes away.
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+ import { clearHeartbeat, touchHeartbeat } from "./monitoring.js";
12
+ import { INBOX_DIR, SESSIONS_DIR } from "./paths.js";
13
+ function resolveSessionByCwd() {
14
+ let best;
15
+ try {
16
+ for (const file of fs.readdirSync(SESSIONS_DIR)) {
17
+ if (!file.endsWith(".json"))
18
+ continue;
19
+ try {
20
+ const info = JSON.parse(fs.readFileSync(path.join(SESSIONS_DIR, file), "utf-8"));
21
+ if (info.cwd !== process.cwd())
22
+ continue;
23
+ if (!best || info.lastActive > best.lastActive)
24
+ best = info;
25
+ }
26
+ catch { }
27
+ }
28
+ }
29
+ catch { }
30
+ return best?.id;
31
+ }
32
+ const sessionId = process.argv[2] ?? resolveSessionByCwd();
33
+ if (!sessionId) {
34
+ process.stderr.write("Usage: watch-inbox.js <sessionId> (no session found for this cwd)\n");
35
+ process.exit(1);
36
+ }
37
+ const inboxFile = path.join(INBOX_DIR, `${sessionId}.json`);
38
+ const sessionFile = path.join(SESSIONS_DIR, `${sessionId}.json`);
39
+ let lastSig = "";
40
+ function checkInbox() {
41
+ let msgs;
42
+ try {
43
+ const parsed = JSON.parse(fs.readFileSync(inboxFile, "utf-8"));
44
+ if (!Array.isArray(parsed))
45
+ return;
46
+ msgs = parsed;
47
+ }
48
+ catch {
49
+ return;
50
+ }
51
+ if (msgs.length === 0) {
52
+ lastSig = "";
53
+ return;
54
+ }
55
+ const sig = msgs.map((m) => m.id ?? "").join(",");
56
+ if (sig === lastSig)
57
+ return;
58
+ lastSig = sig;
59
+ const preview = msgs
60
+ .map((m) => String(m.text ?? "").replace(/\s+/g, " ").slice(0, 80))
61
+ .join(" | ");
62
+ process.stdout.write(`WECHAT: ${msgs.length} msg(s) - ${preview}\n`);
63
+ }
64
+ function cleanup() {
65
+ clearHeartbeat(sessionId);
66
+ }
67
+ process.on("exit", cleanup);
68
+ process.on("SIGINT", () => process.exit(0));
69
+ process.on("SIGTERM", () => process.exit(0));
70
+ touchHeartbeat(sessionId);
71
+ checkInbox();
72
+ // Event-driven: react as soon as the daemon writes the inbox file.
73
+ // fs.watch on the directory survives the file being replaced.
74
+ try {
75
+ fs.watch(INBOX_DIR, (_event, filename) => {
76
+ if (filename === `${sessionId}.json`)
77
+ checkInbox();
78
+ });
79
+ }
80
+ catch {
81
+ // fall back to the interval below
82
+ }
83
+ function sessionAlive() {
84
+ try {
85
+ const info = JSON.parse(fs.readFileSync(sessionFile, "utf-8"));
86
+ process.kill(info.pid, 0);
87
+ return true;
88
+ }
89
+ catch {
90
+ return false;
91
+ }
92
+ }
93
+ // Fallback poll + heartbeat + liveness check.
94
+ setInterval(() => {
95
+ if (!sessionAlive()) {
96
+ // MCP server (and thus the Claude session) is gone.
97
+ process.exit(0);
98
+ }
99
+ touchHeartbeat(sessionId);
100
+ checkInbox();
101
+ }, 10_000);
102
+ //# sourceMappingURL=watch-inbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-inbox.js","sourceRoot":"","sources":["../src/watch-inbox.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "wechat-claude-sessions",
3
+ "version": "1.0.0",
4
+ "description": "Control Claude Code via WeChat using the ilink Bot API",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Neil Lu",
8
+ "homepage": "https://github.com/Neil-UWA/wechat-claude",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/Neil-UWA/wechat-claude.git"
12
+ },
13
+ "engines": {
14
+ "node": ">=20"
15
+ },
16
+ "main": "dist/server.js",
17
+ "bin": {
18
+ "wechat-claude": "dist/cli.js"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "templates",
23
+ "README.md",
24
+ "README.en.md",
25
+ "SECURITY.md",
26
+ "CONTRIBUTING.md",
27
+ "LICENSE"
28
+ ],
29
+ "scripts": {
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
32
+ "build": "tsc",
33
+ "prepublishOnly": "npm run build",
34
+ "start": "node dist/server.js",
35
+ "daemon": "node dist/cli.js daemon",
36
+ "daemon:install": "node dist/cli.js daemon install",
37
+ "daemon:uninstall": "node dist/cli.js daemon uninstall",
38
+ "daemon:status": "node dist/cli.js daemon status",
39
+ "daemon:log": "node dist/cli.js daemon log"
40
+ },
41
+ "dependencies": {
42
+ "@modelcontextprotocol/sdk": "^1.12.1",
43
+ "zod": "^3.25.67"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^22.15.0",
47
+ "typescript": "^5.8.3",
48
+ "vitest": "^4.1.11"
49
+ }
50
+ }