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
- Built a local LLM-powered cybersecurity CLI for authorized network and log analysis.
- Integrated Nmap scans, DNS checks, web exposure checks, SQLite storage, and Markdown reports.
- Added asset history, findings tracking, remediation workflows, baselines, and current-vs-baseline diffs.
- Designed the tool around privacy and safety by processing data locally and enforcing target allowlisting.
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.