@liuyoumi/codex-history 0.5.0 → 0.6.0
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 +12 -0
- package/README.en.md +7 -5
- package/README.md +7 -5
- package/dist/cli.js +25 -49
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0 - 2026-07-13
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Replace typed short-id and command-phrase purge confirmations with a default-no `[y/N]` prompt. Only explicit `y` or `yes` proceeds.
|
|
8
|
+
|
|
9
|
+
## 0.5.1 - 2026-05-30
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Remove prefixed global-state keys that contain a purged thread id, such as composer draft keys stored as `local:<threadId>`.
|
|
14
|
+
|
|
3
15
|
## 0.5.0 - 2026-05-29
|
|
4
16
|
|
|
5
17
|
### Changed
|
package/README.en.md
CHANGED
|
@@ -57,7 +57,7 @@ codex-history purge --grep "Astro"
|
|
|
57
57
|
codex-history purge-orphans
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
`purge` prints the resolved conversation
|
|
60
|
+
`purge` prints the resolved conversation, then shows a default-no `[y/N]` prompt. It deletes only after an explicit `y` or `yes`; pressing Enter, entering `n`, or entering anything else cancels:
|
|
61
61
|
|
|
62
62
|
```text
|
|
63
63
|
About to purge this local Codex conversation:
|
|
@@ -68,7 +68,7 @@ cwd: /Users/me/Projects/example
|
|
|
68
68
|
updated: 2026-05-28T03:16:01.959Z
|
|
69
69
|
|
|
70
70
|
This cannot be undone.
|
|
71
|
-
|
|
71
|
+
Proceed with permanent deletion? [y/N]:
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
## Commands
|
|
@@ -147,7 +147,7 @@ codex-history purge 019e6885
|
|
|
147
147
|
codex-history purge 019e6885 019e6874
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
-
When multiple targets are provided, the tool resolves all targets and prints a batch plan before deletion. If any id is missing, ambiguous, or active, the whole batch is refused before mutation. Interactive batch purge
|
|
150
|
+
When multiple targets are provided, the tool resolves all targets and prints a batch plan before deletion. If any id is missing, ambiguous, or active, the whole batch is refused before mutation. Interactive batch purge uses the same default-no `[y/N]` prompt and proceeds only after `y` or `yes`.
|
|
151
151
|
|
|
152
152
|
You can also select local conversations with filters:
|
|
153
153
|
|
|
@@ -158,7 +158,7 @@ codex-history purge --archived
|
|
|
158
158
|
codex-history purge --cwd /Users/me/Projects/example --grep "Astro"
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
-
`--cwd`, `--grep`, and `--archived` can be combined. Combined filters delete only conversations that match every provided condition. `--cwd` matches working-directory path fragments, while `--grep` matches only title, first user message, and preview. By default, filter mode matches non-archived conversations only; use `--archived` to target archived conversations. Filtered purge prints a batch plan and matched cwd values, then
|
|
161
|
+
`--cwd`, `--grep`, and `--archived` can be combined. Combined filters delete only conversations that match every provided condition. `--cwd` matches working-directory path fragments, while `--grep` matches only title, first user message, and preview. By default, filter mode matches non-archived conversations only; use `--archived` to target archived conversations. Filtered purge prints a batch plan and matched cwd values, then uses the default-no `[y/N]` prompt. Filters cannot be combined with explicit ids.
|
|
162
162
|
|
|
163
163
|
For scripts or non-interactive shells, use `--force`:
|
|
164
164
|
|
|
@@ -170,6 +170,8 @@ codex-history purge --cwd /Users/me/Projects/example --force
|
|
|
170
170
|
|
|
171
171
|
`--force` skips only interactive confirmation. It still keeps schema validation, active-thread protection, and post-purge verification.
|
|
172
172
|
|
|
173
|
+
During deletion, `.codex-global-state.json` and `.codex-global-state.json.bak` are structurally rewritten to remove global-state keys that contain the target thread id, such as composer draft keys stored as `local:<threadId>`.
|
|
174
|
+
|
|
173
175
|
### `purge-orphans`
|
|
174
176
|
|
|
175
177
|
Remove orphaned local data: conversations whose `state_5.sqlite.threads.rollout_path` points to a missing session or archived session file, and logs-only records that remain in `logs_2.sqlite.logs` without a matching thread in the `threads` table.
|
|
@@ -178,7 +180,7 @@ Remove orphaned local data: conversations whose `state_5.sqlite.threads.rollout_
|
|
|
178
180
|
codex-history purge-orphans
|
|
179
181
|
```
|
|
180
182
|
|
|
181
|
-
The command first prints a cleanup plan, affected SQLite row counts, files to delete, and an estimated local disk space impact.
|
|
183
|
+
The command first prints a cleanup plan, affected SQLite row counts, files to delete, and an estimated local disk space impact. It then shows the default-no `[y/N]` prompt and executes only after `y` or `yes`.
|
|
182
184
|
|
|
183
185
|
For scripts or non-interactive shells, use `--force`:
|
|
184
186
|
|
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ codex-history purge --grep "Astro"
|
|
|
57
57
|
codex-history purge-orphans
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
`purge`
|
|
60
|
+
`purge` 会先展示解析到的对话信息,再显示默认拒绝的 `[y/N]` 确认提示。只有显式输入 `y` 或 `yes` 才会删除;直接回车、输入 `n` 或任何其他内容都会取消:
|
|
61
61
|
|
|
62
62
|
```text
|
|
63
63
|
About to purge this local Codex conversation:
|
|
@@ -68,7 +68,7 @@ cwd: /Users/me/Projects/example
|
|
|
68
68
|
updated: 2026-05-28T03:16:01.959Z
|
|
69
69
|
|
|
70
70
|
This cannot be undone.
|
|
71
|
-
|
|
71
|
+
Proceed with permanent deletion? [y/N]:
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
## 命令
|
|
@@ -147,7 +147,7 @@ codex-history purge 019e6885
|
|
|
147
147
|
codex-history purge 019e6885 019e6874
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
-
删除多个目标时,工具会先解析全部目标并展示批量计划;只要任意一个 id 不存在、不唯一,或命中 active thread
|
|
150
|
+
删除多个目标时,工具会先解析全部目标并展示批量计划;只要任意一个 id 不存在、不唯一,或命中 active thread,整个批量操作都会拒绝执行,不会部分删除。交互式批量删除同样使用默认拒绝的 `[y/N]` 确认提示,只有输入 `y` 或 `yes` 才会继续。
|
|
151
151
|
|
|
152
152
|
也可以直接用过滤条件选择要删除的本地对话:
|
|
153
153
|
|
|
@@ -158,7 +158,7 @@ codex-history purge --archived
|
|
|
158
158
|
codex-history purge --cwd /Users/me/Projects/example --grep "Astro"
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
-
`--cwd`、`--grep`、`--archived` 可以组合,组合后只删除同时满足全部条件的对话。`--cwd` 会匹配工作目录路径片段,`--grep` 只匹配标题、首条用户消息和预览。默认只匹配未归档对话;如果要删除已归档对话,请使用 `--archived`。过滤式删除会展示批量计划和匹配到的 cwd
|
|
161
|
+
`--cwd`、`--grep`、`--archived` 可以组合,组合后只删除同时满足全部条件的对话。`--cwd` 会匹配工作目录路径片段,`--grep` 只匹配标题、首条用户消息和预览。默认只匹配未归档对话;如果要删除已归档对话,请使用 `--archived`。过滤式删除会展示批量计划和匹配到的 cwd,并使用默认拒绝的 `[y/N]` 确认提示。过滤条件不能和 id 同时使用。
|
|
162
162
|
|
|
163
163
|
脚本或非交互环境可以使用 `--force`:
|
|
164
164
|
|
|
@@ -170,6 +170,8 @@ codex-history purge --cwd /Users/me/Projects/example --force
|
|
|
170
170
|
|
|
171
171
|
`--force` 只跳过交互确认,不会跳过数据结构校验、active thread 保护和删除后的验证。
|
|
172
172
|
|
|
173
|
+
删除时会结构化重写 `.codex-global-state.json` 和 `.codex-global-state.json.bak`,移除包含目标 thread id 的全局状态键,例如输入草稿缓存里的 `local:<threadId>`。
|
|
174
|
+
|
|
173
175
|
### `purge-orphans`
|
|
174
176
|
|
|
175
177
|
清理本地孤儿数据:包括 `state_5.sqlite.threads.rollout_path` 指向的 session / archived session 文件已经不存在的对话,以及只残留在 `logs_2.sqlite.logs` 中、但 `threads` 表里已经没有对应 thread 的日志记录。
|
|
@@ -178,7 +180,7 @@ codex-history purge --cwd /Users/me/Projects/example --force
|
|
|
178
180
|
codex-history purge-orphans
|
|
179
181
|
```
|
|
180
182
|
|
|
181
|
-
该命令会先展示清理计划、受影响的 SQLite
|
|
183
|
+
该命令会先展示清理计划、受影响的 SQLite 行数、将删除的文件数量,以及估算的本地磁盘空间影响。随后会显示默认拒绝的 `[y/N]` 确认提示;只有输入 `y` 或 `yes` 才会执行。
|
|
182
184
|
|
|
183
185
|
脚本或非交互环境可以使用 `--force`:
|
|
184
186
|
|
package/dist/cli.js
CHANGED
|
@@ -580,7 +580,7 @@ function removeThreadReferences(value, threadId) {
|
|
|
580
580
|
let changed = false;
|
|
581
581
|
const next = {};
|
|
582
582
|
for (const [key, child] of Object.entries(value)) {
|
|
583
|
-
if (key
|
|
583
|
+
if (key.includes(threadId)) {
|
|
584
584
|
changed = true;
|
|
585
585
|
continue;
|
|
586
586
|
}
|
|
@@ -1105,6 +1105,12 @@ function executePurgeOrphansPlanCommand(paths, plan) {
|
|
|
1105
1105
|
return executePurgeOrphans(paths, plan);
|
|
1106
1106
|
}
|
|
1107
1107
|
|
|
1108
|
+
// src/core/confirmation.ts
|
|
1109
|
+
var affirmativeResponses = /* @__PURE__ */ new Set(["y", "yes"]);
|
|
1110
|
+
function isDestructiveActionConfirmed(answer) {
|
|
1111
|
+
return affirmativeResponses.has(answer.trim().toLowerCase());
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1108
1114
|
// src/core/output.ts
|
|
1109
1115
|
function printOutput(value) {
|
|
1110
1116
|
if (typeof value === "string") {
|
|
@@ -1180,12 +1186,18 @@ program.command("purge").argument("[threadIds...]", "Codex thread id(s) or uniqu
|
|
|
1180
1186
|
}
|
|
1181
1187
|
if (plan.plans.length === 1 && !useBatchConfirmation) {
|
|
1182
1188
|
if (!force) {
|
|
1183
|
-
await
|
|
1189
|
+
await confirmDestructiveAction(
|
|
1190
|
+
"Purge requires an interactive terminal. Use --force to skip confirmation.",
|
|
1191
|
+
formatPurgeConfirmation(plan.plans[0])
|
|
1192
|
+
);
|
|
1184
1193
|
}
|
|
1185
1194
|
return formatPurgeResult(executePurgePlanCommand(paths, plan.plans[0]));
|
|
1186
1195
|
}
|
|
1187
1196
|
if (!force) {
|
|
1188
|
-
await
|
|
1197
|
+
await confirmDestructiveAction(
|
|
1198
|
+
"Batch purge requires an interactive terminal. Use --force to skip confirmation.",
|
|
1199
|
+
formatBatchPurgeConfirmation(plan)
|
|
1200
|
+
);
|
|
1189
1201
|
}
|
|
1190
1202
|
return formatBatchPurgeResult(executeBatchPurgePlanCommand(paths, plan));
|
|
1191
1203
|
})
|
|
@@ -1199,7 +1211,10 @@ program.command("purge-orphans").option("--force", "Skip interactive confirmatio
|
|
|
1199
1211
|
return formatPurgeOrphansPlan(plan);
|
|
1200
1212
|
}
|
|
1201
1213
|
if (!force) {
|
|
1202
|
-
await
|
|
1214
|
+
await confirmDestructiveAction(
|
|
1215
|
+
"purge-orphans requires an interactive terminal. Use --force to skip confirmation.",
|
|
1216
|
+
formatPurgeOrphansConfirmation(plan)
|
|
1217
|
+
);
|
|
1203
1218
|
}
|
|
1204
1219
|
return formatPurgeOrphansResult(executePurgeOrphansPlanCommand(paths, plan));
|
|
1205
1220
|
})
|
|
@@ -1234,58 +1249,19 @@ function parsePretty(value) {
|
|
|
1234
1249
|
function shouldUsePager(options) {
|
|
1235
1250
|
return Boolean(options.limit === void 0 && process.stdout.isTTY);
|
|
1236
1251
|
}
|
|
1237
|
-
async function
|
|
1238
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1239
|
-
throw new SafetyRefusalError("Purge requires an interactive terminal. Use --force to skip confirmation.");
|
|
1240
|
-
}
|
|
1241
|
-
const expected = shortId(plan.target.id);
|
|
1242
|
-
process.stdout.write(formatPurgeConfirmation(plan));
|
|
1243
|
-
const readline = createInterface({
|
|
1244
|
-
input: process.stdin,
|
|
1245
|
-
output: process.stdout
|
|
1246
|
-
});
|
|
1247
|
-
try {
|
|
1248
|
-
const answer = await readline.question(`Type ${colorize("yellow", expected)} to confirm: `);
|
|
1249
|
-
if (answer.trim() !== expected) {
|
|
1250
|
-
throw new SafetyRefusalError("Confirmation did not match. No local Codex data was modified.");
|
|
1251
|
-
}
|
|
1252
|
-
} finally {
|
|
1253
|
-
readline.close();
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
async function confirmBatchPurge(plan) {
|
|
1257
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1258
|
-
throw new SafetyRefusalError("Batch purge requires an interactive terminal. Use --force to skip confirmation.");
|
|
1259
|
-
}
|
|
1260
|
-
const expected = "purge-selected";
|
|
1261
|
-
process.stdout.write(formatBatchPurgeConfirmation(plan));
|
|
1262
|
-
const readline = createInterface({
|
|
1263
|
-
input: process.stdin,
|
|
1264
|
-
output: process.stdout
|
|
1265
|
-
});
|
|
1266
|
-
try {
|
|
1267
|
-
const answer = await readline.question(`Type ${colorize("yellow", expected)} to confirm: `);
|
|
1268
|
-
if (answer.trim() !== expected) {
|
|
1269
|
-
throw new SafetyRefusalError("Confirmation did not match. No local Codex data was modified.");
|
|
1270
|
-
}
|
|
1271
|
-
} finally {
|
|
1272
|
-
readline.close();
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
async function confirmPurgeOrphans(plan) {
|
|
1252
|
+
async function confirmDestructiveAction(terminalRefusalMessage, confirmationOutput) {
|
|
1276
1253
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1277
|
-
throw new SafetyRefusalError(
|
|
1254
|
+
throw new SafetyRefusalError(terminalRefusalMessage);
|
|
1278
1255
|
}
|
|
1279
|
-
|
|
1280
|
-
process.stdout.write(formatPurgeOrphansConfirmation(plan));
|
|
1256
|
+
process.stdout.write(confirmationOutput);
|
|
1281
1257
|
const readline = createInterface({
|
|
1282
1258
|
input: process.stdin,
|
|
1283
1259
|
output: process.stdout
|
|
1284
1260
|
});
|
|
1285
1261
|
try {
|
|
1286
|
-
const answer = await readline.question(`
|
|
1287
|
-
if (answer
|
|
1288
|
-
throw new SafetyRefusalError("Confirmation
|
|
1262
|
+
const answer = await readline.question(`Proceed with permanent deletion? ${colorize("yellow", "[y/N]")}: `);
|
|
1263
|
+
if (!isDestructiveActionConfirmed(answer)) {
|
|
1264
|
+
throw new SafetyRefusalError("Confirmation was declined. No local Codex data was modified.");
|
|
1289
1265
|
}
|
|
1290
1266
|
} finally {
|
|
1291
1267
|
readline.close();
|