cc-im 1.0.0 → 1.0.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/README.md CHANGED
@@ -8,15 +8,23 @@
8
8
 
9
9
  - **多平台支持**:飞书和 Telegram,可同时运行或单独使用
10
10
  - **流式输出**:飞书端使用 CardKit 打字机效果,Telegram 端通过 editMessage 实时更新
11
+ - **思考过程展示**:实时显示 Claude 的思考过程(折叠面板)
12
+ - **工具调用通知**:流式显示当前正在使用的工具及参数摘要
13
+ - **图片消息支持**:支持发送图片给 Claude 进行分析
14
+ - **话题会话**:飞书群聊话题(thread)独立会话
11
15
  - **会话管理**:每用户独立 session,支持 `/new` 重置
12
16
  - **并发控制**:同会话串行执行,不同会话可并发,最多排队 3 条消息
13
17
  - **长消息分片**:超长内容自动拆分为多条消息
14
18
  - **权限确认**:通过 Hook 机制实现工具调用的交互式审批
15
19
  - **白名单**:通过环境变量或配置文件控制访问
16
20
  - **停止按钮**:执行过程中可随时停止
21
+ - **工具使用统计**:完成时显示工具调用次数和类型
22
+ - **生命周期通知**:服务启动/关闭时通知活跃用户
17
23
 
18
24
  ## 快速开始
19
25
 
26
+ > 要求:Node.js >= 20,需要预先安装 [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)
27
+
20
28
  ### 同时运行多个平台
21
29
 
22
30
  可以同时启用飞书和 Telegram,只需配置两个平台的凭证即可:
@@ -53,12 +61,14 @@ pnpm dev
53
61
 
54
62
  1. 在[飞书开放平台](https://open.feishu.cn)创建应用
55
63
  2. 开启机器人能力
56
- 3. 添加权限:`im:message`、`im:message:send_as_bot`、`im:message:patch_as_bot`、`cardkit:card`
64
+ 3. 添加权限:`im:message`、`im:message:send_as_bot`、`im:message.group_msg`、`im:message.p2p_msg:readonly`、`cardkit:card:write`
57
65
  4. 事件订阅中启用 **长连接模式**,订阅以下事件:
58
66
  - `im.message.receive_v1` — 接收消息
67
+ - `im.message.recalled_v1` — 消息撤回(自动清理话题会话)
68
+ 5. 回调订阅
59
69
  - `card.action.trigger` — 卡片交互(停止按钮)
60
- 5. 发布应用
61
- 6. 配置并启动:
70
+ 6. 发布应用
71
+ 7. 配置并启动:
62
72
 
63
73
  ```bash
64
74
  export FEISHU_APP_ID=your_app_id
@@ -69,7 +79,7 @@ npx cc-im
69
79
  ### 从源码构建
70
80
 
71
81
  ```bash
72
- git clone <repo-url>
82
+ git clone https://github.com/congqiu/cc-im.git
73
83
  cd cc-im
74
84
  pnpm install
75
85
  cp .env.example .env
@@ -96,6 +106,7 @@ pnpm start # 生产模式
96
106
  | `/doctor` | 运行 Claude 诊断 |
97
107
  | `/compact [topic]` | 压缩当前对话上下文 |
98
108
  | `/todos` | 查看待办事项 |
109
+ | `/threads` | 列出所有话题会话(飞书) |
99
110
 
100
111
  ### 权限相关命令
101
112
 
@@ -112,7 +123,6 @@ pnpm start # 生产模式
112
123
 
113
124
  | 变量 | 说明 | 默认值 |
114
125
  |------|------|--------|
115
- | `PLATFORM` | 平台选择(`telegram` 或 `feishu`),留空自动检测 | 自动检测 |
116
126
  | `FEISHU_APP_ID` | 飞书应用 App ID | 飞书平台必填 |
117
127
  | `FEISHU_APP_SECRET` | 飞书应用 App Secret | 飞书平台必填 |
118
128
  | `TELEGRAM_BOT_TOKEN` | Telegram Bot Token | Telegram 平台必填 |
@@ -136,7 +146,6 @@ pnpm start # 生产模式
136
146
 
137
147
  ```json
138
148
  {
139
- "platform": "telegram",
140
149
  "telegramBotToken": "your_bot_token",
141
150
  "allowedUserIds": ["123456789"],
142
151
  "claudeCliPath": "/usr/local/bin/claude",
@@ -158,7 +167,8 @@ pnpm start # 生产模式
158
167
  ~/.cc-im/
159
168
  ├── config.json # 配置文件
160
169
  ├── data/
161
- └── sessions.json # 会话持久化数据
170
+ ├── sessions.json # 会话持久化数据
171
+ │ └── active-chats.json # 活跃聊天记录(生命周期通知)
162
172
  └── logs/ # 日志文件(可通过 LOG_DIR 自定义)
163
173
  ├── 2026-02-14.log
164
174
  └── 2026-02-15.log
@@ -208,6 +218,10 @@ src/
208
218
  ├── hook/
209
219
  │ ├── permission-server.ts # 权限确认 HTTP 服务
210
220
  │ └── hook-script.ts # Claude Code PreToolUse Hook
221
+ ├── shared/
222
+ │ ├── active-chats.ts # 活跃聊天记录(生命周期通知)
223
+ │ ├── types.ts # 共享类型定义
224
+ │ └── utils.ts # 共享工具函数
211
225
  ├── session/
212
226
  │ └── session-manager.ts # 会话管理(持久化到 data/sessions.json)
213
227
  └── queue/
@@ -21,19 +21,19 @@ export function runClaude(cliPath, prompt, sessionId, workDir, callbacks, option
21
21
  args.push('--', prompt);
22
22
  const env = { ...process.env };
23
23
  if (options?.chatId) {
24
- env.CC_BOT_CHAT_ID = options.chatId;
24
+ env.CC_IM_CHAT_ID = options.chatId;
25
25
  }
26
26
  if (options?.hookPort) {
27
- env.CC_BOT_HOOK_PORT = String(options.hookPort);
27
+ env.CC_IM_HOOK_PORT = String(options.hookPort);
28
28
  }
29
29
  if (options?.threadRootMsgId) {
30
- env.CC_BOT_THREAD_ROOT_MSG_ID = options.threadRootMsgId;
30
+ env.CC_IM_THREAD_ROOT_MSG_ID = options.threadRootMsgId;
31
31
  }
32
32
  if (options?.threadId) {
33
- env.CC_BOT_THREAD_ID = options.threadId;
33
+ env.CC_IM_THREAD_ID = options.threadId;
34
34
  }
35
35
  if (options?.platform) {
36
- env.CC_BOT_PLATFORM = options.platform;
36
+ env.CC_IM_PLATFORM = options.platform;
37
37
  }
38
38
  const child = spawn(cliPath, args, {
39
39
  cwd: workDir,
@@ -153,6 +153,16 @@ export function createEventDispatcher(config) {
153
153
  }
154
154
  },
155
155
  });
156
+ dispatcher.register({
157
+ 'im.message.recalled_v1': async (data) => {
158
+ const messageId = data?.message_id;
159
+ if (!messageId)
160
+ return;
161
+ if (sessionManager.removeThreadByRootMessageId(messageId)) {
162
+ log.info(`Thread session removed for recalled message: ${messageId}`);
163
+ }
164
+ },
165
+ });
156
166
  dispatcher.register({
157
167
  'im.message.receive_v1': async (data) => {
158
168
  const message = data.message;
@@ -7,8 +7,8 @@
7
7
  * which notifies the user via the messaging platform and waits for their decision.
8
8
  *
9
9
  * Environment variables:
10
- * CC_BOT_CHAT_ID - Chat ID to send the permission card to
11
- * CC_BOT_HOOK_PORT - Port of the local permission server (default: 18900)
10
+ * CC_IM_CHAT_ID - Chat ID to send the permission card to
11
+ * CC_IM_HOOK_PORT - Port of the local permission server (default: 18900)
12
12
  *
13
13
  * stdin: JSON { session_id, tool_name, tool_input }
14
14
  * stdout: JSON { permissionDecision: "allow" | "deny" }
@@ -65,8 +65,8 @@ function httpPost(port, path, body) {
65
65
  });
66
66
  }
67
67
  async function main() {
68
- const chatId = process.env.CC_BOT_CHAT_ID;
69
- const port = parseInt(process.env.CC_BOT_HOOK_PORT ?? '18900', 10);
68
+ const chatId = process.env.CC_IM_CHAT_ID;
69
+ const port = parseInt(process.env.CC_IM_HOOK_PORT ?? '18900', 10);
70
70
  // No chat ID configured - allow by default and exit
71
71
  if (!chatId) {
72
72
  // Output allow decision to maintain consistency with hook protocol
@@ -91,9 +91,9 @@ async function main() {
91
91
  process.stdout.write(JSON.stringify({ permissionDecision: 'allow' }));
92
92
  process.exit(HOOK_EXIT_CODES.SUCCESS);
93
93
  }
94
- const threadRootMsgId = process.env.CC_BOT_THREAD_ROOT_MSG_ID;
95
- const threadId = process.env.CC_BOT_THREAD_ID;
96
- const platform = process.env.CC_BOT_PLATFORM;
94
+ const threadRootMsgId = process.env.CC_IM_THREAD_ROOT_MSG_ID;
95
+ const threadId = process.env.CC_IM_THREAD_ID;
96
+ const platform = process.env.CC_IM_PLATFORM;
97
97
  try {
98
98
  const result = await httpPost(port, '/permission-request', {
99
99
  chatId,
@@ -203,6 +203,20 @@ export class SessionManager {
203
203
  }
204
204
  return false;
205
205
  }
206
+ removeThreadByRootMessageId(rootMessageId) {
207
+ for (const [, session] of this.sessions) {
208
+ if (!session.threads)
209
+ continue;
210
+ for (const [threadId, thread] of Object.entries(session.threads)) {
211
+ if (thread.rootMessageId === rootMessageId) {
212
+ delete session.threads[threadId];
213
+ this.save();
214
+ return true;
215
+ }
216
+ }
217
+ }
218
+ return false;
219
+ }
206
220
  listThreads(userId) {
207
221
  const threads = this.sessions.get(userId)?.threads;
208
222
  if (!threads)
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "cc-im",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Multi-platform bot bridge (Feishu & Telegram) for Claude Code CLI",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/congqiu/cc-im.git"
8
+ },
9
+ "homepage": "https://github.com/congqiu/cc-im",
5
10
  "type": "module",
6
11
  "main": "dist/index.js",
7
12
  "bin": {