UNPKG

cc-code-status

Version:

Enhanced Claude Code launcher with statusline - supports multiple custom API configurations and code statistics

661 lines 26.2 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const os = __importStar(require("os")); // CLI 用于全局安装 - 处理状态栏执行和设置 const args = process.argv.slice(2); const command = args[0]; if (command === 'setup' || command === 'install') { // 运行设置 setup(); } else if (command === 'uninstall') { // 运行卸载 uninstall(); } else if (command === 'sync-enable') { // 启用数据上报 setSyncEnabled(true); } else if (command === 'sync-disable') { // 禁用数据上报 setSyncEnabled(false); } else if (command === 'sync-status') { // 查看数据上报状态 showSyncStatus(); } else if (command === 'sync-now' || command === 'sync') { // 主动触发一次数据上报 const { StatusLinePlugin } = require('./index'); StatusLinePlugin.manualSync(); } else if (command === 'custom-sync') { // 自定义数据上报 customSync(); } else if (command === 'week') { // 显示本周统计 showWeekStats(); } else if (command === 'exclude') { // 排除项目管理 const subCommand = args[1]; const projectPath = args[2]; if (subCommand === 'add' && projectPath) { const { StatusLinePlugin } = require('./index'); StatusLinePlugin.addExcludedProject(projectPath); } else if (subCommand === 'remove' && projectPath) { const { StatusLinePlugin } = require('./index'); StatusLinePlugin.removeExcludedProject(projectPath); } else if (subCommand === 'list') { const { StatusLinePlugin } = require('./index'); StatusLinePlugin.listExcludedProjects(); } else { console.log(''); console.log('用法:'); console.log(' cc-code-status exclude add <项目路径> 添加排除项目'); console.log(' cc-code-status exclude remove <项目路径> 移除排除项目'); console.log(' cc-code-status exclude list 列出排除项目'); console.log(''); console.log('示例:'); console.log(' cc-code-status exclude add /Users/qilin/test-project'); console.log(' cc-code-status exclude add .'); console.log(' cc-code-status exclude remove /Users/qilin/test-project'); console.log(' cc-code-status exclude list'); console.log(''); } } else if (command === 'help' || command === '--help' || command === '-h') { // 显示帮助 showHelp(); } else { // 默认:作为状态栏插件运行(用于 Claude Code) const { StatusLinePlugin } = require('./index'); const plugin = new StatusLinePlugin(); plugin.run().catch(() => { console.log('Error'); }); } function setup() { console.log('🚀 设置 CC Code Status 插件...'); const claudeSettingsDir = path.join(os.homedir(), '.claude'); const claudeSettingsFile = path.join(claudeSettingsDir, 'settings.json'); // 如果 .claude 目录不存在则创建 if (!fs.existsSync(claudeSettingsDir)) { console.log('📁 创建 Claude 设置目录...'); fs.mkdirSync(claudeSettingsDir, { recursive: true }); } // 获取全局命令路径 const commandPath = 'cc-code-status'; // 创建或更新 settings.json let settings = {}; if (fs.existsSync(claudeSettingsFile)) { console.log('📝 更新现有 settings.json...'); // 备份现有设置 const backupFile = `${claudeSettingsFile}.backup`; fs.copyFileSync(claudeSettingsFile, backupFile); console.log(` 备份保存至: ${backupFile}`); try { const existingContent = fs.readFileSync(claudeSettingsFile, 'utf8'); settings = JSON.parse(existingContent); } catch (error) { console.error('⚠️ 读取现有设置出错,创建新设置...'); settings = {}; } } else { console.log('📝 创建新的 settings.json...'); } // 更新 statusLine 配置 settings.statusLine = { type: 'command', command: commandPath }; // 写入更新后的设置 fs.writeFileSync(claudeSettingsFile, JSON.stringify(settings, null, 2)); console.log('✅ 设置更新成功!'); console.log(''); console.log('🎉 CC Code Status 插件已配置完成!'); console.log(''); console.log('插件将显示:'); console.log(' 📁 当前目录名称'); console.log(' 👤 Git 用户名'); console.log(''); console.log('卸载: npm uninstall -g cc-code-status'); } function uninstall() { console.log('🗑️ 移除 CC Code Status 插件配置...'); const claudeSettingsFile = path.join(os.homedir(), '.claude', 'settings.json'); if (!fs.existsSync(claudeSettingsFile)) { console.log('⚠️ 未找到 Claude 设置文件。'); return; } try { const content = fs.readFileSync(claudeSettingsFile, 'utf8'); const settings = JSON.parse(content); if (settings.statusLine) { delete settings.statusLine; fs.writeFileSync(claudeSettingsFile, JSON.stringify(settings, null, 2)); console.log('✅ StatusLine 配置已从 Claude 设置中移除。'); } else { console.log('⚠️ 设置中未找到 statusLine 配置。'); } } catch (error) { console.error('❌ 更新设置时出错:', error); } console.log(''); console.log('完成卸载请运行:'); console.log(' npm uninstall -g cc-code-status'); } function setSyncEnabled(enabled) { const configDir = path.join(os.homedir(), '.cc-code-status'); const configFile = path.join(configDir, 'config.json'); // 确保目录存在 if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } // 读取现有配置 let config = { apiUrl: 'http://10.40.0.70:8087/api/cloudcode-ai/batch-receive', syncInterval: 1800000, enabled: false }; if (fs.existsSync(configFile)) { try { const content = fs.readFileSync(configFile, 'utf8'); config = JSON.parse(content); } catch (error) { console.error('⚠️ 读取配置文件失败,使用默认配置'); } } // 更新 enabled 状态 config.enabled = enabled; // 写入配置 try { fs.writeFileSync(configFile, JSON.stringify(config, null, 2)); console.log(''); if (enabled) { console.log('✅ 数据上报功能已启用'); console.log(''); console.log(`📡 API 地址: ${config.apiUrl}`); console.log(`⏱️ 同步间隔: ${config.syncInterval / 1000 / 60} 分钟`); } else { console.log('✅ 数据上报功能已禁用'); } console.log(''); console.log(`配置文件: ${configFile}`); console.log(''); } catch (error) { console.error('❌ 写入配置文件失败:', error); } } function showSyncStatus() { const configFile = path.join(os.homedir(), '.cc-code-status', 'config.json'); if (!fs.existsSync(configFile)) { console.log(''); console.log('⚠️ 配置文件不存在'); console.log(''); console.log('运行以下命令启用数据上报:'); console.log(' cc-code-status sync-enable'); console.log(''); return; } try { const content = fs.readFileSync(configFile, 'utf8'); const config = JSON.parse(content); console.log(''); console.log('========================================'); console.log(' 数据上报状态'); console.log('========================================'); console.log(''); console.log(`状态: ${config.enabled ? '✅ 已启用' : '❌ 已禁用'}`); console.log(`API 地址: ${config.apiUrl || '(未配置)'}`); console.log(`同步间隔: ${(config.syncInterval || 1800000) / 1000 / 60} 分钟`); console.log(''); console.log(`配置文件: ${configFile}`); console.log(''); if (config.enabled) { console.log('要禁用数据上报,运行:'); console.log(' cc-code-status sync-disable'); } else { console.log('要启用数据上报,运行:'); console.log(' cc-code-status sync-enable'); } console.log(''); } catch (error) { console.error('❌ 读取配置失败:', error); } } function customSync() { const readline = require('readline'); const { execSync } = require('child_process'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); console.log(''); console.log('========================================'); console.log(' 自定义数据上报'); console.log('========================================'); console.log(''); // 获取 git 用户名 let username = 'Unknown'; try { username = execSync('git config user.name', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim(); } catch { // 忽略错误 } // 使用 Promise 包装 readline 问答 const askQuestion = (question) => { return new Promise((resolve) => { rl.question(question, (answer) => { resolve(answer.trim()); }); }); }; // 交互式输入数据 (async () => { try { // 检查是否启用了同步功能 const configFile = path.join(os.homedir(), '.cc-code-status', 'config.json'); if (!fs.existsSync(configFile)) { console.log('❌ 配置文件不存在,请先运行: cc-code-status sync-enable'); console.log(''); rl.close(); return; } const configContent = fs.readFileSync(configFile, 'utf8'); const config = JSON.parse(configContent); if (!config.enabled) { console.log('❌ 数据上报功能未启用'); console.log(''); console.log('请先运行以下命令启用数据上报:'); console.log(' cc-code-status sync-enable'); console.log(''); rl.close(); return; } if (!config.apiUrl) { console.log('❌ API 地址未配置'); console.log(''); console.log(`请在配置文件中设置 apiUrl: ${configFile}`); console.log(''); rl.close(); return; } console.log(`当前用户: ${username}`); console.log(''); // 输入对话轮次 const roundsInput = await askQuestion('请输入对话轮次: '); const rounds = parseInt(roundsInput, 10); if (isNaN(rounds) || rounds < 0) { console.log(''); console.log('❌ 对话轮次必须是非负整数'); console.log(''); rl.close(); return; } // 输入新增代码行数 const addedInput = await askQuestion('请输入新增代码行数: '); const codeAdded = parseInt(addedInput, 10); if (isNaN(codeAdded) || codeAdded < 0) { console.log(''); console.log('❌ 新增代码行数必须是非负整数'); console.log(''); rl.close(); return; } // 输入删除代码行数 const deletedInput = await askQuestion('请输入删除代码行数: '); const codeDeleted = parseInt(deletedInput, 10); if (isNaN(codeDeleted) || codeDeleted < 0) { console.log(''); console.log('❌ 删除代码行数必须是非负整数'); console.log(''); rl.close(); return; } console.log(''); console.log('数据汇总:'); console.log(` 用户: ${username}`); console.log(` 对话轮次: ${rounds}`); console.log(` 新增代码: ${codeAdded} 行`); console.log(` 删除代码: ${codeDeleted} 行`); console.log(''); // 确认上报 const confirm = await askQuestion('确认上报以上数据?(y/n): '); if (confirm.toLowerCase() !== 'y' && confirm.toLowerCase() !== 'yes') { console.log(''); console.log('❌ 已取消上报'); console.log(''); rl.close(); return; } // 构造 ConversationDetail 对象 const today = new Date(); const dateString = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`; const conversationDetail = { id: `custom-${Date.now()}`, userId: username, userName: username, conversationCount: rounds, adoptedLines: codeAdded + codeDeleted, codeAdded: codeAdded, codeDeleted: codeDeleted, messages: [], createDate: dateString, updatedAt: dateString }; // 调用 StatusLinePlugin 的上报方法 const { StatusLinePlugin } = require('./index'); const plugin = new StatusLinePlugin(); // 直接调用 syncToBackend 方法(需要使其可访问) plugin.syncToBackend(config.apiUrl, [conversationDetail]); console.log(''); console.log('✅ 数据已提交上报'); console.log(''); console.log('注: 上报结果请查看服务端日志'); console.log(''); rl.close(); } catch (error) { console.log(''); console.log('❌ 上报失败:', error); console.log(''); rl.close(); } })(); } function showHelp() { console.log('CC Code Status Plugin - Claude Code git 用户信息显示'); console.log(''); console.log('用法:'); console.log(' cc-code-status 作为状态栏插件运行(由 Claude Code 使用)'); console.log(' cc-code-status setup 配置 Claude Code 使用此插件'); console.log(' cc-code-status uninstall 从 Claude Code 中移除插件配置'); console.log(' cc-code-status sync-enable 启用数据上报功能'); console.log(' cc-code-status sync-disable 禁用数据上报功能'); console.log(' cc-code-status sync-status 查看数据上报状态'); console.log(' cc-code-status sync-now 主动触发一次数据上报'); console.log(' cc-code-status week 显示本周统计(对话次数/轮数、代码行数)'); console.log(' cc-code-status exclude add <路径> 添加排除项目(不统计该项目)'); console.log(' cc-code-status exclude remove <路径> 移除排除项目'); console.log(' cc-code-status exclude list 列出所有排除项目'); console.log(' cc-code-status help 显示此帮助信息'); console.log(''); console.log('别名: 所有命令都支持短别名 ccs(如 ccs sync-now, ccs exclude list)'); console.log(''); console.log('安装:'); console.log(' npm install -g cc-code-status'); console.log(''); console.log('安装后插件会自动配置。'); } function showWeekStats() { const { execSync } = require('child_process'); try { // 获取 git 用户名 let username = 'Unknown'; try { username = execSync('git config user.name', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim(); } catch { // 忽略错误 } // 计算本周的日期范围(周一到周日) const today = new Date(); const dayOfWeek = today.getDay(); // 0=周日, 1=周一, ..., 6=周六 const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // 到本周一的天数 const weekStart = new Date(today); weekStart.setDate(today.getDate() - daysToMonday); weekStart.setHours(0, 0, 0, 0); const weekEnd = new Date(weekStart); weekEnd.setDate(weekStart.getDate() + 6); weekEnd.setHours(23, 59, 59, 999); // 收集本周每一天的数据 const dailyStats = []; let totalConversations = 0; let totalRounds = 0; let totalAdded = 0; let totalDeleted = 0; const dayNames = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; for (let i = 0; i < 7; i++) { const currentDay = new Date(weekStart); currentDay.setDate(weekStart.getDate() + i); const dayStart = new Date(currentDay); dayStart.setHours(0, 0, 0, 0); const dayEnd = new Date(currentDay); dayEnd.setHours(23, 59, 59, 999); const conversations = countConversations(dayStart, dayEnd); const rounds = countRounds(dayStart, dayEnd); const codeStats = countCodeChanges(dayStart, dayEnd); totalConversations += conversations; totalRounds += rounds; totalAdded += codeStats.added; totalDeleted += codeStats.deleted; dailyStats.push({ date: formatDate(currentDay), dayName: dayNames[currentDay.getDay()], conversations, rounds, codeAdded: codeStats.added, codeDeleted: codeStats.deleted }); } // 输出结果 console.log(''); console.log('========================================'); console.log(` 本周统计 (${formatDate(weekStart)}${formatDate(weekEnd)})`); console.log('========================================'); console.log(''); console.log(`总计: ${totalConversations}次/${totalRounds}轮 | +${totalAdded}/-${totalDeleted} 行`); console.log(''); console.log('每日明细:'); console.log(''); for (const day of dailyStats) { const dateStr = `${day.dayName} (${day.date})`; const statsStr = `${day.conversations}次/${day.rounds}轮 | +${day.codeAdded}/-${day.codeDeleted} 行`; console.log(` ${dateStr.padEnd(20)} ${statsStr}`); } console.log(''); } catch (error) { console.error('❌ 统计失败:', error); } } // ========== 辅助函数 ========== function formatDate(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } function countConversations(dayStart, dayEnd) { try { const projectsDir = path.join(os.homedir(), '.claude', 'projects'); if (!fs.existsSync(projectsDir)) { return 0; } const sessionIds = new Set(); const projectDirs = fs.readdirSync(projectsDir); for (const dir of projectDirs) { const dirPath = path.join(projectsDir, dir); if (!fs.statSync(dirPath).isDirectory()) continue; const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.jsonl') && !f.startsWith('agent-')); for (const file of files) { const filePath = path.join(dirPath, file); const content = fs.readFileSync(filePath, 'utf8'); const lines = content.trim().split('\n'); if (lines.length === 0) continue; for (const line of lines) { try { const record = JSON.parse(line); if (record.sessionId && record.timestamp) { const timestamp = new Date(record.timestamp); if (timestamp >= dayStart && timestamp <= dayEnd) { sessionIds.add(record.sessionId); } break; } } catch { // 忽略解析错误 } } } } return sessionIds.size; } catch { return 0; } } function countRounds(dayStart, dayEnd) { try { const historyPath = path.join(os.homedir(), '.claude', 'history.jsonl'); if (!fs.existsSync(historyPath)) { return 0; } let count = 0; const content = fs.readFileSync(historyPath, 'utf8'); const lines = content.trim().split('\n').filter(line => line.trim()); for (const line of lines) { try { const entry = JSON.parse(line); const display = entry.display?.trim() || ''; if (!display || display.startsWith('/')) { continue; } const timestamp = new Date(entry.timestamp || entry.createdAt || 0); if (timestamp >= dayStart && timestamp <= dayEnd) { count++; } } catch { // 忽略解析错误 } } return count; } catch { return 0; } } function countCodeChanges(dayStart, dayEnd) { try { const projectsDir = path.join(os.homedir(), '.claude', 'projects'); if (!fs.existsSync(projectsDir)) { return { added: 0, deleted: 0 }; } let added = 0; let deleted = 0; const projectDirs = fs.readdirSync(projectsDir); for (const dir of projectDirs) { const dirPath = path.join(projectsDir, dir); if (!fs.statSync(dirPath).isDirectory()) continue; const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.jsonl')); for (const file of files) { const filePath = path.join(dirPath, file); const content = fs.readFileSync(filePath, 'utf8'); const lines = content.trim().split('\n'); for (const line of lines) { try { const record = JSON.parse(line); if (!record.timestamp) continue; const timestamp = new Date(record.timestamp); if (timestamp < dayStart || timestamp > dayEnd) { continue; } if (record.type === 'assistant' && record.message?.content) { for (const item of record.message.content) { if (item.type === 'tool_use' && item.input) { const { name, input } = item; if (name === 'Edit') { const oldLines = (input.old_string || '').split('\n').length; const newLines = (input.new_string || '').split('\n').length; if (newLines > oldLines) { added += newLines - oldLines; } else { deleted += oldLines - newLines; } } else if (name === 'Write') { const lineCount = (input.content || '').split('\n').length; added += lineCount; } } } } } catch { // 忽略解析错误 } } } } return { added, deleted }; } catch { return { added: 0, deleted: 0 }; } } //# sourceMappingURL=cli.backup.js.map