Claude-skill-registry create-analyzer
Create a new packet analyzer for Minecraft Bedrock logs. Generates template code, provides documentation links, and guides testing workflow.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/create-analyzer" ~/.claude/skills/majiayu000-claude-skill-registry-create-analyzer && rm -rf "$T"
manifest:
skills/data/create-analyzer/SKILL.mdsource content
Create Minecraft Bedrock Packet Analyzer
Generate a new domain-specific packet analyzer for analyzing captured Bedrock packets.
Quick Start
- Identify the domain: inventory, movement, entities, chat, blocks, etc.
- Find relevant packets: Search
for packet typesprotocol.d.ts - Generate analyzer file using template below
- Export from index.ts
- Test with captured logs
Analyzer Template
Create file at:
packages/minecraft-logs-analyzers/src/analyzers/{domain}.ts
import { BaseAnalyzer } from "../base-analyzer.ts"; import type { Direction, LogEntry, AnalyzerConfig } from "../types.ts"; const PACKETS_TO_LOG = [ // Add packet names from protocol.d.ts ]; /** * Analyzer for {domain}-related packets. */ export class {Domain}Analyzer extends BaseAnalyzer { readonly config: AnalyzerConfig = { name: "{domain}", packets: PACKETS_TO_LOG, }; constructor(basePath: string, registry?: any) { super(basePath); if (registry) this.registry = registry; this.init(); } protected extractFields( direction: Direction, name: string, packet: any ): LogEntry | null { const base = this.createBaseEntry(direction, name); switch (name) { // Add case for each packet type // case "packet_name": // return { ...base, field: packet.field }; default: return null; } } }
Registration
Add export to
packages/minecraft-logs-analyzers/src/index.ts:
export { {Domain}Analyzer } from "./analyzers/{domain}.ts";
Documentation Locations
| Resource | Location |
|---|---|
| Protocol types | |
| Existing analyzers | |
| Base class | |
| Types | |
| Bedrock plugins | |
Finding Relevant Packets
# Search protocol.d.ts for packet types grep -n "packet_" packages/mineflayer-bedrock/src/protocol.d.ts | head -50 # Find packets used by a specific plugin grep -n "client.on" packages/mineflayer/lib/bedrockPlugins/*.mts # Search for specific packet handling grep -rn "move_player" packages/mineflayer/lib/bedrockPlugins/
Common Packet Domains
| Domain | Key Packets |
|---|---|
| Inventory | , , , , , |
| Movement | , , , , |
| Entities | , , , , |
| Chat | , , |
| Blocks | , , , |
| Combat | , , , |
| World | , , |
Testing Workflow
1. Capture Packets from Real Client
Critical: Always capture from the real Minecraft client first to understand the exact packet format.
# Start capture proxy npm run start --workspace=minecraft-logs-recorder -- -o ./test-logs # Connect Minecraft Bedrock to localhost:19150 # Perform the exact action you want to implement (crafting, chest interaction, etc.) # Disconnect to save logs
The
.jsonl file contains processed packets, .bin contains raw data for replay.
2. Analyze Captured Logs
# View first 20 packets head -20 test-logs/*.jsonl # Count packets by type grep -o '"p":"[^"]*"' test-logs/*.jsonl | sort | uniq -c | sort -rn # Find specific packets grep '"p":"move_player"' test-logs/*.jsonl | head -5
3. Test Your Analyzer
import { {Domain}Analyzer } from 'minecraft-logs-analyzers'; import { createReplayClient } from 'minecraft-logs-recorder/replay'; // Attach analyzer to replay client const analyzer = new {Domain}Analyzer('test-output/replay'); const client = createReplayClient('test-logs/1.21.130-*.bin'); // Analyzer will log packets to test-output/replay-{domain}.jsonl analyzer.attachToBot(client); // Check output // cat test-output/replay-{domain}.jsonl
4. Verify Output Format
# View analyzer output cat test-output/replay-{domain}.jsonl | head -10 # Validate JSON cat test-output/replay-{domain}.jsonl | jq -c '.' | head -10
BaseAnalyzer Methods
| Method | Description |
|---|---|
| Create log entry with t, tick, d, p fields |
| Extract tick from packet.tick |
| Resolve item name from network_id |
| Write entry to JSONL file |
| Log custom debug message |
Override Points
| Method | When to Override |
|---|---|
| Custom filtering (e.g., only log non-empty actions) |
| Required - extract relevant fields |
IMPORTANT: Start with Full Packets
Always log full packet data initially, not filtered/summarized data. This was critical for solving crafting implementation:
// BAD: Filtering too early loses critical details protected shouldLog(name: string, packet: unknown): boolean { // Only log if it has crafting containers... ❌ return hasCraftingContainer; } // GOOD: Log everything first, filter later protected shouldLog(name: string, packet: unknown): boolean { return true; // ✅ See all packets first } // GOOD: Return full packet data in extractFields return { ...base, responses: p.responses, // ✅ Full data, not summarized };
Real client packet captures revealed crucial details that were being filtered out:
usescraft_recipe_auto
container, nothotbar_and_inventorycrafting_input
action requiresresults_deprecated
array with full item dataresult_items- Stack IDs use negative request_id for chained actions
goes directly to inventory, not through cursorplace
Only add filtering after you fully understand the protocol.
Example: Movement Analyzer
import { BaseAnalyzer } from "../base-analyzer.ts"; import type { Direction, LogEntry, AnalyzerConfig } from "../types.ts"; const PACKETS_TO_LOG = [ "player_auth_input", "move_player", "set_actor_motion", ]; export class MovementAnalyzer extends BaseAnalyzer { readonly config: AnalyzerConfig = { name: "movement", packets: PACKETS_TO_LOG, }; constructor(basePath: string) { super(basePath); this.init(); } protected extractFields( direction: Direction, name: string, packet: any ): LogEntry | null { const base = this.createBaseEntry(direction, name); switch (name) { case "player_auth_input": this.updateTick(packet); return { ...base, tick: packet.tick, pos: [packet.position?.x, packet.position?.y, packet.position?.z], yaw: packet.yaw, pitch: packet.pitch, }; case "move_player": return { ...base, pos: [packet.position?.x, packet.position?.y, packet.position?.z], mode: packet.mode, onGround: packet.on_ground, }; case "set_actor_motion": return { ...base, entityId: packet.runtime_entity_id, velocity: [packet.velocity?.x, packet.velocity?.y, packet.velocity?.z], }; default: return null; } } }