-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodex2md.py
More file actions
162 lines (129 loc) · 4.87 KB
/
Copy pathcodex2md.py
File metadata and controls
162 lines (129 loc) · 4.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#!/usr/bin/env python3
"""把 Codex 的 JSONL 对话记录转成可读 Markdown(增量追加)"""
import json, sys, os, glob
from datetime import datetime
CODEX_SESSIONS_DIR = os.path.expanduser('~/.codex/sessions')
RECORDS_DIR = os.path.join(os.getcwd(), 'records') # 写到运行时的当前目录
def get_text_from_content(content):
if isinstance(content, str):
return content
if isinstance(content, list):
parts = []
for block in content:
if isinstance(block, dict):
t = block.get('type', '')
if t in ('input_text', 'output_text', 'text'):
parts.append(block.get('text', ''))
return '\n'.join(p for p in parts if p)
return ''
def state_path(jsonl_path):
basename = os.path.basename(jsonl_path)
parts = basename.replace('.jsonl', '').split('-')
uid = parts[6] if len(parts) > 6 else basename.replace('.jsonl', '')
return os.path.join(RECORDS_DIR, f'.codex-state-{uid}')
def read_offset(jsonl_path):
sp = state_path(jsonl_path)
if os.path.exists(sp):
try:
with open(sp) as f:
return int(f.read().strip().split('\n')[0])
except Exception:
pass
return 0
def write_offset(jsonl_path, offset):
os.makedirs(RECORDS_DIR, exist_ok=True)
with open(state_path(jsonl_path), 'w') as f:
f.write(str(offset) + '\n')
def out_path_for(jsonl_path):
basename = os.path.basename(jsonl_path)
parts = basename.replace('.jsonl', '').split('-')
try:
day = parts[3].split('T')[0]
date = f'{parts[1]}-{parts[2]}-{day}'
uid = parts[6]
except:
date = datetime.fromtimestamp(os.path.getmtime(jsonl_path)).strftime('%Y-%m-%d')
uid = basename[:8]
return os.path.join(RECORDS_DIR, f'codex-{date}-{uid}.md')
def convert(jsonl_path, out_path):
offset = read_offset(jsonl_path)
messages = []
msg_start_offsets = []
current_pos = offset
with open(jsonl_path, 'rb') as f:
f.seek(offset)
for raw in f:
line_start = current_pos
current_pos += len(raw)
try:
obj = json.loads(raw)
except Exception:
continue
role = None
text = ''
ts = obj.get('timestamp', '')
t = obj.get('type', '')
payload = obj.get('payload', {})
if t == 'event_msg' and payload.get('type') == 'user_message':
role = 'user'
text = payload.get('message', '').strip()
elif t == 'response_item':
item_role = payload.get('role', '')
if item_role == 'assistant':
role = 'assistant'
text = get_text_from_content(payload.get('content', ''))
if role and text.strip():
if ts:
try:
dt = datetime.fromisoformat(ts.replace('Z', '+00:00'))
ts = dt.astimezone().strftime('%H:%M:%S')
except:
ts = ''
messages.append((role, ts, text.strip()))
msg_start_offsets.append(line_start)
new_offset = current_pos
if messages and messages[-1][0] == 'user':
new_offset = msg_start_offsets[-1]
messages.pop()
if not messages:
return
lines = []
for role, ts, text in messages:
prefix = f'❯ [{ts}] ' if role == 'user' else f'⏺ [{ts}] '
if not ts:
prefix = '❯ ' if role == 'user' else '⏺ '
lines.append(prefix + text.replace('\n', '\n '))
lines.append('')
os.makedirs(RECORDS_DIR, exist_ok=True)
mode = 'a' if os.path.exists(out_path) else 'w'
with open(out_path, mode) as f:
f.write('\n'.join(lines) + '\n')
write_offset(jsonl_path, new_offset)
print(f'已追加 {len(messages)} 条到: {out_path}')
def catchup():
if not os.path.exists(CODEX_SESSIONS_DIR):
print('未找到 Codex sessions 目录')
return
files = glob.glob(os.path.join(CODEX_SESSIONS_DIR, '**/*.jsonl'), recursive=True)
for jsonl in files:
out = out_path_for(jsonl)
offset = read_offset(jsonl)
if offset < os.path.getsize(jsonl):
convert(jsonl, out)
if __name__ == '__main__':
if '--catchup' in sys.argv:
catchup()
sys.exit(0)
args = [a for a in sys.argv[1:] if not a.startswith('--')]
if args:
jsonl = args[0]
out = args[1] if len(args) > 1 else out_path_for(jsonl)
else:
files = glob.glob(os.path.join(CODEX_SESSIONS_DIR, '**/*.jsonl'), recursive=True)
if not files:
print('没有找到 Codex 对话文件')
sys.exit(1)
files.sort(key=os.path.getmtime, reverse=True)
jsonl = files[0]
out = out_path_for(jsonl)
convert(jsonl, out)