cc-channel-patch
Advanced tools
+259
-118
@@ -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); }); | ||
| } |
+1
-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", |
Network access
Supply chain riskThis module accesses the network.
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
19861
56.16%410
41.38%6
50%2
100%