claude-token-saver 3.29.0 → 3.30.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/README.en.md CHANGED
@@ -452,7 +452,28 @@ Planning documents are moving from PowerPoint to Figma, so the same hook catches
452
452
 
453
453
  The result is an outline: pages and frames become headings, text nodes become body lines, and shapes are counted rather than listed — in a planning document the words are the content, and two hundred `Rectangle 173` lines would drown them. A file with no text at all is refused rather than dressed up as an empty document.
454
454
 
455
- Verified against real files: a community Bootstrap UI kit (8.1MB, 4,155 nodes, 1,312 of them text) and a 52MB Tailwind kit, each converting in under a second. Both `.fig` vintages parse — the current zip container and the older bare fig-kiwi stream. No savings are claimed: a `.fig` unzips to another binary, so there is no readable fallback to price against.
455
+ Verified against real files: a community Bootstrap UI kit (8.1MB, 4,155 nodes, 1,312 of them text) and a 52MB Tailwind kit, each converting in under a second. Both `.fig` vintages parse — the current zip container and the older bare fig-kiwi stream.
456
+
457
+ **`.fig` saves the most of any format.** Unlike the Office containers, `Read` does not refuse a `.fig`: the extension means nothing to it, so it pulls the binary in as text and the context window fills with tokenised noise. Measured against the same 42,760-token control:
458
+
459
+ | File | Size | Extra tokens for a Read | Conversion |
460
+ |---|---|---|---|
461
+ | plan.fig | 26KB | +44,195 | 100 tokens |
462
+ | bootstrap-kit.fig | 8.1MB | +43,994 | 18,397 tokens |
463
+
464
+ Two files three hundred times apart in size cost the same, because Read truncates long before the file ends — you pay for a whole document and receive a fraction of one. The baseline is therefore a flat 44,000 tokens. For comparison, the same probe on a pptx cost +317 tokens and on a docx +185: a refusal message, and nothing else.
465
+
466
+ #### Why the baseline does not scale with file size
467
+
468
+ A baseline has to be what would actually have been spent without the converter. Intuition says a bigger file burns more, but the `Read` tool has a cap (2,000 lines by default, plus a per-line character limit), and a binary file hits it almost immediately: even the 26KB file was already truncated, which is why two files 300× apart came out 201 tokens apart. Had the 8.1MB file gone in whole it would have been millions of tokens — money nobody could have spent, since it does not fit in a 200k context window. Claiming to have saved unspendable money is flattery, not measurement.
469
+
470
+ The same principle runs through every baseline here:
471
+
472
+ - **`.fig`, flat 44,000** — set below both measurements (44,195 and 43,994). A model could burn size-proportional tokens by re-Reading at successive offsets, but one Read is what a sane agent does once the bytes turn out to be binary noise, so one Read is the honest counterfactual.
473
+ - **PDF, 2,500 per page** — below both measured values (2,542 and 2,934).
474
+ - **Office formats, the file's actual XML size** — the one case where proportional is right, because a person really does end up reading that XML; it is measured per file rather than applied as a ratio.
475
+
476
+ The common rule: wherever an estimate and a measurement diverge, the lower number wins. A figure the user can trust is worth more than one that flatters the tool.
456
477
 
457
478
  ### Editing a document: copy, then script
458
479
 
package/README.md CHANGED
@@ -448,7 +448,28 @@ PDF 는 첨부하면 모델이 내용을 그대로 읽습니다. 대신 페이
448
448
 
449
449
  변환 결과는 페이지·프레임 계층을 헤딩으로, 텍스트 노드를 본문으로 정리한 아웃라인입니다. 도형·벡터 같은 시각 요소는 나열하지 않고 개수만 남깁니다. 기획서에서 내용은 글이고, `Rectangle 173` 이 이백 줄 나오면 글이 묻히기 때문입니다. 텍스트가 하나도 없는 파일(순수 그래픽)은 빈 문서로 꾸미지 않고 변환 불가로 알립니다.
450
450
 
451
- 실제 파일로 검증했습니다: 피그마 커뮤니티의 Bootstrap UI kit(8.1MB, 노드 4,155개, 텍스트 1,312개)와 Tailwind kit(52MB)이 각각 0.2초 안에 71.9KB·44KB 아웃라인으로 변환됐고, 한국어 텍스트 왕복도 무손실이었습니다. `.fig` 는 두 세대가 있습니다. 요즘 익스포트는 zip 컨테이너, 옛 익스포트는 fig-kiwi 바이너리 원형인데 둘 다 처리합니다. 절감액은 청구하지 않습니다. `.fig` 는 압축을 풀어도 또 바이너리라 비교할 대안 자체가 없고, 변환이 유일한 읽기 경로입니다.
451
+ 실제 파일로 검증했습니다: 피그마 커뮤니티의 Bootstrap UI kit(8.1MB, 노드 4,155개, 텍스트 1,312개)와 Tailwind kit(52MB)이 각각 0.2초 안에 71.9KB·44KB 아웃라인으로 변환됐고, 한국어 텍스트 왕복도 무손실이었습니다. `.fig` 는 두 세대가 있습니다. 요즘 익스포트는 zip 컨테이너, 옛 익스포트는 fig-kiwi 바이너리 원형인데 둘 다 처리합니다.
452
+
453
+ **`.fig` 의 절감이 가장 큽니다.** 오피스 형식과 달리 `Read` 가 `.fig` 를 거부하지 않습니다. 확장자를 모르니 이진 파일을 그대로 텍스트로 읽어들이고, 컨텍스트가 토큰화된 잡음으로 찹니다. 같은 42,760 토큰 대조군으로 측정했습니다.
454
+
455
+ | 파일 | 크기 | Read 가 더 쓴 토큰 | 변환본 |
456
+ |---|---|---|---|
457
+ | plan.fig | 26KB | +44,195 | 100 토큰 |
458
+ | bootstrap-kit.fig | 8.1MB | +43,994 | 18,397 토큰 |
459
+
460
+ 크기가 300배 차이인데 비용이 같습니다. Read 가 상한에서 자르기 때문인데, 바꿔 말하면 **문서 전체 값을 치르고 일부만 받습니다.** 그래서 기준선은 파일 크기와 무관한 44,000 토큰 고정입니다. 참고로 같은 방법으로 재보니 pptx 는 +317, docx 는 +185 토큰이었습니다. 거부 메시지 한 줄이 전부입니다.
461
+
462
+ #### 왜 파일 크기에 비례시키지 않는가
463
+
464
+ 기준선은 "변환이 없었으면 실제로 나갔을 비용"이어야 합니다. 직관으로는 파일이 클수록 더 태울 것 같지만, `Read` 도구에는 상한이 있어(기본 2,000줄, 줄당 문자 제한) 이진 파일은 그 지점에서 잘립니다. 26KB 파일조차 이미 상한을 넘기므로, 크기가 300배 차이 나는 두 파일이 201 토큰 차이로 같은 값이 나왔습니다. 8.1MB 가 통째로 들어갔다면 수백만 토큰인데, 그 돈은 200k 컨텍스트에 물리적으로 들어가지 않아 애초에 아무도 지불할 수 없습니다. 지불할 수 없는 돈을 아꼈다고 적으면 부풀리기가 됩니다.
465
+
466
+ 이 원칙은 세 곳에 일관되게 적용됩니다.
467
+
468
+ - **`.fig` 44,000 고정** — 실측 두 값(44,195·43,994)보다 낮게 잡습니다. 모델이 offset 을 바꿔 가며 반복 Read 하면 크기에 비례해 태울 수는 있지만, 첫 Read 에서 이진 잡음임이 드러나면 정상적인 에이전트는 더 읽지 않으므로 1회 Read 가 현실적인 대안입니다.
469
+ - **PDF 페이지당 2,500** — 실측치 2,542·2,934 를 밑도는 값입니다.
470
+ - **오피스 형식은 파일별 실제 XML 크기** — 이쪽은 사람이 정말 그 XML 을 읽게 되므로 비례가 맞고, 계수 대신 파일마다 잽니다.
471
+
472
+ 공통 규칙: 기준선이 추정과 실측 사이에서 갈리면 항상 낮은 쪽을 택합니다. 도구를 돋보이게 하는 숫자보다 사용자가 신뢰할 수 있는 숫자가 가치 있습니다.
452
473
 
453
474
  ### 문서를 수정해야 할 때: 복사본 + 스크립트
454
475
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-token-saver",
3
- "version": "3.29.0",
3
+ "version": "3.30.1",
4
4
  "description": "Route the easy work your expensive Claude model keeps repeating down to haiku/sonnet — post-hoc session analysis, no realtime router, no extra LLM calls.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,9 +31,11 @@
31
31
  *
32
32
  * pptx/xlsx/docx: unpacking the container. These never reach the model as
33
33
  * attachments at all — the same probe on a docx added 78 tokens and the model
34
- * replied that it had no file, and Read refuses the format outright. What a
35
- * reader does instead is unzip the archive and wade through its XML, where
36
- * tags and style attributes outweigh the text many times over:
34
+ * replied that it had no file. Read refuses them too, measured the same way:
35
+ * a Read of the 31.8MB deck cost +317 tokens and of the 185KB docx +185, which
36
+ * is a refusal message and nothing more. What a reader does instead is unzip
37
+ * the archive and wade through its XML, where tags and style attributes
38
+ * outweigh the text many times over:
37
39
  *
38
40
  * aws-summit-seoul.pptx 2.1MB of slide XML ~540,429 tokens 23.8× the conversion
39
41
  * 우리은행이력서.docx 312KB of body XML ~78,113 tokens ~46× the conversion
@@ -43,6 +45,21 @@
43
45
  * a real fallback — this very session unzipped a pptx to verify a conversion
44
46
  * before this ledger existed.
45
47
  *
48
+ * .fig: the file itself. Unlike the Office formats, Read does not refuse a
49
+ * .fig — the extension means nothing to it, so it pulls the binary in as text
50
+ * and the context window fills with tokenised noise. Measured against the same
51
+ * 42,760-token control:
52
+ *
53
+ * plan.fig 26KB +44,195 tokens conversion: 100
54
+ * bootstrap-kit.fig 8.1MB +43,994 tokens conversion: 18,397
55
+ *
56
+ * Two files three hundred times apart in size cost the same, because Read
57
+ * truncates at a cap long before the file ends — which also means the reader
58
+ * gets a fraction of a document for the price of a whole one. The baseline is
59
+ * therefore a flat 44,000 tokens rather than anything per-byte. An earlier
60
+ * version of this file claimed .fig had no measurable baseline at all; that
61
+ * was an assumption about Read's behaviour that turned out to be wrong.
62
+ *
46
63
  * Erring low is deliberate throughout. A savings figure that flatters the
47
64
  * tool is worth less than one the user can trust.
48
65
  *
@@ -79,10 +96,10 @@ const ATTACHMENT_BASELINE = {
79
96
  '.docx': { markup: true },
80
97
  '.xlsx': { markup: true },
81
98
  '.xls': { ratio: 1 },
82
- // A .fig unzips to another binary (Figma's kiwi format), so unlike the
83
- // office containers there is no readable markup to price the fallback
84
- // against. Converting is the only way to read it at all; no money claimed.
85
- '.fig': { ratio: 1 },
99
+ // Read swallows a .fig instead of refusing it, at a flat ~44,000 tokens
100
+ // whatever the file's size (see the header). Rounded down from the two
101
+ // measurements, both of which landed just under 44,200.
102
+ '.fig': { fixed: 44_000 },
86
103
  };
87
104
 
88
105
  /**
@@ -106,7 +123,9 @@ function estimateSaving({ ext, pages = 0, markupBytes = 0, markdown = '' }) {
106
123
  const tokens = estimateTokens(markdown);
107
124
  const rule = ATTACHMENT_BASELINE[String(ext).toLowerCase()] || { ratio: 1 };
108
125
  let baseline;
109
- if (rule.perPage && pages > 0) {
126
+ if (rule.fixed) {
127
+ baseline = rule.fixed;
128
+ } else if (rule.perPage && pages > 0) {
110
129
  baseline = pages * rule.perPage;
111
130
  } else if (rule.markup && markupBytes > 0) {
112
131
  baseline = Math.ceil(markupBytes / 4);
package/src/doc2md.cjs CHANGED
@@ -165,10 +165,13 @@ function banner(sourcePath, saving) {
165
165
  if (saving.usd > 0) {
166
166
  const savedTokens = Math.max(0, saving.baseline - saving.tokens);
167
167
  // The alternative differs by format, so the sentence names it: a PDF
168
- // would have been attached, a zip document would have been unpacked.
169
- const alternative = path.extname(sourcePath).toLowerCase() === '.pdf'
170
- ? '원본을 첨부하면'
171
- : '압축을 풀어 본문 XML을 읽으면';
168
+ // would have been attached, a zip document unpacked, and a .fig read
169
+ // straight into the context window, which is the one Read does not
170
+ // refuse.
171
+ const ext = path.extname(sourcePath).toLowerCase();
172
+ const alternative = ext === '.pdf' ? '원본을 첨부하면'
173
+ : ext === '.fig' ? '원본을 그대로 Read 하면'
174
+ : '압축을 풀어 본문 XML을 읽으면';
172
175
  lines.push(
173
176
  `${alternative} 약 ${saving.baseline.toLocaleString('en-US')} 토큰이 드는데, `
174
177
  + `변환본은 ${saving.tokens.toLocaleString('en-US')} 토큰입니다. `
@@ -425,9 +425,9 @@ export function formatReport(data, { color = true, verbose = false, timer = true
425
425
  const byExt = Array.isArray(doc2md.byExt) ? doc2md.byExt : [];
426
426
  // Formats that earned money show it; formats with no baseline to measure
427
427
  // against are counted instead. Printing "$0.00" next to real amounts reads
428
- // as "this format saved nothing", when what it means is that there is no
429
- // working alternative to price it against for .fig there is no readable
430
- // fallback at all, so the conversion is the only way to open the file.
428
+ // as "this format saved nothing", when what it means is that nobody has
429
+ // measured what the alternative costs. Only .xls sits there now the
430
+ // pre-2007 binary, which has no container to price against.
431
431
  const paid = byExt.filter((r) => r.usd > 0);
432
432
  const unpriced = byExt.filter((r) => !(r.usd > 0));
433
433
  const parts = paid.map((r) => `${c(GRAY)}${r.ext} ${r.docs}× ${formatMoney(r.usd)}${c(RESET)}`);