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

cc-channel-patch

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cc-channel-patch - npm Package Compare versions

Comparing version
0.3.0
to
0.4.0
+259
-118
index.mjs

@@ -5,4 +5,5 @@ #!/usr/bin/env node

*
* 绕过 Anthropic 的 tengu_harbor 云控 + accessToken 认证检查,
* 使代理认证模式下也能使用 --dangerously-load-development-channels。
* 双模式补丁:
* - 二进制模式(exe):按固定字符串搜索替换
* - AST 模式(cli.js / npm 安装):用 acorn 解析 JS AST,按语义特征定位替换
*

@@ -18,22 +19,7 @@ * 用法:npx cc-channel-patch # 修补

// ─── 补丁定义 ──────────────────────────────────────────
// [原始字符串, 替换字符串, 说明]
// 长度必须完全一致(二进制原地替换)
const PATCHES = [
[
'function PaH(){return lA("tengu_harbor",!1)}',
'function PaH(){return !0 }',
'Channels feature flag (tengu_harbor)',
],
[
'if(!yf()?.accessToken)',
'if( false )',
'Channel gate auth check',
],
[
'noAuth:!yf()?.accessToken',
'noAuth: false ',
'UI noAuth display check',
],
// ─── 二进制模式补丁定义(exe 专用)────────────────────────
const BINARY_PATCHES = [
['function PaH(){return lA("tengu_harbor",!1)}', 'function PaH(){return !0 }', 'Channels feature flag (tengu_harbor)'],
['if(!yf()?.accessToken)', 'if( false )', 'Channel gate auth check'],
['noAuth:!yf()?.accessToken', 'noAuth: false ', 'UI noAuth display check'],
];

@@ -44,40 +30,26 @@

function findClaude() {
// 1. which/where 查找(最可靠,覆盖所有自定义安装路径)
try {
const cmd = process.platform === 'win32' ? 'where claude 2>nul' : 'which claude 2>/dev/null';
const p = execSync(cmd, {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
}).trim().split('\n')[0].trim();
const p = execSync(cmd, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim().split('\n')[0].trim();
if (p && fs.existsSync(p)) return p;
} catch { /* ignore */ }
// 2. 常见安装路径(覆盖各种安装方式)
const home = homedir();
const candidates = [
// Claude Code native installer(默认)
path.join(home, '.local', 'bin', 'claude.exe'),
path.join(home, '.local', 'bin', 'claude'),
// npm 全局安装
path.join(home, 'AppData', 'Roaming', 'npm', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
path.join(home, 'AppData', 'Roaming', 'npm', 'claude.cmd'),
// scoop / chocolatey / winget 等 Windows 包管理器
path.join(home, 'scoop', 'shims', 'claude.exe'),
path.join('C:', 'ProgramData', 'chocolatey', 'bin', 'claude.exe'),
// Program Files
path.join('C:', 'Program Files', 'Claude Code', 'claude.exe'),
path.join('C:', 'Program Files (x86)', 'Claude Code', 'claude.exe'),
// AppData 常见路径
path.join(home, 'AppData', 'Local', 'Programs', 'claude-code', 'claude.exe'),
path.join(home, 'AppData', 'Local', 'claude-code', 'claude.exe'),
path.join(home, 'AppData', 'Local', 'AnthropicClaude', 'claude.exe'),
// macOS
'/usr/local/bin/claude',
'/opt/homebrew/bin/claude',
path.join(home, '.nvm', 'versions', 'node'), // 标记,下面特殊处理
// Linux
'/usr/bin/claude',
'/snap/bin/claude',
];
for (const c of candidates) {

@@ -87,22 +59,13 @@ if (fs.existsSync(c)) return c;

// 3. npm global prefix 动态查找(覆盖自定义 npm prefix)
try {
const prefix = execSync('npm config get prefix', {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
}).trim();
const prefix = execSync('npm config get prefix', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
if (prefix) {
const npmCandidates = [
path.join(prefix, 'claude.cmd'),
path.join(prefix, 'claude'),
for (const c of [
path.join(prefix, 'claude.cmd'), path.join(prefix, 'claude'),
path.join(prefix, 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
path.join(prefix, 'bin', 'claude'),
];
for (const c of npmCandidates) {
if (fs.existsSync(c)) return c;
}
]) { if (fs.existsSync(c)) return c; }
}
} catch { /* ignore */ }
// 4. PATH 环境变量逐目录搜索(兜底)
const pathDirs = (process.env.PATH || '').split(process.platform === 'win32' ? ';' : ':');

@@ -116,3 +79,2 @@ const exeNames = process.platform === 'win32' ? ['claude.exe', 'claude.cmd'] : ['claude'];

}
return null;

@@ -123,7 +85,2 @@ }

/**
* findClaude() 可能返回 .cmd wrapper 或符号链接。
* 此函数解析到真正包含代码的文件(exe 二进制或 cli.js)。
* 对于 npm 全局安装的 CC,.cmd 旁边有 node_modules/@anthropic-ai/claude-code/cli.js
*/
function resolvePatchTarget(claudePath) {

@@ -133,3 +90,2 @@ const ext = path.extname(claudePath).toLowerCase();

// 如果已经是 .exe 或大文件(>1MB),直接 patch
if (ext === '.exe') return claudePath;

@@ -139,12 +95,8 @@ const stat = fs.statSync(claudePath);

// .cmd / 小文件 → 是 npm wrapper,查找真正的 cli.js
// 策略 1:同目录下 node_modules/@anthropic-ai/claude-code/cli.js
const cliJs = path.join(dir, 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js');
if (fs.existsSync(cliJs)) return cliJs;
// 策略 2:读取 .cmd 内容,提取实际路径
if (ext === '.cmd') {
try {
const content = fs.readFileSync(claudePath, 'utf-8');
// npm .cmd 格式:@IF EXIST "%~dp0\node.exe" (...) ELSE (... "%~dp0\node_modules\@anthropic-ai\claude-code\cli.js" ...)
const match = content.match(/node_modules[\\/]@anthropic-ai[\\/]claude-code[\\/]cli\.js/);

@@ -158,39 +110,36 @@ if (match) {

// 策略 3:如果是 symlink,resolve 到真实路径
try {
const realPath = fs.realpathSync(claudePath);
if (realPath !== claudePath) {
return resolvePatchTarget(realPath);
}
if (realPath !== claudePath) return resolvePatchTarget(realPath);
} catch { /* ignore */ }
// 兜底:原路径
return claudePath;
}
// ─── patch ──────────────────────────────────────────────
// ─── 判断文件类型 ──────────────────────────────────────────
function patch() {
console.log('\n cc-channel-patch — 启用 Claude Code Channels\n');
function isBinaryFile(filePath) {
const ext = path.extname(filePath).toLowerCase();
if (ext === '.exe') return true;
// 检查文件头是否是 PE/ELF/Mach-O 二进制
const header = Buffer.alloc(4);
const fd = fs.openSync(filePath, 'r');
fs.readSync(fd, header, 0, 4, 0);
fs.closeSync(fd);
// PE: MZ, ELF: \x7fELF, Mach-O: \xfe\xed\xfa\xce / \xcf\xfa\xed\xfe
if (header[0] === 0x4D && header[1] === 0x5A) return true; // MZ (PE)
if (header[0] === 0x7F && header[1] === 0x45) return true; // ELF
if (header[0] === 0xFE && header[1] === 0xED) return true; // Mach-O
if (header[0] === 0xCF && header[1] === 0xFA) return true; // Mach-O 64
return false;
}
const claudePath = findClaude();
if (!claudePath) {
console.error(' 找不到 Claude Code 可执行文件。');
console.error(' 请确认已安装 Claude Code 并在 PATH 中。\n');
process.exit(1);
}
// ─── 二进制模式 patch ──────────────────────────────────────
const exePath = resolvePatchTarget(claudePath);
console.log(` 查找: ${claudePath}`);
if (exePath !== claudePath) {
console.log(` 解析: ${exePath}`);
}
console.log(` 目标: ${exePath}`);
function patchBinary(exePath) {
console.log(' 模式: 二进制字符串搜索\n');
const buf = fs.readFileSync(exePath);
let patched = 0;
let skipped = 0;
let missing = 0;
let patched = 0, skipped = 0;
for (const [original, replacement, desc] of PATCHES) {
for (const [original, replacement, desc] of BINARY_PATCHES) {
if (original.length !== replacement.length) {

@@ -200,9 +149,6 @@ console.error(` 致命错误: "${desc}" 补丁长度不匹配`);

}
const origBuf = Buffer.from(original);
const patchBuf = Buffer.from(replacement);
// 搜索并替换所有出现
let pos = 0;
let count = 0;
let pos = 0, count = 0;
while (true) {

@@ -216,3 +162,2 @@ const idx = buf.indexOf(origBuf, pos);

// 检查是否已 patch
let alreadyCount = 0;

@@ -227,20 +172,183 @@ pos = 0;

if (count > 0) {
console.log(` ✅ ${desc} — ${count} 处已修补`);
patched += count;
} else if (alreadyCount > 0) {
console.log(` ⏭️ ${desc} — 已修补 (${alreadyCount} 处)`);
skipped += alreadyCount;
} else {
console.log(` ⚠️ ${desc} — 未找到 (CC 版本可能不兼容)`);
missing++;
if (count > 0) { console.log(` ✅ ${desc} — ${count} 处已修补`); patched += count; }
else if (alreadyCount > 0) { console.log(` ⏭️ ${desc} — 已修补 (${alreadyCount} 处)`); skipped += alreadyCount; }
else { console.log(` ⚠️ ${desc} — 未找到`); }
}
if (patched === 0 && skipped > 0) { console.log('\n 所有补丁已生效,无需操作。\n'); return; }
if (patched === 0) { console.error('\n 未找到可修补位置。\n'); process.exit(1); }
writeResult(exePath, buf);
}
// ─── AST 模式 patch(npm cli.js)──────────────────────────
async function patchAst(exePath) {
console.log(' 模式: AST 语义分析\n');
// 动态下载 acorn(不加依赖)
const acornPath = path.join(homedir(), '.cache', 'cc-channel-patch', 'acorn.js');
if (!fs.existsSync(acornPath)) {
console.log(' 下载 acorn 解析器...');
fs.mkdirSync(path.dirname(acornPath), { recursive: true });
try {
const resp = await fetch('https://unpkg.com/acorn@8.14.0/dist/acorn.js');
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
fs.writeFileSync(acornPath, await resp.text());
} catch (e) {
console.error(` 下载 acorn 失败: ${e.message}`);
console.error(' 可手动下载 https://unpkg.com/acorn@8.14.0/dist/acorn.js 到 ' + acornPath);
process.exit(1);
}
}
if (patched === 0 && skipped > 0) {
console.log('\n 所有补丁已生效,无需操作。\n');
return;
// 加载 acorn
const { createRequire } = await import('node:module');
const require = createRequire(import.meta.url);
const acorn = require(acornPath);
// 读取 cli.js
let code = fs.readFileSync(exePath, 'utf-8');
let shebang = '';
if (code.startsWith('#!')) {
const idx = code.indexOf('\n');
shebang = code.slice(0, idx + 1);
code = code.slice(idx + 1);
}
if (patched === 0) {
// 解析 AST
let ast;
try {
ast = acorn.parse(code, { ecmaVersion: 2022, sourceType: 'module' });
} catch (e) {
console.error(` AST 解析失败: ${e.message}`);
process.exit(1);
}
const src = (node) => code.slice(node.start, node.end);
function findNodes(node, predicate, results = []) {
if (!node || typeof node !== 'object') return results;
if (predicate(node)) results.push(node);
for (const key in node) {
if (node[key] && typeof node[key] === 'object') {
if (Array.isArray(node[key])) node[key].forEach(child => findNodes(child, predicate, results));
else findNodes(node[key], predicate, results);
}
}
return results;
}
const replacements = [];
let patchCount = 0;
// ── Patch 1: tengu_harbor feature flag ──
const harborCalls = findNodes(ast, n =>
n.type === 'CallExpression' && n.arguments?.length === 2 &&
n.arguments[0].type === 'Literal' && n.arguments[0].value === 'tengu_harbor' &&
n.arguments[0].value !== 'tengu_harbor_ledger'
);
let harborPatched = false;
let harborCalleeName = '';
for (const call of harborCalls) {
harborCalleeName = src(call.callee);
const arg = call.arguments[1];
if (arg.type === 'UnaryExpression' && arg.operator === '!' && arg.argument.type === 'Literal' && arg.argument.value === 1) {
replacements.push({ start: arg.start, end: arg.end, replacement: '!0', name: 'harborFlag' });
patchCount++;
console.log(` ✅ tengu_harbor flag — ${harborCalleeName}("tengu_harbor", !1) → !0`);
break;
}
if (arg.type === 'UnaryExpression' && arg.operator === '!' && arg.argument.type === 'Literal' && arg.argument.value === 0) {
harborPatched = true;
console.log(' ⏭️ tengu_harbor flag — 已修补');
break;
}
}
// ── Patch 2: channel decision function(qMq)──
const markerLiterals = findNodes(ast, n =>
n.type === 'Literal' && n.value === 'channels feature is not currently available'
);
let qMqPatched = false;
if (markerLiterals.length > 0) {
const markerPos = markerLiterals[0].start;
const enclosingFuncs = findNodes(ast, n =>
(n.type === 'FunctionDeclaration' || n.type === 'FunctionExpression') &&
n.start < markerPos && n.end > markerPos
);
if (enclosingFuncs.length > 0) {
const targetFunc = enclosingFuncs.sort((a, b) => (a.end - a.start) - (b.end - b.start))[0];
const funcName = targetFunc.id?.name || '(anonymous)';
const bodyStatements = targetFunc.body.body;
if (bodyStatements?.length > 0 && bodyStatements[0].type === 'IfStatement') {
const firstStmt = bodyStatements[0];
const firstSrc = src(firstStmt);
if (firstSrc.includes('claude/channel')) {
// 保留第一个 capability check,删除其余所有 check,直接 return register
const capCheckSrc = src(firstStmt);
const newBody = '{' + capCheckSrc + 'return{action:"register"}}';
replacements.push({ start: targetFunc.body.start, end: targetFunc.body.end, replacement: newBody, name: 'qMq' });
patchCount++;
console.log(` ✅ Channel decision ${funcName}() — 绕过 check 2-7,保留 capability check`);
}
}
}
} else {
// 检查是否已 patch
if (code.includes('claude/channel capability') && !code.includes('channels feature is not currently available')) {
qMqPatched = true;
console.log(' ⏭️ Channel decision — 已修补');
}
}
// ── Patch 3: UI notice function(xl1/Kq_)──
const policyProps = findNodes(ast, n =>
n.type === 'Property' && n.key?.type === 'Identifier' && n.key.name === 'policyBlocked' &&
n.value && src(n.value).includes('channelsEnabled')
);
let noticePatched = false;
if (policyProps.length > 0) {
const propPos = policyProps[0].start;
const noticeFuncs = findNodes(ast, n =>
(n.type === 'FunctionDeclaration' || n.type === 'FunctionExpression') &&
n.start < propPos && n.end > propPos
);
if (noticeFuncs.length > 0) {
const noticeFunc = noticeFuncs.sort((a, b) => (a.end - a.start) - (b.end - b.start))[0];
const nfName = noticeFunc.id?.name || '(anonymous)';
// 提取 getAllowedChannels 和 formatter
const firstCalls = findNodes(noticeFunc.body.body[0], n =>
n.type === 'CallExpression' && n.callee?.type === 'Identifier'
);
const getAllowedChannels = firstCalls.length > 0 ? src(firstCalls[0]) : '$N()';
const mapCalls = findNodes(noticeFunc, n =>
n.type === 'CallExpression' && n.callee?.type === 'MemberExpression' && n.callee.property?.name === 'map'
);
let formatter = 'naH';
if (mapCalls.length > 0 && mapCalls[0].arguments[0]) formatter = src(mapCalls[0].arguments[0]);
const newBody = '{let A=' + getAllowedChannels + ';let q=A.length>0?A.map(' + formatter + ').join(", "):"";return{channels:A,disabled:!1,noAuth:!1,policyBlocked:!1,list:q}}';
replacements.push({ start: noticeFunc.body.start, end: noticeFunc.body.end, replacement: newBody, name: 'notice' });
patchCount++;
console.log(` ✅ UI notice ${nfName}() — disabled/noAuth/policyBlocked 全部置 false`);
}
} else {
if (code.includes('policyBlocked') && !code.includes('channelsEnabled!==!0')) {
noticePatched = true;
console.log(' ⏭️ UI notice — 已修补');
}
}
if (patchCount === 0) {
if (harborPatched || qMqPatched || noticePatched) {
console.log('\n 所有补丁已生效,无需操作。\n');
return;
}
console.error('\n 未找到可修补位置。Claude Code 版本可能不兼容。\n');

@@ -250,3 +358,21 @@ process.exit(1);

// 备份
// 从后往前替换(保持位置不变)
replacements.sort((a, b) => b.start - a.start);
let newCode = code;
for (const r of replacements) {
newCode = newCode.slice(0, r.start) + r.replacement + newCode.slice(r.end);
}
// 验证
if (!newCode.includes('claude/channel')) {
console.error(' 验证失败: capability check 未保留');
process.exit(1);
}
writeResult(exePath, Buffer.from(shebang + newCode, 'utf-8'));
}
// ─── 写入结果 ──────────────────────────────────────────────
function writeResult(exePath, buf) {
const backupPath = exePath + '.bak';

@@ -258,3 +384,2 @@ if (!fs.existsSync(backupPath)) {

// 尝试直接写入
try {

@@ -264,3 +389,2 @@ fs.writeFileSync(exePath, buf);

} catch {
// 文件被锁(CC 正在运行),写到临时文件
const tmpPath = exePath + '.patched';

@@ -271,3 +395,2 @@ fs.writeFileSync(tmpPath, buf);

console.log(' 请退出所有 Claude Code 后执行:\n');
if (process.platform === 'win32') {

@@ -285,14 +408,13 @@ const dir = path.dirname(exePath);

}
console.log(' 恢复方法: npx cc-channel-patch unpatch\n');
}
// ─── unpatch ────────────────────────────────────────────
// ─── patch 入口 ────────────────────────────────────────────
function unpatch() {
console.log('\n cc-channel-patch unpatch — 恢复原始 Claude Code\n');
async function patch() {
console.log('\n cc-channel-patch — 启用 Claude Code Channels\n');
const claudePath = findClaude();
if (!claudePath) {
console.error(' 找不到 Claude Code。\n');
console.error(' 找不到 Claude Code。请确认已安装并在 PATH 中。\n');
process.exit(1);

@@ -302,7 +424,25 @@ }

const exePath = resolvePatchTarget(claudePath);
console.log(` 查找: ${claudePath}`);
if (exePath !== claudePath) console.log(` 解析: ${exePath}`);
console.log(` 目标: ${exePath}\n`);
if (isBinaryFile(exePath)) {
patchBinary(exePath);
} else {
await patchAst(exePath);
}
}
// ─── unpatch ────────────────────────────────────────────────
function unpatch() {
console.log('\n cc-channel-patch unpatch — 恢复原始 Claude Code\n');
const claudePath = findClaude();
if (!claudePath) { console.error(' 找不到 Claude Code。\n'); process.exit(1); }
const exePath = resolvePatchTarget(claudePath);
const backupPath = exePath + '.bak';
if (!fs.existsSync(backupPath)) {
console.error(` 未找到备份文件: ${backupPath}`);
console.error(' 无法恢复(可能从未 patch 过)。\n');
console.error(` 未找到备份文件: ${backupPath}\n`);
process.exit(1);

@@ -320,3 +460,2 @@ }

console.log(' 请退出所有 Claude Code 后执行:\n');
if (process.platform === 'win32') {

@@ -336,3 +475,3 @@ const dir = path.dirname(exePath);

// ─── 入口 ───────────────────────────────────────────────
// ─── 入口 ───────────────────────────────────────────────────

@@ -346,2 +485,4 @@ const cmd = process.argv[2];

支持 exe 安装版(二进制模式)和 npm 安装版(AST 模式),自动检测。
用法:

@@ -353,3 +494,3 @@ npx cc-channel-patch 修补 Claude Code

} else {
patch();
patch().catch(e => { console.error(` 错误: ${e.message}\n`); process.exit(1); });
}
{
"name": "cc-channel-patch",
"version": "0.3.0",
"version": "0.4.0",
"description": "One-command patch to enable Claude Code Channels (bypasses tengu_harbor feature flag)",

@@ -5,0 +5,0 @@ "type": "module",