usbeehive: Vibecoding a Linux USB Tool, and Where It Needed a Human
Every USB-C cable looks the same. Some carry 40 Gbps and 240 W. Others carry USB 2.0 and 60 W, and nothing on the outside tells you which. On macOS, Darryl Morley’s WhatCable answers the question in plain English from a menu-bar icon. Zetaphor had already ported it to Linux in C++ with a KDE Plasma widget, and Vadim Zaliva had stripped that down to a command-line tool. So it could be done on Linux. I wanted it in Rust, as a library other tools could build on, and I had two questions of my own:
- Can it be vibecoded? How far does an AI coding agent get porting working C++ to Rust on its own, in a domain where the ground truth is a spec several hundred pages long and whatever the hardware actually reports?
- Does the Linux side hold up? Does the kernel expose enough of the USB Power Delivery negotiation to say “this cable is what’s limiting your charging”, reliably, on real machines?
The answer to both is yes, with a caveat worth a note. usbeehive is now a Rust command-line tool, library and D-Bus daemon. usbee, a GNOME Quick Settings indicator, is built on it. Getting it right took more than prompting.
Day one: surprisingly far
On 1 May 2026 I had Claude Code port the C++ command-line tool to Rust, and I published the result to crates.io the same day. It enumerated USB devices, read USB-C port state from sysfs, decoded Power Delivery data objects and printed a readable tree:
● usb5 USB 2.0 root
└─ 4-Port USB 2.0 Hub
├─ USB2.0 Hub
│ ├─ USB 10/100/1000 LAN
│ └─ Samsung Type-C Monitor
└─ Dell USB Keyboard Hub
├─ Dell USB Keyboard Hub (HID)
└─ USB-PS/2 Optical Mouse
usbeehive’s tree view on a desktop behind a monitor hub. Colors in the terminal encode the negotiated link speed.
For a day’s work, that’s remarkable. It’s also where a demo usually stops, and where the interesting part began.
Tests that agree with the code
The next day I restructured the crate into a proper library and gave it a test suite that builds a fake sysfs tree on disk and runs the real binary against it. The tests immediately found four bugs in the day-one port. The worst one:
- Identity data read in alphabetical order. The kernel exposes a cable’s identity as separate files (
id_header,cert_stat,product, …). The USB-PD spec defines their order, and the decoders assumed it: slot 0 is the ID header, slot 3 is the cable VDO. The port read them in alphabetical filename order instead, so every real cable was silently mis-decoded. No crash, no warning. Just confident, wrong answers.
The others were quieter: port0-partner and port0-cable counted as ports, programmable-supply chargers reported 0 V, and deep hub chains lost their middle nodes.
Five weeks later I hit a subtler version of the same trap. The test fixtures stored charger profiles as bare integers, and the parser expected bare integers. Everything passed. On real kernels, the usb_power_delivery class writes 5000mV, 3000mA, 45000mW and adds a runtime-PM power directory inside each capabilities folder. On actual hardware, every charger profile decoded as 0 V / 0 A / 0 W, plus one junk entry.
This is the lesson I’d pass on to anyone building hardware tools with an AI agent: the model writes the code and the tests from the same understanding. If that understanding is wrong, the tests agree with the bug. The fix isn’t more tests. It’s a fixture captured from a real machine, and someone who has read the spec closely enough to notice that a number is implausible.
Knowing what the numbers mean
The second kind of human input is about meaning. That’s harder to test for, because the code is correct and the claim is wrong.
- A wattage is a ceiling, not a measurement. Even the kernel’s “live”
voltage_nowandcurrent_nowon UCSI systems are derived from the negotiated contract: the selected profile’s voltage times the current the device requested. Nothing is measured. A laptop that has hit its 80% battery charge limit may hold a healthy 65 W contract while requesting 15 W. Shown as “charging at 15 W”, that reads as a bad cable. I split the D-Bus interface into what the device requests and what the contract allows. I also added a “the device is limiting itself” diagnosis, so a healthy cable no longer gets blamed. - A hint that contradicted its own data. A user reported that usbeehive warned about a cable lacking an e-marker even while that cable carried a 20 V / 5 A contract. Under the spec, that contract is only legal over a 5 A e-marked cable. On their hardware, the firmware never answers the cable query, so the kernel creates no cable node and the rating is never visible. The hint only checked “big charger, no rating visible”. It now checks what was actually negotiated.
Neither of these is a coding problem. Both are the kind of thing you only catch by knowing the domain: the spec, the kernel’s sysfs conventions, and how real firmware deviates from both.
Where it stands
Fourteen releases in under five months:
- a Rust library with IO-free decoders
- a hotplug monitor
- a D-Bus daemon that GNOME, KDE or tray apps can subscribe to for “your charging just got slower” signals
- the GNOME indicator
- as of this writing, 174 GitHub stars, over 800 crates.io downloads and outside bug reports
About two thirds of the commits were co-written with Claude. Along the way, the project was renamed from whatcable to usbeehive at the original author’s request. He suggested the new name.
So: yes, it can be vibecoded, up to a convincing port in a day. Yes, the Linux side holds up, wherever the kernel driver exposes the Type-C and PD classes. The rest, the part that makes it trustworthy, came from treating the spec and the hardware as the source of truth, and the agent’s output as a draft.
That’s how I use these tools on client work too: the agent writes quickly, and I check its output against the datasheet and a real board. If you have a device that Linux doesn’t talk to properly yet, get in touch.