|
|
#!/usr/bin/env node |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const redis = require('../src/models/redis') |
|
|
const claudeAccountService = require('../src/services/claudeAccountService') |
|
|
const readline = require('readline') |
|
|
|
|
|
|
|
|
const rl = readline.createInterface({ |
|
|
input: process.stdin, |
|
|
output: process.stdout |
|
|
}) |
|
|
|
|
|
|
|
|
function askQuestion(question) { |
|
|
return new Promise((resolve) => { |
|
|
rl.question(question, resolve) |
|
|
}) |
|
|
} |
|
|
|
|
|
|
|
|
function parseTimeInput(input) { |
|
|
const now = new Date() |
|
|
|
|
|
|
|
|
const timeMatch = input.match(/^(\d{1,2}):(\d{2})$/) |
|
|
if (timeMatch) { |
|
|
const hour = parseInt(timeMatch[1]) |
|
|
const minute = parseInt(timeMatch[2]) |
|
|
|
|
|
if (hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59) { |
|
|
const time = new Date(now) |
|
|
time.setHours(hour, minute, 0, 0) |
|
|
return time |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const relativeMatch = input.match(/^(\d+)(小时|分钟)前$/) |
|
|
if (relativeMatch) { |
|
|
const amount = parseInt(relativeMatch[1]) |
|
|
const unit = relativeMatch[2] |
|
|
const time = new Date(now) |
|
|
|
|
|
if (unit === '小时') { |
|
|
time.setHours(time.getHours() - amount) |
|
|
} else if (unit === '分钟') { |
|
|
time.setMinutes(time.getMinutes() - amount) |
|
|
} |
|
|
|
|
|
return time |
|
|
} |
|
|
|
|
|
|
|
|
const parsedDate = new Date(input) |
|
|
if (!isNaN(parsedDate.getTime())) { |
|
|
return parsedDate |
|
|
} |
|
|
|
|
|
return null |
|
|
} |
|
|
|
|
|
|
|
|
function showTimeWindowOptions() { |
|
|
const now = new Date() |
|
|
console.log('\n⏰ 可用的5小时时间窗口:') |
|
|
|
|
|
for (let hour = 0; hour < 24; hour += 5) { |
|
|
const start = hour |
|
|
const end = hour + 5 |
|
|
const startStr = `${String(start).padStart(2, '0')}:00` |
|
|
const endStr = `${String(end).padStart(2, '0')}:00` |
|
|
|
|
|
const currentHour = now.getHours() |
|
|
const isActive = currentHour >= start && currentHour < end |
|
|
const status = isActive ? ' 🟢 (当前活跃)' : '' |
|
|
|
|
|
console.log(` ${start / 5 + 1}. ${startStr} - ${endStr}${status}`) |
|
|
} |
|
|
console.log('') |
|
|
} |
|
|
|
|
|
const commands = { |
|
|
|
|
|
async debug() { |
|
|
console.log('🔍 开始调试会话窗口状态...\n') |
|
|
|
|
|
const accounts = await redis.getAllClaudeAccounts() |
|
|
console.log(`📊 找到 ${accounts.length} 个Claude账户\n`) |
|
|
|
|
|
const stats = { |
|
|
total: accounts.length, |
|
|
hasWindow: 0, |
|
|
hasLastUsed: 0, |
|
|
canRecover: 0, |
|
|
expired: 0 |
|
|
} |
|
|
|
|
|
for (const account of accounts) { |
|
|
console.log(`🏢 ${account.name} (${account.id})`) |
|
|
console.log(` 状态: ${account.isActive === 'true' ? '✅ 活跃' : '❌ 禁用'}`) |
|
|
|
|
|
if (account.sessionWindowStart && account.sessionWindowEnd) { |
|
|
stats.hasWindow++ |
|
|
const windowStart = new Date(account.sessionWindowStart) |
|
|
const windowEnd = new Date(account.sessionWindowEnd) |
|
|
const now = new Date() |
|
|
const isActive = now < windowEnd |
|
|
|
|
|
console.log(` 会话窗口: ${windowStart.toLocaleString()} - ${windowEnd.toLocaleString()}`) |
|
|
console.log(` 窗口状态: ${isActive ? '✅ 活跃' : '❌ 已过期'}`) |
|
|
|
|
|
|
|
|
if (!isActive && account.lastUsedAt) { |
|
|
const lastUsed = new Date(account.lastUsedAt) |
|
|
const recoveredWindowStart = claudeAccountService._calculateSessionWindowStart(lastUsed) |
|
|
const recoveredWindowEnd = |
|
|
claudeAccountService._calculateSessionWindowEnd(recoveredWindowStart) |
|
|
|
|
|
if (now < recoveredWindowEnd) { |
|
|
stats.canRecover++ |
|
|
console.log( |
|
|
` 可恢复窗口: ✅ ${recoveredWindowStart.toLocaleString()} - ${recoveredWindowEnd.toLocaleString()}` |
|
|
) |
|
|
} else { |
|
|
stats.expired++ |
|
|
console.log( |
|
|
` 可恢复窗口: ❌ 已过期 (${recoveredWindowStart.toLocaleString()} - ${recoveredWindowEnd.toLocaleString()})` |
|
|
) |
|
|
} |
|
|
} |
|
|
} else { |
|
|
console.log(' 会话窗口: ❌ 无') |
|
|
|
|
|
|
|
|
if (account.lastUsedAt) { |
|
|
const lastUsed = new Date(account.lastUsedAt) |
|
|
const now = new Date() |
|
|
const windowStart = claudeAccountService._calculateSessionWindowStart(lastUsed) |
|
|
const windowEnd = claudeAccountService._calculateSessionWindowEnd(windowStart) |
|
|
|
|
|
if (now < windowEnd) { |
|
|
stats.canRecover++ |
|
|
console.log( |
|
|
` 可恢复窗口: ✅ ${windowStart.toLocaleString()} - ${windowEnd.toLocaleString()}` |
|
|
) |
|
|
} else { |
|
|
stats.expired++ |
|
|
console.log( |
|
|
` 可恢复窗口: ❌ 已过期 (${windowStart.toLocaleString()} - ${windowEnd.toLocaleString()})` |
|
|
) |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
if (account.lastUsedAt) { |
|
|
stats.hasLastUsed++ |
|
|
const lastUsed = new Date(account.lastUsedAt) |
|
|
const now = new Date() |
|
|
const minutesAgo = Math.round((now - lastUsed) / (1000 * 60)) |
|
|
|
|
|
console.log(` 最后使用: ${lastUsed.toLocaleString()} (${minutesAgo}分钟前)`) |
|
|
} else { |
|
|
console.log(' 最后使用: ❌ 无记录') |
|
|
} |
|
|
|
|
|
console.log('') |
|
|
} |
|
|
|
|
|
console.log('📈 汇总统计:') |
|
|
console.log(` 总账户数: ${stats.total}`) |
|
|
console.log(` 有会话窗口: ${stats.hasWindow}`) |
|
|
console.log(` 有使用记录: ${stats.hasLastUsed}`) |
|
|
console.log(` 可以恢复: ${stats.canRecover}`) |
|
|
console.log(` 窗口已过期: ${stats.expired}`) |
|
|
}, |
|
|
|
|
|
|
|
|
async init() { |
|
|
console.log('🔄 初始化会话窗口...\n') |
|
|
const result = await claudeAccountService.initializeSessionWindows() |
|
|
|
|
|
console.log('\n📊 初始化结果:') |
|
|
console.log(` 总账户数: ${result.total}`) |
|
|
console.log(` 成功初始化: ${result.initialized}`) |
|
|
console.log(` 已跳过(已有窗口): ${result.skipped}`) |
|
|
console.log(` 窗口已过期: ${result.expired}`) |
|
|
console.log(` 无使用数据: ${result.noData}`) |
|
|
|
|
|
if (result.error) { |
|
|
console.log(` 错误: ${result.error}`) |
|
|
} |
|
|
}, |
|
|
|
|
|
|
|
|
async force() { |
|
|
console.log('🔄 强制重新计算所有会话窗口...\n') |
|
|
const result = await claudeAccountService.initializeSessionWindows(true) |
|
|
|
|
|
console.log('\n📊 强制重算结果:') |
|
|
console.log(` 总账户数: ${result.total}`) |
|
|
console.log(` 成功初始化: ${result.initialized}`) |
|
|
console.log(` 窗口已过期: ${result.expired}`) |
|
|
console.log(` 无使用数据: ${result.noData}`) |
|
|
|
|
|
if (result.error) { |
|
|
console.log(` 错误: ${result.error}`) |
|
|
} |
|
|
}, |
|
|
|
|
|
|
|
|
async clear() { |
|
|
console.log('🗑️ 清除所有会话窗口...\n') |
|
|
|
|
|
const accounts = await redis.getAllClaudeAccounts() |
|
|
let clearedCount = 0 |
|
|
|
|
|
for (const account of accounts) { |
|
|
if (account.sessionWindowStart || account.sessionWindowEnd) { |
|
|
delete account.sessionWindowStart |
|
|
delete account.sessionWindowEnd |
|
|
delete account.lastRequestTime |
|
|
|
|
|
await redis.setClaudeAccount(account.id, account) |
|
|
clearedCount++ |
|
|
|
|
|
console.log(`✅ 清除账户 ${account.name} (${account.id}) 的会话窗口`) |
|
|
} |
|
|
} |
|
|
|
|
|
console.log(`\n📊 清除完成: 共清除 ${clearedCount} 个账户的会话窗口`) |
|
|
}, |
|
|
|
|
|
|
|
|
async test() { |
|
|
console.log('🧪 创建测试会话窗口...\n') |
|
|
|
|
|
const accounts = await redis.getAllClaudeAccounts() |
|
|
if (accounts.length === 0) { |
|
|
console.log('❌ 没有找到Claude账户') |
|
|
return |
|
|
} |
|
|
|
|
|
const now = new Date() |
|
|
let updatedCount = 0 |
|
|
|
|
|
for (const account of accounts) { |
|
|
if (account.isActive === 'true') { |
|
|
|
|
|
account.lastUsedAt = now.toISOString() |
|
|
|
|
|
|
|
|
const windowStart = claudeAccountService._calculateSessionWindowStart(now) |
|
|
const windowEnd = claudeAccountService._calculateSessionWindowEnd(windowStart) |
|
|
|
|
|
account.sessionWindowStart = windowStart.toISOString() |
|
|
account.sessionWindowEnd = windowEnd.toISOString() |
|
|
account.lastRequestTime = now.toISOString() |
|
|
|
|
|
await redis.setClaudeAccount(account.id, account) |
|
|
updatedCount++ |
|
|
|
|
|
console.log(`✅ 为账户 ${account.name} 创建测试会话窗口:`) |
|
|
console.log(` 窗口时间: ${windowStart.toLocaleString()} - ${windowEnd.toLocaleString()}`) |
|
|
console.log(` 最后使用: ${now.toLocaleString()}\n`) |
|
|
} |
|
|
} |
|
|
|
|
|
console.log(`📊 测试完成: 为 ${updatedCount} 个活跃账户创建了测试会话窗口`) |
|
|
}, |
|
|
|
|
|
|
|
|
async set() { |
|
|
console.log('🔧 手动设置会话窗口...\n') |
|
|
|
|
|
|
|
|
const accounts = await redis.getAllClaudeAccounts() |
|
|
if (accounts.length === 0) { |
|
|
console.log('❌ 没有找到Claude账户') |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
console.log('📋 可用的Claude账户:') |
|
|
accounts.forEach((account, index) => { |
|
|
const status = account.isActive === 'true' ? '✅' : '❌' |
|
|
const hasWindow = account.sessionWindowStart ? '🕐' : '➖' |
|
|
console.log(` ${index + 1}. ${status} ${hasWindow} ${account.name} (${account.id})`) |
|
|
}) |
|
|
|
|
|
|
|
|
const accountIndex = await askQuestion('\n请选择账户 (输入编号): ') |
|
|
const selectedIndex = parseInt(accountIndex) - 1 |
|
|
|
|
|
if (selectedIndex < 0 || selectedIndex >= accounts.length) { |
|
|
console.log('❌ 无效的账户编号') |
|
|
return |
|
|
} |
|
|
|
|
|
const selectedAccount = accounts[selectedIndex] |
|
|
console.log(`\n🎯 已选择账户: ${selectedAccount.name}`) |
|
|
|
|
|
|
|
|
if (selectedAccount.sessionWindowStart && selectedAccount.sessionWindowEnd) { |
|
|
const windowStart = new Date(selectedAccount.sessionWindowStart) |
|
|
const windowEnd = new Date(selectedAccount.sessionWindowEnd) |
|
|
const now = new Date() |
|
|
const isActive = now >= windowStart && now < windowEnd |
|
|
|
|
|
console.log('📊 当前会话窗口:') |
|
|
console.log(` 时间: ${windowStart.toLocaleString()} - ${windowEnd.toLocaleString()}`) |
|
|
console.log(` 状态: ${isActive ? '✅ 活跃' : '❌ 已过期'}`) |
|
|
} else { |
|
|
console.log('📊 当前会话窗口: ❌ 无') |
|
|
} |
|
|
|
|
|
|
|
|
console.log('\n🛠️ 设置选项:') |
|
|
console.log(' 1. 使用预设时间窗口') |
|
|
console.log(' 2. 自定义最后使用时间') |
|
|
console.log(' 3. 直接设置窗口时间') |
|
|
console.log(' 4. 清除会话窗口') |
|
|
|
|
|
const option = await askQuestion('\n请选择设置方式 (1-4): ') |
|
|
|
|
|
switch (option) { |
|
|
case '1': |
|
|
await setPresetWindow(selectedAccount) |
|
|
break |
|
|
case '2': |
|
|
await setCustomLastUsed(selectedAccount) |
|
|
break |
|
|
case '3': |
|
|
await setDirectWindow(selectedAccount) |
|
|
break |
|
|
case '4': |
|
|
await clearAccountWindow(selectedAccount) |
|
|
break |
|
|
default: |
|
|
console.log('❌ 无效的选项') |
|
|
return |
|
|
} |
|
|
}, |
|
|
|
|
|
|
|
|
help() { |
|
|
console.log('🔧 会话窗口管理脚本\n') |
|
|
console.log('用法: node scripts/manage-session-windows.js <command>\n') |
|
|
console.log('命令:') |
|
|
console.log(' debug - 调试所有账户的会话窗口状态') |
|
|
console.log(' init - 初始化会话窗口(跳过已有窗口的账户)') |
|
|
console.log(' force - 强制重新计算所有会话窗口') |
|
|
console.log(' test - 创建测试会话窗口(设置当前时间为使用时间)') |
|
|
console.log(' set - 手动设置特定账户的会话窗口 🆕') |
|
|
console.log(' clear - 清除所有会话窗口') |
|
|
console.log(' help - 显示此帮助信息\n') |
|
|
console.log('示例:') |
|
|
console.log(' node scripts/manage-session-windows.js debug') |
|
|
console.log(' node scripts/manage-session-windows.js set') |
|
|
console.log(' node scripts/manage-session-windows.js test') |
|
|
console.log(' node scripts/manage-session-windows.js force') |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function setPresetWindow(account) { |
|
|
showTimeWindowOptions() |
|
|
|
|
|
const windowChoice = await askQuestion('请选择时间窗口 (1-5): ') |
|
|
const windowIndex = parseInt(windowChoice) - 1 |
|
|
|
|
|
if (windowIndex < 0 || windowIndex >= 5) { |
|
|
console.log('❌ 无效的窗口选择') |
|
|
return |
|
|
} |
|
|
|
|
|
const now = new Date() |
|
|
const startHour = windowIndex * 5 |
|
|
|
|
|
|
|
|
const windowStart = new Date(now) |
|
|
windowStart.setHours(startHour, 0, 0, 0) |
|
|
|
|
|
|
|
|
const windowEnd = new Date(windowStart) |
|
|
windowEnd.setHours(windowEnd.getHours() + 5) |
|
|
|
|
|
|
|
|
if (windowEnd <= now) { |
|
|
windowStart.setDate(windowStart.getDate() + 1) |
|
|
windowEnd.setDate(windowEnd.getDate() + 1) |
|
|
} |
|
|
|
|
|
|
|
|
const setLastUsed = await askQuestion('是否设置当前时间为最后使用时间? (y/N): ') |
|
|
|
|
|
|
|
|
account.sessionWindowStart = windowStart.toISOString() |
|
|
account.sessionWindowEnd = windowEnd.toISOString() |
|
|
account.lastRequestTime = now.toISOString() |
|
|
|
|
|
if (setLastUsed.toLowerCase() === 'y' || setLastUsed.toLowerCase() === 'yes') { |
|
|
account.lastUsedAt = now.toISOString() |
|
|
} |
|
|
|
|
|
await redis.setClaudeAccount(account.id, account) |
|
|
|
|
|
console.log('\n✅ 已设置会话窗口:') |
|
|
console.log(` 账户: ${account.name}`) |
|
|
console.log(` 窗口: ${windowStart.toLocaleString()} - ${windowEnd.toLocaleString()}`) |
|
|
console.log(` 状态: ${now >= windowStart && now < windowEnd ? '✅ 活跃' : '⏰ 未来窗口'}`) |
|
|
} |
|
|
|
|
|
|
|
|
async function setCustomLastUsed(account) { |
|
|
console.log('\n📝 设置最后使用时间:') |
|
|
console.log('支持的时间格式:') |
|
|
console.log(' - HH:MM (如: 14:30)') |
|
|
console.log(' - 相对时间 (如: 2小时前, 30分钟前)') |
|
|
console.log(' - ISO格式 (如: 2025-07-28T14:30:00)') |
|
|
|
|
|
const timeInput = await askQuestion('\n请输入最后使用时间: ') |
|
|
const lastUsedTime = parseTimeInput(timeInput) |
|
|
|
|
|
if (!lastUsedTime) { |
|
|
console.log('❌ 无效的时间格式') |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
const windowStart = claudeAccountService._calculateSessionWindowStart(lastUsedTime) |
|
|
const windowEnd = claudeAccountService._calculateSessionWindowEnd(windowStart) |
|
|
|
|
|
|
|
|
account.lastUsedAt = lastUsedTime.toISOString() |
|
|
account.sessionWindowStart = windowStart.toISOString() |
|
|
account.sessionWindowEnd = windowEnd.toISOString() |
|
|
account.lastRequestTime = lastUsedTime.toISOString() |
|
|
|
|
|
await redis.setClaudeAccount(account.id, account) |
|
|
|
|
|
console.log('\n✅ 已设置会话窗口:') |
|
|
console.log(` 账户: ${account.name}`) |
|
|
console.log(` 最后使用: ${lastUsedTime.toLocaleString()}`) |
|
|
console.log(` 窗口: ${windowStart.toLocaleString()} - ${windowEnd.toLocaleString()}`) |
|
|
|
|
|
const now = new Date() |
|
|
console.log(` 状态: ${now >= windowStart && now < windowEnd ? '✅ 活跃' : '❌ 已过期'}`) |
|
|
} |
|
|
|
|
|
|
|
|
async function setDirectWindow(account) { |
|
|
console.log('\n⏰ 直接设置窗口时间:') |
|
|
|
|
|
const startInput = await askQuestion('请输入窗口开始时间 (HH:MM): ') |
|
|
const startTime = parseTimeInput(startInput) |
|
|
|
|
|
if (!startTime) { |
|
|
console.log('❌ 无效的开始时间格式') |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
const endTime = new Date(startTime) |
|
|
endTime.setHours(endTime.getHours() + 5) |
|
|
|
|
|
|
|
|
if (endTime.getDate() !== startTime.getDate()) { |
|
|
const confirm = await askQuestion(`窗口将跨天到次日 ${endTime.getHours()}:00,确认吗? (y/N): `) |
|
|
if (confirm.toLowerCase() !== 'y' && confirm.toLowerCase() !== 'yes') { |
|
|
console.log('❌ 已取消设置') |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
const now = new Date() |
|
|
|
|
|
|
|
|
account.sessionWindowStart = startTime.toISOString() |
|
|
account.sessionWindowEnd = endTime.toISOString() |
|
|
account.lastRequestTime = now.toISOString() |
|
|
|
|
|
|
|
|
const updateLastUsed = await askQuestion('是否将最后使用时间设置为窗口开始时间? (y/N): ') |
|
|
if (updateLastUsed.toLowerCase() === 'y' || updateLastUsed.toLowerCase() === 'yes') { |
|
|
account.lastUsedAt = startTime.toISOString() |
|
|
} |
|
|
|
|
|
await redis.setClaudeAccount(account.id, account) |
|
|
|
|
|
console.log('\n✅ 已设置会话窗口:') |
|
|
console.log(` 账户: ${account.name}`) |
|
|
console.log(` 窗口: ${startTime.toLocaleString()} - ${endTime.toLocaleString()}`) |
|
|
console.log( |
|
|
` 状态: ${now >= startTime && now < endTime ? '✅ 活跃' : now < startTime ? '⏰ 未来窗口' : '❌ 已过期'}` |
|
|
) |
|
|
} |
|
|
|
|
|
|
|
|
async function clearAccountWindow(account) { |
|
|
const confirm = await askQuestion(`确认清除账户 "${account.name}" 的会话窗口吗? (y/N): `) |
|
|
|
|
|
if (confirm.toLowerCase() !== 'y' && confirm.toLowerCase() !== 'yes') { |
|
|
console.log('❌ 已取消操作') |
|
|
return |
|
|
} |
|
|
|
|
|
|
|
|
delete account.sessionWindowStart |
|
|
delete account.sessionWindowEnd |
|
|
delete account.lastRequestTime |
|
|
|
|
|
await redis.setClaudeAccount(account.id, account) |
|
|
|
|
|
console.log(`\n✅ 已清除账户 "${account.name}" 的会话窗口`) |
|
|
} |
|
|
|
|
|
async function main() { |
|
|
const command = process.argv[2] || 'help' |
|
|
|
|
|
if (!commands[command]) { |
|
|
console.error(`❌ 未知命令: ${command}`) |
|
|
commands.help() |
|
|
process.exit(1) |
|
|
} |
|
|
|
|
|
if (command === 'help') { |
|
|
commands.help() |
|
|
return |
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
await redis.connect() |
|
|
|
|
|
|
|
|
await commands[command]() |
|
|
} catch (error) { |
|
|
console.error('❌ 执行失败:', error) |
|
|
process.exit(1) |
|
|
} finally { |
|
|
await redis.disconnect() |
|
|
rl.close() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (require.main === module) { |
|
|
main().then(() => { |
|
|
console.log('\n🎉 操作完成') |
|
|
process.exit(0) |
|
|
}) |
|
|
} |
|
|
|
|
|
module.exports = { commands } |
|
|
|