@heart-of-gold/toolkit 0.1.39 → 0.1.41

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.
@@ -15,19 +15,19 @@
15
15
  "name": "deep-thought",
16
16
  "source": "./plugins/deep-thought",
17
17
  "description": "The Answer Computer — reasoning tools for brainstorming, planning, and deep thinking",
18
- "version": "0.2.9"
18
+ "version": "0.2.10"
19
19
  },
20
20
  {
21
21
  "name": "marvin",
22
22
  "source": "./plugins/marvin",
23
23
  "description": "The Paranoid Android — quality tools for code review, knowledge compounding, and work execution",
24
- "version": "0.3.9"
24
+ "version": "0.3.10"
25
25
  },
26
26
  {
27
27
  "name": "babel-fish",
28
28
  "source": "./plugins/babel-fish",
29
29
  "description": "Universal Translator — media generation tools for audio, image, and video content",
30
- "version": "0.2.7"
30
+ "version": "0.2.9"
31
31
  },
32
32
  {
33
33
  "name": "quellis",
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **Don't Panic.**
4
4
 
5
- 30 skills for AI coding agents. Five plugins. Works with **Claude Code, Codex, OpenCode, Pi**, and any tool supporting the [agentskills.io](https://agentskills.io) standard. Named after *The Hitchhiker's Guide to the Galaxy* because the universe is absurd and your tools should at least have personality.
5
+ 31 skills for AI coding agents. Five plugins. Works with **Claude Code, Codex, OpenCode, Pi**, and any tool supporting the [agentskills.io](https://agentskills.io) standard. Named after *The Hitchhiker's Guide to the Galaxy* because the universe is absurd and your tools should at least have personality.
6
6
 
7
7
  ## Installation
8
8
 
@@ -136,14 +136,15 @@ Configurable sources (RSS, Gmail, HN, web search), narrative briefs, LinkedIn dr
136
136
 
137
137
  ### [Babel Fish](plugins/babel-fish/) — Universal Translator
138
138
 
139
- Turn words into audio. Turn ideas into images. Visualize anything as a terminal mind map.
139
+ Turn words into audio. Turn ideas into images. Visualize anything as a terminal mind map. Stitch screenshots into a LinkedIn-ready carousel PDF.
140
140
 
141
- 3 skills
141
+ 4 skills
142
142
 
143
143
  ```
144
- /babel-fish:audio # TTS, podcasts, voice cloning, sound effects
145
- /babel-fish:image # AI image generation and editing
146
- /babel-fish:visualize # terminal mind maps from any structured content
144
+ /babel-fish:audio # TTS, podcasts, voice cloning, sound effects
145
+ /babel-fish:image # AI image generation and editing
146
+ /babel-fish:visualize # terminal mind maps from any structured content
147
+ /babel-fish:linkedin-carousel # screenshots → LinkedIn document-post PDF with matched backgrounds
147
148
  ```
148
149
 
149
150
  ### [Quellis](plugins/quellis/) — AI Coaching Companion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heart-of-gold/toolkit",
3
- "version": "0.1.39",
3
+ "version": "0.1.41",
4
4
  "type": "module",
5
5
  "description": "Cross-platform installer for Heart of Gold skills — works with Codex, OpenCode, Pi, Claude Code, and more",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "babel-fish",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Universal Translator — media generation tools for audio, image, video, and visualization",
5
5
  "author": {
6
6
  "name": "ondrej-svec",
@@ -3,7 +3,7 @@
3
3
  > "The Babel fish is small, yellow, leathery, and probably the oddest thing in the universe."
4
4
  > It also translates your words into audio and your ideas into images.
5
5
 
6
- A media generation plugin for Claude Code. Turns text into audio, ideas into images, and structured content into terminal mind maps.
6
+ A media generation plugin for Claude Code. Turns text into audio, ideas into images, structured content into terminal mind maps, and screenshots into LinkedIn-ready carousel PDFs.
7
7
 
8
8
  ## Security & Trust
9
9
 
@@ -36,10 +36,14 @@ Supports Gemini and FLUX models via OpenRouter API.
36
36
  ### `/babel-fish:visualize`
37
37
  Render mind maps and tree visualizations directly in the terminal using Unicode box-drawing characters and ANSI colors. Works over SSH — no browser needed. Use it on brainstorm docs, plan docs, markdown files, or any structured content.
38
38
 
39
+ ### `/babel-fish:linkedin-carousel`
40
+ Turn an ordered set of screenshots or images into a LinkedIn document-post PDF (carousel). Auto-samples each source's corner pixel so per-page padding blends invisibly into the image, picks a sensible canvas aspect from the source dimensions, and renders at 2× with Lanczos resampling. Powered by ImageMagick.
41
+
39
42
  ## Requirements
40
43
 
41
44
  - ElevenLabs API key (for audio)
42
45
  - OpenRouter API key (for image generation)
46
+ - ImageMagick (`magick` on PATH) — for `linkedin-carousel`
43
47
 
44
48
  ## Install
45
49
 
@@ -0,0 +1,139 @@
1
+ ---
2
+ name: linkedin-carousel
3
+ description: >
4
+ Turn a set of screenshots or images into a LinkedIn document-post PDF (carousel)
5
+ with per-page background matching so padding disappears into the source.
6
+ Triggers: linkedin carousel, carousel pdf, document post, slide pdf, screenshots to pdf, linkedin pdf, carousel from images.
7
+ allowed-tools:
8
+ - Read
9
+ - Write
10
+ - Bash
11
+ - Glob
12
+ ---
13
+
14
+ # LinkedIn Carousel — Babel Fish
15
+
16
+ Translating a stack of screenshots into a feed-ready document post. The trick isn't the PDF — it's making the padding invisible.
17
+
18
+ ## Boundaries
19
+
20
+ - **MAY:** read source images, run `magick`, write PNGs and PDFs to the requested output path.
21
+ - **MAY NOT:** upload to LinkedIn, post on the user's behalf, or modify the source images in place.
22
+
23
+ ## Prerequisites
24
+
25
+ - ImageMagick installed (`magick` on PATH). Check with `which magick`.
26
+ - Sources are raster images (PNG/JPG). For non-image inputs, ask the user to export first.
27
+
28
+ ## Phase 0 — Understand
29
+
30
+ **Entry:** User asked for a LinkedIn carousel.
31
+
32
+ Gather, asking only what's missing:
33
+
34
+ - **Sources**: ordered list of image paths (order = page order in the carousel).
35
+ - **Slug**: kebab-case filename for the PDF (e.g. `harness-lab-carousel`). If the user gives a topic, derive it; otherwise ask.
36
+ - **Output path**: default `thoughts/social-media/carousels/<slug>.pdf` if the repo has that dir; otherwise ask.
37
+ - **Aspect preference**: portrait (best feed performance), square, or landscape. If the user has no preference, **infer from sources** in Phase 1.
38
+
39
+ **Exit:** Source list, output path, and aspect intent are known.
40
+
41
+ ## Phase 1 — Plan
42
+
43
+ **Entry:** Inputs gathered.
44
+
45
+ Reason step-by-step before generating:
46
+
47
+ 1. Run `magick identify` on each source to get width × height.
48
+ 2. **Pick canvas aspect** to minimize padding:
49
+ - User asked for portrait → `1080×1350` (×2 = `2160×2700`).
50
+ - User asked for square → `1080×1080` (×2 = `2160×2160`).
51
+ - **No preference** → if the sources cluster around one aspect, pick the **median** w/h ratio — this minimizes total padding across the deck. If sources span a wide range (e.g. 1.6:1 → 2.6:1), pick the aspect that makes the *most* pages padding-free, and accept that the outliers will get bands.
52
+ 3. **Sample background color per page** — the move that makes seams invisible. Need the dimensions first, then sample three corners:
53
+ ```bash
54
+ read W H < <(magick identify -format "%w %h" "$src")
55
+ magick "$src" -format \
56
+ "tl=%[pixel:p{5,5}] tr=%[pixel:p{$((W-5)),5}] bl=%[pixel:p{5,$((H-5))}]\n" \
57
+ info:
58
+ ```
59
+ If all three corners agree, use that color. If they disagree, the source has no clean border — fall back to `#F4EFE3` (or another neutral the user prefers) and tell them.
60
+ 4. Render at **2× target resolution** with Lanczos resampling. 300 DPI in the final PDF.
61
+
62
+ **Exit:** Canvas dimensions chosen, per-page background colors sampled.
63
+
64
+ ## Phase 2 — Build
65
+
66
+ **Entry:** Plan complete.
67
+
68
+ Write the build script to a temp file (avoids shell quoting traps), then run it:
69
+
70
+ ```bash
71
+ #!/bin/bash
72
+ set -e
73
+ OUT=/path/to/output/dir
74
+ SLUG=harness-lab-carousel # from Phase 0
75
+ CANVAS=2160x2700 # from Phase 1
76
+ declare -a SRCS=(/path/1.png /path/2.png /path/3.png)
77
+ declare -a BGS=('#EFF1F5' '#EFF1F5' '#F2ECEB') # from Phase 1 sampling
78
+
79
+ mkdir -p "$OUT"
80
+ PAGES=()
81
+ for i in "${!SRCS[@]}"; do
82
+ # Zero-pad page number so glob ordering survives 10+ pages.
83
+ n=$(printf "%02d" $((i+1)))
84
+ page="$OUT/page${n}.png"
85
+ magick "${SRCS[$i]}" \
86
+ -filter Lanczos \
87
+ -resize "$CANVAS" \
88
+ -background "${BGS[$i]}" \
89
+ -gravity center \
90
+ -extent "$CANVAS" \
91
+ -quality 95 \
92
+ "$page"
93
+ PAGES+=("$page")
94
+ done
95
+
96
+ # Pass pages explicitly in array order — never rely on shell glob ordering.
97
+ magick "${PAGES[@]}" -density 300 -quality 95 "$OUT/${SLUG}.pdf"
98
+ ```
99
+
100
+ **Notes:**
101
+ - `-resize WxH` (without `>` or `!`) fits inside the box preserving aspect; `-extent` then pads to exact canvas using `-background`.
102
+ - Pages are passed to the final `magick` call from the `PAGES` array, not via glob — `page*.png` would put `page10.png` before `page2.png` lexicographically and reorder the carousel.
103
+ - Keep the intermediate `page*.png` files — useful for spot fixes without rebuilding everything.
104
+ - LinkedIn document posts cap at **100 MB** and **300 pages**. A 3–10 page carousel at 2× is typically 1–5 MB.
105
+
106
+ **Exit:** PDF and page PNGs exist at the output path.
107
+
108
+ ## Phase 3 — Review
109
+
110
+ **Entry:** PDF built.
111
+
112
+ Open the PDF for the user (`open <path>` on macOS). Report:
113
+
114
+ - Output path
115
+ - Page count, canvas dimensions, file size
116
+ - Per-page background colors used
117
+ - Any fallbacks (e.g., "page 2's corners disagreed — used neutral cream")
118
+
119
+ Ask whether any page needs a different aspect or a tighter crop. Common follow-ups:
120
+
121
+ - "Page X has too much padding" → re-sample with a different canvas aspect, or crop that source before rebuilding.
122
+ - "Backgrounds don't match" → the source likely has anti-aliased edges; sample further from the corner (`{20,20}`).
123
+ - "Quality looks soft" → confirm 2× and Lanczos; check the source isn't already low-res.
124
+
125
+ ## Constraints
126
+
127
+ - Never modify source images in place.
128
+ - Never upload, share, or post the output. Hand the file to the user.
129
+ - Default to per-page background sampling. Single-color fallback only when sources disagree.
130
+ - Always render at 2× target and downsample at PDF assembly time — not the other way around.
131
+ - Keep the intermediate PNGs unless the user asks to clean them up.
132
+
133
+ ## Output
134
+
135
+ The user receives:
136
+
137
+ - `<output-dir>/<slug>.pdf` — the carousel, ready to upload via LinkedIn's "Add a document".
138
+ - `<output-dir>/page1.png`, `page2.png`, … — per-page renders for inspection.
139
+ - A short summary message with path, dimensions, page count, and any fallbacks taken.
@@ -131,21 +131,47 @@
131
131
  margin-bottom: var(--space-3);
132
132
  }
133
133
 
134
- .theme-toggle {
134
+ .theme-switcher {
135
135
  display: inline-flex;
136
136
  align-items: center;
137
- gap: 10px;
138
- padding: 10px 14px;
137
+ gap: 6px;
138
+ padding: 6px;
139
139
  border-radius: 999px;
140
140
  border: 1px solid var(--border);
141
- background: var(--panel-soft);
141
+ background: color-mix(in srgb, var(--panel-strong) 82%, transparent);
142
142
  box-shadow: var(--shadow-md);
143
- color: var(--muted);
144
143
  backdrop-filter: blur(14px);
145
144
  }
146
145
 
147
- .theme-toggle strong {
146
+ .theme-option {
147
+ display: inline-flex;
148
+ align-items: center;
149
+ justify-content: center;
150
+ gap: 8px;
151
+ min-width: 44px;
152
+ padding: 10px 14px;
153
+ border-radius: 999px;
154
+ color: var(--muted-2);
155
+ transition: background-color 160ms ease, color 160ms ease, transform 160ms ease;
156
+ }
157
+
158
+ .theme-option:hover {
148
159
  color: var(--text-strong);
160
+ background: color-mix(in srgb, var(--panel-soft) 80%, transparent);
161
+ }
162
+
163
+ .theme-option[aria-pressed="true"] {
164
+ background: color-mix(in srgb, var(--accent) 12%, var(--panel-soft));
165
+ color: var(--text-strong);
166
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 24%, var(--border));
167
+ }
168
+
169
+ .theme-option .icon {
170
+ font-size: 15px;
171
+ line-height: 1;
172
+ }
173
+
174
+ .theme-option .text {
149
175
  font-size: 13px;
150
176
  font-weight: 600;
151
177
  }
@@ -414,9 +440,17 @@
414
440
  justify-content: stretch;
415
441
  }
416
442
 
417
- .theme-toggle {
443
+ .theme-switcher {
418
444
  width: 100%;
419
- justify-content: center;
445
+ justify-content: space-between;
446
+ }
447
+
448
+ .theme-option {
449
+ flex: 1 1 0;
450
+ }
451
+
452
+ .theme-option .text {
453
+ display: none;
420
454
  }
421
455
 
422
456
  .hero,
@@ -442,10 +476,20 @@
442
476
  <body>
443
477
  <main>
444
478
  <div class="topbar">
445
- <button class="theme-toggle" id="theme-toggle" type="button" aria-label="Toggle light and dark theme">
446
- <strong>Theme</strong>
447
- <span id="theme-label">Auto</span>
448
- </button>
479
+ <div class="theme-switcher" role="group" aria-label="Theme switcher">
480
+ <button class="theme-option" id="theme-light" type="button" data-theme-value="light" aria-pressed="false" aria-label="Use light theme" title="Light theme">
481
+ <span class="icon" aria-hidden="true">☀</span>
482
+ <span class="text">Light</span>
483
+ </button>
484
+ <button class="theme-option" id="theme-auto" type="button" data-theme-value="auto" aria-pressed="true" aria-label="Use automatic theme" title="Auto theme">
485
+ <span class="icon" aria-hidden="true">◐</span>
486
+ <span class="text">Auto</span>
487
+ </button>
488
+ <button class="theme-option" id="theme-dark" type="button" data-theme-value="dark" aria-pressed="false" aria-label="Use dark theme" title="Dark theme">
489
+ <span class="icon" aria-hidden="true">☾</span>
490
+ <span class="text">Dark</span>
491
+ </button>
492
+ </div>
449
493
  </div>
450
494
 
451
495
  <section class="hero">
@@ -503,10 +547,9 @@
503
547
  (() => {
504
548
  const root = document.documentElement;
505
549
  const metaTheme = document.querySelector('meta[name="theme-color"]');
506
- const button = document.getElementById('theme-toggle');
507
- const label = document.getElementById('theme-label');
508
550
  const storageKey = 'hog-artifact-theme';
509
551
  const media = window.matchMedia('(prefers-color-scheme: dark)');
552
+ const options = Array.from(document.querySelectorAll('.theme-option'));
510
553
 
511
554
  const computeTheme = (mode) => {
512
555
  if (mode === 'auto') return media.matches ? 'dark' : 'light';
@@ -517,18 +560,21 @@
517
560
 
518
561
  const apply = (mode) => {
519
562
  root.dataset.theme = mode;
520
- if (label) label.textContent = mode[0].toUpperCase() + mode.slice(1);
521
563
  if (metaTheme) metaTheme.setAttribute('content', themeColor(mode));
564
+ options.forEach((option) => {
565
+ option.setAttribute('aria-pressed', option.dataset.themeValue === mode ? 'true' : 'false');
566
+ });
522
567
  };
523
568
 
524
569
  const saved = localStorage.getItem(storageKey) || 'auto';
525
570
  apply(saved);
526
571
 
527
- button?.addEventListener('click', () => {
528
- const current = root.dataset.theme || 'auto';
529
- const next = current === 'auto' ? 'dark' : current === 'dark' ? 'light' : 'auto';
530
- localStorage.setItem(storageKey, next);
531
- apply(next);
572
+ options.forEach((option) => {
573
+ option.addEventListener('click', () => {
574
+ const mode = option.dataset.themeValue || 'auto';
575
+ localStorage.setItem(storageKey, mode);
576
+ apply(mode);
577
+ });
532
578
  });
533
579
 
534
580
  media.addEventListener?.('change', () => {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deep-thought",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "description": "The Answer Computer — reasoning tools for brainstorming, planning, architecture design, and deep thinking",
5
5
  "author": {
6
6
  "name": "ondrej-svec",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "marvin",
3
- "version": "0.3.9",
3
+ "version": "0.3.10",
4
4
  "description": "The Paranoid Android — quality tools for code review, knowledge compounding, and work execution",
5
5
  "author": {
6
6
  "name": "ondrej-svec",