feat: Add new files for i18n, libs, and project structure

This commit is contained in:
tukuaiai
2025-12-18 13:33:30 +08:00
parent 516ac26f93
commit bfa0694868
750 changed files with 112255 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
from .codex import CodexParser
from .gemini import GeminiParser
from .claude import ClaudeParser
from .kiro import KiroParser
from .base import SessionData
__all__ = ["CodexParser", "GeminiParser", "ClaudeParser", "KiroParser", "SessionData"]
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import List, Dict
@dataclass
class SessionData:
"""会话数据"""
session_id: str
source: str
file_path: str
file_mtime: float = 0
cwd: str = None
messages: List[Dict] = field(default_factory=list) # [{"time", "role", "content"}]
class BaseParser(ABC):
@abstractmethod
def find_files(self, paths: list) -> list:
pass
@abstractmethod
def parse_file(self, filepath: str) -> SessionData:
pass
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import json
import hashlib
from datetime import datetime
from collections import defaultdict
from .base import BaseParser, SessionData
class ClaudeParser(BaseParser):
def find_files(self, paths: list) -> list:
files = []
for base in paths:
history = os.path.join(base, "history.jsonl")
if os.path.exists(history):
files.append(history)
return files
def parse_file(self, filepath: str) -> list:
"""返回多个 SessionData(按 project 分组)"""
projects = defaultdict(list)
file_mtime = os.path.getmtime(filepath)
with open(filepath, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line:
continue
data = json.loads(line)
content = data.get('display', '')
if not content:
continue
project = data.get('project', 'unknown')
ts_ms = data.get('timestamp', 0)
ts = datetime.fromtimestamp(ts_ms / 1000).isoformat() if ts_ms else ''
projects[project].append({
'time': ts,
'role': 'user',
'content': content
})
return [
SessionData(
session_id='claude-' + hashlib.md5(proj.encode()).hexdigest()[:12],
source='claude',
file_path=filepath,
file_mtime=file_mtime,
cwd=proj,
messages=msgs
)
for proj, msgs in projects.items()
]
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import json
import re
from .base import BaseParser, SessionData
class CodexParser(BaseParser):
def __init__(self, source: str = 'codex'):
self.source = source
def find_files(self, paths: list) -> list:
files = []
for base in paths:
if not os.path.exists(base):
continue
for root, _, names in os.walk(base):
for f in names:
if f.endswith('.jsonl') and f != 'history.jsonl':
files.append(os.path.join(root, f))
return files
def _extract_id(self, filepath: str) -> str:
match = re.search(r'([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})',
os.path.basename(filepath))
return match.group(1) if match else os.path.basename(filepath).replace('.jsonl', '')
def parse_file(self, filepath: str) -> SessionData:
s = SessionData(
session_id=self._extract_id(filepath),
source=self.source,
file_path=filepath,
file_mtime=os.path.getmtime(filepath)
)
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
line = line.strip()
if not line or line[0] != '{':
continue
try:
data = json.loads(line)
except json.JSONDecodeError:
continue
if data.get('type') == 'session_meta':
p = data.get('payload', {})
s.cwd = p.get('cwd')
s.session_id = p.get('id', s.session_id)
continue
if data.get('type') != 'response_item':
continue
payload = data.get('payload', {})
if payload.get('type') != 'message':
continue
role = payload.get('role')
if role not in ('user', 'assistant'):
continue
parts = [item.get('text', '') for item in payload.get('content', [])
if isinstance(item, dict) and item.get('type') in ('input_text', 'output_text', 'text')]
if parts:
s.messages.append({
'time': data.get('timestamp', ''),
'role': 'user' if role == 'user' else 'ai',
'content': ' '.join(parts)
})
return s
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import glob
import json
from .base import BaseParser, SessionData
class GeminiParser(BaseParser):
def find_files(self, paths: list) -> list:
files = []
for base in paths:
if os.path.exists(base):
files.extend(glob.glob(os.path.join(base, "*", "chats", "*.json")))
return files
def parse_file(self, filepath: str) -> SessionData:
s = SessionData(
session_id=os.path.basename(filepath).replace('.json', ''),
source='gemini',
file_path=filepath,
file_mtime=os.path.getmtime(filepath)
)
with open(filepath, 'r', encoding='utf-8') as f:
data = json.load(f)
s.session_id = data.get('sessionId', s.session_id)
for msg in data.get('messages', []):
if msg.get('type') not in ('user', 'gemini'):
continue
content = msg.get('content', '')
if content:
s.messages.append({
'time': msg.get('timestamp', ''),
'role': 'user' if msg.get('type') == 'user' else 'ai',
'content': content
})
return s
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Kiro CLI 解析器 - 从 SQLite 数据库读取"""
import os
import json
import sqlite3
import hashlib
from datetime import datetime
from .base import BaseParser, SessionData
KIRO_DB = os.path.expanduser("~/.local/share/kiro-cli/data.sqlite3")
class KiroParser(BaseParser):
def find_files(self, paths: list) -> list:
"""返回数据库路径(如果存在)"""
if os.path.exists(KIRO_DB):
return [KIRO_DB]
return []
def parse_file(self, filepath: str) -> list:
"""解析 Kiro SQLite 数据库,返回多个 SessionData"""
sessions = []
file_mtime = os.path.getmtime(filepath)
conn = sqlite3.connect(filepath)
for row in conn.execute('SELECT key, value FROM conversations'):
cwd, value = row
try:
data = json.loads(value)
except json.JSONDecodeError:
continue
conv_id = data.get('conversation_id', hashlib.md5(cwd.encode()).hexdigest()[:12])
history = data.get('history', [])
messages = []
for item in history:
# 用户消息
if 'user' in item:
user = item['user']
content = user.get('content', {})
if isinstance(content, dict) and 'Prompt' in content:
prompt = content['Prompt'].get('prompt', '')
if prompt:
messages.append({
'time': '',
'role': 'user',
'content': prompt
})
# AI 回复
if 'assistant' in item:
assistant = item['assistant']
content = assistant.get('content', {})
if isinstance(content, dict) and 'Message' in content:
msg = content['Message'].get('message', '')
if msg:
messages.append({
'time': '',
'role': 'ai',
'content': msg
})
if messages:
sessions.append(SessionData(
session_id=f'kiro-{conv_id[:12]}',
source='kiro',
file_path=f'kiro:{conv_id}',
file_mtime=file_mtime,
cwd=cwd,
messages=messages
))
conn.close()
return sessions