toldya 0.1.0 โ†’ 0.3.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
@@ -4,6 +4,8 @@
4
4
 
5
5
  <p align="center"><b>Stop repeating yourself to your AI.</b></p>
6
6
 
7
+ <p align="center">Part of <a href="https://singhlabs.dev/toldya/">Singh Labs</a>, small tools for people who code with AI.</p>
8
+
7
9
  <p align="center">
8
10
  <a href="https://www.npmjs.com/package/toldya"><img src="https://img.shields.io/npm/v/toldya?color=ffd23f&label=npm" alt="npm version"></a>
9
11
  <img src="https://img.shields.io/badge/dependencies-0-98a179" alt="zero dependencies">
@@ -41,6 +43,7 @@ npx toldya --to AGENTS.md # write rules somewhere else
41
43
  npx toldya --add 1,3 # take repeats 1 and 3 without the prompts
42
44
  npx toldya --min 5 # only things you said 5+ times
43
45
  npx toldya --json # machine-readable report
46
+ npx toldya --card # an image of your top repeats, to share
44
47
  ```
45
48
 
46
49
  Without `--dry` or `--add`, it asks about each repeat: **y** adds it, **n** skips it,
@@ -50,6 +53,14 @@ Nothing is written without a yes.
50
53
  Run it again a week later and every rule it added shows how often you said it before,
51
54
  and how often since.
52
55
 
56
+ ## Share it
57
+
58
+ `npx toldya --all --card` opens your card in the browser. Save it as a PNG, then use the
59
+ **Post on X** or **Share on LinkedIn** button: the post text is written for you. The card is
60
+ drawn on your machine; nothing leaves it unless you click share.
61
+
62
+ <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>
63
+
53
64
  ## What it reads, and what it doesn't
54
65
 
55
66
  - Only **your own messages** in Claude Code's history on this machine (`~/.claude/projects`).
@@ -68,7 +79,6 @@ roughly a month.
68
79
 
69
80
  - **Team mode:** `npx toldya owner/repo` reads a repo's pull-request review comments (from
70
81
  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
82
  - Codex history (not tested on real files yet, so not claimed).
73
83
 
74
84
  ## 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.3.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: save it as a PNG, then post it. Nothing leaves your machine unless you click share.`);
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,53 @@
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
+ // unless you click a share button: that opens X or LinkedIn with a pre-filled post.
4
+ import { readFileSync } from 'node:fs';
5
+
6
+ const MARK = new URL('../assets/mark.webp', import.meta.url);
7
+
8
+ export function cardHtml({ repeats, sessions, from, to }) {
9
+ let mark = '';
10
+ try { mark = 'data:image/webp;base64,' + readFileSync(MARK).toString('base64'); } catch {}
11
+ const data = { rows: repeats.slice(0, 5).map((r) => [r.count, r.phrase]), sessions, from, to, mark };
12
+ // JSON inside <script>: escape "<" so a phrase like "</script>" can't end the block.
13
+ const json = JSON.stringify(data).replace(/</g, '\\u003c');
14
+ return `<!doctype html><meta charset="utf-8"><title>toldya card</title>
15
+ <style>body{margin:0;background:#eee;font:16px system-ui;display:grid;place-items:center;min-height:100vh;gap:16px}
16
+ canvas{max-width:95vw;height:auto;box-shadow:0 2px 12px #0002}.row{display:flex;gap:14px;flex-wrap:wrap;justify-content:center}p{margin:0;color:#555;max-width:60ch;text-align:center}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>
17
+ <canvas id="c" width="1200" height="675"></canvas>
18
+ <div class="row"><a id="save" download="toldya.png" href="#">1. Save as PNG</a><a id="x" target="_blank" rel="noopener" href="#">2. Post on X</a><a id="li" target="_blank" rel="noopener" href="#">2. Share on LinkedIn</a></div>
19
+ <p>Save the picture first, then attach it to your post. The card is drawn on your machine; nothing leaves it unless you click a share button.</p>
20
+ <script>
21
+ const D = ${json}, c = document.getElementById('c'), x = c.getContext('2d'), INK = '#0d0d0d';
22
+ const REPO = 'github.com/singhlabsdev/toldya';
23
+ const first = D.rows[0] || [0, ''];
24
+ const post = 'My AI has heard "' + first[1] + '" ' + first[0] + ' times ๐Ÿ˜… Counted from my own Claude Code history with npx toldya';
25
+ document.getElementById('x').href = 'https://x.com/intent/post?text=' + encodeURIComponent(post) + '&url=' + encodeURIComponent('https://' + REPO);
26
+ document.getElementById('li').href = 'https://www.linkedin.com/feed/?shareActive=true&text=' + encodeURIComponent(post + ' https://' + REPO);
27
+ const font = (w, s) => (x.font = w + ' ' + s + 'px system-ui, "Segoe UI", sans-serif');
28
+ const pill = (px, py, w, h, fill) => {
29
+ x.fillStyle = INK; x.beginPath(); x.roundRect(px + 5, py + 5, w, h, h / 2); x.fill();
30
+ x.fillStyle = fill; x.beginPath(); x.roundRect(px, py, w, h, h / 2); x.fill();
31
+ x.lineWidth = 3; x.strokeStyle = INK; x.stroke();
32
+ };
33
+ const fit = (s, max) => { if (x.measureText(s).width <= max) return s;
34
+ while (s.length > 1 && x.measureText(s + 'โ€ฆ').width > max) s = s.slice(0, -1); return s + 'โ€ฆ'; };
35
+ function draw(img) {
36
+ x.fillStyle = '#fff'; x.fillRect(0, 0, 1200, 675);
37
+ if (img) x.drawImage(img, 30, 150, 330, 330);
38
+ x.fillStyle = INK; font(800, 46); x.fillText('Things I keep telling my AI', 390, 100);
39
+ D.rows.forEach(([n, p], i) => {
40
+ const y = 150 + i * 88;
41
+ pill(390, y, 118, 58, '#ffd23f');
42
+ x.fillStyle = INK; font(800, 30); x.textAlign = 'center'; x.fillText(n + 'ร—', 449, y + 40);
43
+ x.textAlign = 'left'; font(600, 32); x.fillText(fit('โ€œ' + p + 'โ€', 640), 532, y + 41);
44
+ });
45
+ font(500, 22); x.fillStyle = '#6b6b6b';
46
+ x.fillText(D.sessions + ' Claude Code sessions ยท ' + D.from + ' โ€“ ' + D.to, 390, 628);
47
+ pill(990, 596, 180, 50, INK); x.fillStyle = '#fff'; font(700, 24); x.fillText('npx toldya', 1017, 629);
48
+ x.fillStyle = '#6b6b6b'; font(500, 18); x.textAlign = 'right'; x.fillText(REPO, 1170, 580); x.textAlign = 'left';
49
+ document.getElementById('save').href = c.toDataURL('image/png');
50
+ }
51
+ if (D.mark) { const i = new Image(); i.onload = () => draw(i); i.onerror = () => draw(); i.src = D.mark; } else draw();
52
+ </script>`;
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toldya",
3
- "version": "0.1.0",
3
+ "version": "0.3.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
  ],