@liuyoumi/codex-history 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -1
- package/README.en.md +195 -0
- package/README.md +80 -64
- package/dist/cli.js +37 -125
- package/package.json +2 -2
- package/README.zh-CN.md +0 -179
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.3 - 2026-05-29
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Remove purge backup creation and document `purge` as a permanent local deletion flow.
|
|
8
|
+
- Report the CLI version from `package.json` instead of a stale hard-coded value.
|
|
9
|
+
|
|
10
|
+
### Documentation
|
|
11
|
+
|
|
12
|
+
- Refresh the README cover image to show only the terminal window.
|
|
13
|
+
- Add agent workflow guidance for future implementation work.
|
|
14
|
+
- Clarify the boundary between `AGENTS.md` and `docs/agent-workflow.md`.
|
|
15
|
+
|
|
16
|
+
## 0.1.2 - 2026-05-28
|
|
17
|
+
|
|
18
|
+
### Documentation
|
|
19
|
+
|
|
20
|
+
- Use Chinese as the default README language.
|
|
21
|
+
- Add an English README translation.
|
|
22
|
+
- Add README badges and clearer macOS, Windows, and Linux support notes.
|
|
23
|
+
|
|
3
24
|
## 0.1.1 - 2026-05-28
|
|
4
25
|
|
|
5
26
|
### Documentation
|
|
@@ -19,7 +40,7 @@ Initial release candidate.
|
|
|
19
40
|
- `purge <id>` execution by full id or unique short id prefix.
|
|
20
41
|
- Interactive purge confirmation by typing the standard short id.
|
|
21
42
|
- `purge <id> --force` for non-interactive execution.
|
|
22
|
-
- Guarded purge execution with
|
|
43
|
+
- Guarded purge execution with explicit confirmation.
|
|
23
44
|
- SQLite cleanup for supported Codex stores.
|
|
24
45
|
- JSON and JSONL state cleanup for supported Codex files.
|
|
25
46
|
- Rollout jsonl and shell snapshot deletion.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
<h1 align="center">Codex History</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img alt="npm" src="https://img.shields.io/npm/v/@liuyoumi/codex-history?style=flat-square&color=cb9b27" />
|
|
5
|
+
<img alt="platform" src="https://img.shields.io/badge/platform-macOS%20%7C%20Windows%20%7C%20Linux-2ea043?style=flat-square" />
|
|
6
|
+
<img alt="local only" src="https://img.shields.io/badge/local%20data-only-6e7681?style=flat-square" />
|
|
7
|
+
<img alt="confirm" src="https://img.shields.io/badge/confirm-before%20purge-0969da?style=flat-square" />
|
|
8
|
+
<img alt="license" src="https://img.shields.io/npm/l/@liuyoumi/codex-history?style=flat-square" />
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="README.md">中文</a> | <a href="README.en.md">English</a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<p align="center">
|
|
16
|
+
<img src="https://raw.githubusercontent.com/liuyoumi/codex-history/main/assets/cover.png" alt="codex-history cover" width="100%" />
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
A small CLI for finding and removing local Codex conversation history.
|
|
20
|
+
|
|
21
|
+
`codex-history` works on local Codex data files on your machine. It lists conversations using the same short titles shown by Codex when available, lets you narrow the list with `--grep`, and deletes one resolved conversation only after confirmation.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install -g @liuyoumi/codex-history
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or run it without installing:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx @liuyoumi/codex-history doctor
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Supported Platforms
|
|
36
|
+
|
|
37
|
+
`codex-history` has been checked on macOS, Windows, and Linux for install, `doctor`, `list` / `--grep`, and guarded `purge` workflows with confirmation.
|
|
38
|
+
|
|
39
|
+
| Platform | Status |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| macOS | Ready for everyday local history management |
|
|
42
|
+
| Windows | Ready for everyday local history management |
|
|
43
|
+
| Linux | Ready for everyday local history management |
|
|
44
|
+
|
|
45
|
+
Codex local data layouts may change between Codex releases. After updating Codex, run `codex-history doctor` before purging conversations.
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
codex-history doctor
|
|
51
|
+
codex-history list
|
|
52
|
+
codex-history list --grep "Astro"
|
|
53
|
+
codex-history purge 019e6885
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`purge` prints the resolved conversation and asks you to type the standard short id before it deletes anything:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
About to purge this local Codex conversation:
|
|
60
|
+
|
|
61
|
+
title: Implement Astro blog visual audit
|
|
62
|
+
id: 019e6885-b5ae-7ae0-a50d-ce5f75b0ac08
|
|
63
|
+
cwd: /Users/me/Projects/example
|
|
64
|
+
updated: 2026-05-28T03:16:01.959Z
|
|
65
|
+
|
|
66
|
+
This cannot be undone.
|
|
67
|
+
Type 019e6885 to confirm:
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Commands
|
|
71
|
+
|
|
72
|
+
| Command | Description |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `codex-history doctor` | Check whether the local Codex data layout is supported. |
|
|
75
|
+
| `codex-history list` | List local conversations. |
|
|
76
|
+
| `codex-history list --grep <keyword>` | Filter conversations by title, id, or cwd. |
|
|
77
|
+
| `codex-history purge <id>` | Remove one resolved local conversation after confirmation. |
|
|
78
|
+
|
|
79
|
+
### `doctor`
|
|
80
|
+
|
|
81
|
+
Check whether the local Codex data layout is supported by this version.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
codex-history doctor
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Run this first after installing, or after a Codex update. If the local data layout is not supported, destructive commands fail closed instead of guessing how to delete.
|
|
88
|
+
|
|
89
|
+
### `list`
|
|
90
|
+
|
|
91
|
+
List local Codex conversations.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
codex-history list
|
|
95
|
+
codex-history list --grep "Astro"
|
|
96
|
+
codex-history list --limit 20
|
|
97
|
+
codex-history list --pretty=medium
|
|
98
|
+
codex-history list --pretty=full
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Default output is one line per conversation:
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
019e6885 Implement Astro blog visual audit
|
|
105
|
+
019e6874 Review Astro blog visual plan
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`--grep` filters by displayed title, thread id, and cwd. It does not search or print prompt bodies.
|
|
109
|
+
|
|
110
|
+
Use the short id shown by `list`, or paste a full thread id, when running `purge`.
|
|
111
|
+
|
|
112
|
+
By default, `list` shows non-archived conversations. Use `--archived` for archived conversations only, or `--all` for both archived and non-archived conversations.
|
|
113
|
+
|
|
114
|
+
Wrap grep text in quotes when it contains spaces:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
codex-history list --grep "Astro blog"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`--pretty` formats:
|
|
121
|
+
|
|
122
|
+
- `oneline`: short id and title
|
|
123
|
+
- `medium`: full id, updated time, and cwd
|
|
124
|
+
- `full`: `medium` plus created time, archive state, and rollout path
|
|
125
|
+
|
|
126
|
+
When `list` runs in an interactive terminal without `--limit`, output is sent through the system pager. Piped or redirected output skips the pager automatically.
|
|
127
|
+
|
|
128
|
+
### `purge`
|
|
129
|
+
|
|
130
|
+
Remove one local Codex conversation by full id or unique short id prefix.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
codex-history purge 019e6885
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
For scripts or non-interactive shells, use `--force`:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
codex-history purge 019e6885 --force
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`--force` skips only the interactive short-id confirmation. It still keeps schema validation, active-thread protection, and post-purge verification.
|
|
143
|
+
|
|
144
|
+
## Options
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
codex-history --codex-home /path/to/.codex list
|
|
148
|
+
codex-history --json list --grep "Astro"
|
|
149
|
+
codex-history --json purge 019e6885 --force
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
- `--codex-home` defaults to `~/.codex`.
|
|
153
|
+
- `--json` prints machine-readable output. For `purge`, JSON output requires `--force` because interactive confirmation is text-only.
|
|
154
|
+
- Color is enabled only in interactive terminals and respects `NO_COLOR`.
|
|
155
|
+
|
|
156
|
+
## Safety
|
|
157
|
+
|
|
158
|
+
Before deleting, `codex-history`:
|
|
159
|
+
|
|
160
|
+
- validates the supported Codex data model
|
|
161
|
+
- resolves the target to exactly one conversation
|
|
162
|
+
- refuses the currently active thread when detectable
|
|
163
|
+
- removes known references from supported local Codex stores
|
|
164
|
+
- verifies supported stores after mutation
|
|
165
|
+
|
|
166
|
+
This tool only changes local Codex data. It does not delete server-side OpenAI/Codex records, OS backups, terminal scrollback, crash reports, or user-created transcript copies.
|
|
167
|
+
|
|
168
|
+
If Codex Desktop is already showing the conversation you purge, quit or restart Codex before using it again. A running Codex process may still hold the old conversation in memory, and continuing to chat in that old window can write new local data for the same thread.
|
|
169
|
+
|
|
170
|
+
## Q&A
|
|
171
|
+
|
|
172
|
+
### Does this delete server-side Codex data?
|
|
173
|
+
|
|
174
|
+
No. It only modifies supported local files on your machine.
|
|
175
|
+
|
|
176
|
+
### Can I recover a purged conversation?
|
|
177
|
+
|
|
178
|
+
Not with this tool. Treat `purge` as destructive.
|
|
179
|
+
|
|
180
|
+
### Do I need to restart Codex after purging?
|
|
181
|
+
|
|
182
|
+
Recommended, especially for Codex Desktop. `purge` updates local files on disk, but a running Codex process may not refresh its in-memory conversation list immediately. Restart Codex before continuing work.
|
|
183
|
+
|
|
184
|
+
## Development
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
npm install
|
|
188
|
+
npm run typecheck
|
|
189
|
+
npm test
|
|
190
|
+
npm run build
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
MIT
|
package/README.md
CHANGED
|
@@ -1,29 +1,50 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">Codex History</h1>
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img alt="npm" src="https://img.shields.io/npm/v/@liuyoumi/codex-history?style=flat-square&color=cb9b27" />
|
|
5
|
+
<img alt="platform" src="https://img.shields.io/badge/platform-macOS%20%7C%20Windows%20%7C%20Linux-2ea043?style=flat-square" />
|
|
6
|
+
<img alt="local only" src="https://img.shields.io/badge/local%20data-only-6e7681?style=flat-square" />
|
|
7
|
+
<img alt="confirm" src="https://img.shields.io/badge/confirm-before%20purge-0969da?style=flat-square" />
|
|
8
|
+
<img alt="license" src="https://img.shields.io/npm/l/@liuyoumi/codex-history?style=flat-square" />
|
|
9
|
+
</p>
|
|
4
10
|
|
|
5
|
-
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="README.md">中文</a> | <a href="README.en.md">English</a>
|
|
13
|
+
</p>
|
|
6
14
|
|
|
7
|
-
|
|
15
|
+
<p align="center">
|
|
16
|
+
<img src="https://raw.githubusercontent.com/liuyoumi/codex-history/main/assets/cover.png" alt="codex-history 封面" width="100%" />
|
|
17
|
+
</p>
|
|
8
18
|
|
|
9
|
-
|
|
19
|
+
一个用来查找并删除本地 Codex 对话历史的小型命令行工具。
|
|
20
|
+
|
|
21
|
+
`codex-history` 只处理你机器上的本地 Codex 数据。它会尽量使用 Codex 对话列表里显示的短标题,支持用 `--grep` 缩小范围,并且只会在你确认目标后删除一条明确解析出来的对话。
|
|
22
|
+
|
|
23
|
+
## 安装
|
|
10
24
|
|
|
11
25
|
```bash
|
|
12
26
|
npm install -g @liuyoumi/codex-history
|
|
13
27
|
```
|
|
14
28
|
|
|
15
|
-
|
|
29
|
+
也可以不安装直接运行:
|
|
16
30
|
|
|
17
31
|
```bash
|
|
18
32
|
npx @liuyoumi/codex-history doctor
|
|
19
33
|
```
|
|
20
34
|
|
|
21
|
-
##
|
|
35
|
+
## 支持平台
|
|
22
36
|
|
|
23
|
-
- macOS
|
|
24
|
-
|
|
37
|
+
`codex-history` 已在 macOS、Windows、Linux 上完成基础流程验证,包括安装、`doctor`、`list` / `--grep`,以及带确认保护的 `purge`。
|
|
38
|
+
|
|
39
|
+
| 平台 | 状态 |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| macOS | 可用于日常本地历史管理 |
|
|
42
|
+
| Windows | 可用于日常本地历史管理 |
|
|
43
|
+
| Linux | 可用于日常本地历史管理 |
|
|
25
44
|
|
|
26
|
-
|
|
45
|
+
Codex 本地数据结构可能随版本变化。Codex 升级后,建议先运行 `codex-history doctor` 再执行删除操作。
|
|
46
|
+
|
|
47
|
+
## 快速开始
|
|
27
48
|
|
|
28
49
|
```bash
|
|
29
50
|
codex-history doctor
|
|
@@ -32,42 +53,42 @@ codex-history list --grep "Astro"
|
|
|
32
53
|
codex-history purge 019e6885
|
|
33
54
|
```
|
|
34
55
|
|
|
35
|
-
`purge`
|
|
56
|
+
`purge` 会先展示解析到的对话信息,并要求你输入标准短 id,确认后才会删除:
|
|
36
57
|
|
|
37
58
|
```text
|
|
38
59
|
About to purge this local Codex conversation:
|
|
39
60
|
|
|
40
|
-
title:
|
|
61
|
+
title: 实施 Astro 博客视觉审计工具
|
|
41
62
|
id: 019e6885-b5ae-7ae0-a50d-ce5f75b0ac08
|
|
42
63
|
cwd: /Users/me/Projects/example
|
|
43
64
|
updated: 2026-05-28T03:16:01.959Z
|
|
44
65
|
|
|
45
|
-
|
|
66
|
+
This cannot be undone.
|
|
46
67
|
Type 019e6885 to confirm:
|
|
47
68
|
```
|
|
48
69
|
|
|
49
|
-
##
|
|
70
|
+
## 命令
|
|
50
71
|
|
|
51
|
-
|
|
|
72
|
+
| 命令 | 说明 |
|
|
52
73
|
| --- | --- |
|
|
53
|
-
| `codex-history doctor` |
|
|
54
|
-
| `codex-history list` |
|
|
55
|
-
| `codex-history list --grep <keyword>` |
|
|
56
|
-
| `codex-history purge <id>` |
|
|
74
|
+
| `codex-history doctor` | 检查当前本地 Codex 数据结构是否受支持。 |
|
|
75
|
+
| `codex-history list` | 列出本地对话。 |
|
|
76
|
+
| `codex-history list --grep <keyword>` | 按标题、id 或 cwd 过滤对话。 |
|
|
77
|
+
| `codex-history purge <id>` | 确认后删除一条解析到的本地对话。 |
|
|
57
78
|
|
|
58
79
|
### `doctor`
|
|
59
80
|
|
|
60
|
-
|
|
81
|
+
检查当前本地 Codex 数据结构是否被这个版本支持。
|
|
61
82
|
|
|
62
83
|
```bash
|
|
63
84
|
codex-history doctor
|
|
64
85
|
```
|
|
65
86
|
|
|
66
|
-
|
|
87
|
+
建议安装后先跑一次;Codex 更新后也可以再跑一次。如果当前数据结构不受支持,删除命令会拒绝执行,而不是猜测应该怎么删。
|
|
67
88
|
|
|
68
89
|
### `list`
|
|
69
90
|
|
|
70
|
-
|
|
91
|
+
列出本地 Codex 对话。
|
|
71
92
|
|
|
72
93
|
```bash
|
|
73
94
|
codex-history list
|
|
@@ -77,50 +98,50 @@ codex-history list --pretty=medium
|
|
|
77
98
|
codex-history list --pretty=full
|
|
78
99
|
```
|
|
79
100
|
|
|
80
|
-
|
|
101
|
+
默认是一行一条:
|
|
81
102
|
|
|
82
103
|
```text
|
|
83
|
-
019e6885
|
|
84
|
-
019e6874
|
|
104
|
+
019e6885 实施 Astro 博客视觉审计工具
|
|
105
|
+
019e6874 评审 Astro 博客视觉方案
|
|
85
106
|
```
|
|
86
107
|
|
|
87
|
-
`--grep`
|
|
108
|
+
`--grep` 会按显示标题、线程 id、cwd 过滤。它不会搜索或输出对话正文。
|
|
88
109
|
|
|
89
|
-
|
|
110
|
+
执行 `purge` 时,可以使用 `list` 里显示的短 id,也可以粘贴完整 thread id。
|
|
90
111
|
|
|
91
|
-
|
|
112
|
+
默认情况下,`list` 只显示未归档对话。使用 `--archived` 只看已归档对话,使用 `--all` 同时查看已归档和未归档对话。
|
|
92
113
|
|
|
93
|
-
|
|
114
|
+
如果 grep 关键词里有空格,请加引号:
|
|
94
115
|
|
|
95
116
|
```bash
|
|
96
|
-
codex-history list --grep "Astro
|
|
117
|
+
codex-history list --grep "Astro 博客"
|
|
97
118
|
```
|
|
98
119
|
|
|
99
|
-
`--pretty`
|
|
120
|
+
`--pretty` 支持:
|
|
100
121
|
|
|
101
|
-
- `oneline
|
|
102
|
-
- `medium
|
|
103
|
-
- `full
|
|
122
|
+
- `oneline`:短 id 和标题
|
|
123
|
+
- `medium`:完整 id、更新时间、cwd
|
|
124
|
+
- `full`:在 `medium` 基础上增加创建时间、归档状态、rollout 路径
|
|
104
125
|
|
|
105
|
-
|
|
126
|
+
在交互式终端里,如果 `list` 没有加 `--limit`,输出会进入系统分页器。管道或重定向输出会自动跳过分页器。
|
|
106
127
|
|
|
107
128
|
### `purge`
|
|
108
129
|
|
|
109
|
-
|
|
130
|
+
通过完整 id 或唯一短 id 前缀删除一条本地 Codex 对话。
|
|
110
131
|
|
|
111
132
|
```bash
|
|
112
133
|
codex-history purge 019e6885
|
|
113
134
|
```
|
|
114
135
|
|
|
115
|
-
|
|
136
|
+
脚本或非交互环境可以使用 `--force`:
|
|
116
137
|
|
|
117
138
|
```bash
|
|
118
139
|
codex-history purge 019e6885 --force
|
|
119
140
|
```
|
|
120
141
|
|
|
121
|
-
`--force`
|
|
142
|
+
`--force` 只跳过交互式短 id 确认,不会跳过数据结构校验、active thread 保护和删除后的验证。
|
|
122
143
|
|
|
123
|
-
##
|
|
144
|
+
## 选项
|
|
124
145
|
|
|
125
146
|
```bash
|
|
126
147
|
codex-history --codex-home /path/to/.codex list
|
|
@@ -128,44 +149,39 @@ codex-history --json list --grep "Astro"
|
|
|
128
149
|
codex-history --json purge 019e6885 --force
|
|
129
150
|
```
|
|
130
151
|
|
|
131
|
-
- `--codex-home`
|
|
132
|
-
- `--json`
|
|
133
|
-
-
|
|
152
|
+
- `--codex-home` 默认是 `~/.codex`。
|
|
153
|
+
- `--json` 输出机器可读的 JSON。`purge` 使用 JSON 输出时必须加 `--force`,因为交互确认只适合文本模式。
|
|
154
|
+
- 颜色只会在交互式终端中启用,并遵守 `NO_COLOR`。
|
|
134
155
|
|
|
135
|
-
##
|
|
156
|
+
## 安全机制
|
|
136
157
|
|
|
137
|
-
|
|
158
|
+
删除前,`codex-history` 会:
|
|
138
159
|
|
|
139
|
-
-
|
|
140
|
-
-
|
|
141
|
-
-
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
- verifies supported stores after mutation
|
|
160
|
+
- 校验当前 Codex 本地数据结构是否受支持
|
|
161
|
+
- 将目标解析到唯一一条对话
|
|
162
|
+
- 在可检测时拒绝删除当前 active thread
|
|
163
|
+
- 从受支持的本地 Codex 数据中移除已知引用
|
|
164
|
+
- 删除后扫描受支持的数据存储,确认目标引用已经移除
|
|
145
165
|
|
|
146
|
-
|
|
166
|
+
这个工具只处理本地 Codex 数据。它不会删除 OpenAI/Codex 服务端记录、系统备份、终端滚动历史、崩溃报告,或你手动保存过的对话副本。
|
|
147
167
|
|
|
148
|
-
|
|
168
|
+
如果 Codex Desktop 已经打开了你要删除的对话,删除后请先退出或重启 Codex,再继续使用。运行中的 Codex 进程可能仍然在内存里保留旧对话;如果继续在那个旧窗口里聊天,可能会为同一个 thread 重新写入本地数据。
|
|
149
169
|
|
|
150
170
|
## Q&A
|
|
151
171
|
|
|
152
|
-
###
|
|
153
|
-
|
|
154
|
-
No. It only modifies supported local files on your machine.
|
|
155
|
-
|
|
156
|
-
### Where are backups stored?
|
|
172
|
+
### 会删除 Codex 服务端数据吗?
|
|
157
173
|
|
|
158
|
-
|
|
174
|
+
不会。它只修改你机器上受支持的本地文件。
|
|
159
175
|
|
|
160
|
-
###
|
|
176
|
+
### 删除后还能恢复吗?
|
|
161
177
|
|
|
162
|
-
|
|
178
|
+
不能通过这个工具恢复。请把 `purge` 当作破坏性操作对待。
|
|
163
179
|
|
|
164
|
-
###
|
|
180
|
+
### 删除后需要重启 Codex 吗?
|
|
165
181
|
|
|
166
|
-
|
|
182
|
+
建议重启,尤其是 Codex Desktop。`purge` 修改的是磁盘上的本地文件,但正在运行的 Codex 进程可能不会立刻刷新内存里的历史列表。继续工作前,最好先重启 Codex。
|
|
167
183
|
|
|
168
|
-
##
|
|
184
|
+
## 开发
|
|
169
185
|
|
|
170
186
|
```bash
|
|
171
187
|
npm install
|
|
@@ -174,6 +190,6 @@ npm test
|
|
|
174
190
|
npm run build
|
|
175
191
|
```
|
|
176
192
|
|
|
177
|
-
##
|
|
193
|
+
## 许可证
|
|
178
194
|
|
|
179
195
|
MIT
|
package/dist/cli.js
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { spawnSync } from "child_process";
|
|
6
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
6
7
|
import { createInterface } from "readline/promises";
|
|
7
8
|
|
|
8
9
|
// src/core/schema.ts
|
|
9
|
-
import {
|
|
10
|
-
import { dirname } from "path";
|
|
10
|
+
import { readFileSync } from "fs";
|
|
11
11
|
|
|
12
12
|
// src/core/paths.ts
|
|
13
13
|
import { existsSync } from "fs";
|
|
@@ -22,13 +22,10 @@ function expandHome(input) {
|
|
|
22
22
|
}
|
|
23
23
|
return input;
|
|
24
24
|
}
|
|
25
|
-
function resolvePaths(codexHomeInput
|
|
25
|
+
function resolvePaths(codexHomeInput) {
|
|
26
26
|
const codexHome = path.resolve(expandHome(codexHomeInput ?? "~/.codex"));
|
|
27
|
-
const toolHome = path.resolve(expandHome(toolHomeInput ?? process.env.CODEX_HISTORY_HOME ?? "~/.codex-history"));
|
|
28
27
|
return {
|
|
29
28
|
codexHome,
|
|
30
|
-
toolHome,
|
|
31
|
-
backupHome: path.join(toolHome, "backups"),
|
|
32
29
|
stateDb: path.join(codexHome, "state_5.sqlite"),
|
|
33
30
|
logsDb: path.join(codexHome, "logs_2.sqlite"),
|
|
34
31
|
goalsDb: path.join(codexHome, "goals_1.sqlite"),
|
|
@@ -122,18 +119,15 @@ function runDoctor(paths) {
|
|
|
122
119
|
checks.push(checkJsonFile(paths.globalState, "global_state"));
|
|
123
120
|
checks.push(checkJsonFile(paths.globalStateBackup, "global_state_backup"));
|
|
124
121
|
checks.push(checkJsonlFile(paths.sessionIndex, "session_index"));
|
|
125
|
-
checks.push(checkBackupHome(paths.backupHome));
|
|
126
122
|
return {
|
|
127
123
|
supported: !checks.some((check) => check.status === "error"),
|
|
128
124
|
codexHome: paths.codexHome,
|
|
129
125
|
checks
|
|
130
126
|
};
|
|
131
127
|
}
|
|
132
|
-
function validateSupportedDataModel(paths
|
|
128
|
+
function validateSupportedDataModel(paths) {
|
|
133
129
|
const report = runDoctor(paths);
|
|
134
|
-
const errors = report.checks.filter(
|
|
135
|
-
(check) => check.status === "error" && (options.requireBackupHome || check.name !== "backup_home")
|
|
136
|
-
);
|
|
130
|
+
const errors = report.checks.filter((check) => check.status === "error");
|
|
137
131
|
if (errors.length > 0) {
|
|
138
132
|
const details = errors.map((check) => `${check.name}: ${check.detail}`).join("; ");
|
|
139
133
|
throw new Error(`Unsupported Codex data model: ${details}`);
|
|
@@ -255,23 +249,6 @@ function checkJsonlFile(filePath, name) {
|
|
|
255
249
|
};
|
|
256
250
|
}
|
|
257
251
|
}
|
|
258
|
-
function checkBackupHome(backupHome) {
|
|
259
|
-
try {
|
|
260
|
-
mkdirSync(dirname(backupHome), { recursive: true });
|
|
261
|
-
mkdirSync(backupHome, { recursive: true });
|
|
262
|
-
return {
|
|
263
|
-
name: "backup_home",
|
|
264
|
-
status: "ok",
|
|
265
|
-
detail: backupHome
|
|
266
|
-
};
|
|
267
|
-
} catch (error) {
|
|
268
|
-
return {
|
|
269
|
-
name: "backup_home",
|
|
270
|
-
status: "error",
|
|
271
|
-
detail: error instanceof Error ? error.message : String(error)
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
252
|
|
|
276
253
|
// src/commands/doctor.ts
|
|
277
254
|
function doctorCommand(paths) {
|
|
@@ -393,7 +370,7 @@ function listCommand(paths, options = {}) {
|
|
|
393
370
|
}
|
|
394
371
|
|
|
395
372
|
// src/core/executor.ts
|
|
396
|
-
import { existsSync as
|
|
373
|
+
import { existsSync as existsSync8 } from "fs";
|
|
397
374
|
|
|
398
375
|
// src/safety/active-thread.ts
|
|
399
376
|
import { existsSync as existsSync4, statSync } from "fs";
|
|
@@ -450,71 +427,8 @@ function assertThreadIsNotActive(target) {
|
|
|
450
427
|
return checks;
|
|
451
428
|
}
|
|
452
429
|
|
|
453
|
-
// src/safety/backup.ts
|
|
454
|
-
import { copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync2, statSync as statSync2, writeFileSync } from "fs";
|
|
455
|
-
import path2 from "path";
|
|
456
|
-
function createBackup(paths, plan) {
|
|
457
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replaceAll(/[:.]/g, "-");
|
|
458
|
-
const backupDir = path2.join(paths.backupHome, `${timestamp}-${plan.target.id}`);
|
|
459
|
-
mkdirSync2(backupDir, { recursive: true });
|
|
460
|
-
const candidates = /* @__PURE__ */ new Set();
|
|
461
|
-
candidates.add(paths.stateDb);
|
|
462
|
-
addIfPresent(candidates, `${paths.stateDb}-wal`);
|
|
463
|
-
addIfPresent(candidates, `${paths.stateDb}-shm`);
|
|
464
|
-
addIfPresent(candidates, paths.logsDb);
|
|
465
|
-
addIfPresent(candidates, `${paths.logsDb}-wal`);
|
|
466
|
-
addIfPresent(candidates, `${paths.logsDb}-shm`);
|
|
467
|
-
addIfPresent(candidates, paths.goalsDb);
|
|
468
|
-
addIfPresent(candidates, `${paths.goalsDb}-wal`);
|
|
469
|
-
addIfPresent(candidates, `${paths.goalsDb}-shm`);
|
|
470
|
-
addIfPresent(candidates, paths.sessionIndex);
|
|
471
|
-
addIfPresent(candidates, paths.globalState);
|
|
472
|
-
addIfPresent(candidates, paths.globalStateBackup);
|
|
473
|
-
addIfPresent(candidates, plan.target.rolloutPath);
|
|
474
|
-
for (const store of plan.stores) {
|
|
475
|
-
if (store.store === "shell_snapshot") {
|
|
476
|
-
addIfPresent(candidates, store.path);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
const entries = [];
|
|
480
|
-
for (const originalPath of candidates) {
|
|
481
|
-
if (!existsSync5(originalPath)) {
|
|
482
|
-
continue;
|
|
483
|
-
}
|
|
484
|
-
const backupPath = uniqueBackupPath(backupDir, originalPath);
|
|
485
|
-
mkdirSync2(path2.dirname(backupPath), { recursive: true });
|
|
486
|
-
copyFileSync(originalPath, backupPath);
|
|
487
|
-
const stats = statSync2(originalPath);
|
|
488
|
-
entries.push({
|
|
489
|
-
originalPath,
|
|
490
|
-
backupPath,
|
|
491
|
-
size: stats.size,
|
|
492
|
-
mtimeMs: stats.mtimeMs
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
const manifest = {
|
|
496
|
-
threadId: plan.target.id,
|
|
497
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
498
|
-
backupDir,
|
|
499
|
-
entries
|
|
500
|
-
};
|
|
501
|
-
writeFileSync(path2.join(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
502
|
-
return manifest;
|
|
503
|
-
}
|
|
504
|
-
function addIfPresent(paths, filePath) {
|
|
505
|
-
if (existsSync5(filePath)) {
|
|
506
|
-
paths.add(filePath);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
function uniqueBackupPath(backupDir, originalPath) {
|
|
510
|
-
const parsed = path2.parse(originalPath);
|
|
511
|
-
const safeBase = `${parsed.name}${parsed.ext}`.replaceAll(/[^a-zA-Z0-9._-]/g, "_");
|
|
512
|
-
const hash = Buffer.from(originalPath).toString("base64url").slice(0, 12);
|
|
513
|
-
return path2.join(backupDir, `${hash}-${safeBase}`);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
430
|
// src/safety/verify.ts
|
|
517
|
-
import { existsSync as
|
|
431
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
518
432
|
function verifyPurge(paths, plan) {
|
|
519
433
|
const remainingReferences = [];
|
|
520
434
|
const threadId = plan.target.id;
|
|
@@ -524,7 +438,7 @@ function verifyPurge(paths, plan) {
|
|
|
524
438
|
checkTextFile(paths.sessionIndex, "session_index", threadId, remainingReferences);
|
|
525
439
|
checkTextFile(paths.globalState, "global_state", threadId, remainingReferences);
|
|
526
440
|
checkTextFile(paths.globalStateBackup, "global_state_backup", threadId, remainingReferences);
|
|
527
|
-
if (
|
|
441
|
+
if (existsSync5(plan.target.rolloutPath)) {
|
|
528
442
|
remainingReferences.push({
|
|
529
443
|
store: "rollout_jsonl",
|
|
530
444
|
path: plan.target.rolloutPath,
|
|
@@ -532,7 +446,7 @@ function verifyPurge(paths, plan) {
|
|
|
532
446
|
});
|
|
533
447
|
}
|
|
534
448
|
for (const store of plan.stores) {
|
|
535
|
-
if (store.store === "shell_snapshot" &&
|
|
449
|
+
if (store.store === "shell_snapshot" && existsSync5(store.path)) {
|
|
536
450
|
remainingReferences.push({
|
|
537
451
|
store: "shell_snapshot",
|
|
538
452
|
path: store.path,
|
|
@@ -546,7 +460,7 @@ function verifyPurge(paths, plan) {
|
|
|
546
460
|
};
|
|
547
461
|
}
|
|
548
462
|
function checkSqlite(filePath, store, threadId, remainingReferences) {
|
|
549
|
-
if (!
|
|
463
|
+
if (!existsSync5(filePath)) {
|
|
550
464
|
return;
|
|
551
465
|
}
|
|
552
466
|
const db = openReadonlyDatabase(filePath);
|
|
@@ -578,7 +492,7 @@ function checkSqlite(filePath, store, threadId, remainingReferences) {
|
|
|
578
492
|
}
|
|
579
493
|
}
|
|
580
494
|
function checkTextFile(filePath, store, threadId, remainingReferences) {
|
|
581
|
-
if (!
|
|
495
|
+
if (!existsSync5(filePath)) {
|
|
582
496
|
return;
|
|
583
497
|
}
|
|
584
498
|
const content = readFileSync3(filePath, "utf8");
|
|
@@ -592,9 +506,9 @@ function checkTextFile(filePath, store, threadId, remainingReferences) {
|
|
|
592
506
|
}
|
|
593
507
|
|
|
594
508
|
// src/stores/files.ts
|
|
595
|
-
import { existsSync as
|
|
509
|
+
import { existsSync as existsSync6, unlinkSync } from "fs";
|
|
596
510
|
function deleteFileIfExists(filePath) {
|
|
597
|
-
if (!
|
|
511
|
+
if (!existsSync6(filePath)) {
|
|
598
512
|
return {
|
|
599
513
|
path: filePath,
|
|
600
514
|
deleted: false
|
|
@@ -608,9 +522,9 @@ function deleteFileIfExists(filePath) {
|
|
|
608
522
|
}
|
|
609
523
|
|
|
610
524
|
// src/stores/json-state.ts
|
|
611
|
-
import { existsSync as
|
|
525
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
612
526
|
function removeThreadFromSessionIndex(filePath, threadId) {
|
|
613
|
-
if (!
|
|
527
|
+
if (!existsSync7(filePath)) {
|
|
614
528
|
return { path: filePath, changed: false };
|
|
615
529
|
}
|
|
616
530
|
const original = readFileSync4(filePath, "utf8");
|
|
@@ -626,12 +540,12 @@ function removeThreadFromSessionIndex(filePath, threadId) {
|
|
|
626
540
|
` : "";
|
|
627
541
|
const changed = next !== original;
|
|
628
542
|
if (changed) {
|
|
629
|
-
|
|
543
|
+
writeFileSync(filePath, next);
|
|
630
544
|
}
|
|
631
545
|
return { path: filePath, changed };
|
|
632
546
|
}
|
|
633
547
|
function removeThreadFromGlobalState(filePath, threadId) {
|
|
634
|
-
if (!
|
|
548
|
+
if (!existsSync7(filePath)) {
|
|
635
549
|
return { path: filePath, changed: false };
|
|
636
550
|
}
|
|
637
551
|
const original = readFileSync4(filePath, "utf8");
|
|
@@ -639,7 +553,7 @@ function removeThreadFromGlobalState(filePath, threadId) {
|
|
|
639
553
|
const nextValue = removeThreadReferences(parsed, threadId);
|
|
640
554
|
const next = JSON.stringify(nextValue.value, null, 2) + "\n";
|
|
641
555
|
if (nextValue.changed) {
|
|
642
|
-
|
|
556
|
+
writeFileSync(filePath, next);
|
|
643
557
|
}
|
|
644
558
|
return {
|
|
645
559
|
path: filePath,
|
|
@@ -681,7 +595,6 @@ function removeThreadReferences(value, threadId) {
|
|
|
681
595
|
// src/core/executor.ts
|
|
682
596
|
function executePurge(paths, plan) {
|
|
683
597
|
const activeThreadChecks = assertThreadIsNotActive(plan.target);
|
|
684
|
-
const backup = createBackup(paths, plan);
|
|
685
598
|
const sqlite = purgeSqlite(paths, plan.target.id);
|
|
686
599
|
const json = [
|
|
687
600
|
removeThreadFromSessionIndex(paths.sessionIndex, plan.target.id),
|
|
@@ -697,7 +610,6 @@ function executePurge(paths, plan) {
|
|
|
697
610
|
mode: "executed",
|
|
698
611
|
plan,
|
|
699
612
|
activeThreadChecks,
|
|
700
|
-
backup,
|
|
701
613
|
sqlite,
|
|
702
614
|
json,
|
|
703
615
|
files,
|
|
@@ -706,7 +618,7 @@ function executePurge(paths, plan) {
|
|
|
706
618
|
}
|
|
707
619
|
function purgeSqlite(paths, threadId) {
|
|
708
620
|
const results = [];
|
|
709
|
-
if (
|
|
621
|
+
if (existsSync8(paths.stateDb)) {
|
|
710
622
|
const db = openWritableDatabase(paths.stateDb);
|
|
711
623
|
try {
|
|
712
624
|
const transaction = db.transaction(() => {
|
|
@@ -727,7 +639,7 @@ function purgeSqlite(paths, threadId) {
|
|
|
727
639
|
db.close();
|
|
728
640
|
}
|
|
729
641
|
}
|
|
730
|
-
if (
|
|
642
|
+
if (existsSync8(paths.logsDb)) {
|
|
731
643
|
const db = openWritableDatabase(paths.logsDb);
|
|
732
644
|
try {
|
|
733
645
|
const transaction = db.transaction(() => {
|
|
@@ -738,7 +650,7 @@ function purgeSqlite(paths, threadId) {
|
|
|
738
650
|
db.close();
|
|
739
651
|
}
|
|
740
652
|
}
|
|
741
|
-
if (
|
|
653
|
+
if (existsSync8(paths.goalsDb)) {
|
|
742
654
|
const db = openWritableDatabase(paths.goalsDb);
|
|
743
655
|
try {
|
|
744
656
|
const transaction = db.transaction(() => {
|
|
@@ -773,8 +685,8 @@ function purgeFiles(plan) {
|
|
|
773
685
|
}
|
|
774
686
|
|
|
775
687
|
// src/core/planner.ts
|
|
776
|
-
import { existsSync as
|
|
777
|
-
import
|
|
688
|
+
import { existsSync as existsSync9, readdirSync, statSync as statSync2 } from "fs";
|
|
689
|
+
import path2 from "path";
|
|
778
690
|
function resolvePurgeTarget(paths, threadId) {
|
|
779
691
|
if (!threadId.trim()) {
|
|
780
692
|
throw new UsageError("Provide a thread id or unique short id prefix.");
|
|
@@ -801,7 +713,7 @@ function buildPurgePlan(paths, target) {
|
|
|
801
713
|
for (const snapshot of findShellSnapshots(paths.shellSnapshotsDir, target.id)) {
|
|
802
714
|
stores.push(planDeleteFile(snapshot, "shell_snapshot"));
|
|
803
715
|
}
|
|
804
|
-
if (!
|
|
716
|
+
if (!existsSync9(target.rolloutPath)) {
|
|
805
717
|
warnings.push(`rollout file is missing: ${target.rolloutPath}`);
|
|
806
718
|
}
|
|
807
719
|
return {
|
|
@@ -812,7 +724,7 @@ function buildPurgePlan(paths, target) {
|
|
|
812
724
|
};
|
|
813
725
|
}
|
|
814
726
|
function planStateDb(filePath, threadId) {
|
|
815
|
-
if (!
|
|
727
|
+
if (!existsSync9(filePath)) {
|
|
816
728
|
return [missingStore("state_db", filePath)];
|
|
817
729
|
}
|
|
818
730
|
const db = openReadonlyDatabase(filePath);
|
|
@@ -845,7 +757,7 @@ function planStateDb(filePath, threadId) {
|
|
|
845
757
|
}
|
|
846
758
|
}
|
|
847
759
|
function planLogsDb(filePath, threadId) {
|
|
848
|
-
if (!
|
|
760
|
+
if (!existsSync9(filePath)) {
|
|
849
761
|
return [missingStore("logs_db", filePath)];
|
|
850
762
|
}
|
|
851
763
|
const db = openReadonlyDatabase(filePath);
|
|
@@ -856,7 +768,7 @@ function planLogsDb(filePath, threadId) {
|
|
|
856
768
|
}
|
|
857
769
|
}
|
|
858
770
|
function planGoalsDb(filePath, threadId) {
|
|
859
|
-
if (!
|
|
771
|
+
if (!existsSync9(filePath)) {
|
|
860
772
|
return [missingStore("goals_db", filePath)];
|
|
861
773
|
}
|
|
862
774
|
const db = openReadonlyDatabase(filePath);
|
|
@@ -874,7 +786,7 @@ function countTable(db, filePath, tableName, columnName, threadId, storeName) {
|
|
|
874
786
|
path: filePath,
|
|
875
787
|
action: "inspect",
|
|
876
788
|
detail: `table or column unavailable: ${tableName}.${columnName}`,
|
|
877
|
-
exists:
|
|
789
|
+
exists: existsSync9(filePath)
|
|
878
790
|
};
|
|
879
791
|
}
|
|
880
792
|
return countPlan(storeName ?? `state_db.${tableName}`, filePath, count, "delete matching rows");
|
|
@@ -895,7 +807,7 @@ function planRewrite(filePath, store, detail) {
|
|
|
895
807
|
path: filePath,
|
|
896
808
|
action: "rewrite",
|
|
897
809
|
detail,
|
|
898
|
-
exists:
|
|
810
|
+
exists: existsSync9(filePath)
|
|
899
811
|
};
|
|
900
812
|
}
|
|
901
813
|
function planDeleteFile(filePath, store) {
|
|
@@ -904,8 +816,8 @@ function planDeleteFile(filePath, store) {
|
|
|
904
816
|
path: filePath,
|
|
905
817
|
action: "delete_file",
|
|
906
818
|
detail: "delete file",
|
|
907
|
-
exists:
|
|
908
|
-
count:
|
|
819
|
+
exists: existsSync9(filePath),
|
|
820
|
+
count: existsSync9(filePath) ? statSync2(filePath).size : 0
|
|
909
821
|
};
|
|
910
822
|
}
|
|
911
823
|
function missingStore(store, filePath) {
|
|
@@ -918,10 +830,10 @@ function missingStore(store, filePath) {
|
|
|
918
830
|
};
|
|
919
831
|
}
|
|
920
832
|
function findShellSnapshots(shellSnapshotsDir, threadId) {
|
|
921
|
-
if (!
|
|
833
|
+
if (!existsSync9(shellSnapshotsDir)) {
|
|
922
834
|
return [];
|
|
923
835
|
}
|
|
924
|
-
return readdirSync(shellSnapshotsDir).filter((entry) => entry.startsWith(`${threadId}.`) && entry.endsWith(".sh")).map((entry) =>
|
|
836
|
+
return readdirSync(shellSnapshotsDir).filter((entry) => entry.startsWith(`${threadId}.`) && entry.endsWith(".sh")).map((entry) => path2.join(shellSnapshotsDir, entry));
|
|
925
837
|
}
|
|
926
838
|
|
|
927
839
|
// src/commands/purge.ts
|
|
@@ -931,7 +843,7 @@ function planPurgeCommand(paths, threadId) {
|
|
|
931
843
|
return buildPurgePlan(paths, target);
|
|
932
844
|
}
|
|
933
845
|
function executePurgePlanCommand(paths, plan) {
|
|
934
|
-
validateSupportedDataModel(paths
|
|
846
|
+
validateSupportedDataModel(paths);
|
|
935
847
|
return executePurge(paths, plan);
|
|
936
848
|
}
|
|
937
849
|
|
|
@@ -962,8 +874,9 @@ function shortId(id) {
|
|
|
962
874
|
|
|
963
875
|
// src/cli.ts
|
|
964
876
|
var TITLE_MAX_LENGTH = 80;
|
|
877
|
+
var packageVersion = JSON.parse(readFileSync5(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
965
878
|
var program = new Command();
|
|
966
|
-
program.name("codex-history").description("Inspect and safely purge local Codex conversation history.").version(
|
|
879
|
+
program.name("codex-history").description("Inspect and safely purge local Codex conversation history.").version(packageVersion).option("--codex-home <path>", "Path to Codex home directory", "~/.codex").option("--json", "Print machine-readable JSON output");
|
|
967
880
|
program.command("doctor").description("Check whether the local Codex data model is supported.").action(
|
|
968
881
|
() => runCommand(() => {
|
|
969
882
|
const report = doctorCommand(currentPaths());
|
|
@@ -1132,7 +1045,6 @@ function formatPurgeResult(result) {
|
|
|
1132
1045
|
"",
|
|
1133
1046
|
`Target: ${displayTitle(result.plan.target.title)}`,
|
|
1134
1047
|
`Thread id: ${result.plan.target.id}`,
|
|
1135
|
-
`Backup: ${colorize("dim", result.backup.backupDir)}`,
|
|
1136
1048
|
"",
|
|
1137
1049
|
"SQLite changes:",
|
|
1138
1050
|
...result.sqlite.map((change) => `- ${change.store}: ${change.changedRows} row(s)`),
|
|
@@ -1166,7 +1078,7 @@ function formatPurgeConfirmation(plan) {
|
|
|
1166
1078
|
`cwd: ${colorize("dim", plan.target.cwd)}`,
|
|
1167
1079
|
`updated: ${formatDate(plan.target.updatedAtMs)}`,
|
|
1168
1080
|
"",
|
|
1169
|
-
colorize("dim", "
|
|
1081
|
+
colorize("dim", "This cannot be undone."),
|
|
1170
1082
|
""
|
|
1171
1083
|
].join("\n");
|
|
1172
1084
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liuyoumi/codex-history",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "CLI for listing and safely purging local Codex conversation history",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
11
|
"README.md",
|
|
12
|
-
"README.
|
|
12
|
+
"README.en.md",
|
|
13
13
|
"CHANGELOG.md",
|
|
14
14
|
"LICENSE"
|
|
15
15
|
],
|
package/README.zh-CN.md
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
# codex-history
|
|
2
|
-
|
|
3
|
-
[English](README.md) | [简体中文](README.zh-CN.md)
|
|
4
|
-
|
|
5
|
-
一个用来查找并删除本地 Codex 对话历史的小型命令行工具。
|
|
6
|
-
|
|
7
|
-
`codex-history` 只处理你机器上的本地 Codex 数据。它会尽量使用 Codex 对话列表里显示的短标题,支持用 `--grep` 缩小范围,并且只会在你确认目标后删除一条明确解析出来的对话。
|
|
8
|
-
|
|
9
|
-
## 安装
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm install -g @liuyoumi/codex-history
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
也可以不安装直接运行:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npx @liuyoumi/codex-history doctor
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## 支持平台
|
|
22
|
-
|
|
23
|
-
- macOS:v0.1 已验证
|
|
24
|
-
- Windows/Linux:暂未验证
|
|
25
|
-
|
|
26
|
-
## 快速开始
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
codex-history doctor
|
|
30
|
-
codex-history list
|
|
31
|
-
codex-history list --grep "Astro"
|
|
32
|
-
codex-history purge 019e6885
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
`purge` 会先展示解析到的对话信息,并要求你输入标准短 id,确认后才会删除:
|
|
36
|
-
|
|
37
|
-
```text
|
|
38
|
-
About to purge this local Codex conversation:
|
|
39
|
-
|
|
40
|
-
title: 实施 Astro 博客视觉审计工具
|
|
41
|
-
id: 019e6885-b5ae-7ae0-a50d-ce5f75b0ac08
|
|
42
|
-
cwd: /Users/me/Projects/example
|
|
43
|
-
updated: 2026-05-28T03:16:01.959Z
|
|
44
|
-
|
|
45
|
-
A backup will be created before deletion.
|
|
46
|
-
Type 019e6885 to confirm:
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## 命令
|
|
50
|
-
|
|
51
|
-
| 命令 | 说明 |
|
|
52
|
-
| --- | --- |
|
|
53
|
-
| `codex-history doctor` | 检查当前本地 Codex 数据结构是否受支持。 |
|
|
54
|
-
| `codex-history list` | 列出本地对话。 |
|
|
55
|
-
| `codex-history list --grep <keyword>` | 按标题、id 或 cwd 过滤对话。 |
|
|
56
|
-
| `codex-history purge <id>` | 确认后删除一条解析到的本地对话。 |
|
|
57
|
-
|
|
58
|
-
### `doctor`
|
|
59
|
-
|
|
60
|
-
检查当前本地 Codex 数据结构是否被这个版本支持。
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
codex-history doctor
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
建议安装后先跑一次;Codex 更新后也可以再跑一次。如果当前数据结构不受支持,删除命令会拒绝执行,而不是猜测应该怎么删。
|
|
67
|
-
|
|
68
|
-
### `list`
|
|
69
|
-
|
|
70
|
-
列出本地 Codex 对话。
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
codex-history list
|
|
74
|
-
codex-history list --grep "Astro"
|
|
75
|
-
codex-history list --limit 20
|
|
76
|
-
codex-history list --pretty=medium
|
|
77
|
-
codex-history list --pretty=full
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
默认是一行一条:
|
|
81
|
-
|
|
82
|
-
```text
|
|
83
|
-
019e6885 实施 Astro 博客视觉审计工具
|
|
84
|
-
019e6874 评审 Astro 博客视觉方案
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
`--grep` 会按显示标题、线程 id、cwd 过滤。它不会搜索或输出对话正文。
|
|
88
|
-
|
|
89
|
-
执行 `purge` 时,可以使用 `list` 里显示的短 id,也可以粘贴完整 thread id。
|
|
90
|
-
|
|
91
|
-
默认情况下,`list` 只显示未归档对话。使用 `--archived` 只看已归档对话,使用 `--all` 同时查看已归档和未归档对话。
|
|
92
|
-
|
|
93
|
-
如果 grep 关键词里有空格,请加引号:
|
|
94
|
-
|
|
95
|
-
```bash
|
|
96
|
-
codex-history list --grep "Astro 博客"
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
`--pretty` 支持:
|
|
100
|
-
|
|
101
|
-
- `oneline`:短 id 和标题
|
|
102
|
-
- `medium`:完整 id、更新时间、cwd
|
|
103
|
-
- `full`:在 `medium` 基础上增加创建时间、归档状态、rollout 路径
|
|
104
|
-
|
|
105
|
-
在交互式终端里,如果 `list` 没有加 `--limit`,输出会进入系统分页器。管道或重定向输出会自动跳过分页器。
|
|
106
|
-
|
|
107
|
-
### `purge`
|
|
108
|
-
|
|
109
|
-
通过完整 id 或唯一短 id 前缀删除一条本地 Codex 对话。
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
codex-history purge 019e6885
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
脚本或非交互环境可以使用 `--force`:
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
codex-history purge 019e6885 --force
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
`--force` 只跳过交互式短 id 确认,不会跳过数据结构校验、强制备份、active thread 保护和删除后的验证。
|
|
122
|
-
|
|
123
|
-
## 选项
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
codex-history --codex-home /path/to/.codex list
|
|
127
|
-
codex-history --json list --grep "Astro"
|
|
128
|
-
codex-history --json purge 019e6885 --force
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
- `--codex-home` 默认是 `~/.codex`。
|
|
132
|
-
- `--json` 输出机器可读的 JSON。`purge` 使用 JSON 输出时必须加 `--force`,因为交互确认只适合文本模式。
|
|
133
|
-
- 颜色只会在交互式终端中启用,并遵守 `NO_COLOR`。
|
|
134
|
-
|
|
135
|
-
## 安全机制
|
|
136
|
-
|
|
137
|
-
删除前,`codex-history` 会:
|
|
138
|
-
|
|
139
|
-
- 校验当前 Codex 本地数据结构是否受支持
|
|
140
|
-
- 将目标解析到唯一一条对话
|
|
141
|
-
- 在可检测时拒绝删除当前 active thread
|
|
142
|
-
- 在 `~/.codex-history/backups` 下创建强制备份
|
|
143
|
-
- 从受支持的本地 Codex 数据中移除已知引用
|
|
144
|
-
- 删除后扫描受支持的数据存储,确认目标引用已经移除
|
|
145
|
-
|
|
146
|
-
这个工具只处理本地 Codex 数据。它不会删除 OpenAI/Codex 服务端记录、系统备份、终端滚动历史、崩溃报告,或你手动保存过的对话副本。
|
|
147
|
-
|
|
148
|
-
如果 Codex Desktop 已经打开了你要删除的对话,删除后请先退出或重启 Codex,再继续使用。运行中的 Codex 进程可能仍然在内存里保留旧对话;如果继续在那个旧窗口里聊天,可能会为同一个 thread 重新写入本地数据。
|
|
149
|
-
|
|
150
|
-
## Q&A
|
|
151
|
-
|
|
152
|
-
### 会删除 Codex 服务端数据吗?
|
|
153
|
-
|
|
154
|
-
不会。它只修改你机器上受支持的本地文件。
|
|
155
|
-
|
|
156
|
-
### 备份在哪里?
|
|
157
|
-
|
|
158
|
-
备份会写入 `~/.codex-history/backups`。
|
|
159
|
-
|
|
160
|
-
### 删除后还能恢复吗?
|
|
161
|
-
|
|
162
|
-
工具会在删除前创建备份,但 v0.1 还没有自动恢复命令。请把 `purge` 当作破坏性操作对待。
|
|
163
|
-
|
|
164
|
-
### 删除后需要重启 Codex 吗?
|
|
165
|
-
|
|
166
|
-
建议重启,尤其是 Codex Desktop。`purge` 修改的是磁盘上的本地文件,但正在运行的 Codex 进程可能不会立刻刷新内存里的历史列表。继续工作前,最好先重启 Codex。
|
|
167
|
-
|
|
168
|
-
## 开发
|
|
169
|
-
|
|
170
|
-
```bash
|
|
171
|
-
npm install
|
|
172
|
-
npm run typecheck
|
|
173
|
-
npm test
|
|
174
|
-
npm run build
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
## 许可证
|
|
178
|
-
|
|
179
|
-
MIT
|