chatccc 0.2.188 → 0.2.189

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 (67) hide show
  1. package/agent-prompts/claude_specific.md +45 -45
  2. package/agent-prompts/codex_specific.md +2 -2
  3. package/agent-prompts/cursor_specific.md +13 -13
  4. package/config.sample.json +14 -14
  5. package/im-skills/feishu-skill/receive-send-file.md +63 -63
  6. package/im-skills/feishu-skill/receive-send-image.md +24 -24
  7. package/im-skills/feishu-skill/skill.md +3 -3
  8. package/im-skills/wechat-file-skill/receive-send-file.md +38 -38
  9. package/im-skills/wechat-file-skill/send-file.mjs +83 -83
  10. package/im-skills/wechat-file-skill/skill.md +10 -10
  11. package/im-skills/wechat-image-skill/skill.md +10 -10
  12. package/im-skills/wechat-video-skill/receive-send-video.md +38 -38
  13. package/im-skills/wechat-video-skill/send-video.mjs +79 -79
  14. package/im-skills/wechat-video-skill/skill.md +10 -10
  15. package/package.json +1 -1
  16. package/scripts/postinstall-sharp-check.mjs +58 -58
  17. package/src/__tests__/agent-delegate-task-rpc.test.ts +165 -165
  18. package/src/__tests__/builtin-config.test.ts +33 -33
  19. package/src/__tests__/card-plain-text.test.ts +5 -5
  20. package/src/__tests__/cardkit.test.ts +60 -60
  21. package/src/__tests__/cards.test.ts +77 -77
  22. package/src/__tests__/chatgpt-subscription-rpc.test.ts +89 -89
  23. package/src/__tests__/chatgpt-subscription.test.ts +135 -135
  24. package/src/__tests__/chrome-devtools-guard.test.ts +165 -165
  25. package/src/__tests__/claude-adapter.test.ts +592 -592
  26. package/src/__tests__/codex-reset-actions.test.ts +146 -146
  27. package/src/__tests__/config-reload.test.ts +4 -4
  28. package/src/__tests__/config-sample.test.ts +17 -17
  29. package/src/__tests__/feishu-api.test.ts +60 -60
  30. package/src/__tests__/feishu-platform.test.ts +22 -22
  31. package/src/__tests__/format-message.test.ts +47 -47
  32. package/src/__tests__/orchestrator.test.ts +112 -112
  33. package/src/__tests__/privacy.test.ts +198 -198
  34. package/src/__tests__/raw-stream-log.test.ts +106 -106
  35. package/src/__tests__/session.test.ts +17 -17
  36. package/src/__tests__/shared-prefix.test.ts +36 -36
  37. package/src/__tests__/stream-state.test.ts +42 -42
  38. package/src/__tests__/web-ui.test.ts +209 -130
  39. package/src/adapters/claude-adapter.ts +566 -566
  40. package/src/adapters/claude-session-meta-store.ts +120 -120
  41. package/src/adapters/codex-adapter.ts +27 -28
  42. package/src/adapters/cursor-adapter.ts +46 -46
  43. package/src/adapters/raw-stream-log.ts +124 -124
  44. package/src/adapters/resource-monitor.ts +140 -140
  45. package/src/agent-delegate-task-rpc.ts +153 -153
  46. package/src/agent-delegate-task.ts +81 -81
  47. package/src/agent-stop-stuck.ts +129 -129
  48. package/src/builtin/cli.ts +189 -189
  49. package/src/builtin/index.ts +168 -168
  50. package/src/cards.ts +137 -137
  51. package/src/chatgpt-subscription-rpc.ts +27 -27
  52. package/src/chatgpt-subscription.ts +299 -299
  53. package/src/chrome-devtools-guard.ts +318 -318
  54. package/src/codex-reset-actions.ts +184 -184
  55. package/src/config.ts +86 -86
  56. package/src/feishu-platform.ts +20 -20
  57. package/src/format-message.ts +293 -293
  58. package/src/litellm-proxy.ts +374 -374
  59. package/src/orchestrator.ts +143 -143
  60. package/src/privacy.ts +118 -118
  61. package/src/session-chat-binding.ts +6 -6
  62. package/src/session-name.ts +8 -8
  63. package/src/session.ts +98 -98
  64. package/src/shared-prefix.ts +29 -29
  65. package/src/sim-platform.ts +20 -20
  66. package/src/turn-cards.ts +117 -117
  67. package/src/web-ui.ts +142 -24
@@ -1,46 +1,46 @@
1
- ## 重复成功命令保护
2
-
3
- 通过 Claude Agent SDK 在本项目中工作时,重复成功的 shell 命令是完成信号,不应继续使用工具。
4
-
5
- 以下条件**全部**满足时,视为相同命令:
6
- - 命令文本基本相同
7
- - 工作目录相同
8
- - 任务目标未变
9
- - 没有新的用户输入改变任务
10
- - 没有新的错误输出需要重新处理
11
-
12
- 同一命令**连续成功两次**后,停止调用工具,直接给出最终回复。不要"再验证一次"。
13
-
14
- 此规则适用于所有工具调用。
15
-
16
- ## 硬停止规则
17
-
18
- 同一命令在本项目中**不得成功执行三次**。若连续两次成功,第三次必须是最终回复。
19
-
20
- ## 卡住自检
21
-
22
- 如果你感觉任务已经完成,但发现自己陷入了循环(反复调用相同工具、输出 "stuck in a loop" 等),**立即**调用以下接口。
23
-
24
- **重要:必须用 heredoc 写临时文件再 curl,禁止在命令行参数中直接写中文 JSON。** Windows 下命令行参数传中文会变成 `?` 乱码。
25
-
26
- ```bash
27
- cat > /tmp/stop_ccc.json << 'STOPJSON'
28
- {"session_id": "{{session_id}}", "final_reply": "<你给用户的最终回复,总结已完成的工作和结果>"}
29
- STOPJSON
30
- curl -s -X POST {{stop_stuck_url}} -H "Content-Type: application/json" -d @/tmp/stop_ccc.json
31
- ```
32
-
33
- final_reply 注意事项:
34
- - 把 `<...>` 占位符替换为你的实际回复,不要包含双引号 `"` 和反斜杠 `\`
35
- - 不要包含换行,用空格代替
36
- - 保持简短(1-2 句话)
37
-
38
- 调用后停止所有工具调用,直接输出最终回复即可。
39
-
40
- ## 禁止 PushNotification
41
-
42
- **不要调用 PushNotification 工具。** 如果你认为当前会话已经完成任务需要结束,直接调用上述 stop-stuck-loop 接口,不要使用 PushNotification。
43
-
44
- ## 计划/问答模式权限规则
45
-
1
+ ## 重复成功命令保护
2
+
3
+ 通过 Claude Agent SDK 在本项目中工作时,重复成功的 shell 命令是完成信号,不应继续使用工具。
4
+
5
+ 以下条件**全部**满足时,视为相同命令:
6
+ - 命令文本基本相同
7
+ - 工作目录相同
8
+ - 任务目标未变
9
+ - 没有新的用户输入改变任务
10
+ - 没有新的错误输出需要重新处理
11
+
12
+ 同一命令**连续成功两次**后,停止调用工具,直接给出最终回复。不要"再验证一次"。
13
+
14
+ 此规则适用于所有工具调用。
15
+
16
+ ## 硬停止规则
17
+
18
+ 同一命令在本项目中**不得成功执行三次**。若连续两次成功,第三次必须是最终回复。
19
+
20
+ ## 卡住自检
21
+
22
+ 如果你感觉任务已经完成,但发现自己陷入了循环(反复调用相同工具、输出 "stuck in a loop" 等),**立即**调用以下接口。
23
+
24
+ **重要:必须用 heredoc 写临时文件再 curl,禁止在命令行参数中直接写中文 JSON。** Windows 下命令行参数传中文会变成 `?` 乱码。
25
+
26
+ ```bash
27
+ cat > /tmp/stop_ccc.json << 'STOPJSON'
28
+ {"session_id": "{{session_id}}", "final_reply": "<你给用户的最终回复,总结已完成的工作和结果>"}
29
+ STOPJSON
30
+ curl -s -X POST {{stop_stuck_url}} -H "Content-Type: application/json" -d @/tmp/stop_ccc.json
31
+ ```
32
+
33
+ final_reply 注意事项:
34
+ - 把 `<...>` 占位符替换为你的实际回复,不要包含双引号 `"` 和反斜杠 `\`
35
+ - 不要包含换行,用空格代替
36
+ - 保持简短(1-2 句话)
37
+
38
+ 调用后停止所有工具调用,直接输出最终回复即可。
39
+
40
+ ## 禁止 PushNotification
41
+
42
+ **不要调用 PushNotification 工具。** 如果你认为当前会话已经完成任务需要结束,直接调用上述 stop-stuck-loop 接口,不要使用 PushNotification。
43
+
44
+ ## 计划/问答模式权限规则
45
+
46
46
  如果用户消息以 `/plan` 或 `/ask` 开头,说明当前处于只读模式。**遇到任何需要用户同意的权限请求时,不要向用户申请权限,立即调用 stop-stuck-loop 接口提前结束本轮会话。**
@@ -1,3 +1,3 @@
1
- ## 计划/问答模式权限规则
2
-
1
+ ## 计划/问答模式权限规则
2
+
3
3
  如果用户消息以 `/plan` 或 `/ask` 开头,说明当前处于只读模式。**遇到任何需要用户同意的权限请求时,不要向用户申请权限,立即调用 stop-stuck-loop 接口提前结束本轮会话。**
@@ -1,14 +1,14 @@
1
- ## 计划/问答模式权限规则
2
-
3
- 如果用户消息以 `/plan` 或 `/ask` 开头,说明当前处于只读模式。**遇到任何需要用户同意的权限请求时,不要向用户申请权限,立即调用 stop-stuck-loop 接口提前结束本轮会话。**
4
-
5
- ## 用户问题澄清规则
6
-
7
- 当需要在开始实现前向用户确认问题、让用户做选择,或用户要求“从第一性原理出发挖掘真实需求 / 判断需求是否合理 / 开始实现前有什么问题要问我”时,**不要调用 `AskQuestion`、多选表单或任何结构化选择工具**。
8
-
9
- 必须直接用普通文本回复用户:
10
-
11
- - 先简要说明你理解到的真实需求和你对合理性的判断。
12
- - 再列出需要用户确认的问题;如果有推荐选项,就用文本说明“我建议 X,原因是 Y”。
13
- - 问题数量尽量少,只问会影响实现边界、风险或不可逆决策的问题。
1
+ ## 计划/问答模式权限规则
2
+
3
+ 如果用户消息以 `/plan` 或 `/ask` 开头,说明当前处于只读模式。**遇到任何需要用户同意的权限请求时,不要向用户申请权限,立即调用 stop-stuck-loop 接口提前结束本轮会话。**
4
+
5
+ ## 用户问题澄清规则
6
+
7
+ 当需要在开始实现前向用户确认问题、让用户做选择,或用户要求“从第一性原理出发挖掘真实需求 / 判断需求是否合理 / 开始实现前有什么问题要问我”时,**不要调用 `AskQuestion`、多选表单或任何结构化选择工具**。
8
+
9
+ 必须直接用普通文本回复用户:
10
+
11
+ - 先简要说明你理解到的真实需求和你对合理性的判断。
12
+ - 再列出需要用户确认的问题;如果有推荐选项,就用文本说明“我建议 X,原因是 Y”。
13
+ - 问题数量尽量少,只问会影响实现边界、风险或不可逆决策的问题。
14
14
  - 不要把这些问题放进工具调用参数里,也不要只在卡片/工具结果中呈现。
@@ -54,17 +54,17 @@
54
54
  "avatarBatteryMode": "apiPercent",
55
55
  "onDemandMonthlyBudget": 1000
56
56
  },
57
- "codex": {
58
- "enabled": false,
59
- "defaultAgent": false,
60
- "path": "",
61
- "model": "",
62
- "alternativeModel": "",
63
- "effort": ""
64
- },
65
- "ccc": {
66
- "DEEPSEEK_API_KEY": "",
67
- "DEEPSEEK_BASE_URL": "https://api.deepseek.com/v1",
68
- "model": "deepseek-v4-pro[1m]"
69
- }
70
- }
57
+ "codex": {
58
+ "enabled": false,
59
+ "defaultAgent": false,
60
+ "path": "",
61
+ "model": "",
62
+ "alternativeModel": "",
63
+ "effort": ""
64
+ },
65
+ "ccc": {
66
+ "DEEPSEEK_API_KEY": "",
67
+ "DEEPSEEK_BASE_URL": "https://api.deepseek.com/v1",
68
+ "model": "deepseek-v4-pro[1m]"
69
+ }
70
+ }
@@ -1,64 +1,64 @@
1
- # Sending & Downloading Files/Videos
2
-
3
- ## Send Files or Videos
4
-
5
- Videos are sent as regular files (not media), which looks cleaner in Feishu.
6
-
7
- **Use the node script below. Do NOT use curl or raw HTTP — the script correctly handles errors and exits non-zero on failure.**
8
-
9
- ### Script
10
-
11
- ```bash
12
- node "{{send_file_script}}" --url "{{send_file_url}}" --session-id "{{session_id}}" --path "<absolute file path>" --caption "<optional caption>"
13
- ```
14
-
15
- ### Rules
16
-
17
- - Use the node script above — never curl or raw HTTP.
18
- - Save or choose a local file first.
19
- - Use an absolute local path.
20
- - Max file size: 30MB.
21
- - Supported formats: .mp4 .mov .avi .mkv .webm .flv .mp3 .wav .ogg .aac .m4a .pdf .doc .docx .xls .xlsx .csv .ppt .pptx .txt .zip .tar .gz.
22
- - Only send a file/video when the user asked for one or when it materially helps the answer.
23
- - **If the script fails (non-zero exit), read stderr for the error. Do NOT retry with the same path. Either fix the problem (wrong extension, missing file, etc.) or tell the user what the error was. Never retry more than once.**
24
-
25
- ### Video Compression (when file > 30MB)
26
-
27
- If the video exceeds 30MB, compress it with ffmpeg before sending.
28
-
29
- **Ensure ffmpeg is available** (install if missing):
30
-
31
- | OS | Install command |
32
- |----|----------------|
33
- | macOS | `brew install ffmpeg` |
34
- | Linux (Debian/Ubuntu) | `sudo apt install ffmpeg` |
35
- | Linux (RHEL/Fedora) | `sudo dnf install ffmpeg` |
36
- | Windows | `winget install Gyan.FFmpeg` |
37
-
38
- **Two-pass compression** (target ~28MB for 30s video, adjust `b:v` for other durations):
39
-
40
- ```bash
41
- ffmpeg -y -i "<input>" -c:v libx264 -b:v <bitrate>k -pass 1 -f mp4 NUL
42
- ffmpeg -y -i "<input>" -c:v libx264 -b:v <bitrate>k -pass 2 -c:a aac -b:a 128k "<output>"
43
- ```
44
-
45
- Bitrate formula: `bitrate = 28 × 8 × 1000 ÷ duration_seconds - 128` (target ~28MB, safe under 30MB).
46
- On Windows replace `NUL` with `NUL` (same); on Linux/macOS use `/dev/null`.
47
-
48
- If the compressed file still exceeds 30MB, explain to the user that automatic compression wasn't enough and suggest they manually trim or re-encode the source.
49
-
50
- ## Download Files or Videos
51
-
52
- When the user sends a file or video to the bot, the message contains `message_id` and `file_key`. Download it with:
53
-
54
- ```bash
55
- node "{{download_video_script}}" --message-id <message_id> --file-key <file_key> --name <file_name>
56
- ```
57
-
58
- If only `chat_id` and `file_key` are available:
59
-
60
- ```bash
61
- node "{{download_video_script}}" --chat-id <chat_id> --file-key <file_key> --name <file_name>
62
- ```
63
-
1
+ # Sending & Downloading Files/Videos
2
+
3
+ ## Send Files or Videos
4
+
5
+ Videos are sent as regular files (not media), which looks cleaner in Feishu.
6
+
7
+ **Use the node script below. Do NOT use curl or raw HTTP — the script correctly handles errors and exits non-zero on failure.**
8
+
9
+ ### Script
10
+
11
+ ```bash
12
+ node "{{send_file_script}}" --url "{{send_file_url}}" --session-id "{{session_id}}" --path "<absolute file path>" --caption "<optional caption>"
13
+ ```
14
+
15
+ ### Rules
16
+
17
+ - Use the node script above — never curl or raw HTTP.
18
+ - Save or choose a local file first.
19
+ - Use an absolute local path.
20
+ - Max file size: 30MB.
21
+ - Supported formats: .mp4 .mov .avi .mkv .webm .flv .mp3 .wav .ogg .aac .m4a .pdf .doc .docx .xls .xlsx .csv .ppt .pptx .txt .zip .tar .gz.
22
+ - Only send a file/video when the user asked for one or when it materially helps the answer.
23
+ - **If the script fails (non-zero exit), read stderr for the error. Do NOT retry with the same path. Either fix the problem (wrong extension, missing file, etc.) or tell the user what the error was. Never retry more than once.**
24
+
25
+ ### Video Compression (when file > 30MB)
26
+
27
+ If the video exceeds 30MB, compress it with ffmpeg before sending.
28
+
29
+ **Ensure ffmpeg is available** (install if missing):
30
+
31
+ | OS | Install command |
32
+ |----|----------------|
33
+ | macOS | `brew install ffmpeg` |
34
+ | Linux (Debian/Ubuntu) | `sudo apt install ffmpeg` |
35
+ | Linux (RHEL/Fedora) | `sudo dnf install ffmpeg` |
36
+ | Windows | `winget install Gyan.FFmpeg` |
37
+
38
+ **Two-pass compression** (target ~28MB for 30s video, adjust `b:v` for other durations):
39
+
40
+ ```bash
41
+ ffmpeg -y -i "<input>" -c:v libx264 -b:v <bitrate>k -pass 1 -f mp4 NUL
42
+ ffmpeg -y -i "<input>" -c:v libx264 -b:v <bitrate>k -pass 2 -c:a aac -b:a 128k "<output>"
43
+ ```
44
+
45
+ Bitrate formula: `bitrate = 28 × 8 × 1000 ÷ duration_seconds - 128` (target ~28MB, safe under 30MB).
46
+ On Windows replace `NUL` with `NUL` (same); on Linux/macOS use `/dev/null`.
47
+
48
+ If the compressed file still exceeds 30MB, explain to the user that automatic compression wasn't enough and suggest they manually trim or re-encode the source.
49
+
50
+ ## Download Files or Videos
51
+
52
+ When the user sends a file or video to the bot, the message contains `message_id` and `file_key`. Download it with:
53
+
54
+ ```bash
55
+ node "{{download_video_script}}" --message-id <message_id> --file-key <file_key> --name <file_name>
56
+ ```
57
+
58
+ If only `chat_id` and `file_key` are available:
59
+
60
+ ```bash
61
+ node "{{download_video_script}}" --chat-id <chat_id> --file-key <file_key> --name <file_name>
62
+ ```
63
+
64
64
  Downloads are saved under `~/.chatccc/videos/downloads/`.
@@ -1,25 +1,25 @@
1
- # Sending & Receiving Images
2
-
3
- ## Send Images
4
-
5
- **Use the node script below. Do NOT use curl or raw HTTP — the script correctly handles errors and exits non-zero on failure.**
6
-
7
- ### Script
8
-
9
- ```bash
10
- node "{{send_image_script}}" --url "{{send_image_url}}" --session-id "{{session_id}}" --path "<absolute image path>" --caption "<optional caption>"
11
- ```
12
-
13
- ### Rules
14
-
15
- - Use the node script above — never curl or raw HTTP.
16
- - Save or choose a local image file first.
17
- - Use an absolute local path.
18
- - Supported formats: .png, .jpg, .jpeg, .webp, .gif, .bmp.
19
- - Max image size: 10MB.
20
- - Only send an image when the user asked for one or when it materially helps the answer.
21
- - **If the script fails (non-zero exit), read stderr for the error. Do NOT retry with the same path. Either fix the problem (wrong extension, missing file, etc.) or tell the user what the error was. Never retry more than once.**
22
-
23
- ## Receive Images
24
-
1
+ # Sending & Receiving Images
2
+
3
+ ## Send Images
4
+
5
+ **Use the node script below. Do NOT use curl or raw HTTP — the script correctly handles errors and exits non-zero on failure.**
6
+
7
+ ### Script
8
+
9
+ ```bash
10
+ node "{{send_image_script}}" --url "{{send_image_url}}" --session-id "{{session_id}}" --path "<absolute image path>" --caption "<optional caption>"
11
+ ```
12
+
13
+ ### Rules
14
+
15
+ - Use the node script above — never curl or raw HTTP.
16
+ - Save or choose a local image file first.
17
+ - Use an absolute local path.
18
+ - Supported formats: .png, .jpg, .jpeg, .webp, .gif, .bmp.
19
+ - Max image size: 10MB.
20
+ - Only send an image when the user asked for one or when it materially helps the answer.
21
+ - **If the script fails (non-zero exit), read stderr for the error. Do NOT retry with the same path. Either fix the problem (wrong extension, missing file, etc.) or tell the user what the error was. Never retry more than once.**
22
+
23
+ ## Receive Images
24
+
25
25
  Images sent to the bot are automatically downloaded to `~/.chatccc/images/downloads/`. The message contains an `image_key` that maps to the cached file.
@@ -7,6 +7,6 @@ Current working directory: {{cwd}}
7
7
 
8
8
  Use local endpoints instead of calling Feishu Open Platform directly.
9
9
 
10
- - **Send images**: POST `{{send_image_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-image.md`
11
- - **Send files/videos**: POST `{{send_file_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-file.md`
12
- - **Delegate a task to a new agent conversation**: POST `{{delegate_task_url}}` with `{"tool":"codex|claude|cursor","cwd":"<absolute working directory>","open_id":"<Feishu user open_id>","prompt":"<first task>"}`. Use `open_ids` for multiple users. This uses the normal ChatCCC prompt flow, so project prompt injection and IM skills still apply.
10
+ - **Send images**: POST `{{send_image_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-image.md`
11
+ - **Send files/videos**: POST `{{send_file_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-file.md`
12
+ - **Delegate a task to a new agent conversation**: POST `{{delegate_task_url}}` with `{"tool":"codex|claude|cursor","cwd":"<absolute working directory>","open_id":"<Feishu user open_id>","prompt":"<first task>"}`. Use `open_ids` for multiple users. This uses the normal ChatCCC prompt flow, so project prompt injection and IM skills still apply.
@@ -1,39 +1,39 @@
1
- # Receiving & Sending Files (WeChat)
2
-
3
- ## Receive Files
4
-
5
- Files sent to the bot are **automatically downloaded** to `~/.chatccc/images/downloads/` with the `wx_` filename prefix.
6
-
7
- The message text you receive will include the downloaded path in the format:
8
- ```
9
- [文件] C:\Users\<用户名>\.chatccc\images\downloads\wx_<key>_<filename>
10
- ```
11
-
12
- You can read the file at that path to understand what the user sent.
13
-
14
- ## Send Files
15
-
16
- ### Script (recommended)
17
- ```bash
18
- node "{{wechat_send_file_script}}" --path "<absolute file path>" --caption "<optional caption>"
19
- ```
20
-
21
- The script reads the current WeChat session's auth token, chat ID, and context token from `~/.chatccc/state/ilink-auth.json`, then sends the file to the most recent chat.
22
-
23
- ### Direct usage in test scripts
24
-
25
- The underlying SDK call is:
26
- ```ts
27
- import { Client as OpenIlinkWire } from "@openilink/openilink-sdk-node";
28
- const wire = new OpenIlinkWire(token, { base_url: baseUrl });
29
- await wire.sendMediaFile(chatId, contextToken, fileBuffer, "filename.pdf", "caption");
30
- ```
31
-
32
- ### Rules
33
-
34
- - Save or choose a local file first.
35
- - Use an absolute local path.
36
- - Supported formats: .pdf, .doc, .docx, .xls, .xlsx, .csv, .ppt, .pptx, .txt, .zip, .tar, .gz, .rar, .7z, .mp3, .wav, .ogg, .aac, .m4a.
37
- - Max file size: 100MB.
38
- - Only send a file when the user asked for one or when it materially helps the answer.
1
+ # Receiving & Sending Files (WeChat)
2
+
3
+ ## Receive Files
4
+
5
+ Files sent to the bot are **automatically downloaded** to `~/.chatccc/images/downloads/` with the `wx_` filename prefix.
6
+
7
+ The message text you receive will include the downloaded path in the format:
8
+ ```
9
+ [文件] C:\Users\<用户名>\.chatccc\images\downloads\wx_<key>_<filename>
10
+ ```
11
+
12
+ You can read the file at that path to understand what the user sent.
13
+
14
+ ## Send Files
15
+
16
+ ### Script (recommended)
17
+ ```bash
18
+ node "{{wechat_send_file_script}}" --path "<absolute file path>" --caption "<optional caption>"
19
+ ```
20
+
21
+ The script reads the current WeChat session's auth token, chat ID, and context token from `~/.chatccc/state/ilink-auth.json`, then sends the file to the most recent chat.
22
+
23
+ ### Direct usage in test scripts
24
+
25
+ The underlying SDK call is:
26
+ ```ts
27
+ import { Client as OpenIlinkWire } from "@openilink/openilink-sdk-node";
28
+ const wire = new OpenIlinkWire(token, { base_url: baseUrl });
29
+ await wire.sendMediaFile(chatId, contextToken, fileBuffer, "filename.pdf", "caption");
30
+ ```
31
+
32
+ ### Rules
33
+
34
+ - Save or choose a local file first.
35
+ - Use an absolute local path.
36
+ - Supported formats: .pdf, .doc, .docx, .xls, .xlsx, .csv, .ppt, .pptx, .txt, .zip, .tar, .gz, .rar, .7z, .mp3, .wav, .ogg, .aac, .m4a.
37
+ - Max file size: 100MB.
38
+ - Only send a file when the user asked for one or when it materially helps the answer.
39
39
  - **Claw 限制**: 文件发送也计入微信 claw 连发计数,连续 10 条未回复会截断。
@@ -1,84 +1,84 @@
1
- #!/usr/bin/env node
2
- import { existsSync, readFileSync, statSync } from "node:fs";
3
- import { basename, extname, join } from "node:path";
4
- import { homedir } from "node:os";
5
-
6
- import { Client as OpenIlinkWire } from "@openilink/openilink-sdk-node";
7
-
8
- const ILINK_AUTH_PATH = join(homedir(), ".chatccc", "state", "ilink-auth.json");
9
- const MAX_FILE_BYTES = 100 * 1024 * 1024;
10
- const ALLOWED_EXTS = new Set([
11
- ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".csv", ".ppt", ".pptx",
12
- ".txt", ".zip", ".tar", ".gz", ".rar", ".7z",
13
- ".mp3", ".wav", ".ogg", ".aac", ".m4a",
14
- ]);
15
-
16
- function parseArgs(argv) {
17
- const result = {};
18
- for (let i = 0; i < argv.length; i++) {
19
- const key = argv[i];
20
- if (!key.startsWith("--")) continue;
21
- const value = argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[++i] : "true";
22
- result[key.slice(2)] = value;
23
- }
24
- return result;
25
- }
26
-
27
- function usage() {
28
- console.error(`Usage:
29
- node ${basename(process.argv[1])} --path <absolute file path> [--caption <text>]`);
30
- }
31
-
32
- async function main() {
33
- const args = parseArgs(process.argv.slice(2));
34
- const filePath = args.path;
35
- const caption = args.caption || "";
36
-
37
- if (!filePath) {
38
- usage();
39
- process.exit(1);
40
- }
41
-
42
- const ext = extname(filePath).toLowerCase();
43
- if (!ALLOWED_EXTS.has(ext)) {
44
- console.error(`Unsupported file extension: ${ext || "(none)"}`);
45
- process.exit(1);
46
- }
47
-
48
- const st = statSync(filePath);
49
- if (!st.isFile()) {
50
- console.error("File path is not a file");
51
- process.exit(1);
52
- }
53
- if (st.size > MAX_FILE_BYTES) {
54
- console.error("File exceeds 100MB limit");
55
- process.exit(1);
56
- }
57
-
58
- if (!existsSync(ILINK_AUTH_PATH)) {
59
- console.error(`Auth snapshot not found: ${ILINK_AUTH_PATH}`);
60
- console.error("Make sure the WeChat iLink platform is logged in.");
61
- process.exit(1);
62
- }
63
-
64
- const snap = JSON.parse(readFileSync(ILINK_AUTH_PATH, "utf8"));
65
- if (!snap.token || !snap.lastChatId || !snap.contextToken) {
66
- console.error("Auth snapshot missing token, lastChatId, or contextToken.");
67
- process.exit(1);
68
- }
69
-
70
- const fileData = readFileSync(filePath);
71
- const fileName = basename(filePath);
72
-
73
- const wire = new OpenIlinkWire(snap.token, {
74
- base_url: snap.baseUrl,
75
- });
76
-
77
- await wire.sendMediaFile(snap.lastChatId, snap.contextToken, fileData, fileName, caption);
78
- console.log(JSON.stringify({ ok: true, sentTo: 1 }));
79
- }
80
-
81
- main().catch((err) => {
82
- console.error(err instanceof Error ? err.message : String(err));
83
- process.exit(1);
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync, statSync } from "node:fs";
3
+ import { basename, extname, join } from "node:path";
4
+ import { homedir } from "node:os";
5
+
6
+ import { Client as OpenIlinkWire } from "@openilink/openilink-sdk-node";
7
+
8
+ const ILINK_AUTH_PATH = join(homedir(), ".chatccc", "state", "ilink-auth.json");
9
+ const MAX_FILE_BYTES = 100 * 1024 * 1024;
10
+ const ALLOWED_EXTS = new Set([
11
+ ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".csv", ".ppt", ".pptx",
12
+ ".txt", ".zip", ".tar", ".gz", ".rar", ".7z",
13
+ ".mp3", ".wav", ".ogg", ".aac", ".m4a",
14
+ ]);
15
+
16
+ function parseArgs(argv) {
17
+ const result = {};
18
+ for (let i = 0; i < argv.length; i++) {
19
+ const key = argv[i];
20
+ if (!key.startsWith("--")) continue;
21
+ const value = argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[++i] : "true";
22
+ result[key.slice(2)] = value;
23
+ }
24
+ return result;
25
+ }
26
+
27
+ function usage() {
28
+ console.error(`Usage:
29
+ node ${basename(process.argv[1])} --path <absolute file path> [--caption <text>]`);
30
+ }
31
+
32
+ async function main() {
33
+ const args = parseArgs(process.argv.slice(2));
34
+ const filePath = args.path;
35
+ const caption = args.caption || "";
36
+
37
+ if (!filePath) {
38
+ usage();
39
+ process.exit(1);
40
+ }
41
+
42
+ const ext = extname(filePath).toLowerCase();
43
+ if (!ALLOWED_EXTS.has(ext)) {
44
+ console.error(`Unsupported file extension: ${ext || "(none)"}`);
45
+ process.exit(1);
46
+ }
47
+
48
+ const st = statSync(filePath);
49
+ if (!st.isFile()) {
50
+ console.error("File path is not a file");
51
+ process.exit(1);
52
+ }
53
+ if (st.size > MAX_FILE_BYTES) {
54
+ console.error("File exceeds 100MB limit");
55
+ process.exit(1);
56
+ }
57
+
58
+ if (!existsSync(ILINK_AUTH_PATH)) {
59
+ console.error(`Auth snapshot not found: ${ILINK_AUTH_PATH}`);
60
+ console.error("Make sure the WeChat iLink platform is logged in.");
61
+ process.exit(1);
62
+ }
63
+
64
+ const snap = JSON.parse(readFileSync(ILINK_AUTH_PATH, "utf8"));
65
+ if (!snap.token || !snap.lastChatId || !snap.contextToken) {
66
+ console.error("Auth snapshot missing token, lastChatId, or contextToken.");
67
+ process.exit(1);
68
+ }
69
+
70
+ const fileData = readFileSync(filePath);
71
+ const fileName = basename(filePath);
72
+
73
+ const wire = new OpenIlinkWire(snap.token, {
74
+ base_url: snap.baseUrl,
75
+ });
76
+
77
+ await wire.sendMediaFile(snap.lastChatId, snap.contextToken, fileData, fileName, caption);
78
+ console.log(JSON.stringify({ ok: true, sentTo: 1 }));
79
+ }
80
+
81
+ main().catch((err) => {
82
+ console.error(err instanceof Error ? err.message : String(err));
83
+ process.exit(1);
84
84
  });
@@ -1,11 +1,11 @@
1
- ---
2
- name: wechat-file-skill
3
- description: WeChat iLink local skills for sending and receiving files.
4
- ---
5
-
6
- Current working directory: {{cwd}}
7
-
8
- WeChat files are handled through the iLink SDK. The local server does NOT have HTTP RPC endpoints for WeChat; use the helper scripts below instead.
9
-
10
- - **Receive files**: Files sent to the bot are automatically downloaded to `~/.chatccc/images/downloads/` with the filename prefix `wx_`. The message text will include the local path as `[文件] <absolute path>` — read `{{im_skills_cache_dir}}/wechat-file-skill/receive-send-file.md`
1
+ ---
2
+ name: wechat-file-skill
3
+ description: WeChat iLink local skills for sending and receiving files.
4
+ ---
5
+
6
+ Current working directory: {{cwd}}
7
+
8
+ WeChat files are handled through the iLink SDK. The local server does NOT have HTTP RPC endpoints for WeChat; use the helper scripts below instead.
9
+
10
+ - **Receive files**: Files sent to the bot are automatically downloaded to `~/.chatccc/images/downloads/` with the filename prefix `wx_`. The message text will include the local path as `[文件] <absolute path>` — read `{{im_skills_cache_dir}}/wechat-file-skill/receive-send-file.md`
11
11
  - **Send files**: Use the send-file helper script — read `{{im_skills_cache_dir}}/wechat-file-skill/receive-send-file.md`
@@ -1,11 +1,11 @@
1
- ---
2
- name: wechat-image-skill
3
- description: WeChat iLink local skills for sending and receiving images.
4
- ---
5
-
6
- Current working directory: {{cwd}}
7
-
8
- WeChat images are handled through the iLink SDK. The local server does NOT have HTTP RPC endpoints for WeChat; use the helper scripts below instead.
9
-
10
- - **Receive images**: Images sent to the bot are automatically downloaded to `~/.chatccc/images/downloads/` with the filename prefix `wx_`. The message text will include the local path as `[图片] <absolute path>` — read `{{im_skills_cache_dir}}/wechat-image-skill/receive-send-image.md`
1
+ ---
2
+ name: wechat-image-skill
3
+ description: WeChat iLink local skills for sending and receiving images.
4
+ ---
5
+
6
+ Current working directory: {{cwd}}
7
+
8
+ WeChat images are handled through the iLink SDK. The local server does NOT have HTTP RPC endpoints for WeChat; use the helper scripts below instead.
9
+
10
+ - **Receive images**: Images sent to the bot are automatically downloaded to `~/.chatccc/images/downloads/` with the filename prefix `wx_`. The message text will include the local path as `[图片] <absolute path>` — read `{{im_skills_cache_dir}}/wechat-image-skill/receive-send-image.md`
11
11
  - **Send images**: Use the send-image helper script — read `{{im_skills_cache_dir}}/wechat-image-skill/receive-send-image.md`