[2026.04 Week 3] Five Trending Repos of the Week
TL;DR
Skill registries and Claude Code setups still fill the top of the list, but most of the net-new stars this week went to tools with a concrete domain (audio, trace analysis, file typing, VMs).
Token-cost tools jumped into the top 20. rtk, caveman, codeburn, and graphify all trended together, with “cut token use by X%” pitches.
Hardware-adjacent builds showed up near the top. A wearable AI, a PLFM phased-array RADAR, and a lightweight microVM runtime all trended this week.
Voice and audio pipelines kept showing up. VoxCPM2, VibeVoice, jamiepine/voicebox, and the omi wearable all trended together.
This week’s picks:
magika (⭐ 15.9k). Google’s file type detector. A 2048-byte tensor, 1024 from the head and 1024 from the tail, feeds an ONNX model that decides between 200+ content types.
smolvm (⭐ 1.8k). Rust tool for packing microVMs into a single portable binary. The clever bit is how it embeds platform-specific libraries after macOS code-signing runs.
tegaki (⭐ 2.0k). Turns any font into an animated handwriting stroke. Pure TypeScript pipeline with a surprising shortcut for deciding which end of a stroke is the pen entry.
omi (⭐ 10.4k). Always-on wearable that streams conversations over Bluetooth Low Energy (BLE). The firmware fragments each audio frame across multiple BLE notifications with a 3-byte header for reassembly.
chrome-devtools-mcp (⭐ 36.1k). Chrome DevTools exposed as MCP tools. Instead of shipping 50MB of trace JSON back to the model, it runs the real DevTools trace engine and formats only the insights.
google/magika
⭐ 15.9k · Python
magika detects file types from raw bytes using a small neural network. It runs client-side, ships as a Python wheel or a Rust CLI, and can distinguish 200+ content types including things like “Python source” vs “IPython notebook” that rule-based file(1) often gets wrong.
The problem is shape. The model wants a fixed-size input tensor, but files range from 50 bytes to 50 gigabytes. Loading the whole file defeats the point of a fast classifier.
python/src/magika/magika.py:439-469
# we read at most block_size bytes
bytes_num_to_read = min(block_size, seekable.size)
if beg_size > 0:
# Read at most `block_size` bytes from the beginning; `lstrip()``
# them (or `strip()` them if the file size is less or equal than
# `block_size`); take at most `beg_size` bytes, and optionally pad
# them with `padding_token` to get to a list of `beg_size` integers.
beg_content = seekable.read_at(0, bytes_num_to_read)
beg_content = beg_content.lstrip()
beg_ints = Magika._get_beg_ints_with_padding(
beg_content, beg_size, padding_token
)
if end_size > 0:
# Read at most `block_size` bytes from the end; `rstrip()`` them (or
# `strip()` them if the file size is less or equal than
# `block_size`); take at most `end_size` bytes (from the end), and
# optionally pad them (at the beginning) with `padding_token` to get
# to a list of `end_size` integers.
end_content = seekable.read_at(
seekable.size - bytes_num_to_read, bytes_num_to_read
)
end_content = end_content.rstrip()
end_ints = Magika._get_end_ints_with_padding(
end_content, end_size, padding_token
)The config sets beg_size = end_size = 1024 and block_size = 4096. So for any file, magika reads one 4KB window from the head and one 4KB window from the tail. Never the middle. Middle bytes rarely carry type signatures. They’re either compressed noise or uniform text.
Two details make this work for a wider range of files than you’d expect. First, the whitespace stripping. A JSON file might open with 2KB of blank lines. Without lstrip(), the tensor would be half padding before any actual content. Second, the padding token is 256, which is outside the 0-255 byte range. The model can distinguish “we ran out of file” from “we saw a null byte” without ambiguity.
Packing both ends into the same tensor also catches file formats that put their magic at the end. ZIP central directories, MP4 atoms, PDF xref tables. Reading only the head would misclassify all of them.
smol-machines/smolvm
⭐ 1.8k · Rust
smolvm packs a microVM (kernel, init, rootfs, OCI layers) into a single binary that runs anywhere. It provides isolation by default, so you can ship untrusted code or a reproducible dev environment as one file that boots a per-workload VM in under 200ms without Docker or a preinstalled hypervisor. It’s built on libkrun and depends on two platform-specific shared libraries, libkrun and libkrunfw. Bundling those into one portable stub is harder than it sounds because of macOS code-signing.
On macOS, codesign computes a hash over the entire Mach-O binary. If you append anything after signing, the signature is invalid. If you embed libraries inside the binary before signing, the libs have to be valid Mach-O slots, which libkrunfw isn’t. The usual workaround is a tarball plus a wrapper, which breaks the “single binary” story.
smolvm picks a third path. Sign the stub, then tack a compressed libs blob onto the end with a 32-byte footer. At runtime, the stub reads its own tail.
crates/smolvm-pack/src/extract.rs:953-980
pub fn extract_libs_from_binary(exe_path: &Path, debug: bool) -> std::io::Result<Option<PathBuf>> {
use crate::format::{LibsFooter, LIBS_FOOTER_SIZE};
let mut file = File::open(exe_path)?;
let file_size = file.metadata()?.len();
if file_size < LIBS_FOOTER_SIZE as u64 {
return Ok(None);
}
// Read the last 32 bytes
file.seek(SeekFrom::End(-(LIBS_FOOTER_SIZE as i64)))?;
let mut footer_buf = [0u8; LIBS_FOOTER_SIZE];
file.read_exact(&mut footer_buf)?;
let footer = match LibsFooter::from_bytes(&footer_buf) {
Ok(f) => f,
Err(_) => return Ok(None), // No SMOLLIBS footer — no embedded libs
};
// Cache key based on libs content hash
file.seek(SeekFrom::Start(footer.libs_offset))?;
let mut hasher = crc32fast::Hasher::new();
// ... hash the embedded libs blob
let cache_base = dirs::cache_dir()?;
let libs_cache_dir = cache_base
.join("smolvm-libs")
.join(format!("{:08x}", libs_checksum));The footer magic is SMOLLIBS. If it’s there, smolvm hashes the embedded blob, extracts to ~/.cache/smolvm-libs/<crc32>/lib/, and points LD_LIBRARY_PATH at it. If the magic isn’t there, it falls back to system libraries. Same binary behaves differently depending on whether it’s been packed.
Since multiple processes can race on first launch, the extraction takes an exclusive flock on the cache directory and re-checks whether the lib dir exists inside the lock. Standard pattern, but worth calling out because getting this wrong would corrupt the cache under concurrent spawns.
The macOS signature stays valid because the OS verifies the signed slot range, and the appended blob is outside that range. It’s the same trick self-extracting installers have used for years, adapted for signed binaries.
KurtGokhan/tegaki
⭐ 2.0k · TypeScript
tegaki takes a font glyph (the vector shape for one character in a font, like the “A” or “O”) and animates it being drawn, stroke by stroke. You’d think fonts already contain stroke order information. They don’t. A font glyph is a filled outline. The letter “O” is one closed path representing the shape of an O, with no annotation about where a pen should enter or which direction to go.
The generator runs a pipeline that rasterizes the glyph, skeletonizes it down to a 1-pixel-wide center line (Zhang-Suen thinning), and traces the skeleton into polylines. For each polyline, which end is the start?
packages/generator/src/processing/stroke-order.ts:36-69
function orientPolyline(points: Point[]): Point[] {
if (points.length < 2) return points;
const start = points[0]!;
const end = points[points.length - 1]!;
// Near-closed loop: rotate to start from the leftmost point
if (dist(start, end) < 5) {
let bestIdx = 0;
let bestX = points[0]!.x;
let bestY = points[0]!.y;
for (let i = 1; i < points.length; i++) {
const p = points[i]!;
if (p.x < bestX || (p.x === bestX && p.y < bestY)) {
bestX = p.x;
bestY = p.y;
bestIdx = i;
}
}
if (bestIdx !== 0) {
return [...points.slice(bestIdx), ...points.slice(1, bestIdx + 1)];
}
return points;
}
// Open polyline: prefer starting from the left (with top as tiebreaker)
const startScore = start.y + start.x * ORIENT_X_WEIGHT;
const endScore = end.y + end.x * ORIENT_X_WEIGHT;
if (endScore < startScore) {
return [...points].reverse();
}
return points;
}For open strokes, the scoring is y + x * ORIENT_X_WEIGHT. Lower score wins, so the polyline is oriented to start from the top-left-ish end. The x weight is smaller than 1, so vertical position tiebreaks in favor of “higher,” which matches how most people hold a pen.
For closed loops where the first and last points are within 5 pixels of each other, there’s no natural end. The code rotates the point list so the leftmost point is the start, because that’s where a human would typically begin an “O.” The rotation is a splice that slices from bestIdx forward, then back around to bestIdx + 1, preserving the loop.
No ML model or handwriting training data. A few lines of geometry match Latin handwriting convention well enough that animations look natural. The trade-off is baked into the constants. Change ORIENT_X_WEIGHT and you’d get something that looks wrong for English but maybe right for a right-to-left script.
BasedHardware/omi
⭐ 10.4k · Dart, C
omi is a wearable that captures conversations and streams them to a phone. The firmware records at 16kHz, encodes to Opus (a low-latency voice codec also used by WebRTC and Discord) in 20ms frames (~80 bytes each), and ships them over Bluetooth Low Energy (BLE, the low-power wireless protocol phones use for wearables and headphones).
Lets take a look at BLE MTU sizes (the max payload per BLE packet), which on older phones cap notifications at 23 bytes of payload. An 80-byte Opus frame can’t fit in one packet.
The firmware solves this by fragmenting each Opus frame across multiple BLE notifications and tagging every fragment with a 3-byte header so the phone can reassemble them.
omi/firmware/omi/src/lib/core/transport.c:944-1009
static bool push_to_gatt(struct bt_conn *conn)
{
uint8_t *buffer = tx_buffer + RING_BUFFER_HEADER_SIZE;
uint32_t offset = 0;
uint8_t index = 0;
while (offset < tx_buffer_size) {
uint32_t packet_size = MIN(current_mtu - NET_BUFFER_HEADER_SIZE, tx_buffer_size - offset);
// Block until a throttle slot is available. This preserves every audio
// packet while still guaranteeing AUDIO_TX_RESERVED_SLOTS remain free
// for battery/diagnostic/status notifications at all times.
k_sem_take(&audio_tx_sem, K_FOREVER);
uint32_t id = packet_next_index++;
pusher_temp_data[0] = id & 0xFF;
pusher_temp_data[1] = (id >> 8) & 0xFF;
pusher_temp_data[2] = index;
memcpy(pusher_temp_data + NET_BUFFER_HEADER_SIZE, buffer + offset, packet_size);
offset += packet_size;
index++;
struct bt_gatt_notify_params params = {
.attr = &audio_service.attrs[1],
.data = pusher_temp_data,
.len = packet_size + NET_BUFFER_HEADER_SIZE,
.func = on_audio_tx_done,
};
int err = bt_gatt_notify_cb(conn, ¶ms);
// ...
}
return true;
}Every BLE fragment has 3 prefix bytes. Two bytes are a monotonically increasing frame id (low, then high), and one byte is the sub-fragment index within that frame. So the phone sees a stream like (42, 0), (42, 1), (42, 2), (43, 0), (43, 1). If fragment (43, 1) is lost, the phone knows frame 43 is broken without waiting for a timeout, and the Opus decoder skips that frame cleanly instead of desynchronizing.
The semaphore audio_tx_sem throttles outbound notifications so there’s always room for battery and status updates. Without it, a burst of audio packets could starve control traffic on the same connection.
The design pays off because the reassembly protocol is codec-agnostic. The firmware could switch from Opus to raw PCM, or to a different sample rate, and the 3-byte header would still tell the phone where frame boundaries are. The phone-side Dart code (pure_streaming_stt.dart) uses these boundaries to transcode Opus frame-by-frame rather than concatenating first and decoding later, which matters because the Opus decoder keeps state across frames.
ChromeDevTools/chrome-devtools-mcp
⭐ 36.1k · TypeScript
chrome-devtools-mcp exposes Chrome DevTools as MCP tools so a coding agent can launch a browser, navigate, take screenshots, and record performance traces.
A Chrome performance trace is a JSON blob with hundreds of thousands of events. A 10-second recording can easily be 50MB. Dumping that into a model’s context is wasteful and wouldn’t fit anyway. The server runs the real DevTools trace engine in-process, extracts structured insights, and sends only formatted summaries.
src/trace-processing/parse.ts:27-71
const engine = DevTools.TraceEngine.TraceModel.Model.createWithAllHandlers();
export async function parseRawTraceBuffer(
buffer: Uint8Array<ArrayBufferLike> | undefined,
): Promise<TraceResult | TraceParseError> {
engine.resetProcessor();
// ...
const data = JSON.parse(asString) as
| { traceEvents: DevTools.TraceEngine.Types.Events.Event[] }
| DevTools.TraceEngine.Types.Events.Event[];
const events = Array.isArray(data) ? data : data.traceEvents;
await engine.parse(events);
const parsedTrace = engine.parsedTrace();
// ...
const insights = parsedTrace?.insights ?? null;
return { parsedTrace, insights };
}The engine is DevTools.TraceEngine.TraceModel.Model, the same engine that powers the Performance panel in Chrome’s dev tools. It’s bundled as a third-party vendor module. When the agent stops a trace, the server parses the full event stream, asks the engine to compute Insights (LCPBreakdown, DocumentLatency, INPBreakdown, CLSCulprits, etc.), and keeps the whole parsed trace in memory.
What gets sent to the model is a formatted summary. The PerformanceTraceFormatter produces a flat text summary keyed by insight set ids. Then the performance_analyze_insight tool lets the model query a specific insight by id and name.
src/tools/performance.ts:162-177
handler: async (request, response, context) => {
const lastRecording = context.recordedTraces().at(-1);
if (!lastRecording) {
response.appendResponseLine(
'No recorded traces found. Record a performance trace so you have Insights to analyze.',
);
return;
}
response.attachTraceInsight(
lastRecording,
request.params.insightSetId,
request.params.insightName as InsightName,
);
},This is an interesting MCP pattern. The heavyweight artifact (the parsed trace) lives server-side across tool calls. The model pages through structured views of it rather than being handed the raw data. If the summary mentions LCPBreakdown, the model calls performance_analyze_insight to pull detail on that one insight. The Chrome UX Report data for real-world field metrics is fetched lazily when the formatter needs it, so the first tool call returns quickly.
The lesson generalizes. When an MCP tool produces output that’s too big for a context window, keep the artifact server-side, send a map, and let the model request zoom-ins on demand.

