cc-code-status
Version:
Enhanced Claude Code launcher with statusline - supports multiple custom API configurations and code statistics
661 lines • 26.2 kB
JavaScript
;
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