codex-work-receipt 0.7.0 → 0.7.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,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.1 - 2026-07-19
4
+
5
+ - 修正 HTML 长图导出:PNG 只保留完整小票和微信小程序码,不再包含数据二维码或分片数据码。
6
+
3
7
  ## 0.7.0 - 2026-07-19
4
8
 
5
9
  - 新增 Codex 原生桌宠“票仔 · AI 小票工”,提供夜班绿票风格的核心状态与左右移动动画,并支持通过 `--install-pet`、`--uninstall-pet` 和 `--install-companion` 安装管理。
package/README.en.md CHANGED
@@ -24,6 +24,16 @@ Codex Work Receipt summarizes turns, tool calls, Tokens, duration, and models, t
24
24
 
25
25
  Version `0.6.0` uses the cwr2 protocol to create stable privacy-safe facts for each session and calendar day. Overlapping today, last-seven-days, and this-week receipts can be deduplicated by the receiver, and oversized payloads are automatically split into reorderable multipart QR codes that rotate one at a time in the HTML so the camera never sees multiple data codes at once.
26
26
 
27
+ ## Meet Ticket Buddy
28
+
29
+ <p align="center">
30
+ <a href="docs/codex-pet.en.md"><img src="docs/images/codex-pet-showcase.png" alt="Ticket Buddy in idle, working, needs-input, ready, and failed states" width="920"></a>
31
+ </p>
32
+
33
+ <p align="center">
34
+ <sub>Ticket Buddy changes expression as Codex becomes idle, runs a task, needs input, finishes, or gets blocked. Select the image for setup and state details.</sub>
35
+ </p>
36
+
27
37
  ## Quickstart
28
38
 
29
39
  Requires Node.js 20+ and local Codex session records. No clone required; choose today, the last 3 hours, the last 7 days, this week, or a specific session:
@@ -44,7 +54,7 @@ Summarize the last 3 hours:
44
54
  npx codex-work-receipt@latest --hours 3 --lang en
45
55
  ```
46
56
 
47
- HTML and structured data are written to `./codex-work-receipt-output/` by default. The generated page can save a high-resolution PNG containing the full receipt and QR import stub. Non-interactive options such as `--latest` and `--today` remain available; see the [CLI guide](docs/cli.en.md).
57
+ HTML and structured data are written to `./codex-work-receipt-output/` by default. The generated page can save a high-resolution PNG containing only the full receipt and WeChat mini-program code; data QR codes are excluded. Non-interactive options such as `--latest` and `--today` remain available; see the [CLI guide](docs/cli.en.md).
48
58
 
49
59
  ## Ask Codex directly
50
60
 
package/README.md CHANGED
@@ -24,6 +24,16 @@
24
24
 
25
25
  `0.6.0` 使用 cwr2 协议,按“会话 × 自然日”生成稳定的脱敏事实。今日、近 7 日和本周小票即使范围重叠,接收端也能识别相同工作;数据过大时会自动生成多张可重组的分片二维码,并在 HTML 中逐张自动轮播,避免镜头同时识别多个数据码。
26
26
 
27
+ ## 认识票仔
28
+
29
+ <p align="center">
30
+ <a href="docs/codex-pet.md"><img src="docs/images/codex-pet-showcase.png" alt="票仔 AI 小票工的待机、打工、等待确认、完成和失败状态" width="920"></a>
31
+ </p>
32
+
33
+ <p align="center">
34
+ <sub>票仔会跟随 Codex 的空闲、执行、等待确认、完成和失败状态切换表情。点击图片查看安装与状态说明。</sub>
35
+ </p>
36
+
27
37
  ## Quickstart
28
38
 
29
39
  需要 Node.js 20 或更高版本,并且本机已经使用过 Codex。无需克隆仓库,运行后选择“今天 / 最近 3 小时 / 近 7 日 / 本周 / 指定会话”:
@@ -44,7 +54,7 @@ npx codex-work-receipt@latest --today
44
54
  npx codex-work-receipt@latest --hours 3
45
55
  ```
46
56
 
47
- 网页和结构数据默认保存在 `./codex-work-receipt-output/`。生成的网页支持一键保存包含主小票和二维码导入联的高清长图。也可以继续使用 `--latest`、`--today` 等非交互参数,详见 [CLI 使用文档](docs/cli.md)。
57
+ 网页和结构数据默认保存在 `./codex-work-receipt-output/`。生成的网页支持一键保存只包含完整小票和微信小程序码的高清长图,数据二维码不会进入图片。也可以继续使用 `--latest`、`--today` 等非交互参数,详见 [CLI 使用文档](docs/cli.md)。
48
58
 
49
59
  ## 直接跟 Codex 说
50
60
 
package/docs/cli.en.md CHANGED
@@ -87,7 +87,7 @@ Default output:
87
87
 
88
88
  Default filenames include the calendar range. Latest-session and selected-session receipts include a short identifier so receipts from different dates or sessions do not overwrite one another.
89
89
 
90
- Open the generated HTML and click “Save full PNG” to download a high-resolution image. It includes the main receipt, mini-program code, and data QR import stub in the currently selected theme. Theme controls, the page background, and the footer note are excluded. With one data QR, the mini-program code and data code remain side by side. When cwr2 exceeds the safe capacity of one QR code, the page shows the mini-program code first and then rotates multipart data codes one at a time without desktop clicks. The full PNG still includes every numbered part so the offline payload remains complete.
90
+ Open the generated HTML and click “Save full PNG” to download a high-resolution image. It contains only the complete main receipt and WeChat mini-program code in the currently selected theme. Data QR codes, multipart data codes, theme controls, the page background, and the footer note are excluded. The HTML page still presents data codes normally: a single code stays beside the mini-program code, while oversized cwr2 payloads rotate their parts after the mini program has been opened so they can be imported on the phone.
91
91
 
92
92
  Set a timezone and output path:
93
93
 
package/docs/cli.md CHANGED
@@ -87,7 +87,7 @@ npx codex-work-receipt@latest --latest --theme diner
87
87
 
88
88
  默认文件名会携带统计日期范围;最近会话和指定会话则携带短编号,避免不同日期或会话的小票互相覆盖。
89
89
 
90
- 打开生成的 HTML 后,点击“保存完整长图”即可下载高清 PNG。图片包含主小票、小程序码和数据二维码导入联,并自动使用当前选择的主题;主题按钮、网页背景和底部说明不会进入图片。只有一个数据二维码时,小程序码与数据码继续并排展示。cwr2 数据超过单码安全容量时,网页先单独展示小程序码,随后自动逐张轮播分片数据码;用户无需在电脑上切换上一张或下一张,但仍需在小程序中收齐全部分片。完整长图为保留离线数据,会继续包含全部带序号的分片二维码。
90
+ 打开生成的 HTML 后,点击“保存完整长图”即可下载高清 PNG。图片只包含完整主小票和微信小程序码,并自动使用当前选择的主题;数据二维码、分片数据码、主题按钮、网页背景和底部说明都不会进入图片。网页本身仍会正常展示数据二维码:单码时与小程序码并排,cwr2 超出单码安全容量时则在打开小程序后自动逐张轮播分片数据码,供用户在小程序中完成导入。
91
91
 
92
92
  指定时区和输出路径:
93
93
 
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-work-receipt",
3
- "version": "0.7.0",
3
+ "version": "0.7.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",
@@ -142,6 +142,7 @@ const RECEIPT_COPY = {
142
142
  multipartRestart: "重新显示小程序码",
143
143
  dataQrAlt: "当前小票数据二维码",
144
144
  miniProgramAlt: "微信小程序码",
145
+ exportMiniProgramLabel: "微信扫码打开小程序",
145
146
  placeholderLabel: "小程序码",
146
147
  placeholderValue: "待接入",
147
148
  placeholderAria: "小程序码待接入",
@@ -215,6 +216,7 @@ const RECEIPT_COPY = {
215
216
  multipartRestart: "Show mini-program code again",
216
217
  dataQrAlt: "Current receipt data QR code",
217
218
  miniProgramAlt: "WeChat mini-program code",
219
+ exportMiniProgramLabel: "Scan to open the mini program",
218
220
  placeholderLabel: "Mini program",
219
221
  placeholderValue: "Pending",
220
222
  placeholderAria: "Mini-program code pending",
@@ -91,6 +91,7 @@ export function renderHtml({ record, dataQrDataUrl = null, dataQrDataUrls = null
91
91
  busyLabel: copy.exportingImage,
92
92
  successLabel: copy.exportSuccess,
93
93
  errorLabel: copy.exportError,
94
+ miniProgramLabel: copy.exportMiniProgramLabel,
94
95
  fileBase: `codex-work-receipt-${record.source.scope}-${spansMultipleDates ? `${rangeStartDate}-to-${rangeEndDate}` : `${rangeEndDate}-${record.id.slice(4, 12)}`}`,
95
96
  }).replaceAll("<", "\\u003c");
96
97
  const rows = [
@@ -151,7 +152,7 @@ export function renderHtml({ record, dataQrDataUrl = null, dataQrDataUrls = null
151
152
  <div class="qr-grid qr-grid--export-only" hidden>
152
153
  <div class="qr-item">
153
154
  <div class="qr-frame">${miniProgramVisual}</div>
154
- <strong>${escapeHtml(copy.openMiniProgram)}</strong>
155
+ <strong data-export-mini-label>${escapeHtml(copy.openMiniProgram)}</strong>
155
156
  <span>${escapeHtml(copy.openMiniProgramHint)}</span>
156
157
  </div>
157
158
  ${dataQrItems}
@@ -160,7 +161,7 @@ export function renderHtml({ record, dataQrDataUrl = null, dataQrDataUrls = null
160
161
  <div class="qr-grid qr-grid--single">
161
162
  <div class="qr-item">
162
163
  <div class="qr-frame">${miniProgramVisual}</div>
163
- <strong>${escapeHtml(copy.openMiniProgram)}</strong>
164
+ <strong data-export-mini-label>${escapeHtml(copy.openMiniProgram)}</strong>
164
165
  <span>${escapeHtml(copy.openMiniProgramHint)}</span>
165
166
  </div>
166
167
  ${dataQrItems}
@@ -692,6 +693,50 @@ export function renderHtml({ record, dataQrDataUrl = null, dataQrDataUrls = null
692
693
  }));
693
694
  }
694
695
 
696
+ function sanitizeExportNode(node) {
697
+ const multipartLiveNode = node.querySelector(".multipart-live");
698
+ if (multipartLiveNode) multipartLiveNode.remove();
699
+
700
+ const exportGridNode = node.querySelector(".qr-grid--export-only");
701
+ if (exportGridNode) {
702
+ exportGridNode.removeAttribute("hidden");
703
+ exportGridNode.style.display = "grid";
704
+ }
705
+
706
+ node.querySelectorAll("[data-data-qr-index]").forEach((item) => item.remove());
707
+ node.querySelectorAll(".qr-grid").forEach((grid) => {
708
+ grid.style.gridTemplateColumns = "minmax(0, 1fr)";
709
+ grid.style.justifyItems = "center";
710
+ });
711
+
712
+ const miniProgramLabel = node.querySelector("[data-export-mini-label]");
713
+ if (miniProgramLabel) miniProgramLabel.textContent = exportConfig.miniProgramLabel;
714
+ const transferDescription = node.querySelector(".transfer-heading p");
715
+ if (transferDescription) transferDescription.remove();
716
+ const transferNote = node.querySelector(".transfer-note");
717
+ if (transferNote) transferNote.remove();
718
+
719
+ node.querySelectorAll(".paper").forEach((paper) => {
720
+ paper.style.boxShadow = "none";
721
+ });
722
+ }
723
+
724
+ function createExportNode(source, paperColor) {
725
+ const clone = source.cloneNode(true);
726
+ sanitizeExportNode(clone);
727
+ const sourceWidth = Math.ceil(source.getBoundingClientRect().width || source.scrollWidth);
728
+ Object.assign(clone.style, {
729
+ position: "absolute",
730
+ left: "-100000px",
731
+ top: "0",
732
+ width: sourceWidth + "px",
733
+ background: paperColor,
734
+ pointerEvents: "none",
735
+ });
736
+ document.body.appendChild(clone);
737
+ return clone;
738
+ }
739
+
695
740
  function downloadImage(dataUrl, filename) {
696
741
  const link = document.createElement("a");
697
742
  link.href = dataUrl;
@@ -708,15 +753,17 @@ export function renderHtml({ record, dataQrDataUrl = null, dataQrDataUrls = null
708
753
  exportButton.disabled = true;
709
754
  exportButton.textContent = exportConfig.busyLabel;
710
755
  setExportStatus("");
756
+ let renderNode = null;
711
757
 
712
758
  try {
713
759
  if (document.fonts?.ready) await document.fonts.ready;
714
- await waitForImages(exportNode);
715
- const width = Math.ceil(exportNode.scrollWidth);
716
- const height = Math.ceil(exportNode.scrollHeight);
717
- const scale = Math.max(2, Math.min(3, 1500 / Math.max(1, width)));
718
760
  const paperColor = getComputedStyle(document.documentElement).getPropertyValue("--paper").trim() || "#ffffff";
719
- const dataUrl = await domtoimage.toPng(exportNode, {
761
+ renderNode = createExportNode(exportNode, paperColor);
762
+ await waitForImages(renderNode);
763
+ const width = Math.ceil(renderNode.scrollWidth);
764
+ const height = Math.ceil(renderNode.scrollHeight);
765
+ const scale = Math.max(2, Math.min(3, 1500 / Math.max(1, width)));
766
+ const dataUrl = await domtoimage.toPng(renderNode, {
720
767
  width,
721
768
  height,
722
769
  scale,
@@ -725,17 +772,12 @@ export function renderHtml({ record, dataQrDataUrl = null, dataQrDataUrls = null
725
772
  cacheBust: false,
726
773
  style: { background: paperColor },
727
774
  onclone(clone) {
775
+ sanitizeExportNode(clone);
776
+ clone.style.position = "static";
777
+ clone.style.left = "auto";
778
+ clone.style.top = "auto";
779
+ clone.style.width = width + "px";
728
780
  clone.style.background = paperColor;
729
- const multipartLiveClone = clone.querySelector(".multipart-live");
730
- if (multipartLiveClone) multipartLiveClone.remove();
731
- const exportGridClone = clone.querySelector(".qr-grid--export-only");
732
- if (exportGridClone) {
733
- exportGridClone.removeAttribute("hidden");
734
- exportGridClone.style.display = "grid";
735
- }
736
- clone.querySelectorAll(".paper").forEach((paper) => {
737
- paper.style.boxShadow = "none";
738
- });
739
781
  },
740
782
  logger: {},
741
783
  });
@@ -748,6 +790,7 @@ export function renderHtml({ record, dataQrDataUrl = null, dataQrDataUrls = null
748
790
  console.error(error);
749
791
  setExportStatus(exportConfig.errorLabel, "error");
750
792
  } finally {
793
+ if (renderNode) renderNode.remove();
751
794
  exportButton.disabled = false;
752
795
  exportButton.textContent = exportConfig.idleLabel;
753
796
  }