๐ apikeys.ts โข 8172 bytes
/**
* CmdCode V0.5 - ๆๆ้
็ฝฎ็ปไธๅ
ฅๅฃ
*
* ๐ฆ ๆถๆ็ฎๅ๏ผ
* - crypto-util.ts๏ผๅ ๅฏๅญๅจ + ๅฏ้ฅๆฑ ็ฎก็
* - apikeys.ts๏ผ็ปไธๅฏผๅบๅ
ฅๅฃ
*
* ๅญๅจไฝ็ฝฎ๏ผ
* - ~/.cmdcode/secrets.enc๏ผๆๆๆๆ้
็ฝฎ๏ผAES-256-CBC ๅ ๅฏ๏ผ
*/
import {
// ๅ ๅฏ/่งฃๅฏ
encrypt, decrypt,
encryptField, decryptField,
// ้
็ฝฎ็ฎก็
loadSecrets, saveSecrets, updateSecrets, clearSecrets, hasSecrets, maskSecret,
// ๅฏ้ฅๆฑ ็ฎก็
loadChatKeyPool, saveChatKeyPool,
addChatKey, removeChatKey,
loadEmbeddingKeyPool, saveEmbeddingKeyPool,
addEmbeddingKey, removeEmbeddingKey,
// ๅฏ้ฅ่ทๅ
getChatApiKey, rotateChatApiKey,
getEmbeddingApiKey, rotateEmbeddingApiKey,
// ๅฏ้ฅๆฑ ็ถๆ
getChatKeyPoolStatus, getEmbeddingKeyPoolStatus,
resetChatKeyPool, resetEmbeddingKeyPool, resetAllKeyPools,
markChatKeyExhausted, markEmbeddingKeyExhausted,
// ๅฎๆถ้็ฝฎ
startDailyKeyPoolReset, stopDailyKeyPoolReset,
// ๅบ็จ้
็ฝฎ
loadAppConfig, saveAppConfig,
// ็จๆท็ผๅญ
saveUserCache, loadUserCache, clearUserCache,
// ๅ้่ฎฐๅฟ้
็ฝฎ
loadMemorySearchConfig, saveMemorySearchConfig, hasMemorySearchConfig,
DEFAULT_MEMORY_SEARCH,
// ็ฑปๅ
type Secrets, type KeyPoolEntry, type AppSettings
} from './crypto-util.js'
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// ้ๆฐๅฏผๅบๆๆๆฅๅฃ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// ๅ ๅฏ/่งฃๅฏ
export { encrypt, decrypt, encryptField, decryptField }
// ้
็ฝฎ็ฎก็
export { loadSecrets, saveSecrets, updateSecrets, clearSecrets, hasSecrets, maskSecret }
// ๅฏ้ฅๆฑ ็ฎก็
export {
loadChatKeyPool, saveChatKeyPool,
addChatKey, removeChatKey,
loadEmbeddingKeyPool, saveEmbeddingKeyPool,
addEmbeddingKey, removeEmbeddingKey
}
// ๅฏ้ฅ่ทๅ
export {
getChatApiKey, rotateChatApiKey,
getEmbeddingApiKey, rotateEmbeddingApiKey
}
// ๅฏ้ฅๆฑ ็ถๆ
export {
getChatKeyPoolStatus, getEmbeddingKeyPoolStatus,
resetChatKeyPool, resetEmbeddingKeyPool, resetAllKeyPools,
markChatKeyExhausted, markEmbeddingKeyExhausted
}
// ๅฎๆถ้็ฝฎ
export { startDailyKeyPoolReset, stopDailyKeyPoolReset }
// ๅบ็จ้
็ฝฎ
export { loadAppConfig, saveAppConfig }
// ็จๆท็ผๅญ
export { saveUserCache, loadUserCache, clearUserCache }
// ๅ้่ฎฐๅฟ้
็ฝฎ
export { loadMemorySearchConfig, saveMemorySearchConfig, hasMemorySearchConfig, DEFAULT_MEMORY_SEARCH }
// ็ฑปๅ
export type { Secrets, KeyPoolEntry, AppSettings }
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// ไผ่ฏๆๆๅญๅจ๏ผๆ ้ๅ ๅฏ๏ผๆนไพฟ่ฐๅๆฅ็ไฟฎๆน๏ผ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from 'node:fs'
import { join } from 'node:path'
import { homedir } from 'node:os'
const CMD_DIR = join(homedir(), '.cmdcode')
const SESSIONS_DIR = join(CMD_DIR, 'sessions')
export interface SessionIndex {
id: string
createdAt: string
updatedAt: string
messageCount: number
preview: string
}
/** ไฟๅญไผ่ฏ๏ผๆๆJSON๏ผ - P2 #29: ๅ
ๅคไปฝๆง็ๆฌ */
export function saveSessionPlaintext(sessionId: string, data: any): void {
if (!existsSync(SESSIONS_DIR)) mkdirSync(SESSIONS_DIR, { recursive: true })
const filePath = join(SESSIONS_DIR, `${sessionId}.json`)
// P2 #29: ๅคไปฝๆง็ๆฌ๏ผๆๅคไฟ็3ไธชๅคไปฝ๏ผ
if (existsSync(filePath)) {
const backupDir = join(SESSIONS_DIR, 'backup')
if (!existsSync(backupDir)) mkdirSync(backupDir, { recursive: true })
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
const backupPath = join(backupDir, `${sessionId}-${timestamp}.json`)
try {
// ๅคๅถๅฝๅๆไปถๅฐๅคไปฝ
const currentContent = readFileSync(filePath, 'utf-8')
writeFileSync(backupPath, currentContent, 'utf-8')
// ๆธ
็ๆงๅคไปฝ๏ผไฟ็ๆ่ฟ3ไธช๏ผ
const backups = readdirSync(backupDir)
.filter(f => f.startsWith(sessionId) && f.endsWith('.json'))
.sort()
while (backups.length > 3) {
const oldBackup = join(backupDir, backups.shift()!)
try { require('node:fs').unlinkSync(oldBackup) } catch { /* ignore */ }
}
} catch { /* ๅคไปฝๅคฑ่ดฅไธ้ปๆญขไฟๅญ */ }
}
writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8')
}
/** ่ฏปๅไผ่ฏ๏ผๆๆJSON๏ผ */
export function loadSessionPlaintext<T = any>(sessionId: string): T | null {
const filePath = join(SESSIONS_DIR, `${sessionId}.json`)
if (!existsSync(filePath)) return null
try {
return JSON.parse(readFileSync(filePath, 'utf-8')) as T
} catch {
return null
}
}
/** ๅๅบๆๆไผ่ฏ */
export function listPlaintextSessions(): SessionIndex[] {
if (!existsSync(SESSIONS_DIR)) return []
const files = readdirSync(SESSIONS_DIR).filter(f => f.endsWith('.json'))
const sessions = files.map(f => {
const id = f.replace('.json', '')
const filePath = join(SESSIONS_DIR, f)
let messageCount = 0
let preview = ''
let updatedAt = ''
let createdAt = ''
try {
const stat = require('node:fs').statSync(filePath)
updatedAt = new Date(stat.mtime).toISOString()
createdAt = new Date(stat.birthtime).toISOString()
} catch { /* P5: ๆไปถไธๅญๅจๆๆ ๆ้ */ }
try {
const messages = JSON.parse(readFileSync(filePath, 'utf-8'))
if (Array.isArray(messages)) {
messageCount = messages.length
const firstUser = messages.find((m: any) => m.role === 'user')
preview = firstUser?.content?.slice(0, 60) || ''
}
} catch { /* P5: JSON่งฃๆๅคฑ่ดฅๆๆ ผๅผ้่ฏฏ */ }
return { id, createdAt, updatedAt, messageCount, preview }
})
// ๆ updatedAt ้ๅบๆๅ๏ผๆ่ฟๆดๆฐ็ๅจๅ๏ผ
sessions.sort((a, b) => (b.updatedAt || '').localeCompare(a.updatedAt || ''))
return sessions
}
/** ่ทๅๆๆฐไผ่ฏID */
export function getLatestSessionId(): string | null {
if (!existsSync(SESSIONS_DIR)) return null
const files = readdirSync(SESSIONS_DIR).filter(f => f.endsWith('.json')).sort()
if (files.length === 0) return null
return files[files.length - 1].replace('.json', '')
}
/** ๅ ้คไผ่ฏ */
export function deleteSession(sessionId: string): boolean {
const filePath = join(SESSIONS_DIR, `${sessionId}.json`)
if (!existsSync(filePath)) return false
try {
unlinkSync(filePath)
return true
} catch {
return false
}
}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// ๅบๅผๆฅๅฃ๏ผๅ
ผๅฎนๆงไปฃ็ ๏ผ
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
/** @deprecated ไฝฟ็จ getChatApiKey */
export const getCurrentMinimaxKey = getChatApiKey
/** @deprecated ไฝฟ็จ rotateChatApiKey */
export const rotateMinimaxKey = rotateChatApiKey
/** @deprecated ไฝฟ็จ getChatKeyPoolStatus */
export const getMinimaxKeyPoolStatus = getChatKeyPoolStatus
/** @deprecated ไฝฟ็จ resetChatKeyPool */
export const resetMinimaxKeyPool = resetChatKeyPool
/** @deprecated ไฝฟ็จ loadChatKeyPool */
export function getChatKeyPool(): KeyPoolEntry[] { return loadChatKeyPool() }
/** ไฟๅญ API Key ๅ Base URL๏ผ็ฎๅๆฅๅฃ๏ผ */
export function saveKeys(apiKey: string, baseUrl?: string): void {
const secrets = loadSecrets()
secrets.apiKey = apiKey
if (baseUrl) {
secrets.baseUrl = baseUrl
}
saveSecrets(secrets)
}