🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

cc-im

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cc-im - npm Package Compare versions

Comparing version
1.1.0
to
1.1.1
+58
dist/shared/update-check.js
import { get } from 'node:https';
import { createLogger } from '../logger.js';
const log = createLogger('UpdateCheck');
const NPM_REGISTRY_URL = 'https://registry.npmjs.org/cc-im/latest';
const FETCH_TIMEOUT_MS = 5000;
function fetchLatestVersion() {
return new Promise((resolve) => {
const req = get(NPM_REGISTRY_URL, { timeout: FETCH_TIMEOUT_MS }, (res) => {
if (res.statusCode !== 200) {
res.resume();
resolve(null);
return;
}
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
try {
const { version } = JSON.parse(data);
resolve(typeof version === 'string' ? version : null);
}
catch {
resolve(null);
}
});
});
req.on('error', () => resolve(null));
req.on('timeout', () => { req.destroy(); resolve(null); });
});
}
/**
* 比较语义化版本,返回 true 表示 latest 比 current 更新
*/
function isNewer(current, latest) {
const parse = (v) => v.replace(/^v/, '').split('.').map(Number);
const c = parse(current);
const l = parse(latest);
for (let i = 0; i < 3; i++) {
if ((l[i] ?? 0) > (c[i] ?? 0))
return true;
if ((l[i] ?? 0) < (c[i] ?? 0))
return false;
}
return false;
}
/**
* 异步检查是否有新版本,有则打印提示日志。不阻塞启动流程。
*/
export async function checkForUpdate(currentVersion) {
try {
const latest = await fetchLatestVersion();
if (latest && isNewer(currentVersion, latest)) {
log.info(`发现新版本 v${latest}(当前 v${currentVersion}),请运行: npx cc-im@latest 或 npm i -g cc-im@latest`);
}
}
catch {
// 静默忽略,不影响正常启动
}
}
+1
-0

@@ -216,2 +216,3 @@ import { spawn } from 'node:child_process';

}
rl.close();
if (!child.killed) {

@@ -218,0 +219,0 @@ child.kill('SIGTERM');

+1
-2
import { MAX_STREAMING_CONTENT_LENGTH, MAX_CARD_CONTENT_LENGTH } from '../constants.js';
import { splitLongContent as sharedSplitLongContent, truncateText } from '../shared/utils.js';
import { buildInputSummary } from '../shared/utils.js';
import { splitLongContent as sharedSplitLongContent, truncateText, buildInputSummary } from '../shared/utils.js';
const HEADER_TEMPLATES = {

@@ -5,0 +4,0 @@ processing: 'blue',

import { getClient } from './client.js';
import { createLogger } from '../logger.js';
import { safeStringify } from '../shared/utils.js';
import { withRetry } from '../shared/retry.js';
import { withRetry, NonRetryableError } from '../shared/retry.js';
const log = createLogger('CardKit');

@@ -56,2 +56,6 @@ const sessions = new Map();

return withRetry(async () => {
// 限频错误不再重试,避免重试风暴
const s = sessions.get(cardId);
if (s?.completed)
return;
const client = getClient();

@@ -66,6 +70,10 @@ const res = await client.cardkit.v1.card.settings({

if (res?.code && res.code !== 0) {
// 限频错误不可重试,直接穿透
if (res.code === 200400) {
log.warn(`enableStreaming rate limited: ${res.msg}`);
throw new NonRetryableError(`enableStreaming rate limited: code=${res.code}, msg=${res.msg}`);
}
log.error(`enableStreaming failed: code=${res.code}, msg=${res.msg}`);
throw new Error(`enableStreaming error: code=${res.code}, msg=${res.msg}`);
}
const s = sessions.get(cardId);
if (s)

@@ -111,2 +119,4 @@ s.streamingEnabled = true;

return; // 更新过于频繁,等下次节流重试
if (code === 200740)
return; // 查询结果为空(卡片已失效),静默忽略
// 200850 / 300309: 流式超时或已关闭 → 重新启用后重试一次

@@ -120,2 +130,6 @@ if (code === 200850 || code === 300309) {

await enableStreaming(cardId);
// 启用后再次检查是否已完成
const s2 = sessions.get(cardId);
if (!s2 || s2.completed)
return;
const retryRes = await call(nextSeq(cardId));

@@ -201,2 +215,5 @@ if (retryRes?.code && retryRes.code !== 0) {

return;
// 标记已完成,防止并发调用
s.completed = true;
s.streamingEnabled = false;
const client = getClient();

@@ -215,3 +232,2 @@ try {

else {
s.streamingEnabled = false;
log.debug(`Streaming disabled for card ${cardId}`);

@@ -218,0 +234,0 @@ }

@@ -225,5 +225,5 @@ import { join } from 'node:path';

const eventType = data?.header?.event_type || data?.type || 'unknown';
log.info(`Received event: ${eventType}`, safeStringify(data).slice(0, 500));
log.info(`Received event: ${eventType}`);
if (eventType.includes('card') || eventType.includes('action')) {
log.info('Card/Action event data:', safeStringify(data, 2));
log.debug('Card/Action event data:', safeStringify(data, 2));
}

@@ -230,0 +230,0 @@ },

@@ -202,5 +202,5 @@ import { createServer } from 'node:http';

log.info(`Permission server listening on 127.0.0.1:${port}`);
resolve({ close: () => server.close() });
resolve({ close: () => new Promise((r) => server.close(() => r())) });
});
});
}

@@ -13,4 +13,8 @@ import { loadConfig } from './config.js';

import { cleanOldImages } from './shared/utils.js';
import { checkForUpdate } from './shared/update-check.js';
import { initLogger, createLogger, closeLogger } from './logger.js';
import { execFileSync } from 'node:child_process';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const { version: APP_VERSION } = require('../package.json');
const log = createLogger('Main');

@@ -114,3 +118,3 @@ function getClaudeVersion(cliPath) {

const startupMsg = [
'🟢 cc-im 服务已启动',
`🟢 cc-im v${APP_VERSION} 服务已启动`,
'',

@@ -125,2 +129,3 @@ `平台: ${activeBots.join(' + ')}`,

sendLifecycleNotification(activeBots, startupMsg).catch(() => { });
checkForUpdate(APP_VERSION).catch(() => { });
const imageCleanupTimer = setInterval(() => {

@@ -138,2 +143,3 @@ cleanOldImages().then((n) => { if (n > 0)

log.info('Shutting down...');
clearInterval(imageCleanupTimer);
// 发送关闭通知

@@ -140,0 +146,0 @@ const uptimeSec = Math.floor((Date.now() - startedAt) / 1000);

import { createLogger } from '../logger.js';
const log = createLogger('Retry');
/** 抛出此错误时 withRetry 不再重试,直接穿透 */
export class NonRetryableError extends Error {
constructor(message) {
super(message);
this.name = 'NonRetryableError';
}
}
export async function withRetry(fn, opts) {

@@ -12,3 +19,3 @@ const maxRetries = opts?.maxRetries ?? 3;

catch (err) {
if (attempt >= maxRetries)
if (err instanceof NonRetryableError || attempt >= maxRetries)
throw err;

@@ -15,0 +22,0 @@ const delay = Math.min(baseDelay * 2 ** attempt + Math.random() * 200, maxDelay);

/**
* 共享工具函数
*/
import { readdir, stat, unlink } from 'node:fs/promises';
import { readdir, stat, unlink, mkdir } from 'node:fs/promises';
import { join } from 'node:path';
import { IMAGE_DIR } from '../constants.js';
import { createLogger } from '../logger.js';
import { mkdir } from 'node:fs/promises';
const log = createLogger('Utils');

@@ -10,0 +9,0 @@ /**

{
"name": "cc-im",
"version": "1.1.0",
"version": "1.1.1",
"description": "Multi-platform bot bridge (Feishu & Telegram) for Claude Code CLI",

@@ -38,11 +38,11 @@ "repository": {

"dependencies": {
"@larksuiteoapi/node-sdk": "^1.45.0",
"dotenv": "^16.4.7",
"@larksuiteoapi/node-sdk": "^1.59.0",
"dotenv": "^17.3.1",
"telegraf": "^4.16.3"
},
"devDependencies": {
"@types/node": "^22.12.0",
"@types/node": "^25.3.0",
"@vitest/coverage-v8": "^4.0.18",
"tsx": "^4.19.0",
"typescript": "^5.7.0",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
"vitest": "^4.0.18"

@@ -49,0 +49,0 @@ },

@@ -26,2 +26,3 @@ # cc-im

- **守护进程模式**:支持 `-d` 后台运行和 `stop` 停止
- **版本更新检查**:启动时自动检查 npm 最新版本,有更新时提示
- **日志等级配置**:支持 DEBUG/INFO/WARN/ERROR 四级日志

@@ -41,3 +42,3 @@

export TELEGRAM_BOT_TOKEN=your_bot_token
npx cc-im
npx cc-im@latest
```

@@ -55,3 +56,3 @@

export TELEGRAM_BOT_TOKEN=your_bot_token
npx cc-im
npx cc-im@latest

@@ -83,3 +84,3 @@ # 方式二:从源码运行

export FEISHU_APP_SECRET=your_app_secret
npx cc-im
npx cc-im@latest
```

@@ -277,2 +278,3 @@

│ ├── types.ts # 共享类型定义
│ ├── update-check.ts # 启动时版本更新检查
│ └── utils.ts # 共享工具函数

@@ -279,0 +281,0 @@ ├── session/