toldya 0.1.0 → 0.2.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/README.md CHANGED
@@ -41,6 +41,7 @@ npx toldya --to AGENTS.md # write rules somewhere else
41
41
  npx toldya --add 1,3 # take repeats 1 and 3 without the prompts
42
42
  npx toldya --min 5 # only things you said 5+ times
43
43
  npx toldya --json # machine-readable report
44
+ npx toldya --card # an image of your top repeats, to share
44
45
  ```
45
46
 
46
47
  Without `--dry` or `--add`, it asks about each repeat: **y** adds it, **n** skips it,
@@ -50,6 +51,13 @@ Nothing is written without a yes.
50
51
  Run it again a week later and every rule it added shows how often you said it before,
51
52
  and how often since.
52
53
 
54
+ ## Share it
55
+
56
+ `npx toldya --all --card` opens your card in the browser. One click saves it as a PNG.
57
+ It's drawn on your machine; nothing is uploaded.
58
+
59
+ <p align="center"><img src="assets/card.png" width="100%" alt="toldya card: Things I keep telling my AI. keep it simple 41 times, dont complex this 29 times, dont assume 15 times"></p>
60
+
53
61
  ## What it reads, and what it doesn't
54
62
 
55
63
  - Only **your own messages** in Claude Code's history on this machine (`~/.claude/projects`).
@@ -68,7 +76,6 @@ roughly a month.
68
76
 
69
77
  - **Team mode:** `npx toldya owner/repo` reads a repo's pull-request review comments (from
70
78
  people or any review bot), finds what reviewers keep writing, and opens one PR into `AGENTS.md`.
71
- - **A share card:** one image of your top repeats, for when you want to show someone.
72
79
  - Codex history (not tested on real files yet, so not claimed).
73
80
 
74
81
  ## Requirements
Binary file
package/bin/toldya.mjs CHANGED
@@ -5,15 +5,17 @@
5
5
  // finds the corrections you keep repeating, offers each one as a line for the
6
6
  // rule file your agent reads, and next time counts whether you still say it.
7
7
  import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'node:fs';
8
+ import { spawn } from 'node:child_process';
8
9
  import { join, resolve } from 'node:path';
9
- import { homedir } from 'node:os';
10
+ import { homedir, tmpdir } from 'node:os';
10
11
  import { createInterface } from 'node:readline/promises';
11
12
  import {
12
13
  CLAUDE_ROOT, claudeProjects, projectFor, readClaudeSession,
13
14
  corrections, group, repeats, toRule, alreadyWritten, beforeAfter, isRetry,
14
15
  } from '../lib/core.mjs';
16
+ import { cardHtml } from '../lib/card.mjs';
15
17
 
16
- const VERSION = '0.1.0';
18
+ const VERSION = '0.2.0';
17
19
  const argv = process.argv.slice(2);
18
20
  const has = (f) => argv.includes(f);
19
21
  const opt = (f, d) => { const i = argv.indexOf(f); return i >= 0 && argv[i + 1] ? argv[i + 1] : d; };
@@ -27,6 +29,7 @@ if (has('--help') || has('-h')) {
27
29
  --to FILE write rules to FILE instead (e.g. AGENTS.md)
28
30
  --add 1,3 add repeats by their number, without asking
29
31
  --dry show the report, change nothing
32
+ --card make an image of your top repeats, to share
30
33
  --json machine-readable report, change nothing
31
34
 
32
35
  Reads Claude Code's own history on this machine. Sends nothing anywhere.`);
@@ -112,6 +115,18 @@ if (!fresh.length) {
112
115
  process.exit(0);
113
116
  }
114
117
 
118
+ if (has('--card')) {
119
+ if (!found.length) { console.log(`No correction said ${min}+ times yet, so no card.`); process.exit(0); }
120
+ const file = join(tmpdir(), 'toldya-card.html');
121
+ writeFileSync(file, cardHtml({ repeats: found, sessions, from: day(dates[0]), to: day(dates.at(-1)) }));
122
+ const open = process.platform === 'win32' ? ['cmd', ['/c', 'start', '', file]]
123
+ : [process.platform === 'darwin' ? 'open' : 'xdg-open', [file]];
124
+ try { spawn(open[0], open[1], { detached: true, stdio: 'ignore' }).on('error', () => {}).unref(); } catch {}
125
+ console.log(`Your card: ${file}
126
+ It opens in your browser. Click "Save as PNG". Nothing is uploaded.`);
127
+ process.exit(0);
128
+ }
129
+
115
130
  console.log('You keep telling your AI:');
116
131
  const shown = fresh.slice(0, 10);
117
132
  shown.forEach((r, n) => {
package/lib/card.mjs ADDED
@@ -0,0 +1,44 @@
1
+ // toldya --card: one image of your top repeats, for when you want to show someone.
2
+ // Writes a local HTML page that draws the card and saves it as a PNG. No network.
3
+ import { readFileSync } from 'node:fs';
4
+
5
+ const MARK = new URL('../assets/mark.webp', import.meta.url);
6
+
7
+ export function cardHtml({ repeats, sessions, from, to }) {
8
+ let mark = '';
9
+ try { mark = 'data:image/webp;base64,' + readFileSync(MARK).toString('base64'); } catch {}
10
+ const data = { rows: repeats.slice(0, 5).map((r) => [r.count, r.phrase]), sessions, from, to, mark };
11
+ // JSON inside <script>: escape "<" so a phrase like "</script>" can't end the block.
12
+ const json = JSON.stringify(data).replace(/</g, '\\u003c');
13
+ return `<!doctype html><meta charset="utf-8"><title>toldya card</title>
14
+ <style>body{margin:0;background:#eee;font:16px system-ui;display:grid;place-items:center;min-height:100vh;gap:16px}
15
+ canvas{max-width:95vw;height:auto;box-shadow:0 2px 12px #0002}a{font-weight:700;color:#0d0d0d;background:#ffd23f;border:3px solid #0d0d0d;padding:10px 18px;border-radius:30px;text-decoration:none;box-shadow:4px 4px 0 #0d0d0d}</style>
16
+ <canvas id="c" width="1200" height="675"></canvas><a id="save" download="toldya.png" href="#">Save as PNG</a>
17
+ <script>
18
+ const D = ${json}, c = document.getElementById('c'), x = c.getContext('2d'), INK = '#0d0d0d';
19
+ const font = (w, s) => (x.font = w + ' ' + s + 'px system-ui, "Segoe UI", sans-serif');
20
+ const pill = (px, py, w, h, fill) => {
21
+ x.fillStyle = INK; x.beginPath(); x.roundRect(px + 5, py + 5, w, h, h / 2); x.fill();
22
+ x.fillStyle = fill; x.beginPath(); x.roundRect(px, py, w, h, h / 2); x.fill();
23
+ x.lineWidth = 3; x.strokeStyle = INK; x.stroke();
24
+ };
25
+ const fit = (s, max) => { if (x.measureText(s).width <= max) return s;
26
+ while (s.length > 1 && x.measureText(s + '…').width > max) s = s.slice(0, -1); return s + '…'; };
27
+ function draw(img) {
28
+ x.fillStyle = '#fff'; x.fillRect(0, 0, 1200, 675);
29
+ if (img) x.drawImage(img, 30, 150, 330, 330);
30
+ x.fillStyle = INK; font(800, 46); x.fillText('Things I keep telling my AI', 390, 100);
31
+ D.rows.forEach(([n, p], i) => {
32
+ const y = 150 + i * 88;
33
+ pill(390, y, 118, 58, '#ffd23f');
34
+ x.fillStyle = INK; font(800, 30); x.textAlign = 'center'; x.fillText(n + '×', 449, y + 40);
35
+ x.textAlign = 'left'; font(600, 32); x.fillText(fit('“' + p + '”', 640), 532, y + 41);
36
+ });
37
+ font(500, 22); x.fillStyle = '#6b6b6b';
38
+ x.fillText(D.sessions + ' Claude Code sessions · ' + D.from + ' – ' + D.to, 390, 628);
39
+ pill(990, 596, 180, 50, INK); x.fillStyle = '#fff'; font(700, 24); x.fillText('npx toldya', 1017, 629);
40
+ document.getElementById('save').href = c.toDataURL('image/png');
41
+ }
42
+ if (D.mark) { const i = new Image(); i.onload = () => draw(i); i.onerror = () => draw(); i.src = D.mark; } else draw();
43
+ </script>`;
44
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toldya",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Stop repeating yourself to your AI. Finds what you keep telling your coding agent, writes it into CLAUDE.md / AGENTS.md, and counts whether it stopped.",
5
5
  "bin": {
6
6
  "toldya": "bin/toldya.mjs"
@@ -8,6 +8,7 @@
8
8
  "files": [
9
9
  "bin",
10
10
  "lib",
11
+ "assets/mark.webp",
11
12
  "README.md",
12
13
  "LICENSE"
13
14
  ],