{"id":"e1a43b7b-52b8-46c8-8563-71b4d0bce380","shortId":"wydfyD","kind":"skill","title":"earllm-build","tagline":"Build, maintain, and extend the EarLLM One Android project — a Kotlin/Compose app that connects Bluetooth earbuds to an LLM via voice pipeline.","description":"# EarLLM One — Build & Maintain\n\n## Overview\n\nBuild, maintain, and extend the EarLLM One Android project — a Kotlin/Compose app that connects Bluetooth earbuds to an LLM via voice pipeline.\n\n## When to Use This Skill\n\n- When the user mentions \"earllm\" or related topics\n- When the user mentions \"earbudllm\" or related topics\n- When the user mentions \"earbud app\" or related topics\n- When the user mentions \"voice pipeline kotlin\" or related topics\n- When the user mentions \"bluetooth audio android\" or related topics\n- When the user mentions \"sco microphone\" or related topics\n\n## Do Not Use This Skill When\n\n- The task is unrelated to earllm build\n- A simpler, more specific tool can handle the request\n- The user needs general-purpose assistance without domain expertise\n\n## How It Works\n\nEarLLM One is a multi-module Android app (Kotlin + Jetpack Compose) that captures voice from Bluetooth earbuds, transcribes it, sends it to an LLM, and speaks the response back.\n\n## Project Location\n\n`C:\\Users\\renat\\earbudllm`\n\n## Module Dependency Graph\n\n```\napp ──→ voice ──→ audio ──→ core-logging\n  │       │\n  ├──→ bluetooth ──→ core-logging\n  └──→ llm ──→ core-logging\n```\n\n## Modules And Key Files\n\n| Module | Purpose | Key Files |\n|--------|---------|-----------|\n| **core-logging** | Structured logging, performance tracking | `EarLogger.kt`, `PerformanceTracker.kt` |\n| **bluetooth** | BT discovery, pairing, A2DP/HFP profiles | `BluetoothController.kt`, `BluetoothState.kt`, `BluetoothPermissions.kt` |\n| **audio** | Audio routing (SCO/BLE), capture, headset buttons | `AudioRouteController.kt`, `VoiceCaptureController.kt`, `HeadsetButtonController.kt` |\n| **voice** | STT (SpeechRecognizer + Vosk stub), TTS, pipeline | `SpeechToTextController.kt`, `TextToSpeechController.kt`, `VoicePipeline.kt` |\n| **llm** | LLM interface, stub, OpenAI-compatible client | `LlmClient.kt`, `StubLlmClient.kt`, `RealLlmClient.kt`, `SecureTokenStore.kt` |\n| **app** | UI, ViewModel, Service, Settings, all screens | `MainViewModel.kt`, `EarLlmForegroundService.kt`, 6 Compose screens |\n\n## Build Configuration\n\n- **SDK**: minSdk 26, targetSdk 34, compileSdk 34\n- **Build tools**: AGP 8.2.2, Kotlin 1.9.22, Gradle 8.5\n- **Compose BOM**: 2024.02.00\n- **Key deps**: OkHttp, AndroidX Security (EncryptedSharedPreferences), DataStore, Media\n\n## Target Hardware\n\n| Device | Model | Key Details |\n|--------|-------|-------------|\n| Phone | Samsung Galaxy S24 Ultra | Android 14, One UI 6.1, Snapdragon 8 Gen 3 |\n| Earbuds | Xiaomi Redmi Buds 6 Pro | BT 5.3, A2DP/HFP/AVRCP, ANC, LDAC |\n\n## Critical Technical Facts\n\nThese are verified facts from official documentation and device testing. Treat them as ground truth when making decisions:\n\n1. **Bluetooth SCO is limited to 8kHz mono input** on most devices. Some support 16kHz mSBC. BLE Audio (Android 12+, `TYPE_BLE_HEADSET = 26`) supports up to 32kHz stereo. Always prefer BLE Audio when available.\n\n2. **`startBluetoothSco()` is deprecated since Android 12 (API 31).** Use `AudioManager.setCommunicationDevice(AudioDeviceInfo)` and `clearCommunicationDevice()` instead. The project already implements both paths in `AudioRouteController.kt`.\n\n3. **Samsung One UI 7/8 has a known HFP corruption bug** where A2DP playback corrupts the SCO link. The app handles this with silence detection and automatic fallback to the phone's built-in mic.\n\n4. **Redmi Buds 6 Pro tap controls must be set to \"Default\" (Play/Pause)** in the Xiaomi Earbuds companion app. If set to ANC or custom functions, events are handled internally by the earbuds and never reach Android.\n\n5. **Android 14+ requires `FOREGROUND_SERVICE_MICROPHONE` permission** and `foregroundServiceType=\"microphone\"` in the service declaration. `RECORD_AUDIO` must be granted before `startForeground()`.\n\n6. **`VOICE_COMMUNICATION` audio source enables AEC** (Acoustic Echo Cancellation), which is critical to prevent TTS audio output from feeding back into the STT microphone input. Never change this source without understanding the echo implications.\n\n7. **Never play TTS (A2DP) while simultaneously recording via SCO.** The correct sequence is: stop playback → switch to HFP → record → switch to A2DP → play response.\n\n## Data Flow\n\n```\nHeadset button tap\n  → MediaSession (HeadsetButtonController)\n  → TapAction.RECORD_TOGGLE\n  → VoicePipeline.toggleRecording()\n  → VoiceCaptureController captures PCM (16kHz mono)\n  → stopRecording() returns ByteArray\n  → SpeechToTextController.transcribe(pcmData)\n  → LlmClient.chat(messages)\n  → TextToSpeechController.speak(response)\n  → Audio output via A2DP to earbuds\n```\n\n## Adding A New Feature\n\n1. Identify which module(s) are affected\n2. Read existing code in those modules first\n3. Follow the StateFlow pattern — expose state via `MutableStateFlow` / `StateFlow`\n4. Update `MainViewModel.kt` if the feature needs UI integration\n5. Add unit tests in the module's `src/test/` directory\n6. Update docs if the feature changes behavior\n\n## Modifying Audio Capture\n\n- `VoiceCaptureController.kt` handles PCM recording at 16kHz mono\n- WAV headers use hex byte values (not char literals) to avoid shell quoting issues\n- VU meter: RMS calculation → dB conversion → normalized 0-1 range\n- Buffer size: `getMinBufferSize().coerceAtLeast(4096)`\n\n## Changing Bluetooth Behavior\n\n- `BluetoothController.kt` manages discovery, pairing, profile proxies\n- Earbuds detection uses name heuristics: \"buds\", \"earbuds\", \"tws\", \"pods\", \"ear\"\n- Always handle both Bluetooth Classic and BLE Audio paths\n\n## Modifying The Llm Integration\n\n- `LlmClient.kt` defines the interface — keep it generic\n- `StubLlmClient.kt` for offline testing (500ms simulated delay)\n- `RealLlmClient.kt` uses OkHttp to call OpenAI-compatible APIs\n- API keys stored in `SecureTokenStore.kt` (EncryptedSharedPreferences)\n\n## Generating A Build Artifact\n\nAfter code changes, regenerate the ZIP:\n```powershell\n\n## From Project Root\n\npowershell -Command \"Remove-Item 'EarLLM_One_v1.0.zip' -Force -ErrorAction SilentlyContinue; Compress-Archive -Path (Get-ChildItem -Exclude '*.zip','_zip_verify','.git') -DestinationPath 'EarLLM_One_v1.0.zip' -Force\"\n```\n\n## Running Tests\n\n```bash\n./gradlew test --stacktrace          # Unit tests\n./gradlew connectedAndroidTest       # Instrumented tests (device required)\n```\n\n## Phase 2 Roadmap\n\n- Real-time streaming voice conversation with LLM through earbuds\n- Smart assistant: categorize speech into meetings, shopping lists, memos, emails\n- Vosk offline STT integration (currently stubbed)\n- Wake-word detection to avoid keeping SCO open continuously\n- Streaming TTS (Android built-in TTS does NOT support streaming)\n\n## Stt Engine Reference\n\n| Engine | Size | WER | Streaming | Best For |\n|--------|------|-----|-----------|----------|\n| Vosk small-en | 40 MB | ~10% | Yes | Real-time mobile |\n| Vosk lgraph | 128 MB | ~8% | Yes | Better accuracy |\n| Whisper tiny | 40 MB | ~10-12% | No (batch) | Post-utterance polish |\n| Android SpeechRecognizer | 0 MB | varies | Yes | Online, no extra deps |\n\n## Best Practices\n\n- Provide clear, specific context about your project and requirements\n- Review all suggestions before applying them to production code\n- Combine with other complementary skills for comprehensive analysis\n\n## Common Pitfalls\n\n- Using this skill for tasks outside its domain expertise\n- Applying recommendations without understanding your specific context\n- Not providing enough project context for accurate analysis\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.","tags":["earllm","build","antigravity","awesome","skills","sickn33","agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows"],"capabilities":["skill","source-sickn33","skill-earllm-build","topic-agent-skills","topic-agentic-skills","topic-ai-agent-skills","topic-ai-agents","topic-ai-coding","topic-ai-workflows","topic-antigravity","topic-antigravity-skills","topic-claude-code","topic-claude-code-skills","topic-codex-cli","topic-codex-skills"],"categories":["antigravity-awesome-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/sickn33/antigravity-awesome-skills/earllm-build","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add sickn33/antigravity-awesome-skills","source_repo":"https://github.com/sickn33/antigravity-awesome-skills","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 34831 github stars · SKILL.md body (7,776 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-04-24T06:51:06.687Z","embedding":null,"createdAt":"2026-04-18T21:36:27.272Z","updatedAt":"2026-04-24T06:51:06.687Z","lastSeenAt":"2026-04-24T06:51:06.687Z","tsv":"'-1':681 '-12':885 '/gradlew':790,795 '0':680,894 '1':350,597 '1.9.22':284 '10':866,884 '12':369,391 '128':874 '14':310,483 '16khz':364,576,657 '2':385,604,802 '2024.02.00':289 '26':274,373 '3':317,408,612 '31':393 '32khz':377 '34':276,278 '4':444,622 '40':864,882 '4096':687 '5':481,631 '5.3':325 '500ms':731 '6':267,322,447,503,641 '6.1':313 '7':538 '7/8':412 '8':315,876 '8.2.2':282 '8.5':286 '8khz':356 'a2dp':420,542,560,590 'a2dp/hfp':221 'a2dp/hfp/avrcp':326 'accur':954 'accuraci':879 'acoust':510 'ad':593 'add':632 'aec':509 'affect':603 'agp':281 'alreadi':402 'alway':379,707 'analysi':929,955 'anc':327,466 'android':11,38,99,154,309,368,390,480,482,842,892 'androidx':293 'api':392,742,743 'app':15,42,79,155,186,258,427,462 'appli':917,941 'archiv':774 'artifact':752 'ask':989 'assist':140,815 'audio':98,188,226,227,367,382,497,506,519,587,650,714 'audiodeviceinfo':396 'audiomanager.setcommunicationdevice':395 'audioroutecontroller.kt':233,407 'automat':434 'avail':384 'avoid':669,835 'back':176,523 'bash':789 'batch':887 'behavior':648,690 'best':858,902 'better':878 'ble':366,371,381,713 'bluetooth':18,45,97,163,192,217,351,689,710 'bluetoothcontroller.kt':223,691 'bluetoothpermissions.kt':225 'bluetoothstate.kt':224 'bom':288 'boundari':997 'bt':218,324 'bud':321,446,702 'buffer':683 'bug':418 'build':3,4,28,31,124,270,279,751 'built':441,844 'built-in':440,843 'button':232,566 'byte':663 'bytearray':580 'c':179 'calcul':676 'call':738 'cancel':512 'captur':160,230,574,651 'categor':816 'chang':530,647,688,755 'char':666 'childitem':778 'clarif':991 'classic':711 'clear':905,964 'clearcommunicationdevic':398 'client':253 'code':607,754,921 'coerceatleast':686 'combin':922 'command':764 'common':930 'communic':505 'companion':461 'compat':252,741 'compilesdk':277 'complementari':925 'compos':158,268,287 'comprehens':928 'compress':773 'compress-arch':772 'configur':271 'connect':17,44 'connectedandroidtest':796 'context':907,947,952 'continu':839 'control':450 'convers':678,809 'core':190,194,198,209 'core-log':189,193,197,208 'correct':549 'corrupt':417,422 'criteria':1000 'critic':329,515 'current':828 'custom':468 'data':563 'datastor':296 'db':677 'decis':349 'declar':495 'default':455 'defin':721 'delay':733 'dep':291,901 'depend':184 'deprec':388 'describ':968 'destinationpath':784 'detail':303 'detect':432,698,833 'devic':300,340,361,799 'directori':640 'discoveri':219,693 'doc':643 'document':338 'domain':142,939 'ear':706 'earbud':19,46,78,164,318,460,476,592,697,703,813 'earbudllm':70,182 'earllm':2,9,26,36,62,123,147 'earllm-build':1 'earllm_one_v1.0.zip':768,785 'earllmforegroundservice.kt':266 'earlogger.kt':215 'echo':511,536 'email':823 'en':863 'enabl':508 'encryptedsharedprefer':295,748 'engin':852,854 'enough':950 'environ':980 'environment-specif':979 'erroract':770 'event':470 'exclud':779 'exist':606 'expert':985 'expertis':143,940 'expos':617 'extend':7,34 'extra':900 'fact':331,335 'fallback':435 'featur':596,627,646 'feed':522 'file':203,207 'first':611 'flow':564 'follow':613 'forc':769,786 'foreground':485 'foregroundservicetyp':490 'function':469 'galaxi':306 'gen':316 'general':138 'general-purpos':137 'generat':749 'generic':726 'get':777 'get-childitem':776 'getminbuffers':685 'git':783 'gradl':285 'grant':500 'graph':185 'ground':345 'handl':131,428,472,653,708 'hardwar':299 'header':660 'headset':231,372,565 'headsetbuttoncontrol':569 'headsetbuttoncontroller.kt':235 'heurist':701 'hex':662 'hfp':416,556 'identifi':598 'implement':403 'implic':537 'input':358,528,994 'instead':399 'instrument':797 'integr':630,719,827 'interfac':248,723 'intern':473 'issu':672 'item':767 'jetpack':157 'keep':724,836 'key':202,206,290,302,744 'known':415 'kotlin':89,156,283 'kotlin/compose':14,41 'ldac':328 'lgraph':873 'limit':354,956 'link':425 'list':821 'liter':667 'llm':22,49,171,196,246,247,718,811 'llmclient.chat':583 'llmclient.kt':254,720 'locat':178 'log':191,195,199,210,212 'maintain':5,29,32 'mainviewmodel.kt':265,624 'make':348 'manag':692 'match':965 'mb':865,875,883,895 'media':297 'mediasess':568 'meet':819 'memo':822 'mention':61,69,77,86,96,106 'messag':584 'meter':674 'mic':443 'microphon':108,487,491,527 'minsdk':273 'miss':1002 'mobil':871 'model':301 'modifi':649,716 'modul':153,183,200,204,600,610,637 'mono':357,577,658 'msbc':365 'multi':152 'multi-modul':151 'must':451,498 'mutablestateflow':620 'name':700 'need':136,628 'never':478,529,539 'new':595 'normal':679 'offici':337 'offlin':729,825 'okhttp':292,736 'one':10,27,37,148,311,410 'onlin':898 'open':838 'openai':251,740 'openai-compat':250,739 'output':520,588,974 'outsid':937 'overview':30 'pair':220,694 'path':405,715,775 'pattern':616 'pcm':575,654 'pcmdata':582 'perform':213 'performancetracker.kt':216 'permiss':488,995 'phase':801 'phone':304,438 'pipelin':25,52,88,242 'pitfal':931 'play':540,561 'play/pause':456 'playback':421,553 'pod':705 'polish':891 'post':889 'post-utter':888 'powershel':759,763 'practic':903 'prefer':380 'prevent':517 'pro':323,448 'product':920 'profil':222,695 'project':12,39,177,401,761,910,951 'provid':904,949 'proxi':696 'purpos':139,205 'quot':671 'rang':682 'reach':479 'read':605 'real':805,869 'real-tim':804,868 'realllmclient.kt':256,734 'recommend':942 'record':496,545,557,655 'redmi':320,445 'refer':853 'regener':756 'relat':64,72,81,91,101,110 'remov':766 'remove-item':765 'renat':181 'request':133 'requir':484,800,912,993 'respons':175,562,586 'return':579 'review':913,986 'rms':675 'roadmap':803 'root':762 'rout':228 'run':787 's24':307 'safeti':996 'samsung':305,409 'sco':107,352,424,547,837 'sco/ble':229 'scope':967 'screen':264,269 'sdk':272 'secur':294 'securetokenstore.kt':257,747 'send':167 'sequenc':550 'servic':261,486,494 'set':262,453,464 'shell':670 'shop':820 'silenc':431 'silentlycontinu':771 'simpler':126 'simul':732 'simultan':544 'sinc':389 'size':684,855 'skill':57,116,926,934,959 'skill-earllm-build' 'small':862 'small-en':861 'smart':814 'snapdragon':314 'sourc':507,532 'source-sickn33' 'speak':173 'specif':128,906,946,981 'speech':817 'speechrecogn':238,893 'speechtotextcontroller.kt':243 'speechtotextcontroller.transcribe':581 'src/test':639 'stacktrac':792 'startbluetoothsco':386 'startforeground':502 'state':618 'stateflow':615,621 'stereo':378 'stop':552,987 'stoprecord':578 'store':745 'stream':807,840,850,857 'structur':211 'stt':237,526,826,851 'stub':240,249,829 'stubllmclient.kt':255,727 'substitut':977 'success':999 'suggest':915 'support':363,374,849 'switch':554,558 'tap':449,567 'tapaction.record':570 'target':298 'targetsdk':275 'task':119,936,963 'technic':330 'test':341,634,730,788,791,794,798,983 'texttospeechcontroller.kt':244 'texttospeechcontroller.speak':585 'time':806,870 'tini':881 'toggl':571 'tool':129,280 'topic':65,73,82,92,102,111 'topic-agent-skills' 'topic-agentic-skills' 'topic-ai-agent-skills' 'topic-ai-agents' 'topic-ai-coding' 'topic-ai-workflows' 'topic-antigravity' 'topic-antigravity-skills' 'topic-claude-code' 'topic-claude-code-skills' 'topic-codex-cli' 'topic-codex-skills' 'track':214 'transcrib':165 'treat':342,972 'truth':346 'tts':241,518,541,841,846 'tws':704 'type':370 'ui':259,312,411,629 'ultra':308 'understand':534,944 'unit':633,793 'unrel':121 'updat':623,642 'use':55,114,394,661,699,735,932,957 'user':60,68,76,85,95,105,135,180 'utter':890 'valid':982 'valu':664 'vari':896 'verifi':334,782 'via':23,50,546,589,619 'viewmodel':260 'voic':24,51,87,161,187,236,504,808 'voicecapturecontrol':573 'voicecapturecontroller.kt':234,652 'voicepipeline.kt':245 'voicepipeline.togglerecording':572 'vosk':239,824,860,872 'vu':673 'wake':831 'wake-word':830 'wav':659 'wer':856 'whisper':880 'without':141,533,943 'word':832 'work':146 'xiaomi':319,459 'yes':867,877,897 'zip':758,780,781","prices":[{"id":"ba8132bd-c79c-4d1c-ae9f-afc329356bdf","listingId":"e1a43b7b-52b8-46c8-8563-71b4d0bce380","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"sickn33","category":"antigravity-awesome-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T21:36:27.272Z"}],"sources":[{"listingId":"e1a43b7b-52b8-46c8-8563-71b4d0bce380","source":"github","sourceId":"sickn33/antigravity-awesome-skills/earllm-build","sourceUrl":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/earllm-build","isPrimary":false,"firstSeenAt":"2026-04-18T21:36:27.272Z","lastSeenAt":"2026-04-24T06:51:06.687Z"}],"details":{"listingId":"e1a43b7b-52b8-46c8-8563-71b4d0bce380","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"sickn33","slug":"earllm-build","github":{"repo":"sickn33/antigravity-awesome-skills","stars":34831,"topics":["agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows","antigravity","antigravity-skills","claude-code","claude-code-skills","codex-cli","codex-skills","cursor","cursor-skills","developer-tools","gemini-cli","gemini-skills","kiro","mcp","skill-library"],"license":"mit","html_url":"https://github.com/sickn33/antigravity-awesome-skills","pushed_at":"2026-04-24T06:41:17Z","description":"Installable GitHub library of 1,400+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. Includes installer CLI, bundles, workflows, and official/community skill collections.","skill_md_sha":"603a216f64d1efe8772f9cd33dd2c4f5ee131578","skill_md_path":"skills/earllm-build/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/earllm-build"},"layout":"multi","source":"github","category":"antigravity-awesome-skills","frontmatter":{"name":"earllm-build","description":"Build, maintain, and extend the EarLLM One Android project — a Kotlin/Compose app that connects Bluetooth earbuds to an LLM via voice pipeline."},"skills_sh_url":"https://skills.sh/sickn33/antigravity-awesome-skills/earllm-build"},"updatedAt":"2026-04-24T06:51:06.687Z"}}