codex-work-receipt 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 +17 -0
- package/README.en.md +28 -111
- package/README.md +29 -189
- package/docs/cli.en.md +137 -0
- package/docs/cli.md +137 -0
- package/docs/codex-skill.en.md +45 -0
- package/docs/codex-skill.md +45 -0
- package/docs/data-schema.en.md +35 -0
- package/docs/data-schema.md +19 -9
- package/docs/images/social-preview-source.svg +84 -0
- package/docs/images/social-preview.png +0 -0
- package/docs/mobile-import.en.md +19 -0
- package/docs/mobile-import.md +13 -9
- package/docs/privacy.en.md +30 -0
- package/docs/privacy.md +26 -8
- package/package.json +2 -1
- package/skills/ai-work-receipt/SKILL.md +5 -2
- package/src/cli.mjs +23 -4
- package/src/core/args.mjs +35 -4
- package/src/core/metrics.mjs +37 -25
- package/src/core/presentation.mjs +36 -13
- package/src/core/qr-payload.mjs +10 -2
- package/src/core/range.mjs +118 -0
- package/src/core/receipt-record.mjs +10 -3
- package/src/core/selector.mjs +71 -0
- package/src/parsers/codex.mjs +77 -11
- package/src/renderers/html.mjs +191 -40
- package/templates/README.md +3 -4
package/docs/cli.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# CLI 使用
|
|
2
|
+
|
|
3
|
+
<p><strong>中文</strong> · <a href="./cli.en.md">English</a> · <a href="../README.md">返回 README</a></p>
|
|
4
|
+
|
|
5
|
+
## 环境要求
|
|
6
|
+
|
|
7
|
+
- Node.js 20 或更高版本
|
|
8
|
+
- 本机已经使用过 Codex,并存在 `~/.codex/sessions/` 会话记录
|
|
9
|
+
|
|
10
|
+
无需克隆仓库或全局安装。
|
|
11
|
+
|
|
12
|
+
## 交互选择统计范围
|
|
13
|
+
|
|
14
|
+
直接运行:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx codex-work-receipt@latest
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
命令行会让你选择:今天全部活动、最近 7 个自然日、本周,或最近的某个具体会话。选择具体会话时会展示起止时间、轮次、工具调用和模型,帮助辨认。
|
|
21
|
+
|
|
22
|
+
## 非交互统计范围
|
|
23
|
+
|
|
24
|
+
统计最近一次活跃会话,并自动打开小票:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx codex-work-receipt@latest --latest
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
汇总本地时区今天发生的全部 Codex 活动:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx codex-work-receipt@latest --today
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
最近 7 个自然日(含今天):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx codex-work-receipt@latest --range last-7-days
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
本周周一至今:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx codex-work-receipt@latest --range this-week
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 语言与主题
|
|
49
|
+
|
|
50
|
+
生成英文小票:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx codex-work-receipt@latest --latest --lang en
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
选择默认主题:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx codex-work-receipt@latest --latest --theme diner
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
可用主题:
|
|
63
|
+
|
|
64
|
+
- `classic`:经典白票
|
|
65
|
+
- `diner`:复古粉票
|
|
66
|
+
- `payroll`:夜班绿票
|
|
67
|
+
|
|
68
|
+
网页生成后仍然可以即时切换主题,统计口径不会改变。
|
|
69
|
+
|
|
70
|
+
## 输出文件
|
|
71
|
+
|
|
72
|
+
默认输出:
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
./codex-work-receipt-output/
|
|
76
|
+
├── codex-receipt-today-2026-07-18.html
|
|
77
|
+
└── codex-receipt-today-2026-07-18.json
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
默认文件名会携带统计日期范围;最近会话和指定会话则携带短编号,避免不同日期或会话的小票互相覆盖。
|
|
81
|
+
|
|
82
|
+
打开生成的 HTML 后,点击“保存完整长图”即可下载高清 PNG。图片包含主小票、小程序码和数据二维码导入联,并自动使用当前选择的主题;主题按钮、网页背景和底部说明不会进入图片。
|
|
83
|
+
|
|
84
|
+
指定时区和输出路径:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npx codex-work-receipt@latest --today \
|
|
88
|
+
--timezone Asia/Shanghai \
|
|
89
|
+
--output ./my-receipt.html
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
只生成文件,不自动打开浏览器:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npx codex-work-receipt@latest --latest --no-open
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 全部参数
|
|
99
|
+
|
|
100
|
+
| 参数 | 说明 |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| `--range <name>` | `latest`、`today`、`last-7-days` 或 `this-week` |
|
|
103
|
+
| `--latest` | 统计最近活跃的 Codex 会话,默认模式 |
|
|
104
|
+
| `--today` | 汇总指定时区今天发生的 Codex 活动 |
|
|
105
|
+
| `--session <id>` | 统计指定的 Codex 会话 |
|
|
106
|
+
| `--timezone <name>` | 指定 IANA 时区,例如 `Asia/Shanghai` |
|
|
107
|
+
| `--lang <name>` | `zh-CN`(默认)或 `en` |
|
|
108
|
+
| `--theme <name>` | `classic`、`diner` 或 `payroll` |
|
|
109
|
+
| `--output <file>` | 指定 HTML 输出路径 |
|
|
110
|
+
| `--data-dir <directory>` | 指定本地结构历史目录 |
|
|
111
|
+
| `--install-skill` | 安装 AI 打工小票 Codex Skill |
|
|
112
|
+
| `--no-open` | 生成后不自动打开浏览器 |
|
|
113
|
+
|
|
114
|
+
也可以随时运行:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npx codex-work-receipt@latest --help
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 本地历史
|
|
121
|
+
|
|
122
|
+
每次运行会生成或更新:
|
|
123
|
+
|
|
124
|
+
- `codex-work-receipt-output/codex-receipt-*.html`:可切换主题的小票网页
|
|
125
|
+
- `codex-work-receipt-output/codex-receipt-*.json`:当前小票的版本化结构数据
|
|
126
|
+
- `~/.codex-work-receipt/receipts/*.json`:本机历史快照
|
|
127
|
+
- `~/.codex-work-receipt/latest.json`:最近生成的小票
|
|
128
|
+
- `~/.codex-work-receipt/history.jsonl`:去重后的本地历史
|
|
129
|
+
|
|
130
|
+
结构说明见 [数据结构与二维码协议](data-schema.md)。
|
|
131
|
+
|
|
132
|
+
## 当前限制
|
|
133
|
+
|
|
134
|
+
- 当前只支持 Codex;Cursor、WorkBuddy 等数据源仍在规划中
|
|
135
|
+
- 暂不统计修改文件数和代码行数,避免不同工具调用方式产生误导
|
|
136
|
+
- 时间范围按每条事件的本地日期筛选,并分别计算每个会话的 Token 区间增量
|
|
137
|
+
- 桌面网页导出 PNG 使用浏览器本地渲染,不上传小票数据
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Codex Skill
|
|
2
|
+
|
|
3
|
+
<p><a href="./codex-skill.md">中文</a> · <strong>English</strong> · <a href="../README.en.md">Back to README</a></p>
|
|
4
|
+
|
|
5
|
+
AI Work Receipt includes a Codex Skill that converts natural-language requests into the appropriate CLI options.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx codex-work-receipt@latest --install-skill --lang en
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The skill is installed at:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
~/.agents/skills/ai-work-receipt/
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
It does not modify the current repository. Restart Codex if the current session does not detect the newly installed skill.
|
|
20
|
+
|
|
21
|
+
## Use
|
|
22
|
+
|
|
23
|
+
After installation, ask Codex:
|
|
24
|
+
|
|
25
|
+
> Create an AI work receipt for my latest Codex session.
|
|
26
|
+
|
|
27
|
+
> Generate today's AI work receipt with the diner theme.
|
|
28
|
+
|
|
29
|
+
> Generate an AI work receipt for the last seven days.
|
|
30
|
+
|
|
31
|
+
> Show how much Codex worked this week.
|
|
32
|
+
|
|
33
|
+
> Create a Chinese receipt for my latest session.
|
|
34
|
+
|
|
35
|
+
Codex will choose a specific session, today, the last seven days, or this week, plus the language and theme, then run the CLI and open the generated page.
|
|
36
|
+
|
|
37
|
+
You can also invoke the skill explicitly with `$ai-work-receipt`.
|
|
38
|
+
|
|
39
|
+
## Update
|
|
40
|
+
|
|
41
|
+
Run the installation command again to safely replace an older version:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx codex-work-receipt@latest --install-skill --lang en
|
|
45
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Codex Skill
|
|
2
|
+
|
|
3
|
+
<p><strong>中文</strong> · <a href="./codex-skill.en.md">English</a> · <a href="../README.md">返回 README</a></p>
|
|
4
|
+
|
|
5
|
+
AI 打工小票内置了一个 Codex Skill,让 Codex 可以根据自然语言替你选择参数并运行 CLI。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx codex-work-receipt@latest --install-skill
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Skill 会安装到:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
~/.agents/skills/ai-work-receipt/
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
它不会修改当前代码仓库。如果当前会话没有识别到新 Skill,请重启 Codex。
|
|
20
|
+
|
|
21
|
+
## 使用
|
|
22
|
+
|
|
23
|
+
安装后可以直接说:
|
|
24
|
+
|
|
25
|
+
> 给刚刚这次工作开一张 AI 打工小票。
|
|
26
|
+
|
|
27
|
+
> 生成我今天的 AI 打工小票,使用复古粉票。
|
|
28
|
+
|
|
29
|
+
> 生成我最近七天的 AI 打工小票。
|
|
30
|
+
|
|
31
|
+
> 看看本周 Codex 一共打了多少工。
|
|
32
|
+
|
|
33
|
+
> 用英文生成刚刚这次工作的 AI 打工小票。
|
|
34
|
+
|
|
35
|
+
Codex 会根据表达选择单次会话、今天、近七日或本周,以及语言和主题,然后执行命令并打开生成的网页。
|
|
36
|
+
|
|
37
|
+
也可以通过 `$ai-work-receipt` 显式调用这个 Skill。
|
|
38
|
+
|
|
39
|
+
## 更新
|
|
40
|
+
|
|
41
|
+
重新运行安装命令即可安全覆盖旧版本:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx codex-work-receipt@latest --install-skill
|
|
45
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Data schema and QR protocol
|
|
2
|
+
|
|
3
|
+
<p><a href="./data-schema.md">中文</a> · <strong>English</strong> · <a href="../README.en.md">Back to README</a></p>
|
|
4
|
+
|
|
5
|
+
The current schema version is `1`. Each generation saves a structured receipt record locally.
|
|
6
|
+
|
|
7
|
+
## Main fields
|
|
8
|
+
|
|
9
|
+
- `schema_version`: schema version
|
|
10
|
+
- `locale`: desktop receipt language, `zh-CN` or `en`
|
|
11
|
+
- `id`: anonymous ID derived from the metrics snapshot
|
|
12
|
+
- `generated_at`: generation time
|
|
13
|
+
- `source`: data source, selected `scope`, and collector version
|
|
14
|
+
- `period`: actual activity times, timezone, and `range_start_date` / `range_end_date` calendar boundaries
|
|
15
|
+
- `stats`: turns, messages, tools, Tokens, duration, and related metrics
|
|
16
|
+
- `presentation`: default theme, language-neutral `work_profile`, localized role, review, and AI work points
|
|
17
|
+
- `privacy`: explicit declaration of excluded sensitive content
|
|
18
|
+
|
|
19
|
+
The `id` remains stable for the same session, day, or calendar-boundary pair. Regeneration updates the record rather than creating duplicate history. `source.snapshot_hash` detects metric changes.
|
|
20
|
+
|
|
21
|
+
## QR format
|
|
22
|
+
|
|
23
|
+
The QR payload uses compact fields:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
cwr1.<checksum>.<Base64URL of deflateRaw(JSON)>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The mini program validates the prefix and checksum before decompressing and parsing the payload. Future schema versions use the compact `v` field for compatibility.
|
|
30
|
+
|
|
31
|
+
Compact field `o` explicitly carries `latest`, `session`, `today`, `last-7-days`, or `this-week`. `d[3]` and `d[4]` carry the calendar boundaries. These additive fields keep the `cwr1` prefix and schema version `1`, so the updated mini program can still import older QR codes.
|
|
32
|
+
|
|
33
|
+
`presentation.compensation` contains playful AI work points, not real API cost. For compatibility with the current Chinese mini program, QR display copy remains Chinese while compact fields `l` and `r` carry the desktop locale and language-neutral role ID. English HTML and local JSON remain fully localized.
|
|
34
|
+
|
|
35
|
+
See [mobile QR import](mobile-import.en.md).
|
package/docs/data-schema.md
CHANGED
|
@@ -1,25 +1,35 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 数据结构与二维码协议
|
|
2
|
+
|
|
3
|
+
<p><strong>中文</strong> · <a href="./data-schema.en.md">English</a> · <a href="../README.md">返回 README</a></p>
|
|
2
4
|
|
|
3
5
|
当前结构版本为 `1`。每次生成都会在本机保存一份结构记录。
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
## 主要字段
|
|
6
8
|
|
|
7
9
|
- `schema_version`:结构版本
|
|
8
|
-
- `locale
|
|
9
|
-
- `id`:根据统计快照生成的匿名ID
|
|
10
|
+
- `locale`:桌面小票语言,支持 `zh-CN` 和 `en`
|
|
11
|
+
- `id`:根据统计快照生成的匿名 ID
|
|
10
12
|
- `generated_at`:生成时间
|
|
11
|
-
- `source
|
|
12
|
-
- `period
|
|
13
|
+
- `source`:数据来源、`scope` 统计范围和采集器版本
|
|
14
|
+
- `period`:实际活动起止时间、时区,以及 `range_start_date` / `range_end_date` 自然日边界
|
|
13
15
|
- `stats`:轮次、消息、工具、Token、时长等统计
|
|
14
16
|
- `presentation`:默认主题、语言无关的 `work_profile`、本地化工种、点评和 AI 工分
|
|
15
17
|
- `privacy`:明确声明不包含的敏感内容
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
同一个会话、同一天或同一组自然日边界的 `id` 保持稳定;重复生成会更新该记录。`source.snapshot_hash` 用于识别统计内容是否发生变化。
|
|
20
|
+
|
|
21
|
+
## 二维码格式
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
二维码使用精简字段:
|
|
20
24
|
|
|
21
25
|
```text
|
|
22
26
|
cwr1.<checksum>.<deflateRaw(JSON) 的 Base64URL>
|
|
23
27
|
```
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
小程序应检查前缀和校验值,再解压并解析数据。未来结构升级通过 `v` 字段兼容。
|
|
30
|
+
|
|
31
|
+
精简字段 `o` 显式携带 `latest`、`session`、`today`、`last-7-days` 或 `this-week`;`d[3]` 和 `d[4]` 携带自然日边界。新增字段保持 `cwr1` 和结构版本 `1`,旧二维码仍可由新版小程序导入。
|
|
32
|
+
|
|
33
|
+
`presentation.compensation` 是娱乐化 AI 工分,不代表真实 API 费用。为兼容当前中文小程序,二维码展示文案继续使用中文,并通过精简字段 `l` 和 `r` 携带桌面语言及工种语义 ID;英文 HTML 和本地 JSON 不受影响。
|
|
34
|
+
|
|
35
|
+
手机端流程见 [手机扫码导入](mobile-import.md)。
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1280" height="640" viewBox="0 0 1280 640">
|
|
2
|
+
<defs>
|
|
3
|
+
<pattern id="grid" width="32" height="32" patternUnits="userSpaceOnUse">
|
|
4
|
+
<path d="M32 0H0V32" fill="none" stroke="#42CDA7" stroke-opacity="0.09" stroke-width="1"/>
|
|
5
|
+
</pattern>
|
|
6
|
+
<filter id="shadow" x="-30%" y="-30%" width="160%" height="180%">
|
|
7
|
+
<feDropShadow dx="0" dy="16" stdDeviation="18" flood-color="#15342C" flood-opacity="0.19"/>
|
|
8
|
+
</filter>
|
|
9
|
+
<clipPath id="classicClip"><rect x="0" y="0" width="240" height="320" rx="6"/></clipPath>
|
|
10
|
+
<clipPath id="dinerClip"><rect x="0" y="0" width="240" height="320" rx="6"/></clipPath>
|
|
11
|
+
<clipPath id="payrollClip"><rect x="0" y="0" width="240" height="320" rx="6"/></clipPath>
|
|
12
|
+
</defs>
|
|
13
|
+
|
|
14
|
+
<rect width="1280" height="640" fill="#F5F1E8"/>
|
|
15
|
+
<rect width="1280" height="640" fill="url(#grid)"/>
|
|
16
|
+
<circle cx="1040" cy="310" r="288" fill="#42CDA7" fill-opacity="0.12"/>
|
|
17
|
+
<circle cx="1120" cy="116" r="86" fill="#F2A7B6" fill-opacity="0.19"/>
|
|
18
|
+
|
|
19
|
+
<g font-family="-apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif">
|
|
20
|
+
<rect x="68" y="70" width="216" height="40" rx="20" fill="#17231F"/>
|
|
21
|
+
<text x="176" y="96" text-anchor="middle" fill="#F8F4EA" font-size="16" font-weight="700" letter-spacing="1.6">OPEN SOURCE · v0.3.0</text>
|
|
22
|
+
|
|
23
|
+
<text x="68" y="210" fill="#17231F" font-size="70" font-weight="800" letter-spacing="-2">Codex AI</text>
|
|
24
|
+
<text x="68" y="292" fill="#17231F" font-size="76" font-weight="800" letter-spacing="-2">打工小票</text>
|
|
25
|
+
|
|
26
|
+
<rect x="68" y="326" width="62" height="6" rx="3" fill="#42CDA7"/>
|
|
27
|
+
<text x="68" y="376" fill="#41514B" font-size="25" font-weight="500">把本机 Codex 的工作记录,</text>
|
|
28
|
+
<text x="68" y="414" fill="#41514B" font-size="25" font-weight="500">开成一张可以带走的小票。</text>
|
|
29
|
+
|
|
30
|
+
<rect x="68" y="468" width="590" height="72" rx="14" fill="#17231F"/>
|
|
31
|
+
<circle cx="101" cy="504" r="7" fill="#42CDA7"/>
|
|
32
|
+
<text x="124" y="512" fill="#F8F4EA" font-size="21" font-family="SFMono-Regular, Menlo, Monaco, Consolas, monospace">npx codex-work-receipt@latest --latest</text>
|
|
33
|
+
<text x="68" y="580" fill="#65736E" font-size="16" font-weight="600" letter-spacing="0.7">LOCAL-FIRST · PRIVACY-FIRST · BILINGUAL</text>
|
|
34
|
+
</g>
|
|
35
|
+
|
|
36
|
+
<g filter="url(#shadow)" transform="translate(758 104) rotate(-8 120 160)" font-family="Menlo, Monaco, Consolas, monospace">
|
|
37
|
+
<rect width="240" height="320" rx="6" fill="#FFFEFA"/>
|
|
38
|
+
<text x="120" y="38" text-anchor="middle" fill="#17231F" font-size="15" font-weight="700">AI WORK RECEIPT</text>
|
|
39
|
+
<text x="120" y="57" text-anchor="middle" fill="#65736E" font-size="8" letter-spacing="1.4">CODEX · LOCAL SHIFT</text>
|
|
40
|
+
<path d="M24 76H216M24 100H216M24 168H216M24 222H216" stroke="#17231F" stroke-width="1" stroke-dasharray="4 3"/>
|
|
41
|
+
<text x="25" y="92" fill="#17231F" font-size="9">SESSION</text><text x="215" y="92" text-anchor="end" fill="#17231F" font-size="9">#0037</text>
|
|
42
|
+
<text x="25" y="121" fill="#65736E" font-size="8">ROUNDS</text><text x="215" y="121" text-anchor="end" fill="#17231F" font-size="10">42</text>
|
|
43
|
+
<text x="25" y="143" fill="#65736E" font-size="8">TOOL CALLS</text><text x="215" y="143" text-anchor="end" fill="#17231F" font-size="10">118</text>
|
|
44
|
+
<text x="25" y="160" fill="#65736E" font-size="8">AI SCORE</text><text x="215" y="160" text-anchor="end" fill="#17231F" font-size="10">3,862</text>
|
|
45
|
+
<text x="25" y="192" fill="#17231F" font-size="10" font-weight="700">TODAY'S ROLE</text>
|
|
46
|
+
<text x="25" y="211" fill="#17231F" font-size="13" font-weight="700">TOOL CONDUCTOR</text>
|
|
47
|
+
<g fill="#17231F">
|
|
48
|
+
<rect x="35" y="244" width="3" height="42"/><rect x="42" y="244" width="7" height="42"/><rect x="54" y="244" width="2" height="42"/><rect x="61" y="244" width="5" height="42"/><rect x="72" y="244" width="3" height="42"/><rect x="81" y="244" width="8" height="42"/><rect x="94" y="244" width="2" height="42"/><rect x="101" y="244" width="5" height="42"/><rect x="112" y="244" width="7" height="42"/><rect x="124" y="244" width="3" height="42"/><rect x="132" y="244" width="8" height="42"/><rect x="145" y="244" width="2" height="42"/><rect x="152" y="244" width="6" height="42"/><rect x="164" y="244" width="3" height="42"/><rect x="172" y="244" width="8" height="42"/><rect x="185" y="244" width="2" height="42"/><rect x="192" y="244" width="6" height="42"/>
|
|
49
|
+
</g>
|
|
50
|
+
<text x="120" y="302" text-anchor="middle" fill="#65736E" font-size="7" letter-spacing="1">KEEP SHIPPING</text>
|
|
51
|
+
</g>
|
|
52
|
+
<g filter="url(#shadow)" transform="translate(974 86) rotate(8 120 160)" font-family="Menlo, Monaco, Consolas, monospace">
|
|
53
|
+
<rect width="240" height="320" rx="6" fill="#F5B2BF"/>
|
|
54
|
+
<rect x="18" y="18" width="204" height="284" rx="3" fill="none" stroke="#7A233D" stroke-width="2"/>
|
|
55
|
+
<text x="120" y="48" text-anchor="middle" fill="#7A233D" font-size="15" font-weight="700">LATE NIGHT DINER</text>
|
|
56
|
+
<text x="120" y="67" text-anchor="middle" fill="#7A233D" font-size="8" letter-spacing="1.2">AI SHIFT · OPEN 24/7</text>
|
|
57
|
+
<path d="M34 86H206M34 176H206M34 225H206" stroke="#7A233D" stroke-width="1" stroke-dasharray="3 3"/>
|
|
58
|
+
<text x="36" y="108" fill="#7A233D" font-size="9">PROMPT TURNS</text><text x="204" y="108" text-anchor="end" fill="#7A233D" font-size="10">28</text>
|
|
59
|
+
<text x="36" y="132" fill="#7A233D" font-size="9">TOOL ORDERS</text><text x="204" y="132" text-anchor="end" fill="#7A233D" font-size="10">96</text>
|
|
60
|
+
<text x="36" y="156" fill="#7A233D" font-size="9">OVERTIME</text><text x="204" y="156" text-anchor="end" fill="#7A233D" font-size="10">02:17</text>
|
|
61
|
+
<text x="36" y="199" fill="#7A233D" font-size="11" font-weight="700">TOTAL AI SCORE</text>
|
|
62
|
+
<text x="204" y="218" text-anchor="end" fill="#7A233D" font-size="24" font-weight="700">6,420</text>
|
|
63
|
+
<g fill="#7A233D">
|
|
64
|
+
<rect x="43" y="244" width="4" height="32"/><rect x="51" y="244" width="7" height="32"/><rect x="63" y="244" width="2" height="32"/><rect x="70" y="244" width="5" height="32"/><rect x="80" y="244" width="3" height="32"/><rect x="88" y="244" width="8" height="32"/><rect x="101" y="244" width="3" height="32"/><rect x="109" y="244" width="6" height="32"/><rect x="120" y="244" width="2" height="32"/><rect x="127" y="244" width="8" height="32"/><rect x="140" y="244" width="3" height="32"/><rect x="148" y="244" width="6" height="32"/><rect x="159" y="244" width="2" height="32"/><rect x="166" y="244" width="8" height="32"/><rect x="179" y="244" width="4" height="32"/><rect x="188" y="244" width="6" height="32"/>
|
|
65
|
+
</g>
|
|
66
|
+
<text x="120" y="291" text-anchor="middle" fill="#7A233D" font-size="7" letter-spacing="1">THANK YOU, BOSS</text>
|
|
67
|
+
</g>
|
|
68
|
+
<g filter="url(#shadow)" transform="translate(858 246) rotate(1 120 160)" font-family="Menlo, Monaco, Consolas, monospace">
|
|
69
|
+
<rect width="240" height="320" rx="6" fill="#163B32"/>
|
|
70
|
+
<text x="120" y="40" text-anchor="middle" fill="#94EACD" font-size="16" font-weight="700">AI PAYROLL</text>
|
|
71
|
+
<text x="120" y="61" text-anchor="middle" fill="#D8FFF1" font-size="8" letter-spacing="1.4">NIGHT SHIFT REPORT</text>
|
|
72
|
+
<path d="M24 80H216M24 108H216M24 182H216M24 235H216" stroke="#94EACD" stroke-opacity="0.65" stroke-width="1" stroke-dasharray="4 3"/>
|
|
73
|
+
<text x="25" y="99" fill="#D8FFF1" font-size="9">EMPLOYEE</text><text x="215" y="99" text-anchor="end" fill="#94EACD" font-size="9">GPT-5.6</text>
|
|
74
|
+
<text x="25" y="132" fill="#D8FFF1" font-size="8">TOKENS</text><text x="215" y="132" text-anchor="end" fill="#94EACD" font-size="10">284,920</text>
|
|
75
|
+
<text x="25" y="154" fill="#D8FFF1" font-size="8">TOOLS</text><text x="215" y="154" text-anchor="end" fill="#94EACD" font-size="10">137</text>
|
|
76
|
+
<text x="25" y="174" fill="#D8FFF1" font-size="8">ROUNDS</text><text x="215" y="174" text-anchor="end" fill="#94EACD" font-size="10">51</text>
|
|
77
|
+
<text x="25" y="207" fill="#D8FFF1" font-size="10">AI SCORE</text>
|
|
78
|
+
<text x="215" y="226" text-anchor="end" fill="#94EACD" font-size="27" font-weight="700">8,888</text>
|
|
79
|
+
<g fill="#94EACD">
|
|
80
|
+
<rect x="35" y="253" width="3" height="35"/><rect x="42" y="253" width="7" height="35"/><rect x="54" y="253" width="2" height="35"/><rect x="61" y="253" width="5" height="35"/><rect x="72" y="253" width="3" height="35"/><rect x="81" y="253" width="8" height="35"/><rect x="94" y="253" width="2" height="35"/><rect x="101" y="253" width="5" height="35"/><rect x="112" y="253" width="7" height="35"/><rect x="124" y="253" width="3" height="35"/><rect x="132" y="253" width="8" height="35"/><rect x="145" y="253" width="2" height="35"/><rect x="152" y="253" width="6" height="35"/><rect x="164" y="253" width="3" height="35"/><rect x="172" y="253" width="8" height="35"/><rect x="185" y="253" width="2" height="35"/><rect x="192" y="253" width="6" height="35"/>
|
|
81
|
+
</g>
|
|
82
|
+
<text x="120" y="306" text-anchor="middle" fill="#D8FFF1" font-size="7" letter-spacing="1">CLOCKED OUT</text>
|
|
83
|
+
</g>
|
|
84
|
+
</svg>
|
|
Binary file
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Mobile QR import
|
|
2
|
+
|
|
3
|
+
<p><a href="./mobile-import.md">中文</a> · <strong>English</strong> · <a href="../README.en.md">Back to README</a></p>
|
|
4
|
+
|
|
5
|
+
The desktop page shows both the fixed mini-program code and a data QR code for the current receipt. The companion mini program can complete the MVP import without a server:
|
|
6
|
+
|
|
7
|
+
1. Scan the fixed mini-program code in WeChat and open the companion mini program.
|
|
8
|
+
2. Tap the desktop-import action.
|
|
9
|
+
3. Scan the adjacent data QR code.
|
|
10
|
+
4. Validate the `cwr1` prefix, checksum, and schema version.
|
|
11
|
+
5. Decompress and parse the privacy-safe metrics.
|
|
12
|
+
6. Recognize a session, today, last-seven-days, or this-week range, then deduplicate by receipt ID and store it locally.
|
|
13
|
+
7. Render the selected template with Canvas and save it to the phone.
|
|
14
|
+
|
|
15
|
+
The QR code does not transfer an image. The desktop page and mini program independently render the same structured receipt data. Older QR codes without an explicit range field remain supported through the legacy label fallback.
|
|
16
|
+
|
|
17
|
+
The companion mini program is a separate product. This repository does not contain its source code, AppID, backend code, or server credentials.
|
|
18
|
+
|
|
19
|
+
See the [data schema and QR protocol](data-schema.en.md).
|
package/docs/mobile-import.md
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 手机扫码导入
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p><strong>中文</strong> · <a href="./mobile-import.en.md">English</a> · <a href="../README.md">返回 README</a></p>
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
桌面网页会同时展示固定小程序码和当前小票的数据二维码。配套小程序无需服务器即可完成 MVP 导入:
|
|
6
|
+
|
|
7
|
+
1. 微信扫描固定小程序码并打开配套小程序。
|
|
6
8
|
2. 点击“从电脑导入”。
|
|
7
|
-
3.
|
|
8
|
-
4.
|
|
9
|
-
5.
|
|
10
|
-
6.
|
|
11
|
-
7. 使用 Canvas
|
|
9
|
+
3. 扫描网页旁边的数据二维码。
|
|
10
|
+
4. 小程序校验 `cwr1` 前缀、checksum 和结构版本。
|
|
11
|
+
5. 解压并解析脱敏统计数据。
|
|
12
|
+
6. 识别单会话、今日、近七日或本周范围,并按小票 ID 去重写入本地历史。
|
|
13
|
+
7. 使用 Canvas 重新绘制所选模板并保存到手机相册。
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
二维码不会传输图片。桌面端和小程序端根据同一份结构数据分别渲染小票;旧版二维码没有显式范围字段时仍按原有文案兼容识别。
|
|
14
16
|
|
|
15
17
|
配套小程序是独立产品,本仓库不包含其源码、AppID、后台代码或服务端密钥。
|
|
18
|
+
|
|
19
|
+
协议细节见 [数据结构与二维码协议](data-schema.md)。
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Local data and privacy
|
|
2
|
+
|
|
3
|
+
<p><a href="./privacy.md">中文</a> · <strong>English</strong> · <a href="../README.en.md">Back to README</a></p>
|
|
4
|
+
|
|
5
|
+
AI Work Receipt reads Codex sessions, computes metrics, and renders receipts locally.
|
|
6
|
+
|
|
7
|
+
## What it reads
|
|
8
|
+
|
|
9
|
+
- `~/.codex/sessions/**/*.jsonl` by default
|
|
10
|
+
- Event types, timestamps, model names, and numerical metadata
|
|
11
|
+
- Metrics such as turns, tool calls, Tokens, duration, and interruptions
|
|
12
|
+
|
|
13
|
+
## What receipts exclude
|
|
14
|
+
|
|
15
|
+
- Prompts and response text
|
|
16
|
+
- Source code
|
|
17
|
+
- Project paths and file names
|
|
18
|
+
- API keys, account credentials, and environment variables
|
|
19
|
+
|
|
20
|
+
## Where data is saved
|
|
21
|
+
|
|
22
|
+
Generated HTML and JSON 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.
|
|
23
|
+
|
|
24
|
+
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.
|
|
25
|
+
|
|
26
|
+
## QR payload
|
|
27
|
+
|
|
28
|
+
The QR code contains versioned, privacy-safe receipt metrics only. It does not contain the original session or an image. Treat the QR code as a scannable data file and share it only with people you trust.
|
|
29
|
+
|
|
30
|
+
See the [data schema and QR protocol](data-schema.en.md).
|
package/docs/privacy.md
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 本地数据与隐私
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p><strong>中文</strong> · <a href="./privacy.en.md">English</a> · <a href="../README.md">返回 README</a></p>
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- 只提取事件类型、时间和数字统计
|
|
7
|
-
- 不保存 Prompt、回复正文、代码、项目路径或文件名
|
|
8
|
-
- 不向网络发送数据
|
|
9
|
-
- 结构数据保存在 `~/.codex-work-receipt/`
|
|
5
|
+
AI 打工小票在本机读取 Codex 会话,并在本机完成统计和渲染。
|
|
10
6
|
|
|
11
|
-
|
|
7
|
+
## 会读取什么
|
|
12
8
|
|
|
9
|
+
- 默认读取 `~/.codex/sessions/**/*.jsonl`
|
|
10
|
+
- 提取事件类型、时间、模型名称和数字统计
|
|
11
|
+
- 计算轮次、工具调用、Token、时长和中断次数等指标
|
|
12
|
+
|
|
13
|
+
## 不会写入小票什么
|
|
14
|
+
|
|
15
|
+
- Prompt 和回复正文
|
|
16
|
+
- 源代码
|
|
17
|
+
- 项目路径和文件名
|
|
18
|
+
- API Key、账号凭据或环境变量
|
|
19
|
+
|
|
20
|
+
## 会保存在哪里
|
|
21
|
+
|
|
22
|
+
生成的 HTML 和 JSON 默认位于当前目录的 `codex-work-receipt-output/`,去重历史保存在 `~/.codex-work-receipt/`。可以使用 `--output` 和 `--data-dir` 修改位置。
|
|
23
|
+
|
|
24
|
+
运行期间不会把 Codex 会话数据上传到本项目的服务器。通过 `npx` 首次下载或更新 npm 包属于包管理器的正常网络请求,不包含本地会话内容。
|
|
25
|
+
|
|
26
|
+
## 二维码
|
|
27
|
+
|
|
28
|
+
二维码只包含当前小票的版本化脱敏统计,不包含原始会话或图片。二维码等同于一份可扫描的数据文件,请只向信任的人展示。
|
|
29
|
+
|
|
30
|
+
格式说明见 [数据结构与二维码协议](data-schema.md)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-work-receipt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Generate a privacy-first Codex work receipt from local session metadata.",
|
|
6
6
|
"author": "a-bai-2026",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"node": ">=20"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
+
"dom-to-image-more": "^3.10.2",
|
|
55
56
|
"qrcode": "^1.5.4"
|
|
56
57
|
}
|
|
57
58
|
}
|
|
@@ -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 to create, regenerate, open, or locate an “AI 打工小票” or “AI work receipt” for the latest session
|
|
3
|
+
description: Generate and open a privacy-first local AI work receipt from Codex session metadata. Use when the user asks Codex to create, regenerate, open, or locate an “AI 打工小票” or “AI work receipt” for the latest session, 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 打工小票
|
|
@@ -11,13 +11,16 @@ Use the published `codex-work-receipt` CLI as the only statistics and rendering
|
|
|
11
11
|
|
|
12
12
|
- Use `--latest` for “刚刚”“这次”“最近一次”“上一段工作” or an unspecified request for one receipt.
|
|
13
13
|
- Use `--today` for “今天”“今日”“今天全部工作” or a daily summary.
|
|
14
|
+
- Use `--range last-7-days` for “最近七天”“近 7 日” or a rolling seven-calendar-day summary.
|
|
15
|
+
- Use `--range this-week` for “本周”“这周” or a Monday-to-now summary.
|
|
14
16
|
- Use `--theme classic` for “经典白票” or no specified theme.
|
|
15
17
|
- Use `--theme diner` for “复古粉票”“粉色小票” or “diner”.
|
|
16
18
|
- Use `--theme payroll` for “夜班绿票”“绿色小票” or “payroll”.
|
|
17
19
|
- Add `--no-open` only when the user asks not to open the browser.
|
|
18
20
|
- Add `--timezone <IANA name>` only when the user explicitly requests another timezone.
|
|
19
21
|
- Use `--lang en` when the user requests English or asks for the receipt in English. Use `--lang zh-CN` otherwise.
|
|
20
|
-
- If the user asks
|
|
22
|
+
- If the user asks to choose interactively, run the CLI without a range flag so it can show the local selector.
|
|
23
|
+
- If the user asks for an unsupported custom date range, explain the available ranges and do not invent flags.
|
|
21
24
|
|
|
22
25
|
## Execute
|
|
23
26
|
|
package/src/cli.mjs
CHANGED
|
@@ -8,11 +8,14 @@ import QRCode from "qrcode";
|
|
|
8
8
|
|
|
9
9
|
import { parseArgs, printHelp } from "./core/args.mjs";
|
|
10
10
|
import { collectMetrics } from "./core/metrics.mjs";
|
|
11
|
+
import { getReceiptCopy } from "./core/presentation.mjs";
|
|
11
12
|
import { encodeReceiptPayload } from "./core/qr-payload.mjs";
|
|
13
|
+
import { outputSlugForRange, resolveRange } from "./core/range.mjs";
|
|
12
14
|
import { buildReceiptRecord, persistReceiptRecord } from "./core/receipt-record.mjs";
|
|
15
|
+
import { promptForRange } from "./core/selector.mjs";
|
|
13
16
|
import { installCodexSkill } from "./core/skill-installer.mjs";
|
|
14
17
|
import { formatNumber } from "./lib/time.mjs";
|
|
15
|
-
import { loadCodexSessions } from "./parsers/codex.mjs";
|
|
18
|
+
import { listRecentCodexSessions, loadCodexSessions } from "./parsers/codex.mjs";
|
|
16
19
|
import { renderHtml } from "./renderers/html.mjs";
|
|
17
20
|
|
|
18
21
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -61,8 +64,19 @@ async function main() {
|
|
|
61
64
|
return;
|
|
62
65
|
}
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
if (!options.modeExplicit && process.stdin.isTTY && process.stdout.isTTY) {
|
|
68
|
+
const selected = await promptForRange({
|
|
69
|
+
locale: options.locale,
|
|
70
|
+
timezone: options.timezone,
|
|
71
|
+
loadRecentSessions: () => listRecentCodexSessions(10),
|
|
72
|
+
});
|
|
73
|
+
options.mode = selected.mode;
|
|
74
|
+
options.sessionId = selected.sessionId;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const range = resolveRange(options.mode, options.timezone, new Date(), options.sessionId);
|
|
78
|
+
const sessions = loadCodexSessions(range);
|
|
79
|
+
const metrics = collectMetrics(sessions, range);
|
|
66
80
|
const record = buildReceiptRecord(metrics, options.theme, options.locale);
|
|
67
81
|
const qrPayload = encodeReceiptPayload(record);
|
|
68
82
|
const dataQrDataUrl = await QRCode.toDataURL(qrPayload, {
|
|
@@ -72,7 +86,10 @@ async function main() {
|
|
|
72
86
|
color: { dark: "#171713", light: "#ffffff" },
|
|
73
87
|
});
|
|
74
88
|
|
|
75
|
-
const requestedOutput = options.output || path.join(
|
|
89
|
+
const requestedOutput = options.output || path.join(
|
|
90
|
+
DEFAULT_OUTPUT_DIR,
|
|
91
|
+
`codex-receipt-${outputSlugForRange(range, record.id)}.html`,
|
|
92
|
+
);
|
|
76
93
|
const outputFile = path.resolve(/\.html?$/i.test(requestedOutput) ? requestedOutput : `${requestedOutput}.html`);
|
|
77
94
|
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
|
|
78
95
|
|
|
@@ -89,6 +106,7 @@ async function main() {
|
|
|
89
106
|
console.log(`Generated HTML: ${outputFile}`);
|
|
90
107
|
console.log(`Structured data: ${persisted.companionPath}`);
|
|
91
108
|
console.log(`Local history: ${persisted.receiptPath}`);
|
|
109
|
+
console.log(`Range: ${getReceiptCopy(options.locale).scope[record.source.scope]} · ${record.stats.session_count} session(s)`);
|
|
92
110
|
console.log(`Stats: ${record.stats.completed_turns} turns · ${formatNumber(record.stats.tokens.total_tokens, options.locale)} Tokens · ${record.stats.tool_calls} tool calls`);
|
|
93
111
|
console.log(`Data QR: ${qrPayload.length} characters · schema v${record.schema_version}`);
|
|
94
112
|
if (!miniProgramCodeDataUrl) console.log("Mini-program code: not configured; using the explicit placeholder");
|
|
@@ -96,6 +114,7 @@ async function main() {
|
|
|
96
114
|
console.log(`已生成网页:${outputFile}`);
|
|
97
115
|
console.log(`结构数据:${persisted.companionPath}`);
|
|
98
116
|
console.log(`本地历史:${persisted.receiptPath}`);
|
|
117
|
+
console.log(`统计范围:${getReceiptCopy(options.locale).scope[record.source.scope]} · ${record.stats.session_count} 个会话`);
|
|
99
118
|
console.log(`统计:${record.stats.completed_turns} 轮 · ${formatNumber(record.stats.tokens.total_tokens, options.locale)} Token · ${record.stats.tool_calls} 次工具调用`);
|
|
100
119
|
console.log(`数据二维码:${qrPayload.length} 字符 · schema v${record.schema_version}`);
|
|
101
120
|
if (!miniProgramCodeDataUrl) console.log("小程序码:尚未配置,页面使用明确占位符");
|