chatccc 0.1.5 → 0.2.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.
package/README.md CHANGED
@@ -96,25 +96,23 @@ cp .env.example .env
96
96
  编辑 `.env`,填入上一步拿到的凭证:
97
97
 
98
98
  ```env
99
- FEISHU_CLAUDER_APP_ID=cli_xxxxxxxxxxxx
100
- FEISHU_CLAUDER_APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxx
99
+ CHATCCC_APP_ID=cli_xxxxxxxxxxxx
100
+ CHATCCC_APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxx
101
101
  ```
102
102
 
103
- 可选的高级配置:
103
+ 若你曾使用旧版变量名 `FEISHU_CLAUDER_APP_ID` / `FEISHU_CLAUDER_APP_SECRET`,请改为上表中的 `CHATCCC_APP_ID` / `CHATCCC_APP_SECRET`。
104
+
105
+ 可选:Claude 模型与思考深度(**默认均为 `default`**):
104
106
 
105
107
  ```env
106
- # Claude 模型(见下方优先级说明)
107
- CHATCCC_ANTHROPIC_MODEL=dashscope/deepseek-v4-pro-anthropic
108
+ # 网关或服务商文档中的 model;设为 default(任意大小写)或不写则交给 SDK/CLI 默认,且 /status 中显示为 default
109
+ CHATCCC_ANTHROPIC_MODEL=default
108
110
 
109
- # 思考深度,可选值: low | medium | high | max
110
- CHATCCC_ANTHROPIC_EFFORT=max
111
+ # low / medium / high / max;设为 default(任意大小写)或不写则不向 SDK 传入 effort,/status 显示 default
112
+ CHATCCC_ANTHROPIC_EFFORT=default
111
113
  ```
112
114
 
113
- `**CHATCCC_ANTHROPIC_MODEL` 优先级**:环境变量设置的值 > 代码内默认值 `dashscope/deepseek-v4-pro-anthropic`。不设或设为空白时自动使用默认模型。
114
-
115
- `**CHATCCC_ANTHROPIC_EFFORT` 优先级**:环境变量设置的值 > 代码内默认值 `max`。
116
-
117
- `**CHATCCC_PORT` 端口配置**:默认端口为 `18080`。如果你需要在一台机器上同时运行多个 ChatCCC 实例(例如用不同飞书应用分别接入不同项目),可以在各自的 `.env` 中设置不同的端口:
115
+ `**CHATCCC_PORT`(可选)**:默认端口为 `18080`。若在一台机器上同时运行多个 ChatCCC 实例,可在各自的 `.env` 中设置不同端口:
118
116
 
119
117
  ```env
120
118
  # 实例 A(项目1)的 .env
package/bin/chatccc.mjs CHANGED
@@ -1,19 +1,36 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
+ import { existsSync } from "node:fs";
3
4
  import { createRequire } from "node:module";
4
- import { fileURLToPath } from "node:url";
5
5
  import { dirname, join } from "node:path";
6
6
 
7
7
  const require = createRequire(import.meta.url);
8
- // bin/ 的上一级才是 chatccc 包根;勿对 new URL("..") 再 dirname,否则会退到 node_modules
9
- const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
8
+ // 相对 bin/chatccc.mjs 解析 package.json,包根目录不依赖 cwd,也不会多退一级到 node_modules
9
+ const pkgRoot = dirname(require.resolve("../package.json"));
10
10
  const indexTs = join(pkgRoot, "src", "index.ts");
11
11
  const tsxCli = require.resolve("tsx/cli");
12
12
 
13
- const result = spawnSync(
14
- process.execPath,
15
- [tsxCli, indexTs, ...process.argv.slice(2)],
16
- { stdio: "inherit", cwd: process.cwd(), env: process.env }
17
- );
13
+ // npm run dev 一致:当前工作目录下的 .env 会被加载(全局安装时 cwd 一般为你的项目根)
14
+ const envFile = join(process.cwd(), ".env");
15
+ if (!existsSync(envFile)) {
16
+ console.error(`[chatccc] 当前目录下未找到 .env,不会自动加载环境变量: ${envFile}`);
17
+ console.error(" 将只使用系统/用户环境变量。若未设置 CHATCCC_APP_ID / CHATCCC_APP_SECRET,启动会失败。");
18
+ console.error(" 建议: cd 到项目根目录,复制 .env.example 为 .env 并填写后再运行 chatccc。\n");
19
+ }
20
+ const tsxArgs = existsSync(envFile)
21
+ ? [tsxCli, "--env-file", envFile, indexTs, ...process.argv.slice(2)]
22
+ : [tsxCli, indexTs, ...process.argv.slice(2)];
18
23
 
19
- process.exit(result.status === null ? 1 : result.status);
24
+ const result = spawnSync(process.execPath, tsxArgs, {
25
+ stdio: "inherit",
26
+ cwd: process.cwd(),
27
+ env: process.env,
28
+ });
29
+
30
+ const code = result.status === null ? 1 : result.status;
31
+ if (code !== 0) {
32
+ console.error("[ 未启动 ]");
33
+ console.error("chatccc 子进程已结束,服务没有在后台运行。");
34
+ }
35
+
36
+ process.exit(code);
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "chatccc",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
4
4
  "description": "Feishu bot bridge for Claude Code",
5
+ "license": "Apache-2.0",
5
6
  "type": "module",
6
7
  "main": "./src/index.ts",
7
8
  "bin": {
package/src/cardkit.ts CHANGED
@@ -1,158 +1,158 @@
1
- import { BASE_URL, fileLog, ts } from "./config.ts";
2
-
3
- // CardKit 是飞书的卡片流式更新 API,通过 cardElement.content 端点
4
- // 对卡片上指定 element_id 的元素进行增量内容更新,实现打字机效果。
5
- // 与普通卡片 PATCH 的区别:PATCH 是全量替换卡片 JSON,CardKit 是
6
- // 由飞书客户端自行渲染增量文本,用户看到的是逐字出现的效果。
7
- // 参考: https://open.feishu.cn/document/uYjLw4iNukDO6YDN4ITM4MjM
8
-
9
- export async function createCardKitCard(token: string, cardJson: string): Promise<string> {
10
- const body = JSON.stringify({ type: "card_json", data: cardJson });
11
- const resp = await fetch(`${BASE_URL}/cardkit/v1/cards`, {
12
- method: "POST",
13
- headers: {
14
- Authorization: `Bearer ${token}`,
15
- "Content-Type": "application/json",
16
- },
17
- body,
18
- });
19
- const respText = await resp.text();
20
- let data: { code: number; msg?: string; data?: { card_id?: string } };
21
- try {
22
- data = JSON.parse(respText);
23
- } catch {
24
- console.error(`[${ts()}] [CARDIKT] createCard: invalid JSON response (status=${resp.status}) body=${respText.slice(0, 500)}`);
25
- fileLog.flush();
26
- throw new Error(`CardKit create: invalid JSON response (status=${resp.status})`);
27
- }
28
- if (data.code !== 0) {
29
- console.error(`[${ts()}] [CARDIKT] createCard FAIL: status=${resp.status} code=${data.code} msg="${data.msg}" body=${respText.slice(0, 500)}`);
30
- fileLog.flush();
31
- throw new Error(`CardKit create: [${data.code}] ${data.msg}`);
32
- }
33
- console.log(`[${ts()}] [CARDIKT] createCard OK cardId=${data.data?.card_id ?? "(none)"}`);
34
- return data.data?.card_id ?? "";
35
- }
36
-
37
- export async function setCardKitSettings(
38
- token: string,
39
- cardId: string,
40
- settings: Record<string, unknown>,
41
- sequence: number
42
- ): Promise<void> {
43
- const resp = await fetch(`${BASE_URL}/cardkit/v1/cards/${cardId}/settings`, {
44
- method: "PATCH",
45
- headers: {
46
- Authorization: `Bearer ${token}`,
47
- "Content-Type": "application/json",
48
- },
49
- body: JSON.stringify({ settings: JSON.stringify(settings), sequence }),
50
- });
51
- const respText = await resp.text();
52
- let data: { code: number; msg?: string };
53
- try { data = JSON.parse(respText); } catch {
54
- console.error(`[${ts()}] [CARDIKT] settings FAIL: invalid JSON cardId=${cardId} seq=${sequence} status=${resp.status}`);
55
- fileLog.flush();
56
- throw new Error(`CardKit settings: invalid JSON (status=${resp.status})`);
57
- }
58
- if (data.code !== 0) {
59
- console.error(`[${ts()}] [CARDIKT] settings FAIL: cardId=${cardId} seq=${sequence} status=${resp.status} code=${data.code} msg="${data.msg}"`);
60
- fileLog.flush();
61
- throw new Error(`CardKit settings: [${data.code}] ${data.msg}`);
62
- }
63
- console.log(`[${ts()}] [CARDIKT] settings OK cardId=${cardId} seq=${sequence}`);
64
- }
65
-
66
- export async function streamCardKitElement(
67
- token: string,
68
- cardId: string,
69
- elementId: string,
70
- content: string,
71
- sequence: number
72
- ): Promise<void> {
73
- const resp = await fetch(
74
- `${BASE_URL}/cardkit/v1/cards/${cardId}/elements/${elementId}/content`,
75
- {
76
- method: "PUT",
77
- headers: {
78
- Authorization: `Bearer ${token}`,
79
- "Content-Type": "application/json",
80
- },
81
- body: JSON.stringify({ content, sequence }),
82
- }
83
- );
84
- const respText = await resp.text();
85
- let data: { code: number; msg?: string };
86
- try { data = JSON.parse(respText); } catch {
87
- console.error(`[${ts()}] [CARDIKT] streamElement FAIL: invalid JSON cardId=${cardId} element=${elementId} seq=${sequence} status=${resp.status}`);
88
- fileLog.flush();
89
- throw new Error(`CardKit stream: invalid JSON (status=${resp.status})`);
90
- }
91
- if (data.code !== 0) {
92
- console.error(`[${ts()}] [CARDIKT] streamElement FAIL: cardId=${cardId} element=${elementId} seq=${sequence} status=${resp.status} code=${data.code} msg="${data.msg}"`);
93
- fileLog.flush();
94
- throw new Error(`CardKit stream: [${data.code}] ${data.msg}`);
95
- }
96
- // success log is intentionally sparse — uncomment to debug streaming throughput
97
- // console.log(`[${ts()}] [CARDIKT] streamElement OK cardId=${cardId} seq=${sequence}`);
98
- }
99
-
100
- export async function updateCardKitCard(
101
- token: string,
102
- cardId: string,
103
- cardJson: string,
104
- sequence: number
105
- ): Promise<void> {
106
- const resp = await fetch(`${BASE_URL}/cardkit/v1/cards/${cardId}`, {
107
- method: "PUT",
108
- headers: {
109
- Authorization: `Bearer ${token}`,
110
- "Content-Type": "application/json",
111
- },
112
- body: JSON.stringify({ card: { type: "card_json", data: cardJson }, sequence }),
113
- });
114
- const respText = await resp.text();
115
- let data: { code: number; msg?: string };
116
- try { data = JSON.parse(respText); } catch {
117
- console.error(`[${ts()}] [CARDIKT] updateCard FAIL: invalid JSON cardId=${cardId} seq=${sequence} status=${resp.status}`);
118
- fileLog.flush();
119
- throw new Error(`CardKit update: invalid JSON (status=${resp.status})`);
120
- }
121
- if (data.code !== 0) {
122
- console.error(`[${ts()}] [CARDIKT] updateCard FAIL: cardId=${cardId} seq=${sequence} status=${resp.status} code=${data.code} msg="${data.msg}"`);
123
- fileLog.flush();
124
- throw new Error(`CardKit update: [${data.code}] ${data.msg}`);
125
- }
126
- console.log(`[${ts()}] [CARDIKT] updateCard OK cardId=${cardId} seq=${sequence}`);
127
- }
128
-
129
- export async function sendCardKitMessage(
130
- token: string,
131
- chatId: string,
132
- cardId: string
133
- ): Promise<string> {
134
- const payload = { receive_id: chatId, msg_type: "interactive", content: JSON.stringify({ type: "card", data: { card_id: cardId } }) };
135
- const url = `${BASE_URL}/im/v1/messages?receive_id_type=chat_id`;
136
- const resp = await fetch(url, {
137
- method: "POST",
138
- headers: {
139
- Authorization: `Bearer ${token}`,
140
- "Content-Type": "application/json",
141
- },
142
- body: JSON.stringify(payload),
143
- });
144
- const respText = await resp.text();
145
- let data: { code: number; msg?: string; data?: { message_id?: string } };
146
- try { data = JSON.parse(respText); } catch {
147
- console.error(`[${ts()}] [CARDIKT] sendMessage FAIL: invalid JSON cardId=${cardId} chatId=${chatId} status=${resp.status}`);
148
- fileLog.flush();
149
- throw new Error(`sendCardKitMessage: invalid JSON (status=${resp.status})`);
150
- }
151
- if (data.code !== 0) {
152
- console.error(`[${ts()}] [CARDIKT] sendMessage FAIL: status=${resp.status} code=${data.code} msg="${data.msg}" cardId=${cardId} chatId=${chatId}`);
153
- fileLog.flush();
154
- throw new Error(`[${data.code}] ${data.msg}`);
155
- }
156
- console.log(`[${ts()}] [CARDIKT] sendMessage OK cardId=${cardId} chatId=${chatId} msgId=${data.data?.message_id ?? "(none)"}`);
157
- return data.data?.message_id ?? "";
1
+ import { BASE_URL, fileLog, ts } from "./config.ts";
2
+
3
+ // CardKit 是飞书的卡片流式更新 API,通过 cardElement.content 端点
4
+ // 对卡片上指定 element_id 的元素进行增量内容更新,实现打字机效果。
5
+ // 与普通卡片 PATCH 的区别:PATCH 是全量替换卡片 JSON,CardKit 是
6
+ // 由飞书客户端自行渲染增量文本,用户看到的是逐字出现的效果。
7
+ // 参考: https://open.feishu.cn/document/uYjLw4iNukDO6YDN4ITM4MjM
8
+
9
+ export async function createCardKitCard(token: string, cardJson: string): Promise<string> {
10
+ const body = JSON.stringify({ type: "card_json", data: cardJson });
11
+ const resp = await fetch(`${BASE_URL}/cardkit/v1/cards`, {
12
+ method: "POST",
13
+ headers: {
14
+ Authorization: `Bearer ${token}`,
15
+ "Content-Type": "application/json",
16
+ },
17
+ body,
18
+ });
19
+ const respText = await resp.text();
20
+ let data: { code: number; msg?: string; data?: { card_id?: string } };
21
+ try {
22
+ data = JSON.parse(respText);
23
+ } catch {
24
+ console.error(`[${ts()}] [CARDIKT] createCard: invalid JSON response (status=${resp.status}) body=${respText.slice(0, 500)}`);
25
+ fileLog.flush();
26
+ throw new Error(`CardKit create: invalid JSON response (status=${resp.status})`);
27
+ }
28
+ if (data.code !== 0) {
29
+ console.error(`[${ts()}] [CARDIKT] createCard FAIL: status=${resp.status} code=${data.code} msg="${data.msg}" body=${respText.slice(0, 500)}`);
30
+ fileLog.flush();
31
+ throw new Error(`CardKit create: [${data.code}] ${data.msg}`);
32
+ }
33
+ console.log(`[${ts()}] [CARDIKT] createCard OK cardId=${data.data?.card_id ?? "(none)"}`);
34
+ return data.data?.card_id ?? "";
35
+ }
36
+
37
+ export async function setCardKitSettings(
38
+ token: string,
39
+ cardId: string,
40
+ settings: Record<string, unknown>,
41
+ sequence: number
42
+ ): Promise<void> {
43
+ const resp = await fetch(`${BASE_URL}/cardkit/v1/cards/${cardId}/settings`, {
44
+ method: "PATCH",
45
+ headers: {
46
+ Authorization: `Bearer ${token}`,
47
+ "Content-Type": "application/json",
48
+ },
49
+ body: JSON.stringify({ settings: JSON.stringify(settings), sequence }),
50
+ });
51
+ const respText = await resp.text();
52
+ let data: { code: number; msg?: string };
53
+ try { data = JSON.parse(respText); } catch {
54
+ console.error(`[${ts()}] [CARDIKT] settings FAIL: invalid JSON cardId=${cardId} seq=${sequence} status=${resp.status}`);
55
+ fileLog.flush();
56
+ throw new Error(`CardKit settings: invalid JSON (status=${resp.status})`);
57
+ }
58
+ if (data.code !== 0) {
59
+ console.error(`[${ts()}] [CARDIKT] settings FAIL: cardId=${cardId} seq=${sequence} status=${resp.status} code=${data.code} msg="${data.msg}"`);
60
+ fileLog.flush();
61
+ throw new Error(`CardKit settings: [${data.code}] ${data.msg}`);
62
+ }
63
+ console.log(`[${ts()}] [CARDIKT] settings OK cardId=${cardId} seq=${sequence}`);
64
+ }
65
+
66
+ export async function streamCardKitElement(
67
+ token: string,
68
+ cardId: string,
69
+ elementId: string,
70
+ content: string,
71
+ sequence: number
72
+ ): Promise<void> {
73
+ const resp = await fetch(
74
+ `${BASE_URL}/cardkit/v1/cards/${cardId}/elements/${elementId}/content`,
75
+ {
76
+ method: "PUT",
77
+ headers: {
78
+ Authorization: `Bearer ${token}`,
79
+ "Content-Type": "application/json",
80
+ },
81
+ body: JSON.stringify({ content, sequence }),
82
+ }
83
+ );
84
+ const respText = await resp.text();
85
+ let data: { code: number; msg?: string };
86
+ try { data = JSON.parse(respText); } catch {
87
+ console.error(`[${ts()}] [CARDIKT] streamElement FAIL: invalid JSON cardId=${cardId} element=${elementId} seq=${sequence} status=${resp.status}`);
88
+ fileLog.flush();
89
+ throw new Error(`CardKit stream: invalid JSON (status=${resp.status})`);
90
+ }
91
+ if (data.code !== 0) {
92
+ console.error(`[${ts()}] [CARDIKT] streamElement FAIL: cardId=${cardId} element=${elementId} seq=${sequence} status=${resp.status} code=${data.code} msg="${data.msg}"`);
93
+ fileLog.flush();
94
+ throw new Error(`CardKit stream: [${data.code}] ${data.msg}`);
95
+ }
96
+ // success log is intentionally sparse — uncomment to debug streaming throughput
97
+ // console.log(`[${ts()}] [CARDIKT] streamElement OK cardId=${cardId} seq=${sequence}`);
98
+ }
99
+
100
+ export async function updateCardKitCard(
101
+ token: string,
102
+ cardId: string,
103
+ cardJson: string,
104
+ sequence: number
105
+ ): Promise<void> {
106
+ const resp = await fetch(`${BASE_URL}/cardkit/v1/cards/${cardId}`, {
107
+ method: "PUT",
108
+ headers: {
109
+ Authorization: `Bearer ${token}`,
110
+ "Content-Type": "application/json",
111
+ },
112
+ body: JSON.stringify({ card: { type: "card_json", data: cardJson }, sequence }),
113
+ });
114
+ const respText = await resp.text();
115
+ let data: { code: number; msg?: string };
116
+ try { data = JSON.parse(respText); } catch {
117
+ console.error(`[${ts()}] [CARDIKT] updateCard FAIL: invalid JSON cardId=${cardId} seq=${sequence} status=${resp.status}`);
118
+ fileLog.flush();
119
+ throw new Error(`CardKit update: invalid JSON (status=${resp.status})`);
120
+ }
121
+ if (data.code !== 0) {
122
+ console.error(`[${ts()}] [CARDIKT] updateCard FAIL: cardId=${cardId} seq=${sequence} status=${resp.status} code=${data.code} msg="${data.msg}"`);
123
+ fileLog.flush();
124
+ throw new Error(`CardKit update: [${data.code}] ${data.msg}`);
125
+ }
126
+ console.log(`[${ts()}] [CARDIKT] updateCard OK cardId=${cardId} seq=${sequence}`);
127
+ }
128
+
129
+ export async function sendCardKitMessage(
130
+ token: string,
131
+ chatId: string,
132
+ cardId: string
133
+ ): Promise<string> {
134
+ const payload = { receive_id: chatId, msg_type: "interactive", content: JSON.stringify({ type: "card", data: { card_id: cardId } }) };
135
+ const url = `${BASE_URL}/im/v1/messages?receive_id_type=chat_id`;
136
+ const resp = await fetch(url, {
137
+ method: "POST",
138
+ headers: {
139
+ Authorization: `Bearer ${token}`,
140
+ "Content-Type": "application/json",
141
+ },
142
+ body: JSON.stringify(payload),
143
+ });
144
+ const respText = await resp.text();
145
+ let data: { code: number; msg?: string; data?: { message_id?: string } };
146
+ try { data = JSON.parse(respText); } catch {
147
+ console.error(`[${ts()}] [CARDIKT] sendMessage FAIL: invalid JSON cardId=${cardId} chatId=${chatId} status=${resp.status}`);
148
+ fileLog.flush();
149
+ throw new Error(`sendCardKitMessage: invalid JSON (status=${resp.status})`);
150
+ }
151
+ if (data.code !== 0) {
152
+ console.error(`[${ts()}] [CARDIKT] sendMessage FAIL: status=${resp.status} code=${data.code} msg="${data.msg}" cardId=${cardId} chatId=${chatId}`);
153
+ fileLog.flush();
154
+ throw new Error(`[${data.code}] ${data.msg}`);
155
+ }
156
+ console.log(`[${ts()}] [CARDIKT] sendMessage OK cardId=${cardId} chatId=${chatId} msgId=${data.data?.message_id ?? "(none)"}`);
157
+ return data.data?.message_id ?? "";
158
158
  }