Blue Jay

Blue Jay is a local-first cybersecurity assistant built with Python. It helps users analyze network scans, web exposure, DNS records, and system logs in authorized environments, turning raw technical security data into structured findings, remediation guidance, and Markdown reports.

I designed the tool around defensive security learning, home lab workflows, and IT support practice. It combines controlled Nmap scanning, target allowlisting, DNS and web security checks, SQLite-backed asset tracking, findings management, baseline comparisons, and local chat transcripts.

The project uses Ollama for local LLM analysis so scan data and logs stay on the user's machine instead of being sent to a cloud API. Blue Jay focuses on safe, authorized workflows: users define allowed targets, run repeatable checks, compare current results against baselines, and export defensive reports with practical remediation steps.

Status: Local-first defensive security project

Tools Used: Python, Ollama, Nmap, SQLite, Rich, prompt_toolkit, GitHub Actions CI

System Architecture

Blue Jay is a local-first defensive security assistant. It provides a terminal interface for controlled security checks, stores evidence locally, and uses local Ollama models to explain scan results and generate reports.

Architecture Overview
flowchart TB
    User[User / Security Learner] --> CLI[Terminal CLI
bluejay.cli] CLI --> Router[Command Router
bluejay.commands] CLI --> Chat[Local Chat Loop
bluejay.chat] Router --> SystemCmds[System Commands
help, status, config, files] Router --> WorkflowCmds[Workflow Commands
scan, vuln, dig, web, site, nuclei] Router --> FindingCmds[Finding Commands
findings, triage, remediate, report] WorkflowCmds --> TargetSafety[Target Validation
bluejay.targets] TargetSafety --> Allowlist[allowed_targets.txt
localhost + private LAN + approved public targets] WorkflowCmds --> Nmap[Nmap Workflows
bluejay.nmap] WorkflowCmds --> DNS[DNS Collection
dig / resolver checks] WorkflowCmds --> HTTP[HTTP / TLS Checks
bluejay.http_checks] WorkflowCmds --> SiteAudit[Site Audit
bluejay.site_audit] WorkflowCmds --> Nuclei[Optional Nuclei
bluejay.nuclei] Nmap --> RawScans[Raw Scan Files
scans/] DNS --> Logs[Evidence Logs
logs/] HTTP --> Logs SiteAudit --> Logs Nuclei --> Logs Nmap --> Storage[SQLite Storage
bluejay.storage
data/bluejay.db] HTTP --> Storage SiteAudit --> Storage Nuclei --> Storage Storage --> Findings[Assets, Scans,
Evidence, Findings] FindingCmds --> Findings Chat --> Ollama[Local Ollama Models] WorkflowCmds --> Analysis[Local Analysis
bluejay.analysis] FindingCmds --> Reports[Report Builder
bluejay.reports] Analysis --> Ollama Reports --> Ollama Findings --> Reports RawScans --> Analysis Logs --> Analysis Analysis --> MarkdownReports[Markdown Reports
reports/] Reports --> MarkdownReports Chat --> ChatLogs[Saved Chat Transcripts
chats/]
Main Components
Component Responsibility
app.py Thin entrypoint for launching the application.
bluejay.cli Starts the terminal UI, chat loop, and slash-command handling.
bluejay.commands Routes slash commands to the correct command module.
bluejay.cmd_system Handles help, status, config, files, reports, and chat commands.
bluejay.cmd_workflows Handles scan, DNS, web, site audit, Nuclei, profile, and analysis workflows.
bluejay.cmd_findings Handles assets, findings, triage, remediation, retest, baselines, diffs, and reports.
bluejay.targets Normalizes targets and enforces safe target rules.
bluejay.nmap Runs controlled Nmap scans and parses XML output into structured findings.
bluejay.http_checks Performs HTTP, TLS, security-header, and cookie checks.
bluejay.site_audit Runs bounded same-origin crawling and website checks.
bluejay.nuclei Runs optional bounded Nuclei scans and parses JSONL output.
bluejay.storage Stores assets, scans, evidence, and findings in SQLite.
bluejay.analysis Sends scan/log evidence to local Ollama models for defensive analysis.
bluejay.reports Builds evidence-based Markdown reports from stored findings.
bluejay.config Manages local model profile configuration.
bluejay.ui Provides terminal rendering, prompts, history, and completions.
Data Flow
sequenceDiagram
    actor User
    participant CLI as Blue Jay CLI
    participant Safety as Target Safety
    participant Tool as Security Tool
    participant Files as scans/ and logs/
    participant DB as SQLite Database
    participant LLM as Local Ollama Model
    participant Reports as reports/

    User->>CLI: Run slash command
    CLI->>Safety: Validate target and options
    Safety-->>CLI: Approved target
    CLI->>Tool: Run controlled scan or check
    Tool-->>Files: Save raw evidence
    Tool-->>DB: Store assets, scans, evidence, findings
    CLI->>LLM: Analyze saved evidence locally
    LLM-->>CLI: Defensive explanation and recommendations
    CLI->>Reports: Save Markdown report
    CLI-->>User: Show summary and next steps
                    
Command Workflow
flowchart LR
    Start[User enters command] --> Parse[Parse command and arguments]
    Parse --> CommandType{Command type}

    CommandType -->|System| System[Show status, config, files, help, reports]
    CommandType -->|Scan / Web / DNS| Validate[Validate target and safety rules]
    CommandType -->|Findings| Query[Query stored findings and assets]
    CommandType -->|Chat| LocalChat[Send prompt to local model]

    Validate --> Approved{Allowed?}
    Approved -->|No| Reject[Reject command with safety message]
    Approved -->|Yes| Execute[Run bounded tool workflow]

    Execute --> SaveEvidence[Save raw evidence]
    SaveEvidence --> ParseEvidence[Parse structured results]
    ParseEvidence --> Store[Store assets, scans, evidence, findings]
    Store --> Analyze[Analyze with local model]
    Analyze --> Report[Generate Markdown report]

    Query --> Triage[Show triage, remediation, retest, diff, or report]
    LocalChat --> ChatTranscript[Save local chat transcript]
                    
Local Storage Model
erDiagram
    ASSET ||--o{ SCAN : has
    ASSET ||--o{ FINDING : has
    SCAN ||--o{ EVIDENCE : produces
    FINDING ||--o{ EVIDENCE : supported_by
    BASELINE ||--o{ FINDING : compares_against

    ASSET {
        string target
        string address
        string type
        datetime first_seen
        datetime last_seen
    }

    SCAN {
        string scan_id
        string target
        string mode
        string tool
        datetime created_at
    }

    FINDING {
        string finding_id
        string target
        string title
        string severity
        string status
        datetime first_seen
        datetime last_seen
        int times_seen
    }

    EVIDENCE {
        string evidence_id
        string finding_id
        string source
        string file_path
        datetime created_at
    }

    BASELINE {
        string scope
        datetime created_at
        string finding_snapshot
    }
                    
Model Profiles
flowchart TB
    Config[data/config.json] --> Profiles[Model Profiles]
    Profiles --> ChatModel[Chat Model
general guidance] Profiles --> AnalysisModel[Analysis Model
scan and log reports] Profiles --> ExplainModel[Explain Model
finding explanations] ChatModel --> Ollama[Ollama Runtime] AnalysisModel --> Ollama ExplainModel --> Ollama Ollama --> LocalOnly[Local Processing
no cloud API required]
Safety Boundaries

Target validation is applied before active workflows run so Blue Jay stays focused on defensive and authorized use.

flowchart TD
    Target[Requested Target] --> Normalize[Normalize host, URL, or IP]
    Normalize --> Localhost{Localhost?}
    Normalize --> PrivateLAN{Private LAN IP?}
    Normalize --> PublicTarget{Public target?}

    Localhost -->|Yes| Allow[Allow]
    PrivateLAN -->|Yes| Allow
    PublicTarget --> CheckAllowlist[Check allowed_targets.txt]
    CheckAllowlist -->|Listed| Allow
    CheckAllowlist -->|Not listed| Block[Block]

    Allow --> Run[Run bounded scan/check]
    Block --> Message[Show responsible-use message]
                    
Deployment Model
flowchart LR
    Python[Python 3.10+] --> BlueJay[Blue Jay CLI]
    BlueJay --> Ollama[Ollama]
    BlueJay --> Nmap[Nmap]
    BlueJay --> OptionalNuclei[Optional Nuclei]
    BlueJay --> SQLite[SQLite]
    BlueJay --> LocalFolders[Local workspace folders
scans, logs, reports, chats, data]
Storage Layout
Blue Jay workspace
  data/bluejay.db      # SQLite assets, scans, evidence, findings, baselines
  scans/               # raw Nmap scan files
  logs/                # DNS, HTTP/TLS, site audit, and Nuclei evidence
  reports/             # generated Markdown reports
  chats/               # saved local chat transcripts
  allowed_targets.txt  # approved public targets outside localhost/private LAN
Summary
  • The CLI provides a beginner-friendly interactive interface.
  • Command modules separate system, workflow, and finding-management behavior.
  • Tool integrations collect raw evidence from Nmap, DNS, HTTP/TLS checks, website audits, and optional Nuclei scans.
  • SQLite stores assets, scans, evidence, findings, baselines, and scan history.
  • Local Ollama models generate explanations and reports without sending scan data to a cloud service.
  • Markdown reports and saved chat transcripts provide repeatable learning and remediation records.

YummyTummy AI

YummyTummy AI is a full-stack recipe assistant that helps people cook with what they already have at home. Users can enter ingredients, dietary needs, preferences, or cooking goals, then receive practical recipe suggestions through a simple chat-based interface.

I designed and built the platform end to end using Deno, TypeScript, Tailwind CSS, and the Groq API for AI-powered recipe generation and natural language prompts. I worked across backend logic, front-end UX, authentication, saved chats, local chat history, recipe browsing, and account pages so the app felt simple, useful, and easy to use.

The main challenge was making AI output feel reliable in an everyday cooking context while handling async data flows, rate limiting, guarded responses, and serverless deployment on Deno Deploy. It is a strong example of how I combine modern web development, API integration, UX thinking, and practical AI workflows to ship a finished product.

Status: Live project

Tools Used: Deno runtime, TypeScript, Tailwind CSS, Groq API, Supabase, Spoonacular API, Deno KV

System Architecture

YummyTummy AI is a single Deno TypeScript application service. It serves static frontend assets from public/, exposes JSON endpoints from src/server/app.ts, is served over HTTPS in production, and keeps server-owned integrations and persistence behind feature modules.

Runtime Topology
flowchart TB
  browser[User Browser]

  subgraph frontend[Static frontend served from public/]
    direction LR
    pages[HTML pages]
    styles[CSS and vendor assets]
    scripts[Page and feature JS modules]
    pages --> styles
    pages --> scripts
  end

  subgraph service[Deno HTTP service]
    direction TB
    main[src/main.ts]
    runtime[src/server/runtime.ts
env and deploy config] app[src/server/app.ts
route dispatcher] static[Static page and asset serving] controls[Security headers
same-origin checks
rate limits] api[JSON API handlers] main --> runtime runtime --> app app --> static app --> controls controls --> api end subgraph modules[Server feature modules] direction LR auth[Auth and sessions] chat[Chat orchestration] pantry[Pantry and recipe book] saved[Saved chats] end subgraph providers[External services and stores] direction LR supabase[Supabase
Auth and PostgREST tables] groq[Groq API
models and chat completions] spoonacular[Spoonacular API
recipe search and detail] kv[Deno KV or memory fallback] end browser -->|GET pages/assets| static static --> frontend scripts -->|fetch JSON| api api --> auth api --> chat api --> pantry api --> saved auth --> supabase chat --> groq chat --> supabase chat --> kv pantry --> spoonacular pantry --> supabase saved --> supabase
Backend Boundaries
flowchart TB
  app[src/server/app.ts]

  app --> static[Static and HTML routes
/, /about, /recipes
/account, /auth] app --> health[Health and config routes
/health, /chat-models
/auth/client-config] app --> authRoutes[Auth routes
register, login, logout
MFA, change password] app --> meRoutes[Account routes
/me, /me/account, /me/profile] app --> chatRoute[Chat route
POST /chat] app --> pantryRoutes[Pantry routes
search, detail
recipe book] app --> savedRoutes[Saved chat routes
/saved-chats] authRoutes --> authFeature[src/features/auth] meRoutes --> authFeature chatRoute --> chatFeature[src/features/chat] chatRoute --> pantryFeature[src/features/pantry] pantryRoutes --> pantryFeature savedRoutes --> savedFeature[src/features/savedChats] authFeature --> supabaseAuth[Supabase Auth API] authFeature --> profiles[profiles table] chatFeature --> groq[Groq chat completions] chatFeature --> chatHistories[chat_histories table] chatFeature --> chatQuotas[chat_quotas table] pantryFeature --> spoon[Spoonacular API] pantryFeature --> pantryTables[pantry_recipes and user_recipe_book tables] savedFeature --> savedTable[saved_chats table]
Chat Request Lifecycle
sequenceDiagram
  autonumber
  participant Browser
  participant App as Deno app
src/server/app.ts participant Auth as Auth/session participant Guard as Chat guardrails participant Store as Supabase tables participant Pantry as Pantry helpers participant Groq as Groq API Browser->>App: POST /chat App->>App: Validate JSON and same-origin write App->>Auth: Resolve auth cookie or guest session Auth-->>App: Owner key and optional user profile App->>App: Apply IP/session rate limits App->>Store: Consume daily chat quota Store-->>App: Allowed/remaining quota App->>Guard: Check prompt injection and food-domain fit Guard-->>App: Safe response path or refusal steer App->>Store: Load and normalize chat history App->>Pantry: Resolve recipe context when message needs it Pantry-->>App: Optional recipe suggestions/detail steer App->>App: Detect chat mode and build prompt steer App->>Groq: Chat completion request Groq-->>App: Assistant text App->>Store: Persist user and assistant messages App-->>Browser: JSON response with reply and quota state
Persistence Map
erDiagram
  SUPABASE_AUTH_USERS ||--o| PROFILES : owns
  SUPABASE_AUTH_USERS ||--o{ SAVED_CHATS : saves
  SUPABASE_AUTH_USERS ||--o{ USER_RECIPE_BOOK : owns
  PANTRY_RECIPES ||--o{ USER_RECIPE_BOOK : referenced_by

  CHAT_HISTORIES {
    text owner_key PK
    jsonb messages
    timestamp updated_at
  }

  CHAT_QUOTAS {
    text owner_key PK
    jsonb timestamps
    timestamp updated_at
  }

  PROFILES {
    uuid user_id PK
    text_array dietary_requirements
    text_array allergies
    text_array dislikes
  }

  SAVED_CHATS {
    uuid id PK
    uuid user_id FK
    text title
    jsonb history
    timestamp created_at
    timestamp updated_at
  }

  PANTRY_RECIPES {
    uuid id PK
    int spoonacular_id
    text title
    text image
    int ready_in_minutes
    int servings
    jsonb ingredients
    text source_url
  }

  USER_RECIPE_BOOK {
    uuid id PK
    uuid user_id FK
    uuid pantry_recipe_id FK
    timestamp created_at
    timestamp updated_at
  }
                    
Deployment Shape
flowchart LR
  denoTask[deno task start/dev/run]
  main[src/main.ts]
  config[src/server/runtime.ts
env validation] service[Deno service process] denoTask --> main main --> config main --> service service --> env[Required env vars
GROQ_API_KEY, SUPABASE_URL
SUPABASE_ANON_KEY, SERVICE_ROLE_KEY] service --> optional[Optional env vars
SPOONACULAR_API_KEY, MODEL
GROQ_MODELS, hosts]
Storage Layout
Supabase
  auth.users          # managed user authentication
  profiles            # dietary preferences, allergies, dislikes
  saved_chats         # named saved conversations
  chat_histories      # owner-keyed chat state
  chat_quotas         # daily usage timestamps
  pantry_recipes      # cached Spoonacular recipe records
  user_recipe_book    # user saved recipe links

Deno KV / memory
  ephemeral app state, guest/session limits, and runtime counters

Supabase owns durable user, chat, and recipe data, while Deno KV or in-memory storage handles short-lived application state depending on the deployment environment.

Key Responsibilities
Module Responsibility
src/main.ts Starts the Deno application service and loads the server runtime.
src/server/app.ts Owns routing, request orchestration, static page delivery, JSON endpoints, and feature dispatch.
src/server/security.ts Applies security headers including CSP, HSTS, and other browser-facing protections.
src/server/rateLimit.ts Enforces IP and session limits to protect chat and account routes from abuse.
src/features/auth Handles registration, login, sessions, MFA, profile state, and account routes.
src/features/chat Builds prompts, applies guardrails, manages chat modes/history/quota, and calls Groq completions.
src/features/pantry Searches and stores recipes through Spoonacular and Supabase-backed recipe book tables.
src/features/savedChats Provides saved chat CRUD backed by the saved_chats table.
public/ Contains the static HTML, CSS, and browser modules served by the Deno service.
Deployment Notes

The app runs as a Deno TypeScript service with deno task start, deno task dev, or an equivalent deployment command. In production, HTTPS is handled by the deployment platform while the app process handles routing, static assets, and JSON endpoints. Production configuration depends on Groq and Supabase credentials, with Spoonacular and model/host settings supplied through optional environment variables.

Vault Music

Vault Music is an in-development macOS music player built with Python. It is focused on making local music feel simple again, with support planned around core formats like MP3, WAV, and FLAC.

The project is about ownership over music libraries and a quieter listening experience: open your files, play your albums, and keep the interface clear instead of turning music into another crowded platform. Its direction is inspired by Soulseek and other early-2000s music apps that treated collections, discovery, and local files as something personal.

Vault Music is still in active development, with the current focus on reliable playback, clean library handling, and shaping a Mac-first interface that stays lightweight and direct.

Status: In active development

Tools Used: Python, Tkinter, macOS AppKit NSSound, NumPy, afconvert

System Architecture

Vault Music is a local-first macOS desktop audio-library app. The application is a single Python process with a Tkinter UI, a local JSON-backed library, copied audio files in Application Support, and macOS-native playback through NSSound.

System Context
flowchart LR
    user[User] --> app[Vault Music Desktop App]
    finder[macOS Finder / File Dialogs] --> app

    app --> appkit[macOS AppKit NSSound]
    app --> afconvert[macOS afconvert]
    app --> localdata[(Application Support Data)]
    app --> exportfolder[(Export Folder)]

    localdata --> libraryjson[library.json]
    localdata --> playlistsjson[playlists.json]
    localdata --> songsdir[songs/
copied audio files] app --> assets[Bundled app assets]
Component Architecture
flowchart TD
    main[main.py
Process entry point] --> ui[AudioPlayerApp
audio_player.app] subgraph UI["Tkinter Desktop UI"] ui --> songsview[Songs view] ui --> albumsview[Albums view] ui --> playlistview[Playlist sidebar] ui --> transport[Transport controls] ui --> waveformcanvas[Waveform canvas] end subgraph Domain["Application Domain"] library[LibraryManager
audio_player.library] models[Song and AlbumSummary
audio_player.models] exporter[Playlist bundle exporter
audio_player.exporter] utils[Formatting and path helpers
audio_player.utils] end subgraph Services["Platform / Analysis Services"] playback[NSSoundBackend
audio_player.playback] waveform[Waveform peak builder
audio_player.waveform] bpm[BPM analyzer
audio_player.bpm] config[Path builder
audio_player.config] end subgraph Storage["Local Storage"] appdata[(Application Support Data
or AUDIOPLAYER_DATA_DIR)] songs[(songs/)] librarydb[(library.json)] playlistdb[(playlists.json)] legacy[(legacy songs/ and playlists/)] end subgraph External["External Dependencies"] nssound[macOS AppKit NSSound] afconvert[afconvert
/usr/bin/afconvert] numpy[NumPy] pyobjc[PyObjC Cocoa bridge] end ui --> library ui --> playback ui --> waveform ui --> bpm ui --> config ui --> assets[App icons
assets/app_icon files] library --> models library --> exporter library --> utils library --> appdata library --> songs library --> librarydb library --> playlistdb library --> legacy exporter --> utils exporter --> songs exporter --> exportdir[(Export playlist folder)] config --> appdata playback --> nssound playback --> pyobjc waveform --> afconvert bpm --> afconvert bpm --> numpy
Startup And State Sync
sequenceDiagram
    actor User
    participant Main as main.py
    participant App as AudioPlayerApp
    participant Config as build_paths()
    participant Library as LibraryManager
    participant Disk as Application Support

    User->>Main: Launch app
    Main->>App: Create Tk root and app
    App->>Config: Resolve paths
    Config-->>App: AppPaths
    App->>Library: Initialize with paths
    Library->>Disk: Ensure app data and songs folders
    Library->>Disk: Import legacy songs if needed
    Library->>Disk: Read library.json and playlists.json
    Library->>Disk: Sync JSON library with songs/
    Library-->>App: Loaded library and playlists
    App->>App: Build UI and refresh views
                    
Import Songs Or Albums
sequenceDiagram
    actor User
    participant App as AudioPlayerApp
    participant Library as LibraryManager
    participant Disk as songs/ and JSON state

    User->>App: Choose audio files or album folder
    App->>Library: import_files() or import_album()
    Library->>Disk: Copy supported .mp3, .wav, .flac files
    Library->>Disk: Generate unique safe filenames
    Library->>Library: Sync Song records with copied files
    Library->>Disk: Save library.json
    Library-->>App: Imported song records
    App->>App: Refresh Songs, Albums, and Playlist views
                    
Playback And Waveform
sequenceDiagram
    actor User
    participant App as AudioPlayerApp
    participant Library as LibraryManager
    participant Player as NSSoundBackend
    participant Waveform as waveform worker thread
    participant AppKit as macOS NSSound
    participant Disk as songs/

    User->>App: Play selected song
    App->>Library: Resolve Song and file path
    Library-->>App: Song path under songs/
    App->>Player: play(path)
    Player->>AppKit: Load and play with NSSound
    App->>Waveform: Start daemon thread
    Waveform->>Disk: Read WAV or convert with afconvert
    Waveform-->>App: Schedule waveform peaks on Tk event loop
    App->>Library: increment_play_count()
    Library->>Disk: Save library.json
    App->>App: Poll playback every 250 ms and advance queue on finish
                    
BPM Analysis
sequenceDiagram
    actor User
    participant App as AudioPlayerApp
    participant Worker as BPM worker thread
    participant Analyzer as audio_player.bpm
    participant Library as LibraryManager
    participant Disk as songs/ and library.json

    User->>App: Analyze BPM for selected songs
    App->>Worker: Start daemon thread
    loop Each selected song
        Worker->>Analyzer: analyze_bpm(path)
        Analyzer->>Analyzer: Convert non-WAV with afconvert
        Analyzer->>Analyzer: Estimate tempo with NumPy
    end
    Worker-->>App: Schedule results on Tk event loop
    App->>Library: update_bpm(song_id, bpm)
    Library->>Disk: Save library.json
    App->>App: Refresh visible song rows
                    
Playlist Export
sequenceDiagram
    actor User
    participant App as AudioPlayerApp
    participant Library as LibraryManager
    participant Exporter as export_playlist_bundle()
    participant Source as songs/
    participant Target as Export folder

    User->>App: Export selected playlist
    App->>Library: export_playlist(name, destination)
    Library->>Library: Resolve playlist song ids to Song records
    Library->>Exporter: Build bundle
    Exporter->>Source: Read source audio files
    Exporter->>Target: Copy songs into playlist folder
    Exporter->>Target: Write .m3u8 playlist file
    Exporter-->>App: Export path, playlist file, missing files
                    
Data Model
erDiagram
    SONG {
        string id
        string filename
        string title
        string artist
        string album
        int play_count
        int bpm
    }

    PLAYLIST {
        string name
        string song_ids
    }

    ALBUM_SUMMARY {
        string key
        string title
        string artist_label
        int song_count
    }

    PLAYLIST }o--o{ SONG : contains
    ALBUM_SUMMARY ||--o{ SONG : groups_by_album
                    
Storage Layout
~/Library/Application Support/AudioPlayer/
  library.json       # serialized Song records
  playlists.json     # playlist name -> ordered song id list
  songs/             # copied local audio files used by the app

AUDIOPLAYER_DATA_DIR can override the Application Support directory for development and testing.

Key Responsibilities
Module Responsibility
main.py Starts the Tkinter application.
audio_player.app Owns UI state, event handling, queue behavior, playback polling, drag/drop interactions, and worker-thread result dispatch.
audio_player.config Resolves app data paths, supported file extensions, legacy paths, and icon candidates.
audio_player.library Imports files, synchronizes disk state, manages songs/albums/playlists, persists JSON, and delegates playlist export.
audio_player.models Defines Song and AlbumSummary data structures.
audio_player.playback Wraps macOS NSSound playback, pause/resume, stop, seek, duration, and completion detection.
audio_player.waveform Produces normalized waveform peaks from WAV data and converts non-WAV files with afconvert.
audio_player.bpm Estimates BPM using PCM analysis and NumPy, with afconvert conversion for non-WAV files.
audio_player.exporter Copies playlist audio files and writes portable .m3u8 playlist bundles.
audio_player.utils Provides sanitization, unique path generation, song labels, and time formatting.
Deployment Shape

The app runs from source with python main.py or can be packaged as a macOS app bundle using pyinstaller "Vault Music.spec". Runtime data remains outside the bundle in the configured Application Support directory.