chatccc 0.2.52 → 0.2.53

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 (39) hide show
  1. package/README.md +95 -256
  2. package/bin/chatccc.mjs +23 -23
  3. package/demo/ilink_echo_probe.ts +222 -222
  4. package/im-skills/feishu-skill/download-video.mjs +162 -162
  5. package/im-skills/feishu-skill/receive-send-file.md +66 -66
  6. package/im-skills/feishu-skill/receive-send-image.md +27 -27
  7. package/im-skills/feishu-skill/send-file.mjs +50 -50
  8. package/im-skills/feishu-skill/send-image.mjs +50 -50
  9. package/im-skills/feishu-skill/skill.md +10 -10
  10. package/package.json +59 -59
  11. package/src/__tests__/agent-image-rpc.test.ts +33 -33
  12. package/src/__tests__/agent-rpc-body.test.ts +41 -41
  13. package/src/__tests__/card-plain-text.test.ts +42 -42
  14. package/src/__tests__/codex-adapter.test.ts +304 -304
  15. package/src/__tests__/config-reload.test.ts +26 -26
  16. package/src/__tests__/config-sample.test.ts +19 -19
  17. package/src/__tests__/crash-logging.test.ts +62 -62
  18. package/src/__tests__/fixtures/codex_simple_text.jsonl +4 -4
  19. package/src/__tests__/fixtures/codex_with_tool.jsonl +6 -6
  20. package/src/__tests__/im-skills.test.ts +46 -46
  21. package/src/__tests__/session.test.ts +57 -2
  22. package/src/__tests__/sim-agent.test.ts +173 -173
  23. package/src/__tests__/sim-platform.test.ts +76 -76
  24. package/src/__tests__/sim-store.test.ts +213 -213
  25. package/src/__tests__/stream-state.test.ts +134 -134
  26. package/src/__tests__/wechat-platform.test.ts +57 -32
  27. package/src/adapters/codex-adapter.ts +294 -294
  28. package/src/adapters/codex-session-meta-store.ts +130 -130
  29. package/src/agent-file-rpc.ts +156 -156
  30. package/src/agent-image-rpc.ts +152 -152
  31. package/src/agent-rpc-body.ts +48 -48
  32. package/src/card-plain-text.ts +108 -108
  33. package/src/im-skills.ts +109 -109
  34. package/src/platform-adapter.ts +60 -60
  35. package/src/session-chat-binding.ts +6 -1
  36. package/src/session.ts +40 -35
  37. package/src/sim-agent.ts +167 -167
  38. package/src/trace.ts +50 -50
  39. package/src/wechat-platform.ts +1 -1
@@ -1,51 +1,51 @@
1
- #!/usr/bin/env node
2
- import { basename } from "node:path";
3
-
4
- function parseArgs(argv) {
5
- const result = {};
6
- for (let i = 0; i < argv.length; i++) {
7
- const key = argv[i];
8
- if (!key.startsWith("--")) continue;
9
- const value = argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[++i] : "true";
10
- result[key.slice(2)] = value;
11
- }
12
- return result;
13
- }
14
-
15
- function usage() {
16
- console.error(`Usage:
17
- node ${basename(process.argv[1])} --url <url> --session-id <session_id> --path <absolute image path> [--caption <text>]`);
18
- }
19
-
20
- async function main() {
21
- const args = parseArgs(process.argv.slice(2));
22
- const url = args.url || process.env.CHATCCC_SEND_IMAGE_URL;
23
- const sessionId = args["session-id"] || args.session_id || process.env.CHATCCC_SESSION_ID;
24
- const path = args.path;
25
- const caption = args.caption || "";
26
-
27
- if (!url || !sessionId || !path) {
28
- usage();
29
- process.exit(1);
30
- }
31
-
32
- const body = Buffer.from(JSON.stringify({ session_id: sessionId, path, caption }), "utf8");
33
- const response = await fetch(url, {
34
- method: "POST",
35
- headers: {
36
- "Content-Type": "application/json; charset=utf-8",
37
- },
38
- body,
39
- });
40
-
41
- const text = await response.text();
42
- if (!response.ok) {
43
- throw new Error(`HTTP ${response.status}: ${text}`);
44
- }
45
- console.log(text);
46
- }
47
-
48
- main().catch((err) => {
49
- console.error(err instanceof Error ? err.message : String(err));
50
- process.exit(1);
1
+ #!/usr/bin/env node
2
+ import { basename } from "node:path";
3
+
4
+ function parseArgs(argv) {
5
+ const result = {};
6
+ for (let i = 0; i < argv.length; i++) {
7
+ const key = argv[i];
8
+ if (!key.startsWith("--")) continue;
9
+ const value = argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[++i] : "true";
10
+ result[key.slice(2)] = value;
11
+ }
12
+ return result;
13
+ }
14
+
15
+ function usage() {
16
+ console.error(`Usage:
17
+ node ${basename(process.argv[1])} --url <url> --session-id <session_id> --path <absolute image path> [--caption <text>]`);
18
+ }
19
+
20
+ async function main() {
21
+ const args = parseArgs(process.argv.slice(2));
22
+ const url = args.url || process.env.CHATCCC_SEND_IMAGE_URL;
23
+ const sessionId = args["session-id"] || args.session_id || process.env.CHATCCC_SESSION_ID;
24
+ const path = args.path;
25
+ const caption = args.caption || "";
26
+
27
+ if (!url || !sessionId || !path) {
28
+ usage();
29
+ process.exit(1);
30
+ }
31
+
32
+ const body = Buffer.from(JSON.stringify({ session_id: sessionId, path, caption }), "utf8");
33
+ const response = await fetch(url, {
34
+ method: "POST",
35
+ headers: {
36
+ "Content-Type": "application/json; charset=utf-8",
37
+ },
38
+ body,
39
+ });
40
+
41
+ const text = await response.text();
42
+ if (!response.ok) {
43
+ throw new Error(`HTTP ${response.status}: ${text}`);
44
+ }
45
+ console.log(text);
46
+ }
47
+
48
+ main().catch((err) => {
49
+ console.error(err instanceof Error ? err.message : String(err));
50
+ process.exit(1);
51
51
  });
@@ -1,11 +1,11 @@
1
- ---
2
- name: feishu-skill
3
- description: Feishu IM local skills for sending and receiving images, files, and videos.
4
- ---
5
-
6
- Current working directory: {{cwd}}
7
-
8
- Use local endpoints instead of calling Feishu Open Platform directly.
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`
1
+ ---
2
+ name: feishu-skill
3
+ description: Feishu IM local skills for sending and receiving images, files, and videos.
4
+ ---
5
+
6
+ Current working directory: {{cwd}}
7
+
8
+ Use local endpoints instead of calling Feishu Open Platform directly.
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
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`
package/package.json CHANGED
@@ -1,59 +1,59 @@
1
- {
2
- "name": "chatccc",
3
- "version": "0.2.52",
4
- "description": "Feishu bot bridge for Claude Code",
5
- "license": "Apache-2.0",
6
- "type": "module",
7
- "main": "./src/index.ts",
8
- "bin": {
9
- "chatccc": "bin/chatccc.mjs"
10
- },
11
- "files": [
12
- "src/",
13
- "bin/",
14
- "demo/ilink_echo_probe.ts",
15
- "im-skills/",
16
- "images/img_readme_*.jpg",
17
- "images/img_readme_*.png",
18
- "images/avatars/status_*.png",
19
- "images/avatars/badges/",
20
- "package.json",
21
- "README.md",
22
- "config.sample.json"
23
- ],
24
- "scripts": {
25
- "dev": "tsx src/index.ts",
26
- "chatccc": "tsx src/index.ts",
27
- "start": "tsx src/index.ts",
28
- "demo:bot-test": "tsx demo/bot_test.ts",
29
- "demo:bot-test:local": "tsx demo/bot_test.ts --local",
30
- "demo:create-group": "tsx src/index.ts",
31
- "demo:create-group:local": "tsx src/index.ts --local",
32
- "demo:permission-check": "tsx demo/permission_check.ts",
33
- "demo:claude-hi": "tsx demo/claude_say_hi.ts",
34
- "demo:codex-hi": "tsx demo/codex_say_hi.ts",
35
- "demo:ilink-echo": "tsx demo/ilink_echo_probe.ts",
36
- "test": "vitest run",
37
- "test:watch": "vitest"
38
- },
39
- "dependencies": {
40
- "@anthropic-ai/claude-agent-sdk": "0.2.133",
41
- "@larksuiteoapi/node-sdk": "^1.59.0",
42
- "@openilink/openilink-sdk-node": "^0.6.0",
43
- "nodemailer": "^8.0.7",
44
- "qrcode-terminal": "^0.12.0",
45
- "sharp": "^0.34.5",
46
- "tsx": "^4.0.0",
47
- "ws": "^8.18.0"
48
- },
49
- "devDependencies": {
50
- "@types/node": "^20.0.0",
51
- "@types/qrcode-terminal": "^0.12.2",
52
- "@types/ws": "^8.18.1",
53
- "typescript": "^5.0.0",
54
- "vitest": "^4.1.5"
55
- },
56
- "engines": {
57
- "node": ">=20"
58
- }
59
- }
1
+ {
2
+ "name": "chatccc",
3
+ "version": "0.2.53",
4
+ "description": "Feishu bot bridge for Claude Code",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "./src/index.ts",
8
+ "bin": {
9
+ "chatccc": "bin/chatccc.mjs"
10
+ },
11
+ "files": [
12
+ "src/",
13
+ "bin/",
14
+ "demo/ilink_echo_probe.ts",
15
+ "im-skills/",
16
+ "images/img_readme_*.jpg",
17
+ "images/img_readme_*.png",
18
+ "images/avatars/status_*.png",
19
+ "images/avatars/badges/",
20
+ "package.json",
21
+ "README.md",
22
+ "config.sample.json"
23
+ ],
24
+ "scripts": {
25
+ "dev": "tsx src/index.ts",
26
+ "chatccc": "tsx src/index.ts",
27
+ "start": "tsx src/index.ts",
28
+ "demo:bot-test": "tsx demo/bot_test.ts",
29
+ "demo:bot-test:local": "tsx demo/bot_test.ts --local",
30
+ "demo:create-group": "tsx src/index.ts",
31
+ "demo:create-group:local": "tsx src/index.ts --local",
32
+ "demo:permission-check": "tsx demo/permission_check.ts",
33
+ "demo:claude-hi": "tsx demo/claude_say_hi.ts",
34
+ "demo:codex-hi": "tsx demo/codex_say_hi.ts",
35
+ "demo:ilink-echo": "tsx demo/ilink_echo_probe.ts",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest"
38
+ },
39
+ "dependencies": {
40
+ "@anthropic-ai/claude-agent-sdk": "0.2.133",
41
+ "@larksuiteoapi/node-sdk": "^1.59.0",
42
+ "@openilink/openilink-sdk-node": "^0.6.0",
43
+ "nodemailer": "^8.0.7",
44
+ "qrcode-terminal": "^0.12.0",
45
+ "sharp": "^0.34.5",
46
+ "tsx": "^4.0.0",
47
+ "ws": "^8.18.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^20.0.0",
51
+ "@types/qrcode-terminal": "^0.12.2",
52
+ "@types/ws": "^8.18.1",
53
+ "typescript": "^5.0.0",
54
+ "vitest": "^4.1.5"
55
+ },
56
+ "engines": {
57
+ "node": ">=20"
58
+ }
59
+ }
@@ -1,34 +1,34 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import {
4
- AGENT_SEND_IMAGE_PATH,
5
- buildAgentImageCapabilityPrompt,
6
- } from "../agent-image-rpc.ts";
7
-
8
- describe("agent image RPC", () => {
9
- it("builds prompt instructions with session_id", () => {
10
- const prompt = buildAgentImageCapabilityPrompt({
11
- url: `http://127.0.0.1:18080${AGENT_SEND_IMAGE_PATH}`,
12
- sessionId: "sid-test",
13
- });
14
-
15
- expect(prompt).toContain("POST http://127.0.0.1:18080/api/agent/send-image");
16
- expect(prompt).toContain('"session_id":"sid-test"');
17
- expect(prompt).toContain("Content-Type: application/json; charset=utf-8");
18
- expect(prompt).toContain("UTF-8 encoded JSON bytes");
19
- expect(prompt).toContain('"path"');
20
- expect(prompt).not.toContain("Authorization: Bearer");
21
- expect(prompt).not.toContain("CHATCCC_SEND_IMAGE_URL");
22
- expect(prompt).not.toContain("CHATCCC_SEND_IMAGE_TOKEN");
23
- });
24
-
25
- it("builds prompt with cwd hint", () => {
26
- const prompt = buildAgentImageCapabilityPrompt({
27
- url: `http://127.0.0.1:18080${AGENT_SEND_IMAGE_PATH}`,
28
- sessionId: "sid-1",
29
- cwd: "F:/repo",
30
- });
31
-
32
- expect(prompt).toContain("Current working directory: F:/repo");
33
- });
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ AGENT_SEND_IMAGE_PATH,
5
+ buildAgentImageCapabilityPrompt,
6
+ } from "../agent-image-rpc.ts";
7
+
8
+ describe("agent image RPC", () => {
9
+ it("builds prompt instructions with session_id", () => {
10
+ const prompt = buildAgentImageCapabilityPrompt({
11
+ url: `http://127.0.0.1:18080${AGENT_SEND_IMAGE_PATH}`,
12
+ sessionId: "sid-test",
13
+ });
14
+
15
+ expect(prompt).toContain("POST http://127.0.0.1:18080/api/agent/send-image");
16
+ expect(prompt).toContain('"session_id":"sid-test"');
17
+ expect(prompt).toContain("Content-Type: application/json; charset=utf-8");
18
+ expect(prompt).toContain("UTF-8 encoded JSON bytes");
19
+ expect(prompt).toContain('"path"');
20
+ expect(prompt).not.toContain("Authorization: Bearer");
21
+ expect(prompt).not.toContain("CHATCCC_SEND_IMAGE_URL");
22
+ expect(prompt).not.toContain("CHATCCC_SEND_IMAGE_TOKEN");
23
+ });
24
+
25
+ it("builds prompt with cwd hint", () => {
26
+ const prompt = buildAgentImageCapabilityPrompt({
27
+ url: `http://127.0.0.1:18080${AGENT_SEND_IMAGE_PATH}`,
28
+ sessionId: "sid-1",
29
+ cwd: "F:/repo",
30
+ });
31
+
32
+ expect(prompt).toContain("Current working directory: F:/repo");
33
+ });
34
34
  });
@@ -1,41 +1,41 @@
1
- import type { IncomingMessage } from "node:http";
2
- import { Readable } from "node:stream";
3
-
4
- import { describe, expect, it } from "vitest";
5
-
6
- import { getRequestCharset, readUtf8JsonBody } from "../agent-rpc-body.ts";
7
-
8
- function requestFrom(body: Buffer, contentType?: string): IncomingMessage {
9
- const req = Readable.from([body]) as IncomingMessage;
10
- req.headers = {};
11
- if (contentType) req.headers["content-type"] = contentType;
12
- return req;
13
- }
14
-
15
- describe("agent RPC body parsing", () => {
16
- it("defaults JSON requests to UTF-8 and preserves Unicode captions", async () => {
17
- const caption = "\u4e2d\u6587 caption";
18
- const body = Buffer.from(JSON.stringify({ caption }), "utf8");
19
- const req = requestFrom(body, "application/json");
20
-
21
- await expect(readUtf8JsonBody(req, 1024)).resolves.toEqual({ caption });
22
- });
23
-
24
- it("reads charset from content-type", () => {
25
- const req = requestFrom(Buffer.from("{}"), 'application/json; charset="utf-8"');
26
-
27
- expect(getRequestCharset(req)).toBe("utf-8");
28
- });
29
-
30
- it("rejects non UTF-8 charsets", async () => {
31
- const req = requestFrom(Buffer.from("{}"), "application/json; charset=gbk");
32
-
33
- await expect(readUtf8JsonBody(req, 1024)).rejects.toThrow("Unsupported charset");
34
- });
35
-
36
- it("rejects invalid UTF-8 bytes", async () => {
37
- const req = requestFrom(Buffer.from([0xff, 0xfe]), "application/json; charset=utf-8");
38
-
39
- await expect(readUtf8JsonBody(req, 1024)).rejects.toThrow("valid UTF-8");
40
- });
41
- });
1
+ import type { IncomingMessage } from "node:http";
2
+ import { Readable } from "node:stream";
3
+
4
+ import { describe, expect, it } from "vitest";
5
+
6
+ import { getRequestCharset, readUtf8JsonBody } from "../agent-rpc-body.ts";
7
+
8
+ function requestFrom(body: Buffer, contentType?: string): IncomingMessage {
9
+ const req = Readable.from([body]) as IncomingMessage;
10
+ req.headers = {};
11
+ if (contentType) req.headers["content-type"] = contentType;
12
+ return req;
13
+ }
14
+
15
+ describe("agent RPC body parsing", () => {
16
+ it("defaults JSON requests to UTF-8 and preserves Unicode captions", async () => {
17
+ const caption = "\u4e2d\u6587 caption";
18
+ const body = Buffer.from(JSON.stringify({ caption }), "utf8");
19
+ const req = requestFrom(body, "application/json");
20
+
21
+ await expect(readUtf8JsonBody(req, 1024)).resolves.toEqual({ caption });
22
+ });
23
+
24
+ it("reads charset from content-type", () => {
25
+ const req = requestFrom(Buffer.from("{}"), 'application/json; charset="utf-8"');
26
+
27
+ expect(getRequestCharset(req)).toBe("utf-8");
28
+ });
29
+
30
+ it("rejects non UTF-8 charsets", async () => {
31
+ const req = requestFrom(Buffer.from("{}"), "application/json; charset=gbk");
32
+
33
+ await expect(readUtf8JsonBody(req, 1024)).rejects.toThrow("Unsupported charset");
34
+ });
35
+
36
+ it("rejects invalid UTF-8 bytes", async () => {
37
+ const req = requestFrom(Buffer.from([0xff, 0xfe]), "application/json; charset=utf-8");
38
+
39
+ await expect(readUtf8JsonBody(req, 1024)).rejects.toThrow("valid UTF-8");
40
+ });
41
+ });
@@ -1,42 +1,42 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { cardJsonToPlainText } from "../card-plain-text.ts";
4
- import {
5
- buildHelpCard,
6
- buildProgressCard,
7
- buildStatusCard,
8
- } from "../cards.ts";
9
-
10
- describe("cardJsonToPlainText", () => {
11
- it("converts help cards to readable text with commands", () => {
12
- const text = cardJsonToPlainText(buildHelpCard("hello"));
13
-
14
- expect(text).toContain("# ChatCCC");
15
- expect(text).toContain("hello");
16
- expect(text).toContain("/new");
17
- expect(text).toContain("/new cursor");
18
- expect(text).toContain("/new codex");
19
- expect(text).toContain("/restart");
20
- expect(text).toContain("/cd");
21
- });
22
-
23
- it("converts status cards from v1 card format", () => {
24
- const text = cardJsonToPlainText(buildStatusCard("status body", "green"));
25
-
26
- expect(text).toContain("# 会话状态");
27
- expect(text).toContain("status body");
28
- });
29
-
30
- it("converts schema 2.0 progress cards", () => {
31
- const text = cardJsonToPlainText(buildProgressCard("stream body"));
32
-
33
- expect(text).toContain("# 生成中...");
34
- expect(text).toContain("stream body");
35
- expect(text).toContain("/status");
36
- expect(text).toContain("/stop");
37
- });
38
-
39
- it("returns null for invalid card json", () => {
40
- expect(cardJsonToPlainText("{bad json")).toBeNull();
41
- });
42
- });
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { cardJsonToPlainText } from "../card-plain-text.ts";
4
+ import {
5
+ buildHelpCard,
6
+ buildProgressCard,
7
+ buildStatusCard,
8
+ } from "../cards.ts";
9
+
10
+ describe("cardJsonToPlainText", () => {
11
+ it("converts help cards to readable text with commands", () => {
12
+ const text = cardJsonToPlainText(buildHelpCard("hello"));
13
+
14
+ expect(text).toContain("# ChatCCC");
15
+ expect(text).toContain("hello");
16
+ expect(text).toContain("/new");
17
+ expect(text).toContain("/new cursor");
18
+ expect(text).toContain("/new codex");
19
+ expect(text).toContain("/restart");
20
+ expect(text).toContain("/cd");
21
+ });
22
+
23
+ it("converts status cards from v1 card format", () => {
24
+ const text = cardJsonToPlainText(buildStatusCard("status body", "green"));
25
+
26
+ expect(text).toContain("# 会话状态");
27
+ expect(text).toContain("status body");
28
+ });
29
+
30
+ it("converts schema 2.0 progress cards", () => {
31
+ const text = cardJsonToPlainText(buildProgressCard("stream body"));
32
+
33
+ expect(text).toContain("# 生成中...");
34
+ expect(text).toContain("stream body");
35
+ expect(text).toContain("/status");
36
+ expect(text).toContain("/stop");
37
+ });
38
+
39
+ it("returns null for invalid card json", () => {
40
+ expect(cardJsonToPlainText("{bad json")).toBeNull();
41
+ });
42
+ });