@liuyoumi/codex-history 0.3.0 → 0.5.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 +24 -2
- package/README.md +24 -2
- package/dist/cli.js +84 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 - 2026-05-29
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Narrow `--grep` matching to conversation text fields and let `--cwd` match working-directory path fragments.
|
|
8
|
+
|
|
9
|
+
## 0.4.0 - 2026-05-29
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Allow `purge` to select regular conversations with `--cwd`, `--grep`, and `--archived` filters.
|
|
14
|
+
|
|
3
15
|
## 0.3.0 - 2026-05-29
|
|
4
16
|
|
|
5
17
|
### Added
|
package/README.en.md
CHANGED
|
@@ -52,6 +52,8 @@ codex-history list
|
|
|
52
52
|
codex-history list --grep "Astro"
|
|
53
53
|
codex-history purge 019e6885
|
|
54
54
|
codex-history purge 019e6885 019e6874
|
|
55
|
+
codex-history purge --cwd /Users/me/Projects/example
|
|
56
|
+
codex-history purge --grep "Astro"
|
|
55
57
|
codex-history purge-orphans
|
|
56
58
|
```
|
|
57
59
|
|
|
@@ -75,8 +77,10 @@ Type 019e6885 to confirm:
|
|
|
75
77
|
| --- | --- |
|
|
76
78
|
| `codex-history doctor` | Check whether the local Codex data layout is supported. |
|
|
77
79
|
| `codex-history list` | List local conversations. |
|
|
78
|
-
| `codex-history list --grep <keyword>` | Filter conversations by title,
|
|
80
|
+
| `codex-history list --grep <keyword>` | Filter conversations by title, first user message, or preview. |
|
|
79
81
|
| `codex-history purge <id...>` | Remove one or more resolved local conversations after confirmation. |
|
|
82
|
+
| `codex-history purge --cwd <path>` | Remove local conversations whose working directory contains the path fragment after confirmation. |
|
|
83
|
+
| `codex-history purge --grep <keyword>` | Remove local conversations matching title, first user message, or preview after confirmation. |
|
|
80
84
|
| `codex-history purge-orphans` | Remove orphaned local data after confirmation. |
|
|
81
85
|
|
|
82
86
|
### `doctor`
|
|
@@ -108,7 +112,13 @@ Default output is one line per conversation:
|
|
|
108
112
|
019e6874 Review Astro blog visual plan
|
|
109
113
|
```
|
|
110
114
|
|
|
111
|
-
`--grep` filters by displayed title,
|
|
115
|
+
`--grep` filters by displayed title, first user message, and preview. It does not match thread ids or cwd values, and it does not search or print full transcript bodies.
|
|
116
|
+
|
|
117
|
+
`--cwd` filters by a working-directory path fragment, so you can provide either a full path or a project name / partial path:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
codex-history list --cwd codex-history
|
|
121
|
+
```
|
|
112
122
|
|
|
113
123
|
Use the short id shown by `list`, or paste a full thread id, when running `purge`.
|
|
114
124
|
|
|
@@ -139,11 +149,23 @@ codex-history purge 019e6885 019e6874
|
|
|
139
149
|
|
|
140
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 requires typing `purge-selected`.
|
|
141
151
|
|
|
152
|
+
You can also select local conversations with filters:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
codex-history purge --cwd /Users/me/Projects/example
|
|
156
|
+
codex-history purge --grep "Astro"
|
|
157
|
+
codex-history purge --archived
|
|
158
|
+
codex-history purge --cwd /Users/me/Projects/example --grep "Astro"
|
|
159
|
+
```
|
|
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 requires typing `purge-selected`. Filters cannot be combined with explicit ids.
|
|
162
|
+
|
|
142
163
|
For scripts or non-interactive shells, use `--force`:
|
|
143
164
|
|
|
144
165
|
```bash
|
|
145
166
|
codex-history purge 019e6885 --force
|
|
146
167
|
codex-history purge 019e6885 019e6874 --force
|
|
168
|
+
codex-history purge --cwd /Users/me/Projects/example --force
|
|
147
169
|
```
|
|
148
170
|
|
|
149
171
|
`--force` skips only interactive confirmation. It still keeps schema validation, active-thread protection, and post-purge verification.
|
package/README.md
CHANGED
|
@@ -52,6 +52,8 @@ codex-history list
|
|
|
52
52
|
codex-history list --grep "Astro"
|
|
53
53
|
codex-history purge 019e6885
|
|
54
54
|
codex-history purge 019e6885 019e6874
|
|
55
|
+
codex-history purge --cwd /Users/me/Projects/example
|
|
56
|
+
codex-history purge --grep "Astro"
|
|
55
57
|
codex-history purge-orphans
|
|
56
58
|
```
|
|
57
59
|
|
|
@@ -75,8 +77,10 @@ Type 019e6885 to confirm:
|
|
|
75
77
|
| --- | --- |
|
|
76
78
|
| `codex-history doctor` | 检查当前本地 Codex 数据结构是否受支持。 |
|
|
77
79
|
| `codex-history list` | 列出本地对话。 |
|
|
78
|
-
| `codex-history list --grep <keyword>` |
|
|
80
|
+
| `codex-history list --grep <keyword>` | 按标题、首条用户消息或预览过滤对话。 |
|
|
79
81
|
| `codex-history purge <id...>` | 确认后删除一条或多条解析到的本地对话。 |
|
|
82
|
+
| `codex-history purge --cwd <path>` | 确认后删除工作目录路径包含该片段的本地对话。 |
|
|
83
|
+
| `codex-history purge --grep <keyword>` | 确认后删除匹配标题、首条用户消息或预览的本地对话。 |
|
|
80
84
|
| `codex-history purge-orphans` | 确认后清理本地孤儿数据。 |
|
|
81
85
|
|
|
82
86
|
### `doctor`
|
|
@@ -108,7 +112,13 @@ codex-history list --pretty=full
|
|
|
108
112
|
019e6874 评审 Astro 博客视觉方案
|
|
109
113
|
```
|
|
110
114
|
|
|
111
|
-
`--grep`
|
|
115
|
+
`--grep` 会按显示标题、首条用户消息和预览过滤。它不会匹配线程 id 或 cwd,也不会搜索或输出完整对话正文。
|
|
116
|
+
|
|
117
|
+
`--cwd` 会按工作目录路径片段过滤,既可以输入完整路径,也可以输入项目名或部分路径:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
codex-history list --cwd codex-history
|
|
121
|
+
```
|
|
112
122
|
|
|
113
123
|
执行 `purge` 时,可以使用 `list` 里显示的短 id,也可以粘贴完整 thread id。
|
|
114
124
|
|
|
@@ -139,11 +149,23 @@ codex-history purge 019e6885 019e6874
|
|
|
139
149
|
|
|
140
150
|
删除多个目标时,工具会先解析全部目标并展示批量计划;只要任意一个 id 不存在、不唯一,或命中 active thread,整个批量操作都会拒绝执行,不会部分删除。交互式批量删除需要输入 `purge-selected` 确认。
|
|
141
151
|
|
|
152
|
+
也可以直接用过滤条件选择要删除的本地对话:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
codex-history purge --cwd /Users/me/Projects/example
|
|
156
|
+
codex-history purge --grep "Astro"
|
|
157
|
+
codex-history purge --archived
|
|
158
|
+
codex-history purge --cwd /Users/me/Projects/example --grep "Astro"
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`--cwd`、`--grep`、`--archived` 可以组合,组合后只删除同时满足全部条件的对话。`--cwd` 会匹配工作目录路径片段,`--grep` 只匹配标题、首条用户消息和预览。默认只匹配未归档对话;如果要删除已归档对话,请使用 `--archived`。过滤式删除会展示批量计划和匹配到的 cwd,并要求输入 `purge-selected` 确认。过滤条件不能和 id 同时使用。
|
|
162
|
+
|
|
142
163
|
脚本或非交互环境可以使用 `--force`:
|
|
143
164
|
|
|
144
165
|
```bash
|
|
145
166
|
codex-history purge 019e6885 --force
|
|
146
167
|
codex-history purge 019e6885 019e6874 --force
|
|
168
|
+
codex-history purge --cwd /Users/me/Projects/example --force
|
|
147
169
|
```
|
|
148
170
|
|
|
149
171
|
`--force` 只跳过交互确认,不会跳过数据结构校验、active thread 保护和删除后的验证。
|
package/dist/cli.js
CHANGED
|
@@ -290,8 +290,8 @@ function listThreads(paths, options = {}) {
|
|
|
290
290
|
params.push(1);
|
|
291
291
|
}
|
|
292
292
|
if (options.cwd) {
|
|
293
|
-
where.push(
|
|
294
|
-
params.push(options.cwd);
|
|
293
|
+
where.push(`lower(${quoteIdentifier("cwd")}) like lower(?) escape '\\'`);
|
|
294
|
+
params.push(`%${escapeLike(options.cwd)}%`);
|
|
295
295
|
}
|
|
296
296
|
const whereClause = where.length > 0 ? `where ${where.join(" and ")}` : "";
|
|
297
297
|
const hasLimit = typeof limit === "number" && Number.isFinite(limit) && limit > 0;
|
|
@@ -321,9 +321,10 @@ function filterThreads(threads, keyword) {
|
|
|
321
321
|
const normalizedKeyword = keyword.toLocaleLowerCase();
|
|
322
322
|
return threads.filter((thread) => {
|
|
323
323
|
const haystack = [
|
|
324
|
-
thread.id,
|
|
325
324
|
thread.title,
|
|
326
|
-
thread.
|
|
325
|
+
thread.sourceTitle,
|
|
326
|
+
thread.firstUserMessage,
|
|
327
|
+
thread.preview
|
|
327
328
|
].join("\n").toLocaleLowerCase();
|
|
328
329
|
return haystack.includes(normalizedKeyword);
|
|
329
330
|
});
|
|
@@ -853,11 +854,35 @@ function planBatchPurgeCommand(paths, threadIds) {
|
|
|
853
854
|
}
|
|
854
855
|
return {
|
|
855
856
|
mode: "planned",
|
|
857
|
+
source: "explicit",
|
|
856
858
|
requestedCount: threadIds.length,
|
|
857
859
|
plans,
|
|
858
860
|
duplicateInputs
|
|
859
861
|
};
|
|
860
862
|
}
|
|
863
|
+
function planFilteredPurgeCommand(paths, filters) {
|
|
864
|
+
validateSupportedDataModel(paths);
|
|
865
|
+
if (!hasPurgeFilter(filters)) {
|
|
866
|
+
throw new UsageError("Provide thread id(s) or at least one filter such as --cwd, --grep, or --archived.");
|
|
867
|
+
}
|
|
868
|
+
const listOptions = {
|
|
869
|
+
cwd: filters.cwd,
|
|
870
|
+
grep: filters.grep,
|
|
871
|
+
archived: filters.archived
|
|
872
|
+
};
|
|
873
|
+
const targets = listThreads(paths, listOptions);
|
|
874
|
+
return {
|
|
875
|
+
mode: "planned",
|
|
876
|
+
source: "filtered",
|
|
877
|
+
requestedCount: targets.length,
|
|
878
|
+
plans: targets.map((target) => buildPurgePlan(paths, target)),
|
|
879
|
+
filters,
|
|
880
|
+
duplicateInputs: []
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
function hasPurgeFilter(filters) {
|
|
884
|
+
return Boolean(filters.cwd || filters.grep || filters.archived);
|
|
885
|
+
}
|
|
861
886
|
function executePurgePlanCommand(paths, plan) {
|
|
862
887
|
validateSupportedDataModel(paths);
|
|
863
888
|
return executePurge(paths, plan);
|
|
@@ -1115,7 +1140,7 @@ program.command("doctor").description("Check whether the local Codex data model
|
|
|
1115
1140
|
return formatDoctor(report);
|
|
1116
1141
|
})
|
|
1117
1142
|
);
|
|
1118
|
-
program.command("list").description("List local Codex conversations.").option("--limit <number>", "Maximum rows to show", parseInteger).option("--all", "Include archived and non-archived threads").option("--archived", "Show archived threads").option("--cwd <path>", "Filter by
|
|
1143
|
+
program.command("list").description("List local Codex conversations.").option("--limit <number>", "Maximum rows to show", parseInteger).option("--all", "Include archived and non-archived threads").option("--archived", "Show archived threads").option("--cwd <path>", "Filter by working directory path fragment").option("--grep <keyword>", "Filter by title, first user message, or preview keyword").option("--pretty <format>", "Output format: oneline, medium, full", parsePretty, "oneline").action(
|
|
1119
1144
|
(options) => runCommand(
|
|
1120
1145
|
() => formatThreads(
|
|
1121
1146
|
listCommand(currentPaths(), {
|
|
@@ -1130,12 +1155,29 @@ program.command("list").description("List local Codex conversations.").option("-
|
|
|
1130
1155
|
)
|
|
1131
1156
|
)
|
|
1132
1157
|
);
|
|
1133
|
-
program.command("purge").argument("
|
|
1158
|
+
program.command("purge").argument("[threadIds...]", "Codex thread id(s) or unique short id prefix(es) to purge").option("--force", "Skip interactive confirmation").option("--cwd <path>", "Filter conversations by working directory path fragment").option("--grep <keyword>", "Filter conversations by title, first user message, or preview keyword").option("--archived", "Filter archived conversations").description("Purge local Codex conversation(s) after target confirmation.").action(
|
|
1134
1159
|
(threadIds, options) => runCommand(async () => {
|
|
1135
1160
|
const paths = currentPaths();
|
|
1136
|
-
const
|
|
1161
|
+
const requestedThreadIds = threadIds ?? [];
|
|
1162
|
+
const filters = {
|
|
1163
|
+
cwd: options.cwd,
|
|
1164
|
+
grep: options.grep,
|
|
1165
|
+
archived: Boolean(options.archived)
|
|
1166
|
+
};
|
|
1167
|
+
const hasExplicitTargets = requestedThreadIds.length > 0;
|
|
1168
|
+
const hasFilters = hasPurgeFilter(filters);
|
|
1169
|
+
if (hasExplicitTargets && hasFilters) {
|
|
1170
|
+
throw new UsageError("Thread ids cannot be combined with --cwd, --grep, or --archived.");
|
|
1171
|
+
}
|
|
1172
|
+
if (!hasExplicitTargets && !hasFilters) {
|
|
1173
|
+
throw new UsageError("Provide thread id(s) or at least one filter such as --cwd, --grep, or --archived.");
|
|
1174
|
+
}
|
|
1175
|
+
const plan = hasExplicitTargets ? planBatchPurgeCommand(paths, requestedThreadIds) : planFilteredPurgeCommand(paths, filters);
|
|
1137
1176
|
const force = Boolean(options.force);
|
|
1138
|
-
const useBatchConfirmation = plan.requestedCount > 1;
|
|
1177
|
+
const useBatchConfirmation = plan.source === "filtered" || plan.requestedCount > 1;
|
|
1178
|
+
if (plan.plans.length === 0) {
|
|
1179
|
+
return "No matching local Codex conversations found.";
|
|
1180
|
+
}
|
|
1139
1181
|
if (plan.plans.length === 1 && !useBatchConfirmation) {
|
|
1140
1182
|
if (!force) {
|
|
1141
1183
|
await confirmPurge(plan.plans[0]);
|
|
@@ -1343,7 +1385,7 @@ function formatBatchPurgeResult(result) {
|
|
|
1343
1385
|
colorize("green", "Batch purge executed."),
|
|
1344
1386
|
"",
|
|
1345
1387
|
"Summary:",
|
|
1346
|
-
`-
|
|
1388
|
+
`- Selection: ${formatBatchPurgeSelection(result.plan)}`,
|
|
1347
1389
|
`- Unique targets purged: ${result.plan.plans.length}`,
|
|
1348
1390
|
`- Duplicate inputs skipped: ${result.plan.duplicateInputs.length}`,
|
|
1349
1391
|
`- SQLite row changes: ${sqliteChanges.reduce((sum, change) => sum + change.rows, 0)}`,
|
|
@@ -1424,18 +1466,50 @@ function formatBatchPurgeConfirmation(plan) {
|
|
|
1424
1466
|
return [
|
|
1425
1467
|
"About to purge selected local Codex conversations:",
|
|
1426
1468
|
"",
|
|
1427
|
-
`
|
|
1469
|
+
`Selection: ${formatBatchPurgeSelection(plan)}`,
|
|
1428
1470
|
`Unique targets: ${plan.plans.length}`,
|
|
1429
1471
|
`Duplicate inputs skipped: ${plan.duplicateInputs.length}`,
|
|
1430
1472
|
"",
|
|
1431
1473
|
"SQLite row changes:",
|
|
1432
1474
|
...formatSqliteRows(summarizePlannedSqliteRows(plan.plans)),
|
|
1433
1475
|
"",
|
|
1476
|
+
...formatMatchedCwdValues(plan),
|
|
1434
1477
|
...formatBatchPurgeExamples(plan),
|
|
1435
1478
|
colorize("dim", "This cannot be undone."),
|
|
1436
1479
|
""
|
|
1437
1480
|
].join("\n");
|
|
1438
1481
|
}
|
|
1482
|
+
function formatBatchPurgeSelection(plan) {
|
|
1483
|
+
if (plan.source === "explicit") {
|
|
1484
|
+
return `${plan.requestedCount} requested target(s)`;
|
|
1485
|
+
}
|
|
1486
|
+
return formatPurgeFilters(plan.filters ?? {});
|
|
1487
|
+
}
|
|
1488
|
+
function formatPurgeFilters(filters) {
|
|
1489
|
+
const parts = [];
|
|
1490
|
+
if (filters.cwd) {
|
|
1491
|
+
parts.push(`cwd contains "${filters.cwd}"`);
|
|
1492
|
+
}
|
|
1493
|
+
if (filters.grep) {
|
|
1494
|
+
parts.push(`grep="${filters.grep}"`);
|
|
1495
|
+
}
|
|
1496
|
+
if (filters.archived) {
|
|
1497
|
+
parts.push("archived=true");
|
|
1498
|
+
}
|
|
1499
|
+
return parts.length > 0 ? parts.join(", ") : "filtered conversations";
|
|
1500
|
+
}
|
|
1501
|
+
function formatMatchedCwdValues(plan) {
|
|
1502
|
+
if (plan.source !== "filtered" || !plan.filters?.cwd || plan.plans.length === 0) {
|
|
1503
|
+
return [];
|
|
1504
|
+
}
|
|
1505
|
+
const cwdValues = [...new Set(plan.plans.map((item) => item.target.cwd))].sort();
|
|
1506
|
+
const shown = cwdValues.slice(0, 5);
|
|
1507
|
+
const lines = ["Matched cwd values:", ...shown.map((cwd) => `- ${colorize("dim", cwd)}`), ""];
|
|
1508
|
+
if (cwdValues.length > shown.length) {
|
|
1509
|
+
lines.push(`Showing ${shown.length} of ${cwdValues.length} cwd value(s).`, "");
|
|
1510
|
+
}
|
|
1511
|
+
return lines;
|
|
1512
|
+
}
|
|
1439
1513
|
function formatPurgeOrphansConfirmation(plan) {
|
|
1440
1514
|
return [
|
|
1441
1515
|
"About to purge orphaned local Codex data:",
|