codex-work-receipt 0.9.0 → 0.10.1

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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.1
4
+
5
+ - 修复宽屏 HTML 报告中“更多小票功能”向左覆盖小票主体的问题,展开面板现在与赞助商卡片保持中心对齐并随视口自适应宽度。
6
+ - 中小屏提前切换为单栏文档流,四个功能 Tab 改为两行展示;手机端命令列表改为单列,避免横向遮挡和拥挤。
7
+
8
+ ## 0.10.0
9
+
10
+ - 新增 `--custom-range`、`--from` 和 `--to`,支持自定义完整自然日或精确到时分的统计区间。
11
+ - 自定义自然日继续生成 cwr2 规范事实;精确时间区间使用不参与供销社统计的兼容 cwr1 私人摘要。
12
+ - 新增 `--select-session`、`--select-project` 和 `--project <directory>`,支持交互选择会话、项目或直接按项目目录筛选。
13
+ - 项目仓库地址与路径立即转换为本机加盐的匿名身份,不写入 HTML、历史或微信导入文件。
14
+ - HTML“更多小票功能”扩展为 15 条命令,并按时间范围、会话与项目、自动模式、票仔扩展四个可访问 Tab 展示。
15
+ - 命令 Tab 记忆选择状态,支持键盘切换、桌面双列和移动端横向导航,继续排除在长图导出之外。
16
+ - 修复自动模式合并相邻 Stop 触发时丢失原始触发时间、可能导致跨午夜小票写入错误日期的问题。
17
+
3
18
  ## 0.9.0
4
19
 
5
20
  - 新增缓存命中率、每轮 Token / 输出 Token / 工具调用 / 完成耗时等效率指标。
package/README.en.md CHANGED
@@ -36,7 +36,7 @@ The cwr2 protocol creates stable privacy-safe facts for each session and calenda
36
36
 
37
37
  ## Quickstart
38
38
 
39
- Requires Node.js 20+ and local Codex session records. No clone is required. The first interactive run asks whether to save automatically or use manual-only mode. Manual mode then offers today, the last 3 hours, the last 7 days, this week, or a specific session:
39
+ Requires Node.js 20+ and local Codex session records. No clone is required. The first interactive run asks whether to save automatically or use manual-only mode. Manual mode can generate by time range, a selected session, or a selected project:
40
40
 
41
41
  ```bash
42
42
  npx codex-work-receipt@latest --lang en
@@ -54,9 +54,19 @@ Summarize the last 3 hours:
54
54
  npx codex-work-receipt@latest --hours 3 --lang en
55
55
  ```
56
56
 
57
- “Last N hours” is a rolling summary for private history only. It does not participate in AI Work Cooperative deduplicated accounting. Use today, this week, the last seven days, or a specific session when you want accountable canonical facts.
57
+ “Last N hours” is a rolling summary for private history only. It does not participate in AI Work Cooperative deduplicated accounting. Use today, this week, the last seven days, a specific session, or custom whole calendar dates when you want accountable canonical facts.
58
58
 
59
- HTML, structured data, and the WeChat import file are written to `./codex-work-receipt-output/` by default. The page can download its `.cwr.json` file and save a high-resolution PNG containing only the full receipt and WeChat mini-program code. A “More receipt features” section below the sponsor provides 12 copyable commands for receipt ranges, automatic mode, the Codex skill, and Ticket Buddy. The web controls stay out of the exported image. See the [CLI guide](docs/cli.en.md).
59
+ Interactively choose a session, project, or custom range:
60
+
61
+ ```bash
62
+ npx codex-work-receipt@latest --select-session --lang en
63
+ npx codex-work-receipt@latest --select-project --lang en
64
+ npx codex-work-receipt@latest --custom-range --lang en
65
+ ```
66
+
67
+ Advanced use can pass `--project <directory>`, `--from <start>`, and `--to <end>` directly. Whole calendar-date ranges produce cwr2 canonical facts; exact date-time ranges are private cwr1 summaries.
68
+
69
+ HTML, structured data, and the WeChat import file are written to `./codex-work-receipt-output/` by default. The page can download its `.cwr.json` file and save a high-resolution PNG containing only the full receipt and WeChat mini-program code. “More receipt features” provides 15 copyable commands organized into tabs for time ranges, sessions and projects, automation, and Ticket Buddy extensions. The web controls stay out of the exported image. See the [CLI guide](docs/cli.en.md).
60
70
 
61
71
  ## Automatic saving or manual only
62
72
 
package/README.md CHANGED
@@ -36,7 +36,7 @@ cwr2 协议按“会话 × 自然日”生成稳定的脱敏事实。今日、
36
36
 
37
37
  ## Quickstart
38
38
 
39
- 需要 Node.js 20 或更高版本,并且本机已经使用过 Codex。无需克隆仓库。首次交互式运行会先让你选择“自动保存”或“仅手动”;选择手动后,再选择“今天 / 最近 3 小时 / 近 7 日 / 本周 / 指定会话”:
39
+ 需要 Node.js 20 或更高版本,并且本机已经使用过 Codex。无需克隆仓库。首次交互式运行会先让你选择“自动保存”或“仅手动”;选择手动后,可以按时间、指定会话或指定项目生成小票:
40
40
 
41
41
  ```bash
42
42
  npx codex-work-receipt@latest
@@ -54,9 +54,19 @@ npx codex-work-receipt@latest --today
54
54
  npx codex-work-receipt@latest --hours 3
55
55
  ```
56
56
 
57
- “最近 N 小时”是滚动摘要,只用于查看和保存私人历史,不参与 AI 供销社的去重统计。需要进入供销社时,请生成“今日 / 本周 / 近 7 日 / 指定会话”小票。
57
+ “最近 N 小时”是滚动摘要,只用于查看和保存私人历史,不参与 AI 供销社的去重统计。需要进入供销社时,请生成“今日 / 本周 / 近 7 日 / 指定会话 / 自定义完整自然日”小票。
58
58
 
59
- 网页、结构数据和微信导入文件默认保存在 `./codex-work-receipt-output/`。生成的网页支持一键下载 `.cwr.json`,也可以保存只包含完整小票和微信小程序码的高清长图。赞助商下方的“更多小票功能”还提供 12 条常用命令及复制按钮,帮助用户发现不同统计范围、自动模式、Skill 和票仔。数据二维码和网页功能区不会进入图片。详见 [CLI 使用文档](docs/cli.md)。
59
+ 交互选择会话、项目或自定义区间:
60
+
61
+ ```bash
62
+ npx codex-work-receipt@latest --select-session
63
+ npx codex-work-receipt@latest --select-project
64
+ npx codex-work-receipt@latest --custom-range
65
+ ```
66
+
67
+ 也可以使用 `--project <目录>`、`--from <开始>` 和 `--to <结束>` 直接指定。完整自然日区间生成 cwr2 规范事实;精确到时分的区间属于私人 cwr1 摘要。
68
+
69
+ 网页、结构数据和微信导入文件默认保存在 `./codex-work-receipt-output/`。生成的网页支持一键下载 `.cwr.json`,也可以保存只包含完整小票和微信小程序码的高清长图。赞助商下方的“更多小票功能”提供 15 条可复制命令,并按“时间范围 / 会话与项目 / 自动模式 / 票仔扩展”分 Tab 展示。数据二维码和网页功能区不会进入图片。详见 [CLI 使用文档](docs/cli.md)。
60
70
 
61
71
  ## 自动保存或仅手动
62
72
 
package/docs/cli.en.md CHANGED
@@ -22,7 +22,7 @@ The first interactive run asks you to choose:
22
22
  - `Automatic saving`: quietly refresh today's receipt and `.cwr.json` WeChat import file whenever a Codex turn stops
23
23
  - `Manual only`: generate only when you run the command or ask Ticket Buddy
24
24
 
25
- After choosing manual-only mode, choose all activity today, the last 3 hours, the last 7 calendar days, this week, or a specific recent session. Session choices include their time range, turns, tool calls, and model for identification. Explicit range flags such as `--today` and `--latest` never trigger mode setup.
25
+ After choosing manual-only mode, choose today, the last 3 hours, the last 7 calendar days, this week, a custom range, a recent session, or a recent project. Session choices show their time range, turns, tool calls, and model; project choices show a local project name, latest activity, and session count. Explicit range flags such as `--today` and `--latest` never trigger mode setup.
26
26
 
27
27
  ## Automatic saving
28
28
 
@@ -89,6 +89,38 @@ Monday through now:
89
89
  npx codex-work-receipt@latest --range this-week --lang en
90
90
  ```
91
91
 
92
+ ## Custom ranges, sessions, and projects
93
+
94
+ Interactively choose whole calendar dates or an exact date-time range:
95
+
96
+ ```bash
97
+ npx codex-work-receipt@latest --custom-range --lang en
98
+ ```
99
+
100
+ You can also pass boundaries directly. A date-only end is inclusive; exact date-time input uses the half-open interval `[start, end)`:
101
+
102
+ ```bash
103
+ npx codex-work-receipt@latest --from 2026-07-01 --to 2026-07-15 --lang en
104
+ npx codex-work-receipt@latest --from 2026-07-23T09:00 --to 2026-07-23T18:30 --lang en
105
+ ```
106
+
107
+ Whole calendar dates produce cwr2 canonical facts and can participate in cooperative deduplication. Exact time ranges can cut through a calendar day, so they produce compatible private cwr1 summaries instead.
108
+
109
+ Interactively choose a recent session or project:
110
+
111
+ ```bash
112
+ npx codex-work-receipt@latest --select-session --lang en
113
+ npx codex-work-receipt@latest --select-project --lang en
114
+ ```
115
+
116
+ After selecting a project, choose today, the last 3 hours, the last 7 days, this week, or a custom range. Advanced use can pass a local directory and combine it with any range:
117
+
118
+ ```bash
119
+ npx codex-work-receipt@latest --project ./my-project --today --lang en
120
+ ```
121
+
122
+ Project names appear only in the terminal selector. Repository URLs and paths are immediately replaced with a locally salted anonymous identity and never enter HTML, history, or WeChat import files.
123
+
92
124
  ## Language and themes
93
125
 
94
126
  Generate a Chinese receipt:
@@ -148,11 +180,17 @@ npx codex-work-receipt@latest --latest --lang en --no-open
148
180
 
149
181
  | Option | Description |
150
182
  | --- | --- |
151
- | `--range <name>` | `latest`, `last-hours`, `today`, `last-7-days`, or `this-week` |
183
+ | `--range <name>` | `latest`, `last-hours`, `custom-range`, `today`, `last-7-days`, or `this-week` |
152
184
  | `--hours <number>` | Summarize the last 1-168 hours |
185
+ | `--custom-range` | Interactively choose custom calendar dates or an exact time range |
186
+ | `--from <value>` | Set the custom start date or date-time |
187
+ | `--to <value>` | Set the custom end date or date-time |
153
188
  | `--latest` | Summarize the latest active Codex session; default mode |
154
189
  | `--today` | Summarize activity from today in the selected timezone |
155
190
  | `--session <id>` | Summarize one specific Codex session |
191
+ | `--select-session` | Interactively choose a recent Codex session |
192
+ | `--project <directory>` | Limit the receipt to one local project directory |
193
+ | `--select-project` | Interactively choose a recent project and range |
156
194
  | `--timezone <name>` | Set an IANA timezone such as `Asia/Shanghai` |
157
195
  | `--lang <name>` | `zh-CN` (default) or `en` |
158
196
  | `--theme <name>` | `classic`, `diner`, or `payroll` |
package/docs/cli.md CHANGED
@@ -22,7 +22,7 @@ npx codex-work-receipt@latest
22
22
  - `自动保存`:Codex 每完成一轮工作,就静默刷新今天的小票和 `.cwr.json` 微信导入文件
23
23
  - `仅手动`:只有执行命令或告诉票仔时才生成
24
24
 
25
- 选择仅手动后,命令行会继续让你选择今天全部活动、最近 3 小时、最近 7 个自然日、本周,或最近的某个具体会话。选择具体会话时会展示起止时间、轮次、工具调用和模型,帮助辨认。显式使用 `--today`、`--latest` 等范围参数时不会触发模式问答。
25
+ 选择仅手动后,命令行会继续让你选择今天、最近 3 小时、最近 7 个自然日、本周、自定义区间、具体会话或具体项目。选择会话时会展示起止时间、轮次、工具调用和模型;选择项目时会展示本地项目名、最近活动和会话数量。显式使用 `--today`、`--latest` 等范围参数时不会触发模式问答。
26
26
 
27
27
  ## 自动保存
28
28
 
@@ -89,6 +89,38 @@ npx codex-work-receipt@latest --range last-7-days
89
89
  npx codex-work-receipt@latest --range this-week
90
90
  ```
91
91
 
92
+ ## 自定义区间、会话与项目
93
+
94
+ 交互选择自定义自然日或精确时间区间:
95
+
96
+ ```bash
97
+ npx codex-work-receipt@latest --custom-range
98
+ ```
99
+
100
+ 也可以直接传入边界。日期格式的结束日包含在统计中;时间格式使用半开区间 `[开始, 结束)`:
101
+
102
+ ```bash
103
+ npx codex-work-receipt@latest --from 2026-07-01 --to 2026-07-15
104
+ npx codex-work-receipt@latest --from 2026-07-23T09:00 --to 2026-07-23T18:30
105
+ ```
106
+
107
+ 完整自然日范围生成 cwr2 规范事实,可以参与供销社去重统计。精确到时分的范围可能截断自然日,因此生成兼容的私人 cwr1 摘要,不参与供销社统计。
108
+
109
+ 交互选择最近会话或项目:
110
+
111
+ ```bash
112
+ npx codex-work-receipt@latest --select-session
113
+ npx codex-work-receipt@latest --select-project
114
+ ```
115
+
116
+ 项目选择后可以继续选择今日、最近 3 小时、最近 7 日、本周或自定义区间。高级用法可以通过本地目录直接指定项目,并与任意范围组合:
117
+
118
+ ```bash
119
+ npx codex-work-receipt@latest --project ./my-project --today
120
+ ```
121
+
122
+ 项目名称只用于终端选择;仓库地址或路径会立即转换为本机加盐的匿名项目身份,不会写入 HTML、历史记录或微信导入文件。
123
+
92
124
  ## 语言与主题
93
125
 
94
126
  生成英文小票:
@@ -148,11 +180,17 @@ npx codex-work-receipt@latest --latest --no-open
148
180
 
149
181
  | 参数 | 说明 |
150
182
  | --- | --- |
151
- | `--range <name>` | `latest`、`last-hours`、`today`、`last-7-days` 或 `this-week` |
183
+ | `--range <name>` | `latest`、`last-hours`、`custom-range`、`today`、`last-7-days` 或 `this-week` |
152
184
  | `--hours <number>` | 统计最近 1~168 小时;等价于 `last-hours` 范围 |
185
+ | `--custom-range` | 交互选择自定义日期或精确时间区间 |
186
+ | `--from <value>` | 自定义开始日期或时间 |
187
+ | `--to <value>` | 自定义结束日期或时间 |
153
188
  | `--latest` | 统计最近活跃的 Codex 会话,默认模式 |
154
189
  | `--today` | 汇总指定时区今天发生的 Codex 活动 |
155
190
  | `--session <id>` | 统计指定的 Codex 会话 |
191
+ | `--select-session` | 交互选择最近的 Codex 会话 |
192
+ | `--project <directory>` | 只统计指定的本地项目目录 |
193
+ | `--select-project` | 交互选择最近项目及统计范围 |
156
194
  | `--timezone <name>` | 指定 IANA 时区,例如 `Asia/Shanghai` |
157
195
  | `--lang <name>` | `zh-CN`(默认)或 `en` |
158
196
  | `--theme <name>` | `classic`、`diner` 或 `payroll` |
@@ -38,7 +38,7 @@ After installation, ask Codex:
38
38
 
39
39
  > Create a Chinese receipt for my latest session.
40
40
 
41
- Codex will choose a specific session, the last 1-168 hours, today, the last seven days, or this week, plus the language and theme, then run the CLI and open the generated page. “The last few hours” defaults to three hours.
41
+ Codex will choose a specific session, the current or selected project, a custom date/time range, the last 1-168 hours, today, the last seven days, or this week, plus the language and theme, then run the CLI and open the generated page. “The last few hours” defaults to three hours.
42
42
 
43
43
  If another Codex task is still running and you want the receipt immediately, start a separate local Codex chat. A message sent to the active chat may steer the current run or wait in its queue, depending on Follow-up behavior.
44
44
 
@@ -38,7 +38,7 @@ Skill 会安装到:
38
38
 
39
39
  > 用英文生成刚刚这次工作的 AI 打工小票。
40
40
 
41
- Codex 会根据表达选择单次会话、最近 1~168 小时、今天、近七日或本周,以及语言和主题,然后执行命令并打开生成的网页。“最近几个小时”默认统计最近 3 小时。
41
+ Codex 会根据表达选择单次会话、当前或指定项目、自定义日期/时间、最近 1~168 小时、今天、近七日或本周,以及语言和主题,然后执行命令并打开生成的网页。“最近几个小时”默认统计最近 3 小时。
42
42
 
43
43
  “最近 N 小时”生成的是只用于私人历史的滚动摘要,不进入 AI 供销社;如果用户明确希望参与供销社统计,应改用今日、本周、近七日或指定会话范围。
44
44
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  <p><a href="./data-schema.md">中文</a> · <strong>English</strong> · <a href="../README.en.md">Back to README</a></p>
4
4
 
5
- The full receipt schema is currently version `2`; rolling-hour summaries remain on compatible version `1`. Each run saves both the full local record and a privacy-safe WeChat import file.
5
+ The full receipt schema is currently version `2`; rolling-hour and custom exact-time summaries remain on compatible version `1`. Custom whole-calendar-date ranges use version `2`. Each run saves both the full local record and a privacy-safe WeChat import file.
6
6
 
7
7
  ## Main fields
8
8
 
@@ -10,7 +10,7 @@ The full receipt schema is currently version `2`; rolling-hour summaries remain
10
10
  - `locale`: desktop receipt language, `zh-CN` or `en`
11
11
  - `id`: anonymous ID derived from the metrics snapshot
12
12
  - `generated_at`: generation time
13
- - `source`: data source, selected `scope`, and collector version
13
+ - `source`: data source, selected `scope`, `range_kind`, optional `filter_kind`, and collector version
14
14
  - `period`: actual activity times, timezone, and `range_start_date` / `range_end_date` calendar boundaries
15
15
  - `stats`: turns, messages, tools, Tokens, duration, and detailed local efficiency insights
16
16
  - `presentation`: default theme, language-neutral `work_profile`, localized role, review, and AI work points
@@ -73,7 +73,7 @@ cwr2p.<transferId>.<partIndex>.<partCount>.<totalChecksum>.<partChecksum>.<chunk
73
73
 
74
74
  The mini program validates the prefix and checksum before decompressing and parsing the payload. Future schema versions use the compact `v` field for compatibility.
75
75
 
76
- Compact field `o` explicitly carries `latest`, `session`, `last-hours`, `today`, `last-7-days`, or `this-week`. `d[3]` and `d[4]` carry date boundaries. Today, last-seven-days, and this-week receipts continue to use cwr2 canonical facts. `last-hours` is a rolling window, so it uses the compatible cwr1 summary payload to avoid colliding with session-day fact identities; it stays in private history and does not participate in AI Work Cooperative accounting. The updated mini program can still import older QR codes.
76
+ Compact field `o` explicitly carries `latest`, `session`, `last-hours`, `custom-range`, `today`, `last-7-days`, or `this-week`. `d[0]` / `d[1]` carry exact boundaries and `d[3]` / `d[4]` carry calendar-date boundaries. Today, last-seven-days, this-week, and custom whole-calendar-date receipts use cwr2 canonical facts. `last-hours` and custom exact-time ranges may cut through a calendar day, so they use compatible cwr1 summaries to avoid colliding with session-day fact identities; they stay in private history and do not participate in AI Work Cooperative accounting. Project filtering keeps the selected time scope and transfers only filtered anonymous facts, never the project name, repository URL, or path. The updated mini program can still import older QR codes.
77
77
 
78
78
  When the same Codex session has multiple append-only log revisions, the generator keeps the more complete revision. Fact IDs in a cwr2 manifest must be unique; an identity collision stops QR generation instead of emitting an invalid payload.
79
79
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  <p><strong>中文</strong> · <a href="./data-schema.en.md">English</a> · <a href="../README.md">返回 README</a></p>
4
4
 
5
- 当前完整记录结构版本为 `2`;滚动小时摘要为了兼容继续使用版本 `1`。每次生成都会在本机保存完整结构记录和一份脱敏微信导入文件。
5
+ 当前完整记录结构版本为 `2`;滚动小时和自定义精确时间摘要为了兼容继续使用版本 `1`。自定义完整自然日范围使用版本 `2`。每次生成都会在本机保存完整结构记录和一份脱敏微信导入文件。
6
6
 
7
7
  ## 主要字段
8
8
 
@@ -10,7 +10,7 @@
10
10
  - `locale`:桌面小票语言,支持 `zh-CN` 和 `en`
11
11
  - `id`:根据统计快照生成的匿名 ID
12
12
  - `generated_at`:生成时间
13
- - `source`:数据来源、`scope` 统计范围和采集器版本
13
+ - `source`:数据来源、`scope` 统计范围、`range_kind` 区间精度、可选的 `filter_kind` 和采集器版本
14
14
  - `period`:实际活动起止时间、时区,以及 `range_start_date` / `range_end_date` 自然日边界
15
15
  - `stats`:轮次、消息、工具、Token、时长,以及本地详细效率洞察
16
16
  - `presentation`:默认主题、语言无关的 `work_profile`、本地化工种、点评和 AI 工分
@@ -73,7 +73,7 @@ cwr2p.<transferId>.<partIndex>.<partCount>.<totalChecksum>.<partChecksum>.<chunk
73
73
 
74
74
  小程序应检查前缀和校验值,再解压并解析数据。未来结构升级通过 `v` 字段兼容。
75
75
 
76
- 精简字段 `o` 显式携带 `latest`、`session`、`last-hours`、`today`、`last-7-days` 或 `this-week`;`d[3]` `d[4]` 携带日期边界。今日、近 7 日和本周继续使用 cwr2 canonical facts。`last-hours` 是滚动时间窗,为避免与“会话 × 自然日”事实发生身份冲突,使用兼容的 cwr1 摘要载荷,只进入私人历史、不参与 AI 供销社统计。旧二维码仍可由新版小程序导入。
76
+ 精简字段 `o` 显式携带 `latest`、`session`、`last-hours`、`custom-range`、`today`、`last-7-days` 或 `this-week`;`d[0]` / `d[1]` 携带精确边界,`d[3]` / `d[4]` 携带自然日边界。今日、近 7 日、本周和自定义完整自然日范围使用 cwr2 canonical facts。`last-hours` 与自定义精确时间区间可能截断自然日,为避免与“会话 × 自然日”事实发生身份冲突,使用兼容的 cwr1 摘要载荷,只进入私人历史、不参与 AI 供销社统计。项目筛选沿用所选时间范围,只传输筛选后的匿名事实,不传输项目名称、仓库地址或路径。旧二维码仍可由新版小程序导入。
77
77
 
78
78
  同一 Codex 会话出现多份 append-only 日志修订时,生成器只采用更完整的修订;cwr2 manifest 中的 factId 必须唯一,检测到身份冲突时不会输出可扫码二维码。
79
79
 
@@ -9,9 +9,12 @@ AI Work Receipt reads Codex sessions, computes metrics, and renders receipts loc
9
9
  - `~/.codex/sessions/**/*.jsonl` by default
10
10
  - Event types, timestamps, model names, privacy-safe tool categories, and numerical metadata
11
11
  - Metrics such as turns, tool calls, Tokens, duration, interruptions, cache hit rate, latency percentiles, and hourly distribution
12
+ - A session repository URL or working directory only when the user selects or specifies a project for local grouping
12
13
 
13
14
  Session logs are parsed locally in bounded chunks, one JSONL row at a time. Raw tool names are immediately mapped to stable categories. Prompts, responses, images, raw tool names, tool arguments, commands, and tool output are discarded from the in-memory statistics representation.
14
15
 
16
+ Project filtering groups Git sessions by repository identity and non-Git sessions by working directory. The raw repository URL or path is immediately transformed with a random key that exists only on this computer. A project name appears only in the interactive terminal selector; HTML, full records, history, and WeChat import files do not store the original project name, repository URL, or path.
17
+
15
18
  ## What receipts exclude
16
19
 
17
20
  - Prompts and response text
@@ -23,6 +26,8 @@ Session logs are parsed locally in bounded chunks, one JSONL row at a time. Raw
23
26
 
24
27
  Generated HTML, full JSON, and privacy-safe `.cwr.json` WeChat import files are written to `codex-work-receipt-output/` by default. Deduplicated history is stored under `~/.codex-work-receipt/`. Use `--output` and `--data-dir` to change these locations.
25
28
 
29
+ The first project-filtered run creates a permission-restricted `project-identity.key` in the local data directory. It is used only to create project identities that cannot be correlated across installations.
30
+
26
31
  The runtime does not upload Codex session data to a project server. The initial or updated package download performed by `npx` is a normal package-manager network request and does not contain local session data.
27
32
 
28
33
  If the user actively sends a `.cwr.json` file to a WeChat chat, that file passes through WeChat's file-transfer system. The user can then select it in the companion mini program, preview it, and save it to the anonymous account database for history recovery and canonical deduplication. The file excludes prompts, response text, source code, project paths, file names, and original session IDs. Joining AI Cooperative aggregate statistics remains a separate user choice.
package/docs/privacy.md CHANGED
@@ -9,9 +9,12 @@ AI 打工小票在本机读取 Codex 会话,并在本机完成统计和渲染
9
9
  - 默认读取 `~/.codex/sessions/**/*.jsonl`
10
10
  - 提取事件类型、时间、模型名称、脱敏工具类别和数字统计
11
11
  - 计算轮次、工具调用、Token、时长、中断次数、缓存命中率、延迟分位与小时分布等指标
12
+ - 仅在用户选择或指定项目时读取会话的仓库地址或工作目录,用于本地归组
12
13
 
13
14
  会话日志会在本机分块逐行解析。每行解析后只保留统计所需字段;工具名会立即映射为稳定类别。Prompt、回复、图片、原始工具名、工具参数、命令和工具输出不会保留在内存统计对象中。
14
15
 
16
+ 项目筛选优先按 Git 仓库身份归组,非 Git 项目按工作目录归组。原始仓库地址或路径会立即通过只存在于本机的随机密钥转换为匿名项目身份;项目名称仅出现在交互式终端列表中。HTML、完整记录、历史和微信导入文件均不保存原始项目名称、仓库地址或路径。
17
+
15
18
  ## 不会写入小票什么
16
19
 
17
20
  - Prompt 和回复正文
@@ -23,6 +26,8 @@ AI 打工小票在本机读取 Codex 会话,并在本机完成统计和渲染
23
26
 
24
27
  生成的 HTML、完整 JSON 和脱敏 `.cwr.json` 微信导入文件默认位于当前目录的 `codex-work-receipt-output/`,去重历史保存在 `~/.codex-work-receipt/`。可以使用 `--output` 和 `--data-dir` 修改位置。
25
28
 
29
+ 首次使用项目筛选时会在本地数据目录创建权限受限的 `project-identity.key`,只用于生成不可跨设备关联的项目匿名身份。
30
+
26
31
  运行期间不会把 Codex 会话数据上传到本项目的服务器。通过 `npx` 首次下载或更新 npm 包属于包管理器的正常网络请求,不包含本地会话内容。
27
32
 
28
33
  如果用户主动把 `.cwr.json` 发送到微信聊天,文件会经过微信的文件传输体系。随后用户可在配套小程序中选择该聊天文件、确认内容并保存到匿名账号数据库,用于历史恢复和规范去重。文件不会包含 Prompt、回复正文、代码、项目路径、文件名或原始会话 ID;是否参与 AI 供销社匿名统计仍由用户单独决定。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-work-receipt",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "type": "module",
5
5
  "description": "Generate a privacy-first Codex work receipt from local session metadata.",
6
6
  "author": "a-bai-2026",
@@ -27,7 +27,7 @@
27
27
  "scripts": {
28
28
  "receipt": "node src/cli.mjs",
29
29
  "test": "node --test",
30
- "check": "node --check src/cli.mjs && node --check src/auto-hook.mjs && node --check src/auto-runner.mjs && node --check src/core/auto-mode.mjs && node --check src/core/generator.mjs && node --check src/core/mode-selector.mjs && node --check src/lib/files.mjs && node --check src/core/skill-installer.mjs && node --check src/core/pet-installer.mjs && node --check src/core/range.mjs",
30
+ "check": "node --check src/cli.mjs && node --check src/auto-hook.mjs && node --check src/auto-runner.mjs && node --check src/core/auto-mode.mjs && node --check src/core/generator.mjs && node --check src/core/mode-selector.mjs && node --check src/core/project-identity.mjs && node --check src/core/selector.mjs && node --check src/renderers/html.mjs && node --check src/parsers/codex.mjs && node --check src/lib/files.mjs && node --check src/core/skill-installer.mjs && node --check src/core/pet-installer.mjs && node --check src/core/range.mjs",
31
31
  "prepublishOnly": "npm test && npm run check"
32
32
  },
33
33
  "files": [
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: ai-work-receipt
3
- description: Generate and open a privacy-first local AI work receipt from Codex session metadata. Use when the user asks Codex or 票仔 to create, regenerate, open, or locate an “AI 打工小票”, “工票”, or “AI work receipt” for the latest session, the last few hours, today, the last seven days, or this week, in Chinese or English. Do not use for real invoices, salary calculations, API billing, or non-Codex activity.
3
+ description: Generate and open a privacy-first local AI work receipt from Codex session metadata. Use when the user asks Codex or 票仔 to create, regenerate, open, or locate an “AI 打工小票”, “工票”, or “AI work receipt” for a session, project, custom range, recent hours, today, the last seven days, or this week, in Chinese or English. Do not use for real invoices, salary calculations, API billing, or non-Codex activity.
4
4
  ---
5
5
 
6
6
  # AI 打工小票
@@ -13,17 +13,22 @@ Use the published `codex-work-receipt` CLI as the only statistics and rendering
13
13
  - Use `--hours <N>` for “最近 N 小时”“过去 N 小时” or “近 N 小时”. Accept integers from 1 to 168.
14
14
  - Use `--hours 3` for an unspecified “最近几个小时”“过去几个小时” request.
15
15
  - Use `--hours 12` for “最近半天”.
16
- - Treat `--hours` receipts as rolling summaries for private history only. They do not participate in AI Work Cooperative accounting; if the user asks for an accountable cooperative receipt, use today, this week, the last seven days, or a specific session instead.
16
+ - Treat `--hours` receipts as rolling summaries for private history only. They do not participate in AI Work Cooperative accounting; if the user asks for an accountable cooperative receipt, use today, this week, the last seven days, a specific session, or custom whole calendar dates instead.
17
17
  - Use `--today` for “今天”“今日”“今天全部工作” or a daily summary.
18
18
  - Use `--range last-7-days` for “最近七天”“近 7 日” or a rolling seven-calendar-day summary.
19
19
  - Use `--range this-week` for “本周”“这周” or a Monday-to-now summary.
20
+ - Use `--from YYYY-MM-DD --to YYYY-MM-DD` when the user gives whole start and end dates. The end date is inclusive and the receipt can produce cwr2 canonical facts.
21
+ - Use `--from YYYY-MM-DDTHH:mm --to YYYY-MM-DDTHH:mm` when the user gives exact local times. Explain that the result is a private cwr1 summary and does not participate in cooperative accounting.
22
+ - Use `--custom-range` when the user explicitly wants to enter a custom range interactively.
23
+ - Use `--select-session` when the user wants to choose a recent session interactively. Use `--session <id>` only when an exact session ID is already available from the user or CLI output.
24
+ - Add `--project .` when the user asks for the current project. Use `--select-project` when the user wants to choose among recent projects interactively. Combine a project filter with the requested time range.
20
25
  - Use `--theme classic` for “经典白票” or no specified theme.
21
26
  - Use `--theme diner` for “复古粉票”“粉色小票” or “diner”.
22
27
  - Use `--theme payroll` for “夜班绿票”“绿色小票” or “payroll”.
23
28
  - Add `--no-open` only when the user asks not to open the browser.
24
29
  - Add `--timezone <IANA name>` only when the user explicitly requests another timezone.
25
30
  - Use `--lang en` when the user requests English or asks for the receipt in English. Use `--lang zh-CN` otherwise.
26
- - If the user asks to choose interactively, run the CLI without a range flag so it can show the local selector.
31
+ - If the user asks to choose a range interactively without specifying session or project selection, run the CLI without a range flag so it can show the complete local selector.
27
32
  - If the user asks for an unsupported custom date range, explain the available ranges and do not invent flags.
28
33
  - Treat requests addressed to “票仔” the same as direct AI work receipt requests, for example “票仔,开今天的票”.
29
34
 
@@ -36,11 +41,11 @@ Use the published `codex-work-receipt` CLI as the only statistics and rendering
36
41
  npx --yes codex-work-receipt@latest --latest --lang zh-CN --theme classic
37
42
  ```
38
43
 
39
- Replace only the mode and optional flags according to the request.
44
+ Replace only the mode, optional project filter, and presentation flags according to the request.
40
45
 
41
46
  3. Let the CLI open the generated HTML unless the user requested `--no-open`.
42
47
  4. On success, report both the HTML path and the `.cwr.json` WeChat import file path. Tell the user to send the import file to WeChat File Transfer, open the official mini program, and choose “Import from chat file”. If the CLI reports that a single data QR is available, mention scanning as an optional shortcut; never instruct the user to scan multipart codes.
43
- 5. When `--hours` was used, explicitly state that the receipt is a private rolling summary and will not enter AI Work Cooperative statistics.
48
+ 5. When `--hours` or an exact date-time custom range was used, explicitly state that the receipt is a private summary and will not enter AI Work Cooperative statistics.
44
49
 
45
50
  ## Privacy and failures
46
51
 
@@ -42,7 +42,7 @@ function crossesConfiguredDay(workReceiptHome, left, right) {
42
42
  return dateKey(left, timezone) !== dateKey(right, timezone);
43
43
  }
44
44
 
45
- function acquireLock(lockPath, pendingPath) {
45
+ function acquireLock(lockPath, pendingPath, pendingAt = new Date()) {
46
46
  fs.mkdirSync(path.dirname(lockPath), { recursive: true });
47
47
  try {
48
48
  const descriptor = fs.openSync(lockPath, "wx");
@@ -55,12 +55,12 @@ function acquireLock(lockPath, pendingPath) {
55
55
  const age = Date.now() - fs.statSync(lockPath).mtimeMs;
56
56
  if (age > LOCK_STALE_MS) {
57
57
  fs.rmSync(lockPath, { force: true });
58
- return acquireLock(lockPath, pendingPath);
58
+ return acquireLock(lockPath, pendingPath, pendingAt);
59
59
  }
60
60
  } catch {
61
61
  // Another worker may have released the lock between checks.
62
62
  }
63
- fs.writeFileSync(pendingPath, `${new Date().toISOString()}\n`, "utf8");
63
+ fs.writeFileSync(pendingPath, `${pendingAt.toISOString()}\n`, "utf8");
64
64
  return false;
65
65
  }
66
66
  }
@@ -124,7 +124,7 @@ async function generateOnce({ workReceiptHome, now = new Date() }) {
124
124
  export async function runAutomaticReceipt({ workReceiptHome, now = new Date(), retryAfterCoalesce = true }) {
125
125
  const lockPath = path.join(workReceiptHome, "auto.lock");
126
126
  const pendingPath = path.join(workReceiptHome, "auto.pending");
127
- if (!acquireLock(lockPath, pendingPath)) {
127
+ if (!acquireLock(lockPath, pendingPath, now)) {
128
128
  if (!retryAfterCoalesce) return { status: "coalesced" };
129
129
  await delay(800);
130
130
  if (fs.existsSync(lockPath)) return { status: "coalesced" };
package/src/cli.mjs CHANGED
@@ -18,13 +18,19 @@ import {
18
18
  import { generateReceipt } from "./core/generator.mjs";
19
19
  import { promptForGenerationMode } from "./core/mode-selector.mjs";
20
20
  import { printOpenSourcePrompt } from "./core/open-source.mjs";
21
- import { getRollingSummaryNotice, getScopeLabel } from "./core/presentation.mjs";
21
+ import { getCustomSummaryNotice, getRollingSummaryNotice, getScopeLabel } from "./core/presentation.mjs";
22
+ import { getProjectIdentitySecret, projectDescriptorFromPath } from "./core/project-identity.mjs";
22
23
  import { encodeSingleReceiptQr } from "./core/qr-payload.mjs";
23
- import { promptForRange } from "./core/selector.mjs";
24
+ import {
25
+ promptForCustomRange,
26
+ promptForProjectRange,
27
+ promptForRange,
28
+ promptForSpecificSession,
29
+ } from "./core/selector.mjs";
24
30
  import { installCodexSkill } from "./core/skill-installer.mjs";
25
31
  import { installCodexPet, uninstallCodexPet } from "./core/pet-installer.mjs";
26
32
  import { formatNumber } from "./lib/time.mjs";
27
- import { listRecentCodexSessions } from "./parsers/codex.mjs";
33
+ import { listRecentCodexProjects, listRecentCodexSessions } from "./parsers/codex.mjs";
28
34
 
29
35
  const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
30
36
  const PROJECT_DIR = path.dirname(SCRIPT_DIR);
@@ -185,6 +191,49 @@ async function main() {
185
191
  return;
186
192
  }
187
193
 
194
+ if ((options.selectSession || options.selectProject || (options.mode === "custom-range" && !options.from)) && !isInteractive()) {
195
+ throw new Error("交互选择命令需要在终端中运行;也可以使用 --session、--project、--from 和 --to");
196
+ }
197
+
198
+ let projectSecret = null;
199
+ const ensureProjectSecret = () => {
200
+ projectSecret ||= getProjectIdentitySecret({ dataDir: options.dataDir });
201
+ return projectSecret;
202
+ };
203
+ const loadRecentProjects = () => listRecentCodexProjects(10, {
204
+ projectSecret: ensureProjectSecret(),
205
+ });
206
+
207
+ if (options.project) {
208
+ options.projectId = projectDescriptorFromPath(options.project, ensureProjectSecret()).projectId;
209
+ }
210
+ if (options.selectSession) {
211
+ const selected = await promptForSpecificSession({
212
+ locale: options.locale,
213
+ timezone: options.timezone,
214
+ loadRecentSessions: () => listRecentCodexSessions(10),
215
+ });
216
+ options.mode = selected.mode;
217
+ options.sessionId = selected.sessionId;
218
+ options.modeExplicit = true;
219
+ } else if (options.selectProject) {
220
+ const selected = await promptForProjectRange({
221
+ locale: options.locale,
222
+ timezone: options.timezone,
223
+ loadRecentProjects,
224
+ });
225
+ options.mode = selected.mode;
226
+ options.projectId = selected.projectId;
227
+ options.hours = selected.hours || options.hours;
228
+ options.from = selected.from || options.from;
229
+ options.to = selected.to || options.to;
230
+ options.modeExplicit = true;
231
+ } else if (options.mode === "custom-range" && !options.from) {
232
+ const selected = await promptForCustomRange({ locale: options.locale, timezone: options.timezone });
233
+ options.from = selected.from;
234
+ options.to = selected.to;
235
+ }
236
+
188
237
  if (!options.modeExplicit && isInteractive()) {
189
238
  const workReceiptHome = getWorkReceiptHome({ dataDir: options.dataDir });
190
239
  if (!readAutoConfig({ workReceiptHome })) {
@@ -199,10 +248,14 @@ async function main() {
199
248
  locale: options.locale,
200
249
  timezone: options.timezone,
201
250
  loadRecentSessions: () => listRecentCodexSessions(10),
251
+ loadRecentProjects,
202
252
  });
203
253
  options.mode = selected.mode;
204
254
  options.sessionId = selected.sessionId;
205
255
  options.hours = selected.hours || options.hours;
256
+ options.projectId = selected.projectId || options.projectId;
257
+ options.from = selected.from || options.from;
258
+ options.to = selected.to || options.to;
206
259
  }
207
260
 
208
261
  const generated = await generateReceipt(options, {
@@ -235,7 +288,7 @@ async function main() {
235
288
  console.log(`Structured data: ${persisted.companionPath}`);
236
289
  console.log(`WeChat import file: ${persisted.transferPath}`);
237
290
  console.log(`Local history: ${persisted.receiptPath}`);
238
- console.log(`Range: ${getScopeLabel(record.source.scope, options.locale, record.source.hours)} · ${record.stats.session_count} session(s)`);
291
+ console.log(`Range: ${getScopeLabel(record.source.scope, options.locale, record.source.hours, { rangeKind: record.source.range_kind, filterKind: record.source.filter_kind })} · ${record.stats.session_count} session(s)`);
239
292
  console.log(`Stats: ${record.stats.completed_turns} turns · ${formatNumber(record.stats.tokens.total_tokens, options.locale)} Tokens · ${record.stats.tool_calls} tool calls`);
240
293
  console.log(dataQrDataUrl
241
294
  ? `Data QR: available as one code · QR version ${dataQrVersion}`
@@ -246,13 +299,16 @@ async function main() {
246
299
  if (record.source.scope === "last-hours") {
247
300
  console.log(`Note: ${getRollingSummaryNotice(options.locale, record.source.hours)}`);
248
301
  }
302
+ if (record.source.scope === "custom-range" && record.source.range_kind === "exact-time") {
303
+ console.log(`Note: ${getCustomSummaryNotice(options.locale)}`);
304
+ }
249
305
  if (!miniProgramCodeDataUrl) console.log("Mini-program code: not configured; using the explicit placeholder");
250
306
  } else {
251
307
  console.log(`已生成网页:${outputFile}`);
252
308
  console.log(`结构数据:${persisted.companionPath}`);
253
309
  console.log(`微信导入文件:${persisted.transferPath}`);
254
310
  console.log(`本地历史:${persisted.receiptPath}`);
255
- console.log(`统计范围:${getScopeLabel(record.source.scope, options.locale, record.source.hours)} · ${record.stats.session_count} 个会话`);
311
+ console.log(`统计范围:${getScopeLabel(record.source.scope, options.locale, record.source.hours, { rangeKind: record.source.range_kind, filterKind: record.source.filter_kind })} · ${record.stats.session_count} 个会话`);
256
312
  console.log(`统计:${record.stats.completed_turns} 轮 · ${formatNumber(record.stats.tokens.total_tokens, options.locale)} Token · ${record.stats.tool_calls} 次工具调用`);
257
313
  console.log(dataQrDataUrl
258
314
  ? `数据二维码:可用 · 单码 · QR version ${dataQrVersion}`
@@ -263,6 +319,9 @@ async function main() {
263
319
  if (record.source.scope === "last-hours") {
264
320
  console.log(`提示:${getRollingSummaryNotice(options.locale, record.source.hours)}`);
265
321
  }
322
+ if (record.source.scope === "custom-range" && record.source.range_kind === "exact-time") {
323
+ console.log(`提示:${getCustomSummaryNotice(options.locale)}`);
324
+ }
266
325
  if (!miniProgramCodeDataUrl) console.log("小程序码:尚未配置,页面使用明确占位符");
267
326
  }
268
327
  printOpenSourcePrompt("receipt", options.locale);