chatccc 0.2.267 → 0.2.269

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.
@@ -0,0 +1,271 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { USER_DATA_DIR } from "./config.js";
5
+ export const SAFE_MAINTENANCE_FILE = join(USER_DATA_DIR, "state", "safe-maintenance.json");
6
+ export const SAFE_MAINTENANCE_STABLE_IDLE_MS = 1_000;
7
+ const EMPTY_SNAPSHOT = {
8
+ activeSessionIds: [],
9
+ queuedSessionIds: [],
10
+ activeEngineIds: [],
11
+ activeWorkLabels: [],
12
+ };
13
+ export class SafeMaintenanceCoordinator {
14
+ filePath;
15
+ now;
16
+ idFactory;
17
+ stableIdleMs;
18
+ pollMs;
19
+ autoPoll;
20
+ trackedWork = new Map();
21
+ runtime = null;
22
+ job;
23
+ snapshot = EMPTY_SNAPSHOT;
24
+ stableSince = null;
25
+ timer = null;
26
+ tickRunning = false;
27
+ constructor(options = {}) {
28
+ this.filePath = options.filePath ?? SAFE_MAINTENANCE_FILE;
29
+ this.now = options.now ?? (() => new Date());
30
+ this.idFactory = options.idFactory ?? randomUUID;
31
+ this.stableIdleMs = options.stableIdleMs ?? SAFE_MAINTENANCE_STABLE_IDLE_MS;
32
+ this.pollMs = options.pollMs ?? 500;
33
+ this.autoPoll = options.autoPoll ?? true;
34
+ this.job = readJob(this.filePath);
35
+ }
36
+ configure(runtime) {
37
+ this.runtime = runtime;
38
+ }
39
+ isAdmissionClosed() {
40
+ return this.job?.phase === "draining" || this.job?.phase === "executing";
41
+ }
42
+ beginTrackedWork(label) {
43
+ const id = this.idFactory();
44
+ this.trackedWork.set(id, label);
45
+ let released = false;
46
+ return () => {
47
+ if (released)
48
+ return;
49
+ released = true;
50
+ this.trackedWork.delete(id);
51
+ };
52
+ }
53
+ async schedule(kind, requester) {
54
+ const now = this.now().toISOString();
55
+ if (this.job?.phase === "executing")
56
+ return structuredClone(this.job);
57
+ const previous = this.job;
58
+ if (this.job?.phase === "draining") {
59
+ const requesters = addRequester(this.job.requesters, requester);
60
+ this.job = {
61
+ ...this.job,
62
+ kind: this.job.kind === "update" || kind === "update" ? "update" : "restart",
63
+ requesters,
64
+ updatedAt: now,
65
+ };
66
+ }
67
+ else {
68
+ this.job = {
69
+ schemaVersion: 1,
70
+ jobId: this.idFactory(),
71
+ kind,
72
+ phase: "draining",
73
+ requestedAt: now,
74
+ updatedAt: now,
75
+ requesters: [requester],
76
+ };
77
+ }
78
+ this.stableSince = null;
79
+ try {
80
+ writeJob(this.filePath, this.job);
81
+ }
82
+ catch (error) {
83
+ this.job = previous;
84
+ throw error;
85
+ }
86
+ this.startPolling();
87
+ return structuredClone(this.job);
88
+ }
89
+ async cancel(notify = true) {
90
+ if (this.job?.phase !== "draining")
91
+ return false;
92
+ const requesters = this.job.requesters;
93
+ removeJob(this.filePath);
94
+ this.job = null;
95
+ this.snapshot = EMPTY_SNAPSHOT;
96
+ this.stableSince = null;
97
+ this.stopPolling();
98
+ if (notify)
99
+ await this.notifyAll(requesters, "已取消安全维护预约,ChatCCC 恢复接受新任务。");
100
+ return true;
101
+ }
102
+ async status() {
103
+ if (this.runtime && this.isAdmissionClosed())
104
+ this.snapshot = await this.collectSnapshot();
105
+ return {
106
+ job: this.job ? structuredClone(this.job) : null,
107
+ snapshot: structuredClone(this.snapshot),
108
+ waitingCount: snapshotCount(this.snapshot),
109
+ };
110
+ }
111
+ async tick() {
112
+ if (this.tickRunning || this.job?.phase !== "draining" || !this.runtime)
113
+ return;
114
+ this.tickRunning = true;
115
+ try {
116
+ this.snapshot = await this.collectSnapshot();
117
+ if (snapshotCount(this.snapshot) > 0) {
118
+ this.stableSince = null;
119
+ return;
120
+ }
121
+ const nowMs = this.now().getTime();
122
+ if (this.stableSince === null) {
123
+ this.stableSince = nowMs;
124
+ return;
125
+ }
126
+ if (nowMs - this.stableSince < this.stableIdleMs)
127
+ return;
128
+ await this.executeCurrentJob();
129
+ }
130
+ finally {
131
+ this.tickRunning = false;
132
+ }
133
+ }
134
+ async recoverAfterStartup(internalRestart) {
135
+ if (!this.job || !this.runtime)
136
+ return;
137
+ if (this.job.phase === "draining") {
138
+ this.startPolling();
139
+ await this.notifyAll(this.job.requesters, "ChatCCC 已恢复未完成的安全维护预约,继续等待现有任务结束。");
140
+ return;
141
+ }
142
+ if (this.job.phase !== "executing")
143
+ return;
144
+ if (internalRestart) {
145
+ const completed = { ...this.job, phase: "completed", updatedAt: this.now().toISOString() };
146
+ writeJob(this.filePath, completed);
147
+ this.job = completed;
148
+ await this.notifyAll(this.job.requesters, this.job.kind === "update" ? "ChatCCC 已安全更新并重新启动。" : "ChatCCC 已安全重新启动。");
149
+ return;
150
+ }
151
+ const message = "安全维护执行期间进程意外退出;为避免重启循环,未自动重试。";
152
+ const failed = {
153
+ ...this.job,
154
+ phase: "failed",
155
+ updatedAt: this.now().toISOString(),
156
+ lastError: message,
157
+ };
158
+ writeJob(this.filePath, failed);
159
+ this.job = failed;
160
+ await this.notifyAll(this.job.requesters, message);
161
+ }
162
+ async collectSnapshot() {
163
+ const external = await this.runtime.getSnapshot();
164
+ return {
165
+ activeSessionIds: [...external.activeSessionIds],
166
+ queuedSessionIds: [...external.queuedSessionIds],
167
+ activeEngineIds: [...external.activeEngineIds],
168
+ activeWorkLabels: [...external.activeWorkLabels, ...this.trackedWork.values()],
169
+ };
170
+ }
171
+ async executeCurrentJob() {
172
+ if (!this.job || !this.runtime)
173
+ return;
174
+ const executing = { ...this.job, phase: "executing", updatedAt: this.now().toISOString() };
175
+ writeJob(this.filePath, executing);
176
+ this.job = executing;
177
+ this.stopPolling();
178
+ const label = this.job.kind === "update" ? "更新并重启" : "重启";
179
+ await this.notifyAll(this.job.requesters, `现有任务已全部完成,开始安全${label}。`);
180
+ const started = await this.runtime.execute(this.job.kind).catch(async (error) => {
181
+ await this.markFailed(error instanceof Error ? error.message : String(error));
182
+ return false;
183
+ });
184
+ if (!started && this.job?.phase === "executing") {
185
+ await this.markFailed(`安全${label}未能启动,当前进程将继续提供服务。`);
186
+ }
187
+ }
188
+ async markFailed(message) {
189
+ if (!this.job)
190
+ return;
191
+ const failed = {
192
+ ...this.job,
193
+ phase: "failed",
194
+ updatedAt: this.now().toISOString(),
195
+ lastError: message,
196
+ };
197
+ writeJob(this.filePath, failed);
198
+ this.job = failed;
199
+ await this.notifyAll(this.job.requesters, message);
200
+ }
201
+ startPolling() {
202
+ if (!this.autoPoll || this.timer || this.job?.phase !== "draining")
203
+ return;
204
+ this.timer = setInterval(() => { void this.tick(); }, this.pollMs);
205
+ this.timer.unref?.();
206
+ }
207
+ stopPolling() {
208
+ if (!this.timer)
209
+ return;
210
+ clearInterval(this.timer);
211
+ this.timer = null;
212
+ }
213
+ async notifyAll(requesters, message) {
214
+ if (!this.runtime)
215
+ return;
216
+ await Promise.all(requesters.map((requester) => this.runtime.notify(requester, message).catch(() => { })));
217
+ }
218
+ }
219
+ function snapshotCount(snapshot) {
220
+ return snapshot.activeSessionIds.length
221
+ + snapshot.queuedSessionIds.length
222
+ + snapshot.activeEngineIds.length
223
+ + snapshot.activeWorkLabels.length;
224
+ }
225
+ function addRequester(requesters, requester) {
226
+ if (requesters.some((item) => item.platform === requester.platform && item.chatId === requester.chatId))
227
+ return requesters;
228
+ return [...requesters, requester];
229
+ }
230
+ function readJob(filePath) {
231
+ if (!existsSync(filePath))
232
+ return null;
233
+ try {
234
+ const value = JSON.parse(readFileSync(filePath, "utf8"));
235
+ if (value.schemaVersion !== 1 || !value.jobId || !Array.isArray(value.requesters))
236
+ return null;
237
+ if (!["restart", "update"].includes(value.kind) || !["draining", "executing", "completed", "failed"].includes(value.phase))
238
+ return null;
239
+ return value;
240
+ }
241
+ catch {
242
+ return null;
243
+ }
244
+ }
245
+ function writeJob(filePath, job) {
246
+ mkdirSync(dirname(filePath), { recursive: true });
247
+ const temporary = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
248
+ try {
249
+ writeFileSync(temporary, `${JSON.stringify(job, null, 2)}\n`, "utf8");
250
+ renameSync(temporary, filePath);
251
+ }
252
+ finally {
253
+ rmSync(temporary, { force: true });
254
+ }
255
+ }
256
+ function removeJob(filePath) {
257
+ rmSync(filePath, { force: true });
258
+ }
259
+ export let safeMaintenanceCoordinator = new SafeMaintenanceCoordinator();
260
+ export function _setSafeMaintenanceCoordinatorForTest(coordinator) {
261
+ safeMaintenanceCoordinator = coordinator;
262
+ }
263
+ export function _resetSafeMaintenanceCoordinatorForTest() {
264
+ safeMaintenanceCoordinator = new SafeMaintenanceCoordinator();
265
+ }
266
+ export function isSafeMaintenanceAdmissionClosed() {
267
+ return safeMaintenanceCoordinator.isAdmissionClosed();
268
+ }
269
+ export function beginSafeMaintenanceTrackedWork(label) {
270
+ return safeMaintenanceCoordinator.beginTrackedWork(label);
271
+ }
@@ -133,6 +133,18 @@ export function setUnifiedDisplayLoopHandle(h) {
133
133
  unifiedDisplayLoopHandle = h;
134
134
  }
135
135
  export const queuedMessages = new Map();
136
+ /** Includes prompt execution, finalization, auto-recovery reservations, and accepted queues. */
137
+ export function getSessionDrainSnapshot() {
138
+ const active = new Set([
139
+ ...activePrompts.keys(),
140
+ ...finalizingSessions,
141
+ ...autoRecoveryReservations,
142
+ ]);
143
+ return {
144
+ activeSessionIds: [...active].sort(),
145
+ queuedSessionIds: [...queuedMessages.keys()].sort(),
146
+ };
147
+ }
136
148
  export function enqueueMessage(sessionId, msg) {
137
149
  if (queuedMessages.has(sessionId))
138
150
  return false;
@@ -6,6 +6,7 @@ import { progressView } from "./progress/view.js";
6
6
  import { createAgentActivityTracker, formatAgentActivityTitle, updateAgentActivity, } from "./agent-activity.js";
7
7
  import { simplifyToolUse, simplifyToolResult } from "./simplify.js";
8
8
  import { logTrace } from "./trace.js";
9
+ import { appendExecutionTranscriptBlock, } from "./execution-transcript.js";
9
10
  import { createClaudeAdapter } from "./adapters/claude-adapter.js";
10
11
  import { createCursorAdapter } from "./adapters/cursor-adapter.js";
11
12
  import { createCodexAdapter } from "./adapters/codex-adapter.js";
@@ -698,6 +699,11 @@ export function pickFinalReply(state) {
698
699
  return state.finalCompleteText || state.finalText;
699
700
  }
700
701
  export function accumulateBlockContent(block, state, toolCallMap) {
702
+ if (state.transcript !== undefined || (block.type !== "agent_progress" && block.type !== "text_reset")) {
703
+ const transcriptState = { transcript: state.transcript ?? [] };
704
+ appendExecutionTranscriptBlock(block, transcriptState);
705
+ state.transcript = transcriptState.transcript;
706
+ }
701
707
  switch (block.type) {
702
708
  case "thinking":
703
709
  state.chunkCount++;
@@ -907,8 +913,10 @@ export async function initClaudeSession(tool, overrideCwd, chatId) {
907
913
  await addRecentDir(cwd);
908
914
  return { sessionId, cwd };
909
915
  }
910
- export async function resumeAndPrompt(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId) {
911
- return runAgentSession(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId);
916
+ export async function resumeAndPrompt(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId, initiatorOpenId) {
917
+ return runAgentSession(sessionId, userText, platform, chatId, msgTimestamp, tool, traceId, {
918
+ initiatorOpenId,
919
+ });
912
920
  }
913
921
  export async function runAgentSession(sessionId, userText, platform, _chatId, msgTimestamp, tool, traceId, options = {}) {
914
922
  const tid = traceId ?? "";
@@ -984,8 +992,10 @@ export async function runAgentSession(sessionId, userText, platform, _chatId, ms
984
992
  const skillVariables = {
985
993
  cwd,
986
994
  session_id: sessionId,
995
+ open_id: options.initiatorOpenId,
987
996
  im_skills_cache_dir: imSkillsCacheDir,
988
997
  delegate_task_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/delegate-task`,
998
+ set_cwd_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/set-cwd`,
989
999
  send_image_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/send-image`,
990
1000
  send_file_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/send-file`,
991
1001
  send_image_script: join(feishuSkillDir, "send-image.mjs"),
@@ -1145,6 +1155,7 @@ export async function runAgentSession(sessionId, userText, platform, _chatId, ms
1145
1155
  refreshBusySessionAvatar(sessionId, tool, platform).catch(() => { });
1146
1156
  const state = {
1147
1157
  accumulatedContent: "",
1158
+ transcript: [],
1148
1159
  finalText: "",
1149
1160
  finalCompleteText: "",
1150
1161
  chunkCount: 0,
@@ -1196,6 +1207,7 @@ export async function runAgentSession(sessionId, userText, platform, _chatId, ms
1196
1207
  status: "auto_ended",
1197
1208
  accumulatedContent: state.accumulatedContent,
1198
1209
  finalReply: pickFinalReply(state).trim(),
1210
+ transcript: state.transcript,
1199
1211
  activity: activityTracker.activity,
1200
1212
  chunkCount: state.chunkCount,
1201
1213
  turnCount: nextTurnCount,
@@ -1308,6 +1320,7 @@ export async function runAgentSession(sessionId, userText, platform, _chatId, ms
1308
1320
  status: "running",
1309
1321
  accumulatedContent: state.accumulatedContent,
1310
1322
  finalReply: pickFinalReply(state),
1323
+ transcript: state.transcript,
1311
1324
  activity: activityTracker.activity,
1312
1325
  chunkCount: state.chunkCount,
1313
1326
  turnCount: nextTurnCount,
@@ -1406,6 +1419,7 @@ export async function runAgentSession(sessionId, userText, platform, _chatId, ms
1406
1419
  status: finalStatus,
1407
1420
  accumulatedContent: state.accumulatedContent,
1408
1421
  finalReply: finalReplyToWrite,
1422
+ transcript: state.transcript,
1409
1423
  activity: activityTracker.activity,
1410
1424
  chunkCount: state.chunkCount,
1411
1425
  turnCount: nextTurnCount,
@@ -100,6 +100,7 @@ export function createEmptyStreamState(sessionId, cwd, tool, turnCount) {
100
100
  status: "running",
101
101
  accumulatedContent: "",
102
102
  finalReply: "",
103
+ transcript: [],
103
104
  activity: createAgentActivityTracker(now).activity,
104
105
  chunkCount: 0,
105
106
  turnCount,
@@ -17,6 +17,7 @@ import { AGENT_TEAM_PAGE_HTML } from "./agent-team/web/agent-team-page.js";
17
17
  export { AGENT_TEAM_PAGE_HTML } from "./agent-team/web/agent-team-page.js";
18
18
  import { buildWebUiUrl, createInternalRestartEnv, openWebUiInDefaultBrowser, } from "./startup-lifecycle.js";
19
19
  import { engineManager } from "./engines/engine-specs.js";
20
+ import { isSafeMaintenanceAdmissionClosed } from "./safe-maintenance.js";
20
21
  const PROJECT_ROOT = CHATCCC_PACKAGE_ROOT;
21
22
  const USER_DATA_DIR = join(homedir(), ".chatccc");
22
23
  const CONFIG_FILE = join(USER_DATA_DIR, "config.json");
@@ -656,6 +657,10 @@ async function handleEngineStatus(engineId, res) {
656
657
  }
657
658
  }
658
659
  async function handleEngineInstall(engineId, res) {
660
+ if (isSafeMaintenanceAdmissionClosed()) {
661
+ jsonReply(res, 409, { ok: false, error: "ChatCCC 正在等待安全维护,暂不接受新的依赖安装任务。" });
662
+ return;
663
+ }
659
664
  try {
660
665
  jsonReply(res, 202, { ok: true, job: await engineManager.startInstall(engineId) });
661
666
  }
@@ -1,12 +1,26 @@
1
1
  ---
2
2
  name: feishu-skill
3
- description: Feishu IM local skills for sending and receiving images, files, and videos.
3
+ description: Feishu IM local skills for sending images, files, videos, and for creating new sessions or switching working directories.
4
4
  ---
5
5
 
6
6
  Current working directory: {{cwd}}
7
+ Your session id: {{session_id}}
8
+ Your Feishu open_id: {{open_id}}
7
9
 
8
10
  Use local endpoints instead of calling Feishu Open Platform directly.
9
11
 
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.
12
+ - **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`
13
+ - **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`
14
+ - **Create a new session (新建会话)**: POST `{{delegate_task_url}}` with `{"tool":"claude|cursor|codex|ccc|dsh","cwd":"<absolute path>","open_id":"{{open_id}}","prompt":"<optional first task>"}`. This creates a new Feishu group and session, and only adds you (the requester). `tool` and `prompt` are optional; omit `prompt` to just create the session without a first task.
15
+ - **Set default working directory (cd / 切换目录)**: POST `{{set_cwd_url}}` with `{"session_id":"{{session_id}}","dir":"<absolute path>"}`. This sets the default directory for future new sessions only; it does not change the current session.
16
+
17
+ How to map user requests to these endpoints:
18
+
19
+ - "新建会话 / 开个新会话 / 换个新会话" → create a new session (no prompt). Use `cwd` = your current working directory ({{cwd}}) unless the user names a directory.
20
+ - "在 <目录> 新建会话(做 <任务>)" → create a new session with `cwd` = that directory, and set `prompt` to the task if one was given.
21
+ - "cd 到 <目录> / 切换到 <目录> / 去 <目录> 干活" → judge intent:
22
+ - if the user wants to start working there now (a fresh conversation in that directory) → create a new session with `cwd` = that directory.
23
+ - if the user only wants to change the default directory for future sessions → call set-cwd.
24
+ - when ambiguous, prefer creating a new session (the more common intent for "切换到").
25
+ - Directory names may be fuzzy or relative; resolve them to an absolute local path (using your file tools) before calling either endpoint.
26
+ - `open_id` must always be passed as exactly {{open_id}}; do not invent it.
package/package.json CHANGED
@@ -1,76 +1,76 @@
1
- {
2
- "name": "chatccc",
3
- "version": "0.2.267",
4
- "description": "Feishu bot bridge for Claude Code",
5
- "license": "Apache-2.0",
6
- "type": "module",
7
- "main": "./dist/src/index.js",
8
- "bin": {
9
- "chatccc": "bin/chatccc.mjs",
10
- "cccagent": "bin/cccagent.mjs"
11
- },
12
- "files": [
13
- "dist/",
14
- "deepccc-agent/os-prompts/",
15
- "bin/",
16
- "scripts/postinstall-sharp-check.mjs",
17
- "demo/ilink_echo_probe.ts",
18
- "agent-prompts/",
19
- "im-skills/",
20
- ".agents/skills/create-chatccc-feishu-app/",
21
- ".claude/skills/create-chatccc-feishu-app/",
22
- ".cursor/skills/create-chatccc-feishu-app/",
23
- "images/img_readme_*.jpg",
24
- "images/img_readme_*.png",
25
- "images/avatars/status_*.png",
26
- "images/avatars/badges/",
27
- "images/avatars/combinations/",
28
- "package.json",
29
- "README.md",
30
- "config.sample.json"
31
- ],
32
- "scripts": {
33
- "build": "node scripts/build.mjs",
34
- "prepack": "npm run build",
35
- "dev": "tsx src/index.ts",
36
- "chatccc": "tsx src/index.ts",
37
- "start": "tsx src/index.ts",
38
- "demo:bot-test": "tsx demo/bot_test.ts",
39
- "demo:bot-test:local": "tsx demo/bot_test.ts --local",
40
- "demo:create-group": "tsx src/index.ts",
41
- "demo:create-group:local": "tsx src/index.ts --local",
42
- "demo:permission-check": "tsx demo/permission_check.ts",
43
- "demo:claude-hi": "tsx demo/claude_say_hi.ts",
44
- "demo:codex-hi": "tsx demo/codex_say_hi.ts",
45
- "demo:codex-app-server-approval": "tsx demo/codex-app-server-approval/approval_demo.ts",
46
- "demo:ilink-echo": "tsx demo/ilink_echo_probe.ts",
47
- "claude-proxy": "tsx src/litellm-proxy.ts",
48
- "test": "vitest run",
49
- "test:deepccc": "vitest run --root deepccc-agent",
50
- "test:watch": "vitest",
51
- "postinstall": "node scripts/postinstall-sharp-check.mjs"
52
- },
53
- "dependencies": {
54
- "@ai-sdk/anthropic": "^3.0.105",
55
- "@ai-sdk/openai-compatible": "^2.0.47",
56
- "@larksuiteoapi/node-sdk": "^1.59.0",
57
- "@openilink/openilink-sdk-node": "^0.6.0",
58
- "@vscode/ripgrep": "^1.18.0",
59
- "ai": "^6.0.184",
60
- "nodemailer": "^8.0.7",
61
- "qrcode-terminal": "^0.12.0",
62
- "sharp": "^0.34.5",
63
- "ws": "^8.18.0"
64
- },
65
- "devDependencies": {
66
- "@types/node": "^20.0.0",
67
- "@types/qrcode-terminal": "^0.12.2",
68
- "@types/ws": "^8.18.1",
69
- "tsx": "^4.0.0",
70
- "typescript": "^5.0.0",
71
- "vitest": "^3.2.4"
72
- },
73
- "engines": {
74
- "node": ">=20"
75
- }
76
- }
1
+ {
2
+ "name": "chatccc",
3
+ "version": "0.2.269",
4
+ "description": "Feishu bot bridge for Claude Code",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "./dist/src/index.js",
8
+ "bin": {
9
+ "chatccc": "bin/chatccc.mjs",
10
+ "cccagent": "bin/cccagent.mjs"
11
+ },
12
+ "files": [
13
+ "dist/",
14
+ "deepccc-agent/os-prompts/",
15
+ "bin/",
16
+ "scripts/postinstall-sharp-check.mjs",
17
+ "demo/ilink_echo_probe.ts",
18
+ "agent-prompts/",
19
+ "im-skills/",
20
+ ".agents/skills/create-chatccc-feishu-app/",
21
+ ".claude/skills/create-chatccc-feishu-app/",
22
+ ".cursor/skills/create-chatccc-feishu-app/",
23
+ "images/img_readme_*.jpg",
24
+ "images/img_readme_*.png",
25
+ "images/avatars/status_*.png",
26
+ "images/avatars/badges/",
27
+ "images/avatars/combinations/",
28
+ "package.json",
29
+ "README.md",
30
+ "config.sample.json"
31
+ ],
32
+ "scripts": {
33
+ "build": "node scripts/build.mjs",
34
+ "prepack": "npm run build",
35
+ "dev": "tsx src/index.ts",
36
+ "chatccc": "tsx src/index.ts",
37
+ "start": "tsx src/index.ts",
38
+ "demo:bot-test": "tsx demo/bot_test.ts",
39
+ "demo:bot-test:local": "tsx demo/bot_test.ts --local",
40
+ "demo:create-group": "tsx src/index.ts",
41
+ "demo:create-group:local": "tsx src/index.ts --local",
42
+ "demo:permission-check": "tsx demo/permission_check.ts",
43
+ "demo:claude-hi": "tsx demo/claude_say_hi.ts",
44
+ "demo:codex-hi": "tsx demo/codex_say_hi.ts",
45
+ "demo:codex-app-server-approval": "tsx demo/codex-app-server-approval/approval_demo.ts",
46
+ "demo:ilink-echo": "tsx demo/ilink_echo_probe.ts",
47
+ "claude-proxy": "tsx src/litellm-proxy.ts",
48
+ "test": "vitest run",
49
+ "test:deepccc": "vitest run --root deepccc-agent",
50
+ "test:watch": "vitest",
51
+ "postinstall": "node scripts/postinstall-sharp-check.mjs"
52
+ },
53
+ "dependencies": {
54
+ "@ai-sdk/anthropic": "^3.0.105",
55
+ "@ai-sdk/openai-compatible": "^2.0.47",
56
+ "@larksuiteoapi/node-sdk": "^1.59.0",
57
+ "@openilink/openilink-sdk-node": "^0.6.0",
58
+ "@vscode/ripgrep": "^1.18.0",
59
+ "ai": "^6.0.184",
60
+ "nodemailer": "^8.0.7",
61
+ "qrcode-terminal": "^0.12.0",
62
+ "sharp": "^0.34.5",
63
+ "ws": "^8.18.0"
64
+ },
65
+ "devDependencies": {
66
+ "@types/node": "^20.0.0",
67
+ "@types/qrcode-terminal": "^0.12.2",
68
+ "@types/ws": "^8.18.1",
69
+ "tsx": "^4.0.0",
70
+ "typescript": "^5.0.0",
71
+ "vitest": "^3.2.4"
72
+ },
73
+ "engines": {
74
+ "node": ">=20"
75
+ }
76
+ }