Conversation
- Introduced a comprehensive 'Getting Started' guide in `Docs/getting-started.md` to assist new users with setup and usage of the Friend-Lite backend. - Updated initialization script references in `Docs/init-system.md` to reflect the new `wizard.py` instead of `init.py`. - Added new local OMI Bluetooth connection scripts in `extras/local-omi-bt/` for improved audio streaming capabilities. - Removed the deprecated `connect-laptop.py` script and replaced it with `connect-omi.py` for better functionality. - Updated dependencies in `requirements.txt` to include new libraries for enhanced audio processing and backend communication. - Added a new `start.sh` script for easier execution of the local OMI connection process.
|
@coderabbitai review |
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughThis PR implements a comprehensive system reorganization: migrating from Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant BLE as BLE Device<br/>(OMI)
participant connect as connect-omi.py<br/>(Async Pipeline)
participant send as send_to_adv.py<br/>(Backend Stream)
participant backend as Friend-Lite<br/>Backend
User->>connect: Start connect-omi.py
connect->>connect: Initialize (load OMI_MAC or discover)
connect->>BLE: Connect via BleakClient
BLE-->>connect: Audio stream (Opus packets)
rect rgb(100, 200, 100)
Note over connect: Decode & Enqueue
connect->>connect: Decode Opus→PCM (audio_queue)
connect->>connect: RollingFileSink (local storage)
end
connect->>send: Pass AudioChunk stream
send->>send: Authenticate (JWT via ADMIN_EMAIL/PASSWORD)
send->>backend: POST /auth/login
backend-->>send: JWT token
rect rgb(100, 150, 255)
Note over send: Stream via WebSocket
send->>backend: WebSocket connect (token + device name)
send->>backend: audio-start message
loop Every AudioChunk
send->>backend: audio-chunk (metadata + binary)
end
send->>backend: audio-stop message
end
backend-->>send: ✓ Audio processed
send-->>User: Streaming complete
sequenceDiagram
actor User
participant wizard as wizard.py<br/>(Root Orchestrator)
participant backend as backends/advanced<br/>init.py
participant speaker as extras/speaker-<br/>recognition/init.py
participant env as Environment<br/>(.env files)
User->>wizard: python wizard.py [--https] [args]
wizard->>wizard: Parse CLI arguments
rect rgb(200, 150, 100)
Note over wizard: Setup Each Service
wizard->>backend: uv run --with-requirements init.py
backend->>env: Load .env.template → .env
backend->>env: Update via dotenv.set_key()
backend-->>wizard: ✓ Backend configured
end
wizard->>speaker: uv run --with-requirements init.py [--https] [--compute-mode]
rect rgb(150, 200, 150)
Note over speaker: Interactive Setup
speaker->>User: Prompt HF token
speaker->>User: Prompt compute mode (cpu/gpu)
speaker->>User: Prompt Deepgram API key
speaker->>User: Prompt HTTPS (yes/no)
User-->>speaker: Responses
speaker->>env: Backup existing .env
speaker->>env: Generate from template
speaker->>env: Apply values via set_key()
speaker->>env: chmod 0600
end
speaker-->>wizard: ✓ Speaker Recognition setup
wizard->>User: Display summary & next steps
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes The changes span multiple interconnected systems with heterogeneous modifications: new async Bluetooth pipeline with audio codecs, interactive setup orchestration, environment configuration refactoring, and documentation reorganization. While individual cohorts follow consistent patterns (e.g., template-based .env generation), the diversity of affected subsystems and non-trivial logic density (async BLE handling, JWT authentication, device discovery, template rendering) demand separate reasoning for distinct components. Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
extras/speaker-recognition/docker-compose.yml (1)
58-85: Port defaults and healthcheck scheme
- Provide defaults to avoid empty env failures.
- Healthcheck should respect REACT_UI_HTTPS.
Apply:
web-ui: platform: linux/amd64 @@ - ports: - - "${REACT_UI_PORT}:${REACT_UI_PORT}" + ports: + - "${REACT_UI_PORT:-5173}:${REACT_UI_PORT:-5173}" @@ - healthcheck: - test: ["CMD", "curl", "-f", "-k", "https://localhost:${REACT_UI_PORT}"] + healthcheck: + test: ["CMD-SHELL", "if [ \"${REACT_UI_HTTPS:-false}\" = \"true\" ]; then curl -f -k \"https://localhost:${REACT_UI_PORT:-5173}\"; else curl -f \"http://localhost:${REACT_UI_PORT:-5173}\"; fi"] interval: 30s timeout: 10s retries: 3Optional: note that pinning linux/amd64 may run under emulation on Apple Silicon. Consider aarch64 builds later.
wizard.py (1)
152-177: Don’t pass secrets via CLI args; use env for subprocessCLI args expose tokens (ps, logs). Pass via env and import os.
Apply:
@@ -from dotenv import get_key +import os +from dotenv import get_key @@ - if service_name == 'speaker-recognition': + if service_name == 'speaker-recognition': # Pass Deepgram API key from backend if available backend_env_path = 'backends/advanced/.env' deepgram_key = read_env_value(backend_env_path, 'DEEPGRAM_API_KEY') - if deepgram_key and deepgram_key != 'your_deepgram_api_key_here': - cmd.extend(['--deepgram-api-key', deepgram_key]) + env = os.environ.copy() + if deepgram_key and deepgram_key != 'your_deepgram_api_key_here': + env['DEEPGRAM_API_KEY'] = deepgram_key @@ speaker_env_path = 'extras/speaker-recognition/.env' hf_token = read_env_value(speaker_env_path, 'HF_TOKEN') - if hf_token and hf_token != 'your_huggingface_token_here': - cmd.extend(['--hf-token', hf_token]) + if hf_token and hf_token != 'your_huggingface_token_here': + env['HF_TOKEN'] = hf_token @@ compute_mode = read_env_value(speaker_env_path, 'COMPUTE_MODE') if compute_mode in ['cpu', 'gpu']: - cmd.extend(['--compute-mode', compute_mode]) + env['COMPUTE_MODE'] = compute_mode @@ - result = subprocess.run( + result = subprocess.run( cmd, cwd=service['path'], check=True, - timeout=300 # 5 minute timeout for service setup + timeout=300, # 5 minute timeout for service setup + env=env if 'env' in locals() else None )Downstream init.py should read from env. If it requires flags today, consider supporting both for transition.
Also applies to: 187-192, 12-12
🧹 Nitpick comments (13)
.gitignore (1)
76-76: Addition aligns with nginx removal in this PR.Adding
backends/advanced/nginx.confto .gitignore is correct and ensures the removed configuration file won't be accidentally re-tracked.Consider grouping this pattern with the related nginx ignore at line 54 (
extras/speaker-recognition/nginx.conf) or with otherbackends/advanced/*patterns (lines 34–35, 51) for better organization.extras/speaker-recognition/setup-requirements.txt (1)
3-3: Consider pinning the python-dotenv version for reproducibility.The unpinned
python-dotenv>=1.0.0allows any future version, which could introduce breaking changes. Based on learnings, the latest stable version is 1.1.1 (released Jun 24, 2025).Apply this diff to pin to a specific version or use a compatible release operator:
-python-dotenv>=1.0.0 +python-dotenv==1.1.1Or use compatible release to allow patch updates:
-python-dotenv>=1.0.0 +python-dotenv~=1.1.0backends/advanced/setup-requirements.txt (1)
4-4: Add version constraint for python-dotenv.The dependency lacks any version specification, which could lead to compatibility issues. Based on learnings, python-dotenv 1.1.1 is the latest stable version (released Jun 24, 2025).
Apply this diff to add a version constraint:
-python-dotenv +python-dotenv>=1.0.0Or pin to a specific version for maximum reproducibility:
-python-dotenv +python-dotenv==1.1.1extras/local-omi-bt/requirements.txt (1)
1-10: Add version constraints to unpinned dependencies.Several critical dependencies lack version constraints, which could lead to compatibility issues. Based on learnings:
- httpx latest: 0.28.1
- websockets latest: 15.0.1
- python-dotenv latest: 1.1.1
Apply this diff to add version constraints:
bleak==0.22.3 numpy>=1.26.4 scipy>=1.12.0 opuslib>=3.0.1 friend-lite-sdk -easy_audio_interfaces -python-dotenv -asyncstdlib -httpx -websockets +easy_audio_interfaces>=0.7.0 +python-dotenv>=1.0.0 +asyncstdlib>=3.12.0 +httpx>=0.28.0 +websockets>=15.0.0Docs/getting-started.md (2)
317-317: Fix hyphenation for compound adjective.The phrase "1.5 minute collection" should be hyphenated when used as a compound adjective modifying "collection."
Apply this diff:
-- **Collection timeout**: 1.5 minute collection for optimal online processing quality +- **Collection timeout**: 1.5-minute collection for optimal online processing quality
46-46: Optional: Add language specifier to code block and convert bare URL.Minor markdown style improvements flagged by static analysis:
- Line 46: Fenced code block missing language identifier
- Line 530: Bare URL should be formatted as link
These are nitpicks and can be addressed later if desired.
Also applies to: 530-530
extras/local-omi-bt/send_to_adv.py (3)
66-66: Use logging.exception for better error diagnostics.When logging within exception handlers,
logging.exception()automatically includes the stack trace, which is more helpful for debugging thanlogging.error().Apply this diff:
except httpx.TimeoutException: - logger.error("Authentication request timed out") + logger.exception("Authentication request timed out") return None except httpx.RequestError as e: - logger.error(f"Authentication request failed: {e}") + logger.exception(f"Authentication request failed: {e}") return None except Exception as e: - logger.error(f"Unexpected authentication error: {e}") + logger.exception(f"Unexpected authentication error: {e}") return NoneAlso applies to: 69-69, 72-72
71-73: Avoid catching bare Exception.Catching
Exceptionis too broad and can hide unexpected errors. Consider catching only specific exceptions or letting unexpected exceptions propagate.If you need to catch unexpected errors, at least re-raise after logging:
- except Exception as e: - logger.exception(f"Unexpected authentication error: {e}") - return None + except Exception: + logger.exception("Unexpected authentication error") + raiseOr remove this catch block if httpx exceptions are sufficient.
93-96: Add timeout to WebSocket receive operation.The
await websocket.recv()call could hang indefinitely if the backend doesn't send the ready message.Apply this diff:
+ import asyncio + logger.info(f"Connecting to WebSocket: {websocket_uri}") async with websockets.connect(uri_with_token) as websocket: # Wait for ready message from backend - ready_msg = await websocket.recv() + try: + ready_msg = await asyncio.wait_for(websocket.recv(), timeout=10.0) + except asyncio.TimeoutError: + logger.error("Timeout waiting for backend ready message") + return logger.info(f"Backend ready: {ready_msg}")setup-requirements.txt (1)
3-3: Add version constraint for python-dotenv.The dependency lacks any version specification, which could lead to compatibility issues in the future. Based on learnings, python-dotenv 1.1.1 is the latest stable version (released Jun 24, 2025).
Apply this diff to add a version constraint:
-python-dotenv +python-dotenv>=1.0.0Or pin to a specific version for maximum reproducibility:
-python-dotenv +python-dotenv==1.1.1backends/advanced/init.py (1)
342-372: Avoid double .env backups; keep a single backup stepgenerate_env_file() re-backs up after backup_existing_env() already did. Remove the second backup to reduce noise.
Apply:
- # Backup existing .env if it exists - if env_path.exists(): - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - backup_path = env_path.with_suffix(f'.backup.{timestamp}') - shutil.copy2(env_path, backup_path) - self.console.print(f"[blue][INFO][/blue] Backed up existing .env to {backup_path.name}")Optional: don’t print generated passwords in cleartext; instead show a hint and write only to .env (permissions already 0600).
extras/speaker-recognition/init.py (2)
222-236: Deduplicate .env backup; use backup_existing_env().You already have backup_existing_env(); call it here and remove the inline duplication.
@@ - # Backup existing .env if it exists - if env_path.exists(): - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - backup_path = env_path.with_suffix(f'.backup.{timestamp}') - shutil.copy2(env_path, backup_path) - self.console.print(f"[blue][INFO][/blue] Backed up existing .env to {backup_path.name}") + # Backup existing .env if it exists + self.backup_existing_env()Optionally, if you keep the call above, consider removing the now-redundant backup_existing_env() method to avoid confusion.
319-325: Avoid bareexcept Exception; catch expected errors.Narrow scope or re-raise unknowns; aligns with BLE001.
- except Exception as e: + except (OSError, subprocess.SubprocessError, ValueError) as e: self.console.print(f"[red][ERROR][/red] Setup failed: {e}") sys.exit(1)If broader coverage is desired, add a debug flag to re-raise unrecognized exceptions after logging.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
backends/advanced/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (19)
.gitignore(1 hunks)CLAUDE.md(8 hunks)Docs/getting-started.md(1 hunks)Docs/init-system.md(3 hunks)backends/advanced/init.py(2 hunks)backends/advanced/nginx.conf(0 hunks)backends/advanced/setup-requirements.txt(1 hunks)extras/local-omi-bt/README.md(1 hunks)extras/local-omi-bt/connect-omi.py(1 hunks)extras/local-omi-bt/requirements.txt(1 hunks)extras/local-omi-bt/send_to_adv.py(1 hunks)extras/local-omi-bt/start.sh(1 hunks)extras/speaker-recognition/docker-compose.yml(2 hunks)extras/speaker-recognition/init.py(1 hunks)extras/speaker-recognition/setup-requirements.txt(1 hunks)extras/speaker-recognition/webui/Dockerfile(1 hunks)quickstart.md(1 hunks)setup-requirements.txt(1 hunks)wizard.py(6 hunks)
💤 Files with no reviewable changes (1)
- backends/advanced/nginx.conf
🧰 Additional context used
🪛 Gitleaks (8.28.0)
Docs/getting-started.md
[high] 226-226: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 362-368: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
CLAUDE.md
[high] 320-326: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
🪛 LanguageTool
Docs/getting-started.md
[style] ~9-~9: For conciseness, consider replacing this expression with an adverb.
Context: ...ort "AI wearable" agents/functionality. At the moment, the basic functionalities are: - Audio...
(AT_THE_MOMENT)
[grammar] ~317-~317: Use a hyphen to join words.
Context: ...processing - Collection timeout: 1.5 minute collection for optimal online pro...
(QB_NEW_EN_HYPHEN)
[style] ~476-~476: The double modal “needed Encrypted” is nonstandard (only accepted in certain dialects). Consider “to be Encrypted”.
Context: ...x.x.x) - no CORS configuration needed - Encrypted inter-service communication **Service ...
(NEEDS_FIXED)
CLAUDE.md
[grammar] ~7-~7: Use a hyphen to join words.
Context: ...at the very least audio capture, speaker specific transcription, memory extractio...
(QB_NEW_EN_HYPHEN)
🪛 markdownlint-cli2 (0.18.1)
Docs/getting-started.md
46-46: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
530-530: Bare URL used
(MD034, no-bare-urls)
CLAUDE.md
99-99: Bare URL used
(MD034, no-bare-urls)
🪛 Ruff (0.14.0)
extras/local-omi-bt/send_to_adv.py
60-60: Do not use bare except
(E722)
60-61: try-except-pass detected, consider logging the exception
(S110)
66-66: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
69-69: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
71-71: Do not catch blind exception: Exception
(BLE001)
72-72: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
extras/local-omi-bt/connect-omi.py
72-72: Unused function argument: sender
(ARG001)
77-77: Do not catch blind exception: Exception
(BLE001)
78-78: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
98-98: Local variable test_client is assigned to but never used
Remove assignment to unused variable test_client
(F841)
100-100: Do not catch blind exception: Exception
(BLE001)
101-101: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
102-102: Use logging.exception instead of logging.error
Replace with exception
(TRY400)
extras/speaker-recognition/init.py
172-172: subprocess call: check for execution of untrusted input
(S603)
196-196: Do not catch blind exception: Exception
(BLE001)
203-203: f-string without any placeholders
Remove extraneous f prefix
(F541)
204-204: f-string without any placeholders
Remove extraneous f prefix
(F541)
205-205: f-string without any placeholders
Remove extraneous f prefix
(F541)
206-206: f-string without any placeholders
Remove extraneous f prefix
(F541)
257-257: f-string without any placeholders
Remove extraneous f prefix
(F541)
323-323: Do not catch blind exception: Exception
(BLE001)
wizard.py
163-163: Possible hardcoded password assigned to: "hf_token"
(S105)
332-332: f-string without any placeholders
Remove extraneous f prefix
(F541)
🪛 Shellcheck (0.11.0)
extras/local-omi-bt/start.sh
[error] 1-1: Tips depend on target shell and yours is unknown. Add a shebang or a 'shell' directive.
(SC2148)
🔇 Additional comments (5)
Docs/init-system.md (1)
15-15: LGTM! Documentation accurately reflects the wizard.py migration.The updates correctly reference
wizard.pyas the root orchestrator, replacing previousinit.pyreferences. This aligns with the PR's architectural changes.Also applies to: 25-25, 42-42
Docs/getting-started.md (1)
1-730: Comprehensive getting started guide!This documentation provides excellent end-to-end guidance covering setup, configuration, deployment patterns, and troubleshooting. The content is well-organized and includes practical examples.
quickstart.md (1)
96-96: Command switch to wizard.py looks goodMatches the new orchestrator and uv flow.
wizard.py (1)
332-333: Remove extraneous f-string prefixThe issue is confirmed. Line 332 contains an f-string with no placeholders—only literal text and rich formatting markup. Removing the
fprefix will clear the F541 linting error.- console.print(f"[dim] cd extras/speaker-recognition && uv run --with-requirements setup-requirements.txt python init.py[/dim]") + console.print("[dim] cd extras/speaker-recognition && uv run --with-requirements setup-requirements.txt python init.py[/dim]")CLAUDE.md (1)
320-327: The stated concern about gitleaks false positives is not applicable to this repository.This codebase has no gitleaks configuration, custom rules, or secret scanning workflows configured. The placeholder
YOUR_TOKENwill not be flagged by default gitleaks or any active scanning. However, for consistency with the rest of the codebase (which uses shell variables like$ADMIN_TOKEN,$USER_TOKEN, and$TOKENin other documentation files), using${TOKEN}would improve uniformity.Likely an incorrect or invalid review comment.
| ## Project Overview | ||
|
|
||
| Friend-Lite is at the core an AI-powered personal system - various devices, incuding but not limited to wearables from OMI can be used for at the very least audio capture, speaker specific transcription, memory extraction and retriaval. | ||
| Friend-Lite is at the core an AI-powered personal system - various devices, including but not limited to wearables from OMI can be used for at the very least audio capture, speaker specific transcription, memory extraction and retrieval. |
There was a problem hiding this comment.
Hyphenation nit
Use “speaker‑specific transcription”.
Apply:
-Friend-Lite is at the core an AI-powered personal system - various devices, including but not limited to wearables from OMI can be used for at the very least audio capture, speaker specific transcription, memory extraction and retrieval.
+Friend-Lite is at the core an AI-powered personal system — various devices, including but not limited to wearables from OMI, can be used for at the very least audio capture, speaker-specific transcription, memory extraction, and retrieval.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Friend-Lite is at the core an AI-powered personal system - various devices, including but not limited to wearables from OMI can be used for at the very least audio capture, speaker specific transcription, memory extraction and retrieval. | |
| Friend-Lite is at the core an AI-powered personal system — various devices, including but not limited to wearables from OMI, can be used for at the very least audio capture, speaker-specific transcription, memory extraction, and retrieval. |
🧰 Tools
🪛 LanguageTool
[grammar] ~7-~7: Use a hyphen to join words.
Context: ...at the very least audio capture, speaker specific transcription, memory extractio...
(QB_NEW_EN_HYPHEN)
| async def find_and_set_omi_mac() -> None: | ||
| devices = await list_devices() | ||
| assert len(devices) == 1, "Expected 1 Omi device, got %d" % len(devices) | ||
| OMI_MAC = devices[0].address | ||
| set_key(env_path, "OMI_MAC", OMI_MAC) | ||
| logger.info("OMI_MAC set to %s and saved to .env" % OMI_MAC) | ||
|
|
There was a problem hiding this comment.
Fix OMI_MAC scoping bug and improve robustness/logging
- Ensure discovered MAC is used after discovery.
- Replace assert with guards.
- Use logging.exception for tracebacks.
- Drop unused variable in BleakClient test.
Apply:
@@
- async def find_and_set_omi_mac() -> None:
- devices = await list_devices()
- assert len(devices) == 1, "Expected 1 Omi device, got %d" % len(devices)
- OMI_MAC = devices[0].address
- set_key(env_path, "OMI_MAC", OMI_MAC)
- logger.info("OMI_MAC set to %s and saved to .env" % OMI_MAC)
+ async def find_and_set_omi_mac() -> str:
+ devices = await list_devices()
+ if not devices:
+ raise RuntimeError("No OMI devices found. Ensure the device is on and in range.")
+ if len(devices) > 1:
+ logger.warning("Multiple OMI-like devices found; selecting the first: %s", devices[0].name)
+ mac = devices[0].address
+ set_key(env_path, "OMI_MAC", mac)
+ logger.info("OMI_MAC set to %s and saved to .env", mac)
+ return mac
@@
- logger.info("Starting OMI Bluetooth connection and audio streaming")
- if not OMI_MAC:
- await find_and_set_omi_mac()
+ logger.info("Starting OMI Bluetooth connection and audio streaming")
+ omi_mac = OMI_MAC
+ if not omi_mac:
+ omi_mac = await find_and_set_omi_mac()
else:
- logger.info("using OMI_MAC from .env: %s" % OMI_MAC)
+ logger.info("using OMI_MAC from .env: %s", omi_mac)
@@
- async with BleakClient(OMI_MAC) as test_client:
- logger.info(f"Successfully connected to device {OMI_MAC}")
- except Exception as e:
- logger.error(f"Failed to connect to device {OMI_MAC}: {e}")
- logger.error("Exiting without creating audio sink or backend connection")
+ async with BleakClient(omi_mac):
+ logger.info("Successfully connected to device %s", omi_mac)
+ except Exception:
+ logger.exception("Failed to connect to device %s", omi_mac)
+ logger.error("Exiting without creating audio sink or backend connection")
return
@@
- listen_to_omi(OMI_MAC, OMI_CHAR_UUID, handle_ble_data),
+ listen_to_omi(omi_mac, OMI_CHAR_UUID, handle_ble_data),Also improve callback logging and remove unused function:
@@
- def handle_ble_data(sender: Any, data: bytes) -> None:
+ def handle_ble_data(_sender: Any, data: bytes) -> None:
decoded_pcm: bytes = decoder.decode_packet(data)
if decoded_pcm:
try:
audio_queue.put_nowait(decoded_pcm)
- except Exception as e:
- logger.error("Queue Error: %s", e)
+ except Exception:
+ logger.exception("Queue put error")
@@
-async def as_audio_chunks(it) -> AsyncGenerator[AudioChunk, None]:
- """Convert bytes iterator to AudioChunk iterator."""
- async for data in it:
- yield AudioChunk(audio=data, rate=16000, width=2, channels=1)
+# (unused) as_audio_chunks helper can be removed or used where neededAlso applies to: 88-104, 98-101, 72-79, 47-51
🤖 Prompt for AI Agents
In extras/local-omi-bt/connect-omi.py around lines 81-87 (also review ranges
47-51, 72-79, 88-104, 98-101): fix the OMI_MAC scoping bug by not creating a
local-only OMI_MAC — either assign to the module/global variable or return the
discovered MAC from find_and_set_omi_mac and use that caller-side; replace the
assert with a runtime guard that logs an error and safely returns or raises a
descriptive exception when the device count is not exactly one; wrap device
discovery and client operations in try/except and use logger.exception(...) to
log full tracebacks on failures; remove any unused variables (e.g., the unused
BleakClient test variable) and delete the unused callback helper function, and
while here improve callback logging by emitting clear debug/info messages
showing address and payload instead of silent or minimal prints.
There was a problem hiding this comment.
@coderabbitai try:
audio_queue.put_nowait(decoded_pcm)
-
except Exception as e: -
logger.error("Queue Error: %s", e)
-
except Exception: -
logger.exception("Queue put error")
@@
doesnt this mean it becomes harder to know the error?
There was a problem hiding this comment.
@AnkushMalaker Actually, it's the opposite! logger.exception() provides more information, not less.
Here's the difference:
Current code:
except Exception as e:
logger.error("Queue Error: %s", e)This only logs the exception's string representation (e.g., "Queue Error: list index out of range").
Suggested code:
except Exception:
logger.exception("Queue put error")This logs the full stack trace automatically, showing exactly where the error occurred and the complete call chain. It's equivalent to:
except Exception as e:
logger.error("Queue put error: %s", e, exc_info=True)Example output comparison:
With logger.error():
ERROR: Queue Error: Full
With logger.exception():
ERROR: Queue put error
Traceback (most recent call last):
File "connect-omi.py", line 76, in handle_ble_data
audio_queue.put_nowait(decoded_pcm)
File "/usr/lib/python3.11/asyncio/queues.py", line 196, in put_nowait
...
asyncio.QueueFull: ...
The logger.exception() method is specifically designed for use in exception handlers and automatically captures the full traceback context, making debugging much easier. This is a Python logging best practice! 🐛
| # Usage | ||
| Run using `uv run --with-requirements requirements.txt python connect-laptop.py` | ||
| from this directory. No newline at end of file |
There was a problem hiding this comment.
Fix script name mismatch
Use connect-omi.py to match the added script.
Apply:
-Run using `uv run --with-requirements requirements.txt python connect-laptop.py`
+Run using `uv run --with-requirements requirements.txt python connect-omi.py` 📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Usage | |
| Run using `uv run --with-requirements requirements.txt python connect-laptop.py` | |
| from this directory. | |
| # Usage | |
| Run using `uv run --with-requirements requirements.txt python connect-omi.py` | |
| from this directory. |
🤖 Prompt for AI Agents
In extras/local-omi-bt/README.md around lines 1 to 3, the usage example
references the wrong script name (connect-laptop.py); update the command to use
the actual script added (connect-omi.py) so the README matches the repository.
Replace the referenced filename in the run command with connect-omi.py and
ensure the working-directory note remains correct.
| websocket_uri = "ws://100.83.66.30:8000/ws_pcm" | ||
| backend_url = "http://100.83.66.30:8000" | ||
| env_path = ".env" |
There was a problem hiding this comment.
Hardcoded configuration should be environment-based.
The WebSocket URI and backend URL are hardcoded with a specific IP address (100.83.66.30). This reduces portability and makes the code environment-specific.
Apply this diff to use environment variables:
# Configuration
-websocket_uri = "ws://100.83.66.30:8000/ws_pcm"
-backend_url = "http://100.83.66.30:8000"
+websocket_uri = os.getenv("BACKEND_WEBSOCKET_URL", "ws://localhost:8000/ws_pcm")
+backend_url = os.getenv("BACKEND_URL", "http://localhost:8000")
env_path = ".env"Then document these environment variables in the .env.template or README.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| websocket_uri = "ws://100.83.66.30:8000/ws_pcm" | |
| backend_url = "http://100.83.66.30:8000" | |
| env_path = ".env" | |
| websocket_uri = os.getenv("BACKEND_WEBSOCKET_URL", "ws://localhost:8000/ws_pcm") | |
| backend_url = os.getenv("BACKEND_URL", "http://localhost:8000") | |
| env_path = ".env" |
🤖 Prompt for AI Agents
In extras/local-omi-bt/send_to_adv.py around lines 12 to 14, the websocket_uri
and backend_url are hardcoded to a specific IP; replace them with values loaded
from environment variables (e.g., WEBSOCKET_URI and BACKEND_URL) using
os.environ.get with sensible defaults, and load .env via python-dotenv if
currently used (env path variable can remain for loading). Ensure the code
validates presence or falls back to a reasonable default and does not leak
secrets, then add WEBSOCKET_URI and BACKEND_URL entries (with example values) to
.env.template or document them in the README.
| except: | ||
| pass |
There was a problem hiding this comment.
Replace bare except with specific exception handling.
The bare except clause can catch system-exiting exceptions like KeyboardInterrupt and SystemExit, which should not be suppressed.
Apply this diff:
try:
error_data = response.json()
error_msg = error_data.get('detail', error_msg)
- except:
- pass
+ except (ValueError, KeyError):
+ # JSON parsing failed or 'detail' key missing - use default error_msg
+ pass📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| except: | |
| pass | |
| try: | |
| error_data = response.json() | |
| error_msg = error_data.get('detail', error_msg) | |
| except (ValueError, KeyError): | |
| # JSON parsing failed or 'detail' key missing - use default error_msg | |
| pass |
🧰 Tools
🪛 Ruff (0.14.0)
60-60: Do not use bare except
(E722)
60-61: try-except-pass detected, consider logging the exception
(S110)
🤖 Prompt for AI Agents
In extras/local-omi-bt/send_to_adv.py around lines 60 to 61, replace the bare
"except:" with a specific exception handler so system-exiting exceptions
(KeyboardInterrupt, SystemExit) are not swallowed; change it to catch Exception
(or the precise exceptions you expect, e.g., OSError, ValueError) like "except
Exception as e:" and handle or log the error (or re-raise for non-recoverable
cases) instead of silently passing.
| # Generate nginx.conf from template | ||
| self.console.print("[blue][INFO][/blue] Creating nginx configuration...") | ||
| nginx_template = Path("nginx.conf.template") | ||
| if nginx_template.exists(): | ||
| try: | ||
| with open(nginx_template, 'r') as f: | ||
| nginx_content = f.read() | ||
|
|
||
| # Replace TAILSCALE_IP with server_ip | ||
| nginx_content = nginx_content.replace('TAILSCALE_IP', server_ip) | ||
|
|
||
| with open('nginx.conf', 'w') as f: | ||
| f.write(nginx_content) | ||
|
|
||
| self.console.print(f"[green][SUCCESS][/green] nginx.conf created for: {server_ip}") | ||
| except Exception as e: | ||
| self.console.print(f"[yellow][WARNING][/yellow] nginx.conf generation failed: {e}") | ||
| else: | ||
| self.console.print("[yellow][WARNING][/yellow] nginx.conf.template not found") | ||
|
|
There was a problem hiding this comment.
Make nginx paths robust and narrow exception scope.
Resolve paths from repo root; avoid catching bare Exception (BLE001).
@@
- self.console.print("[blue][INFO][/blue] Creating nginx configuration...")
- nginx_template = Path("nginx.conf.template")
+ self.console.print("[blue][INFO][/blue] Creating nginx configuration...")
+ # Resolve from repo root
+ nginx_template = (repo_root / "nginx.conf.template")
if nginx_template.exists():
try:
- with open(nginx_template, 'r') as f:
+ with open(nginx_template, 'r') as f:
nginx_content = f.read()
@@
- nginx_content = nginx_content.replace('TAILSCALE_IP', server_ip)
+ nginx_content = nginx_content.replace('TAILSCALE_IP', server_ip)
@@
- with open('nginx.conf', 'w') as f:
+ with open(repo_root / 'nginx.conf', 'w') as f:
f.write(nginx_content)
@@
- except Exception as e:
+ except OSError as e:
self.console.print(f"[yellow][WARNING][/yellow] nginx.conf generation failed: {e}")
else:
self.console.print("[yellow][WARNING][/yellow] nginx.conf.template not found")Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 Ruff (0.14.0)
196-196: Do not catch blind exception: Exception
(BLE001)
| self.console.print() | ||
| self.console.print("📋 [bold]HTTPS Mode URLs:[/bold]") | ||
| self.console.print(f" 🌐 HTTPS Access: https://localhost:8444/") | ||
| self.console.print(f" 🌐 HTTP Redirect: http://localhost:8081/ → HTTPS") | ||
| self.console.print(f" 📱 Service API: https://localhost:8444/api/") | ||
| self.console.print(f" 💡 Accept SSL certificate in browser") | ||
| else: |
There was a problem hiding this comment.
Fix F541 and show actual host in HTTPS URLs.
Use server_ip instead of hardcoded localhost; drop unused f-strings.
@@
- self.console.print("📋 [bold]HTTPS Mode URLs:[/bold]")
- self.console.print(f" 🌐 HTTPS Access: https://localhost:8444/")
- self.console.print(f" 🌐 HTTP Redirect: http://localhost:8081/ → HTTPS")
- self.console.print(f" 📱 Service API: https://localhost:8444/api/")
- self.console.print(f" 💡 Accept SSL certificate in browser")
+ self.console.print("📋 [bold]HTTPS Mode URLs:[/bold]")
+ self.console.print(f" 🌐 HTTPS Access: https://{server_ip}:8444/")
+ self.console.print(f" 🌐 HTTP Redirect: http://{server_ip}:8081/ → HTTPS")
+ self.console.print(f" 📱 Service API: https://{server_ip}:8444/api/")
+ self.console.print(" 💡 Accept SSL certificate in browser")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| self.console.print() | |
| self.console.print("📋 [bold]HTTPS Mode URLs:[/bold]") | |
| self.console.print(f" 🌐 HTTPS Access: https://localhost:8444/") | |
| self.console.print(f" 🌐 HTTP Redirect: http://localhost:8081/ → HTTPS") | |
| self.console.print(f" 📱 Service API: https://localhost:8444/api/") | |
| self.console.print(f" 💡 Accept SSL certificate in browser") | |
| else: | |
| self.console.print() | |
| self.console.print("📋 [bold]HTTPS Mode URLs:[/bold]") | |
| self.console.print(f" 🌐 HTTPS Access: https://{server_ip}:8444/") | |
| self.console.print(f" 🌐 HTTP Redirect: http://{server_ip}:8081/ → HTTPS") | |
| self.console.print(f" 📱 Service API: https://{server_ip}:8444/api/") | |
| self.console.print(" 💡 Accept SSL certificate in browser") | |
| else: |
🧰 Tools
🪛 Ruff (0.14.0)
203-203: f-string without any placeholders
Remove extraneous f prefix
(F541)
204-204: f-string without any placeholders
Remove extraneous f prefix
(F541)
205-205: f-string without any placeholders
Remove extraneous f prefix
(F541)
206-206: f-string without any placeholders
Remove extraneous f prefix
(F541)
🤖 Prompt for AI Agents
In extras/speaker-recognition/init.py around lines 201 to 207, the HTTPS URLs
use hardcoded "localhost" and several print() calls use unnecessary f-strings
(F541); replace "localhost" with the actual server_ip variable in the URL
strings and remove the leading f from any string literals that do not
interpolate variables, or convert the strings to proper f-strings only when
including server_ip (e.g., use f"https://{server_ip}:8444/" and
f"http://{server_ip}:8081/ → HTTPS"), and ensure the Service API and certificate
hint also reference server_ip where appropriate.
| def generate_env_file(self): | ||
| """Generate .env file from template and update with configuration""" | ||
| env_path = Path(".env") | ||
| env_template = Path(".env.template") | ||
|
|
||
| # Backup existing .env if it exists | ||
| if env_path.exists(): | ||
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | ||
| backup_path = env_path.with_suffix(f'.backup.{timestamp}') | ||
| shutil.copy2(env_path, backup_path) | ||
| self.console.print(f"[blue][INFO][/blue] Backed up existing .env to {backup_path.name}") | ||
|
|
||
| # Copy template to .env | ||
| if env_template.exists(): | ||
| shutil.copy2(env_template, env_path) | ||
| self.console.print("[blue][INFO][/blue] Copied .env.template to .env") | ||
| else: | ||
| self.console.print("[yellow][WARNING][/yellow] .env.template not found, creating new .env") | ||
| env_path.touch(mode=0o600) | ||
|
|
||
| # Update configured values using set_key | ||
| env_path_str = str(env_path) | ||
| for key, value in self.config.items(): | ||
| if value: # Only set non-empty values | ||
| set_key(env_path_str, key, value) | ||
|
|
||
| # Ensure secure permissions | ||
| os.chmod(env_path, 0o600) | ||
|
|
||
| self.console.print("[green][SUCCESS][/green] .env file configured successfully with secure permissions") | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
Check-in hygiene: ensure .env not tracked; verify required files exist.
Automate verification to reduce surprises at runtime and avoid secrets in Git.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Verify expected files =="
fd -a 'generate-ssl.sh' 2>/dev/null || true
fd -a 'nginx.conf.template' 2>/dev/null || true
echo "== Verify .gitignore has .env (not tracked) =="
if fd -a .gitignore >/dev/null 2>&1; then
fd -a .gitignore | xargs -I{} rg -nP '^\s*\.env($|/)' {} || echo "MISSING: add '.env' to .gitignore"
else
echo "No .gitignore found at repo root; consider adding one with '.env'."
fi
if git ls-files --error-unmatch .env >/dev/null 2>&1; then
echo "WARNING: .env is tracked in Git! Remove and add to .gitignore."
fiLength of output: 197
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Check .gitignore at root =="
if [ -f .gitignore ]; then
echo "FOUND .gitignore"
echo "--- Contents (with line numbers) ---"
rg -n '\.env' .gitignore || echo "No .env patterns found in .gitignore"
echo "--- Full .gitignore (first 30 lines) ---"
head -30 .gitignore
else
echo "NOT FOUND: .gitignore does not exist at repo root"
fi
echo ""
echo "== Check .env file status in Git =="
git status .env 2>&1 || true
echo ""
echo "== Check if .env is in git index (tracked) =="
git ls-files --stage .env 2>&1 || echo ".env not in index (good)"
echo ""
echo "== Search for generate-ssl.sh references in extras/speaker-recognition/ =="
rg -n 'generate-ssl' extras/speaker-recognition/ -g '*.py' -g '*.sh' 2>/dev/null || echo "No references to generate-ssl.sh found"
echo ""
echo "== Check if generate-ssl.sh is actually needed in extras/speaker-recognition/ =="
if [ -f extras/speaker-recognition/generate-ssl.sh ]; then
echo "FOUND: extras/speaker-recognition/generate-ssl.sh exists"
else
echo "NOT FOUND: extras/speaker-recognition/generate-ssl.sh"
fiLength of output: 578
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Inspect init.py to understand generate_env_file() dependencies =="
if [ -f extras/speaker-recognition/init.py ]; then
echo "File: extras/speaker-recognition/init.py"
echo "--- Lines 217-247 (generate_env_file method) ---"
sed -n '217,247p' extras/speaker-recognition/init.py
echo ""
echo "--- Check for .env.template reference =="
rg -n 'env.*template' extras/speaker-recognition/init.py -A2 -B2
else
echo "init.py not found"
fi
echo ""
echo "== Check if .env.template exists in extras/speaker-recognition/ =="
if [ -f extras/speaker-recognition/.env.template ]; then
echo "FOUND: .env.template"
head -10 extras/speaker-recognition/.env.template
else
echo "NOT FOUND: .env.template in extras/speaker-recognition/"
fi
echo ""
echo "== Check repo root for .env.template =="
if [ -f .env.template ]; then
echo "FOUND: .env.template at root"
else
echo "NOT FOUND: .env.template at root"
fiLength of output: 2716
Add .env to .gitignore at repository root to prevent secrets from being tracked in Git.
The generate_env_file() function is well-implemented with secure permissions (0o600) and graceful fallback handling. However, .env.template exists in the repo and the code properly handles cases where it's missing. The real concern is that no .gitignore exists at the repository root, which means .env (containing sensitive values like HF_TOKEN) can be accidentally committed. Add an entry for .env to prevent this security risk.
🤖 Prompt for AI Agents
extras/speaker-recognition/init.py lines 217-247: add a root-level .gitignore
entry for .env to prevent secrets from being committed; create or update
.gitignore at the repository root to include a single line ".env" (and optional
".env.*" if you want backups ignored), save and commit the change alongside the
PR so generated .env files (and any backups like .env.backup.*) are not tracked
by git while leaving .env.template in the repo.
There was a problem hiding this comment.
no .gitignore exists at the repository root.
it does, are you stupid?
❯ ls -a | grep ignore
.gitignore
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Display .gitignore content =="
cat .gitignore
echo ""
echo "== Check for .env patterns in .gitignore =="
rg -n '\.env' .gitignore || echo "No .env patterns found"
echo ""
echo "== Check if .env files are tracked =="
git ls-files | rg '\.env$' || echo "No .env files in git index (good)"
Length of output: 1445
@AnkushMalaker I apologize for the error. You're absolutely right—.gitignore exists and is properly configured.
Looking at the actual content:
- Line 3:
**/*.envcorrectly ignores all.envfiles - Line 4:
!**/.env.templateproperly allows template files - Line 74:
.env.backup.*ignores backup files - Confirmed: No
.envfiles are tracked in git
The security concern I raised was invalid. The generate_env_file() implementation is solid and your .gitignore is properly protecting secrets. My mistake—no changes needed here.
(\___/)
(o . )
( > < )
(_______)
[feeling foolish]
| self.console.print(f"✅ Compute Mode: {self.config.get('COMPUTE_MODE', 'Not configured')}") | ||
| self.console.print(f"✅ HTTPS Enabled: {self.config.get('REACT_UI_HTTPS', 'false')}") | ||
| if self.config.get('DEEPGRAM_API_KEY'): | ||
| self.console.print(f"✅ Deepgram API Key: Configured") |
There was a problem hiding this comment.
Fix F541 (f-string without placeholders).
Remove the unnecessary f-prefix.
- self.console.print(f"✅ Deepgram API Key: Configured")
+ self.console.print("✅ Deepgram API Key: Configured")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| self.console.print(f"✅ Deepgram API Key: Configured") | |
| self.console.print("✅ Deepgram API Key: Configured") |
🧰 Tools
🪛 Ruff (0.14.0)
257-257: f-string without any placeholders
Remove extraneous f prefix
(F541)
🤖 Prompt for AI Agents
In extras/speaker-recognition/init.py around line 257, there's an f-string with
no placeholders ("f" prefix) causing F541; remove the unnecessary "f" prefix so
the string is a normal literal (e.g., change f"✅ Deepgram API Key: Configured"
to "✅ Deepgram API Key: Configured") to satisfy linting.
| # Note: Port is determined at runtime by REACT_UI_PORT env var | ||
| # Actual port binding is configured in docker-compose.yml | ||
|
|
There was a problem hiding this comment.
Ensure Vite binds to REACT_UI_PORT
Without passing --port, non-5173 ports will fail healthchecks/bindings.
Apply:
-# Start development server
-CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
+# Start development server with configurable port
+CMD ["sh", "-c", "npm run dev -- --host 0.0.0.0 --port ${REACT_UI_PORT:-5173}"]Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
extras/speaker-recognition/webui/Dockerfile lines 19-21: the Dockerfile
currently doesn't pass the REACT_UI_PORT env var into the Vite dev server
command, so Vite will bind to the default port and non-5173 ports will fail
healthchecks; update the Dockerfile to invoke Vite (or the npm start script)
with the --port flag using the environment variable (e.g. --port
${REACT_UI_PORT:-5173}) and also include --host 0.0.0.0 so it is reachable from
outside the container; modify the CMD/ENTRYPOINT line to use that flag (or
update the start script to forward the flag) and ensure REACT_UI_PORT is
documented or defaulted if not set.
Summary by CodeRabbit
New Features
Documentation
Chores