Skip to content

Commit 86a3c1b

Browse files
dderuntzclaude
andcommitted
Flasher: real Claude Code Duck binaries via arduino-cli CI
New workflow .github/workflows/cc-firmware-release.yml builds both Claude Code firmware variants (rubber_duck_s3_ducky for the official PCB, rubber_duck_s3 for XIAO Seeed S3) using arduino-cli + the esp32 board package, runs esptool.py merge_bin into single offset-0 binaries (cc-ducky.bin / cc-xiao.bin), and uploads them as GitHub Release assets on cc-v* tag push. Both sketches use only ESP-IDF native drivers (driver/i2s_std, driver/gpio, driver/i2s_pdm, math) — no external Arduino library deps to manage. esp32 core pinned to 3.0.7. Manifests for the two variants reference the latest-release URL pattern, same as bambu, so the flasher auto-tracks new firmware on every cc-v* tag push without page edits. Flasher's Claude Code tab gets real <esp-web-install-button> tags in place of the prior "Coming soon" placeholders. Tab structure + image slots untouched. To release: git tag cc-v0.1.0 && git push origin cc-v0.1.0. Workflow runs ~2 min, binaries appear under Releases, flasher serves them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6e547dc commit 86a3c1b

4 files changed

Lines changed: 189 additions & 22 deletions

File tree

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Build Claude Code Duck firmware variants (Ducky PCB + XIAO Seeed S3)
2+
# and attach them as merged single-file binaries to a GitHub Release.
3+
# The web flasher (docs/flash/index.html) references the binaries via
4+
# the "latest release" URL pattern, so each successful release auto-
5+
# updates what gets installed when a user clicks Flash on the Claude
6+
# Code Duck tab.
7+
#
8+
# Trigger: push of a tag matching `cc-v*` (e.g., cc-v1.0.0). Hand-
9+
# trigger: workflow_dispatch from the Actions tab.
10+
#
11+
# Note: separate from the bambu firmware workflow so each product
12+
# moves on its own cadence — `cc-v*` tags fire this one, `bambu-v*`
13+
# tags fire the bambu one. Same .bin upload pattern, different
14+
# release assets (cc-ducky.bin / cc-xiao.bin).
15+
name: Claude Code Duck firmware release
16+
17+
on:
18+
push:
19+
tags:
20+
- 'cc-v*'
21+
workflow_dispatch:
22+
23+
permissions:
24+
contents: write # needed to create releases / upload assets
25+
26+
jobs:
27+
build-and-release:
28+
runs-on: ubuntu-latest
29+
strategy:
30+
fail-fast: false
31+
matrix:
32+
include:
33+
- variant: ducky
34+
sketch_dir: firmware/rubber_duck_s3_ducky
35+
sketch_file: rubber_duck_s3_ducky.ino
36+
asset_name: cc-ducky.bin
37+
label: Claude Code Duck — Ducky PCB
38+
- variant: xiao
39+
sketch_dir: firmware/rubber_duck_s3
40+
sketch_file: rubber_duck_s3.ino
41+
asset_name: cc-xiao.bin
42+
label: Claude Code Duck — XIAO Seeed S3
43+
44+
steps:
45+
- uses: actions/checkout@v4
46+
47+
- name: Install arduino-cli
48+
run: |
49+
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=$HOME/bin sh
50+
echo "$HOME/bin" >> $GITHUB_PATH
51+
52+
- name: Install ESP32 board package
53+
run: |
54+
arduino-cli config init
55+
arduino-cli config add board_manager.additional_urls \
56+
https://espressif.github.io/arduino-esp32/package_esp32_index.json
57+
arduino-cli core update-index
58+
# Pin to a known-good ESP32 core. Bump deliberately when
59+
# validating against a newer arduino-esp32 release.
60+
arduino-cli core install esp32:esp32@3.0.7
61+
62+
- name: Install esptool (for merge_bin)
63+
run: pip install esptool
64+
65+
- name: Compile ${{ matrix.label }}
66+
# FQBN: esp32:esp32:esp32s3 with USB CDC on boot so the chip
67+
# appears as /dev/cu.usbmodem* without an external UART bridge.
68+
# PSRAM=opi for the WROOM-1 (ducky PCB) — XIAO Sense's S3 also
69+
# has PSRAM enabled in its default partition. PartitionScheme
70+
# default_8MB to match the 8MB flash on both boards.
71+
run: |
72+
arduino-cli compile \
73+
--fqbn 'esp32:esp32:esp32s3:USBMode=hwcdc,CDCOnBoot=cdc,PartitionScheme=default_8MB,FlashSize=8M,PSRAM=opi' \
74+
--output-dir build_${{ matrix.variant }} \
75+
--build-property 'build.extra_flags=-DUSB_MANUFACTURER="IDEO" -DUSB_PRODUCT="Duck Duck Duck"' \
76+
${{ matrix.sketch_dir }}
77+
78+
- name: Merge into single flashable image
79+
# arduino-cli output: <sketch>.ino.bootloader.bin /
80+
# .partitions.bin / .ino.bin. Merge into one offset-0 binary
81+
# so the ESP Web Tools manifest can reference a single file.
82+
run: |
83+
cd build_${{ matrix.variant }}
84+
ls -la
85+
BOOT=$(ls *.bootloader.bin | head -n1)
86+
PART=$(ls *.partitions.bin | head -n1)
87+
APP=$(ls *.ino.bin | head -n1)
88+
esptool.py --chip esp32s3 merge_bin \
89+
-o ../${{ matrix.asset_name }} \
90+
--flash_mode dio --flash_size 8MB --flash_freq 80m \
91+
0x0 "$BOOT" \
92+
0x8000 "$PART" \
93+
0x10000 "$APP"
94+
cd ..
95+
ls -la ${{ matrix.asset_name }}
96+
97+
- name: Upload merged binary as artifact
98+
uses: actions/upload-artifact@v4
99+
with:
100+
name: ${{ matrix.asset_name }}
101+
path: ${{ matrix.asset_name }}
102+
if-no-files-found: error
103+
104+
release:
105+
needs: build-and-release
106+
runs-on: ubuntu-latest
107+
if: startsWith(github.ref, 'refs/tags/cc-v')
108+
steps:
109+
- uses: actions/checkout@v4
110+
- name: Download all variant binaries
111+
uses: actions/download-artifact@v4
112+
with:
113+
path: dist
114+
115+
- name: Flatten artifact dir layout
116+
run: |
117+
mkdir -p release
118+
find dist -name '*.bin' -exec cp {} release/ \;
119+
ls -la release/
120+
121+
- name: Create / update GitHub Release
122+
uses: softprops/action-gh-release@v2
123+
with:
124+
name: Claude Code Duck firmware ${{ github.ref_name }}
125+
files: release/*.bin
126+
generate_release_notes: true
127+
body: |
128+
Pre-built firmware binaries for the Claude Code Duck (Mac
129+
companion that watches your Claude Code session and
130+
reacts via voice + servo + LED).
131+
132+
**Flash from a browser:** https://ideo.github.io/Rubber-Duck/flash/
133+
(Chrome / Edge — uses WebSerial, no install needed.)
134+
135+
**Flash from the command line:**
136+
```
137+
python -m esptool --chip esp32s3 -p /dev/cu.usbmodem101 \
138+
write_flash 0x0 cc-ducky.bin
139+
```
140+
141+
Variants:
142+
- `cc-ducky.bin` — official IDEO Duck PCB (custom WROOM-1)
143+
- `cc-xiao.bin` — DIY build on standard XIAO Seeed ESP32-S3

docs/flash/index.html

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -176,44 +176,36 @@ <h1>Flash a Duck</h1>
176176
<!-- ============ CLAUDE CODE DUCK TAB ============ -->
177177
<div id="panel-cc">
178178
<p>The Mac companion duck — sits on your desk, watches your
179-
Claude Code session, reacts with voice + servo + LED. The widget
180-
app does most of the work; this firmware lives on the duck and
181-
handles audio, animation, and serial protocol with the widget.</p>
179+
Claude Code session, reacts with voice + servo + LED. Pairs
180+
with the
181+
<a href="https://github.com/ideo/Rubber-Duck/releases">Duck Duck Duck</a>
182+
widget app on macOS. This firmware handles audio, animation,
183+
and the serial protocol with the widget.</p>
182184

183-
<div class="browser-warn" style="background:#eef5ff;border-color:#9ec0e8">
184-
<strong>Browser flashing for the Claude Code Duck is
185-
coming soon.</strong> The Arduino-sketch build pipeline isn't
186-
wired into our release workflow yet. For now, flash via the
187-
Arduino IDE — see
188-
<a href="https://github.com/ideo/Rubber-Duck/tree/main/firmware">firmware/README.md</a>
189-
for board selection, library deps, and serial port setup.
190-
</div>
191-
192-
<div class="variant" style="opacity:.65">
185+
<div class="variant">
193186
<div class="img"><img src="img/ducky-pcb.jpg" alt="Ducky PCB"
194187
onerror="this.style.display='none'"></div>
195188
<div class="body">
196189
<h3>Ducky PCB</h3>
197190
<p class="meta">The official IDEO Duck product PCB — custom
198191
WROOM-1 board with full-duplex I2S, ChirpSynth audio engine,
199-
servo + LED on the standard pinout.</p>
200-
<button disabled style="background:#eee;color:#999;border:1px solid #ddd;border-radius:8px;padding:.6em 1.2em;cursor:not-allowed">
201-
Flash — coming soon
202-
</button>
192+
servo + addressable LED ring.</p>
193+
<esp-web-install-button manifest="manifests/cc-ducky.json">
194+
</esp-web-install-button>
203195
</div>
204196
</div>
205197

206-
<div class="variant" style="opacity:.65">
198+
<div class="variant">
207199
<div class="img"><img src="img/xiao-s3.jpg" alt="XIAO Seeed ESP32-S3"
208200
onerror="this.style.display='none'"></div>
209201
<div class="body">
210202
<h3>XIAO Seeed S3</h3>
211203
<p class="meta">Standard XIAO Seeed ESP32-S3 + cobbled mic /
212204
amp / servo. DIY build path for hobbyists who don't want to
213-
order the custom PCB.</p>
214-
<button disabled style="background:#eee;color:#999;border:1px solid #ddd;border-radius:8px;padding:.6em 1.2em;cursor:not-allowed">
215-
Flash — coming soon
216-
</button>
205+
order the custom PCB. (No NeoPixel ring — that's the one
206+
hardware difference from the official PCB.)</p>
207+
<esp-web-install-button manifest="manifests/cc-xiao.json">
208+
</esp-web-install-button>
217209
</div>
218210
</div>
219211
</div>

docs/flash/manifests/cc-ducky.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "Claude Code Duck — Ducky PCB",
3+
"version": "latest",
4+
"new_install_prompt_erase": true,
5+
"builds": [
6+
{
7+
"chipFamily": "ESP32-S3",
8+
"parts": [
9+
{
10+
"path": "https://github.com/ideo/Rubber-Duck/releases/latest/download/cc-ducky.bin",
11+
"offset": 0
12+
}
13+
]
14+
}
15+
]
16+
}

docs/flash/manifests/cc-xiao.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "Claude Code Duck — XIAO Seeed S3",
3+
"version": "latest",
4+
"new_install_prompt_erase": true,
5+
"builds": [
6+
{
7+
"chipFamily": "ESP32-S3",
8+
"parts": [
9+
{
10+
"path": "https://github.com/ideo/Rubber-Duck/releases/latest/download/cc-xiao.bin",
11+
"offset": 0
12+
}
13+
]
14+
}
15+
]
16+
}

0 commit comments

Comments
 (0)