Skip to content

Better init system#74

Merged
AnkushMalaker merged 35 commits intomainfrom
better-init-system
Sep 5, 2025
Merged

Better init system#74
AnkushMalaker merged 35 commits intomainfrom
better-init-system

Conversation

@AnkushMalaker
Copy link
Collaborator

@AnkushMalaker AnkushMalaker commented Sep 1, 2025

Summary by CodeRabbit

  • New Features

    • Interactive orchestrated setup and per-service wizards; service manager CLI; modern React Web UI with live recording and advanced search.
  • New APIs / UI

    • Semantic memory search with configurable relevance threshold, dual-layer filtering, and total memory counts; login pre-flight health check and richer login error reporting.
  • Speaker / Audio

    • Speaker enrollment UI, primary-speaker configuration, service health/status, and speaker-based gating to skip non-primary audio memories.
  • Documentation

    • Expanded Quickstart, init/system/setup, ports/access, and per-service docs.
  • Chores

    • New setup scripts, SSL helpers, env templates, Docker/compose adjustments, and UI styling (slider).

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 1, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds an orchestrated root setup and services CLI, interactive advanced-backend and HTTPS setup scripts, semantic memory search with score_threshold and total_count, speaker-configuration APIs/UI and speaker-based memory gating, provider-aware health checks and Qdrant native counting/scoring, extras setup scripts and Docker/context tweaks, and removes legacy init.sh.

Changes

Cohort / File(s) Summary
Root orchestrator & service CLI
init.py, services.py, setup-requirements.txt
New root-level orchestrator (init.py) to select/delegate per-service setup and services.py CLI to start/stop/status services; root setup dependency (rich) added.
Advanced backend interactive setup & docs
backends/advanced/init.py, backends/advanced/setup-requirements.txt, backends/advanced/SETUP_SCRIPTS.md, Docs/init-system.md
Adds Rich-based interactive FriendLiteSetup with non-interactive fallbacks, .env backups, template provisioning, HTTPS orchestration, and documentation for orchestrated vs per-service flows.
HTTPS & init helpers
backends/advanced/init-https.sh, backends/advanced/setup-https.sh, backends/advanced/run-test.sh, backends/advanced/ssl/generate-ssl.sh
New/updated shell helpers for HTTPS provisioning, self-signed cert generation, .env/template handling, and auto-creating missing memory/diarization configs from templates.
Docker / compose / build context
backends/advanced/Dockerfile, backends/advanced/docker-compose.yml, backends/advanced/.dockerignore, extras/openmemory-mcp/docker-compose.yml
Dockerfile copies use wildcards for config templates; docker-compose removes qdrant start dependency and nginx https profile; .dockerignore explicitly includes webui, ssl, nginx.conf*; OpenMemory mem0_store mounts host path.
Memory interfaces & providers
backends/advanced/src/.../memory/base.py, .../compat_service.py, .../memory_service.py, .../providers/vector_stores.py, .../providers/openmemory_mcp_service.py
Adds optional count_memories API and score_threshold param to search_memories; Qdrant provider uses native count API and returns result.score as similarity; OpenMemory MCP accepts threshold param (ignored server-side).
Controllers, health & routes
backends/advanced/src/.../controllers/memory_controller.py, .../controllers/system_controller.py, .../routers/modules/system_routes.py, .../main.py
get_memories includes total_count; search route accepts score_threshold; new speaker-config/enrolled-speakers/service-status endpoints; auth health endpoint and provider-aware health checks (Friend-Lite vs OpenMemory MCP) added.
Processing & user schema
backends/advanced/src/.../processors.py, backends/advanced/src/.../users.py
Adds User.primary_speakers field and ProcessorManager gating (_should_process_memory) to skip memory creation when primary speakers are absent.
Web UI: Memories, System & API client
backends/advanced/webui/src/pages/Memories.tsx, .../System.tsx, .../services/api.ts, .../styles/slider.css
Adds semantic search flow, dual-layer filtering, relevance-threshold slider and styles, total_count display, SpeakerConfiguration UI, speakerApi client, and memoriesApi.search(scoreThreshold).
Extras: OpenMemory MCP
extras/openmemory-mcp/setup.sh, extras/openmemory-mcp/docker-compose.yml, .gitignore
MCP setup script added; mem0 storage changed to host path; .gitignore updated to include extras/openmemory-mcp/data/* and .env.backup.*.
Extras: Parakeet ASR
extras/asr-services/.env.template, extras/asr-services/docker-compose.yml, extras/asr-services/setup.sh
Adds Parakeet ASR .env.template, parameterizes host/container ports via env vars, and provisioning script.
Extras: Speaker Recognition
extras/speaker-recognition/setup.sh, extras/speaker-recognition/webui/src/pages/Enrollment.tsx, extras/speaker-recognition/.env.template
Adds speaker-recognition setup script; enrollment UI stores durations in milliseconds; default React UI port adjusted.
Docs, quickstart & ports
README.md, CLAUDE.md, backends/advanced/README.md, backends/advanced/Docs/*, Docs/ports-and-access.md, Docs/init-system.md
Broad documentation updates: React Web UI emphasis, orchestrated setup flow and commands, semantic memory features, endpoint/URL changes, and setup orchestration guidance.
Auth UI flow & health checks
backends/advanced/webui/src/contexts/AuthContext.tsx, backends/advanced/webui/src/pages/LoginPage.tsx
Login API returns richer result objects; LoginPage performs pre-flight auth health check and surfaces typed error categories.
Misc / removed legacy & ignores
init.sh (deleted), .gitignore
Removes legacy init.sh; adds .gitignore entries for .env.backup.* and extras/openmemory-mcp/data/*; minor comment/format tweaks across files.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Root as init.py (orchestrator)
  participant Adv as backends/advanced/init.py
  participant Extras as extras/* (setup)
  participant FS as Filesystem

  User->>Root: run orchestrated setup
  Root->>User: present services & prompt selection
  Root->>Adv: invoke advanced setup (pass extras URLs)
  Adv->>User: interactive prompts (auth, LLM, memory, network, HTTPS)
  Adv->>FS: backup/create .env and copy templates (memory/diarization)
  Root->>Extras: run per-service setup scripts (optional)
  Extras-->>Root: return service URLs
  Root-->>User: print summary & next steps
Loading
sequenceDiagram
  autonumber
  actor UI as Web UI
  participant API as FastAPI
  participant MemSvc as MemoryService
  participant Qdrant as Qdrant

  UI->>API: GET /api/memories?user_id=...
  API->>MemSvc: get_all_memories(user_id)
  MemSvc->>Qdrant: vector search (apply score_threshold if provided)
  Qdrant-->>MemSvc: results (with result.score)
  MemSvc-->>API: memories
  API->>MemSvc: count_memories(user_id)
  MemSvc->>Qdrant: native count API (filtered)
  Qdrant-->>MemSvc: total_count
  API-->>UI: {memories, count, total_count}
Loading
sequenceDiagram
  autonumber
  actor User
  participant UI as System UI
  participant API as FastAPI (system routes)
  participant Store as User Model
  participant Proc as ProcessorManager
  participant Trans as Transcript Store

  User->>UI: save speaker configuration
  UI->>API: POST /api/speaker-configuration
  API->>Store: save user.primary_speakers
  API-->>UI: success

  Proc->>Store: fetch user.primary_speakers
  Proc->>Trans: fetch transcript segments for audio_uuid
  alt primary speaker present
    Proc->>Proc: continue processing and create memory
  else no primary speaker
    Proc->>Proc: mark memory SKIPPED with reason
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Openmemory mcp #69 — Strong overlap on memory subsystem changes (OpenMemory MCP integration, score_threshold/count APIs, Qdrant search/count behavior).
  • Better init system #74 — Similar init/system orchestration and setup-script changes; likely touches the same initialization flow and files.

Poem

In cables' glow I twitch my ear,
New wizards whisper, "Configure here!" 🐇
Memories scored and counts in sight,
Speakers picked — skip or write.
Dashboards hum; the setup's bright,
I thump — deploy it through the night!


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f9ef977 and f710193.

📒 Files selected for processing (5)
  • backends/advanced/init.py (1 hunks)
  • backends/advanced/src/advanced_omi_backend/memory/providers/vector_stores.py (4 hunks)
  • backends/advanced/webui/src/pages/Memories.tsx (9 hunks)
  • init.py (1 hunks)
  • services.py (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch better-init-system

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 32

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (13)
backends/advanced/Docs/UI.md (1)

193-199: Streamlit section is legacy—label it clearly or move to a legacy appendix

This React UI doc still contains “Streamlit Configuration.” Mark as deprecated to prevent users configuring the wrong UI.

-### Streamlit Configuration
+### Legacy Streamlit UI (Deprecated)
+The legacy Streamlit dashboard has been moved to `src/_webui_original/` and is not used by the modern React UI. The following settings apply only to the legacy interface.
backends/advanced/Docs/quickstart.md (5)

188-191: Unify dashboard URL with dev/prod split

Elsewhere you list 5173; here you say 3000. Suggest showing both.

-1. Open `http://localhost:3000`
+1. Open the Web Dashboard:
+   - Development (Vite): `http://localhost:5173`
+   - Production (built UI): `http://localhost:3000`

160-162: Parakeet URL port inconsistent with extras/asr-services template

Here it’s 8080; the template defaults to host 8767 -> container 8765. Align to prevent 404s.

-PARAKEET_ASR_URL=http://host.docker.internal:8080
+# Match extras/asr-services/.env.template defaults
+PARAKEET_ASR_URL=http://host.docker.internal:8767

545-548: Incorrect config path for memory_config.yaml

Docs say backends/advanced-backend; the repo uses backends/advanced. Fix path to avoid confusion.

-- **Path**: `backends/advanced-backend/memory_config.yaml`
+- **Path**: `backends/advanced/memory_config.yaml`

436-446: Fix service name in ASR startup instructions
In backends/advanced/Docs/quickstart.md (lines 436–446), replace:

docker compose up moonshine -d

with:

docker compose up parakeet-asr -d

to match the parakeet-asr service defined in extras/asr-services/docker-compose.yml.


139-147: Unify recommended OpenAI model to gpt-5-mini
The docs currently mix gpt-4o-mini (env examples at lines 139–147, 559–566, 607–611, 663–666) with gpt-5-mini in comments/YAML. Update all OPENAI_MODEL entries to gpt-5-mini to match the recommended model.

- OPENAI_MODEL=gpt-4o-mini
+ OPENAI_MODEL=gpt-5-mini
backends/advanced/src/advanced_omi_backend/controllers/memory_controller.py (1)

115-123: Ownership check can falsely block valid deletions (limit=1000 window)

Fetching 1000 memories and checking membership may miss a valid memory beyond that window. Prefer a direct lookup by ID (and user) to verify ownership.

-        if not user.is_superuser:
-            # Check if memory belongs to current user
-            user_memories = await memory_service.get_all_memories(user.user_id, 1000)
-
-            memory_ids = [str(mem.get("id", mem.get("memory_id", ""))) for mem in user_memories]
-            if memory_id not in memory_ids:
-                return JSONResponse(status_code=404, content={"message": "Memory not found"})
+        if not user.is_superuser:
+            # Prefer authoritative per-ID check to avoid pagination/window issues
+            if hasattr(memory_service, "get_memory_by_id"):
+                mem = await memory_service.get_memory_by_id(memory_id)
+                if not mem or str(mem.get("user_id")) != str(user.user_id):
+                    return JSONResponse(status_code=404, content={"message": "Memory not found"})
+            else:
+                # Fallback: explicitly reject without a safe ownership check
+                return JSONResponse(status_code=403, content={"message": "Forbidden"})

If get_memory_by_id doesn’t exist yet, I can add get_memory_by_id(memory_id) or owns_memory(user_id, memory_id) to the service layer and wire providers accordingly. Want me to open a follow-up patch?

backends/advanced/Docs/memories.md (2)

446-453: Contradiction: using custom_prompt vs guidance

Earlier you warn not to use custom_prompt, yet this snippet sets work_config["custom_prompt"]. Replace with the supported key.

-    work_config = MEM0_CONFIG.copy()
-    work_config["vector_store"]["config"]["collection_name"] = "work_memories"
-    work_config["custom_prompt"] = "Focus on work-related tasks, meetings, and projects"
+    work_config = MEM0_CONFIG.copy()
+    work_config["vector_store"]["config"]["collection_name"] = "work_memories"
+    work_config["custom_fact_extraction_prompt"] = (
+        "Focus on work-related tasks, meetings, and projects"
+    )

78-108: Sync docs path for memory_service.py

  • In backends/advanced/Docs/memories.md, update the path reference:
    - The core configuration is in `src/memory/memory_service.py:45-81`:
    + The core configuration is in `src/advanced_omi_backend/memory/memory_service.py:45-81`:
backends/advanced/src/advanced_omi_backend/main.py (2)

881-904: Redact sensitive connection strings in /health.

Exposing mongodb_uri can leak credentials if MONGODB_URI embeds user:pass@host. Remove it or redact before returning.

Apply:

-            "mongodb_uri": MONGODB_URI,
+            # Avoid leaking credentials in connection strings
+            # "mongodb_uri": "(redacted)",

363-372: CORS defaults miss the documented UI dev port (5173).

Add 5173 (localhost and 127.0.0.1) and broaden Tailscale regex to cover common ports.

-default_origins = "http://localhost:3000,http://localhost:3001,http://127.0.0.1:3000,http://127.0.0.1:3002"
+default_origins = (
+    "http://localhost:3000,http://localhost:3001,http://localhost:3002,http://localhost:5173,"
+    "http://127.0.0.1:3000,http://127.0.0.1:3001,http://127.0.0.1:3002,http://127.0.0.1:5173"
+)
@@
-# Support Tailscale IP range (100.x.x.x) via regex pattern
-tailscale_regex = r"http://100\.\d{1,3}\.\d{1,3}\.\d{1,3}:3000"
+# Support Tailscale IP range (100.x.x.x) via regex pattern (common dev ports)
+tailscale_regex = r"http://100\.\d{1,3}\.\d{1,3}\.\d{1,3}:(3000|3001|3002|5173)"
README.md (2)

248-249: Web UI port consistency.

Elsewhere you direct users to 5173. Update this to avoid confusion.

-5. **Access WebUI**: `http://localhost:3000` (React) or `http://localhost:8501` (Streamlit)
+5. **Access WebUI**: `http://localhost:5173` (React dev). If using the legacy UI, see Streamlit at `http://localhost:8501`.

124-144: Directory naming consistency.

Use backends/advanced/ (matches code paths) instead of backends/advanced-backend/.

-#### **Advanced Backend** (`backends/advanced-backend/`) **RECOMMENDED**
+#### **Advanced Backend** (`backends/advanced/`) **RECOMMENDED**
🧹 Nitpick comments (82)
backends/advanced/setup-requirements.txt (2)

2-3: Constrain versions to avoid surprise breaking changes.

Prefer upper bounds for CLI libs to keep setup flows stable (e.g., rich>=13,<14 and pyyaml>=6,<7).

-rich>=13.0.0
-pyyaml>=6.0.0
+rich>=13.0.0,<14
+pyyaml>=6.0.0,<7

3-3: Add trailing newline.

Avoid EOF newline lint noise and inconsistent diffs.

setup-requirements.txt (1)

1-2: Stabilize root setup dependency by capping major.

Add an upper bound to reduce risk from major Rich changes during onboarding.

- rich>=13.0.0
+ rich>=13.0.0,<14
extras/openmemory-mcp/setup.sh (2)

2-2: Harden shell options.

Use -euo pipefail to catch unset vars and pipeline errors.

-set -e
+set -euo pipefail
+IFS=$'\n\t'

7-12: Ensure compose runs from the script directory and works across Docker/Compose variants.

Change to the script dir and support both docker compose and docker-compose.

-echo "Starting OpenMemory MCP server..."
-echo ""
-
-# Start external server
-docker compose up -d
+echo "Starting OpenMemory MCP server..."
+echo ""
+
+# Run from this directory
+cd "$(dirname "$0")"
+
+# Pick compose command
+if command -v docker >/dev/null && docker compose version >/dev/null 2>&1; then
+  COMPOSE_CMD="docker compose"
+elif command -v docker-compose >/dev/null 2>&1; then
+  COMPOSE_CMD="docker-compose"
+else
+  echo "ERROR: Docker Compose not found." >&2
+  exit 1
+fi
+
+$COMPOSE_CMD up -d
extras/openmemory-mcp/docker-compose.yml (3)

27-31: Simplify healthcheck to avoid Python/requests dependency.

Using curl reduces image coupling and speeds checks.

-      test: ["CMD", "python", "-c", "import requests; exit(0 if requests.get('http://localhost:8765/docs').status_code == 200 else 1)"]
+      test: ["CMD-SHELL", "curl -fsS http://localhost:8765/docs >/dev/null || exit 1"]

Optionally switch to a dedicated /health endpoint if available.


41-44: Have UI wait for a healthy API.

Gate UI on openmemory-mcp health for smoother first run.

-    depends_on:
-      - openmemory-mcp
+    depends_on:
+      openmemory-mcp:
+        condition: service_healthy

44-44: Add trailing newline at EOF.

Fixes YAML lint error and avoids noisy diffs.

-      - ui  # Only starts when --profile ui is used
+      - ui  # Only starts when --profile ui is used
+
backends/advanced/src/advanced_omi_backend/memory/compat_service.py (2)

233-237: Inline import inside function violates repo coding guidelines

Move from advanced_omi_backend.users import User to the top of the file or add a brief comment explaining a necessary lazy import to break a cycle (prefer refactor).

-        try:
-            from advanced_omi_backend.users import User
+        try:
+            # from advanced_omi_backend.users import User  # Move to module top if possible

If this import is required to avoid a circular dependency, document the reason inline per “Use lazy imports sparingly.”


394-419: Service factory lazy imports — prefer module-level imports (or document cycle)

Both get_memory_service() and shutdown_memory_service() import from .service_factory inside functions. Per guidelines, move to the top of the module unless there’s a proven cycle; otherwise, add a short comment justifying the lazy import.

Minimal change if cycles exist:

-        from .service_factory import get_memory_service as get_core_service
+        # Lazy import to avoid init-time cycles; document if required by design.
+        from .service_factory import get_memory_service as get_core_service

Preferred change (no cycles):

+# at top of file with other imports
+from .service_factory import (
+    get_memory_service as get_core_service,
+    shutdown_memory_service as shutdown_core_service,
+)
...
-    from .service_factory import shutdown_memory_service as shutdown_core_service
-    shutdown_core_service()
+    shutdown_core_service()
backends/advanced/docker-compose.yml (3)

45-47: Startup race if MEMORY_PROVIDER=QDRANT

Dropping qdrant from depends_on may start backend before Qdrant is ready. Recommend keeping an opt-in path that waits on Qdrant health when Qdrant is used.

Suggested commented guidance:

-      # You may not want qdrant if you are using openmemory_mcp
-      # qdrant:
-      #   condition: service_started
+      # If using Qdrant (MEMORY_PROVIDER=QDRANT), uncomment below and ensure qdrant has a healthcheck.
+      # qdrant:
+      #   condition: service_healthy

Add qdrant healthcheck:

   qdrant:
     image: qdrant/qdrant:latest
     ports:
-      - "6333:6333" # gRPC
-      - "6334:6334" # HTTP
+      - "6333:6333" # HTTP
+      - "6334:6334" # gRPC
     volumes:
       - ./data/qdrant_data:/qdrant/storage 
+    healthcheck:
+      test: ["CMD", "curl", "-f", "http://localhost:6333/readyz"]
+      interval: 10s
+      timeout: 5s
+      retries: 12

82-84: Port comments reversed

Qdrant defaults: 6333 HTTP, 6334 gRPC. Fix comments to avoid confusion.

-      - "6333:6333" # gRPC
-      - "6334:6334" # HTTP
+      - "6333:6333" # HTTP
+      - "6334:6334" # gRPC

166-175: Dead commented volumes block

These named volumes are unused and fully commented. Safe to remove to reduce noise.

backends/advanced/webui/src/styles/slider.css (3)

52-66: Track has no background; add progress fill and Firefox support

The track pseudo-elements lack a background, so appearance will vary. Add a base track color, WebKit gradient driven by --progress, and Firefox ::-moz-range-progress for the filled portion.

 input[type="range"].slider::-webkit-slider-runnable-track {
   width: 100%;
   height: 8px;
   cursor: pointer;
   border-radius: 5px;
+  background: linear-gradient(
+    to right,
+    #3B82F6 var(--progress, 0%),
+    #E5E7EB var(--progress, 0%)
+  );
 }
 
 input[type="range"].slider::-moz-range-track {
   width: 100%;
   height: 8px;
   cursor: pointer;
   border-radius: 5px;
+  background: #E5E7EB;
 }
+
+/* Firefox filled portion (left of thumb) */
+input[type="range"].slider::-moz-range-progress {
+  height: 8px;
+  border-radius: 5px;
+  background: #3B82F6;
+}

1-15: Add focus-visible styles for accessibility

Provide clear keyboard focus on the thumb.

 input[type="range"].slider {
   -webkit-appearance: none;
   appearance: none;
   width: 100%;
   height: 8px;
   border-radius: 5px;
   outline: none;
   opacity: 0.9;
   transition: opacity 0.2s;
 }
+
+/* Keyboard focus */
+input[type="range"].slider:focus-visible::-webkit-slider-thumb {
+  box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.35);
+}
+input[type="range"].slider:focus-visible::-moz-range-thumb {
+  box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.35);
+}

67-78: Dark mode only sets background on the input, not the track

Mirror the gradient on the track pseudo-elements for consistent dark-mode rendering.

 .dark input[type="range"].slider {
   background: linear-gradient(to right, #3B82F6 var(--progress), #374151 var(--progress));
 }
 
 .dark input[type="range"].slider::-webkit-slider-thumb {
   background: #60A5FA;
 }
 
 .dark input[type="range"].slider::-moz-range-thumb {
   background: #60A5FA;
 }
+/* Dark tracks */
+.dark input[type="range"].slider::-webkit-slider-runnable-track {
+  background: linear-gradient(
+    to right,
+    #60A5FA var(--progress, 0%),
+    #1F2937 var(--progress, 0%)
+  );
+}
+.dark input[type="range"].slider::-moz-range-track {
+  background: #1F2937;
+}
+.dark input[type="range"].slider::-moz-range-progress {
+  background: #60A5FA;
+}
backends/advanced/src/advanced_omi_backend/users.py (1)

26-26: Prefer a typed schema over list[dict] for primary_speakers

Using a Pydantic model improves validation and API docs. Keep default_factory=list.

-    primary_speakers: list[dict] = Field(default_factory=list)
+    primary_speakers: list["PrimarySpeaker"] = Field(default_factory=list)
...
-    # Speaker processing filter configuration
-    primary_speakers: list[dict] = Field(default_factory=list)
+    # Speaker processing filter configuration
+    primary_speakers: list["PrimarySpeaker"] = Field(default_factory=list)

Add this model in the same module (outside the selected range):

class PrimarySpeaker(BaseModel):
    speaker_id: str
    label: str | None = None
    active: bool = True
    min_confidence: float | None = None  # optional per-user threshold override

I can push a small migration helper to normalize any existing dict entries into PrimarySpeaker instances if needed.

Also applies to: 41-42

CLAUDE.md (3)

484-486: Tighten wording for new Friend‑Lite features; keep terms consistent with backend.

Minor phrasing tweaks improve clarity and align with the Qdrant native count usage and similarity score handling.

Apply:

- - **Semantic search** with relevance threshold filtering
- - **Memory count API** with total count tracking from native Qdrant
- - Direct Qdrant vector storage with accurate similarity scoring
+ - **Semantic search** with relevance-threshold filtering
+ - **Memory count API** using Qdrant's native count endpoint (total_count)
+ - Direct Qdrant vector storage with accurate similarity scoring

523-529: Fix “etc.” punctuation and verify comparison wording.

  • “etc.” needs a period in AmE, and “threshold filtering” vs “relevance scoring” should be consistently contrasted.

Apply:

- | **Cross-client** | Friend-Lite only | Works with Claude Desktop, Cursor, etc |
+ | **Cross-client** | Friend-Lite only | Works with Claude Desktop, Cursor, etc. |

Also confirm that “Friend‑Lite: threshold filtering” + “OpenMemory: relevance scoring” reflects actual API behavior in both providers (docs and code). If Friend‑Lite now exposes both raw scores and a threshold, consider “relevance scoring with optional threshold filtering” for precision.


647-647: Document request parameters for /api/memories/search.

Readers will expect param names (e.g., query, limit, min_score/relevance_threshold). Add them and an example curl.

Would you like me to draft the exact parameter list once you confirm the final names in memory_controller.py?

backends/advanced/src/advanced_omi_backend/memory/base.py (1)

136-148: Optional count API is fine; signal “unsupported” clearly.

Returning None here is good to indicate “not supported.” Consider prefixing the unused param to silence ARG002 or add a short explanation comment.

Apply:

- async def count_memories(self, user_id: str) -> Optional[int]:
+ async def count_memories(self, user_id: str) -> Optional[int]:
+     # Optional API; default implementation intentionally unused

(Or rename to _user_id if your linters allow signature variance across overrides.)

backends/advanced/README.md (1)

54-65: Standardize HTTPS script name/location and flow.

Docs here reference init-https.sh while SETUP_SCRIPTS.md uses setup-https.sh. Pick one canonical name (recommend setup-https.sh under backends/advanced) and update both places for consistency. Also add “From backends/advanced” context to avoid path confusion.

Proposed tweak (if canonicalizing to setup-https.sh):

-# If not done during init.sh, run HTTPS setup
-./init-https.sh 100.83.66.30  # Replace with your IP
+# If not done during init, run HTTPS setup (from backends/advanced)
+./setup-https.sh 100.83.66.30  # Replace with your IP
backends/advanced/src/advanced_omi_backend/speaker_recognition_client.py (2)

323-361: Honor user_id in GET /speakers and improve exception logging.

  • If the service later supports per-user filtering, pass user_id as an optional query param now.
  • Use logger.exception for stack traces on unexpected errors.

Apply:

-            async with aiohttp.ClientSession() as session:
-                # Use the /speakers endpoint - currently no user filtering in speaker service
-                async with session.get(
-                    f"{self.service_url}/speakers",
-                    timeout=aiohttp.ClientTimeout(total=10),
-                ) as response:
+            async with aiohttp.ClientSession() as session:
+                params = {}
+                if user_id:
+                    params["user_id"] = user_id
+                async with session.get(
+                    f"{self.service_url}/speakers",
+                    params=params or None,
+                    timeout=aiohttp.ClientTimeout(total=10),
+                ) as response:
@@
-        except Exception as e:
-            logger.error(f"Error getting enrolled speakers: {e}")
+        except Exception as e:
+            logger.exception(f"Error getting enrolled speakers: {e}")
             return {"speakers": []}

363-401: Health check is pragmatic; prefer exception logs for diagnostics.

Swap to logger.exception for the outer catch; keep the lightweight per-endpoint probing.

Apply:

-        except Exception as e:
-            logger.error(f"Error during speaker service health check: {e}")
+        except Exception as e:
+            logger.exception(f"Error during speaker service health check: {e}")
             return False
extras/speaker-recognition/webui/src/pages/Enrollment.tsx (3)

117-125: Avoid double recomputation and state divergence between sessions and currentSession

You recompute derived fields twice. Centralize to a helper to keep logic single-sourced.

@@
-  const updateSession = useCallback((sessionId: string, updates: Partial<EnrollmentSession>) => {
+  const withDerived = (base: EnrollmentSession, updates: Partial<EnrollmentSession>) => {
+    const next = { ...base, ...updates }
+    if (updates.audioFiles) {
+      next.totalDuration = updates.audioFiles.reduce((sum, a) => sum + a.duration, 0)
+      next.quality = calculateSessionQuality(updates.audioFiles)
+    }
+    return next
+  }
+
+  const updateSession = useCallback((sessionId: string, updates: Partial<EnrollmentSession>) => {
     setSessions(prev => prev.map(session => {
       if (session.id === sessionId) {
-        const updatedSession = { ...session, ...updates }
-        if (updates.audioFiles) {
-          updatedSession.totalDuration = updates.audioFiles.reduce((sum, audio) => sum + audio.duration, 0)
-          updatedSession.quality = calculateSessionQuality(updates.audioFiles)
-        }
-        return updatedSession
+        return withDerived(session, updates)
       }
       return session
     }))
 
     if (currentSession?.id === sessionId) {
-      setCurrentSession(prev => {
-        if (!prev) return null
-        const updatedSession = { ...prev, ...updates }
-        if (updates.audioFiles) {
-          updatedSession.totalDuration = updates.audioFiles.reduce((sum, audio) => sum + audio.duration, 0)
-          updatedSession.quality = calculateSessionQuality(updates.audioFiles)
-        }
-        return updatedSession
-      })
+      setCurrentSession(prev => (prev ? withDerived(prev, updates) : null))
     }
   }, [currentSession, calculateSessionQuality])

187-190: Fix setInterval typing for browser (avoid NodeJS.Timeout in web)

Use ReturnType to prevent DOM/Node mismatch in TS configs.

-  const recordingIntervalRef = useRef<NodeJS.Timeout | null>(null)
+  const recordingIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null)

433-436: Preserve file extensions for recorded blobs in FormData

Recorded items use names like “Recording 10:01:00” (no extension). Some backends infer content-type from filename. Add an extension based on blob.type.

-      for (const audio of currentSession.audioFiles) {
-        formData.append('files', audio.blob, audio.name)
-      }
+      for (const audio of currentSession.audioFiles) {
+        const ext = audio.blob.type.includes('wav') ? '.wav'
+                  : audio.blob.type.includes('webm') ? '.webm'
+                  : ''
+        const fname = audio.name?.toLowerCase().endsWith(ext) ? audio.name : `${audio.name}${ext}`
+        formData.append('files', audio.blob, fname)
+      }
backends/advanced/Docs/UI.md (1)

12-16: Clarify dev vs prod dashboard URLs and keep consistent across docs

Explicitly call out “5173 (dev, Vite)” and “3000 (prod, built app)” to avoid confusion and align with Quickstart.

-- **HTTP**: `http://localhost:5173` (development) or `http://localhost:3000` (production)
+- **HTTP**:
+  - Development (Vite): `http://localhost:5173`
+  - Production (built UI): `http://localhost:3000`
extras/asr-services/.env.template (1)

8-9: Order ports and add trailing newline to satisfy linters and readability

Reorder container→host and end file with newline.

-# Service Port Configuration
-PARAKEET_HOST_PORT=8767
-PARAKEET_CONTAINER_PORT=8765
+# Service Port Configuration
+PARAKEET_CONTAINER_PORT=8765
+PARAKEET_HOST_PORT=8767
+
extras/asr-services/setup.sh (3)

2-2: Harden shell options for safer failure handling

Use stricter bash flags to catch unset vars and pipeline errors.

-set -e
+set -Eeuo pipefail
+IFS=$'\n\t'

8-14: Guard against missing .env.template

If the template is absent, cp will fail silently under set -e. Add a check and clear error.

 if [ ! -f ".env" ]; then
-    echo "📄 Creating .env file from template..."
-    cp .env.template .env
-    echo "✅ .env file created"
+    echo "📄 Creating .env file from template..."
+    if [ -f ".env.template" ]; then
+        cp .env.template .env
+        echo "✅ .env file created"
+    else
+        echo "❌ .env.template not found; cannot create .env"
+        exit 1
+    fi
 else

22-25: Print the actual mapped URL/port instead of a hardcoded 8767 and host.docker.internal

Host port is now configurable via PARAKEET_HOST_PORT; also, host.docker.internal is not universal on Linux. Detect the bound port and print both host-facing and in-container URLs.

-echo "✅ Parakeet ASR running:"
-echo "  📝 Service URL: http://host.docker.internal:8767"
-echo ""
-echo "💡 Configure PARAKEET_ASR_URL in your backend's transcription settings"
+echo "✅ Parakeet ASR running:"
+# Resolve container and host ports dynamically
+CONTAINER_PORT="${PARAKEET_CONTAINER_PORT:-8765}"
+HOST_PORT="$(docker compose port parakeet-asr "$CONTAINER_PORT" 2>/dev/null | awk -F: 'END{print $2}')"
+HOST_PORT="${HOST_PORT:-${PARAKEET_HOST_PORT:-8767}}"
+echo "  📝 Host URL:        http://localhost:${HOST_PORT}"
+echo "  📝 In-container URL: http://parakeet-asr:${CONTAINER_PORT}"
+echo ""
+echo "💡 Configure PARAKEET_ASR_URL in your backend's transcription settings."
+echo "   - If backend runs on host:        http://localhost:${HOST_PORT}"
+echo "   - If backend runs in Docker:      http://parakeet-asr:${CONTAINER_PORT}"
backends/advanced/src/advanced_omi_backend/controllers/memory_controller.py (2)

5-7: Remove unused import

asyncio isn’t used in this module.

-import asyncio

82-101: Consider including total_count in search responses for UI parity

If the UI displays global counts, return total_count here too (service returns None when unsupported).

Example:

total_count = await memory_service.count_memories(target_user_id)
return {"query": query, "results": search_results, "count": len(search_results), "total_count": total_count, "user_id": target_user_id}
backends/advanced/run-test.sh (2)

70-76: Log when template missing to aid diagnostics

Emit a warning if memory_config.yaml.template is absent so failures aren’t silent.

 if [ ! -f "memory_config.yaml" ] && [ -f "memory_config.yaml.template" ]; then
     print_info "Creating memory_config.yaml from template..."
     cp memory_config.yaml.template memory_config.yaml
     print_success "memory_config.yaml created"
+elif [ ! -f "memory_config.yaml" ]; then
+    print_warning "memory_config.yaml.template not found; proceeding without memory_config.yaml"
 fi

77-83: Mirror the warning for diarization config

Same rationale as above.

 if [ ! -f "diarization_config.json" ] && [ -f "diarization_config.json.template" ]; then
     print_info "Creating diarization_config.json from template..."
     cp diarization_config.json.template diarization_config.json
     print_success "diarization_config.json created"
+elif [ ! -f "diarization_config.json" ]; then
+    print_warning "diarization_config.json.template not found; proceeding without diarization_config.json"
 fi
extras/asr-services/docker-compose.yml (1)

21-24: Use mapping-style env for Compose and brace expansion

Minor style/robustness tweak: Compose handles mapping-style environment well and ${VAR} avoids edge cases.

-    environment:
-      - HF_HOME=/models
-      - PARAKEET_MODEL=$PARAKEET_MODEL
+    environment:
+      HF_HOME: /models
+      PARAKEET_MODEL: ${PARAKEET_MODEL}
backends/advanced/init-https.sh (1)

73-83: Good: provision memory_config.yaml from template

This step reduces setup friction and mirrors other scripts. Consider adding the same guard for diarization_config.json for parity.

extras/speaker-recognition/setup.sh (1)

26-31: Optional: sanity check GPU availability.

If gpu selected, warn when nvidia-smi is unavailable and fall back to CPU.

 if [ -f ".env" ]; then
@@
   COMPUTE_MODE=$(printf "%s" "$COMPUTE_MODE" | tr '[:upper:]' '[:lower:]')
+  if [ "$COMPUTE_MODE" = "gpu" ] && ! command -v nvidia-smi >/dev/null 2>&1; then
+    echo "⚠️  GPU requested but NVIDIA runtime not detected; falling back to cpu"
+    COMPUTE_MODE=cpu
+  fi
backends/advanced/src/advanced_omi_backend/memory/memory_service.py (2)

275-296: Make exception logging actionable; keep return semantics.

Log stacktrace on failure and avoid blind Exception.

-        try:
-            count = await self.vector_store.count_memories(user_id)
-            memory_logger.info(f"🔢 Total {count} memories for user {user_id}")
-            return count
-        except Exception as e:
-            memory_logger.error(f"Count memories failed: {e}")
-            return None
+        try:
+            count = await self.vector_store.count_memories(user_id)
+            memory_logger.info(f"🔢 Total {count} memories for user {user_id}")
+            return count
+        except Exception:
+            memory_logger.exception("Count memories failed")
+            return None

Do downstream callers expect None on provider-unsupported cases and 0 on “supported but empty”? If not, we should standardize.


57-99: Index payload fields used in filters to keep count/search O(1) on payload.

Since filters use metadata.user_id heavily, create a payload index at init to avoid full scans on large collections.

If not already elsewhere, augment vector store init to call create_payload_index on metadata.user_id (keyword).

backends/advanced/src/advanced_omi_backend/memory/providers/vector_stores.py (2)

339-363: Use exact counts explicitly and improve error logging.

Make “exactness” explicit and log stacktraces for count failures.

-            result = await self.client.count(
+            result = await self.client.count(
                 collection_name=self.collection_name,
-                count_filter=search_filter
+                count_filter=search_filter,
+                exact=True
             )
@@
-        except Exception as e:
-            memory_logger.error(f"Qdrant count memories failed: {e}")
+        except Exception:
+            memory_logger.exception("Qdrant count memories failed")
             return 0

The Python client’s count returns a result with .count; API docs confirm this. Please ensure the client version matches the server (1.10+).


151-170: Optional: add a payload index for metadata.user_id to speed filter operations.

Create a keyword index once to accelerate search/scroll/count by user.

Example (top-level init phase):

await self.client.create_payload_index(
    collection_name=self.collection_name,
    field_name="metadata.user_id",
    field_schema="keyword",
)

Docs: Count/filters rely on payload; indexing avoids scans.

init.py (6)

7-13: Remove unused import and tidy imports

rprint is unused. Also keep import groups stdlib → third‑party → local per guidelines.

 import subprocess
 import sys
 from pathlib import Path
-from rich.console import Console
-from rich.prompt import Confirm
-from rich import print as rprint
+from rich.console import Console
+from rich.prompt import Confirm

26-41: Make extras setup portable (no exec bit required)

Running ./setup.sh will fail if the script isn’t executable on fresh clones or Windows. Invoke via bash to avoid permission pitfalls.

 'extras': {
     'speaker-recognition': {
         'path': 'extras/speaker-recognition',
-        'cmd': ['./setup.sh'],
+        'cmd': ['bash', 'setup.sh'],
         'description': 'Speaker identification and enrollment'
     },
     'asr-services': {
         'path': 'extras/asr-services', 
-        'cmd': ['./setup.sh'],
+        'cmd': ['bash', 'setup.sh'],
         'description': 'Offline speech-to-text (Parakeet)'
     },
     'openmemory-mcp': {
         'path': 'extras/openmemory-mcp',
-        'cmd': ['./setup.sh'], 
+        'cmd': ['bash', 'setup.sh'], 
         'description': 'OpenMemory MCP server'
     }
 }

44-62: Check executability and provide clearer messaging

Consider checking script executability or switching to bash (above). The “will be created in Phase 2” message can confuse users if the folder will not actually be created by this PR.

-            return False, f"Script {script_path} does not exist (will be created in Phase 2)"
+            return False, f"Script {script_path} not found"

95-106: Make service URLs configurable and resilient

Hardcoding host.docker.internal can break on some Linux/Podman setups. Allow override via env vars with sensible defaults.

     if service_name == 'advanced':
         service = SERVICES['backend'][service_name]
 
         # For advanced backend, pass URLs of other selected services
         cmd = service['cmd'].copy()
         if 'speaker-recognition' in selected_services:
-            cmd.extend(['--speaker-service-url', 'http://host.docker.internal:8085'])
+            cmd.extend(['--speaker-service-url', os.getenv('SPEAKER_SERVICE_URL', 'http://host.docker.internal:8085')])
         if 'asr-services' in selected_services:
-            cmd.extend(['--parakeet-asr-url', 'http://host.docker.internal:8767'])
+            cmd.extend(['--parakeet-asr-url', os.getenv('PARAKEET_ASR_URL', 'http://host.docker.internal:8767')])

Add at top:

+import os

181-212: Remove f-strings without placeholders

These trigger Ruff F541; drop the f prefix.

-    console.print(f"\n🎊 [bold green]Setup Complete![/bold green]")
+    console.print("\n🎊 [bold green]Setup Complete![/bold green]")
@@
-    console.print(f"\n🚀 [bold]Enjoy Friend-Lite![/bold]")
+    console.print("\n🚀 [bold]Enjoy Friend-Lite![/bold]")
@@
-    console.print(f"\n💡 [dim]Tip: You can also setup services individually:[/dim]")
-    console.print(f"[dim]   cd backends/advanced && uv run --with-requirements setup-requirements.txt python init.py[/dim]")
+    console.print("\n💡 [dim]Tip: You can also setup services individually:[/dim]")
+    console.print("[dim]   cd backends/advanced && uv run --with-requirements setup-requirements.txt python init.py[/dim]")

16-94: Minor UX polish: dedupe and deterministic ordering

  • Use a set to avoid accidental duplicates in selected.
  • Present extras in a stable sorted order by key.
-    selected = []
+    selected: list[str] = []
@@
-    for service_name, service_config in SERVICES['extras'].items():
+    for service_name in sorted(SERVICES['extras'].keys()):
+        service_config = SERVICES['extras'][service_name]
@@
-        if enable_service:
-            selected.append(service_name)
+        if enable_service and service_name not in selected:
+            selected.append(service_name)
backends/advanced/Docs/memories.md (4)

168-181: Model names seem inconsistent/duplicated

OPENAI_MODEL=gpt-5-mini appears twice (recommended and alternative). Verify actual model names and keep examples consistent.


569-589: Tighten wording and list formatting for enhanced search/count

Fix minor grammar and keep bullets consistent.

-The memory service exposes these endpoints with enhanced search capabilities:
+The memory service exposes these endpoints with enhanced search capabilities:
@@
-- `GET /api/memories` - Get user memories with total count support (keyed by database user_id)
-- `GET /api/memories/search?query={query}&limit={limit}` - **Semantic memory search** with relevance scoring (user-scoped)  
-- `GET /api/memories/unfiltered` - User's memories without filtering for debugging
+- `GET /api/memories` — Get user memories with total count support (keyed by database user_id)
+- `GET /api/memories/search?query={query}&limit={limit}` — semantic memory search with relevance scoring (user‑scoped)
+- `GET /api/memories/unfiltered` — user’s memories without filtering (for debugging)
@@
-**Semantic Search (`/api/memories/search`)**:
-- **Relevance Scoring**: Returns similarity scores from vector database (0.0-1.0 range)
-- **Configurable Limits**: Supports `limit` parameter for result count control  
-- **User Scoped**: Results automatically filtered by authenticated user
-- **Vector-based**: Uses embeddings for contextual understanding beyond keyword matching
+**Semantic search (`/api/memories/search`):**
+- Relevance scoring: returns similarity scores from the vector database (0.0–1.0)
+- Configurable limits: supports a `limit` parameter to control result count  
+- User scoped: results automatically filtered by the authenticated user
+- Vector‑based: uses embeddings for contextual understanding beyond keyword matching
@@
-**Memory Count API**: 
-- **Friend-Lite Provider**: Native Qdrant count API provides accurate total counts
-- **OpenMemory MCP Provider**: Count support varies by OpenMemory implementation
-- **Response Format**: `{"memories": [...], "total_count": 42}` when supported
+**Memory count API:**
+- Friend‑Lite provider: native Qdrant count API provides accurate total counts
+- OpenMemory MCP provider: count support varies by implementation
+- Response format: `{"memories": [...], "total_count": 42}` (when supported)

571-576: Confirm auth requirements for admin/debug endpoints

Docs list DELETE /api/memories/{memory_id} and /api/memories/admin. Ensure they’re documented as auth‑protected (admin/superuser) if that’s the case.


675-677: Fix incomplete concluding sentences

The last two lines trail off; tighten for clarity.

-Both load all user memories and view all memories are helpful
-Both views complement each other - the debug view helps you understand how the system is working, while the clean view
-helps you understand what content is being stored.
+Both “all user memories” and “view all memories” are helpful.
+The debug view shows how the system is working, while the clean view focuses on the content being stored.
backends/advanced/src/advanced_omi_backend/routers/modules/system_routes.py (4)

8-12: Remove unused import

Optional isn’t used.

-from typing import Optional

106-119: Body schema for POST and explicit validation

Prefer a Pydantic model for primary_speakers to get request body validation and OpenAPI docs.

+from pydantic import BaseModel
+
+class PrimarySpeaker(BaseModel):
+    speaker_id: str
+    name: str
+    user_id: str
+    selected_at: str | None = None
@@
-@router.post("/speaker-configuration")
+@router.post("/speaker-configuration")
 async def update_speaker_configuration(
-    primary_speakers: list[dict],
+    primary_speakers: list[PrimarySpeaker],
     current_user: User = Depends(current_active_user)
 ):
     """Update current user's primary speakers configuration."""
-    return await system_controller.update_speaker_configuration(current_user, primary_speakers)
+    return await system_controller.update_speaker_configuration(
+        current_user, [s.model_dump() for s in primary_speakers]
+    )

127-130: Intentional unused arg for auth — silence linter

current_user is unused but enforces admin auth. Use _ to silence ARG001.

-async def get_speaker_service_status(current_user: User = Depends(current_superuser)):
+async def get_speaker_service_status(_: User = Depends(current_superuser)):

106-125: Optional: add response models for better contracts

Consider response_model=... for these endpoints to lock down API shapes and improve docs.

backends/advanced/src/advanced_omi_backend/main.py (4)

1101-1144: OpenMemory MCP probe looks good.

Cleanly bounded to provider + URL, 5s timeout, non-critical. Consider preferring /health first if available, but current /docs 200 check is acceptable.


222-233: Move mid-file imports to top to comply with guidelines.

Top-level imports appear mid-file; relocate beside other imports after the module docstring. Verify it doesn’t reintroduce cycles.

-from advanced_omi_backend.client_manager import init_client_manager
@@
-from advanced_omi_backend.client_manager import (
-    init_client_user_mapping,
-    register_client_user_mapping,
-    track_client_user_relationship,
-    unregister_client_user_mapping,
-)

Add near existing imports (around other local imports):

+from advanced_omi_backend.client_manager import (
+    init_client_manager,
+    init_client_user_mapping,
+    register_client_user_mapping,
+    track_client_user_relationship,
+    unregister_client_user_mapping,
+)

941-968: Prefer logger.exception and structured logging placeholders.

Error branches use f-strings with logger.error and broad Exception. Use logger.exception (captures traceback) and %s placeholders for args. Keep broad catches where appropriate, but prefer narrower exceptions when possible.

Example:

-        application_logger.error(f"⚠️ Connection Failed: {str(e)} - Service may not be running")
+        application_logger.exception("⚠️ Connection Failed: Service may not be running: %s", e)

Also applies to: 1085-1099, 1135-1143


1051-1059: Optional services downgrade overall health.

Speech-to-text not configured sets overall_healthy=False. If STT is optional in your deployment, consider not degrading overall status, or gate it by an env (e.g., OPTIONAL_STT=true).

README.md (2)

11-16: Tighten wording and cross-link docs.

-**Manual setup (alternative):**
-1. Copy `.env.template` to `.env` and configure your API keys manually
-2. Set up transcription provider (Deepgram/Mistral/Offline), LLM provider (OpenAI/Ollama), and memory provider
-3. Start services and optional extras as needed
-
-**Documentation:** See `CLAUDE.md` and `backends/advanced/Docs/quickstart.md` for detailed setup
+**Manual setup (alternative):**
+1. Copy `.env.template` to `.env` and set required keys
+2. Configure transcription (Deepgram/Mistral/Offline), LLM (OpenAI/Ollama), and memory provider
+3. Start services and any optional extras
+
+**Documentation:** See `CLAUDE.md` and `backends/advanced/Docs/quickstart.md` for details

131-139: Reflect new UI/semantics concisely.

-- **Semantic memory search** with relevance threshold filtering and total count display
-- **Speaker-based memory filtering**: Optional control over processing based on participant presence
+- **Semantic memory search** with relevance-threshold filtering and total-count display
+- **Speaker-based memory filtering** to gate processing based on participant presence
- ...
-- Modern React web UI with live recording and advanced search
+- Modern React web UI with live recording and advanced/semantic search
backends/advanced/src/advanced_omi_backend/controllers/system_controller.py (4)

917-925: Get speaker config: OK; prefer logger.exception on error.

Logic is fine; switch to logger.exception in the except to capture traceback.

-        logger.error(f"Error getting speaker configuration for user {user.user_id}: {e}")
+        logger.exception("Error getting speaker configuration for user %s", user.user_id)

974-1008: Avoid magic user_id; prefer None or current user if supported.

The client notes no per-user filtering; drop "1" to avoid confusion. If/when supported, pass user.user_id.

-        # Get enrolled speakers - using hardcoded user_id=1 for now (as noted in speaker_recognition_client.py)
-        speakers = await speaker_client.get_enrolled_speakers(user_id="1")
+        # Speaker service currently ignores user_id; call without magic values
+        speakers = await speaker_client.get_enrolled_speakers()

1010-1054: Move imports out of functions; prefer logger.exception on errors.

Import SpeakerRecognitionClient at module top for consistency (no circular deps here) and switch error logs to logger.exception.

-        from advanced_omi_backend.speaker_recognition_client import SpeakerRecognitionClient
+        # (moved to top-level imports)
@@
-        logger.error(f"Error checking speaker service status: {e}")
+        logger.exception("Error checking speaker service status: %s", e)

Add near other imports:

+from advanced_omi_backend.speaker_recognition_client import SpeakerRecognitionClient

82-101: Commented-out code via triple-quoted string.

Replace the inert triple-quoted block with real comments to avoid stray bytecode and improve clarity.

-        """This is a hack to update it the DB INCASE a process failed
+        # This is a hack to update in the DB in case a process failed
         if transcription_stage.get("status") == "started" and not transcription_stage.get("completed", False):
             ...
-        """
+        # end hack
backends/advanced/setup-https.sh (4)

91-93: Add editor fallback when $EDITOR/nano are unavailable

On minimal systems nano might not exist. Provide a safe fallback.

-        if prompt_yes_no "Would you like to edit .env now?" "y"; then
-            ${EDITOR:-nano} .env
-        fi
+        if prompt_yes_no "Would you like to edit .env now?" "y"; then
+            EDITOR_CMD="${EDITOR:-${VISUAL:-nano}}"
+            command -v "$EDITOR_CMD" >/dev/null 2>&1 || EDITOR_CMD="vi"
+            "$EDITOR_CMD" .env
+        fi

73-81: Guard for missing .env.template during “update from template”

When .env exists and user chooses update, the script assumes .env.template exists; add a check to avoid abrupt exit due to set -e.

-    if prompt_yes_no "Do you want to update it from template?" "n"; then
+    if prompt_yes_no "Do you want to update it from template?" "n"; then
+        if [ ! -f ".env.template" ]; then
+            print_error ".env.template not found!"
+            exit 1
+        fi
         cp .env .env.backup

148-166: Validate IP input for HTTPS init

A simple IPv4 check avoids accidental bad input being passed to init-https.sh.

-        read -p "IP Address: " TAILSCALE_IP
+        read -r -p "IP Address: " TAILSCALE_IP
@@
-        if [ -n "$TAILSCALE_IP" ]; then
+        if [[ -n "$TAILSCALE_IP" && "$TAILSCALE_IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
             ./init-https.sh "$TAILSCALE_IP"
             HTTPS_ENABLED=true
         else
             print_warning "Skipping HTTPS setup - no IP provided"
             HTTPS_ENABLED=false
         fi

240-243: Use HTTPS health endpoint when HTTPS is enabled

Show the correct curl command based on HTTPS choice (consider -k for self-signed).

-echo "3. Check service health:"
-echo "   ${CYAN}curl http://localhost:8000/health${NC}"
+echo "3. Check service health:"
+if [ "$HTTPS_ENABLED" = true ]; then
+  echo "   ${CYAN}curl -k https://localhost/health${NC}"
+else
+  echo "   ${CYAN}curl http://localhost:8000/health${NC}"
+fi
backends/advanced/webui/src/pages/Memories.tsx (3)

107-114: Use functional state updates after async delete

Avoid stale closures when updating arrays after await.

-      setMemories(memories.filter(m => m.id !== memoryId))
+      setMemories(prev => prev.filter(m => m.id !== memoryId))
       if (isSemanticFilterActive) {
-        setSemanticResults(semanticResults.filter(m => m.id !== memoryId))
+        setSemanticResults(prev => prev.filter(m => m.id !== memoryId))
       }

244-264: Let users run semantic search even with zero loaded memories

The search bar is hidden when memories.length === 0. Consider always showing the search UX for discoverability.

-{memories.length > 0 && (
+{true && (
   <div className="relative flex items-center">

46-58: Remove noisy console logs in production

Multiple verbose logs may clutter the console. Consider gating behind env or removing before release.

backends/advanced/webui/src/pages/System.tsx (4)

155-166: Replace only-first-underscore with all-underscores

service.replace('_', ' ') changes only the first underscore. Use global replace.

-    return displayNames[service] || service.replace('_', ' ').toUpperCase()
+    return displayNames[service] || service.replaceAll('_', ' ').toUpperCase()
+    // If older TS/targets: service.split('_').join(' ').toUpperCase()

260-275: Avoid any by extending the service status type

You’re reading .status and .provider via as any. Extend the HealthData service value type to include optional fields for safer access.

-  services: Record<string, {
-    healthy: boolean
-    message?: string
-  }>
+  services: Record<string, {
+    healthy: boolean
+    message?: string
+    status?: string
+    provider?: string
+  }>

And remove the casts.


559-571: Load speaker service status when user changes

user?.is_superuser is captured once. Add user to dependencies so admins toggling roles see status without reload.

-  useEffect(() => {
-    loadSpeakerData()
-  }, [])
+  useEffect(() => {
+    loadSpeakerData()
+  }, [user])

615-617: Clean up save timeout on unmount

Avoid setState on unmounted component.

-      setTimeout(() => setMessage(''), 3000)
+      const t = setTimeout(() => setMessage(''), 3000)
+      // Optionally store in a ref and clear in a useEffect cleanup
// Add once in component body
const timerRef = useRef<number | null>(null)
useEffect(() => () => { if (timerRef.current) clearTimeout(timerRef.current) }, [])
// In saveSpeakerConfiguration success:
timerRef.current = window.setTimeout(() => setMessage(''), 3000)
backends/advanced/init.py (4)

25-34: Allow running from repo root; perform ops relative to script dir.

The current guard forces running only from backends/advanced. Make it location-agnostic and chdir safely.

Apply this diff:

     def __init__(self, args=None):
         self.console = Console()
         self.config: Dict[str, Any] = {}
         self.args = args or argparse.Namespace()
         
-        # Check if we're in the right directory
-        if not Path("pyproject.toml").exists() or not Path("src").exists():
-            self.console.print("[red][ERROR][/red] Please run this script from the backends/advanced directory")
-            sys.exit(1)
+        # Resolve to this file's directory so invocation works from anywhere
+        script_dir = Path(__file__).resolve().parent
+        if not (script_dir / "pyproject.toml").exists() or not (script_dir / "src").exists():
+            self.console.print("[red][ERROR][/red] backends/advanced appears incomplete (missing pyproject.toml or src)")
+            sys.exit(1)
+        os.chdir(script_dir)

271-274: Validate ports (numeric, 1–65535).

Prevents invalid values that break docker/health checks.

Apply this diff:

-        self.config["HOST_IP"] = self.prompt_value("Host IP for services", "localhost")
-        self.config["BACKEND_PUBLIC_PORT"] = self.prompt_value("Backend port", "8000")
-        self.config["WEBUI_PORT"] = self.prompt_value("Web UI port", "5173")
+        self.config["HOST_IP"] = self.prompt_value("Host IP for services", "localhost")
+        def _prompt_port(label: str, default: str) -> str:
+            while True:
+                p = self.prompt_value(label, default)
+                if p.isdigit() and 1 <= int(p) <= 65535:
+                    return p
+                self.console.print("[red]Invalid port. Must be an integer between 1 and 65535.[/red]")
+        self.config["BACKEND_PUBLIC_PORT"] = _prompt_port("Backend port", "8000")
+        self.config["WEBUI_PORT"] = _prompt_port("Web UI port", "5173")

159-159: Standardize Parakeet compose invocation (-d placement).

Keep the command consistent across messages.

Apply this diff:

-            self.console.print("[yellow][WARNING][/yellow] Remember to start Parakeet service: cd ../../extras/asr-services && docker compose up parakeet")
+            self.console.print("[yellow][WARNING][/yellow] Remember to start Parakeet service: cd ../../extras/asr-services && docker compose up -d parakeet")
-            self.console.print("   [cyan]cd ../../extras/asr-services && docker compose up parakeet -d[/cyan]")
+            self.console.print("   [cyan]cd ../../extras/asr-services && docker compose up -d parakeet[/cyan]")

Also applies to: 381-382


423-425: Avoid catching broad Exception in CLI entry.

Catching Exception masks programming errors and trips BLE001. Narrow to expected ones.

Apply this diff:

-        except Exception as e:
+        except (OSError, ValueError) as e:
             self.console.print(f"[red][ERROR][/red] Setup failed: {e}")
             sys.exit(1)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (8)
Docs/init-system.md (8)

65-70: Add secrets guidance for HF_TOKEN.

Document that HF_TOKEN should go in a local .env, never committed; optionally reference .env.example.

Suggested addition:

 - **Requires**: HF_TOKEN for pyannote models
+ - **Requires**: HF_TOKEN for pyannote models
+   - Store in `extras/speaker-recognition/.env` (not committed). Do not paste tokens into shell history.

96-103: Resolve markdownlint MD034: wrap bare URLs.

Convert endpoint URLs to Markdown links.

- **Backend API**: http://localhost:8000
- **Backend WebUI**: http://localhost:5173  
- **Speaker Recognition**: http://localhost:8085
- **Speaker Recognition WebUI**: http://localhost:5173
- **Parakeet ASR**: http://localhost:8767
- **OpenMemory MCP**: http://localhost:8765
+ **Backend API**: <http://localhost:8000>
+ **Backend WebUI**: <http://localhost:5173>  
+ **Speaker Recognition**: <http://localhost:8085>
+ **Speaker Recognition WebUI**: <http://localhost:5173>
+ **Parakeet ASR**: <http://localhost:8767>
+ **OpenMemory MCP**: <http://localhost:8765>

104-109: Document container networking alternative.

Add Compose-based alternative using service names/networks (often simpler than host.docker.internal).

Suggested addition:

 Services use `host.docker.internal` for inter-container communication:
 ...
 - `http://host.docker.internal:8765` - OpenMemory MCP
+Alternatively, when services share a user-defined network, prefer service-to-service URLs:
+ - `http://speaker-service:8085`, `http://parakeet:8767`, `http://openmemory-mcp:8765`

112-130: Compose command consistency and build flag.

Align ASR command with others and ensure build occurs on first run.

-# Start ASR Services
+# Start ASR Services
 cd extras/asr-services
-docker compose up parakeet -d
+docker compose up --build -d parakeet

Also, verify the service name is actually parakeet.


139-143: Pre-reqs: call out uv install and GPU tooling.

Add explicit prerequisites so first-time users don’t stall.

 ### Required Dependencies
 - **Root**: `setup-requirements.txt` (rich>=13.0.0)
 - **Backend**: `setup-requirements.txt` (rich>=13.0.0, pyyaml>=6.0.0)
 - **Extras**: No additional setup dependencies required
+### Prerequisites
+- Docker (and Docker Compose plugin)
+- uv installed and on PATH
+- For GPU mode: NVIDIA drivers and `nvidia-container-toolkit` installed/configured

146-151: Expand “Common Issues” with networking/GPU notes.

These are frequent pitfalls for the flows you document.

 - **Service startup failures**: Check Docker is running and has sufficient resources
+ - **Linux host networking**: If `host.docker.internal` fails, add `extra_hosts: ["host.docker.internal:host-gateway"]`.
+ - **GPU mode failures**: Confirm NVIDIA drivers and `nvidia-container-toolkit` are installed and Compose has `runtime: nvidia` (or equivalent).

153-162: Add OpenMemory MCP health check.

Completes the set of health probes.

 # ASR service health
 curl http://localhost:8767/health
+
+# OpenMemory MCP health
+curl http://localhost:8765/health

78-81: Avoid bare URL; wrap WebUI link.

Also matches linter guidance.

- **WebUI**: Available at http://localhost:8765
+ **WebUI**: Available at <http://localhost:8765>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e595f3e and 0612587.

📒 Files selected for processing (2)
  • Docs/init-system.md (1 hunks)
  • extras/openmemory-mcp/setup.sh (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • extras/openmemory-mcp/setup.sh
🧰 Additional context used
🪛 LanguageTool
Docs/init-system.md

[grammar] ~9-~9: There might be a mistake here.
Context: ... ## Architecture ### Root Orchestrator - Location: /init.py - Purpose: Se...

(QB_NEW_EN)


[grammar] ~10-~10: There might be a mistake here.
Context: ... ### Root Orchestrator - Location: /init.py - Purpose: Service selection and delegat...

(QB_NEW_EN)


[grammar] ~11-~11: There might be a mistake here.
Context: ...*: Service selection and delegation only - Does NOT: Handle service-specific conf...

(QB_NEW_EN)


[grammar] ~14-~14: There might be a mistake here.
Context: ...plicate setup logic ### Service Scripts - Backend: backends/advanced/init.py -...

(QB_NEW_EN)


[grammar] ~15-~15: There might be a mistake here.
Context: ... Complete Python-based interactive setup - Speaker Recognition: `extras/speaker-r...

(QB_NEW_EN)


[grammar] ~16-~16: There might be a mistake here.
Context: ...ecognition/setup.sh- Simple bash setup - **ASR Services**:extras/asr-services/set...

(QB_NEW_EN)


[grammar] ~17-~17: There might be a mistake here.
Context: ...vices/setup.sh- Service startup script - **OpenMemory MCP**:extras/openmemory-mcp...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...ge ### Orchestrated Setup (Recommended) Set up multiple services together with a...

(QB_NEW_EN)


[grammar] ~30-~30: There might be a mistake here.
Context: ...thon init.py ``` The orchestrator will: 1. Show service status and availability 2. ...

(QB_NEW_EN)


[grammar] ~36-~36: There might be a mistake here.
Context: ...g services ### Individual Service Setup Each service can be configured independe...

(QB_NEW_EN)


[grammar] ~59-~59: There might be a mistake here.
Context: ...## Service Details ### Advanced Backend - Interactive setup for authentication, ...

(QB_NEW_EN)


[grammar] ~60-~60: There might be a mistake here.
Context: ...LLM, transcription, and memory providers - Accepts arguments: `--speaker-service-...

(QB_NEW_EN)


[grammar] ~62-~62: There might be a mistake here.
Context: ...nv` file with all required configuration - Default ports: Backend (8000), WebUI (...

(QB_NEW_EN)


[grammar] ~65-~65: There might be a mistake here.
Context: ...), WebUI (5173) ### Speaker Recognition - Prompts for: Hugging Face token, compu...

(QB_NEW_EN)


[grammar] ~66-~66: There might be a mistake here.
Context: ...gging Face token, compute mode (cpu/gpu) - Service port: 8085 - WebUI port: 5...

(QB_NEW_EN)


[grammar] ~67-~67: There might be a mistake here.
Context: ... mode (cpu/gpu) - Service port: 8085 - WebUI port: 5173 - Requires: HF_TO...

(QB_NEW_EN)


[grammar] ~68-~68: There might be a mistake here.
Context: ...vice port**: 8085 - WebUI port: 5173 - Requires: HF_TOKEN for pyannote models...

(QB_NEW_EN)


[grammar] ~71-~71: There might be a mistake here.
Context: ...EN for pyannote models ### ASR Services - Starts: Parakeet ASR service via Docke...

(QB_NEW_EN)


[grammar] ~72-~72: There might be a mistake here.
Context: ... Parakeet ASR service via Docker Compose - Service port: 8767 - Purpose: Offl...

(QB_NEW_EN)


[grammar] ~73-~73: There might be a mistake here.
Context: ...ocker Compose - Service port: 8767 - Purpose: Offline speech-to-text proces...

(QB_NEW_EN)


[grammar] ~74-~74: There might be a mistake here.
Context: ...ose**: Offline speech-to-text processing - No configuration required ### OpenMem...

(QB_NEW_EN)


[grammar] ~77-~77: There might be a mistake here.
Context: ...iguration required** ### OpenMemory MCP - Starts: External OpenMemory MCP server...

(QB_NEW_EN)


[grammar] ~78-~78: There might be a mistake here.
Context: ...Starts**: External OpenMemory MCP server - Service port: 8765 - WebUI: Avai...

(QB_NEW_EN)


[grammar] ~79-~79: There might be a mistake here.
Context: ...mory MCP server - Service port: 8765 - WebUI: Available at http://localhost:8...

(QB_NEW_EN)


[grammar] ~80-~80: There might be a mistake here.
Context: ...UI**: Available at http://localhost:8765 - Purpose: Cross-client memory compatibi...

(QB_NEW_EN)


[grammar] ~87-~87: There might be a mistake here.
Context: ...elected | Backend Gets Configured With | |------------------|--------------------...

(QB_NEW_EN)


[grammar] ~88-~88: There might be a mistake here.
Context: ...--------|------------------------------| | Speaker Recognition | `SPEAKER_SERVICE...

(QB_NEW_EN)


[grammar] ~89-~89: There might be a mistake here.
Context: ..._URL=http://host.docker.internal:8085` | | ASR Services | `PARAKEET_ASR_URL=http:...

(QB_NEW_EN)


[grammar] ~96-~96: There might be a mistake here.
Context: ...vice URLs ### Default Service Endpoints - Backend API: http://localhost:8000 - *...

(QB_NEW_EN)


[grammar] ~97-~97: There might be a mistake here.
Context: ...- Backend API: http://localhost:8000 - Backend WebUI: http://localhost:5173 ...

(QB_NEW_EN)


[grammar] ~98-~98: There might be a mistake here.
Context: ...Backend WebUI: http://localhost:5173 - Speaker Recognition: http://localhost:...

(QB_NEW_EN)


[grammar] ~99-~99: There might be a mistake here.
Context: ...ker Recognition**: http://localhost:8085 - Speaker Recognition WebUI: http://loca...

(QB_NEW_EN)


[grammar] ~100-~100: There might be a mistake here.
Context: ...cognition WebUI**: http://localhost:5173 - Parakeet ASR: http://localhost:8767 - ...

(QB_NEW_EN)


[grammar] ~101-~101: There might be a mistake here.
Context: ... Parakeet ASR: http://localhost:8767 - OpenMemory MCP: http://localhost:8765 ...

(QB_NEW_EN)


[grammar] ~104-~104: There might be a mistake here.
Context: ...### Container-to-Container Communication Services use host.docker.internal for ...

(QB_NEW_EN)


[grammar] ~136-~136: There might be a mistake here.
Context: ...on/.env- Speaker service configuration - All services backup existing.env` file...

(QB_NEW_EN)


[grammar] ~139-~139: There might be a mistake here.
Context: ...automatically ### Required Dependencies - Root: setup-requirements.txt (rich>=...

(QB_NEW_EN)


[grammar] ~140-~140: There might be a mistake here.
Context: ... setup-requirements.txt (rich>=13.0.0) - Backend: setup-requirements.txt (ric...

(QB_NEW_EN)


[grammar] ~141-~141: There might be a mistake here.
Context: ...ments.txt` (rich>=13.0.0, pyyaml>=6.0.0) - Extras: No additional setup dependenci...

(QB_NEW_EN)


[grammar] ~146-~146: There might be a mistake here.
Context: ...d ## Troubleshooting ### Common Issues - Port conflicts: Check if services are ...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
Docs/init-system.md

80-80: Bare URL used

(MD034, no-bare-urls)


97-97: Bare URL used

(MD034, no-bare-urls)


98-98: Bare URL used

(MD034, no-bare-urls)


99-99: Bare URL used

(MD034, no-bare-urls)


100-100: Bare URL used

(MD034, no-bare-urls)


101-101: Bare URL used

(MD034, no-bare-urls)


102-102: Bare URL used

(MD034, no-bare-urls)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: integration-tests
🔇 Additional comments (2)
Docs/init-system.md (2)

25-28: Verify uv run syntax; consider adding -- separator.

Many users expect uv run ... -- python init.py. Please confirm the exact CLI and update to prevent copy/paste failures.

Apply if correct:

-uv run --with-requirements setup-requirements.txt python init.py
+uv run --with-requirements setup-requirements.txt -- python init.py

96-103: Clarify or fix WebUI port conflict: Both Backend WebUI and Speaker Recognition WebUI default to http://localhost:5173 in Docs/init-system.md, causing a runtime collision. Assign a distinct default port (e.g. change Speaker Recognition WebUI to 5174) or explicitly document how to override one of the ports via env/compose configuration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
README.md (1)

243-248: Fix path and WebUI port to match new init system

Update stale path and ports to backends/advanced and 5173.

-2. **Use Advanced Backend**: `cd backends/advanced-backend`
+2. **Use Advanced Backend**: `cd backends/advanced`
@@
-5. **Access WebUI**: `http://localhost:3000` (React) or `http://localhost:8501` (Streamlit)
+5. **Access WebUI**: `http://localhost:5173` (React)
♻️ Duplicate comments (2)
init.py (1)

118-131: Add timeout, check=True, and surface stdout/stderr; handle subprocess errors explicitly

Prevents hangs, improves diagnostics, and addresses Ruff S603 guidance. Also replaces blanket except Exception with specific handlers.

-    try:
-        result = subprocess.run(
-            cmd, 
-            cwd=service['path'],
-            check=False
-        )
-        
-        if result.returncode == 0:
-            console.print(f"✅ {service_name} setup completed")
-            return True
-        else:
-            console.print(f"❌ {service_name} setup failed (exit code: {result.returncode})")
-            return False
-            
-    except FileNotFoundError as e:
-        console.print(f"❌ {service_name} setup failed: {e}")
-        return False
-    except Exception as e:
-        console.print(f"❌ {service_name} setup failed: {e}")
-        return False
+    try:
+        result = subprocess.run(
+            cmd,
+            cwd=service['path'],
+            check=True,
+            capture_output=True,
+            text=True,
+            timeout=1800,
+            shell=False,
+        )
+        console.print(f"✅ {service_name} setup completed")
+        return True
+    except FileNotFoundError as e:
+        console.print(f"❌ {service_name} setup failed: {e}")
+        return False
+    except subprocess.TimeoutExpired as e:
+        console.print(f"❌ {service_name} setup timed out after {e.timeout}s")
+        return False
+    except subprocess.CalledProcessError as e:
+        stdout = (e.stdout or "").strip()
+        stderr = (e.stderr or "").strip()
+        console.print(f"❌ {service_name} setup failed (exit {e.returncode})")
+        if stdout:
+            console.print("[dim]stdout:[/dim]\n" + stdout[-4000:])
+        if stderr:
+            console.print("[dim]stderr:[/dim]\n" + stderr[-4000:])
+        return False
+    except Exception as e:
+        console.print(f"❌ {service_name} setup failed: {e}")
+        return False

Also applies to: 132-137

Docs/init-system.md (1)

97-101: Add Linux caveat for host.docker.internal mapping

Prevents broken intra-container URLs on Linux.

 This eliminates the need to manually configure service URLs when running services on the same machine.
+> Note (Linux): If `host.docker.internal` doesn’t resolve by default, add a host-gateway mapping.
+> - docker run: `--add-host=host.docker.internal:host-gateway`
+> - Docker Compose:
+>   ```yaml
+>   extra_hosts:
+>     - "host.docker.internal:host-gateway"
+>   ```
🧹 Nitpick comments (6)
init.py (2)

181-181: Remove extraneous f-strings

No interpolation; drop the f prefix to appease Ruff F541.

-    console.print(f"\n🎊 [bold green]Setup Complete![/bold green]")
+    console.print("\n🎊 [bold green]Setup Complete![/bold green]")
...
-    console.print(f"\n🚀 [bold]Enjoy Friend-Lite![/bold]")
+    console.print("\n🚀 [bold]Enjoy Friend-Lite![/bold]")
-    console.print(f"\n💡 [dim]Tip: You can also setup services individually:[/dim]")
+    console.print("\n💡 [dim]Tip: You can also setup services individually:[/dim]")
-    console.print(f"[dim]   cd backends/advanced && uv run --with-requirements setup-requirements.txt python init.py[/dim]")
+    console.print("[dim]   cd backends/advanced && uv run --with-requirements setup-requirements.txt python init.py[/dim]")

Also applies to: 218-218, 221-222


28-40: Windows compatibility: guard .sh invocation

Running ./setup.sh will fail on Windows. Consider a platform-aware runner or documenting WSL requirement.

-            'cmd': ['./setup.sh'],
+            'cmd': ['./setup.sh'],  # On Windows, run via WSL or `bash -lc ./setup.sh`

Or detect platform in run_service_setup and wrap with ["bash", "-lc", "./setup.sh"] when os.name == "nt".

Also applies to: 100-106

README.md (2)

5-8: Ensure consistency with orchestrator commands

Use the same uv run --with-requirements ... python init.py and python services.py start --all --build phrasing everywhere for clarity.

-2. Run the orchestrated setup: `uv run --with-requirements setup-requirements.txt python init.py`
-3. Start all services: `python services.py start --all --build`
+2. Run the orchestrated setup: `uv run --with-requirements setup-requirements.txt python init.py`
+3. Start all services: `python services.py start --all --build`

(If intentional to vary wording, ignore.)

Also applies to: 215-219


123-131: Avoid port collision messaging for multiple WebUIs

Speaker Recognition’s WebUI is also listed as 5173 elsewhere; if both UIs exist, clarify ports or note mutual exclusivity to prevent confusion.

Would you like a doc snippet enumerating default ports per service and noting conflicts?

Also applies to: 137-139

Docs/init-system.md (2)

70-75: Verify WebUI port duplication

Both Backend WebUI and Speaker Recognition list 5173. If two UIs run simultaneously, clarify or adjust one port.

Propose updating Speaker Recognition WebUI to a non-conflicting port if applicable (e.g., 5174) and reflect in compose files.

Also applies to: 109-116


125-146: Service management examples: add --build note for selective starts

When starting specific services that aren’t built yet, recommend --build for parity with “start all”.

-# Start specific services
-python services.py start backend speaker-recognition
+# Start specific services (add --build on first run)
+python services.py start backend speaker-recognition --build
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0612587 and 4667ceb.

📒 Files selected for processing (5)
  • Docs/init-system.md (1 hunks)
  • README.md (5 hunks)
  • extras/asr-services/setup.sh (1 hunks)
  • extras/openmemory-mcp/setup.sh (1 hunks)
  • init.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • extras/openmemory-mcp/setup.sh
  • extras/asr-services/setup.sh
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Format Python code with Black using a 100-character line length
Use isort to organize Python imports
Never import modules in the middle of functions or files; all imports must be at the top of the Python file after the docstring
Group Python imports in the order: standard library, third-party, then local imports
Use lazy imports sparingly and only when absolutely necessary to resolve circular imports

Files:

  • init.py
🧬 Code graph analysis (1)
init.py (1)
backends/advanced/init.py (2)
  • run (383-425)
  • main (428-439)
🪛 LanguageTool
Docs/init-system.md

[grammar] ~14-~14: There might be a mistake here.
Context: ... ## Architecture ### Root Orchestrator - Location: /init.py - Purpose: Se...

(QB_NEW_EN)


[grammar] ~15-~15: There might be a mistake here.
Context: ... ### Root Orchestrator - Location: /init.py - Purpose: Service selection and delegat...

(QB_NEW_EN)


[grammar] ~16-~16: There might be a mistake here.
Context: ...*: Service selection and delegation only - Does NOT: Handle service-specific conf...

(QB_NEW_EN)


[grammar] ~19-~19: There might be a mistake here.
Context: ...plicate setup logic ### Service Scripts - Backend: backends/advanced/init.py -...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ... Complete Python-based interactive setup - Speaker Recognition: `extras/speaker-r...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...ecognition/setup.sh- Simple bash setup - **ASR Services**:extras/asr-services/set...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...vices/setup.sh- Service startup script - **OpenMemory MCP**:extras/openmemory-mcp...

(QB_NEW_EN)


[grammar] ~27-~27: There might be a mistake here.
Context: ...ge ### Orchestrated Setup (Recommended) Set up multiple services together with a...

(QB_NEW_EN)


[grammar] ~35-~35: There might be a mistake here.
Context: ...thon init.py ``` The orchestrator will: 1. Show service status and availability 2. ...

(QB_NEW_EN)


[grammar] ~41-~41: There might be a mistake here.
Context: ...g services ### Individual Service Setup Each service can be configured independe...

(QB_NEW_EN)


[grammar] ~64-~64: There might be a mistake here.
Context: ...## Service Details ### Advanced Backend - Interactive setup for authentication, ...

(QB_NEW_EN)


[grammar] ~65-~65: There might be a mistake here.
Context: ...LLM, transcription, and memory providers - Accepts arguments: `--speaker-service-...

(QB_NEW_EN)


[grammar] ~67-~67: There might be a mistake here.
Context: ...nv` file with all required configuration - Default ports: Backend (8000), WebUI (...

(QB_NEW_EN)


[grammar] ~70-~70: There might be a mistake here.
Context: ...), WebUI (5173) ### Speaker Recognition - Prompts for: Hugging Face token, compu...

(QB_NEW_EN)


[grammar] ~71-~71: There might be a mistake here.
Context: ...gging Face token, compute mode (cpu/gpu) - Service port: 8085 - WebUI port: 5...

(QB_NEW_EN)


[grammar] ~72-~72: There might be a mistake here.
Context: ... mode (cpu/gpu) - Service port: 8085 - WebUI port: 5173 - Requires: HF_TO...

(QB_NEW_EN)


[grammar] ~73-~73: There might be a mistake here.
Context: ...vice port**: 8085 - WebUI port: 5173 - Requires: HF_TOKEN for pyannote models...

(QB_NEW_EN)


[grammar] ~76-~76: There might be a mistake here.
Context: ...EN for pyannote models ### ASR Services - Starts: Parakeet ASR service via Docke...

(QB_NEW_EN)


[grammar] ~77-~77: There might be a mistake here.
Context: ... Parakeet ASR service via Docker Compose - Service port: 8767 - Purpose: Offl...

(QB_NEW_EN)


[grammar] ~78-~78: There might be a mistake here.
Context: ...ocker Compose - Service port: 8767 - Purpose: Offline speech-to-text proces...

(QB_NEW_EN)


[grammar] ~79-~79: There might be a mistake here.
Context: ...ose**: Offline speech-to-text processing - No configuration required ### OpenMem...

(QB_NEW_EN)


[grammar] ~82-~82: There might be a mistake here.
Context: ...iguration required** ### OpenMemory MCP - Starts: External OpenMemory MCP server...

(QB_NEW_EN)


[grammar] ~83-~83: There might be a mistake here.
Context: ...Starts**: External OpenMemory MCP server - Service port: 8765 - WebUI: Avai...

(QB_NEW_EN)


[grammar] ~84-~84: There might be a mistake here.
Context: ...mory MCP server - Service port: 8765 - WebUI: Available at http://localhost:8...

(QB_NEW_EN)


[grammar] ~85-~85: There might be a mistake here.
Context: ...UI**: Available at http://localhost:8765 - Purpose: Cross-client memory compatibi...

(QB_NEW_EN)


[grammar] ~92-~92: There might be a mistake here.
Context: ...elected | Backend Gets Configured With | |------------------|--------------------...

(QB_NEW_EN)


[grammar] ~93-~93: There might be a mistake here.
Context: ...--------|------------------------------| | Speaker Recognition | `SPEAKER_SERVICE...

(QB_NEW_EN)


[grammar] ~94-~94: There might be a mistake here.
Context: ..._URL=http://host.docker.internal:8085` | | ASR Services | `PARAKEET_ASR_URL=http:...

(QB_NEW_EN)


[grammar] ~109-~109: There might be a mistake here.
Context: ...vice URLs ### Default Service Endpoints - Backend API: http://localhost:8000 - *...

(QB_NEW_EN)


[grammar] ~110-~110: There might be a mistake here.
Context: ...- Backend API: http://localhost:8000 - Backend WebUI: http://localhost:5173 ...

(QB_NEW_EN)


[grammar] ~111-~111: There might be a mistake here.
Context: ...Backend WebUI: http://localhost:5173 - Speaker Recognition: http://localhost:...

(QB_NEW_EN)


[grammar] ~112-~112: There might be a mistake here.
Context: ...ker Recognition**: http://localhost:8085 - Speaker Recognition WebUI: http://loca...

(QB_NEW_EN)


[grammar] ~113-~113: There might be a mistake here.
Context: ...cognition WebUI**: http://localhost:5173 - Parakeet ASR: http://localhost:8767 - ...

(QB_NEW_EN)


[grammar] ~114-~114: There might be a mistake here.
Context: ... Parakeet ASR: http://localhost:8767 - OpenMemory MCP: http://localhost:8765 ...

(QB_NEW_EN)


[grammar] ~117-~117: There might be a mistake here.
Context: ...### Container-to-Container Communication Services use host.docker.internal for ...

(QB_NEW_EN)


[grammar] ~127-~127: There might be a mistake here.
Context: ...ement**: ### Unified Service Management Use the services.py script for all ser...

(QB_NEW_EN)


[grammar] ~147-~147: There might be a mistake here.
Context: ...y-mcp ``` ### Manual Service Management You can also manage services individuall...

(QB_NEW_EN)


[grammar] ~168-~168: There might be a mistake here.
Context: ...on/.env- Speaker service configuration - All services backup existing.env` file...

(QB_NEW_EN)


[grammar] ~171-~171: There might be a mistake here.
Context: ...automatically ### Required Dependencies - Root: setup-requirements.txt (rich>=...

(QB_NEW_EN)


[grammar] ~172-~172: There might be a mistake here.
Context: ... setup-requirements.txt (rich>=13.0.0) - Backend: setup-requirements.txt (ric...

(QB_NEW_EN)


[grammar] ~173-~173: There might be a mistake here.
Context: ...ments.txt` (rich>=13.0.0, pyyaml>=6.0.0) - Extras: No additional setup dependenci...

(QB_NEW_EN)


[grammar] ~178-~178: There might be a mistake here.
Context: ...d ## Troubleshooting ### Common Issues - Port conflicts: Check if services are ...

(QB_NEW_EN)

README.md

[grammar] ~5-~5: There might be a mistake here.
Context: ...tart Interactive setup (recommended): 1. From the project root, run `uv run --wit...

(QB_NEW_EN)


[grammar] ~10-~10: There might be a mistake here.
Context: ...dashboard Manual setup (alternative): 1. Go to backends/advanced/ and copy `.en...

(QB_NEW_EN)


[grammar] ~11-~11: There might be a mistake here.
Context: ... 1. Go to backends/advanced/ and copy .env.template to .env 2. Configure your API keys and s...

(QB_NEW_EN)


[grammar] ~15-~15: There might be a mistake here.
Context: ...e CLAUDE.md and Docs/init-system.md for detailed setup guide ## Overview Frien...

(QB_NEW_EN)


[grammar] ~48-~48: There might be a mistake here.
Context: ...nce threshold filtering and live results - Memory count display with total count ...

(QB_NEW_EN)


[grammar] ~49-~49: There might be a mistake here.
Context: ...tal count tracking from native providers - Speaker-based memory filtering to cont...

(QB_NEW_EN)


[grammar] ~50-~50: There might be a mistake here.
Context: ...processing based on participant presence - Action item detection and tracking -...

(QB_NEW_EN)


[grammar] ~80-~80: There might be a mistake here.
Context: ...elevance scoring and threshold filtering - Smart deduplication and memory updates (...

(QB_NEW_EN)


[grammar] ~123-~123: There might be a mistake here.
Context: ...d** (backends/advanced/) RECOMMENDED Use case: Production use, full feature...

(QB_NEW_EN)


[grammar] ~217-~217: There might be a mistake here.
Context: ...features 2. Run the orchestrated setup: uv run --with-requirements setup-requirements.txt python init.py 3. Start all services: `python services.py ...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
Docs/init-system.md

85-85: Bare URL used

(MD034, no-bare-urls)


110-110: Bare URL used

(MD034, no-bare-urls)


111-111: Bare URL used

(MD034, no-bare-urls)


112-112: Bare URL used

(MD034, no-bare-urls)


113-113: Bare URL used

(MD034, no-bare-urls)


114-114: Bare URL used

(MD034, no-bare-urls)


115-115: Bare URL used

(MD034, no-bare-urls)

🪛 Ruff (0.12.2)
init.py

119-119: subprocess call: check for execution of untrusted input

(S603)


135-135: Do not catch blind exception: Exception

(BLE001)


181-181: f-string without any placeholders

Remove extraneous f prefix

(F541)


218-218: f-string without any placeholders

Remove extraneous f prefix

(F541)


221-221: f-string without any placeholders

Remove extraneous f prefix

(F541)


222-222: f-string without any placeholders

Remove extraneous f prefix

(F541)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: integration-tests
🔇 Additional comments (1)
init.py (1)

119-124: No action required – cmd is sourced from the internal SERVICES registry and only extended with static literals, and shell=False is retained.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (4)
services.py (1)

82-88: Add timeout and better error handling for subprocess commands.

The subprocess call could hang indefinitely and doesn't capture output for debugging failures.

Add timeout and capture output for better error reporting:

 try:
     result = subprocess.run(
         cmd,
         cwd=service_path,
         capture_output=True,
         text=True,
-        check=False
+        check=False,
+        timeout=300  # 5 minute timeout for docker operations
     )
init.py (2)

118-131: Add timeout and better error handling for subprocess.

Long-running setup scripts can hang indefinitely without feedback to the user.

Add a generous timeout and capture output for debugging:

 try:
     result = subprocess.run(
         cmd, 
         cwd=service['path'],
-        check=False
+        check=False,
+        timeout=1800,  # 30 minute timeout for setup scripts
+        capture_output=True,
+        text=True
     )
     
     if result.returncode == 0:
         console.print(f"✅ {service_name} setup completed")
         return True
     else:
         console.print(f"❌ {service_name} setup failed (exit code: {result.returncode})")
+        if result.stderr:
+            console.print(f"[dim]Error: {result.stderr}[/dim]")
         return False

132-137: Handle specific subprocess exceptions instead of generic catch-all.

Replace generic exception handling with specific subprocess exceptions:

 except FileNotFoundError as e:
     console.print(f"❌ {service_name} setup failed: {e}")
     return False
+except subprocess.TimeoutExpired as e:
+    console.print(f"❌ {service_name} setup timed out after {e.timeout}s")
+    return False
+except subprocess.SubprocessError as e:
+    console.print(f"❌ {service_name} setup failed: {e}")
+    return False
 except Exception as e:
     console.print(f"❌ {service_name} setup failed: {e}")
     return False
Docs/init-system.md (1)

88-99: Add Linux caveat for host.docker.internal mapping.

Repeat of prior feedback; prevents broken intra-container URLs on Linux.

 This eliminates the need to manually configure service URLs when running services on the same machine.
+
+Note (Linux): If `host.docker.internal` is unavailable, add the host-gateway mapping so containers can reach services on the host:
+
+- docker run: `--add-host=host.docker.internal:host-gateway`
+- Docker Compose:
+  ```yaml
+  extra_hosts:
+    - "host.docker.internal:host-gateway"
+  ```
🧹 Nitpick comments (12)
services.py (1)

7-14: Missing required imports for Python module execution.

The module imports don't include the os module that would be needed for environment variable checking, and the shebang line suggests direct execution but the file may not have executable permissions.

Add the os import if you plan to check environment variables:

+import os
 import subprocess
 import sys
 import argparse
init.py (3)

181-181: Remove unnecessary f-string prefix.

The string doesn't contain any placeholders.

Remove the f prefix:

-    console.print(f"\n🎊 [bold green]Setup Complete![/bold green]")
+    console.print("\n🎊 [bold green]Setup Complete![/bold green]")

218-218: Remove unnecessary f-string prefix.

Remove the f prefix:

-    console.print(f"\n🚀 [bold]Enjoy Friend-Lite![/bold]")
+    console.print("\n🚀 [bold]Enjoy Friend-Lite![/bold]")

221-222: Remove unnecessary f-string prefixes in tip section.

Remove the f prefixes from both lines:

-    console.print(f"\n💡 [dim]Tip: You can also setup services individually:[/dim]")
-    console.print(f"[dim]   cd backends/advanced && uv run --with-requirements setup-requirements.txt python init.py[/dim]")
+    console.print("\n💡 [dim]Tip: You can also setup services individually:[/dim]")
+    console.print("[dim]   cd backends/advanced && uv run --with-requirements setup-requirements.txt python init.py[/dim]")
README.md (2)

217-219: Add error handling guidance for setup failures.

Consider adding troubleshooting steps in case the setup fails.

Would you like me to create a troubleshooting section that covers common setup issues like missing dependencies, Docker permissions, or port conflicts?


219-219: Convert bare URL to proper Markdown link.

Format the URL as a proper Markdown link:

-4. Access the Web UI at http://localhost:5173 for conversation management
+4. Access the Web UI at [http://localhost:5173](http://localhost:5173) for conversation management
Docs/init-system.md (6)

41-43: Add Windows note for shell scripts.

Many users will hit .sh on Windows; point them to Git Bash/WSL.

Add after the heading:

 ### Individual Service Setup
 Each service can be configured independently:
+
+Note (Windows): Run `.sh` scripts via Git Bash or WSL.

83-86: Wrap bare URL to satisfy markdownlint MD034.

-- **WebUI**: Available at http://localhost:8765
+- **WebUI**: Available at <http://localhost:8765>

109-116: Wrap bare URLs in Default Service Endpoints.

-- **Backend API**: http://localhost:8000
-- **Backend WebUI**: http://localhost:5173  
-- **Speaker Recognition**: http://localhost:8085
-- **Speaker Recognition WebUI**: http://localhost:5173
-- **Parakeet ASR**: http://localhost:8767
-- **OpenMemory MCP**: http://localhost:8765
+- **Backend API**: <http://localhost:8000>
+- **Backend WebUI**: <http://localhost:5173>  
+- **Speaker Recognition**: <http://localhost:8085>
+- **Speaker Recognition WebUI**: <http://localhost:5173>
+- **Parakeet ASR**: <http://localhost:8767>
+- **OpenMemory MCP**: <http://localhost:8765>

117-122: Wrap bare URLs and add Linux caveat reference here too (optional).

-Services use `host.docker.internal` for inter-container communication:
-- `http://host.docker.internal:8085` - Speaker Recognition
-- `http://host.docker.internal:8767` - Parakeet ASR  
-- `http://host.docker.internal:8765` - OpenMemory MCP
+Services use `host.docker.internal` for inter-container communication:
+- <http://host.docker.internal:8085> - Speaker Recognition
+- <http://host.docker.internal:8767> - Parakeet ASR  
+- <http://host.docker.internal:8765> - OpenMemory MCP

171-175: List uv explicitly as a prerequisite with a link.

You invoke uv throughout; make it an explicit dependency.

 ### Required Dependencies
 - **Root**: `setup-requirements.txt` (rich>=13.0.0)
 - **Backend**: `setup-requirements.txt` (rich>=13.0.0, pyyaml>=6.0.0)
-- **Extras**: No additional setup dependencies required
+- **Extras**: No additional setup dependencies required
+- **Tooling**: [uv](https://docs.astral.sh/uv/) installed and on PATH

184-194: Add OpenMemory MCP health check.

 # ASR service health
 curl http://localhost:8767/health
+
+# OpenMemory MCP health
+curl http://localhost:8765/health
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4667ceb and 7649948.

📒 Files selected for processing (4)
  • Docs/init-system.md (1 hunks)
  • README.md (7 hunks)
  • init.py (1 hunks)
  • services.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Format Python code with Black using a 100-character line length
Use isort to organize Python imports
Never import modules in the middle of functions or files; all imports must be at the top of the Python file after the docstring
Group Python imports in the order: standard library, third-party, then local imports
Use lazy imports sparingly and only when absolutely necessary to resolve circular imports

Files:

  • services.py
  • init.py
🧬 Code graph analysis (2)
services.py (1)
backends/advanced/init.py (1)
  • run (383-425)
init.py (1)
backends/advanced/init.py (2)
  • run (383-425)
  • main (428-439)
🪛 Ruff (0.12.2)
services.py

82-82: subprocess call: check for execution of untrusted input

(S603)


96-96: Do not catch blind exception: Exception

(BLE001)

init.py

119-119: subprocess call: check for execution of untrusted input

(S603)


135-135: Do not catch blind exception: Exception

(BLE001)


181-181: f-string without any placeholders

Remove extraneous f prefix

(F541)


218-218: f-string without any placeholders

Remove extraneous f prefix

(F541)


221-221: f-string without any placeholders

Remove extraneous f prefix

(F541)


222-222: f-string without any placeholders

Remove extraneous f prefix

(F541)

🪛 LanguageTool
Docs/init-system.md

[grammar] ~14-~14: There might be a mistake here.
Context: ... ## Architecture ### Root Orchestrator - Location: /init.py - Purpose: Se...

(QB_NEW_EN)


[grammar] ~15-~15: There might be a mistake here.
Context: ... ### Root Orchestrator - Location: /init.py - Purpose: Service selection and delegat...

(QB_NEW_EN)


[grammar] ~16-~16: There might be a mistake here.
Context: ...*: Service selection and delegation only - Does NOT: Handle service-specific conf...

(QB_NEW_EN)


[grammar] ~19-~19: There might be a mistake here.
Context: ...plicate setup logic ### Service Scripts - Backend: backends/advanced/init.py -...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ... Complete Python-based interactive setup - Speaker Recognition: `extras/speaker-r...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...ecognition/setup.sh- Simple bash setup - **ASR Services**:extras/asr-services/set...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...vices/setup.sh- Service startup script - **OpenMemory MCP**:extras/openmemory-mcp...

(QB_NEW_EN)


[grammar] ~27-~27: There might be a mistake here.
Context: ...ge ### Orchestrated Setup (Recommended) Set up multiple services together with a...

(QB_NEW_EN)


[grammar] ~35-~35: There might be a mistake here.
Context: ...thon init.py ``` The orchestrator will: 1. Show service status and availability 2. ...

(QB_NEW_EN)


[grammar] ~41-~41: There might be a mistake here.
Context: ...g services ### Individual Service Setup Each service can be configured independe...

(QB_NEW_EN)


[grammar] ~64-~64: There might be a mistake here.
Context: ...## Service Details ### Advanced Backend - Interactive setup for authentication, ...

(QB_NEW_EN)


[grammar] ~65-~65: There might be a mistake here.
Context: ...LLM, transcription, and memory providers - Accepts arguments: `--speaker-service-...

(QB_NEW_EN)


[grammar] ~67-~67: There might be a mistake here.
Context: ...nv` file with all required configuration - Default ports: Backend (8000), WebUI (...

(QB_NEW_EN)


[grammar] ~70-~70: There might be a mistake here.
Context: ...), WebUI (5173) ### Speaker Recognition - Prompts for: Hugging Face token, compu...

(QB_NEW_EN)


[grammar] ~71-~71: There might be a mistake here.
Context: ...gging Face token, compute mode (cpu/gpu) - Service port: 8085 - WebUI port: 5...

(QB_NEW_EN)


[grammar] ~72-~72: There might be a mistake here.
Context: ... mode (cpu/gpu) - Service port: 8085 - WebUI port: 5173 - Requires: HF_TO...

(QB_NEW_EN)


[grammar] ~73-~73: There might be a mistake here.
Context: ...vice port**: 8085 - WebUI port: 5173 - Requires: HF_TOKEN for pyannote models...

(QB_NEW_EN)


[grammar] ~76-~76: There might be a mistake here.
Context: ...EN for pyannote models ### ASR Services - Starts: Parakeet ASR service via Docke...

(QB_NEW_EN)


[grammar] ~77-~77: There might be a mistake here.
Context: ... Parakeet ASR service via Docker Compose - Service port: 8767 - Purpose: Offl...

(QB_NEW_EN)


[grammar] ~78-~78: There might be a mistake here.
Context: ...ocker Compose - Service port: 8767 - Purpose: Offline speech-to-text proces...

(QB_NEW_EN)


[grammar] ~79-~79: There might be a mistake here.
Context: ...ose**: Offline speech-to-text processing - No configuration required ### OpenMem...

(QB_NEW_EN)


[grammar] ~82-~82: There might be a mistake here.
Context: ...iguration required** ### OpenMemory MCP - Starts: External OpenMemory MCP server...

(QB_NEW_EN)


[grammar] ~83-~83: There might be a mistake here.
Context: ...Starts**: External OpenMemory MCP server - Service port: 8765 - WebUI: Avai...

(QB_NEW_EN)


[grammar] ~84-~84: There might be a mistake here.
Context: ...mory MCP server - Service port: 8765 - WebUI: Available at http://localhost:8...

(QB_NEW_EN)


[grammar] ~85-~85: There might be a mistake here.
Context: ...UI**: Available at http://localhost:8765 - Purpose: Cross-client memory compatibi...

(QB_NEW_EN)


[grammar] ~92-~92: There might be a mistake here.
Context: ...elected | Backend Gets Configured With | |------------------|--------------------...

(QB_NEW_EN)


[grammar] ~93-~93: There might be a mistake here.
Context: ...--------|------------------------------| | Speaker Recognition | `SPEAKER_SERVICE...

(QB_NEW_EN)


[grammar] ~94-~94: There might be a mistake here.
Context: ..._URL=http://host.docker.internal:8085` | | ASR Services | `PARAKEET_ASR_URL=http:...

(QB_NEW_EN)


[grammar] ~109-~109: There might be a mistake here.
Context: ...vice URLs ### Default Service Endpoints - Backend API: http://localhost:8000 - *...

(QB_NEW_EN)


[grammar] ~110-~110: There might be a mistake here.
Context: ...- Backend API: http://localhost:8000 - Backend WebUI: http://localhost:5173 ...

(QB_NEW_EN)


[grammar] ~111-~111: There might be a mistake here.
Context: ...Backend WebUI: http://localhost:5173 - Speaker Recognition: http://localhost:...

(QB_NEW_EN)


[grammar] ~112-~112: There might be a mistake here.
Context: ...ker Recognition**: http://localhost:8085 - Speaker Recognition WebUI: http://loca...

(QB_NEW_EN)


[grammar] ~113-~113: There might be a mistake here.
Context: ...cognition WebUI**: http://localhost:5173 - Parakeet ASR: http://localhost:8767 - ...

(QB_NEW_EN)


[grammar] ~114-~114: There might be a mistake here.
Context: ... Parakeet ASR: http://localhost:8767 - OpenMemory MCP: http://localhost:8765 ...

(QB_NEW_EN)


[grammar] ~117-~117: There might be a mistake here.
Context: ...### Container-to-Container Communication Services use host.docker.internal for ...

(QB_NEW_EN)


[grammar] ~127-~127: There might be a mistake here.
Context: ...ement**: ### Unified Service Management Use the services.py script for all ser...

(QB_NEW_EN)


[grammar] ~147-~147: There might be a mistake here.
Context: ...y-mcp ``` ### Manual Service Management You can also manage services individuall...

(QB_NEW_EN)


[grammar] ~168-~168: There might be a mistake here.
Context: ...on/.env- Speaker service configuration - All services backup existing.env` file...

(QB_NEW_EN)


[grammar] ~171-~171: There might be a mistake here.
Context: ...automatically ### Required Dependencies - Root: setup-requirements.txt (rich>=...

(QB_NEW_EN)


[grammar] ~172-~172: There might be a mistake here.
Context: ... setup-requirements.txt (rich>=13.0.0) - Backend: setup-requirements.txt (ric...

(QB_NEW_EN)


[grammar] ~173-~173: There might be a mistake here.
Context: ...ments.txt` (rich>=13.0.0, pyyaml>=6.0.0) - Extras: No additional setup dependenci...

(QB_NEW_EN)


[grammar] ~178-~178: There might be a mistake here.
Context: ...d ## Troubleshooting ### Common Issues - Port conflicts: Check if services are ...

(QB_NEW_EN)

README.md

[grammar] ~5-~5: There might be a mistake here.
Context: ...tart Interactive setup (recommended): 1. From the project root, run `uv run --wit...

(QB_NEW_EN)


[grammar] ~10-~10: There might be a mistake here.
Context: ...dashboard Manual setup (alternative): 1. Go to backends/advanced/ and copy `.en...

(QB_NEW_EN)


[grammar] ~11-~11: There might be a mistake here.
Context: ... 1. Go to backends/advanced/ and copy .env.template to .env 2. Configure your API keys and s...

(QB_NEW_EN)


[grammar] ~15-~15: There might be a mistake here.
Context: ...e CLAUDE.md and Docs/init-system.md for detailed setup guide ## Overview Frien...

(QB_NEW_EN)


[grammar] ~48-~48: There might be a mistake here.
Context: ...nce threshold filtering and live results - Memory count display with total count ...

(QB_NEW_EN)


[grammar] ~49-~49: There might be a mistake here.
Context: ...tal count tracking from native providers - Speaker-based memory filtering to cont...

(QB_NEW_EN)


[grammar] ~50-~50: There might be a mistake here.
Context: ...processing based on participant presence - Action item detection and tracking -...

(QB_NEW_EN)


[grammar] ~80-~80: There might be a mistake here.
Context: ...elevance scoring and threshold filtering - Smart deduplication and memory updates (...

(QB_NEW_EN)


[grammar] ~123-~123: There might be a mistake here.
Context: ...d** (backends/advanced/) RECOMMENDED Use case: Production use, full feature...

(QB_NEW_EN)


[grammar] ~217-~217: There might be a mistake here.
Context: ...features 2. Run the orchestrated setup: uv run --with-requirements setup-requirements.txt python init.py 3. Start all services: `python services.py ...

(QB_NEW_EN)


[grammar] ~244-~244: There might be a mistake here.
Context: ...ository** 2. Run interactive setup: uv run --with-requirements setup-requirements.txt python init.py 3. Start all services: `python services.p...

(QB_NEW_EN)


[grammar] ~245-~245: There might be a mistake here.
Context: ...hon init.py3. **Start all services**:python services.py start --all --build4. **Access WebUI**:http://localhost:5173` ...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
Docs/init-system.md

85-85: Bare URL used

(MD034, no-bare-urls)


110-110: Bare URL used

(MD034, no-bare-urls)


111-111: Bare URL used

(MD034, no-bare-urls)


112-112: Bare URL used

(MD034, no-bare-urls)


113-113: Bare URL used

(MD034, no-bare-urls)


114-114: Bare URL used

(MD034, no-bare-urls)


115-115: Bare URL used

(MD034, no-bare-urls)

README.md

219-219: Bare URL used

(MD034, no-bare-urls)

🔇 Additional comments (4)
README.md (1)

5-8: LGTM! Interactive setup instructions now correctly use the new Python orchestrators.

The instructions properly reference the new init.py and services.py scripts with the correct uv command format.

Docs/init-system.md (3)

1-9: Solid, clear structure for the new init system.

Overview and separation of concerns read well and match the PR goals.


64-69: Possible WebUI port conflict (5173).

Backend and Speaker Recognition list WebUI on 5173; simultaneous runs will clash. Clarify override.

Add a short note under Speaker Recognition:

 - **Requires**: HF_TOKEN for pyannote models
+
+Note: The Speaker Recognition WebUI also defaults to Vite's 5173. If running alongside the Backend WebUI, change one mapping in Docker Compose, for example:
+
+```yaml
+ports:
+  - "5174:5173"
+```

If a supported env var exists for this, reference it instead.

Also applies to: 70-74, 109-115


166-170: Confirm .env backup behavior is implemented in scripts.

Docs promise automatic backups; verify each script actually does this or narrow the claim.

Comment on lines +50 to +53
if service_name == 'backend':
return (service_path / '.env').exists()
else:
return (service_path / '.env').exists()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Redundant condition in service configuration check.

The function returns the same check for both backend and other services.

Simplify the logic since both branches do the same thing:

 def check_service_configured(service_name):
     """Check if service is configured (has .env file)"""
     service = SERVICES[service_name]
     service_path = Path(service['path'])
-    
-    # Backend uses advanced init, others use .env
-    if service_name == 'backend':
-        return (service_path / '.env').exists()
-    else:
-        return (service_path / '.env').exists()
+    return (service_path / '.env').exists()
📝 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.

Suggested change
if service_name == 'backend':
return (service_path / '.env').exists()
else:
return (service_path / '.env').exists()
def check_service_configured(service_name):
"""Check if service is configured (has .env file)"""
service = SERVICES[service_name]
service_path = Path(service['path'])
return (service_path / '.env').exists()
🤖 Prompt for AI Agents
In services.py around lines 50 to 53, the if/else is redundant because both
branches return the same (service_path / '.env').exists(); replace the
conditional with a single return statement: return (service_path /
'.env').exists(), removing the if/else and any unused service_name-specific
logic.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (14)
backends/advanced/init.py (4)

1-1: Remove shebang or make the file executable (EXE001).

If not intended to run as ./init.py, drop the shebang; otherwise chmod +x backends/advanced/init.py.

-#!/usr/bin/env python3

257-261: Fix default Speaker Recognition port to 8085 for consistency.

Elsewhere the service uses 8085; 8001 here breaks OOTB config.

-                speaker_url = self.prompt_value("Speaker Recognition service URL", "http://host.docker.internal:8001")
+                speaker_url = self.prompt_value("Speaker Recognition service URL", "http://host.docker.internal:8085")

345-352: Avoid leaking OpenAI defaults when provider != openai.

Write OPENAI_* only for LLM_PROVIDER=openai, and OLLAMA_* only for ollama.

     def generate_env_file(self):
         """Generate the .env file from configuration"""
-        env_content = f"""# =============================================================================
+        provider = self.config.get('LLM_PROVIDER', '')
+        openai_api_key = self.config.get('OPENAI_API_KEY', '') if provider == 'openai' else ''
+        openai_model = self.config.get('OPENAI_MODEL', '') if provider == 'openai' else ''
+        openai_base_url = self.config.get('OPENAI_BASE_URL', '') if provider == 'openai' else ''
+        ollama_base_url = self.config.get('OLLAMA_BASE_URL', '') if provider == 'ollama' else ''
+        ollama_model = self.config.get('OLLAMA_MODEL', '') if provider == 'ollama' else ''
+
+        env_content = f"""# =============================================================================
 ...
 # LLM Provider  
 LLM_PROVIDER={self.config.get('LLM_PROVIDER', '')}
-OPENAI_API_KEY={self.config.get('OPENAI_API_KEY', '')}
-OPENAI_MODEL={self.config.get('OPENAI_MODEL', 'gpt-4o-mini')}
-OPENAI_BASE_URL={self.config.get('OPENAI_BASE_URL', 'https://api.openai.com/v1')}
-OLLAMA_BASE_URL={self.config.get('OLLAMA_BASE_URL', '')}
-OLLAMA_MODEL={self.config.get('OLLAMA_MODEL', '')}
+OPENAI_API_KEY={openai_api_key}
+OPENAI_MODEL={openai_model}
+OPENAI_BASE_URL={openai_base_url}
+OLLAMA_BASE_URL={ollama_base_url}
+OLLAMA_MODEL={ollama_model}

380-384: Write .env with restrictive perms (contains secrets).

Set mode 600 to reduce accidental exposure.

-        with open(".env", "w") as f:
-            f.write(env_content)
-        
-        self.console.print("[green][SUCCESS][/green] .env file created successfully")
+        with open(".env", "w") as f:
+            f.write(env_content)
+        try:
+            os.chmod(".env", 0o600)
+        except OSError:
+            pass
+        self.console.print("[green][SUCCESS][/green] .env file created successfully (mode 600)")
extras/speaker-recognition/setup.sh (8)

2-2: Harden shell options and add error trap.

Use -euo pipefail and a basic ERR trap.

-set -e
+set -euo pipefail
+trap 'echo "❌ Error on line $LINENO"; exit 1' ERR

10-11: Quote backup target to avoid word splitting (SC2046).

-    cp .env .env.backup.$(date +%Y%m%d_%H%M%S)
+    cp .env ".env.backup.$(date +%Y%m%d_%H%M%S)"

13-15: Guard template copy and restrict .env permissions.

-# Start from template
-cp .env.template .env
+# Start from template
+if [ ! -f ".env.template" ]; then
+  echo "❌ .env.template not found"; exit 1
+fi
+cp .env.template .env
+chmod 600 .env || true

20-21: Do not echo secrets; validate HF token.

-read -p "HF Token: " HF_TOKEN
+read -r -s -p "HF Token: " HF_TOKEN
+echo
+if [ -z "${HF_TOKEN:-}" ]; then
+  echo "❌ HF Token is required"; exit 1
+fi

26-28: Normalize/validate compute mode input.

-read -p "Compute Mode [cpu/gpu]: " COMPUTE_MODE
-COMPUTE_MODE=${COMPUTE_MODE:-cpu}
+read -r -p "Compute Mode [cpu/gpu]: " COMPUTE_MODE
+COMPUTE_MODE=${COMPUTE_MODE:-cpu}
+COMPUTE_MODE=$(printf "%s" "$COMPUTE_MODE" | tr '[:upper:]' '[:lower:]')
+if [ "$COMPUTE_MODE" != "cpu" ] && [ "$COMPUTE_MODE" != "gpu" ]; then
+  echo "⚠️  Unknown compute mode '$COMPUTE_MODE', defaulting to cpu"
+  COMPUTE_MODE=cpu
+fi

36-39: Make sed in-place portable and escape special chars; anchor keys.

Handles macOS vs GNU sed, escapes &, and anchors to start-of-line.

-sed -i "s|HF_TOKEN=.*|HF_TOKEN=$HF_TOKEN|" .env
-sed -i "s|COMPUTE_MODE=.*|COMPUTE_MODE=$COMPUTE_MODE|" .env
+_sedi=(-i)
+if sed --version >/dev/null 2>&1; then :; else _sedi=(-i ""); fi
+HF_ESC=${HF_TOKEN//&/\\&}
+sed "${_sedi[@]}" -E "s|^HF_TOKEN=.*|HF_TOKEN=${HF_ESC}|" .env
+sed "${_sedi[@]}" -E "s|^COMPUTE_MODE=.*|COMPUTE_MODE=${COMPUTE_MODE}|" .env

50-52: Same sed portability/anchoring for HTTPS env keys.

-    sed -i "s|REACT_UI_HTTPS=.*|REACT_UI_HTTPS=true|" .env
-    sed -i "s|REACT_UI_PORT=.*|REACT_UI_PORT=5175|" .env
+    sed "${_sedi[@]}" -E "s|^REACT_UI_HTTPS=.*|REACT_UI_HTTPS=true|" .env
+    sed "${_sedi[@]}" -E "s|^REACT_UI_PORT=.*|REACT_UI_PORT=5175|" .env

84-86: Same sed portability/anchoring for HTTP env keys.

-    sed -i "s|REACT_UI_HTTPS=.*|REACT_UI_HTTPS=false|" .env
-    sed -i "s|REACT_UI_PORT=.*|REACT_UI_PORT=5174|" .env
+    sed "${_sedi[@]}" -E "s|^REACT_UI_HTTPS=.*|REACT_UI_HTTPS=false|" .env
+    sed "${_sedi[@]}" -E "s|^REACT_UI_PORT=.*|REACT_UI_PORT=5174|" .env
services.py (2)

44-54: Remove redundant condition in config check.

Function returns the same in both branches.

-    # Backend uses advanced init, others use .env
-    if service_name == 'backend':
-        return (service_path / '.env').exists()
-    else:
-        return (service_path / '.env').exists()
+    return (service_path / '.env').exists()

81-98: Avoid blind exception catch; add optional timeout handling.

Catching Exception hides interrupts; also prevent hangs.

-    try:
-        result = subprocess.run(
+    try:
+        result = subprocess.run(
             cmd,
             cwd=service_path,
             capture_output=True,
             text=True,
-            check=False
+            check=False,
+            timeout=600
         )
-        
+
         if result.returncode == 0:
             return True
         else:
             console.print(f"[red]❌ Command failed: {result.stderr}[/red]")
             return False
-            
-    except Exception as e:
-        console.print(f"[red]❌ Error running command: {e}[/red]")
-        return False
+    except subprocess.TimeoutExpired as e:
+        console.print(f"[red]❌ Command timed out after {e.timeout}s[/red]")
+        return False
+    except (subprocess.SubprocessError, OSError) as e:
+        console.print(f"[red]❌ Error running command: {e}[/red]")
+        return False
+    except KeyboardInterrupt:
+        raise
🧹 Nitpick comments (12)
backends/advanced/init.py (4)

286-296: Validate server IP/domain before invoking SSL script.

User-provided input flows to subprocess; add a simple allowlist check.

-            self.console.print("[blue][INFO][/blue] HTTPS enables microphone access in browsers")
-            server_ip = self.prompt_value("Server IP/Domain for SSL certificate", "localhost")
+            self.console.print("[blue][INFO][/blue] HTTPS enables microphone access in browsers")
+            server_ip = self.prompt_value("Server IP/Domain for SSL certificate", "localhost")
+            # Basic allowlist for hostnames/IPs (letters, digits, dot, dash, colon)
+            import re  # at top-level in final change per guidelines
+            if not re.fullmatch(r"[A-Za-z0-9.\-:]+", server_ip):
+                self.console.print("[red]Invalid host[/red]: only letters, digits, dot, dash, and colon are allowed")
+                return

Remember to move the import to the top of the file to comply with import rules.


316-323: Catch a narrower exception when writing nginx.conf.

Avoid blind except; handle filesystem errors explicitly.

-                except Exception as e:
+                except OSError as e:
                     self.console.print(f"[yellow][WARNING][/yellow] nginx.conf generation failed: {e}")

472-478: Top-level broad except: either narrow or annotate intent.

Broad catch in a CLI entrypoint can be intentional; annotate to silence BLE001.

-        except Exception as e:
+        except Exception as e:  # noqa: BLE001 - top-level guard is intentional
             self.console.print(f"[red][ERROR][/red] Setup failed: {e}")
             sys.exit(1)

158-161: Consistent detach for Parakeet ASR instructions.

Earlier message blocks the terminal; use -d like in Next Steps.

-            self.console.print("[yellow][WARNING][/yellow] Remember to start Parakeet service: cd ../../extras/asr-services && docker compose up parakeet")
+            self.console.print("[yellow][WARNING][/yellow] Remember to start Parakeet service: cd ../../extras/asr-services && docker compose up parakeet -d")
Docs/ports-and-access.md (5)

21-30: Replace bare URLs with Markdown links (fix MD034).

Converts the HTTP-mode table cells to proper links.

-| **Advanced Backend** | 8000 | 5173 | http://localhost:8000 (API)<br>http://localhost:5173 (Dashboard) |
-| **Speaker Recognition** | 8085 | 5174 | http://localhost:8085 (API)<br>http://localhost:5174 (WebUI) |
-| **Parakeet ASR** | 8767 | - | http://localhost:8767 (API) |
-| **OpenMemory MCP** | 8765 | 8765 | http://localhost:8765 (API + WebUI) |
+| **Advanced Backend** | 8000 | 5173 | [http://localhost:8000](http://localhost:8000) (API)<br>[http://localhost:5173](http://localhost:5173) (Dashboard) |
+| **Speaker Recognition** | 8085 | 5174 | [http://localhost:8085](http://localhost:8085) (API)<br>[http://localhost:5174](http://localhost:5174) (Web UI) |
+| **Parakeet ASR** | 8767 | - | [http://localhost:8767](http://localhost:8767) (API) |
+| **OpenMemory MCP** | 8765 | 8765 | [http://localhost:8765](http://localhost:8765) (API + Web UI) |

36-43: HTTPS table: use Markdown links and consistent “Web UI” spelling.

-| **Advanced Backend** | 80→443 | 443 | https://localhost/ (Main)<br>https://localhost/api/ (API) | ✅ Yes |
-| **Speaker Recognition** | 8081→8444 | 8444 | https://localhost:8444/ (Main)<br>https://localhost:8444/api/ (API) | ✅ Yes |
+| **Advanced Backend** | 80→443 | 443 | [https://localhost/](https://localhost/) (Main)<br>[https://localhost/api/](https://localhost/api/) (API) | ✅ Yes |
+| **Speaker Recognition** | 8081→8444 | 8444 | [https://localhost:8444/](https://localhost:8444/) (Main)<br>[https://localhost:8444/api/](https://localhost:8444/api/) (API) | ✅ Yes |

134-138: Summary: convert bare URLs to links.

-🌐 **Main Application**: http://localhost:5173  
-🎤 **Speaker Recognition**: http://localhost:5174 (HTTP) or https://localhost:8444 (HTTPS)  
-🔧 **Backend API**: http://localhost:8000  
-📝 **ASR Service**: http://localhost:8767  
-🧠 **Memory Service**: http://localhost:8765
+🌐 **Main Application**: [http://localhost:5173](http://localhost:5173)  
+🎤 **Speaker Recognition**: [http://localhost:5174](http://localhost:5174) (HTTP) or [https://localhost:8444](https://localhost:8444) (HTTPS)  
+🔧 **Backend API**: [http://localhost:8000](http://localhost:8000)  
+📝 **ASR Service**: [http://localhost:8767](http://localhost:8767)  
+🧠 **Memory Service**: [http://localhost:8765](http://localhost:8765)

109-114: Clarify mic access nuance on localhost vs. remote.

Add a one-liner so users know HTTP mic access is only reliably allowed on localhost in Chromium; remote hosts need HTTPS.

-1. **Local development**: Use HTTP mode, access via `http://localhost:5174`
-   - Some browsers allow localhost microphone access over HTTP
+1. **Local development**: Use HTTP mode, access via `http://localhost:5174`
+   - Chromium-based browsers allow microphone over HTTP on `localhost` only; other browsers may still require HTTPS

121-127: Add quick commands to identify port conflicts.

Small troubleshooting aid.

 1. **Check running services**: `uv run --with-requirements setup-requirements.txt python services.py status`
 2. **Stop conflicting services**: `uv run --with-requirements setup-requirements.txt python services.py stop --all`
 3. **Change ports in .env files** if needed
 4. **Restart services**: `uv run --with-requirements setup-requirements.txt python services.py start --all`
+5. **Find which process holds a port**:
+   - macOS: `lsof -nP -iTCP:<PORT> -sTCP:LISTEN`
+   - Linux: `ss -ltnp 'sport = :<PORT>'` or `sudo netstat -ltnp | grep :<PORT>`
services.py (3)

7-14: Order imports per guidelines (stdlib → third-party), run isort/Black.

Keeps consistency with the repo’s Python import policy.

-import subprocess
-import sys
-import argparse
-from pathlib import Path
-from rich.console import Console
-from rich.table import Table
-from rich import print as rprint
+import argparse
+import subprocess
+import sys
+from pathlib import Path
+
+from rich.console import Console
+from rich.table import Table
+from rich import print as rprint

24-29: Clarify dual ports display.

Minor UX: make HTTP vs HTTPS explicit for speaker-recognition.

-        'ports': ['8085', '5174/8444']
+        'ports': ['8085', '5174 (HTTP UI), 8444 (HTTPS UI)']

142-165: Status view shows only “configured”, not actual runtime state.

Add a State column by querying docker compose ps per service.

-    table = Table()
+    table = Table()
     table.add_column("Service", style="cyan")
     table.add_column("Configured", justify="center")
-    table.add_column("Description", style="dim")
+    table.add_column("State", justify="center")
+    table.add_column("Description", style="dim")
     table.add_column("Ports", style="green")
     
     for service_name, service_info in SERVICES.items():
         configured = "✅" if check_service_configured(service_name) else "❌"
         ports = ", ".join(service_info['ports'])
-        table.add_row(
-            service_name,
-            configured, 
-            service_info['description'],
-            ports
-        )
+        state = "—"
+        if configured == "✅":
+            try:
+                ps = subprocess.run(
+                    ["docker", "compose", "ps", "-q"],
+                    cwd=Path(service_info["path"]),
+                    capture_output=True,
+                    text=True,
+                    check=False,
+                )
+                state = "🟢 Up" if ps.stdout.strip() else "🔴 Down"
+            except Exception:
+                state = "❓"
+        table.add_row(service_name, configured, state, service_info['description'], ports)

If you prefer, I can wire a “status” subcommand that prints each compose’s ps output verbatim.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7649948 and 489932c.

📒 Files selected for processing (5)
  • Docs/ports-and-access.md (1 hunks)
  • backends/advanced/init.py (1 hunks)
  • extras/speaker-recognition/.env.template (1 hunks)
  • extras/speaker-recognition/setup.sh (1 hunks)
  • services.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • extras/speaker-recognition/.env.template
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Format Python code with Black using a 100-character line length
Use isort to organize Python imports
Never import modules in the middle of functions or files; all imports must be at the top of the Python file after the docstring
Group Python imports in the order: standard library, third-party, then local imports
Use lazy imports sparingly and only when absolutely necessary to resolve circular imports

Files:

  • backends/advanced/init.py
  • services.py
🧬 Code graph analysis (2)
backends/advanced/init.py (2)
services.py (1)
  • main (166-227)
init.py (1)
  • main (154-222)
services.py (1)
backends/advanced/init.py (2)
  • run (435-478)
  • main (481-492)
🪛 LanguageTool
Docs/ports-and-access.md

[grammar] ~21-~21: There might be a mistake here.
Context: ... | API Port | Web UI Port | Access URL | |---------|----------|-------------|----...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...-|----------|-------------|------------| | Advanced Backend | 8000 | 5173 | h...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ...)
http://localhost:5173 (Dashboard) | | Speaker Recognition | 8085 | 5174 ...

(QB_NEW_EN)


[grammar] ~24-~24: There might be a mistake here.
Context: ...(API)
http://localhost:5174 (WebUI) | | Parakeet ASR | 8767 | - | http://l...

(QB_NEW_EN)


[grammar] ~25-~25: There might be a mistake here.
Context: ...8767 | - | http://localhost:8767 (API) | | OpenMemory MCP | 8765 | 8765 | htt...

(QB_NEW_EN)


[grammar] ~28-~28: There might be a mistake here.
Context: ... Main Dashboard**: http://localhost:5173 🎤 Speaker Recognition: http://localho...

(QB_NEW_EN)


[grammar] ~29-~29: There might be a mistake here.
Context: ...ker Recognition**: http://localhost:5174 ❌ No microphone access - browsers requ...

(QB_NEW_EN)


[grammar] ~30-~30: There might be a mistake here.
Context: ...e access** - browsers require HTTPS for microphone --- ## HTTPS Mode (For Microphone Access) | Se...

(QB_NEW_EN)


[grammar] ~36-~36: There might be a mistake here.
Context: ... Port | Access URL | Microphone Access | |---------|-----------|------------|----...

(QB_NEW_EN)


[grammar] ~37-~37: There might be a mistake here.
Context: ...------|------------|-------------------| | Advanced Backend | 80→443 | 443 | ...

(QB_NEW_EN)


[grammar] ~38-~38: There might be a mistake here.
Context: ...r>https://localhost/api/ (API) | ✅ Yes | | Speaker Recognition | 8081→8444 | ...

(QB_NEW_EN)


[grammar] ~41-~41: There might be a mistake here.
Context: ...//localhost/ (Advanced Backend with SSL) 🎤 Speaker Recognition: https://localh...

(QB_NEW_EN)


[grammar] ~42-~42: There might be a mistake here.
Context: ...ost:8444/ (Speaker Recognition with SSL) ✅ Full microphone access - both servic...

(QB_NEW_EN)


[grammar] ~43-~43: There might be a mistake here.
Context: ...ccess** - both services secured with SSL ### Port Details (HTTPS Mode) - **Advanced B...

(QB_NEW_EN)


[grammar] ~45-~45: There might be a mistake here.
Context: ... with SSL ### Port Details (HTTPS Mode) - Advanced Backend nginx: Ports 80 (HTTP...

(QB_NEW_EN)


[grammar] ~46-~46: There might be a mistake here.
Context: ...: Ports 80 (HTTP redirect) + 443 (HTTPS) - Speaker Recognition nginx: Ports 8081 ...

(QB_NEW_EN)


[grammar] ~47-~47: There might be a mistake here.
Context: ...orts 8081 (HTTP redirect) + 8444 (HTTPS) - No port conflicts - different port ran...

(QB_NEW_EN)


[grammar] ~54-~54: There might be a mistake here.
Context: ... Why Two Modes? ### HTTP Mode (Default) ✅ Simple setup - No SSL certificates...

(QB_NEW_EN)


[grammar] ~59-~59: There might be a mistake here.
Context: ...or microphone ### HTTPS Mode (Advanced) ✅ Microphone access - Browsers allow...

(QB_NEW_EN)


[grammar] ~113-~113: There might be a mistake here.
Context: ...ers require HTTPS for microphone access over network 3. Mixed setup: Keep backe...

(QB_NEW_EN)


[grammar] ~115-~115: There might be a mistake here.
Context: ...ccess over network 3. Mixed setup: Keep backend on HTTP, only enable HTTPS for ...

(QB_NEW_EN)


[grammar] ~123-~123: There might be a mistake here.
Context: ...flicts: 1. Check running services: uv run --with-requirements setup-requirements.txt python services.py status 2. Stop conflicting services: `uv run --w...

(QB_NEW_EN)


[grammar] ~124-~124: There might be a mistake here.
Context: ...atus2. **Stop conflicting services**:uv run --with-requirements setup-requirements.txt python services.py stop --all` 3. Change ports in .env files if needed 4...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
Docs/ports-and-access.md

23-23: Bare URL used

(MD034, no-bare-urls)


23-23: Bare URL used

(MD034, no-bare-urls)


24-24: Bare URL used

(MD034, no-bare-urls)


24-24: Bare URL used

(MD034, no-bare-urls)


25-25: Bare URL used

(MD034, no-bare-urls)


26-26: Bare URL used

(MD034, no-bare-urls)


28-28: Bare URL used

(MD034, no-bare-urls)


29-29: Bare URL used

(MD034, no-bare-urls)


38-38: Bare URL used

(MD034, no-bare-urls)


38-38: Bare URL used

(MD034, no-bare-urls)


39-39: Bare URL used

(MD034, no-bare-urls)


39-39: Bare URL used

(MD034, no-bare-urls)


41-41: Bare URL used

(MD034, no-bare-urls)


42-42: Bare URL used

(MD034, no-bare-urls)


134-134: Bare URL used

(MD034, no-bare-urls)


135-135: Bare URL used

(MD034, no-bare-urls)


135-135: Bare URL used

(MD034, no-bare-urls)


136-136: Bare URL used

(MD034, no-bare-urls)


137-137: Bare URL used

(MD034, no-bare-urls)


138-138: Bare URL used

(MD034, no-bare-urls)

🪛 Shellcheck (0.10.0)
extras/speaker-recognition/setup.sh

[warning] 10-10: Quote this to prevent word splitting.

(SC2046)

🪛 Ruff (0.12.2)
backends/advanced/init.py

1-1: Shebang is present but file is not executable

(EXE001)


295-295: subprocess call: check for execution of untrusted input

(S603)


320-320: Do not catch blind exception: Exception

(BLE001)


476-476: Do not catch blind exception: Exception

(BLE001)

services.py

82-82: subprocess call: check for execution of untrusted input

(S603)


96-96: Do not catch blind exception: Exception

(BLE001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: integration-tests
🔇 Additional comments (2)
backends/advanced/init.py (1)

145-151: Confirm Mistral default model name – verify that “voxtral-mini-2507” is an exact, supported model identifier in your Mistral ASR backend to prevent configuration failures.

extras/speaker-recognition/setup.sh (1)

91-93: Verify docker compose service names match docker-compose.yml.

Ensure “speaker-service” and “web-ui” are correct targets; otherwise users will get errors.

Would you like me to scan docker-compose.yml and auto-derive the service names for the help text?

Comment on lines +334 to +338
# Authentication
AUTH_SECRET_KEY={self.config.get('AUTH_SECRET_KEY', '')}
ADMIN_EMAIL={self.config.get('ADMIN_EMAIL', '')}
ADMIN_PASSWORD={self.config.get('ADMIN_PASSWORD', '')}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Quote secrets to prevent dotenv parsing issues (spaces/#).

Unquoted ADMIN_PASSWORD can be truncated or misparsed.

-# Authentication
-AUTH_SECRET_KEY={self.config.get('AUTH_SECRET_KEY', '')}
-ADMIN_EMAIL={self.config.get('ADMIN_EMAIL', '')}
-ADMIN_PASSWORD={self.config.get('ADMIN_PASSWORD', '')}
+# Authentication
+AUTH_SECRET_KEY={self.config.get('AUTH_SECRET_KEY', '')}
+ADMIN_EMAIL={self.config.get('ADMIN_EMAIL', '')}
+ADMIN_PASSWORD="{self.config.get('ADMIN_PASSWORD', '').replace('"', '\\"')}"

If you prefer consistency, apply quoting to all values via a helper and render k=v lines. I can provide that refactor if desired.

📝 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.

Suggested change
# Authentication
AUTH_SECRET_KEY={self.config.get('AUTH_SECRET_KEY', '')}
ADMIN_EMAIL={self.config.get('ADMIN_EMAIL', '')}
ADMIN_PASSWORD={self.config.get('ADMIN_PASSWORD', '')}
# Authentication
AUTH_SECRET_KEY={self.config.get('AUTH_SECRET_KEY', '')}
ADMIN_EMAIL={self.config.get('ADMIN_EMAIL', '')}
ADMIN_PASSWORD="{self.config.get('ADMIN_PASSWORD', '').replace('\"', '\\\"')}"
🤖 Prompt for AI Agents
In backends/advanced/init.py around lines 334 to 338, the AUTH_SECRET_KEY,
ADMIN_EMAIL and ADMIN_PASSWORD values are rendered unquoted which can cause
dotenv parsing/truncation issues for values containing spaces or #; wrap these
values in properly escaped quotes (e.g. double-quotes, escaping any inner
quotes/backslashes) when writing the k=v lines or implement a small helper that
returns a safely quoted value for every env var and use it to render each line
uniformly so all secrets are robustly quoted and safe from misparsing.

Comment on lines +66 to +67
sed "s/TAILSCALE_IP/$SERVER_IP/g" nginx.conf.template > nginx.conf
echo "✅ nginx.conf created for: $SERVER_IP"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Escape replacement when templating nginx.conf.

Avoids sed metachar issues with domains/IPs.

-        sed "s/TAILSCALE_IP/$SERVER_IP/g" nginx.conf.template > nginx.conf
+        ESC_SERVER_IP=${SERVER_IP//&/\\&}
+        sed "s|TAILSCALE_IP|$ESC_SERVER_IP|g" nginx.conf.template > nginx.conf
📝 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.

Suggested change
sed "s/TAILSCALE_IP/$SERVER_IP/g" nginx.conf.template > nginx.conf
echo "✅ nginx.conf created for: $SERVER_IP"
# Escape any '&' in the replacement to avoid sed metachar pitfalls
ESC_SERVER_IP=${SERVER_IP//&/\\&}
sed "s|TAILSCALE_IP|$ESC_SERVER_IP|g" nginx.conf.template > nginx.conf
echo "✅ nginx.conf created for: $SERVER_IP"
🤖 Prompt for AI Agents
In extras/speaker-recognition/setup.sh around lines 66-67, the sed substitution
can break when $SERVER_IP contains sed-special characters; update the templating
to either use an alternate delimiter (e.g. sed "s|TAILSCALE_IP|$SERVER_IP|g") or
sanitize/escape $SERVER_IP before passing it to sed (escape at least '/', '&',
and '\' in the variable), and ensure the variable is quoted so the replacement
is safe.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (12)
extras/speaker-recognition/setup.sh (7)

8-11: Quote backup target to avoid word splitting (SC2046).

Prevents accidental globbing/splitting when constructing the backup path.

 if [ -f ".env" ]; then
   echo "⚠️  .env already exists. Backing up..."
-    cp .env .env.backup.$(date +%Y%m%d_%H%M%S)
+    cp .env ".env.backup.$(date +%Y%m%d_%H%M%S)"
 fi

39-46: Validate HTTPS mode selection (1/2) and use read -r.

Keeps behavior explicit and avoids silent defaults on bad input.

-read -p "Enter choice [1-2] (1): " HTTPS_CHOICE
-HTTPS_CHOICE=${HTTPS_CHOICE:-1}
-
-if [ "$HTTPS_CHOICE" = "2" ]; then
-    HTTPS_MODE="https"
-else
-    HTTPS_MODE="http"
-fi
+while :; do
+  read -r -p "Enter choice [1-2] (1): " HTTPS_CHOICE
+  HTTPS_CHOICE=${HTTPS_CHOICE:-1}
+  case "$HTTPS_CHOICE" in
+    1) HTTPS_MODE="http"; break ;;
+    2) HTTPS_MODE="https"; break ;;
+    *) echo "Invalid choice. Enter 1 or 2." ;;
+  esac
+done

2-2: Harden shell options and add an error trap.

Use strict mode to fail fast and surface errors clearly.

-set -e
+set -euo pipefail
+trap 'echo "Error on line $LINENO"; exit 1' ERR

13-15: Guard against missing template and lock down .env permissions.

Fail fast if the template is absent; ensure secrets aren’t world-readable.

 # Start from template
-cp .env.template .env
+if [ ! -f ".env.template" ]; then
+  echo "❌ .env.template not found" >&2; exit 1
+fi
+cp .env.template .env
+chmod 600 .env || true

16-21: Don’t echo tokens; validate input.

Read silently and ensure the token is provided.

 echo ""
 echo "🔑 Hugging Face Token (required for pyannote models)"
 echo "Get yours from: https://huggingface.co/settings/tokens"
-read -p "HF Token: " HF_TOKEN
+read -r -s -p "HF Token: " HF_TOKEN
+echo
+if [ -z "${HF_TOKEN:-}" ]; then
+  echo "❌ HF Token is required" >&2; exit 1
+fi

48-51: Make in-place edits portable and escape replacements; avoid leaking secrets via sed args.

  • sed -i differs on macOS vs GNU; add compatibility.
  • Escape “&” in HF_TOKEN; anchor keys.
  • Note: putting secrets in sed args can be visible in process list; consider a here-doc or small helper to rewrite .env without exposing args. For now, this keeps current approach but safer.
-# Update .env file
-sed -i "s|HF_TOKEN=.*|HF_TOKEN=$HF_TOKEN|" .env
-sed -i "s|COMPUTE_MODE=.*|COMPUTE_MODE=$COMPUTE_MODE|" .env
+# Update .env file (portable -i) and safe replacements
+_sedi=(-i)
+if sed --version >/dev/null 2>&1; then :; else _sedi=(-i ""); fi
+# Escape '&' which has special meaning in sed replacement
+HF_ESC=${HF_TOKEN//&/\\&}
+sed "${_sedi[@]}" "s|^HF_TOKEN=.*|HF_TOKEN=${HF_ESC}|" .env
+sed "${_sedi[@]}" "s|^COMPUTE_MODE=.*|COMPUTE_MODE=${COMPUTE_MODE}|" .env

75-82: Escape replacement when templating nginx.conf.

Avoid sed metachar issues (e.g., “&”) and use an alternate delimiter.

-        sed "s/TAILSCALE_IP/$SERVER_IP/g" nginx.conf.template > nginx.conf
+        ESC_SERVER_IP=${SERVER_IP//&/\\&}
+        sed "s|TAILSCALE_IP|$ESC_SERVER_IP|g" nginx.conf.template > nginx.conf
backends/advanced/init.py (5)

1-1: Fix EXE001: shebang present but file not executable.

Either make the file executable or remove the shebang.

Apply one of:

-#!/usr/bin/env python3

or run:

chmod +x backends/advanced/init.py

257-260: Fix default Speaker Recognition port (8085).

Align the default with the rest of the PR to avoid OOTB breakage.

-                speaker_url = self.prompt_value("Speaker Recognition service URL", "http://host.docker.internal:8001")
+                speaker_url = self.prompt_value("Speaker Recognition service URL", "http://host.docker.internal:8085")

336-340: Quote ADMIN_PASSWORD to avoid dotenv parsing issues.

Prevents truncation when value contains spaces or #.

 # Authentication
 AUTH_SECRET_KEY={self.config.get('AUTH_SECRET_KEY', '')}
 ADMIN_EMAIL={self.config.get('ADMIN_EMAIL', '')}
-ADMIN_PASSWORD={self.config.get('ADMIN_PASSWORD', '')}
+ADMIN_PASSWORD="{self.config.get('ADMIN_PASSWORD', '').replace('"', '\\"')}"

347-354: Avoid leaking OpenAI defaults when provider ≠ openai.

Write empty values unless explicitly configured.

 OPENAI_API_KEY={self.config.get('OPENAI_API_KEY', '')}
-OPENAI_MODEL={self.config.get('OPENAI_MODEL', 'gpt-4o-mini')}
-OPENAI_BASE_URL={self.config.get('OPENAI_BASE_URL', 'https://api.openai.com/v1')}
+OPENAI_MODEL={self.config.get('OPENAI_MODEL', '')}
+OPENAI_BASE_URL={self.config.get('OPENAI_BASE_URL', '')}
 OLLAMA_BASE_URL={self.config.get('OLLAMA_BASE_URL', '')}
 OLLAMA_MODEL={self.config.get('OLLAMA_MODEL', '')}

382-385: Harden .env file permissions (mode 600).

Secrets should not be world-readable.

         with open(".env", "w") as f:
             f.write(env_content)
-        
-        self.console.print("[green][SUCCESS][/green] .env file created successfully")
+        try:
+            os.chmod(".env", 0o600)
+        except OSError:
+            pass
+        self.console.print("[green][SUCCESS][/green] .env file created successfully (mode 600)")
🧹 Nitpick comments (5)
extras/speaker-recognition/setup.sh (4)

26-33: Validate compute-mode choice (1/2) and use read -r.

Prevents typos and unexpected fallbacks.

-read -p "Enter choice [1-2] (1): " COMPUTE_CHOICE
-COMPUTE_CHOICE=${COMPUTE_CHOICE:-1}
-
-if [ "$COMPUTE_CHOICE" = "2" ]; then
-    COMPUTE_MODE="gpu"
-else
-    COMPUTE_MODE="cpu"
-fi
+while :; do
+  read -r -p "Enter choice [1-2] (1): " COMPUTE_CHOICE
+  COMPUTE_CHOICE=${COMPUTE_CHOICE:-1}
+  case "$COMPUTE_CHOICE" in
+    1) COMPUTE_MODE="cpu"; break ;;
+    2) COMPUTE_MODE="gpu"; break ;;
+    *) echo "Invalid choice. Enter 1 or 2." ;;
+  esac
+done

58-60: Use read -r and trim for server input.

Minor robustness for backslashes and spacing.

-read -p "Server IP/Domain (localhost): " SERVER_IP
+read -r -p "Server IP/Domain (localhost): " SERVER_IP
 SERVER_IP=${SERVER_IP:-localhost}

61-64: Reuse portable sed and anchor keys for HTTPS env.

Consistency and correctness if keys appear multiple times.

-    sed -i "s|REACT_UI_HTTPS=.*|REACT_UI_HTTPS=true|" .env
-    sed -i "s|REACT_UI_PORT=.*|REACT_UI_PORT=5175|" .env
+    sed "${_sedi[@]}" "s|^REACT_UI_HTTPS=.*|REACT_UI_HTTPS=true|" .env
+    sed "${_sedi[@]}" "s|^REACT_UI_PORT=.*|REACT_UI_PORT=5175|" .env

95-97: Reuse portable sed for HTTP branch.

Consistency across both branches.

-    sed -i "s|REACT_UI_HTTPS=.*|REACT_UI_HTTPS=false|" .env
-    sed -i "s|REACT_UI_PORT=.*|REACT_UI_PORT=5174|" .env
+    sed "${_sedi[@]}" "s|^REACT_UI_HTTPS=.*|REACT_UI_HTTPS=false|" .env
+    sed "${_sedi[@]}" "s|^REACT_UI_PORT=.*|REACT_UI_PORT=5174|" .env
backends/advanced/init.py (1)

286-303: HTTPS flow: set flag when chosen, run script via bash, and catch specific exceptions.

Prevents silent misconfig if the script isn’t executable and avoids blanket except.

-        if enable_https:
+        if enable_https:
             self.console.print("[blue][INFO][/blue] HTTPS enables microphone access in browsers")
             self.console.print("[blue][INFO][/blue] For distributed deployments, use your Tailscale IP (e.g., 100.64.1.2)")
             self.console.print("[blue][INFO][/blue] For local-only access, use 'localhost'")
             server_ip = self.prompt_value("Server IP/Domain for SSL certificate (Tailscale IP or localhost)", "localhost")
+            self.config["HTTPS_ENABLED"] = "true"
             
             # Generate SSL certificates
             self.console.print("[blue][INFO][/blue] Generating SSL certificates...")
             ssl_script = Path("ssl/generate-ssl.sh")
             if ssl_script.exists():
                 try:
-                    subprocess.run([str(ssl_script), server_ip], check=True, cwd=".")
+                    subprocess.run(["bash", str(ssl_script), server_ip], check=True, cwd=".")
                     self.console.print("[green][SUCCESS][/green] SSL certificates generated")
-                except subprocess.CalledProcessError:
-                    self.console.print("[yellow][WARNING][/yellow] SSL certificate generation failed")
+                except (subprocess.CalledProcessError, FileNotFoundError, PermissionError) as e:
+                    self.console.print(f"[yellow][WARNING][/yellow] SSL certificate generation failed: {e}")
             else:
                 self.console.print("[yellow][WARNING][/yellow] ssl/generate-ssl.sh not found")
@@
-                except Exception as e:
-                    self.console.print(f"[yellow][WARNING][/yellow] nginx.conf generation failed: {e}")
+                except OSError as e:
+                    self.console.print(f"[yellow][WARNING][/yellow] nginx.conf generation failed: {e}")

Optional: validate server_ip (hostname/IP) before invoking the script:

import ipaddress, re

def _is_valid_host(s: str) -> bool:
    try:
        ipaddress.ip_address(s)
        return True
    except ValueError:
        return bool(re.fullmatch(r"[a-zA-Z0-9.-]+", s))

Also applies to: 318-325

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 489932c and 641e304.

📒 Files selected for processing (2)
  • backends/advanced/init.py (1 hunks)
  • extras/speaker-recognition/setup.sh (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Format Python code with Black using a 100-character line length
Use isort to organize Python imports
Never import modules in the middle of functions or files; all imports must be at the top of the Python file after the docstring
Group Python imports in the order: standard library, third-party, then local imports
Use lazy imports sparingly and only when absolutely necessary to resolve circular imports

Files:

  • backends/advanced/init.py
🧬 Code graph analysis (1)
backends/advanced/init.py (2)
init.py (1)
  • main (154-222)
services.py (1)
  • main (166-227)
🪛 Ruff (0.12.2)
backends/advanced/init.py

1-1: Shebang is present but file is not executable

(EXE001)


297-297: subprocess call: check for execution of untrusted input

(S603)


322-322: Do not catch blind exception: Exception

(BLE001)


478-478: Do not catch blind exception: Exception

(BLE001)

🪛 Shellcheck (0.10.0)
extras/speaker-recognition/setup.sh

[warning] 10-10: Quote this to prevent word splitting.

(SC2046)

🔇 Additional comments (1)
extras/speaker-recognition/setup.sh (1)

89-106: Ports & service names verified: setup.sh’s ports (8444/8081 for HTTPS/Nginx; 5174/5175 for React UI) and service names (speaker-service, web-ui) align with docker-compose.yml and related configs.

Comment on lines +128 to +139
if choice == "1":
self.config["TRANSCRIPTION_PROVIDER"] = "deepgram"
self.console.print("[blue][INFO][/blue] Deepgram selected")
self.console.print("Get your API key from: https://console.deepgram.com/")

api_key = self.prompt_value("Deepgram API key (leave empty to skip)", "")
if api_key:
self.config["DEEPGRAM_API_KEY"] = api_key
self.console.print("[green][SUCCESS][/green] Deepgram configured")
else:
self.console.print("[yellow][WARNING][/yellow] No API key provided - transcription will not work")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Don’t set Deepgram as provider unless an API key is provided.

Pre-setting the provider with a missing key causes misconfig at runtime.

-        if choice == "1":
-            self.config["TRANSCRIPTION_PROVIDER"] = "deepgram"
+        if choice == "1":
             self.console.print("[blue][INFO][/blue] Deepgram selected")
             self.console.print("Get your API key from: https://console.deepgram.com/")
             
             api_key = self.prompt_value("Deepgram API key (leave empty to skip)", "")
             if api_key:
+                self.config["TRANSCRIPTION_PROVIDER"] = "deepgram"
                 self.config["DEEPGRAM_API_KEY"] = api_key
                 self.console.print("[green][SUCCESS][/green] Deepgram configured")
             else:
-                self.console.print("[yellow][WARNING][/yellow] No API key provided - transcription will not work")
+                self.config.pop("TRANSCRIPTION_PROVIDER", None)
+                self.console.print("[yellow][WARNING][/yellow] No API key provided - skipping transcription setup")
📝 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.

Suggested change
if choice == "1":
self.config["TRANSCRIPTION_PROVIDER"] = "deepgram"
self.console.print("[blue][INFO][/blue] Deepgram selected")
self.console.print("Get your API key from: https://console.deepgram.com/")
api_key = self.prompt_value("Deepgram API key (leave empty to skip)", "")
if api_key:
self.config["DEEPGRAM_API_KEY"] = api_key
self.console.print("[green][SUCCESS][/green] Deepgram configured")
else:
self.console.print("[yellow][WARNING][/yellow] No API key provided - transcription will not work")
if choice == "1":
self.console.print("[blue][INFO][/blue] Deepgram selected")
self.console.print("Get your API key from: https://console.deepgram.com/")
api_key = self.prompt_value("Deepgram API key (leave empty to skip)", "")
if api_key:
self.config["TRANSCRIPTION_PROVIDER"] = "deepgram"
self.config["DEEPGRAM_API_KEY"] = api_key
self.console.print("[green][SUCCESS][/green] Deepgram configured")
else:
self.config.pop("TRANSCRIPTION_PROVIDER", None)
self.console.print("[yellow][WARNING][/yellow] No API key provided - skipping transcription setup")
🤖 Prompt for AI Agents
In backends/advanced/init.py around lines 128 to 139, the code sets
TRANSCRIPTION_PROVIDER to "deepgram" before confirming an API key, which can
create a misconfiguration at runtime; change the flow so the provider is only
set when a non-empty Deepgram API key is provided (move
self.config["TRANSCRIPTION_PROVIDER"] = "deepgram" inside the if api_key block),
assign DEEPGRAM_API_KEY only when api_key is truthy, and in the else branch do
not modify TRANSCRIPTION_PROVIDER (instead keep existing value or set to None)
while emitting the warning about missing key.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (9)
backends/advanced/init.py (9)

1-1: Resolve EXE001: shebang vs. executable bit

Either mark this file executable or remove the shebang to silence Ruff.

-#!/usr/bin/env python3
+"""
+Friend-Lite Advanced Backend Interactive Setup Script
+Interactive configuration for all services and API keys
+"""

Alternatively: chmod +x backends/advanced/init.py.


128-139: Gate Deepgram provider on presence of API key

Avoid writing a broken config when no key is provided.

-        if choice == "1":
-            self.config["TRANSCRIPTION_PROVIDER"] = "deepgram"
+        if choice == "1":
             self.console.print("[blue][INFO][/blue] Deepgram selected")
             self.console.print("Get your API key from: https://console.deepgram.com/")
             
             api_key = self.prompt_value("Deepgram API key (leave empty to skip)", "")
             if api_key:
+                self.config["TRANSCRIPTION_PROVIDER"] = "deepgram"
                 self.config["DEEPGRAM_API_KEY"] = api_key
                 self.console.print("[green][SUCCESS][/green] Deepgram configured")
             else:
-                self.console.print("[yellow][WARNING][/yellow] No API key provided - transcription will not work")
+                self.config.pop("TRANSCRIPTION_PROVIDER", None)
+                self.console.print("[yellow][WARNING][/yellow] No API key provided - skipping transcription setup")

140-154: Apply the same gating for Mistral

Only set provider and model when credentials are present.

-        elif choice == "2":
-            self.config["TRANSCRIPTION_PROVIDER"] = "mistral"
+        elif choice == "2":
             self.console.print("[blue][INFO][/blue] Mistral selected")
             self.console.print("Get your API key from: https://console.mistral.ai/")
             
             api_key = self.prompt_value("Mistral API key (leave empty to skip)", "")
             model = self.prompt_value("Mistral model", "voxtral-mini-2507")
             
             if api_key:
+                self.config["TRANSCRIPTION_PROVIDER"] = "mistral"
                 self.config["MISTRAL_API_KEY"] = api_key
                 self.config["MISTRAL_MODEL"] = model
                 self.console.print("[green][SUCCESS][/green] Mistral configured")
             else:
-                self.console.print("[yellow][WARNING][/yellow] No API key provided - transcription will not work")
+                self.config.pop("TRANSCRIPTION_PROVIDER", None)
+                self.console.print("[yellow][WARNING][/yellow] No API key provided - skipping transcription setup")

177-193: Gate OpenAI provider on presence of API key

Pre-setting LLM_PROVIDER to openai without a key leaves a broken env.

-        if choice == "1":
-            self.config["LLM_PROVIDER"] = "openai"
+        if choice == "1":
             self.console.print("[blue][INFO][/blue] OpenAI selected")
             self.console.print("Get your API key from: https://platform.openai.com/api-keys")
             
             api_key = self.prompt_value("OpenAI API key (leave empty to skip)", "")
             model = self.prompt_value("OpenAI model", "gpt-4o-mini")
             base_url = self.prompt_value("OpenAI base URL (for proxies/compatible APIs)", "https://api.openai.com/v1")
             
             if api_key:
+                self.config["LLM_PROVIDER"] = "openai"
                 self.config["OPENAI_API_KEY"] = api_key
                 self.config["OPENAI_MODEL"] = model
                 self.config["OPENAI_BASE_URL"] = base_url
                 self.console.print("[green][SUCCESS][/green] OpenAI configured")
             else:
-                self.console.print("[yellow][WARNING][/yellow] No API key provided - memory extraction will not work")
+                self.config.pop("LLM_PROVIDER", None)
+                self.console.print("[yellow][WARNING][/yellow] No API key provided - skipping LLM setup")

257-261: Fix default Speaker Recognition port (8085 expected elsewhere)

Align the default with the rest of the PR to avoid OOTB breakage.

-                speaker_url = self.prompt_value("Speaker Recognition service URL", "http://host.docker.internal:8001")
+                speaker_url = self.prompt_value("Speaker Recognition service URL", "http://host.docker.internal:8085")

Run to verify repo-wide consistency:

#!/bin/bash
rg -n -C2 -g '!**/node_modules/**' -e '8085' -e '8001'

346-353: Don’t leak OpenAI defaults when provider isn’t OpenAI

Write empty values unless OpenAI is configured.

 OPENAI_API_KEY={self.config.get('OPENAI_API_KEY', '')}
-OPENAI_MODEL={self.config.get('OPENAI_MODEL', 'gpt-4o-mini')}
-OPENAI_BASE_URL={self.config.get('OPENAI_BASE_URL', 'https://api.openai.com/v1')}
+OPENAI_MODEL={self.config.get('OPENAI_MODEL', '')}
+OPENAI_BASE_URL={self.config.get('OPENAI_BASE_URL', '')}
 OLLAMA_BASE_URL={self.config.get('OLLAMA_BASE_URL', '')}
 OLLAMA_MODEL={self.config.get('OLLAMA_MODEL', '')}

335-339: Quote secrets to avoid dotenv parsing issues

Passwords with spaces or # get truncated when unquoted.

 # Authentication
 AUTH_SECRET_KEY={self.config.get('AUTH_SECRET_KEY', '')}
 ADMIN_EMAIL={self.config.get('ADMIN_EMAIL', '')}
-ADMIN_PASSWORD={self.config.get('ADMIN_PASSWORD', '')}
+ADMIN_PASSWORD="{self.config.get('ADMIN_PASSWORD', '').replace('"', '\\"')}"

Optional: apply consistent quoting via a small helper for all values.


361-368: Persist HTTPS settings into the .env

Currently computed but not written, causing drift at runtime.

 # Optional Services
 SPEAKER_SERVICE_URL={self.config.get('SPEAKER_SERVICE_URL', '')}
 PARAKEET_ASR_URL={self.config.get('PARAKEET_ASR_URL', '')}
 
+# HTTPS
+HTTPS_ENABLED={self.config.get('HTTPS_ENABLED', 'false')}
+SERVER_IP={self.config.get('SERVER_IP', '')}
+
 # Network Configuration
 BACKEND_PUBLIC_PORT={self.config.get('BACKEND_PUBLIC_PORT', '8000')}
 WEBUI_PORT={self.config.get('WEBUI_PORT', '5173')}

380-383: Write .env with 0600 permissions (secrets inside)

Reduce accidental exposure on multi-user systems.

-        with open(".env", "w") as f:
-            f.write(env_content)
-        
-        self.console.print("[green][SUCCESS][/green] .env file created successfully")
+        with open(".env", "w") as f:
+            f.write(env_content)
+        try:
+            os.chmod(".env", 0o600)
+        except OSError:
+            pass
+        self.console.print("[green][SUCCESS][/green] .env file created successfully (mode 600)")
🧹 Nitpick comments (4)
backends/advanced/init.py (4)

321-323: Narrow exception handling around nginx.conf generation (BLE001)

Catching Exception broadly hides actionable errors.

-                except Exception as e:
+                except (OSError, IOError, UnicodeError) as e:
                     self.console.print(f"[yellow][WARNING][/yellow] nginx.conf generation failed: {e}")

94-102: Harden backup of existing .env

Ensure the backup inherits restrictive perms as well.

             backup_path = f".env.backup.{timestamp}"
             shutil.copy2(env_path, backup_path)
+            try:
+                os.chmod(backup_path, 0o600)
+            except OSError:
+                pass
             self.console.print(f"[blue][INFO][/blue] Backed up existing .env file to {backup_path}")

272-274: Validate port inputs (1–65535)

Prevent invalid ports from being written to the env.

-        self.config["BACKEND_PUBLIC_PORT"] = self.prompt_value("Backend port", "8000")
-        self.config["WEBUI_PORT"] = self.prompt_value("Web UI port", "5173")
+        def _prompt_port(label: str, default: str) -> str:
+            while True:
+                p = self.prompt_value(label, default)
+                if p.isdigit() and 1 <= int(p) <= 65535:
+                    return p
+                self.console.print("[red]Invalid port. Enter a number 1–65535.[/red]")
+        self.config["BACKEND_PUBLIC_PORT"] = _prompt_port("Backend port", "8000")
+        self.config["WEBUI_PORT"] = _prompt_port("Web UI port", "5173")

492-494: Narrow top-level exception catch (BLE001)

Catch expected failures explicitly; avoid a blind Exception.

-        except Exception as e:
+        except (OSError, ValueError, subprocess.CalledProcessError) as e:
             self.console.print(f"[red][ERROR][/red] Setup failed: {e}")
             sys.exit(1)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 641e304 and e783d01.

📒 Files selected for processing (1)
  • backends/advanced/init.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Format Python code with Black using a 100-character line length
Use isort to organize Python imports
Never import modules in the middle of functions or files; all imports must be at the top of the Python file after the docstring
Group Python imports in the order: standard library, third-party, then local imports
Use lazy imports sparingly and only when absolutely necessary to resolve circular imports

Files:

  • backends/advanced/init.py
🧬 Code graph analysis (1)
backends/advanced/init.py (2)
init.py (1)
  • main (154-222)
services.py (1)
  • main (166-227)
🪛 Ruff (0.12.2)
backends/advanced/init.py

1-1: Shebang is present but file is not executable

(EXE001)


296-296: subprocess call: check for execution of untrusted input

(S603)


321-321: Do not catch blind exception: Exception

(BLE001)


492-492: Do not catch blind exception: Exception

(BLE001)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
init.py (1)

139-158: Add timeout, capture stdout/stderr, and handle subprocess errors explicitly

Prevents hangs, surfaces failures, and addresses Ruff S603 guidance. This also aligns with the earlier review feedback.

-    try:
-        result = subprocess.run(
-            cmd, 
-            cwd=service['path'],
-            check=False
-        )
-        
-        if result.returncode == 0:
-            console.print(f"✅ {service_name} setup completed")
-            return True
-        else:
-            console.print(f"❌ {service_name} setup failed (exit code: {result.returncode})")
-            return False
-            
-    except FileNotFoundError as e:
-        console.print(f"❌ {service_name} setup failed: {e}")
-        return False
-    except Exception as e:
-        console.print(f"❌ {service_name} setup failed: {e}")
-        return False
+    try:
+        result = subprocess.run(
+            cmd,
+            cwd=service["path"],
+            check=True,
+            capture_output=True,
+            text=True,
+            timeout=1800,
+        )
+        console.print(f"✅ {service_name} setup completed")
+        return True
+    except FileNotFoundError as e:
+        console.print(f"❌ {service_name} setup failed: {e}")
+        return False
+    except subprocess.TimeoutExpired as e:
+        console.print(f"❌ {service_name} setup timed out after {e.timeout}s")
+        return False
+    except subprocess.CalledProcessError as e:
+        stdout = (e.stdout or "").strip()
+        stderr = (e.stderr or "").strip()
+        console.print(f"❌ {service_name} setup failed (exit code: {e.returncode})")
+        if stdout:
+            console.print(f"[dim]stdout:[/dim]\n{stdout}")
+        if stderr:
+            console.print(f"[dim]stderr:[/dim]\n{stderr}")
+        return False
+    except Exception as e:
+        console.print(f"❌ {service_name} setup failed: {e}")
+        return False
🧹 Nitpick comments (3)
init.py (3)

121-127: Avoid hard-coded service URLs; make them discoverable or configurable

Consider reading these from env vars, a small artifact emitted by the extras setup (e.g., a file with the URL), or falling back to the current defaults.


205-205: Remove f-prefix from strings without placeholders (Ruff F541)

Cosmetic, but keeps CI quiet.

-    console.print(f"\n🎊 [bold green]Setup Complete![/bold green]")
+    console.print("\n🎊 [bold green]Setup Complete![/bold green]")
-    console.print(f"\n🚀 [bold]Enjoy Friend-Lite![/bold]")
+    console.print("\n🚀 [bold]Enjoy Friend-Lite![/bold]")
-    console.print(f"\n💡 [dim]Tip: You can also setup services individually:[/dim]")
+    console.print("\n💡 [dim]Tip: You can also setup services individually:[/dim]")
-    console.print(f"[dim]   cd backends/advanced && uv run --with-requirements setup-requirements.txt python init.py[/dim]")
+    console.print("[dim]   cd backends/advanced && uv run --with-requirements setup-requirements.txt python init.py[/dim]")

Also applies to: 242-242, 245-246


185-188: Unreachable branch

Backend is always added, so this “no services selected” branch won’t execute. Consider removing or guarding against a future change.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e783d01 and cb5920d.

📒 Files selected for processing (1)
  • init.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Format Python code with Black using a 100-character line length
Use isort to organize Python imports
Never import modules in the middle of functions or files; all imports must be at the top of the Python file after the docstring
Group Python imports in the order: standard library, third-party, then local imports
Use lazy imports sparingly and only when absolutely necessary to resolve circular imports

Files:

  • init.py
🧬 Code graph analysis (1)
init.py (1)
backends/advanced/init.py (2)
  • run (451-494)
  • main (497-508)
🪛 Ruff (0.12.2)
init.py

140-140: subprocess call: check for execution of untrusted input

(S603)


156-156: Do not catch blind exception: Exception

(BLE001)


205-205: f-string without any placeholders

Remove extraneous f prefix

(F541)


242-242: f-string without any placeholders

Remove extraneous f prefix

(F541)


245-245: f-string without any placeholders

Remove extraneous f prefix

(F541)


246-246: f-string without any placeholders

Remove extraneous f prefix

(F541)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: integration-tests
🔇 Additional comments (5)
init.py (5)

44-62: Service existence check reads cleanly

The directory/script checks yield actionable messages; this fits the orchestrator role well.


63-94: Interactive selection UX looks solid

Default “No” for non-interactive runs with EOF fallback is a good touch.


160-174: Status reporting is clear and helpful

Concise availability overview; good use of Rich markup.


19-23: No changes needed for setup-requirements.txt path

The file setup-requirements.txt exists both at the repo root and under backends/advanced, so using the relative path from the backends/advanced CWD is valid.


214-233: Mapping of ‘advanced’ to ‘backend’ is valid. services.py defines and accepts ‘backend’, ‘speaker-recognition’, ‘asr-services’, and ‘openmemory-mcp’, matching init.py’s mapping. No changes needed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
services.py (2)

49-53: Simplify redundant condition.

Both branches return the same expression.

-    # Backend uses advanced init, others use .env
-    if service_name == 'backend':
-        return (service_path / '.env').exists()
-    else:
-        return (service_path / '.env').exists()
+    # All services considered "configured" when .env exists in their path
+    return (service_path / '.env').exists()

129-131: Avoid catching generic Exception; handle expected errors and re-raise interrupts.

This swallows KeyboardInterrupt and hides useful diagnostics (Ruff BLE001).

-    except Exception as e:
-        console.print(f"[red]❌ Error running command: {e}[/red]")
-        return False
+    except KeyboardInterrupt:
+        console.print("[yellow]⚠️  Interrupted by user[/yellow]")
+        return False
+    except FileNotFoundError:
+        console.print("[red]❌ Docker not found. Is Docker Desktop/Engine installed and in PATH?[/red]")
+        return False
+    except subprocess.TimeoutExpired as e:
+        console.print(f"[red]❌ Command timed out after {e.timeout}s[/red]")
+        return False
+    except (subprocess.SubprocessError, PermissionError, OSError) as e:
+        console.print(f"[red]❌ Error running command: {e}[/red]")
+        return False
🧹 Nitpick comments (6)
services.py (6)

7-14: Remove unused imports and avoid shadowing print.

sys and rprint aren’t used. Shadowing built-in print is risky and unnecessary here.

-import subprocess
-import sys
-import argparse
-from pathlib import Path
-from rich.console import Console
-from rich.table import Table
-from rich import print as rprint
+import subprocess
+import argparse
+from pathlib import Path
+from rich.console import Console
+from rich.table import Table

69-80: Guard against unsupported commands to prevent accidental bare docker compose.

Currently an unexpected command would run docker compose with no args.

     cmd = ['docker', 'compose']
     if command == 'up':
         cmd.extend(['up', '-d'])
         if build:
             cmd.append('--build')
     elif command == 'down':
         cmd.extend(['down'])
     elif command == 'restart':
         cmd.extend(['restart'])
     elif command == 'status':
         cmd.extend(['ps'])
+    else:
+        raise ValueError(f"Unsupported compose command: {command}")

81-113: Replace assert in production path with explicit guard.

Avoid assertions for runtime flow; handle the unexpected stdout None case gracefully (Ruff S101).

-            # Stream output line by line
-            assert process.stdout is not None
-            for line in process.stdout:
+            # Stream output line by line
+            if process.stdout is None:
+                console.print("[red]❌ Unexpected: no stdout from docker compose[/red]")
+                process.wait()
+                return False
+            for line in process.stdout:
                 line = line.rstrip()

114-128: Add a timeout to non-build commands and improve error output.

Prevents hangs (e.g., stalled Docker daemon) and shows stdout when stderr is empty.

-            result = subprocess.run(
+            result = subprocess.run(
                 cmd,
                 cwd=service_path,
-                capture_output=True,
+                capture_output=True,
                 text=True,
-                check=False
+                check=False,
+                timeout=120
             )
 
             if result.returncode == 0:
                 return True
             else:
-                console.print(f"[red]❌ Command failed: {result.stderr}[/red]")
+                msg = result.stderr.strip() or result.stdout.strip() or "Unknown error"
+                console.print(f"[red]❌ Command failed: {msg}[/red]")
                 return False

175-197: Optionally show actual container state, not just “Configured”.

Enhances status by reflecting running/stopped state via docker compose ps.

-    table = Table()
+    table = Table()
     table.add_column("Service", style="cyan")
     table.add_column("Configured", justify="center")
+    table.add_column("State", justify="center")
     table.add_column("Description", style="dim")
     table.add_column("Ports", style="green")
 
     for service_name, service_info in SERVICES.items():
-        configured = "✅" if check_service_configured(service_name) else "❌"
+        configured = "✅" if check_service_configured(service_name) else "❌"
+        state = "—"
+        if configured:
+            result = subprocess.run(
+                ["docker", "compose", "ps", "--format", "json"],
+                cwd=Path(service_info["path"]),
+                capture_output=True,
+                text=True,
+                check=False,
+                timeout=10,
+            )
+            if result.returncode == 0 and result.stdout.strip():
+                try:
+                    import json  # top-level import preferred; move this up if adopted
+                    ps = json.loads(result.stdout)
+                    states = {c.get("State", "").lower() for c in ps or []}
+                    state = "running" if "running" in states else ("exited" if states else "n/a")
+                except Exception:
+                    state = "n/a"
         ports = ", ".join(service_info['ports'])
         table.add_row(
             service_name,
-            configured, 
+            configured,
+            state,
             service_info['description'],
             ports
         )

If you adopt this, move import json to the top of the file per guidelines.


199-261: Validate service names once and reuse the list.

Minor DRY tidy: extract validation into a helper to reduce duplicated blocks in start/stop.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cb5920d and 165d2f1.

📒 Files selected for processing (1)
  • services.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Format Python code with Black using a 100-character line length
Use isort to organize Python imports
Never import modules in the middle of functions or files; all imports must be at the top of the Python file after the docstring
Group Python imports in the order: standard library, third-party, then local imports
Use lazy imports sparingly and only when absolutely necessary to resolve circular imports

Files:

  • services.py
🧬 Code graph analysis (1)
services.py (1)
backends/advanced/init.py (2)
  • run (451-494)
  • main (497-508)
🪛 Ruff (0.12.2)
services.py

85-85: subprocess call: check for execution of untrusted input

(S603)


95-95: Use of assert detected

(S101)


115-115: subprocess call: check for execution of untrusted input

(S603)


129-129: Do not catch blind exception: Exception

(BLE001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: integration-tests
🔇 Additional comments (1)
services.py (1)

1-6: Nice CLI scaffold and clear UX.

Good module structure, helpful Rich output, and sensible defaults (status when no subcommand).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
services.py (3)

18-43: Standardize and verify ports; avoid ambiguous '5174/8444'.

Represent each port discretely and align with docker-compose to prevent UI confusion and drift.

Apply the minimal disambiguation now:

     'speaker-recognition': {
         'path': 'extras/speaker-recognition', 
         'compose_file': 'docker-compose.yml',
         'description': 'Speaker Recognition Service',
-        'ports': ['8085', '5174/8444']
+        'ports': ['8085', '5174', '8444']
     },

Verify all SERVICES ports match their compose files:

#!/bin/bash
# Verify exposed host ports in compose files vs SERVICES table
set -euo pipefail
declare -A MAP=( \
  ["backend"]="backends/advanced/docker-compose.yml" \
  ["speaker-recognition"]="extras/speaker-recognition/docker-compose.yml" \
  ["asr-services"]="extras/asr-services/docker-compose.yml" \
  ["openmemory-mcp"]="extras/openmemory-mcp/docker-compose.yml" \
)
for svc in "${!MAP[@]}"; do
  file="${MAP[$svc]}"
  echo "== $svc :: $file"
  if [[ -f "$file" ]]; then
    rg -nP '^\s*-\s*"?\d{2,5}(:\d{2,5})?"?' "$file" | sed 's/^/  /' || true
  else
    echo "  (compose file missing)"
  fi
done

45-55: Remove redundant condition in configuration check.

Both branches return the same expression.

 def check_service_configured(service_name):
     """Check if service is configured (has .env file)"""
     service = SERVICES[service_name]
     service_path = Path(service['path'])
-    
-    # Backend uses advanced init, others use .env
-    if service_name == 'backend':
-        return (service_path / '.env').exists()
-    else:
-        return (service_path / '.env').exists()
+    return (service_path / '.env').exists()

167-169: Narrow exception handling (Ruff BLE001) and surface actionable errors.

Handle expected subprocess/file errors; don’t swallow interrupts.

-    except Exception as e:
-        console.print(f"[red]❌ Error running command: {e}[/red]")
-        return False
+    except FileNotFoundError:
+        console.print("[red]❌ 'docker' not found. Please install Docker Desktop/Engine and ensure it is in PATH.[/red]")
+        return False
+    except PermissionError as e:
+        console.print(f"[red]❌ Permission error running command: {e}[/red]")
+        return False
+    except subprocess.SubprocessError as e:
+        console.print(f"[red]❌ Docker compose error: {e}[/red]")
+        return False
+    except KeyboardInterrupt:
+        raise
+    except Exception as e:
+        console.print(f"[red]❌ Unexpected error: {e}[/red]")
+        raise
🧹 Nitpick comments (4)
services.py (4)

65-76: Pass compose file explicitly to docker compose.

More robust than relying on cwd; avoids surprises if filenames change.

-    cmd = ['docker', 'compose']
+    cmd = ['docker', 'compose', '-f', str(compose_file)]
     if command == 'up':

86-93: Ruff S603: subprocess usage is safe; annotate or further constrain.

Inputs are whitelisted and shell=False; add noqa to silence S603 (or keep and enforce via policy).

-            process = subprocess.Popen(
+            process = subprocess.Popen(  # noqa: S603
                 cmd,
                 cwd=service_path,
                 stdout=subprocess.PIPE,
                 stderr=subprocess.STDOUT,
                 text=True,
                 bufsize=1
             )
-            result = subprocess.run(
+            result = subprocess.run(  # noqa: S603
                 cmd,
                 cwd=service_path,
                 capture_output=True,
                 text=True,
                 check=False
             )

Also applies to: 148-154


110-121: Fix misleading comment (“last 20 lines”).

Code prints the first 20 lines, not the last. Adjust the comment or the logic.

-                # Show only last 20 lines worth of output
+                # Show only the first 20 lines of output

237-256: Expose a 'restart' subcommand to match implemented support.

You already implement 'restart' in run_compose_command; wire it into the CLI.

     subparsers = parser.add_subparsers(dest='command', help='Available commands')
@@
     # Stop command  
     stop_parser = subparsers.add_parser('stop', help='Stop services')
@@
     stop_parser.add_argument('--all', action='store_true', help='Stop all services')
+
+    # Restart command
+    restart_parser = subparsers.add_parser('restart', help='Restart services')
+    restart_parser.add_argument('services', nargs='*',
+                                help='Services to restart (or use --all)')
+    restart_parser.add_argument('--all', action='store_true', help='Restart all services')
@@
     elif args.command == 'start':
@@
         start_services(services, args.build)
-        
+    
     elif args.command == 'stop':
@@
         stop_services(services)
+
+    elif args.command == 'restart':
+        if args.all:
+            services = list(SERVICES.keys())
+        elif args.services:
+            invalid_services = [s for s in args.services if s not in SERVICES]
+            if invalid_services:
+                console.print(f"[red]❌ Invalid service names: {', '.join(invalid_services)}[/red]")
+                console.print(f"Available services: {', '.join(SERVICES.keys())}")
+                return
+            services = args.services
+        else:
+            console.print("[red]❌ No services specified. Use --all or specify service names.[/red]")
+            return
+        for s in services:
+            console.print(f"\n🔧 Restarting {s}...")
+            if run_compose_command(s, 'restart'):
+                console.print(f"[green]✅ {s} restarted[/green]")
+            else:
+                console.print(f"[red]❌ Failed to restart {s}[/red]")

Also applies to: 266-299

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 165d2f1 and 8904def.

📒 Files selected for processing (1)
  • services.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Format Python code with Black using a 100-character line length
Use isort to organize Python imports
Never import modules in the middle of functions or files; all imports must be at the top of the Python file after the docstring
Group Python imports in the order: standard library, third-party, then local imports
Use lazy imports sparingly and only when absolutely necessary to resolve circular imports

Files:

  • services.py
🧬 Code graph analysis (1)
services.py (1)
backends/advanced/init.py (2)
  • run (451-494)
  • main (497-508)
🪛 Ruff (0.12.2)
services.py

86-86: subprocess call: check for execution of untrusted input

(S603)


100-100: Use of assert detected

(S101)


148-148: subprocess call: check for execution of untrusted input

(S603)


159-159: f-string without any placeholders

Remove extraneous f prefix

(F541)


167-167: Do not catch blind exception: Exception

(BLE001)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backends/advanced/Docs/architecture.md (1)

742-745: Fix diagram: WebUI should not talk directly to MongoDB or the filesystem

The flowchart shows the React WebUI calling MongoDB and AudioFiles directly, bypassing the backend/proxy. This is a security and architecture mismatch with the rest of the doc (nginx + backend as the gateway).

Apply this diff to correct the data flow:

-    WebUI -->|🔐 Cookie/JWT auth<br/>🕐 1hr lifetime| AuthGW
-    WebUI -->|📊 API calls| MongoDB
-    WebUI -->|🎵 Audio playback| AudioFiles
+    WebUI -->|🔐 Cookie/JWT auth<br/>🕐 1hr lifetime| AuthGW
+    WebUI -->|📊 API calls| Backend
+    WebUI -->|🎵 GET /api/audio| Backend
+    Backend -->|DB queries| MongoDB
+    Backend -->|serve files| AudioFiles
♻️ Duplicate comments (20)
backends/advanced/Docs/architecture.md (1)

582-612: React migration looks consistent across diagrams and sections

Nice job replacing Streamlit with the React WebUI: dev port 5173, nginx in prod, and compose arrows (WebBrowser → 5173 in dev, → nginx:80 in prod) line up. This addresses the prior Streamlit inconsistency.

Also applies to: 623-630, 648-648

backends/advanced/src/advanced_omi_backend/memory/providers/vector_stores.py (2)

150-201: Guard logging when result.score is None to avoid TypeError.

Formatting {result.score:.3f} crashes on None. Log safely.

-                # Log similarity scores for debugging
-                memory_logger.debug(f"Retrieved memory with score {result.score:.3f}: {result.payload.get('content', '')[:50]}...")
+                # Log similarity scores for debugging (safe for None)
+                score_display = f"{result.score:.3f}" if result.score is not None else "n/a"
+                memory_logger.debug(
+                    f"Retrieved memory with score {score_display}: "
+                    f"{result.payload.get('content', '')[:50]}..."
+                )

355-379: Align return type to Optional[int], improve error handling, and consider approximate count.

Base now returns Optional[int]; this impl should match and return None on failure. Also prefer logging.exception for stack traces and pass exact=False if approximate counts are OK.

-    async def count_memories(self, user_id: str) -> int:
-        """Count total number of memories for a user in Qdrant using native count API."""
+    async def count_memories(self, user_id: str) -> Optional[int]:
+        """Count total number of memories for a user in Qdrant using native count API.
+        
+        Returns None on failure to distinguish from an empty set (0).
+        """
         try:
-            
             search_filter = Filter(
                 must=[
                     FieldCondition(
                         key="metadata.user_id", 
                         match=MatchValue(value=user_id)
                     )
                 ]
             )
             
-            # Use Qdrant's native count API (documented in qdrant/qdrant/docs)
-            # Count operation: CountPoints -> CountResponse with count result
+            # Native count API (CountPoints -> CountResponse.count)
             result = await self.client.count(
                 collection_name=self.collection_name,
-                count_filter=search_filter
+                count_filter=search_filter,
+                exact=False  # set True if exact counts are required
             )
-            
-            return result.count
-            
-        except Exception as e:
-            memory_logger.error(f"Qdrant count memories failed: {e}")
-            return 0
+            return int(result.count) if result and hasattr(result, "count") else 0
+        except Exception:
+            memory_logger.exception("Qdrant count memories failed")
+            return None
backends/advanced/src/advanced_omi_backend/controllers/system_controller.py (1)

981-1015: Use real user_id and move import to module top.

  • Don’t hardcode "1". Pass user.user_id. This was flagged earlier too.
  • Follow repo guideline: avoid in-function imports; move to top with other local imports.
-        from advanced_omi_backend.speaker_recognition_client import SpeakerRecognitionClient
@@
-        # Get enrolled speakers - using hardcoded user_id=1 for now (as noted in speaker_recognition_client.py)
-        speakers = await speaker_client.get_enrolled_speakers(user_id="1")
+        # Get enrolled speakers for the current user
+        speakers = await speaker_client.get_enrolled_speakers(user_id=user.user_id)
@@
-    except Exception as e:
-        logger.error(f"Error getting enrolled speakers for user {user.user_id}: {e}")
+    except Exception:
+        logger.exception("Error getting enrolled speakers for user %s", user.user_id)
         return {

Add at file top (grouped with local imports):

from advanced_omi_backend.speaker_recognition_client import SpeakerRecognitionClient
backends/advanced/src/advanced_omi_backend/main.py (1)

969-1013: Nice: memory test now has a real timeout.

This addresses the prior hang concern with asyncio.wait_for and clarifies the status message.

backends/advanced/SETUP_SCRIPTS.md (2)

25-29: Call the root orchestrator (init.py at repo root), not the backend script.

The table already says init.py lives at the repo root; align the Usage block accordingly.

 ```bash
-# From repository root
-python backends/advanced/init.py
+# From repository root
+uv run python init.py

---

`124-128`: **Update the “New Users” flow to use the root init.py and path-qualify HTTPS.**

Running the backend’s init directly contradicts the new orchestrator flow; also make HTTPS invocation path-agnostic.



```diff
-1. **Run main setup**: `python backends/advanced/init.py`
+1. **Run main setup**: `uv run python init.py`
 2. **Start services**: `docker compose up --build -d`
-3. **Optional HTTPS**: `./setup-https.sh your-ip` (if needed)
+3. **Optional HTTPS**: `backends/advanced/setup-https.sh your-ip` (if needed)
init.py (2)

95-99: Move mid-function import to the top-level import block.

Violates repo import policy; import datetime at the top with stdlib group.

@@
-import subprocess
-import sys
-from pathlib import Path
+import subprocess
+import sys
+from datetime import datetime
+from pathlib import Path
@@
-def cleanup_unselected_services(selected_services):
+def cleanup_unselected_services(selected_services):
     """Backup and remove .env files from services that weren't selected"""
-    from datetime import datetime
-    
+    

148-161: subprocess.run lacks timeout and output capture; CalledProcessError path is unreachable.

Add timeout, check=True, capture output, and print stdout/stderr on failure/timeouts.

     try:
-        result = subprocess.run(
-            cmd, 
-            cwd=service['path'],
-            check=False
-        )
-        
-        if result.returncode == 0:
-            console.print(f"✅ {service_name} setup completed")
-            return True
-        else:
-            console.print(f"❌ {service_name} setup failed (exit code: {result.returncode})")
-            return False
+        result = subprocess.run(
+            cmd,
+            cwd=service['path'],
+            check=True,
+            timeout=1800,
+            capture_output=True,
+            text=True,
+        )
+        console.print(f"✅ {service_name} setup completed")
+        if result.stdout:
+            console.print(f"[dim]{result.stdout.strip()}[/dim]")
+        return True
@@
-    except subprocess.TimeoutExpired as e:
-        console.print(f"❌ {service_name} setup timed out after {e.timeout} seconds")
-        if e.stdout:
-            console.print(f"   stdout: {e.stdout.decode()}")
-        if e.stderr:
-            console.print(f"   stderr: {e.stderr.decode()}")
+    except subprocess.TimeoutExpired as e:
+        console.print(f"❌ {service_name} setup timed out after {e.timeout}s")
+        if e.stdout:
+            console.print(f"   stdout: {e.stdout.strip()}")
+        if e.stderr:
+            console.print(f"   stderr: {e.stderr.strip()}")
         return False
-    except subprocess.CalledProcessError as e:
-        console.print(f"❌ {service_name} setup failed with exit code {e.returncode}")
-        if e.stdout:
-            console.print(f"   stdout: {e.stdout.decode()}")
-        if e.stderr:
-            console.print(f"   stderr: {e.stderr.decode()}")
+    except subprocess.CalledProcessError as e:
+        console.print(f"❌ {service_name} setup failed with exit code {e.returncode}")
+        if e.stdout:
+            console.print(f"   stdout: {e.stdout.strip()}")
+        if e.stderr:
+            console.print(f"   stderr: {e.stderr.strip()}")
         return False

Also applies to: 165-178

backends/advanced/init.py (11)

1-1: Remove shebang or mark file executable.

Either remove the shebang or chmod +x backends/advanced/init.py to satisfy EXE001. Prefer removing since you invoke via python.

-#!/usr/bin/env python3

7-23: Clean imports: add re, drop unused rprint; keep top-level grouping.

rprint is unused; re is needed for server name validation.

 import argparse
 import getpass
 import os
+import re
 import secrets
 import shutil
 import subprocess
 import sys
 from datetime import datetime
 from pathlib import Path
 from typing import Any, Dict, Optional
 
-from rich import print as rprint
 from rich.console import Console
 from rich.panel import Panel
 from rich.prompt import Confirm, Prompt
 from rich.text import Text

279-281: Harden CLI HTTPS defaults when --enable-https is set.

Ensure non-None server_ip.

-            server_ip = getattr(self.args, 'server_ip', 'localhost')
+            server_ip = getattr(self.args, 'server_ip', None) or 'localhost'

308-308: Invoke SSL script via bash; keep arg vector to satisfy S603.

Avoid relying on executable bit and keep no-shell invocation.

-                    subprocess.run([str(ssl_script), server_ip], check=True, cwd=str(script_dir))
+                    subprocess.run(["bash", str(ssl_script), server_ip], check=True, cwd=str(script_dir))

128-139: Gate Deepgram provider on presence of API key.

Avoid misconfiguration when no key is provided.

-        if choice == "1":
-            self.config["TRANSCRIPTION_PROVIDER"] = "deepgram"
+        if choice == "1":
             self.console.print("[blue][INFO][/blue] Deepgram selected")
             self.console.print("Get your API key from: https://console.deepgram.com/")
             
             api_key = self.prompt_value("Deepgram API key (leave empty to skip)", "")
             if api_key:
+                self.config["TRANSCRIPTION_PROVIDER"] = "deepgram"
                 self.config["DEEPGRAM_API_KEY"] = api_key
                 self.console.print("[green][SUCCESS][/green] Deepgram configured")
             else:
-                self.console.print("[yellow][WARNING][/yellow] No API key provided - transcription will not work")
+                self.config.pop("TRANSCRIPTION_PROVIDER", None)
+                self.console.print("[yellow][WARNING][/yellow] No API key provided - skipping transcription setup")

140-154: Apply same gating for Mistral.

Only set provider when a key is present.

-        elif choice == "2":
-            self.config["TRANSCRIPTION_PROVIDER"] = "mistral"
+        elif choice == "2":
             self.console.print("[blue][INFO][/blue] Mistral selected")
             self.console.print("Get your API key from: https://console.mistral.ai/")
             
             api_key = self.prompt_value("Mistral API key (leave empty to skip)", "")
             model = self.prompt_value("Mistral model", "voxtral-mini-2507")
             
             if api_key:
+                self.config["TRANSCRIPTION_PROVIDER"] = "mistral"
                 self.config["MISTRAL_API_KEY"] = api_key
                 self.config["MISTRAL_MODEL"] = model
                 self.console.print("[green][SUCCESS][/green] Mistral configured")
             else:
-                self.console.print("[yellow][WARNING][/yellow] No API key provided - transcription will not work")
+                self.config.pop("TRANSCRIPTION_PROVIDER", None)
+                self.console.print("[yellow][WARNING][/yellow] No API key provided - skipping transcription setup")

177-193: Gate OpenAI LLM provider on API key and clear stray OPENAI_ on skip.*

Pre-setting provider without a key leaves a broken config.

-        if choice == "1":
-            self.config["LLM_PROVIDER"] = "openai"
+        if choice == "1":
             self.console.print("[blue][INFO][/blue] OpenAI selected")
             self.console.print("Get your API key from: https://platform.openai.com/api-keys")
             
             api_key = self.prompt_value("OpenAI API key (leave empty to skip)", "")
             model = self.prompt_value("OpenAI model", "gpt-4o-mini")
             base_url = self.prompt_value("OpenAI base URL (for proxies/compatible APIs)", "https://api.openai.com/v1")
             
             if api_key:
+                self.config["LLM_PROVIDER"] = "openai"
                 self.config["OPENAI_API_KEY"] = api_key
                 self.config["OPENAI_MODEL"] = model
                 self.config["OPENAI_BASE_URL"] = base_url
                 self.console.print("[green][SUCCESS][/green] OpenAI configured")
             else:
-                self.console.print("[yellow][WARNING][/yellow] No API key provided - memory extraction will not work")
+                self.config.pop("LLM_PROVIDER", None)
+                self.config.pop("OPENAI_API_KEY", None)
+                self.config.pop("OPENAI_MODEL", None)
+                self.config.pop("OPENAI_BASE_URL", None)
+                self.console.print("[yellow][WARNING][/yellow] No API key provided - skipping LLM setup")

257-261: Fix default Speaker Recognition port (8085 expected).

Align default with the rest of the repo to avoid OOTB failure.

-                speaker_url = self.prompt_value("Speaker Recognition service URL", "http://host.docker.internal:8001")
+                speaker_url = self.prompt_value("Speaker Recognition service URL", "http://host.docker.internal:8085")

298-305: Validate server name/IP before invoking SSL tooling.

Minimal allowlist prevents bad inputs.

         if enable_https:
-            
+            # Basic hostname/IP validation (letters, digits, dots, dashes)
+            if not re.fullmatch(r"[A-Za-z0-9.\-]+", server_ip):
+                self.console.print(f"[red]Invalid server name/IP: {server_ip}[/red]")
+                return
             # Generate SSL certificates
             self.console.print("[blue][INFO][/blue] Generating SSL certificates...")
             # Use path relative to this script's directory
             script_dir = Path(__file__).parent
             ssl_script = script_dir / "ssl" / "generate-ssl.sh"

372-379: Persist HTTPS settings to .env.

Write HTTPS_ENABLED and SERVER_IP so downstream services can consume them.

 # Optional Services
 SPEAKER_SERVICE_URL={self.config.get('SPEAKER_SERVICE_URL', '')}
 PARAKEET_ASR_URL={self.config.get('PARAKEET_ASR_URL', '')}
 
+# HTTPS
+HTTPS_ENABLED={self.config.get('HTTPS_ENABLED', 'false')}
+SERVER_IP="{self.config.get('SERVER_IP', '').replace('\\', '\\\\').replace('"', '\\"')}"
+
 # Network Configuration
 BACKEND_PUBLIC_PORT={self.config.get('BACKEND_PUBLIC_PORT', '8000')}
 WEBUI_PORT={self.config.get('WEBUI_PORT', '5173')}

347-365: Quote secrets/URLs to avoid dotenv parsing issues (spaces, #, special chars).

Wrap values in quotes with escaping.

 # Authentication
-AUTH_SECRET_KEY={self.config.get('AUTH_SECRET_KEY', '')}
-ADMIN_EMAIL={self.config.get('ADMIN_EMAIL', '')}
-ADMIN_PASSWORD={self.config.get('ADMIN_PASSWORD', '')}
+AUTH_SECRET_KEY="{self.config.get('AUTH_SECRET_KEY', '').replace('\\', '\\\\').replace('"', '\\"')}"
+ADMIN_EMAIL="{self.config.get('ADMIN_EMAIL', '').replace('\\', '\\\\').replace('"', '\\"')}"
+ADMIN_PASSWORD="{self.config.get('ADMIN_PASSWORD', '').replace('\\', '\\\\').replace('"', '\\"')}"
 
 # Transcription Provider
-TRANSCRIPTION_PROVIDER={self.config.get('TRANSCRIPTION_PROVIDER', '')}
-DEEPGRAM_API_KEY={self.config.get('DEEPGRAM_API_KEY', '')}
-MISTRAL_API_KEY={self.config.get('MISTRAL_API_KEY', '')}
-MISTRAL_MODEL={self.config.get('MISTRAL_MODEL', '')}
+TRANSCRIPTION_PROVIDER="{self.config.get('TRANSCRIPTION_PROVIDER', '').replace('\\', '\\\\').replace('"', '\\"')}"
+DEEPGRAM_API_KEY="{self.config.get('DEEPGRAM_API_KEY', '').replace('\\', '\\\\').replace('"', '\\"')}"
+MISTRAL_API_KEY="{self.config.get('MISTRAL_API_KEY', '').replace('\\', '\\\\').replace('"', '\\"')}"
+MISTRAL_MODEL="{self.config.get('MISTRAL_MODEL', '').replace('\\', '\\\\').replace('"', '\\"')}"
 
 # LLM Provider  
-LLM_PROVIDER={self.config.get('LLM_PROVIDER', '')}
-OPENAI_API_KEY={self.config.get('OPENAI_API_KEY', '')}
-OPENAI_MODEL={self.config.get('OPENAI_MODEL', '')}
-OPENAI_BASE_URL={self.config.get('OPENAI_BASE_URL', '')}
-OLLAMA_BASE_URL={self.config.get('OLLAMA_BASE_URL', '')}
-OLLAMA_MODEL={self.config.get('OLLAMA_MODEL', '')}
+LLM_PROVIDER="{self.config.get('LLM_PROVIDER', '').replace('\\', '\\\\').replace('"', '\\"')}"
+OPENAI_API_KEY="{self.config.get('OPENAI_API_KEY', '').replace('\\', '\\\\').replace('"', '\\"')}"
+OPENAI_MODEL="{self.config.get('OPENAI_MODEL', '').replace('\\', '\\\\').replace('"', '\\"')}"
+OPENAI_BASE_URL="{self.config.get('OPENAI_BASE_URL', '').replace('\\', '\\\\').replace('"', '\\"')}"
+OLLAMA_BASE_URL="{self.config.get('OLLAMA_BASE_URL', '').replace('\\', '\\\\').replace('"', '\\"')}"
+OLLAMA_MODEL="{self.config.get('OLLAMA_MODEL', '').replace('\\', '\\\\').replace('"', '\\"')}"
 # Memory Provider
-MEMORY_PROVIDER={self.config.get('MEMORY_PROVIDER', 'friend_lite')}
-QDRANT_BASE_URL={self.config.get('QDRANT_BASE_URL', 'qdrant')}
-OPENMEMORY_MCP_URL={self.config.get('OPENMEMORY_MCP_URL', '')}
-OPENMEMORY_CLIENT_NAME={self.config.get('OPENMEMORY_CLIENT_NAME', '')}
-OPENMEMORY_USER_ID={self.config.get('OPENMEMORY_USER_ID', '')}
+MEMORY_PROVIDER="{self.config.get('MEMORY_PROVIDER', 'friend_lite').replace('\\', '\\\\').replace('"', '\\"')}"
+QDRANT_BASE_URL="{self.config.get('QDRANT_BASE_URL', 'qdrant').replace('\\', '\\\\').replace('"', '\\"')}"
+OPENMEMORY_MCP_URL="{self.config.get('OPENMEMORY_MCP_URL', '').replace('\\', '\\\\').replace('"', '\\"')}"
+OPENMEMORY_CLIENT_NAME="{self.config.get('OPENMEMORY_CLIENT_NAME', '').replace('\\', '\\\\').replace('"', '\\"')}"
+OPENMEMORY_USER_ID="{self.config.get('OPENMEMORY_USER_ID', '').replace('\\', '\\\\').replace('"', '\\"')}"
🧹 Nitpick comments (24)
backends/advanced/Docs/architecture.md (3)

623-631: Tighten wording and version pinning in “React WebUI” and “Infrastructure”

Minor clarity/consistency nits and reproducibility:

  • Keep bullet phrasing parallel and drop redundant words.
  • Avoid “Latest” for container versions; pin tags to a known version.

Proposed edits:

-#### React WebUI Container (`webui`)
-- **Purpose**: Modern React-based web dashboard interface
-- **Dependencies**: React, TypeScript, Vite for development, Nginx for production
-- **Backend Integration**: HTTP API client with JWT authentication
-- **Configuration**: Backend URL configuration for API calls
-- **Development**: Vite dev server on port 5173
-- **Production**: Nginx serving built React app on port 80
+#### React WebUI Container (`webui`)
+- **Purpose**: React/TypeScript web dashboard
+- **Stack**: React, TypeScript, Vite (dev), Nginx (prod)
+- **Auth**: JWT-backed API client
+- **Config**: Backend URL for API calls
+- **Dev**: Vite server on port 5173
+- **Prod**: Nginx serves build on port 80

-**Qdrant Latest**: Vector database for memory embeddings
+**Qdrant 1.11.x**: Vector database for memory embeddings

If you prefer different exact versions, pin to what’s used in compose to avoid drift.

Also applies to: 636-636


198-199: HTTPS note: clarify dev vs prod

You mention “WebSocket streaming (HTTPS)”. Consider noting: HTTPS via nginx in prod; http://localhost:5173 during dev.

-**Live Recording**: Real-time audio recording with WebSocket streaming (HTTPS)
+**Live Recording**: Real-time audio recording with WebSocket streaming (HTTPS via nginx in prod; http://localhost:5173 in dev)

458-459: UI trigger reference: link or route for the “Close conversation” action

Add a short pointer (button label or route) so readers can find the control in the React UI.

-   - UI button in React dashboard
+   - UI button in React dashboard (Memories → Active Session → “Close Conversation”)
backends/advanced/src/advanced_omi_backend/memory/base.py (1)

138-151: Good change to Optional[int]; silence the unused-arg warning in the default.

Ruff flags user_id as unused in the base default. Add a one-liner to make that explicit.

 async def count_memories(self, user_id: str) -> Optional[int]:
@@
-        return None
+        del user_id  # clarify unused in default implementation
+        return None
backends/advanced/src/advanced_omi_backend/processors.py (2)

163-233: Harden speaker matching to avoid KeyError and normalize “Unknown” robustly.

Guard against missing name fields and case/whitespace variants; otherwise a KeyError drops into the broad except and unintentionally allows processing.

-            for segment in chunk['transcript']:
+            for segment in chunk["transcript"]:
                 total_segments += 1
-                if 'identified_as' in segment and segment['identified_as'] and segment['identified_as'] != 'Unknown':
-                    original_name = segment['identified_as']
-                    normalized_name = original_name.strip().lower()
+                name_raw = str(segment.get("identified_as", "")).strip()
+                if name_raw and name_raw.lower() not in {"unknown", "unk"}:
+                    original_name = name_raw
+                    normalized_name = name_raw.lower()
                     transcript_speakers.add(normalized_name)
                     transcript_speaker_originals[normalized_name] = original_name
                     identified_segments += 1
@@
-            primary_speaker_names = {ps['name'].strip().lower() for ps in user.primary_speakers}
-            primary_speaker_originals = {ps['name'].strip().lower(): ps['name'] for ps in user.primary_speakers}
+            primary_speaker_names = {
+                str(ps.get("name", "")).strip().lower() for ps in (user.primary_speakers or [])
+            }
+            primary_speaker_originals = {
+                str(ps.get("name", "")).strip().lower(): str(ps.get("name", ""))
+                for ps in (user.primary_speakers or [])
+            }
@@
-        except Exception as e:
+        except Exception as e:
             # On any error, default to processing (fail-safe)
-            audio_logger.warning(f"Error checking primary speakers filter for {audio_uuid}: {e} - defaulting to process conversation")
+            audio_logger.warning(
+                f"Error checking primary speakers filter for {audio_uuid}: {e} - defaulting to process conversation",
+                exc_info=True,
+            )
             return True, f"Error in speaker filtering: {str(e)} - processing conversation as fallback"

951-975: Consistently update memory status via one repository.

Here you use AudioChunksRepository.update_memory_processing_status, elsewhere conversation_repo.update_memory_processing_status. Pick one path for consistency/observability and to avoid divergent schemas (especially for error_message).

If conversation_repo is the new canonical layer, switch this call accordingly. Otherwise, align the other sites.

backends/advanced/src/advanced_omi_backend/controllers/system_controller.py (3)

35-43: Metrics enrichment looks good; consistent provider normalization.

Nice addition. Keep this lowercase normalization consistent with main.py (see comment there).


924-937: Use logger.exception for stack traces on failures.

Return is fine; switch to logger.exception for better diagnostics.

-    except Exception as e:
-        logger.error(f"Error getting speaker configuration for user {user.user_id}: {e}")
+    except Exception:
+        logger.exception("Error getting speaker configuration for user %s", user.user_id)

1017-1060: Move import to top and log exceptions with stack traces.

Keep imports at module level per coding guidelines, and use logger.exception.

-        from advanced_omi_backend.speaker_recognition_client import SpeakerRecognitionClient
@@
-    except Exception as e:
-        logger.error(f"Error checking speaker service status: {e}")
+    except Exception:
+        logger.exception("Error checking speaker service status")
         return {

Add at file top with local imports:

from advanced_omi_backend.speaker_recognition_client import SpeakerRecognitionClient
backends/advanced/src/advanced_omi_backend/main.py (2)

910-914: Normalize memory provider casing once.

Lowercase here to match comparisons and the controller metrics function.

-    memory_provider = os.getenv("MEMORY_PROVIDER", "friend_lite")
+    memory_provider = os.getenv("MEMORY_PROVIDER", "friend_lite").lower()
     speaker_service_url = os.getenv("SPEAKER_SERVICE_URL")
     openmemory_mcp_url = os.getenv("OPENMEMORY_MCP_URL")

1101-1144: OpenMemory MCP health: consider a more permissive probe or fallback.

/docs may not be present in some deployments. Optionally try a small set of endpoints (/health, /, maybe a trivial API call) and accept 200/404/422 as “reachable,” mirroring mcp_client.test_connection().

I can wire a small helper to probe multiple endpoints with a 5s timeout if you want.

backends/advanced/SETUP_SCRIPTS.md (2)

31-32: Add language identifiers to fenced blocks (MD040).

-```
+```text

Apply to both the “Example Flow” and “Script Locations” blocks.

Also applies to: 137-144


16-16: Remove trailing colons from headings (MD026).

Minor markdownlint cleanup; drop the ending “:” or disable MD026 locally.

Also applies to: 25-25, 31-31, 94-94, 99-99, 105-105, 111-111, 124-124, 129-129

init.py (3)

263-263: Remove f-strings without placeholders (Ruff F541).

-    console.print(f"\n🎊 [bold green]Setup Complete![/bold green]")
+    console.print("\n🎊 [bold green]Setup Complete![/bold green]")
@@
-    console.print(f"\n🚀 [bold]Enjoy Friend-Lite![/bold]")
+    console.print("\n🚀 [bold]Enjoy Friend-Lite![/bold]")
@@
-    console.print(f"\n💡 [dim]Tip: You can also setup services individually:[/dim]")
-    console.print(f"[dim]   cd backends/advanced && uv run --with-requirements setup-requirements.txt python init.py[/dim]")
+    console.print("\n💡 [dim]Tip: You can also setup services individually:[/dim]")
+    console.print("[dim]   cd backends/advanced && uv run --with-requirements setup-requirements.txt python init.py[/dim]")

Also applies to: 300-300, 303-304


116-131: Centralize service ports/URLs to avoid future drift.

Define constants or a small map (e.g., SPEAKER_PORT = 8001) and reuse in both docs and scripts.


1-24: Imports ordering and policy check.

After moving datetime, run isort/Black (100-char line) to satisfy repo guidelines.

backends/advanced/setup-https.sh (4)

61-63: Use read -r to avoid backslash escapes; keep IFS intact.

-    read -p "$prompt" response
+    IFS= read -r -p "$prompt" response
@@
-        read -p "IP Address: " TAILSCALE_IP
+        IFS= read -r -p "IP Address: " TAILSCALE_IP

Also applies to: 153-154


156-156: Invoke init-https.sh via bash to avoid execute-bit issues.

-            ./init-https.sh "$TAILSCALE_IP"
+            bash ./init-https.sh "$TAILSCALE_IP"

174-182: Ensure python3 is available before using it.

-# Helper function to update or add environment variable in .env file
+# Require python3 for env updates
+command -v python3 >/dev/null 2>&1 || { print_error "python3 is required for .env updates"; exit 1; }
+# Helper function to update or add environment variable in .env file

1-312: Optional: Centralize defaults (ports/URLs) to a single source.

Reduce drift between docs, init.py, and this script.

backends/advanced/init.py (4)

333-334: Narrow exception type for nginx.conf generation.

Catching Exception masks unrelated failures.

-                except Exception as e:
+                except OSError as e:
                     self.console.print(f"[yellow][WARNING][/yellow] nginx.conf generation failed: {e}")

372-375: Quote optional service URLs in .env.

Maintain consistency and robustness.

 # Optional Services
-SPEAKER_SERVICE_URL={self.config.get('SPEAKER_SERVICE_URL', '')}
-PARAKEET_ASR_URL={self.config.get('PARAKEET_ASR_URL', '')}
+SPEAKER_SERVICE_URL="{self.config.get('SPEAKER_SERVICE_URL', '').replace('\\', '\\\\').replace('"', '\\"')}"
+PARAKEET_ASR_URL="{self.config.get('PARAKEET_ASR_URL', '').replace('\\', '\\\\').replace('"', '\\"')}"

506-509: Avoid catching broad Exception at the top level.

Limit to expected error classes to satisfy BLE001 and avoid masking bugs.

-        except Exception as e:
+        except (OSError, subprocess.CalledProcessError, ValueError) as e:
             self.console.print(f"[red][ERROR][/red] Setup failed: {e}")
             sys.exit(1)

272-274: Optional: validate port inputs are 1–65535.

Prevents accidental bad configs. I can provide a helper if desired.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 69d5524 and 939c2a1.

📒 Files selected for processing (12)
  • backends/advanced/Docs/architecture.md (7 hunks)
  • backends/advanced/SETUP_SCRIPTS.md (1 hunks)
  • backends/advanced/init.py (1 hunks)
  • backends/advanced/setup-https.sh (1 hunks)
  • backends/advanced/src/advanced_omi_backend/controllers/memory_controller.py (3 hunks)
  • backends/advanced/src/advanced_omi_backend/controllers/system_controller.py (3 hunks)
  • backends/advanced/src/advanced_omi_backend/main.py (3 hunks)
  • backends/advanced/src/advanced_omi_backend/memory/base.py (4 hunks)
  • backends/advanced/src/advanced_omi_backend/memory/providers/vector_stores.py (4 hunks)
  • backends/advanced/src/advanced_omi_backend/processors.py (3 hunks)
  • extras/speaker-recognition/setup.sh (1 hunks)
  • init.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • extras/speaker-recognition/setup.sh
  • backends/advanced/src/advanced_omi_backend/controllers/memory_controller.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Format Python code with Black using a 100-character line length
Use isort to organize Python imports
Never import modules in the middle of functions or files; all imports must be at the top of the Python file after the docstring
Group Python imports in the order: standard library, third-party, then local imports
Use lazy imports sparingly and only when absolutely necessary to resolve circular imports

Files:

  • backends/advanced/src/advanced_omi_backend/processors.py
  • backends/advanced/src/advanced_omi_backend/memory/providers/vector_stores.py
  • backends/advanced/src/advanced_omi_backend/memory/base.py
  • backends/advanced/src/advanced_omi_backend/main.py
  • backends/advanced/init.py
  • backends/advanced/src/advanced_omi_backend/controllers/system_controller.py
  • init.py
🧬 Code graph analysis (8)
backends/advanced/src/advanced_omi_backend/processors.py (2)
backends/advanced/src/advanced_omi_backend/users.py (2)
  • get_user_by_id (88-95)
  • user_id (45-47)
backends/advanced/src/advanced_omi_backend/database.py (2)
  • get_chunk_by_audio_uuid (145-147)
  • update_memory_processing_status (230-246)
backends/advanced/src/advanced_omi_backend/memory/providers/vector_stores.py (5)
backends/advanced/src/advanced_omi_backend/memory/base.py (6)
  • MemoryEntry (26-50)
  • VectorStoreBase (263-393)
  • search_memories (101-119)
  • search_memories (294-312)
  • count_memories (138-150)
  • count_memories (327-339)
backends/advanced/src/advanced_omi_backend/controllers/memory_controller.py (1)
  • search_memories (77-101)
backends/advanced/src/advanced_omi_backend/memory/compat_service.py (2)
  • search_memories (164-191)
  • count_memories (137-150)
backends/advanced/src/advanced_omi_backend/routers/modules/memory_routes.py (1)
  • search_memories (42-50)
backends/advanced/src/advanced_omi_backend/memory/memory_service.py (2)
  • search_memories (215-250)
  • count_memories (276-296)
backends/advanced/setup-https.sh (1)
backends/advanced/init.py (1)
  • print_header (36-45)
backends/advanced/src/advanced_omi_backend/memory/base.py (3)
backends/advanced/src/advanced_omi_backend/memory/providers/vector_stores.py (1)
  • count_memories (355-379)
backends/advanced/src/advanced_omi_backend/memory/compat_service.py (1)
  • count_memories (137-150)
backends/advanced/src/advanced_omi_backend/memory/memory_service.py (1)
  • count_memories (276-296)
backends/advanced/src/advanced_omi_backend/main.py (6)
backends/advanced/src/advanced_omi_backend/memory/base.py (3)
  • test_connection (177-183)
  • test_connection (254-260)
  • test_connection (387-393)
backends/advanced/src/advanced_omi_backend/memory/providers/vector_stores.py (1)
  • test_connection (298-308)
backends/advanced/src/advanced_omi_backend/memory/compat_service.py (1)
  • test_connection (359-371)
backends/advanced/src/advanced_omi_backend/memory/providers/openmemory_mcp_service.py (1)
  • test_connection (330-342)
backends/advanced/src/advanced_omi_backend/memory/memory_service.py (1)
  • test_connection (339-351)
backends/advanced/src/advanced_omi_backend/memory/providers/mcp_client.py (1)
  • test_connection (331-356)
backends/advanced/init.py (3)
backends/advanced/setup-https.sh (1)
  • print_header (31-37)
init.py (1)
  • main (198-304)
services.py (1)
  • main (219-280)
backends/advanced/src/advanced_omi_backend/controllers/system_controller.py (4)
backends/advanced/src/advanced_omi_backend/routers/modules/system_routes.py (4)
  • get_speaker_configuration (107-109)
  • update_speaker_configuration (113-118)
  • get_enrolled_speakers (122-124)
  • get_speaker_service_status (128-130)
backends/advanced/src/advanced_omi_backend/users.py (2)
  • User (35-80)
  • user_id (45-47)
backends/advanced/src/advanced_omi_backend/speaker_recognition_client.py (3)
  • get_enrolled_speakers (323-361)
  • SpeakerRecognitionClient (19-400)
  • health_check (363-400)
backends/advanced/src/advanced_omi_backend/main.py (1)
  • health_check (874-1181)
init.py (1)
backends/advanced/init.py (2)
  • run (465-508)
  • main (511-526)
🪛 Ruff (0.12.2)
backends/advanced/src/advanced_omi_backend/processors.py

229-229: Do not catch blind exception: Exception

(BLE001)


232-232: Use explicit conversion flag

Replace with conversion flag

(RUF010)

backends/advanced/src/advanced_omi_backend/memory/providers/vector_stores.py

375-375: Consider moving this statement to an else block

(TRY300)


377-377: Do not catch blind exception: Exception

(BLE001)


378-378: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

backends/advanced/src/advanced_omi_backend/memory/base.py

138-138: Unused method argument: user_id

(ARG002)


327-327: Unused method argument: user_id

(ARG002)

backends/advanced/src/advanced_omi_backend/main.py

997-997: Do not catch blind exception: Exception

(BLE001)


999-999: Use explicit conversion flag

Replace with conversion flag

(RUF010)


1135-1135: Do not catch blind exception: Exception

(BLE001)


1137-1137: Use explicit conversion flag

Replace with conversion flag

(RUF010)

backends/advanced/init.py

1-1: Shebang is present but file is not executable

(EXE001)


308-308: subprocess call: check for execution of untrusted input

(S603)


333-333: Do not catch blind exception: Exception

(BLE001)


506-506: Do not catch blind exception: Exception

(BLE001)

backends/advanced/src/advanced_omi_backend/controllers/system_controller.py

932-932: Do not catch blind exception: Exception

(BLE001)


933-933: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


935-935: Use explicit conversion flag

Replace with conversion flag

(RUF010)


974-974: Do not catch blind exception: Exception

(BLE001)


975-975: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


977-977: Use explicit conversion flag

Replace with conversion flag

(RUF010)


1007-1007: Do not catch blind exception: Exception

(BLE001)


1008-1008: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1012-1012: Use explicit conversion flag

Replace with conversion flag

(RUF010)


1053-1053: Do not catch blind exception: Exception

(BLE001)


1054-1054: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1058-1058: Use explicit conversion flag

Replace with conversion flag

(RUF010)

init.py

149-149: subprocess call: check for execution of untrusted input

(S603)


179-179: Do not catch blind exception: Exception

(BLE001)


263-263: f-string without any placeholders

Remove extraneous f prefix

(F541)


300-300: f-string without any placeholders

Remove extraneous f prefix

(F541)


303-303: f-string without any placeholders

Remove extraneous f prefix

(F541)


304-304: f-string without any placeholders

Remove extraneous f prefix

(F541)

🪛 LanguageTool
backends/advanced/Docs/architecture.md

[grammar] ~623-~623: There might be a mistake here.
Context: ...es #### React WebUI Container (webui) - Purpose: Modern React-based web dashbo...

(QB_NEW_EN)


[grammar] ~624-~624: There might be a mistake here.
Context: ...dern React-based web dashboard interface - Dependencies: React, TypeScript, Vite ...

(QB_NEW_EN)


[grammar] ~625-~625: There might be a mistake here.
Context: ...te for development, Nginx for production - Backend Integration: HTTP API client w...

(QB_NEW_EN)


[grammar] ~626-~626: There might be a mistake here.
Context: ... HTTP API client with JWT authentication - Configuration: Backend URL configurati...

(QB_NEW_EN)


[grammar] ~627-~627: There might be a mistake here.
Context: ... Backend URL configuration for API calls - Development: Vite dev server on port 5...

(QB_NEW_EN)


[grammar] ~628-~628: There might be a mistake here.
Context: ...elopment**: Vite dev server on port 5173 - Production: Nginx serving built React ...

(QB_NEW_EN)


[grammar] ~631-~631: There might be a mistake here.
Context: ... port 80 #### Infrastructure Containers - MongoDB 4.4.18: Primary data storage w...

(QB_NEW_EN)


[grammar] ~632-~632: There might be a mistake here.
Context: ...*: Primary data storage with persistence - Qdrant Latest: Vector database for mem...

(QB_NEW_EN)


[grammar] ~633-~633: There might be a mistake here.
Context: ...*: Vector database for memory embeddings - Neo4j 5.15: Graph database for memory ...

(QB_NEW_EN)


[grammar] ~634-~634: There might be a mistake here.
Context: ...ory relationships and entity connections - Nginx Alpine: Reverse proxy and load b...

(QB_NEW_EN)

backends/advanced/SETUP_SCRIPTS.md

[grammar] ~7-~7: There might be a mistake here.
Context: ...view | Script | Purpose | When to Use | |--------|---------|-------------| | `in...

(QB_NEW_EN)


[grammar] ~8-~8: There might be a mistake here.
Context: ...Use | |--------|---------|-------------| | init.py | **Main interactive setup w...

(QB_NEW_EN)


[grammar] ~9-~9: There might be a mistake here.
Context: ...d configuration (located at repo root) | | setup-https.sh | HTTPS certificate g...

(QB_NEW_EN)


[grammar] ~16-~16: There might be a mistake here.
Context: ... with guided prompts. ### What it does: - ✅ Authentication Setup: Admin email/...

(QB_NEW_EN)


[grammar] ~17-~17: There might be a mistake here.
Context: ...mail/password with secure key generation - ✅ Transcription Provider Selection: ...

(QB_NEW_EN)


[grammar] ~18-~18: There might be a mistake here.
Context: ...Deepgram, Mistral, or Offline (Parakeet) - ✅ LLM Provider Configuration: Choose...

(QB_NEW_EN)


[grammar] ~19-~19: There might be a mistake here.
Context: ...e between OpenAI (recommended) or Ollama - ✅ Memory Provider Setup: Choose betw...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ...een Friend-Lite Native or OpenMemory MCP - ✅ API Key Collection: Prompts for re...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...d keys with helpful links to obtain them - ✅ Optional Services: Speaker Recogni...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...eaker Recognition, network configuration - ✅ Configuration Validation: Creates ...

(QB_NEW_EN)


[grammar] ~94-~94: There might be a mistake here.
Context: ...r secure HTTPS access. ### When needed: - Microphone access from browsers (HTTPS...

(QB_NEW_EN)


[grammar] ~95-~95: There might be a mistake here.
Context: ... access** from browsers (HTTPS required) - Remote access via Tailscale or network...

(QB_NEW_EN)


[grammar] ~96-~96: There might be a mistake here.
Context: ...Remote access** via Tailscale or network - Production deployments requiring secur...

(QB_NEW_EN)


[grammar] ~105-~105: There might be a mistake here.
Context: ...ale or network IP ``` ### What it does: - Generates self-signed SSL certificates f...

(QB_NEW_EN)


[grammar] ~106-~106: There might be a mistake here.
Context: ...self-signed SSL certificates for your IP - Configures nginx proxy for HTTPS access ...

(QB_NEW_EN)


[grammar] ~107-~107: There might be a mistake here.
Context: ... Configures nginx proxy for HTTPS access - Sets up docker-compose HTTPS profile - P...

(QB_NEW_EN)


[grammar] ~108-~108: There might be a mistake here.
Context: ...s - Sets up docker-compose HTTPS profile - Provides HTTPS URLs for dashboard access...

(QB_NEW_EN)


[grammar] ~124-~124: There might be a mistake here.
Context: ...Setup Flow ### New Users (Recommended): 1. Run main setup: `python backends/advan...

(QB_NEW_EN)


[grammar] ~129-~129: There might be a mistake here.
Context: ...d) ### Manual Configuration (Advanced): 1. Copy template: cp .env.template .env...

(QB_NEW_EN)


[grammar] ~130-~130: There might be a mistake here.
Context: ...ation (Advanced): 1. Copy template: cp .env.template .env 2. Edit manually: Configure all providers...

(QB_NEW_EN)


[grammar] ~151-~151: There might be a mistake here.
Context: ...*: Ensure your IP is accessible and not behind firewall ## Key Benefits of New Setup ...

(QB_NEW_EN)


[grammar] ~157-~157: There might be a mistake here.
Context: ...quired keys ✅ Provider selection: Choose best services for your needs ✅ **Comp...

(QB_NEW_EN)


[grammar] ~158-~158: There might be a mistake here.
Context: ...r needs ✅ Complete configuration: Creates working .env with all settings ✅ **Ne...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
backends/advanced/SETUP_SCRIPTS.md

16-16: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


25-25: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


31-31: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


32-32: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


94-94: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


99-99: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


105-105: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


111-111: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


124-124: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


129-129: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)


137-137: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: integration-tests
🔇 Additional comments (4)
backends/advanced/src/advanced_omi_backend/processors.py (1)

22-22: Import at module scope complies with repo rules.

Moving get_user_by_id to the top-level import is correct. LGTM.

init.py (1)

120-131: Executable paths originate from the static SERVICES constant; only flag values (e.g. server_ip) come from user input, not the program slot.

backends/advanced/setup-https.sh (1)

241-244: Align speaker service port with orchestrator (init.py).

This script sets 8001; init.py currently passes 8085. Unify on one port (recommend 8001) in both places.

backends/advanced/init.py (1)

391-397: Nice: secure .env permissions (0600).

Good application of safe file creation semantics.

Comment on lines +111 to +119
### After HTTPS setup:
```bash
# Start with HTTPS profile
docker compose --profile https up --build -d

# Access via HTTPS
https://localhost/
https://100.83.66.30/ # Your configured IP
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Confirm the docker-compose https profile exists before documenting it.

If the https profile was removed/renamed, these commands will confuse users.


🏁 Script executed:

#!/bin/bash
# Verify any docker-compose file declares an https profile
rg -n --glob 'backends/**/docker-compose*.yml' -C2 -e 'profiles?:' | rg -n 'https' -n -C0 || {
  echo "No https profile found in compose files"; exit 1;
}

Length of output: 187


🏁 Script executed:

#!/bin/bash
# List all docker-compose files under backends and inspect their profiles
echo "Docker-compose files found:"
find backends -type f \( -name 'docker-compose*.yml' -o -name 'docker-compose*.yaml' \)

echo -e "\nScanning each for 'profiles:' sections with context:"
for file in $(find backends -type f \( -name 'docker-compose*.yml' -o -name 'docker-compose*.yaml' \)); do
  echo -e "\n---- File: $file ----"
  rg -n "profiles:" -C3 "$file" || echo "  No 'profiles:' key in $file"
done

Length of output: 3007


Add an https profile in backends/advanced/docker-compose.yml or remove the --profile https flag in SETUP_SCRIPTS.md
The Compose files under backends/advanced don’t declare any profiles: section, so docker compose --profile https will fail.

🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

111-111: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)

🤖 Prompt for AI Agents
In backends/advanced/SETUP_SCRIPTS.md around lines 111 to 119, the docs call
"docker compose --profile https" but the Compose files in backends/advanced lack
a corresponding "profiles:" declaration, causing the command to fail; either add
an "https" profile to relevant service definitions in
backends/advanced/docker-compose.yml (e.g., add a profiles: ["https"] entry to
the services meant to run under HTTPS and ensure any dependent services are
included) or update SETUP_SCRIPTS.md to remove the "--profile https" flag and
use the appropriate docker compose command without profiles (and adjust the
example startup command and any notes accordingly).

@@ -0,0 +1,313 @@
#!/bin/bash
set -e
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Harden shell options.

Prefer strict mode for early, visible failures.

-set -e
+set -Eeuo pipefail
+# Propagate ERR into functions/subshells when supported
+set -o errtrace 2>/dev/null || true
📝 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.

Suggested change
set -e
set -Eeuo pipefail
# Propagate ERR into functions/subshells when supported
set -o errtrace 2>/dev/null || true
🤖 Prompt for AI Agents
In backends/advanced/setup-https.sh around line 2, the script currently uses
only "set -e"; change this to enable strict shell options by adding "set -euo
pipefail" and set a safe IFS (e.g. IFS=$'\n\t') at the top of the script so
unbound variables, pipeline failures, and word-splitting issues surface early
and cause visible failures.

Comment on lines +939 to +979
async def update_speaker_configuration(user: User, primary_speakers: list[dict]):
"""Update current user's primary speakers configuration."""
try:
# Validate speaker data format
for speaker in primary_speakers:
if not isinstance(speaker, dict):
return JSONResponse(
status_code=400, content={"error": "Each speaker must be a dictionary"}
)

required_fields = ["speaker_id", "name", "user_id"]
for field in required_fields:
if field not in speaker:
return JSONResponse(
status_code=400, content={"error": f"Missing required field: {field}"}
)

# Enforce server-side user_id and add timestamp to each speaker
for speaker in primary_speakers:
speaker["user_id"] = user.user_id # Override client-supplied user_id
speaker["selected_at"] = datetime.now(UTC).isoformat()

# Update user model
user.primary_speakers = primary_speakers
await user.save()

logger.info(f"Updated primary speakers configuration for user {user.user_id}: {len(primary_speakers)} speakers")

return {
"message": "Primary speakers configuration updated successfully",
"primary_speakers": primary_speakers,
"count": len(primary_speakers),
"status": "success"
}

except Exception as e:
logger.error(f"Error updating speaker configuration for user {user.user_id}: {e}")
return JSONResponse(
status_code=500, content={"error": f"Failed to update speaker configuration: {str(e)}"}
)

Copy link
Contributor

@coderabbitai coderabbitai bot Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Don’t require client user_id; sanitize payload and server-stamp ownership.

You correctly overwrite user_id, but still require it in input. Drop it from required_fields, sanitize keys, and keep stack traces on errors.

-            required_fields = ["speaker_id", "name", "user_id"]
+            required_fields = ["speaker_id", "name"]
             for field in required_fields:
                 if field not in speaker:
                     return JSONResponse(
                         status_code=400, content={"error": f"Missing required field: {field}"}
                     )
 
-        # Enforce server-side user_id and add timestamp to each speaker
-        for speaker in primary_speakers:
-            speaker["user_id"] = user.user_id  # Override client-supplied user_id
-            speaker["selected_at"] = datetime.now(UTC).isoformat()
+        # Enforce server-side user_id, add timestamp, and sanitize payload
+        sanitized_speakers: list[dict] = []
+        for speaker in primary_speakers:
+            sanitized = {
+                "speaker_id": str(speaker["speaker_id"]),
+                "name": str(speaker["name"]),
+                "user_id": user.user_id,
+                "selected_at": datetime.now(UTC).isoformat(),
+            }
+            sanitized_speakers.append(sanitized)
 
         # Update user model
-        user.primary_speakers = primary_speakers
+        user.primary_speakers = sanitized_speakers
         await user.save()
 
-        logger.info(f"Updated primary speakers configuration for user {user.user_id}: {len(primary_speakers)} speakers")
+        logger.info(
+            "Updated primary speakers configuration for user %s: %d speakers",
+            user.user_id,
+            len(sanitized_speakers),
+        )
 
         return {
             "message": "Primary speakers configuration updated successfully",
-            "primary_speakers": primary_speakers,
-            "count": len(primary_speakers),
+            "primary_speakers": sanitized_speakers,
+            "count": len(sanitized_speakers),
             "status": "success"
         }
 
-    except Exception as e:
-        logger.error(f"Error updating speaker configuration for user {user.user_id}: {e}")
+    except Exception:
+        logger.exception("Error updating speaker configuration for user %s", user.user_id)
         return JSONResponse(
             status_code=500, content={"error": f"Failed to update speaker configuration: {str(e)}"}
         )

If you want, I can sketch a Pydantic model for speakers and wire it in FastAPI for schema validation.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.12.2)

974-974: Do not catch blind exception: Exception

(BLE001)


975-975: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


977-977: Use explicit conversion flag

Replace with conversion flag

(RUF010)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, can you help unify these? @coderabbitai

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@AnkushMalaker, I'll help you unify and improve the speaker configuration API with proper Pydantic models and sanitization. Here's a comprehensive solution:

Based on the codebase analysis, here's the unified solution with Pydantic models and improved validation:

1. First, create the Pydantic models (add these imports and models to the top of the file):

from pydantic import BaseModel, Field, validator
from typing import List, Optional

2. Add these Pydantic models after the imports:

class SpeakerConfigurationItem(BaseModel):
    """Speaker configuration item model."""
    speaker_id: str = Field(..., description="Unique identifier for the speaker")
    name: str = Field(..., min_length=1, max_length=100, description="Speaker display name")
    
    @validator('speaker_id')
    def validate_speaker_id(cls, v):
        if not v or not v.strip():
            raise ValueError('speaker_id cannot be empty')
        return v.strip()
    
    @validator('name')
    def validate_name(cls, v):
        if not v or not v.strip():
            raise ValueError('name cannot be empty')
        return v.strip()

class SpeakerConfigurationUpdate(BaseModel):
    """Request model for updating speaker configuration."""
    primary_speakers: List[SpeakerConfigurationItem] = Field(
        default=[], 
        description="List of primary speakers for memory processing filter"
    )

class SpeakerConfigurationResponse(BaseModel):
    """Response model for speaker configuration."""
    primary_speakers: List[dict]
    user_id: str
    status: str
    message: Optional[str] = None
    count: Optional[int] = None

3. Now here's the improved and unified implementation:

async def get_speaker_configuration(user: User) -> dict:
    """Get current user's primary speakers configuration."""
    try:
        return {
            "primary_speakers": user.primary_speakers,
            "user_id": user.user_id,
            "status": "success",
            "count": len(user.primary_speakers)
        }
    except Exception:
        logger.exception("Error getting speaker configuration for user %s", user.user_id)
        return JSONResponse(
            status_code=500, 
            content={"error": "Failed to get speaker configuration"}
        )


async def update_speaker_configuration(user: User, speaker_config: SpeakerConfigurationUpdate) -> dict:
    """Update current user's primary speakers configuration."""
    try:
        # Extract and validate speakers using Pydantic
        primary_speakers = speaker_config.primary_speakers
        
        # Create sanitized speaker list with server-side ownership
        sanitized_speakers: List[dict] = []
        current_time = datetime.now(UTC).isoformat()
        
        for speaker_item in primary_speakers:
            sanitized = {
                "speaker_id": speaker_item.speaker_id,
                "name": speaker_item.name,
                "user_id": user.user_id,  # Server-stamped ownership
                "selected_at": current_time,
            }
            sanitized_speakers.append(sanitized)
        
        # Update user model
        user.primary_speakers = sanitized_speakers
        await user.save()
        
        logger.info(
            "Updated primary speakers configuration for user %s: %d speakers",
            user.user_id,
            len(sanitized_speakers),
        )
        
        return {
            "message": "Primary speakers configuration updated successfully",
            "primary_speakers": sanitized_speakers,
            "count": len(sanitized_speakers),
            "status": "success"
        }
        
    except ValueError as ve:
        logger.warning("Validation error for user %s: %s", user.user_id, str(ve))
        return JSONResponse(
            status_code=400, 
            content={"error": f"Validation error: {str(ve)}"}
        )
    except Exception:
        logger.exception("Error updating speaker configuration for user %s", user.user_id)
        return JSONResponse(
            status_code=500, 
            content={"error": "Failed to update speaker configuration"}
        )

4. Update the route signatures in system_routes.py to use the Pydantic model:

@router.post("/speaker-configuration")
async def update_speaker_configuration(
    speaker_config: SpeakerConfigurationUpdate,
    current_user: User = Depends(current_active_user)
):
    return await system_controller.update_speaker_configuration(current_user, speaker_config)

Key improvements in this unified approach:

  1. Pydantic Validation: Automatic input validation with clear error messages
  2. Security: No client-supplied user_id required - server-stamped ownership
  3. Sanitization: Proper string trimming and validation
  4. Consistency: Both functions follow the same error handling pattern
  5. Logging: Uses logger.exception() for better stack traces
  6. Type Safety: Strong typing with Pydantic models
  7. Documentation: Built-in API documentation through Pydantic schemas

This unified approach ensures data integrity, security, and maintainability while providing clear API documentation automatically through FastAPI's integration with Pydantic models.


🏁 Script executed:

# Let's first look at the current imports and structure of the system controller
head -50 backends/advanced/src/advanced_omi_backend/controllers/system_controller.py

Length of output: 1646


🏁 Script executed:

# Check if there are existing Pydantic models we should follow for consistency
find backends/advanced/src -name "*.py" -exec grep -l "from pydantic import" {} \; | head -5

Length of output: 223


🏁 Script executed:

# Look at existing user model structure to understand the pattern
grep -A 10 -B 5 "primary_speakers" backends/advanced/src/advanced_omi_backend/users.py

Length of output: 1243


Let me check the existing Pydantic patterns to ensure consistency:

Now I'll provide the complete unified solution following the existing codebase patterns:

Complete Implementation

1. Add these imports to the top of system_controller.py (after the existing imports):

from pydantic import BaseModel, Field, validator
from typing import List, Optional

2. Add these Pydantic models after the logger declarations:

class SpeakerItem(BaseModel):
    """Individual speaker configuration item."""
    speaker_id: str = Field(..., description="Unique identifier for the speaker")
    name: str = Field(..., min_length=1, max_length=100, description="Speaker display name")
    
    @validator('speaker_id')
    def validate_speaker_id(cls, v):
        if not v or not v.strip():
            raise ValueError('speaker_id cannot be empty')
        return v.strip()
    
    @validator('name') 
    def validate_name(cls, v):
        if not v or not v.strip():
            raise ValueError('name cannot be empty')
        return v.strip()


class SpeakerConfigurationRequest(BaseModel):
    """Request payload for updating speaker configuration."""
    primary_speakers: List[SpeakerItem] = Field(
        default=[],
        description="List of primary speakers for memory processing filter"
    )

3. Replace the existing speaker configuration functions with these unified versions:

async def get_speaker_configuration(user: User) -> dict:
    """Get current user's primary speakers configuration."""
    try:
        return {
            "primary_speakers": user.primary_speakers,
            "user_id": user.user_id,
            "count": len(user.primary_speakers),
            "status": "success"
        }
    except Exception:
        logger.exception("Error getting speaker configuration for user %s", user.user_id)
        return JSONResponse(
            status_code=500, 
            content={"error": "Failed to get speaker configuration"}
        )


async def update_speaker_configuration(user: User, speaker_config: SpeakerConfigurationRequest) -> dict:
    """Update current user's primary speakers configuration."""
    try:
        # Create sanitized speaker list with server-side ownership
        sanitized_speakers: List[dict] = []
        current_time = datetime.now(UTC).isoformat()
        
        for speaker_item in speaker_config.primary_speakers:
            sanitized = {
                "speaker_id": speaker_item.speaker_id,
                "name": speaker_item.name,
                "user_id": user.user_id,  # Server-stamped ownership
                "selected_at": current_time,
            }
            sanitized_speakers.append(sanitized)
        
        # Update user model
        user.primary_speakers = sanitized_speakers
        await user.save()
        
        logger.info(
            "Updated primary speakers configuration for user %s: %d speakers",
            user.user_id,
            len(sanitized_speakers),
        )
        
        return {
            "message": "Primary speakers configuration updated successfully",
            "primary_speakers": sanitized_speakers,
            "count": len(sanitized_speakers),
            "status": "success"
        }
        
    except ValueError as ve:
        logger.warning("Validation error for user %s: %s", user.user_id, str(ve))
        return JSONResponse(
            status_code=400, 
            content={"error": f"Validation error: {str(ve)}"}
        )
    except Exception:
        logger.exception("Error updating speaker configuration for user %s", user.user_id)
        return JSONResponse(
            status_code=500, 
            content={"error": "Failed to update speaker configuration"}
        )

4. Update the route definition in system_routes.py:

from advanced_omi_backend.controllers.system_controller import SpeakerConfigurationRequest

@router.post("/speaker-configuration")
async def update_speaker_configuration(
    speaker_config: SpeakerConfigurationRequest,
    current_user: User = Depends(current_active_user)
):
    """Update user's primary speakers configuration for memory processing filter."""
    return await system_controller.update_speaker_configuration(current_user, speaker_config)

Key Improvements

Security: Removed user_id from required client fields - server-stamped ownership
Validation: Pydantic models with automatic input sanitization and validation
Logging: Proper exception logging with structured messages
Consistency: Unified error handling and response format
Type Safety: Strong typing throughout the API
Documentation: Auto-generated OpenAPI docs via Pydantic schemas

This unified approach eliminates the security issue of accepting client-supplied user_id, adds robust input validation, and provides better error handling while maintaining consistency with the existing codebase patterns.


🏁 Script executed:

# Check existing Pydantic model patterns in chat_routes.py
grep -A 15 -B 5 "class.*BaseModel" backends/advanced/src/advanced_omi_backend/routers/modules/chat_routes.py

Length of output: 1511


🏁 Script executed:

# Also check how Pydantic imports are structured in the existing files
head -20 backends/advanced/src/advanced_omi_backend/routers/modules/chat_routes.py | grep -E "(import|from).*pydantic"

Length of output: 158


Perfect! Now I have the complete picture. The codebase uses from pydantic import BaseModel, Field and follows a clean pattern. Here's the final unified implementation:

Complete Implementation

1. Add the Pydantic import to system_controller.py (after existing imports):

from pydantic import BaseModel, Field, validator

2. Add these Pydantic models after the logger declarations:

class SpeakerItem(BaseModel):
    """Individual speaker configuration item."""
    speaker_id: str = Field(..., description="Unique identifier for the speaker")
    name: str = Field(..., min_length=1, max_length=100, description="Speaker display name")
    
    @validator('speaker_id')
    def validate_speaker_id(cls, v):
        if not v or not v.strip():
            raise ValueError('speaker_id cannot be empty')
        return v.strip()
    
    @validator('name') 
    def validate_name(cls, v):
        if not v or not v.strip():
            raise ValueError('name cannot be empty')
        return v.strip()


class SpeakerConfigurationRequest(BaseModel):
    """Request payload for updating speaker configuration."""
    primary_speakers: list[SpeakerItem] = Field(
        default=[],
        description="List of primary speakers for memory processing filter"
    )

3. Replace the existing speaker configuration functions in system_controller.py:

async def get_speaker_configuration(user: User) -> dict:
    """Get current user's primary speakers configuration."""
    try:
        return {
            "primary_speakers": user.primary_speakers,
            "user_id": user.user_id,
            "count": len(user.primary_speakers),
            "status": "success"
        }
    except Exception:
        logger.exception("Error getting speaker configuration for user %s", user.user_id)
        return JSONResponse(
            status_code=500, 
            content={"error": "Failed to get speaker configuration"}
        )


async def update_speaker_configuration(user: User, speaker_config: SpeakerConfigurationRequest) -> dict:
    """Update current user's primary speakers configuration."""
    try:
        # Create sanitized speaker list with server-side ownership
        sanitized_speakers: list[dict] = []
        current_time = datetime.now(UTC).isoformat()
        
        for speaker_item in speaker_config.primary_speakers:
            sanitized = {
                "speaker_id": speaker_item.speaker_id,
                "name": speaker_item.name,
                "user_id": user.user_id,  # Server-stamped ownership
                "selected_at": current_time,
            }
            sanitized_speakers.append(sanitized)
        
        # Update user model
        user.primary_speakers = sanitized_speakers
        await user.save()
        
        logger.info(
            "Updated primary speakers configuration for user %s: %d speakers",
            user.user_id,
            len(sanitized_speakers),
        )
        
        return {
            "message": "Primary speakers configuration updated successfully",
            "primary_speakers": sanitized_speakers,
            "count": len(sanitized_speakers),
            "status": "success"
        }
        
    except ValueError as ve:
        logger.warning("Validation error for user %s: %s", user.user_id, str(ve))
        return JSONResponse(
            status_code=400, 
            content={"error": f"Validation error: {str(ve)}"}
        )
    except Exception:
        logger.exception("Error updating speaker configuration for user %s", user.user_id)
        return JSONResponse(
            status_code=500, 
            content={"error": "Failed to update speaker configuration"}
        )

4. Update system_routes.py to import and use the Pydantic model:

Add this import at the top:

from advanced_omi_backend.controllers.system_controller import SpeakerConfigurationRequest

Update the POST route:

@router.post("/speaker-configuration")
async def update_speaker_configuration(
    speaker_config: SpeakerConfigurationRequest,
    current_user: User = Depends(current_active_user)
):
    """Update user's primary speakers configuration for memory processing filter."""
    return await system_controller.update_speaker_configuration(current_user, speaker_config)

Key Benefits of This Unified Approach

  1. Security: No client-supplied user_id - server enforces ownership
  2. Validation: Automatic input sanitization and validation via Pydantic
  3. Consistency: Matches existing codebase patterns (using list[dict] like the user model)
  4. Error Handling: Proper exception logging and structured error responses
  5. Type Safety: Strong typing throughout the API
  6. Documentation: Auto-generated OpenAPI documentation
  7. Maintainability: Clean, unified code structure

The implementation follows the exact patterns used in chat_routes.py and maintains consistency with the existing primary_speakers: list[dict] field definition in the user model.

Comment on lines +105 to 107
limit: int = 10,
score_threshold: float = 0.0
) -> List[MemoryEntry]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Propagate the new score_threshold across implementations and callers.

The abstract signature now includes score_threshold. Please verify all concrete implementations and wrappers (service, compat layer, controllers/routes) pass and handle this argument to avoid runtime mismatches.

Run to check for out-of-date definitions/call sites:

Also applies to: 114-115


🏁 Script executed:

#!/bin/bash
rg -nP --type=py -C2 '\bsearch_memories\s*\('
rg -nP --type=py -C2 'async\s+def\s+search_memories\('

Length of output: 14414


Propagate score_threshold in all search_memories implementations

  • In memory_service.py (around the deduplication loop), include both limit and score_threshold when calling self.vector_store.search_memories.
  • In memory/providers/openmemory_mcp_service.py, extend the async def search_memories(...) signature to accept limit: int = 10 and score_threshold: float = 0.0 (and forward them to underlying calls).
  • In extras/openmemory-mcp/test_standalone.py, update the search_memories stub (and any client invocations) to include the new score_threshold parameter.

Comment on lines +14 to +23
from qdrant_client import AsyncQdrantClient
from qdrant_client.models import (
Distance,
FieldCondition,
Filter,
FilterSelector,
MatchValue,
PointStruct,
VectorParams,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consolidate imports at module scope; remove remaining function-scoped imports.

Nice move bringing Qdrant imports up top. Two function-scoped imports of uuid remain (Lines 246 and 327). Per repo rules, drop them and rely on the module-level import.

-            import uuid
             try:
                 # Try to parse as UUID first
                 uuid.UUID(memory_id)
-            import uuid
             try:
                 # Try to parse as UUID first
                 uuid.UUID(memory_id)
📝 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.

Suggested change
from qdrant_client import AsyncQdrantClient
from qdrant_client.models import (
Distance,
FieldCondition,
Filter,
FilterSelector,
MatchValue,
PointStruct,
VectorParams,
)
try:
# Try to parse as UUID first
uuid.UUID(memory_id)
Suggested change
from qdrant_client import AsyncQdrantClient
from qdrant_client.models import (
Distance,
FieldCondition,
Filter,
FilterSelector,
MatchValue,
PointStruct,
VectorParams,
)
try:
# Try to parse as UUID first
uuid.UUID(memory_id)
🤖 Prompt for AI Agents
In backends/advanced/src/advanced_omi_backend/memory/providers/vector_stores.py
around lines 14-23 and referencing function-scoped imports at lines 246 and 327,
consolidate the uuid import by adding a single module-level import (import uuid)
with the other top-level imports and remove the two function-scoped "import
uuid" statements at lines 246 and 327 so functions use the module-level uuid
symbol instead.

Comment on lines +123 to +126
if 'speaker-recognition' in selected_services:
cmd.extend(['--speaker-service-url', 'http://host.docker.internal:8085'])
if 'asr-services' in selected_services:
cmd.extend(['--parakeet-asr-url', 'http://host.docker.internal:8767'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Port mismatch: speaker-recognition URL differs from setup-https.sh (8085 vs 8001).

Breaks inter-service wiring. Align to the actual service port (8001 per advanced/setup-https.sh).

-        if 'speaker-recognition' in selected_services:
-            cmd.extend(['--speaker-service-url', 'http://host.docker.internal:8085'])
+        if 'speaker-recognition' in selected_services:
+            cmd.extend(['--speaker-service-url', 'http://host.docker.internal:8001'])
📝 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.

Suggested change
if 'speaker-recognition' in selected_services:
cmd.extend(['--speaker-service-url', 'http://host.docker.internal:8085'])
if 'asr-services' in selected_services:
cmd.extend(['--parakeet-asr-url', 'http://host.docker.internal:8767'])
if 'speaker-recognition' in selected_services:
cmd.extend(['--speaker-service-url', 'http://host.docker.internal:8001'])
if 'asr-services' in selected_services:
cmd.extend(['--parakeet-asr-url', 'http://host.docker.internal:8767'])
🤖 Prompt for AI Agents
In init.py around lines 123 to 126, the speaker-recognition service URL is set
to http://host.docker.internal:8085 which mismatches setup-https.sh (should be
8001); change the speaker-recognition URL to http://host.docker.internal:8001 so
it aligns with the advanced/setup-https.sh configuration and keeps inter-service
wiring consistent (leave the asr URL unchanged).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
extras/speaker-recognition/scripts/enroll_speaker.py (2)

289-294: Fix undefined function calls in main (NameError).

list_speakers_with_url and delete_speaker_with_url are not defined; current script crashes when invoked with --list or --delete.

Apply either a minimal call-site fix or (recommended) the fuller refactor below that also plumbs --service-url through all ops and adds HTTP timeouts.


62-84: Plumb --service-url everywhere and add HTTP timeouts (also resolves NameError).

Honor the CLI --service-url for all enroll/list/delete requests and add sane timeouts. This also replaces undefined calls in main.

@@
-def enroll_single_file(file_path: str, speaker_id: str, speaker_name: str, start: Optional[float] = None, end: Optional[float] = None, service_url: str = None) -> bool:
+def enroll_single_file(
+    file_path: str,
+    speaker_id: str,
+    speaker_name: str,
+    start: Optional[float] = None,
+    end: Optional[float] = None,
+    service_url: str | None = None,
+) -> bool:
@@
-    logger.info(f"Enrolling from file: {file_path}")
+    logger.info(f"Enrolling from file: {file_path}")
+    base_url = service_url or SPEAKER_SERVICE_URL
@@
-            response = requests.post(f"{SPEAKER_SERVICE_URL}/enroll/upload", files=files, data=data)
+            response = requests.post(
+                f"{base_url}/enroll/upload",
+                files=files,
+                data=data,
+                timeout=(10, 300),
+            )
@@
-def enroll_multiple_files(file_paths: List[str], speaker_id: str, speaker_name: str) -> bool:
+def enroll_multiple_files(
+    file_paths: List[str],
+    speaker_id: str,
+    speaker_name: str,
+    service_url: str | None = None,
+) -> bool:
@@
-    logger.info(f"Enrolling from {len(valid_files)} files...")
+    logger.info(f"Enrolling from {len(valid_files)} files...")
+    base_url = service_url or SPEAKER_SERVICE_URL
@@
-        response = requests.post(f"{SPEAKER_SERVICE_URL}/enroll/batch", files=files, data=data)
+        response = requests.post(
+            f"{base_url}/enroll/batch",
+            files=files,
+            data=data,
+            timeout=(10, 600),
+        )
@@
-def enroll_from_directory(directory: str, speaker_id: str, speaker_name: str) -> bool:
+def enroll_from_directory(
+    directory: str, speaker_id: str, speaker_name: str, service_url: str | None = None
+) -> bool:
@@
-    return enroll_multiple_files(audio_files, speaker_id, speaker_name)
+    return enroll_multiple_files(
+        audio_files, speaker_id, speaker_name, service_url=service_url
+    )
@@
-def list_speakers() -> bool:
+def list_speakers(service_url: str = SPEAKER_SERVICE_URL) -> bool:
@@
-        response = requests.get(f"{SPEAKER_SERVICE_URL}/speakers")
+        response = requests.get(f"{service_url}/speakers", timeout=10)
@@
-def delete_speaker(speaker_id: str) -> bool:
+def delete_speaker(speaker_id: str, service_url: str = SPEAKER_SERVICE_URL) -> bool:
@@
-        response = requests.delete(f"{SPEAKER_SERVICE_URL}/speakers/{speaker_id}")
+        response = requests.delete(
+            f"{service_url}/speakers/{speaker_id}", timeout=10
+        )
@@
-    if args.list:
-        return 0 if list_speakers_with_url(service_url) else 1
+    if args.list:
+        return 0 if list_speakers(service_url) else 1
@@
-    elif args.delete:
-        return 0 if delete_speaker_with_url(args.delete, service_url) else 1
+    elif args.delete:
+        return 0 if delete_speaker(args.delete, service_url) else 1
@@
-        if args.file:
-            success = enroll_single_file(args.file, args.id, args.name, args.start, args.end)
+        if args.file:
+            success = enroll_single_file(
+                args.file, args.id, args.name, args.start, args.end, service_url=service_url
+            )
@@
-        elif args.files:
-            success = enroll_multiple_files(args.files, args.id, args.name)
+        elif args.files:
+            success = enroll_multiple_files(args.files, args.id, args.name, service_url=service_url)
@@
-        elif args.dir:
-            success = enroll_from_directory(args.dir, args.id, args.name)
+        elif args.dir:
+            success = enroll_from_directory(args.dir, args.id, args.name, service_url=service_url)
@@
-                success = enroll_single_file(audio_path, args.id, args.name)
+                success = enroll_single_file(audio_path, args.id, args.name, service_url=service_url)

Also applies to: 101-124, 138-155, 207-236, 239-256, 289-318

backends/advanced/webui/src/contexts/AuthContext.tsx (1)

61-96: Ensure failed login doesn’t leave a stale token in storage.

If getMe fails after setting the token, the catch returns an error but the token remains persisted. Clear token/user on any login failure.

-  const login = async (email: string, password: string): Promise<{success: boolean, error?: string, errorType?: string}> => {
+  const login = async (email: string, password: string): Promise<LoginResult> => {
     try {
       const response = await authApi.login(email, password)

       const { access_token } = response.data
-      setToken(access_token)
-      localStorage.setItem('token', access_token)
+      localStorage.setItem('token', access_token)
+      setToken(access_token)

       // Get user info
       const userResponse = await authApi.getMe()
       setUser(userResponse.data)

       return { success: true }
-    } catch (error: any) {
+    } catch (error: unknown) {
       console.error('Login failed:', error)
 
       // Parse structured error response from backend
       let errorMessage = 'Login failed. Please try again.'
-      let errorType = 'unknown'
+      let errorType: LoginErrorType = 'unknown'
 
-      if (error.response?.data) {
-        const errorData = error.response.data
+      // Axios-style narrowing
+      const axiosErr = error as { response?: { data?: any }; code?: string }
+      if (axiosErr?.response?.data) {
+        const errorData = axiosErr.response.data
         errorMessage = errorData.detail || errorMessage
         errorType = errorData.error_type || errorType
-      } else if (error.code === 'ERR_NETWORK') {
+      } else if ((axiosErr && axiosErr.code) === 'ERR_NETWORK') {
         errorMessage = 'Unable to connect to server. Please check your connection and try again.'
         errorType = 'connection_failure'
       }
 
+      // Roll back any partially persisted auth state
+      localStorage.removeItem('token')
+      setToken(null)
+      setUser(null)
+
       return { 
         success: false, 
         error: errorMessage,
         errorType: errorType
       }
     }
   }
backends/advanced/src/advanced_omi_backend/main.py (2)

971-1003: Don’t leak secrets in /health – remove MongoDB URI from response.

MONGODB_URI may contain credentials; exposing it is a security risk.

         "config": {
-            "mongodb_uri": MONGODB_URI,
             "qdrant_url": f"http://{QDRANT_BASE_URL}:{QDRANT_PORT}",
             "transcription_service": (
                 f"Speech to Text ({transcription_provider.name})"
                 if transcription_provider
                 else "Speech to Text (Not Configured)"
             ),

223-233: Move mid-file imports to the top (per repo guidelines).

These imports should live under the module docstring with other imports; keep the calls in place.

-# Initialize client manager with active_clients reference
-from advanced_omi_backend.client_manager import init_client_manager
-
-init_client_manager(active_clients)
-
-# Initialize client utilities with the mapping dictionaries
-from advanced_omi_backend.client_manager import (
+init_client_manager(active_clients)

Add near the top (after existing local imports):

from advanced_omi_backend.client_manager import (
    init_client_manager,
    init_client_user_mapping,
    register_client_user_mapping,
    track_client_user_relationship,
    unregister_client_user_mapping,
)
♻️ Duplicate comments (21)
extras/openmemory-mcp/setup.sh (2)

3-5: Harden defaults: enable ERR trapping and protect secrets by default.

Add -E and set a restrictive umask early so .env/backups aren’t world-readable.

-# Enable strict error handling
-set -euo pipefail
+# Enable strict error handling
+set -Eeuo pipefail
+umask 077

74-79: Fix misleading URLs and clarify UI profile.

Web UI runs on 3001 and only when started with the ui profile; API/docs are at 8765.

-echo "🌐 MCP Server: http://localhost:8765"
-echo "📱 Web Interface: http://localhost:8765"
-echo "🔧 UI (optional): docker compose --profile ui up -d"
+echo "🌐 API & Docs: http://localhost:8765 (see /docs)"
+echo "📱 Web UI: http://localhost:3001 (start with: docker compose --profile ui up -d)"
services.py (5)

25-31: Standardize ports list; avoid ambiguous '5174/8444'.

List each port separately.

-        'ports': ['8085', '5174/8444']
+        'ports': ['8085', '5174', '8444']

50-55: Remove redundant conditional.

Both branches return the same expression.

-    # Backend uses advanced init, others use .env
-    if service_name == 'backend':
-        return (service_path / '.env').exists()
-    else:
-        return (service_path / '.env').exists()
+    return (service_path / '.env').exists()

98-104: Replace assert with explicit check (runtime-safe).

Asserts may be optimized out and aren’t user-friendly on failure.

-            assert process.stdout is not None
+            if process.stdout is None:
+                console.print(f"[red]❌ No stdout stream from docker compose for {service_name}[/red]")
+                return False

142-148: Fix f-string lint and add context.

Use the service name and avoid placeholder-less f-strings.

-                console.print(f"[red]❌ Command failed[/red]")
+                console.print(f"[red]❌ Command failed for {service_name}[/red]")
                 if result.stderr:
-                    console.print("[red]Error output:[/red]")
+                    console.print(f"[red]Error output for {service_name}:[/red]")

153-155: Avoid catching blind Exception; handle subprocess/OSError explicitly.

Improves error clarity and doesn’t hide interrupts.

-    except Exception as e:
+    except subprocess.SubprocessError as e:
+        console.print(f"[red]❌ Error running command: {e}[/red]")
+        return False
+    except OSError as e:
         console.print(f"[red]❌ Error running command: {e}[/red]")
         return False
init.py (3)

7-13: Follow import policy: move datetime to top-level and drop unused rprint.

No mid-function imports; keep groups ordered and remove unused imports.

-import subprocess
-import sys
-from pathlib import Path
-from rich.console import Console
-from rich.prompt import Confirm
-from rich import print as rprint
+import subprocess
+import sys
+from datetime import datetime
+from pathlib import Path
+from rich.console import Console
+from rich.prompt import Confirm

95-99: Remove mid-function import (violates project policy).

Now that datetime is imported at module level, delete this.

-    from datetime import datetime
-    
+    

123-126: Port mismatch: verify speaker-recognition service port.

setup-https and compose commonly expose 8001; code uses 8085. Align after verifying.

-        if 'speaker-recognition' in selected_services:
-            cmd.extend(['--speaker-service-url', 'http://host.docker.internal:8085'])
+        if 'speaker-recognition' in selected_services:
+            cmd.extend(['--speaker-service-url', 'http://host.docker.internal:8001'])

Verification script:

#!/bin/bash
set -Eeuo pipefail
echo "Searching speaker-recognition ports..."
rg -n "ports:|:\s*8(001|085)" -n -C2 extras/speaker-recognition/docker-compose.yml || true
rg -n "8001|8085" -n -C2 extras/speaker-recognition -g '!**/node_modules/**' || true
backends/advanced/init.py (11)

1-1: Shebang present but file may not be executable (ruff EXE001).

Either make executable or drop the shebang.

Options:

  • Keep shebang and run: chmod +x backends/advanced/init.py
  • Or remove it:
-#!/usr/bin/env python3

To verify:

#!/bin/bash
test -x backends/advanced/init.py && echo "executable ✅" || echo "not executable ❌"

7-23: Clean imports: remove unused rprint; add re for validation; keep order.

Aligns with guidelines and prepares for server_ip regex validation.

 import argparse
 import getpass
 import os
+import re
 import secrets
 import shutil
 import subprocess
 import sys
 from datetime import datetime
 from pathlib import Path
 from typing import Any, Dict, Optional
 
-from rich import print as rprint
 from rich.console import Console
 from rich.panel import Panel
 from rich.prompt import Confirm, Prompt
 from rich.text import Text

128-139: Gate Deepgram provider on presence of API key.

Avoids broken config when no key is supplied.

-        if choice == "1":
-            self.config["TRANSCRIPTION_PROVIDER"] = "deepgram"
+        if choice == "1":
             self.console.print("[blue][INFO][/blue] Deepgram selected")
             self.console.print("Get your API key from: https://console.deepgram.com/")
             
             api_key = self.prompt_value("Deepgram API key (leave empty to skip)", "")
             if api_key:
+                self.config["TRANSCRIPTION_PROVIDER"] = "deepgram"
                 self.config["DEEPGRAM_API_KEY"] = api_key
                 self.console.print("[green][SUCCESS][/green] Deepgram configured")
             else:
-                self.console.print("[yellow][WARNING][/yellow] No API key provided - transcription will not work")
+                self.config.pop("TRANSCRIPTION_PROVIDER", None)
+                self.console.print("[yellow][WARNING][/yellow] No API key provided - skipping transcription setup")

140-154: Gate Mistral provider on presence of API key.

Mirror Deepgram behavior.

-        elif choice == "2":
-            self.config["TRANSCRIPTION_PROVIDER"] = "mistral"
+        elif choice == "2":
             self.console.print("[blue][INFO][/blue] Mistral selected")
             self.console.print("Get your API key from: https://console.mistral.ai/")
             
             api_key = self.prompt_value("Mistral API key (leave empty to skip)", "")
             model = self.prompt_value("Mistral model", "voxtral-mini-2507")
             
             if api_key:
+                self.config["TRANSCRIPTION_PROVIDER"] = "mistral"
                 self.config["MISTRAL_API_KEY"] = api_key
                 self.config["MISTRAL_MODEL"] = model
                 self.console.print("[green][SUCCESS][/green] Mistral configured")
             else:
-                self.console.print("[yellow][WARNING][/yellow] No API key provided - transcription will not work")
+                self.config.pop("TRANSCRIPTION_PROVIDER", None)
+                self.console.print("[yellow][WARNING][/yellow] No API key provided - skipping transcription setup")

177-193: Gate OpenAI provider on presence of API key.

Prevents LLM_PROVIDER=openai without credentials.

-        if choice == "1":
-            self.config["LLM_PROVIDER"] = "openai"
+        if choice == "1":
             self.console.print("[blue][INFO][/blue] OpenAI selected")
             self.console.print("Get your API key from: https://platform.openai.com/api-keys")
             
             api_key = self.prompt_value("OpenAI API key (leave empty to skip)", "")
             model = self.prompt_value("OpenAI model", "gpt-4o-mini")
             base_url = self.prompt_value("OpenAI base URL (for proxies/compatible APIs)", "https://api.openai.com/v1")
             
             if api_key:
+                self.config["LLM_PROVIDER"] = "openai"
                 self.config["OPENAI_API_KEY"] = api_key
                 self.config["OPENAI_MODEL"] = model
                 self.config["OPENAI_BASE_URL"] = base_url
                 self.console.print("[green][SUCCESS][/green] OpenAI configured")
             else:
-                self.console.print("[yellow][WARNING][/yellow] No API key provided - memory extraction will not work")
+                self.config.pop("LLM_PROVIDER", None)
+                self.console.print("[yellow][WARNING][/yellow] No API key provided - skipping LLM setup")

257-261: Fix default Speaker Recognition port (8085 expected).

Keeps defaults consistent across repo.

-                speaker_url = self.prompt_value("Speaker Recognition service URL", "http://host.docker.internal:8001")
+                speaker_url = self.prompt_value("Speaker Recognition service URL", "http://host.docker.internal:8085")

279-282: Avoid None server_ip when --enable-https is used without --server-ip.

Ensures a sane default is applied.

-            enable_https = True
-            server_ip = getattr(self.args, 'server_ip', 'localhost')
+            enable_https = True
+            server_ip = getattr(self.args, 'server_ip', None) or 'localhost'

279-282: Validate server_ip and reject unsafe hostnames.

Basic allowlist prevents injection/typos before templating and subprocess.

             enable_https = True
-            server_ip = getattr(self.args, 'server_ip', 'localhost')
+            server_ip = getattr(self.args, 'server_ip', None) or 'localhost'
+            if not re.fullmatch(r"[A-Za-z0-9.\-]+", server_ip):
+                self.console.print(f"[red]Invalid server name/IP: {server_ip}[/red]")
+                return
@@
-                server_ip = self.prompt_value("Server IP/Domain for SSL certificate (Tailscale IP or localhost)", "localhost")
+                server_ip = self.prompt_value("Server IP/Domain for SSL certificate (Tailscale IP or localhost)", "localhost")
+                if not re.fullmatch(r"[A-Za-z0-9.\-]+", server_ip):
+                    self.console.print(f"[red]Invalid server name/IP: {server_ip}[/red]")
+                    return

Also applies to: 292-297


306-309: Harden subprocess call (ruff S603) and improve portability.

Invoke via bash explicitly.

-                    subprocess.run([str(ssl_script), server_ip], check=True, cwd=str(script_dir), timeout=180)
+                    subprocess.run(["bash", str(ssl_script), server_ip], check=True, cwd=str(script_dir), timeout=180)

349-353: Quote secrets in .env to avoid dotenv parsing issues.

Prevents breakage on spaces/#/special chars.

 # Authentication
 AUTH_SECRET_KEY={self.config.get('AUTH_SECRET_KEY', '')}
 ADMIN_EMAIL={self.config.get('ADMIN_EMAIL', '')}
-ADMIN_PASSWORD={self.config.get('ADMIN_PASSWORD', '')}
+ADMIN_PASSWORD="{self.config.get('ADMIN_PASSWORD', '').replace('"', '\\"')}"

374-381: Persist HTTPS settings to .env.

Required so other components know HTTPS state.

 # Optional Services
 SPEAKER_SERVICE_URL={self.config.get('SPEAKER_SERVICE_URL', '')}
 PARAKEET_ASR_URL={self.config.get('PARAKEET_ASR_URL', '')}
 
+# HTTPS
+HTTPS_ENABLED={self.config.get('HTTPS_ENABLED', 'false')}
+SERVER_IP={self.config.get('SERVER_IP', '')}
+
 # Network Configuration
 BACKEND_PUBLIC_PORT={self.config.get('BACKEND_PUBLIC_PORT', '8000')}
 WEBUI_PORT={self.config.get('WEBUI_PORT', '5173')}
🧹 Nitpick comments (22)
extras/speaker-recognition/scripts/enroll_speaker.py (2)

186-200: Make ffmpeg segment extraction robust (validate times, dynamic timeout, stderr logging).

Prevents bad ranges, adapts timeout to segment length, and surfaces ffmpeg errors. Addresses S603 concerns; no shell used and paths are controlled.

-            if start is not None and end is not None:
-                logger.info(f"Extracting segment from {start}s to {end}s")
-                # Use ffmpeg to extract segment
-                segment_path = output_path.replace('.wav', '_segment.wav')
-                cmd = [
-                    'ffmpeg', '-i', output_path,
-                    '-ss', str(start), '-to', str(end),
-                    '-c', 'copy', segment_path,
-                    '-y'
-                ]
-                subprocess.run(cmd, capture_output=True, check=True, timeout=60)
-                os.unlink(output_path)
-                return segment_path
+            if start is not None and end is not None:
+                if start < 0 or end <= start:
+                    logger.error("Invalid start/end times: start must be >= 0 and end > start")
+                    os.unlink(output_path)
+                    return None
+                if duration and end > duration:
+                    logger.info("Clamping end to video duration")
+                    end = float(duration)
+                seg_len = end - start
+                timeout = max(60, min(300, int(seg_len * 2 + 10)))
+                logger.info(f"Extracting segment from {start}s to {end}s")
+                # Use ffmpeg to extract segment
+                segment_path = output_path.replace('.wav', '_segment.wav')
+                cmd = [
+                    'ffmpeg', '-hide_banner', '-loglevel', 'error', '-nostdin',
+                    '-i', output_path,
+                    '-ss', str(start), '-to', str(end),
+                    '-c', 'copy',
+                    segment_path,
+                    '-y'
+                ]
+                try:
+                    subprocess.run(cmd, capture_output=True, check=True, timeout=timeout)
+                except subprocess.CalledProcessError as e:
+                    err = e.stderr.decode(errors='ignore') if e.stderr else str(e)
+                    logger.error(f"ffmpeg failed: {err}")
+                    os.unlink(output_path)
+                    return None
+                os.unlink(output_path)
+                return segment_path

25-35: Reorder imports per guidelines and move yt_dlp to module scope (no in-function imports).

Groups stdlib/third-party and avoids lazy import. Keeps optional dependency via soft import.

-import argparse
-import os
-import sys
-import requests
-import json
-import tempfile
-from pathlib import Path
-from typing import List, Optional, Tuple
-import subprocess
-import logging
+import argparse
+import logging
+import os
+import subprocess
+import sys
+import tempfile
+from pathlib import Path
+from typing import List, Optional, Tuple
+import json
+
+import requests
+
+try:
+    import yt_dlp  # optional
+except ImportError:
+    yt_dlp = None
-    try:
-        import yt_dlp
-    except ImportError:
-        logger.error("yt-dlp not installed. Install with: pip install yt-dlp")
-        return None
+    if yt_dlp is None:
+        logger.error("yt-dlp not installed. Install with: pip install yt-dlp")
+        return None

Also applies to: 160-164

backends/advanced/webui/src/contexts/AuthContext.tsx (2)

14-14: Stabilize the public API with a typed result union.

Define a LoginResult type and use a discriminated union for errorType to prevent typos across callers.

-interface AuthContextType {
+type LoginErrorType = 'connection_failure' | 'authentication_failure' | 'unknown'
+type LoginResult = { success: true } | { success: false; error: string; errorType: LoginErrorType }
+
+interface AuthContextType {
   user: User | null
   token: string | null
-  login: (email: string, password: string) => Promise<{success: boolean, error?: string, errorType?: string}>
+  login: (email: string, password: string) => Promise<LoginResult>
   logout: () => void
   isLoading: boolean
   isAdmin: boolean
 }

61-61: Prefer ‘unknown’ over ‘any’ for caught errors and narrow.

Adopt AxiosError (if available) or structural narrowing; avoids unsafe access.

-} catch (error: any) {
+} catch (error: unknown) {
backends/advanced/webui/src/pages/LoginPage.tsx (1)

39-48: Handle aborted health checks explicitly.

Optional: specialize AbortError to show the same connectivity message without console noise.

-    } catch (healthError) {
+    } catch (healthError: any) {
+      // Treat AbortError as connection failure
+      if (healthError?.name === 'AbortError') {
+        setError('Unable to connect to server. Please check your connection and try again.')
+        setIsLoading(false)
+        return
+      }
       setError('Unable to connect to server. Please check your connection and try again.')
       setIsLoading(false)
       return
     }
backends/advanced/src/advanced_omi_backend/main.py (6)

383-401: Return 503 for infra outages and silence “unused arg” warnings.

Use 503 for DB connectivity issues and prefix unused request with _ to satisfy linters.

-@app.exception_handler(ConnectionFailure)
-@app.exception_handler(PyMongoError)
-async def database_exception_handler(request: Request, exc: Exception):
+@app.exception_handler(ConnectionFailure)
+@app.exception_handler(PyMongoError)
+async def database_exception_handler(_request: Request, exc: Exception):
     """Handle database connection failures and return structured error response."""
     logger.error(f"Database connection error: {type(exc).__name__}: {exc}")
     return JSONResponse(
-        status_code=500,
+        status_code=503,
         content={
             "detail": "Unable to connect to server. Please check your connection and try again.",
             "error_type": "connection_failure",
             "error_category": "database"
         }
     )

403-415: Align network failures with 503 and fix unused arg.

Same rationale as DB handler.

-@app.exception_handler(ConnectionError)
-async def connection_exception_handler(request: Request, exc: ConnectionError):
+@app.exception_handler(ConnectionError)
+async def connection_exception_handler(_request: Request, exc: ConnectionError):
     """Handle general connection errors and return structured error response."""
     logger.error(f"Connection error: {exc}")
     return JSONResponse(
-        status_code=500,
+        status_code=503,
         content={
             "detail": "Unable to connect to server. Please check your connection and try again.",
             "error_type": "connection_failure",
             "error_category": "network"
         }
     )

417-436: Fix unused arg; keep 401 body structured.

-@app.exception_handler(HTTPException)
-async def http_exception_handler(request: Request, exc: HTTPException):
+@app.exception_handler(HTTPException)
+async def http_exception_handler(_request: Request, exc: HTTPException):

1067-1111: Use explicit conversion flag and prefer logger.exception when catching broad errors.

Minor lint/stability improvements for the memory_service block.

-        except Exception as e:
-            health_status["services"]["memory_service"] = {
-                "status": f"⚠️ Friend-Lite Memory Failed: {str(e)}",
+        except Exception as e:
+            health_status["services"]["memory_service"] = {
+                "status": f"⚠️ Friend-Lite Memory Failed: {e!s}",
                 "healthy": False,
                 "provider": "friend_lite",
                 "critical": False,
             }
-            overall_healthy = False
+            overall_healthy = False

1199-1242: Apply same conversion fix for OpenMemory MCP failures.

-        except Exception as e:
-            health_status["services"]["openmemory_mcp"] = {
-                "status": f"⚠️ Connection Failed: {str(e)}",
+        except Exception as e:
+            health_status["services"]["openmemory_mcp"] = {
+                "status": f"⚠️ Connection Failed: {e!s}",
                 "healthy": False,
                 "url": openmemory_mcp_url,
                 "provider": "openmemory_mcp",
                 "critical": False,
             }

1289-1294: Top-level import for uvicorn (style consistency).

Optional per guidelines; avoids mid-function import.

-if __name__ == "__main__":
-    import uvicorn
+if __name__ == "__main__":

And near the file’s imports:

import uvicorn
extras/openmemory-mcp/setup.sh (4)

10-13: Quote backup filename (SC2046) to avoid word splitting/globbing.

ShellCheck flagged this; quoting is sufficient.

-    cp .env .env.backup.$(date +%Y%m%d_%H%M%S)
+    cp .env ".env.backup.$(date +%Y%m%d_%H%M%S)"

41-44: Avoid global directory state; use a subshell for the checkout.

Prevents surprising PWD changes if the script is sourced or extended.

-cd cache/mem0
-echo "  Checking out fix/get-endpoint branch..."
-git checkout fix/get-endpoint
-cd ../..
+(
+  cd cache/mem0
+  echo "  Checking out fix/get-endpoint branch..."
+  git checkout fix/get-endpoint
+)

6-8: Add preflight tool checks for better UX.

Fail fast if git or docker are missing.

 echo "🧠 OpenMemory MCP Setup"
 echo "======================"
 
+# Preflight checks
+for cmd in git docker; do
+  if ! command -v "$cmd" >/dev/null 2>&1; then
+    echo "❌ Required tool not found: $cmd" >&2
+    exit 1
+  fi
+done

52-59: Support non-interactive setups via OPENAI_API_KEY env.

Use env var when present; otherwise prompt silently.

-while true; do
-    read -s -r -p "OpenAI API Key: " OPENAI_API_KEY
-    echo  # Print newline after silent input
-    if [ -n "$OPENAI_API_KEY" ]; then
-        break
-    fi
-    echo "Error: OpenAI API Key cannot be empty. Please try again."
-done
+if [ -z "${OPENAI_API_KEY:-}" ]; then
+  while true; do
+    read -s -r -p "OpenAI API Key: " OPENAI_API_KEY
+    echo
+    [ -n "$OPENAI_API_KEY" ] && break
+    echo "Error: OpenAI API Key cannot be empty. Please try again."
+  done
+else
+  echo "(Using OPENAI_API_KEY from environment)"
+fi
services.py (2)

70-81: Pass compose file explicitly for robustness.

If compose file names diverge from defaults, this avoids surprises.

-    cmd = ['docker', 'compose']
+    cmd = ['docker', 'compose', '-f', str(compose_file)]

122-126: Show recent build output on failure to aid debugging.

Print the last ~40 lines.

-            if process.returncode != 0:
-                console.print(f"\n[red]❌ Build failed for {service_name}[/red]")
-                return False
+            if process.returncode != 0:
+                console.print(f"\n[red]❌ Build failed for {service_name}[/red]")
+                if all_output:
+                    console.print("[red]— Last 40 lines —[/red]")
+                    for l in all_output[-40:]:
+                        console.print(f"  [dim]{l}[/dim]")
+                return False
init.py (2)

149-156: Remove unused variable and keep run side effects only.

result isn’t used.

-        result = subprocess.run(
-            cmd, 
-            cwd=service['path'],
-            check=True,
-            capture_output=True,
-            text=True,
-            timeout=300  # 5 minute timeout for service setup
-        )
+        subprocess.run(
+            cmd,
+            cwd=service['path'],
+            check=True,
+            capture_output=True,
+            text=True,
+            timeout=300,  # 5 minute timeout for service setup
+        )

262-303: Remove placeholder-less f-strings.

These trigger Ruff F541 and add no value.

-    console.print(f"\n🎊 [bold green]Setup Complete![/bold green]")
+    console.print("\n🎊 [bold green]Setup Complete![/bold green]")
@@
-    console.print(f"\n🚀 [bold]Enjoy Friend-Lite![/bold]")
+    console.print("\n🚀 [bold]Enjoy Friend-Lite![/bold]")
@@
-    console.print(f"\n💡 [dim]Tip: You can also setup services individually:[/dim]")
-    console.print(f"[dim]   cd backends/advanced && uv run --with-requirements setup-requirements.txt python init.py[/dim]")
+    console.print("\n💡 [dim]Tip: You can also setup services individually:[/dim]")
+    console.print("[dim]   cd backends/advanced && uv run --with-requirements setup-requirements.txt python init.py[/dim]")
backends/advanced/init.py (3)

328-331: Write nginx.conf next to this script (not CWD).

Avoids path surprises when invoked from other dirs.

-                    with open('nginx.conf', 'w') as f:
+                    with open(script_dir / 'nginx.conf', 'w') as f:
                         f.write(nginx_content)

335-337: Avoid blind except; narrow to filesystem errors.

Matches Ruff BLE001 guidance.

-                except Exception as e:
+                except (OSError, IOError) as e:
                     self.console.print(f"[yellow][WARNING][/yellow] nginx.conf generation failed: {e}")

508-510: Narrow the top-level exception handler (ruff BLE001).

Catch expected classes; let unexpected bubble.

-        except Exception as e:
+        except (subprocess.SubprocessError, OSError, IOError) as e:
             self.console.print(f"[red][ERROR][/red] Setup failed: {e}")
             sys.exit(1)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 939c2a1 and 6a442d3.

📒 Files selected for processing (9)
  • backends/advanced/init.py (1 hunks)
  • backends/advanced/setup-https.sh (1 hunks)
  • backends/advanced/src/advanced_omi_backend/main.py (5 hunks)
  • backends/advanced/webui/src/contexts/AuthContext.tsx (3 hunks)
  • backends/advanced/webui/src/pages/LoginPage.tsx (2 hunks)
  • extras/openmemory-mcp/setup.sh (1 hunks)
  • extras/speaker-recognition/scripts/enroll_speaker.py (1 hunks)
  • init.py (1 hunks)
  • services.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • backends/advanced/setup-https.sh
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Format Python code with Black using a 100-character line length
Use isort to organize Python imports
Never import modules in the middle of functions or files; all imports must be at the top of the Python file after the docstring
Group Python imports in the order: standard library, third-party, then local imports
Use lazy imports sparingly and only when absolutely necessary to resolve circular imports

Files:

  • extras/speaker-recognition/scripts/enroll_speaker.py
  • init.py
  • backends/advanced/src/advanced_omi_backend/main.py
  • backends/advanced/init.py
  • services.py
🧬 Code graph analysis (5)
backends/advanced/webui/src/pages/LoginPage.tsx (1)
backends/advanced/webui/src/services/api.ts (1)
  • BACKEND_URL (33-33)
init.py (3)
backends/advanced/init.py (2)
  • run (467-510)
  • main (513-528)
extras/speaker-recognition/scripts/enroll_speaker.py (1)
  • main (258-318)
services.py (1)
  • main (223-284)
backends/advanced/src/advanced_omi_backend/main.py (6)
backends/advanced/src/advanced_omi_backend/memory/providers/vector_stores.py (1)
  • test_connection (298-308)
backends/advanced/src/advanced_omi_backend/memory/compat_service.py (1)
  • test_connection (359-371)
backends/advanced/src/advanced_omi_backend/memory/providers/openmemory_mcp_service.py (1)
  • test_connection (330-342)
backends/advanced/src/advanced_omi_backend/memory/memory_service.py (1)
  • test_connection (339-351)
backends/advanced/src/advanced_omi_backend/memory/providers/mcp_client.py (1)
  • test_connection (331-356)
backends/advanced/src/advanced_omi_backend/memory/providers/llm_providers.py (1)
  • test_connection (233-252)
backends/advanced/init.py (2)
init.py (1)
  • main (197-303)
services.py (1)
  • main (223-284)
services.py (2)
backends/advanced/init.py (2)
  • run (467-510)
  • main (513-528)
init.py (1)
  • main (197-303)
🪛 Shellcheck (0.10.0)
extras/openmemory-mcp/setup.sh

[warning] 12-12: Quote this to prevent word splitting.

(SC2046)

🪛 Ruff (0.12.2)
extras/speaker-recognition/scripts/enroll_speaker.py

196-196: subprocess call: check for execution of untrusted input

(S603)

init.py

149-149: Local variable result is assigned to but never used

Remove assignment to unused variable result

(F841)


149-149: subprocess call: check for execution of untrusted input

(S603)


159-159: Consider moving this statement to an else block

(TRY300)


178-178: Do not catch blind exception: Exception

(BLE001)


262-262: f-string without any placeholders

Remove extraneous f prefix

(F541)


299-299: f-string without any placeholders

Remove extraneous f prefix

(F541)


302-302: f-string without any placeholders

Remove extraneous f prefix

(F541)


303-303: f-string without any placeholders

Remove extraneous f prefix

(F541)

backends/advanced/src/advanced_omi_backend/main.py

390-390: Unused function argument: request

(ARG001)


404-404: Unused function argument: request

(ARG001)


418-418: Unused function argument: request

(ARG001)


455-455: Do not catch blind exception: Exception

(BLE001)


467-467: Do not catch blind exception: Exception

(BLE001)


468-468: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


1095-1095: Do not catch blind exception: Exception

(BLE001)


1097-1097: Use explicit conversion flag

Replace with conversion flag

(RUF010)


1233-1233: Do not catch blind exception: Exception

(BLE001)


1235-1235: Use explicit conversion flag

Replace with conversion flag

(RUF010)

backends/advanced/init.py

1-1: Shebang is present but file is not executable

(EXE001)


308-308: subprocess call: check for execution of untrusted input

(S603)


335-335: Do not catch blind exception: Exception

(BLE001)


508-508: Do not catch blind exception: Exception

(BLE001)

services.py

86-86: subprocess call: check for execution of untrusted input

(S603)


98-98: Use of assert detected

(S101)


130-130: subprocess call: check for execution of untrusted input

(S603)


142-142: f-string without any placeholders

Remove extraneous f prefix

(F541)


153-153: Do not catch blind exception: Exception

(BLE001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: integration-tests

Comment on lines +443 to +478
@app.get("/api/auth/health")
async def auth_health_check():
"""Pre-flight health check for authentication service connectivity."""
try:
# Test database connectivity
await mongo_client.admin.command("ping")

# Test memory service if available
if memory_service:
try:
await asyncio.wait_for(memory_service.test_connection(), timeout=2.0)
memory_status = "ok"
except Exception as e:
logger.warning(f"Memory service health check failed: {e}")
memory_status = "degraded"
else:
memory_status = "unavailable"

return {
"status": "ok",
"database": "ok",
"memory_service": memory_status,
"timestamp": int(time.time())
}
except Exception as e:
logger.error(f"Auth health check failed: {e}")
return JSONResponse(
status_code=500,
content={
"status": "error",
"detail": "Service connectivity check failed",
"error_type": "connection_failure",
"timestamp": int(time.time())
}
)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add a real timeout for Mongo ping and log full trace on failure.

Pre-flight health can hang without timeout; use asyncio.wait_for and logger.exception.

 @app.get("/api/auth/health")
 async def auth_health_check():
     """Pre-flight health check for authentication service connectivity."""
     try:
-        # Test database connectivity
-        await mongo_client.admin.command("ping")
+        # Test database connectivity (bounded)
+        await asyncio.wait_for(mongo_client.admin.command("ping"), timeout=2.0)
@@
-    except Exception as e:
-        logger.error(f"Auth health check failed: {e}")
+    except Exception:
+        logger.exception("Auth health check failed")
         return JSONResponse(
             status_code=500,
             content={
                 "status": "error",
                 "detail": "Service connectivity check failed",
                 "error_type": "connection_failure",
                 "timestamp": int(time.time())
             }
         )
📝 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.

Suggested change
@app.get("/api/auth/health")
async def auth_health_check():
"""Pre-flight health check for authentication service connectivity."""
try:
# Test database connectivity
await mongo_client.admin.command("ping")
# Test memory service if available
if memory_service:
try:
await asyncio.wait_for(memory_service.test_connection(), timeout=2.0)
memory_status = "ok"
except Exception as e:
logger.warning(f"Memory service health check failed: {e}")
memory_status = "degraded"
else:
memory_status = "unavailable"
return {
"status": "ok",
"database": "ok",
"memory_service": memory_status,
"timestamp": int(time.time())
}
except Exception as e:
logger.error(f"Auth health check failed: {e}")
return JSONResponse(
status_code=500,
content={
"status": "error",
"detail": "Service connectivity check failed",
"error_type": "connection_failure",
"timestamp": int(time.time())
}
)
@app.get("/api/auth/health")
async def auth_health_check():
"""Pre-flight health check for authentication service connectivity."""
try:
# Test database connectivity (bounded)
await asyncio.wait_for(mongo_client.admin.command("ping"), timeout=2.0)
# Test memory service if available
if memory_service:
try:
await asyncio.wait_for(memory_service.test_connection(), timeout=2.0)
memory_status = "ok"
except Exception as e:
logger.warning(f"Memory service health check failed: {e}")
memory_status = "degraded"
else:
memory_status = "unavailable"
return {
"status": "ok",
"database": "ok",
"memory_service": memory_status,
"timestamp": int(time.time())
}
except Exception:
logger.exception("Auth health check failed")
return JSONResponse(
status_code=500,
content={
"status": "error",
"detail": "Service connectivity check failed",
"error_type": "connection_failure",
"timestamp": int(time.time())
}
)
🧰 Tools
🪛 Ruff (0.12.2)

455-455: Do not catch blind exception: Exception

(BLE001)


467-467: Do not catch blind exception: Exception

(BLE001)


468-468: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

🤖 Prompt for AI Agents
In backends/advanced/src/advanced_omi_backend/main.py around lines 443 to 478,
the MongoDB ping is currently unbounded and failures are logged without a full
traceback; wrap the await mongo_client.admin.command("ping") call in
asyncio.wait_for with a short timeout (e.g., 2.0 seconds) to prevent hanging,
and in the outer except block replace logger.error with logger.exception (or
call logger.exception in addition) so the full stack trace is recorded; ensure
asyncio is imported/available and adjust the error handling/response as needed
while keeping the response behavior unchanged.

Comment on lines +61 to +69
# Update .env file safely using awk - replace existing line or append if missing
temp_file=$(mktemp)
awk -v key="$OPENAI_API_KEY" '
/^OPENAI_API_KEY=/ { print "OPENAI_API_KEY=" key; found=1; next }
{ print }
END { if (!found) print "OPENAI_API_KEY=" key }
' .env > "$temp_file"
mv "$temp_file" .env

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure .env remains 600 after in-place update.

mv may drop earlier chmod depending on umask; re-apply permissions after replace.

-mv "$temp_file" .env
+mv "$temp_file" .env
+chmod 600 .env
📝 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.

Suggested change
# Update .env file safely using awk - replace existing line or append if missing
temp_file=$(mktemp)
awk -v key="$OPENAI_API_KEY" '
/^OPENAI_API_KEY=/ { print "OPENAI_API_KEY=" key; found=1; next }
{ print }
END { if (!found) print "OPENAI_API_KEY=" key }
' .env > "$temp_file"
mv "$temp_file" .env
# Update .env file safely using awk - replace existing line or append if missing
temp_file=$(mktemp)
awk -v key="$OPENAI_API_KEY" '
/^OPENAI_API_KEY=/ { print "OPENAI_API_KEY=" key; found=1; next }
{ print }
END { if (!found) print "OPENAI_API_KEY=" key }
' .env > "$temp_file"
mv "$temp_file" .env
chmod 600 .env
🤖 Prompt for AI Agents
In extras/openmemory-mcp/setup.sh around lines 61 to 69, the script replaces
.env by writing to a temp file and then mv'ing it into place but does not
preserve file permissions; re-apply secure permissions after the replacement to
ensure .env remains 600. Update the flow to either set the temp file mode to 600
before the mv or (safer) run chmod 600 .env immediately after mv "$temp_file"
.env so the resulting .env always has permission 600 regardless of umask.

Comment on lines +165 to +176
console.print(f"❌ {service_name} setup timed out after {e.timeout} seconds")
if e.stdout:
console.print(f" stdout: {e.stdout.decode()}")
if e.stderr:
console.print(f" stderr: {e.stderr.decode()}")
return False
except subprocess.CalledProcessError as e:
console.print(f"❌ {service_name} setup failed with exit code {e.returncode}")
if e.stdout:
console.print(f" stdout: {e.stdout.decode()}")
if e.stderr:
console.print(f" stderr: {e.stderr.decode()}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: decoding text stdout/stderr raises AttributeError (strings have no .decode).

With text=True, e.stdout/e.stderr are strings. Print directly with None guards.

-    except subprocess.TimeoutExpired as e:
+    except subprocess.TimeoutExpired as e:
         console.print(f"❌ {service_name} setup timed out after {e.timeout} seconds")
-        if e.stdout:
-            console.print(f"   stdout: {e.stdout.decode()}")
-        if e.stderr:
-            console.print(f"   stderr: {e.stderr.decode()}")
+        if e.stdout:
+            console.print(f"   stdout: {e.stdout}")
+        if e.stderr:
+            console.print(f"   stderr: {e.stderr}")
         return False
-    except subprocess.CalledProcessError as e:
+    except subprocess.CalledProcessError as e:
         console.print(f"❌ {service_name} setup failed with exit code {e.returncode}")
-        if e.stdout:
-            console.print(f"   stdout: {e.stdout.decode()}")
-        if e.stderr:
-            console.print(f"   stderr: {e.stderr.decode()}")
+        if e.stdout:
+            console.print(f"   stdout: {e.stdout}")
+        if e.stderr:
+            console.print(f"   stderr: {e.stderr}")
         return False
📝 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.

Suggested change
console.print(f"❌ {service_name} setup timed out after {e.timeout} seconds")
if e.stdout:
console.print(f" stdout: {e.stdout.decode()}")
if e.stderr:
console.print(f" stderr: {e.stderr.decode()}")
return False
except subprocess.CalledProcessError as e:
console.print(f"❌ {service_name} setup failed with exit code {e.returncode}")
if e.stdout:
console.print(f" stdout: {e.stdout.decode()}")
if e.stderr:
console.print(f" stderr: {e.stderr.decode()}")
except subprocess.TimeoutExpired as e:
console.print(f"❌ {service_name} setup timed out after {e.timeout} seconds")
if e.stdout:
console.print(f" stdout: {e.stdout}")
if e.stderr:
console.print(f" stderr: {e.stderr}")
return False
except subprocess.CalledProcessError as e:
console.print(f"❌ {service_name} setup failed with exit code {e.returncode}")
if e.stdout:
console.print(f" stdout: {e.stdout}")
if e.stderr:
console.print(f" stderr: {e.stderr}")
return False
🤖 Prompt for AI Agents
In init.py around lines 165 to 176, the exception handlers call .decode() on
e.stdout/e.stderr which will raise AttributeError when subprocess was invoked
with text=True (stdout/stderr are already strings); change those checks to guard
against None and print the values directly (e.g. if e.stdout is not None:
console.print(f"   stdout: {e.stdout}")) for both the TimeoutExpired and
CalledProcessError blocks so no .decode() is used.

Comment on lines +18 to +43
SERVICES = {
'backend': {
'path': 'backends/advanced',
'compose_file': 'docker-compose.yml',
'description': 'Advanced Backend + WebUI',
'ports': ['8000', '5173']
},
'speaker-recognition': {
'path': 'extras/speaker-recognition',
'compose_file': 'docker-compose.yml',
'description': 'Speaker Recognition Service',
'ports': ['8085', '5174/8444']
},
'asr-services': {
'path': 'extras/asr-services',
'compose_file': 'docker-compose.yml',
'description': 'Parakeet ASR Service',
'ports': ['8767']
},
'openmemory-mcp': {
'path': 'extras/openmemory-mcp',
'compose_file': 'docker-compose.yml',
'description': 'OpenMemory MCP Server',
'ports': ['8765']
}
}
Copy link
Contributor

@coderabbitai coderabbitai bot Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify port entries match docker-compose.yml across services.

There are known mismatches; confirm and align (e.g., backend UI, ASR, openmemory-mcp UI).


🏁 Script executed:

#!/bin/bash
# Verify exposed host ports in compose files vs SERVICES mapping
set -Eeuo pipefail
declare -A svc_paths=(
  [backend]="backends/advanced/docker-compose.yml"
  [speaker-recognition]="extras/speaker-recognition/docker-compose.yml"
  [asr-services]="extras/asr-services/docker-compose.yml"
  [openmemory-mcp]="extras/openmemory-mcp/docker-compose.yml"
)
for s in "${!svc_paths[@]}"; do
  f="${svc_paths[$s]}"
  echo "== $s -> $f"
  if [ -f "$f" ]; then
    rg -nP '^\s*-\s*"?(?<host>\d{2,5})(?::\d{2,5})?"?' "$f" -or '$host' | sort -u | paste -sd, -
  else
    echo "missing compose file"
  fi
done
echo "SERVICES.ports in services.py:"
rg -nP "^\s*'ports':\s*\[.*\]" services.py -n -C1

Length of output: 776


🏁 Script executed:

#!/usr/bin/env bash
set -Eeuo pipefail

# List compose files and their ports sections
declare -a services=(
  "backend:backends/advanced/docker-compose.yml"
  "speaker-recognition:extras/speaker-recognition/docker-compose.yml"
  "asr-services:extras/asr-services/docker-compose.yml"
  "openmemory-mcp:extras/openmemory-mcp/docker-compose.yml"
)

for entry in "${services[@]}"; do
  IFS=":" read -r name file <<< "$entry"
  echo -e "\n== $name ($file) =="
  if [[ -f "$file" ]]; then
    grep -nE 'ports:' -A5 "$file"
  else
    echo "File not found"
  fi
done

echo -e "\nCurrent SERVICES.ports mapping in services.py:"
grep -nE "'ports':\s*\[.*\]" services.py -C1

Length of output: 5275


Align SERVICES.ports with actual host ports in docker-compose.yml

  • speaker-recognition: update its ports list to include the React UI port (${REACT_UI_PORT}) and the Nginx ports (8444, 8081), not just 8085.
  • openmemory-mcp: include the Qdrant port (6335) and the UI port (3001) alongside 8765.
  • backend (8000, 5173) and asr-services (8767) already match their compose files.
🤖 Prompt for AI Agents
In services.py around lines 18 to 43, the SERVICES dictionary has incorrect port
lists for some services; update speaker-recognition.ports to include the React
UI host port (from ${REACT_UI_PORT}) and the nginx/external ports 8444 and 8081
in addition to 8085, and update openmemory-mcp.ports to include Qdrant port 6335
and the UI port 3001 alongside 8765; leave backend and asr-services ports
unchanged.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@claude can you fix this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link

@claude claude bot Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude encountered an error —— View job


I'll analyze this and get back to you.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
Docs/init-system.md (2)

98-99: Linux caveat: include docker run variant and scope.

Nice addition. Also mention the docker run flag and that this applies when containers need to reach services on the host.

-Note (Linux): If `host.docker.internal` is unavailable, add `extra_hosts: - "host.docker.internal:host-gateway"` to the relevant services in `docker-compose.yml`.
+Note (Linux): If `host.docker.internal` is unavailable, add `extra_hosts: - "host.docker.internal:host-gateway"` to the relevant services in `docker-compose.yml`. For `docker run`, use `--add-host=host.docker.internal:host-gateway`. This applies when containers must reach services bound on the host.

10-10: Clarify “setup scripts don’t start services” — call out exceptions.

This line contradicts the ASR/OpenMemory sections that are startup scripts. Suggest softening the blanket statement and naming the exceptions.

-The root orchestrator handles service selection and delegates configuration to individual service scripts. **Setup scripts only configure - they don't start services automatically.** This prevents unnecessary resource usage and gives you control over when services actually run.
+The root orchestrator handles service selection and delegates configuration to individual service scripts. In general, setup scripts only configure and do not start services automatically. **Exceptions:** `extras/asr-services` and `extras/openmemory-mcp` include startup scripts. This prevents unnecessary resource usage and gives you control over when services actually run.
🧹 Nitpick comments (6)
Docs/init-system.md (6)

7-9: Resolve “init.py does configuration” vs “Root Orchestrator does NOT handle service-specific configuration.”

Wording in Overview implies init.py sets up service configurations, but the Root Orchestrator section says it does not handle service-specific config. Rephrase to emphasize orchestration/hand-off.

-- **Configuration** (`init.py`) - Set up service configurations, API keys, and .env files
+- **Orchestration** (`init.py`) - Coordinate selections, delegate to per-service setup scripts, and write cross-service URL hand-offs to per-service `.env` files
 - **Service Management** (`services.py`) - Start, stop, and manage running services
@@
-- **Purpose**: Service selection and delegation only
-- **Does NOT**: Handle service-specific configuration or duplicate setup logic
+- **Purpose**: Service selection, coordination, and hand-off (writes cross-service URLs to per-service `.env` files)
+- **Does NOT**: Implement service-specific prompts/business logic or duplicate setup logic

Also applies to: 15-17


118-123: Prefer service names for intra-Compose networking; reserve host.docker.internal for container→host.

Using host.docker.internal everywhere can be misleading. Suggest a one-line clarification.

-Services use `host.docker.internal` for inter-container communication:
+For containers that must reach services on the host, use `host.docker.internal`. If services share the same Compose project/network, prefer service names (e.g., `http://speaker-service:8085`) for inter-container communication:

110-116: Port collision risk: both WebUIs default to 5173. Add a note or adjust defaults.

Backend WebUI and Speaker Recognition WebUI both list 5173; they can’t run simultaneously on the same host without a remap.

 - **Backend WebUI**: http://localhost:5173  
@@
 - **Speaker Recognition WebUI**: http://localhost:5173
+Note: The Backend and Speaker Recognition WebUIs both default to port 5173. If you run both, change one port (e.g., via Vite's `VITE_PORT` or a Compose port mapping) to avoid conflicts.

25-27: Add a short “Prerequisites” section before Usage.

Reduces setup friction by stating required tooling up front.

 ## Usage
+
+## Prerequisites
+- Docker (Compose v2)
+- Python 3.11+
+- `uv` installed
+- Optional: NVIDIA Container Toolkit (for GPU paths)

85-85: Fix markdownlint MD034 (bare URLs) or suppress.

Convert bare URLs to markdown links or add an explicit lint ignore. Example:

-**WebUI**: Available at http://localhost:8765
+**WebUI**: Available at [http://localhost:8765](http://localhost:8765)

Also applies to: 110-116


169-171: Where are .env backups stored and how are they named?

Add a brief note (e.g., .env.bak.YYYYmmdd-HHMMSS) to help users locate/restore.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ffce264 and d3f53f1.

📒 Files selected for processing (1)
  • Docs/init-system.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
Docs/init-system.md

[grammar] ~14-~14: There might be a mistake here.
Context: ... ## Architecture ### Root Orchestrator - Location: /init.py - Purpose: Se...

(QB_NEW_EN)


[grammar] ~15-~15: There might be a mistake here.
Context: ... ### Root Orchestrator - Location: /init.py - Purpose: Service selection and delegat...

(QB_NEW_EN)


[grammar] ~16-~16: There might be a mistake here.
Context: ...*: Service selection and delegation only - Does NOT: Handle service-specific conf...

(QB_NEW_EN)


[grammar] ~19-~19: There might be a mistake here.
Context: ...plicate setup logic ### Service Scripts - Backend: backends/advanced/init.py -...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ... Complete Python-based interactive setup - Speaker Recognition: `extras/speaker-r...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...ecognition/setup.sh- Simple bash setup - **ASR Services**:extras/asr-services/set...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...vices/setup.sh- Service startup script - **OpenMemory MCP**:extras/openmemory-mcp...

(QB_NEW_EN)


[grammar] ~27-~27: There might be a mistake here.
Context: ...ge ### Orchestrated Setup (Recommended) Set up multiple services together with a...

(QB_NEW_EN)


[grammar] ~35-~35: There might be a mistake here.
Context: ...thon init.py ``` The orchestrator will: 1. Show service status and availability 2. ...

(QB_NEW_EN)


[grammar] ~41-~41: There might be a mistake here.
Context: ...g services ### Individual Service Setup Each service can be configured independe...

(QB_NEW_EN)


[grammar] ~64-~64: There might be a mistake here.
Context: ...## Service Details ### Advanced Backend - Interactive setup for authentication, ...

(QB_NEW_EN)


[grammar] ~65-~65: There might be a mistake here.
Context: ...LLM, transcription, and memory providers - Accepts arguments: `--speaker-service-...

(QB_NEW_EN)


[grammar] ~67-~67: There might be a mistake here.
Context: ...nv` file with all required configuration - Default ports: Backend (8000), WebUI (...

(QB_NEW_EN)


[grammar] ~70-~70: There might be a mistake here.
Context: ...), WebUI (5173) ### Speaker Recognition - Prompts for: Hugging Face token, compu...

(QB_NEW_EN)


[grammar] ~71-~71: There might be a mistake here.
Context: ...gging Face token, compute mode (cpu/gpu) - Service port: 8085 - WebUI port: 5...

(QB_NEW_EN)


[grammar] ~72-~72: There might be a mistake here.
Context: ... mode (cpu/gpu) - Service port: 8085 - WebUI port: 5173 - Requires: HF_TO...

(QB_NEW_EN)


[grammar] ~73-~73: There might be a mistake here.
Context: ...vice port**: 8085 - WebUI port: 5173 - Requires: HF_TOKEN for pyannote models...

(QB_NEW_EN)


[grammar] ~76-~76: There might be a mistake here.
Context: ...EN for pyannote models ### ASR Services - Starts: Parakeet ASR service via Docke...

(QB_NEW_EN)


[grammar] ~77-~77: There might be a mistake here.
Context: ... Parakeet ASR service via Docker Compose - Service port: 8767 - Purpose: Offl...

(QB_NEW_EN)


[grammar] ~78-~78: There might be a mistake here.
Context: ...ocker Compose - Service port: 8767 - Purpose: Offline speech-to-text proces...

(QB_NEW_EN)


[grammar] ~79-~79: There might be a mistake here.
Context: ...ose**: Offline speech-to-text processing - No configuration required ### OpenMem...

(QB_NEW_EN)


[grammar] ~82-~82: There might be a mistake here.
Context: ...iguration required** ### OpenMemory MCP - Starts: External OpenMemory MCP server...

(QB_NEW_EN)


[grammar] ~83-~83: There might be a mistake here.
Context: ...Starts**: External OpenMemory MCP server - Service port: 8765 - WebUI: Avai...

(QB_NEW_EN)


[grammar] ~84-~84: There might be a mistake here.
Context: ...mory MCP server - Service port: 8765 - WebUI: Available at http://localhost:8...

(QB_NEW_EN)


[grammar] ~85-~85: There might be a mistake here.
Context: ...UI**: Available at http://localhost:8765 - Purpose: Cross-client memory compatibi...

(QB_NEW_EN)


[grammar] ~92-~92: There might be a mistake here.
Context: ...th | |----------------------|----------------...

(QB_NEW_EN)


[grammar] ~93-~93: There might be a mistake here.
Context: ...---------------------------------------| | Speaker Recognition | `SPEAKER_SERVIC...

(QB_NEW_EN)


[grammar] ~94-~94: There might be a mistake here.
Context: ...//host.docker.internal:8085 | | ASR Services |PARAKEET_ASR_U...

(QB_NEW_EN)


[grammar] ~110-~110: There might be a mistake here.
Context: ...vice URLs ### Default Service Endpoints - Backend API: http://localhost:8000 - *...

(QB_NEW_EN)


[grammar] ~111-~111: There might be a mistake here.
Context: ...- Backend API: http://localhost:8000 - Backend WebUI: http://localhost:5173 ...

(QB_NEW_EN)


[grammar] ~112-~112: There might be a mistake here.
Context: ...Backend WebUI: http://localhost:5173 - Speaker Recognition: http://localhost:...

(QB_NEW_EN)


[grammar] ~113-~113: There might be a mistake here.
Context: ...ker Recognition**: http://localhost:8085 - Speaker Recognition WebUI: http://loca...

(QB_NEW_EN)


[grammar] ~114-~114: There might be a mistake here.
Context: ...cognition WebUI**: http://localhost:5173 - Parakeet ASR: http://localhost:8767 - ...

(QB_NEW_EN)


[grammar] ~115-~115: There might be a mistake here.
Context: ... Parakeet ASR: http://localhost:8767 - OpenMemory MCP: http://localhost:8765 ...

(QB_NEW_EN)


[grammar] ~118-~118: There might be a mistake here.
Context: ...### Container-to-Container Communication Services use host.docker.internal for ...

(QB_NEW_EN)


[grammar] ~128-~128: There might be a mistake here.
Context: ...ement**: ### Unified Service Management Use the services.py script for all ser...

(QB_NEW_EN)


[grammar] ~148-~148: There might be a mistake here.
Context: ...y-mcp ``` ### Manual Service Management You can also manage services individuall...

(QB_NEW_EN)


[grammar] ~169-~169: There might be a mistake here.
Context: ...on/.env- Speaker service configuration - All services backup existing.env` file...

(QB_NEW_EN)


[grammar] ~172-~172: There might be a mistake here.
Context: ...automatically ### Required Dependencies - Root: setup-requirements.txt (rich>=...

(QB_NEW_EN)


[grammar] ~173-~173: There might be a mistake here.
Context: ... setup-requirements.txt (rich>=13.0.0) - Backend: setup-requirements.txt (ric...

(QB_NEW_EN)


[grammar] ~174-~174: There might be a mistake here.
Context: ...ments.txt` (rich>=13.0.0, pyyaml>=6.0.0) - Extras: No additional setup dependenci...

(QB_NEW_EN)


[grammar] ~179-~179: There might be a mistake here.
Context: ...d ## Troubleshooting ### Common Issues - Port conflicts: Check if services are ...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
Docs/init-system.md

85-85: Bare URL used

(MD034, no-bare-urls)


111-111: Bare URL used

(MD034, no-bare-urls)


112-112: Bare URL used

(MD034, no-bare-urls)


113-113: Bare URL used

(MD034, no-bare-urls)


114-114: Bare URL used

(MD034, no-bare-urls)


115-115: Bare URL used

(MD034, no-bare-urls)


116-116: Bare URL used

(MD034, no-bare-urls)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: integration-tests
🔇 Additional comments (1)
Docs/init-system.md (1)

187-195: Verify health endpoints and expected status codes.

Confirm these endpoints exist and return 200 OK; if different, document expected paths/status.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
Docs/init-system.md (4)

97-99: Linux caveat: include docker run mapping example as well.

You added the Compose extra_hosts note; add the docker run variant for completeness.

-Note (Linux): If `host.docker.internal` is unavailable, add `extra_hosts: - "host.docker.internal:host-gateway"` to the relevant services in `docker-compose.yml`.
+Note (Linux): If `host.docker.internal` is unavailable, add `extra_hosts: - "host.docker.internal:host-gateway"` in `docker-compose.yml`,  
+or when using `docker run` add: `--add-host=host.docker.internal:host-gateway`.

85-85: Fix markdownlint MD034: replace bare URLs with links or code spans.

Convert these to [text](url) or wrap endpoints in backticks to suppress lints and improve readability.

Example changes:

-**WebUI**: Available at http://localhost:8765
+**WebUI**: Available at [http://localhost:8765](http://localhost:8765)

-- **Backend API**: http://localhost:8000
+- **Backend API**: `http://localhost:8000`
-- **Backend WebUI**: http://localhost:5173
+- **Backend WebUI**: `http://localhost:5173`
-- **Speaker Recognition**: http://localhost:8085
+- **Speaker Recognition**: `http://localhost:8085`
-- **Parakeet ASR**: http://localhost:8767
+- **Parakeet ASR**: `http://localhost:8767`
-- **OpenMemory MCP**: http://localhost:8765
+- **OpenMemory MCP**: `http://localhost:8765`

Also applies to: 111-116


167-171: Document .env backup naming/location.

Specify the backup filename pattern (e.g., .env.bak with timestamp) and whether it stays in-place or moves to a backups/ dir.

-- All services backup existing `.env` files automatically
+- All services back up existing `.env` files automatically (e.g., `.env.bak.2025-09-01T12-00-00` in the same directory).

124-146: List canonical service names accepted by services.py.

Add a short list/table so users know the exact tokens (backend, speaker-recognition, asr-services, openmemory-mcp) and any aliases.

Example addition:

 ### Unified Service Management
 Use the `services.py` script for all service operations:
+
+Accepted service names: `backend`, `speaker-recognition`, `asr-services`, `openmemory-mcp`.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d3f53f1 and f9ef977.

📒 Files selected for processing (1)
  • Docs/init-system.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
Docs/init-system.md

[grammar] ~14-~14: There might be a mistake here.
Context: ... ## Architecture ### Root Orchestrator - Location: /init.py - Purpose: Se...

(QB_NEW_EN)


[grammar] ~15-~15: There might be a mistake here.
Context: ... ### Root Orchestrator - Location: /init.py - Purpose: Service selection and delegat...

(QB_NEW_EN)


[grammar] ~16-~16: There might be a mistake here.
Context: ...*: Service selection and delegation only - Does NOT: Handle service-specific conf...

(QB_NEW_EN)


[grammar] ~19-~19: There might be a mistake here.
Context: ...plicate setup logic ### Service Scripts - Backend: backends/advanced/init.py -...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ... Complete Python-based interactive setup - Speaker Recognition: `extras/speaker-r...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...ecognition/setup.sh- Simple bash setup - **ASR Services**:extras/asr-services/set...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...vices/setup.sh- Service startup script - **OpenMemory MCP**:extras/openmemory-mcp...

(QB_NEW_EN)


[grammar] ~27-~27: There might be a mistake here.
Context: ...ge ### Orchestrated Setup (Recommended) Set up multiple services together with a...

(QB_NEW_EN)


[grammar] ~35-~35: There might be a mistake here.
Context: ...thon init.py ``` The orchestrator will: 1. Show service status and availability 2. ...

(QB_NEW_EN)


[grammar] ~41-~41: There might be a mistake here.
Context: ...g services ### Individual Service Setup Each service can be configured independe...

(QB_NEW_EN)


[grammar] ~64-~64: There might be a mistake here.
Context: ...## Service Details ### Advanced Backend - Interactive setup for authentication, ...

(QB_NEW_EN)


[grammar] ~65-~65: There might be a mistake here.
Context: ...LLM, transcription, and memory providers - Accepts arguments: `--speaker-service-...

(QB_NEW_EN)


[grammar] ~67-~67: There might be a mistake here.
Context: ...nv` file with all required configuration - Default ports: Backend (8000), WebUI (...

(QB_NEW_EN)


[grammar] ~70-~70: There might be a mistake here.
Context: ...), WebUI (5173) ### Speaker Recognition - Prompts for: Hugging Face token, compu...

(QB_NEW_EN)


[grammar] ~71-~71: There might be a mistake here.
Context: ...gging Face token, compute mode (cpu/gpu) - Service port: 8085 - WebUI port: 5...

(QB_NEW_EN)


[grammar] ~72-~72: There might be a mistake here.
Context: ... mode (cpu/gpu) - Service port: 8085 - WebUI port: 5173 - Requires: HF_TO...

(QB_NEW_EN)


[grammar] ~73-~73: There might be a mistake here.
Context: ...vice port**: 8085 - WebUI port: 5173 - Requires: HF_TOKEN for pyannote models...

(QB_NEW_EN)


[grammar] ~76-~76: There might be a mistake here.
Context: ...EN for pyannote models ### ASR Services - Starts: Parakeet ASR service via Docke...

(QB_NEW_EN)


[grammar] ~77-~77: There might be a mistake here.
Context: ... Parakeet ASR service via Docker Compose - Service port: 8767 - Purpose: Offl...

(QB_NEW_EN)


[grammar] ~78-~78: There might be a mistake here.
Context: ...ocker Compose - Service port: 8767 - Purpose: Offline speech-to-text proces...

(QB_NEW_EN)


[grammar] ~79-~79: There might be a mistake here.
Context: ...ose**: Offline speech-to-text processing - No configuration required ### OpenMem...

(QB_NEW_EN)


[grammar] ~82-~82: There might be a mistake here.
Context: ...iguration required** ### OpenMemory MCP - Starts: External OpenMemory MCP server...

(QB_NEW_EN)


[grammar] ~83-~83: There might be a mistake here.
Context: ...Starts**: External OpenMemory MCP server - Service port: 8765 - WebUI: Avai...

(QB_NEW_EN)


[grammar] ~84-~84: There might be a mistake here.
Context: ...mory MCP server - Service port: 8765 - WebUI: Available at http://localhost:8...

(QB_NEW_EN)


[grammar] ~85-~85: There might be a mistake here.
Context: ...UI**: Available at http://localhost:8765 - Purpose: Cross-client memory compatibi...

(QB_NEW_EN)


[grammar] ~92-~92: There might be a mistake here.
Context: ...th | |----------------------|----------------...

(QB_NEW_EN)


[grammar] ~93-~93: There might be a mistake here.
Context: ...---------------------------------------| | Speaker Recognition | `SPEAKER_SERVIC...

(QB_NEW_EN)


[grammar] ~94-~94: There might be a mistake here.
Context: ...//host.docker.internal:8085 | | ASR Services |PARAKEET_ASR_U...

(QB_NEW_EN)


[grammar] ~110-~110: There might be a mistake here.
Context: ...vice URLs ### Default Service Endpoints - Backend API: http://localhost:8000 - *...

(QB_NEW_EN)


[grammar] ~111-~111: There might be a mistake here.
Context: ...- Backend API: http://localhost:8000 - Backend WebUI: http://localhost:5173 ...

(QB_NEW_EN)


[grammar] ~112-~112: There might be a mistake here.
Context: ...Backend WebUI: http://localhost:5173 - Speaker Recognition: http://localhost:...

(QB_NEW_EN)


[grammar] ~113-~113: There might be a mistake here.
Context: ...ker Recognition**: http://localhost:8085 - Speaker Recognition WebUI: http://loca...

(QB_NEW_EN)


[grammar] ~114-~114: There might be a mistake here.
Context: ...cognition WebUI**: http://localhost:5173 - Parakeet ASR: http://localhost:8767 - ...

(QB_NEW_EN)


[grammar] ~115-~115: There might be a mistake here.
Context: ... Parakeet ASR: http://localhost:8767 - OpenMemory MCP: http://localhost:8765 ...

(QB_NEW_EN)


[grammar] ~118-~118: There might be a mistake here.
Context: ...### Container-to-Container Communication Services use host.docker.internal for ...

(QB_NEW_EN)


[grammar] ~128-~128: There might be a mistake here.
Context: ...ement**: ### Unified Service Management Use the services.py script for all ser...

(QB_NEW_EN)


[grammar] ~148-~148: There might be a mistake here.
Context: ...y-mcp ``` ### Manual Service Management You can also manage services individuall...

(QB_NEW_EN)


[grammar] ~169-~169: There might be a mistake here.
Context: ...on/.env- Speaker service configuration - All services backup existing.env` file...

(QB_NEW_EN)


[grammar] ~172-~172: There might be a mistake here.
Context: ...automatically ### Required Dependencies - Root: setup-requirements.txt (rich>=...

(QB_NEW_EN)


[grammar] ~173-~173: There might be a mistake here.
Context: ... setup-requirements.txt (rich>=13.0.0) - Backend: setup-requirements.txt (ric...

(QB_NEW_EN)


[grammar] ~174-~174: There might be a mistake here.
Context: ...ments.txt` (rich>=13.0.0, pyyaml>=6.0.0) - Extras: No additional setup dependenci...

(QB_NEW_EN)


[grammar] ~179-~179: There might be a mistake here.
Context: ...d ## Troubleshooting ### Common Issues - Port conflicts: Check if services are ...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
Docs/init-system.md

85-85: Bare URL used

(MD034, no-bare-urls)


111-111: Bare URL used

(MD034, no-bare-urls)


112-112: Bare URL used

(MD034, no-bare-urls)


113-113: Bare URL used

(MD034, no-bare-urls)


114-114: Bare URL used

(MD034, no-bare-urls)


115-115: Bare URL used

(MD034, no-bare-urls)


116-116: Bare URL used

(MD034, no-bare-urls)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: integration-tests
🔇 Additional comments (4)
Docs/init-system.md (4)

10-10: Thanks for fixing the earlier contradiction about setup scripts.

The explicit exceptions for ASR and OpenMemory MCP resolve the inconsistency from prior reviews. Looks good.


82-86: Confirm “WebUI” for OpenMemory MCP is accurate.

If OpenMemory MCP exposes only an API and no UI, reword to “endpoint” and remove the WebUI claim.

Suggested edit if no UI:

-**WebUI**: Available at [http://localhost:8765](http://localhost:8765)
+**Endpoint**: `http://localhost:8765`

31-33: Docs/init-system.md and services CLI validated: setup-requirements.txt files are present at the project root and backends/advanced, and services.py supports the --all and --build flags used in the documentation.


187-195: Health endpoints verified as correct. All three services—the Backend (backends/advanced/src/advanced_omi_backend/main.py @ /health), Speaker Recognition (simple_speaker_recognition/api/service.py @ /health), and ASR (parakeet-offline.py @ /health)—expose the /health route, so no changes to the docs are needed.

Comment on lines +68 to +69
- **Default ports**: Backend (8000), WebUI (5173)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Port collision: both Backend WebUI and Speaker Recognition WebUI set to 5173.

Running both concurrently on the host will fail. Either change one default (e.g., Speaker WebUI → 5174) or add a note explaining how to override.

Suggested doc tweak:

- **Default ports**: Backend (8000), WebUI (5173)
+ **Default ports**: Backend (8000), WebUI (5173)  
+ Note: If Speaker Recognition WebUI is also running, change one UI port to avoid a host-port conflict.

- **WebUI port**: 5173
+ **WebUI port**: 5174 (changeable)

Run to confirm actual exposed ports in compose files:

Also applies to: 70-74


🏁 Script executed:

#!/bin/bash
rg -nC2 -g '!**/node_modules/**' -P 'ports:\s*\n(?:\s*-.*\n)*|["'\'']5173["'\'']' --type=yaml

Length of output: 95


🏁 Script executed:

#!/bin/bash
# Search for any references to port 5173
rg -n "5173" --color=never

# Search for 'ports:' entries in all YAML files
rg -n "ports:" -g "*.yml" -g "*.yaml" --color=never

# Search for environment variable defining port in .env files
rg -n "VITE_PORT" -g ".env*" --color=never

# Search for 'port' keyword in Vite config files
rg -n "port" -g "vite.config.*" --color=never

Length of output: 11501


Clarify and document port override to avoid WebUI collision
In Docs/init-system.md (lines 68–74), add a note that the Speaker Recognition WebUI also defaults to port 5173 and must be overridden to avoid a host‐port conflict. Suggested diff:

- **Default ports**: Backend (8000), WebUI (5173)
+ **Default ports**: Backend (8000), WebUI (5173)  
+ ⚠️ If you’re running Speaker Recognition WebUI on the same host, override one of the UI ports (e.g. set REACT_UI_PORT or WEBUI_PORT to 5174) to prevent collision.

- **WebUI port**: 5173
+ **WebUI port**: 5173 (changeable via ENV var; e.g., REACT_UI_PORT)
📝 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.

Suggested change
- **Default ports**: Backend (8000), WebUI (5173)
- **Default ports**: Backend (8000), WebUI (5173)
⚠️ If you’re running Speaker Recognition WebUI on the same host, override one of the UI ports (e.g. set REACT_UI_PORT or WEBUI_PORT to 5174) to prevent collision.
- **WebUI port**: 5173 (changeable via ENV var; e.g., REACT_UI_PORT)
🤖 Prompt for AI Agents
In Docs/init-system.md around lines 68 to 69, the docs list default ports but
don’t mention that the Speaker Recognition WebUI also defaults to 5173, which
can cause a host-port conflict; update the section to explicitly note this
collision and add a short instruction that users must override the Speaker
Recognition WebUI port (or the WebUI backend port) when both are run on the same
host, giving an example of where to change the port and a brief pointer to the
config or env var to set.

Comment on lines +118 to +123
### Container-to-Container Communication
Services use `host.docker.internal` for inter-container communication:
- `http://host.docker.internal:8085` - Speaker Recognition
- `http://host.docker.internal:8767` - Parakeet ASR
- `http://host.docker.internal:8765` - OpenMemory MCP

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Clarify when to use host.docker.internal vs service DNS names.

For containers on the same Compose network, prefer service names (DNS) over host.docker.internal, which targets the host, not peer containers.

Proposed wording:

-Services use `host.docker.internal` for inter-container communication:
+When services run in the same Docker Compose project, use service DNS names (e.g., `http://speaker-service:8085`).  
+Use `host.docker.internal` only when a container must call a service running on the host OS:

And adapt the bullets accordingly (add examples with service names).

📝 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.

Suggested change
### Container-to-Container Communication
Services use `host.docker.internal` for inter-container communication:
- `http://host.docker.internal:8085` - Speaker Recognition
- `http://host.docker.internal:8767` - Parakeet ASR
- `http://host.docker.internal:8765` - OpenMemory MCP
### Container-to-Container Communication
When services run in the same Docker Compose project, use service DNS names (e.g., `http://speaker-service:8085`).
Use `host.docker.internal` only when a container must call a service running on the host OS:
- `http://host.docker.internal:8085` - Speaker Recognition
- `http://host.docker.internal:8767` - Parakeet ASR
- `http://host.docker.internal:8765` - OpenMemory MCP
🧰 Tools
🪛 LanguageTool

[grammar] ~118-~118: There might be a mistake here.
Context: ...### Container-to-Container Communication Services use host.docker.internal for ...

(QB_NEW_EN)

🤖 Prompt for AI Agents
In Docs/init-system.md around lines 118 to 123, the guidance currently
recommends using host.docker.internal for inter-container communication but that
confuses host vs peer container addressing; update the text to clarify that
containers on the same Docker Compose network should use service DNS names
(e.g., http://speaker-recognition:8085, http://parakeet-asr:8767,
http://openmemory-mcp:8765) and reserve host.docker.internal for accessing
services running on the host machine from inside containers; modify the three
bullets to show both the preferred service-name examples and note the
host.docker.internal case where appropriate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant