Skip to content

devisales/pulse

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Pulse

The app that hates your post-2000 songs. 😀

A distributed music streaming platform monorepo designed to explore production-grade architecture for Spotify-like experiences. The "post-2000 rejection" rule is an intentional product experiment used to exercise the event pipeline (Nirvana: yes, Justin Bieber: sorry).

If you try to hack it, you'll still run through validation, AI reasoning, and transcoding stages. All Linkin Park songs are allowed though. Somebody put an IF in the code base, damn! πŸ‘€

πŸ“– README Glossary

Quick links to every component's documentation, organized by layer.

πŸ“¦ Packages

Package README
@pack/kernel repos/packages/kernel/README.md
@pack/event-inventory repos/packages/event-inventory/README.md
@pack/env-orchestration repos/packages/env-orchestration/README.md
@pack/nats-broker-messaging repos/packages/nats-broker-messaging/README.md
@pack/cache repos/packages/cache/README.md
@pack/patterns repos/packages/patterns/README.md
@pack/neon-tokens repos/packages/neon-tokens/README.md

πŸ”· Microservices β€” Identity

Service README
Authority repos/domain/identity/authority/README.md
Slim Shady repos/domain/identity/slim-shady/README.md

πŸ”· Microservices β€” AI

Service README
Petrified repos/domain/ai/petrified/README.md
Fort Minor repos/domain/ai/fort-minor/README.md
Stereo repos/domain/ai/stereo/README.md

πŸ”· Microservices β€” Streaming

Service README
Soundgarden repos/domain/streaming/soundgarden/README.md
Mockingbird repos/domain/streaming/mockingbird/README.md
Hybrid Storage repos/domain/streaming/hybrid-storage/README.md

πŸ”· Microservices β€” Realtime

Service README
Backstage repos/domain/realtime/backstage/README.md

πŸ–₯️ Apps & πŸ€– Agents

Component README
Pulse (frontend) repos/apps/pulse/README.md
Shinoda (agent) repos/agents/shinoda/README.md

This repository combines a Next.js frontend, domain microservices, and internal platform packages to deliver resilient streaming, modular domain design, and a fast developer workflow.


Monorepo Structure πŸ—‚οΈ

Pulse is a pnpm workspace monorepo coordinated by Turborepo. The workspace topology isn't incidental tooling β€” it is the architecture. pnpm enforces explicit dependency boundaries between packages and services. Turbo makes shared packages first-class build inputs, so every service compiles against the same DDD vocabulary without version drift.

pulse/
└── repos/                       # πŸ—‚οΈ  All workspaces live under this folder
    β”œβ”€β”€ apps/                    # πŸ–₯️  User-facing applications
    β”‚   └── pulse/               #     Next.js 16 player UI + BFF
    β”‚
    β”œβ”€β”€ agents/                  # πŸ€–  AI orchestration agents
    β”‚   β”œβ”€β”€ shinoda/             #     Mastra-powered operations agent
    β”‚   └── .agents/             # πŸ“‹  Agent context, skills, and plans
    β”‚       β”œβ”€β”€ context/
    β”‚       β”œβ”€β”€ plans/
    β”‚       └── skills/
    β”‚
    β”œβ”€β”€ packages/                # πŸ“¦  Shared infrastructure libraries
    β”‚   β”œβ”€β”€ kernel/              #     DDD primitives (Entity, UseCase, EventBus…)
    β”‚   β”œβ”€β”€ event-inventory/     #     NATS event subject enums
    β”‚   β”œβ”€β”€ env-orchestration/  # Env-var helpers + docker-compose.yml
    β”‚   β”œβ”€β”€ nats-broker-messaging/     # NATS transport layer
    β”‚   β”œβ”€β”€ cache/               #     Redis cache abstraction
    β”‚   β”œβ”€β”€ patterns/            #     Circuit breaker + resilience patterns
    β”‚   └── neon-tokens/         #     OKLCH neon design tokens
    β”‚
    β”œβ”€β”€ domain/                  # πŸ”·  Bounded-context microservices
    β”‚   β”œβ”€β”€ identity/
    β”‚   β”‚   β”œβ”€β”€ authority/       #     Auth, JWT, sessions, OAuth
    β”‚   β”‚   └── slim-shady/      #     User profiles + preferences
    β”‚   β”œβ”€β”€ streaming/
    β”‚   β”‚   β”œβ”€β”€ soundgarden/     #     Upload ingestion edge
    β”‚   β”‚   β”œβ”€β”€ mockingbird/     #     HLS transcoder
    β”‚   β”‚   └── hybrid-storage/  #     HLS segment store + delivery
    β”‚   β”œβ”€β”€ ai/
    β”‚   β”‚   β”œβ”€β”€ petrified/       #     Audio fingerprinting + duplicate detection
    β”‚   β”‚   β”œβ”€β”€ fort-minor/      #     AI transcription (Whisper)
    β”‚   β”‚   └── stereo/          #     AI reasoning + approval/rejection
    β”‚   └── realtime/
    β”‚       └── backstage/       #     Pipeline projection + Socket.IO broadcast
    β”‚
    └── .agents/                 # πŸ“‹  Root-level agent context & plans

Workspace Naming Philosophy

The naming convention is intentional and layered:

Layer Style Rationale
apps/ Product names User-facing applications named after what they do
agents/ Music artists AI personalities with character β€” any music reference, not a fixed theme
packages/ Functional/descriptive Infrastructure must be self-evident
domain/<context>/ Business terminology DDD bounded contexts in plain language
domain/<context>/<service> Music references Each service has a story (see Nomenclature)

Components Nomenclature 🎡

Every microservice name is a music reference. This isn't decoration β€” it's a signal that the service has a distinct identity and a single well-scoped responsibility.

Service Music Reference Why It Fits
Soundgarden Band name A garden where you deposit audio β€” the ingestion entry point
Mockingbird Eminem song A bird that mimics sounds; the transcoder re-sings the original in new formats
Slim Shady Eminem alter ego The profile service hides behind the identity β€” a secondary persona
Authority "Points of Authority" (Linkin Park) Owns access control for the whole platform β€” it dictates what you're allowed to do
Shinod AI Mike Shinoda (Linkin Park) Orchestrates everything β€” just like Shinoda orchestrates LP's production
Petrified Fort Minor song Freezes audio identity β€” the fingerprinting module
Fort Minor Mike Shinoda's side project Extracts the voice β€” the transcription module
Stereo Fort Minor song Dual-channel thinking: merges fingerprint + transcription to decide
Hybrid Storage Hybrid Theory (Linkin Park debut album) "Hybrid" captures the dual responsibility β€” it both stores HLS segments to MinIO and serves them as a delivery provider for playback
Backstage Venue metaphor The place where the real show is observed, not performed

All Linkin Park songs pass the reasoning stage. Somebody put an IF in the codebase. πŸ‘€


Architecture πŸ›οΈ

Clean Architecture Per Service

Every microservice follows the same four-layer structure so the codebase stays navigable regardless of which service you're in:

<service>/
β”œβ”€β”€ domain/          # Entities, Value Objects, Events, Ports (abstract classes)
β”œβ”€β”€ application/     # Use Cases β€” extend UseCase from @pack/kernel
β”œβ”€β”€ infra/           # DB adapters, NATS wiring, MinIO, Redis, config
└── interface/       # HTTP controllers, NATS consumers, guards, DTOs, pipes

Ports are abstract classes, not TypeScript interfaces. This is a deliberate convention enforced across the entire repo β€” it allows NestJS DI tokens to be derived from the port class itself, keeping the adapter wiring clean.

Shared Packages

Packages are the architectural glue. They're not utilities β€” they're the shared vocabulary that keeps every service structurally consistent.

Package Exports Used By
@pack/kernel UseCase, DomainEntity, AggregateRoot, ValueObject, DomainEvent, EventBus, Id, EventMap All microservices
@pack/event-inventory AuthorityEvent, UserEvent, TrackEvent enums β€” canonical NATS subject names All microservices, agent
@pack/env-orchestration requireStringEnv, requireNumberEnv, optionalStringEnv, optionalNumberEnv Service bootstrap
@pack/nats-broker-messaging NatsPublisher, NatsConsumer, NatsEventBusAdapter, NestJS integration, error hierarchy All event-driven services
@pack/cache CachePort, RedisCacheAdapter, RedisLike port Petrified, Fort Minor
@pack/patterns CircuitBreaker, CircuitBreakerState, UniqueEntityId Authority, sync boundaries
@pack/neon-tokens 28 OKLCH neon color tokens, Tailwind overrides, gradient utilities (.bg-neon, .text-neon, .bg-neon-warm, .bg-neon-cool), .glassy-surface utility repos/apps/pulse

Backend Guidelines

@pack/kernel is the backend source of truth. Downstream services and packages must adapt to kernel; they must not redefine, shadow, or locally fork kernel abstractions.

Do not modify repos/packages/kernel as part of feature work or package-local fixes unless the task is explicitly a kernel change. If a downstream package conflicts with kernel, fix the downstream package instead of patching kernel.

Transport Model

Pulse is deliberately not a purely async platform. Each transport layer has a purpose:

Transport Used For
HTTP Frontend β†’ BFF, BFF β†’ services, control-plane access
NATS Async backend workflow orchestration between microservices
Socket.IO Realtime pipeline visibility from Backstage β†’ frontend

Frontend Architecture πŸ–₯️

repos/apps/pulse is a Next.js 16 App Router application that acts simultaneously as the player UI and a lightweight Backend for Frontend (BFF). The design reflects the platform's core rule β€” no post-2000 songs β€” through a SynthWave/Retrowave visual identity: deep purples, neon magentas, and glowing gradients that feel like a Linkin Park concert lit up in 1999.

Pulse on MacBook β€” Gallery and Uploader slots side by side


Route Groups and Parallel Slots πŸ—ΊοΈ

The entire routing model is built around Next.js route groups and parallel slots. Route groups establish top-level layout ownership without affecting URL structure; parallel slots let each UI region render and update independently.

app/
β”œβ”€β”€ (public)/                    # πŸ”“ Unauthenticated layout root
β”‚   └── (auth)/
β”‚       β”œβ”€β”€ login/
β”‚       └── signup/
β”‚
β”œβ”€β”€ (protected)/                 # πŸ”’ Auth-guarded layout root
β”‚   └── (player)/                #    Player shell β€” the main UI
β”‚       β”œβ”€β”€ layout.tsx           #    Owns the top-level grid, injects all slots
β”‚       β”œβ”€β”€ @gallery/            #    ↳ Parallel slot β€” track gallery list
β”‚       β”œβ”€β”€ @uploader/           #    ↳ Parallel slot β€” upload dropzone OR reasoning UI
β”‚       β”œβ”€β”€ @user-menu/          #    ↳ Parallel slot β€” user avatar + dropdown
β”‚       └── @now-playing/        #    ↳ Parallel slot β€” active playback bar
β”‚           β”œβ”€β”€ @track-metadata/ #       ↳ Nested slot β€” song title + artist
β”‚           β”œβ”€β”€ @playback/       #       ↳ Nested slot β€” prev / play-pause / next + scrubber
β”‚           └── @volume-bar/     #       ↳ Nested slot β€” volume control
β”‚
β”œβ”€β”€ api/                         # πŸ”€ BFF proxy routes
β”‚   β”œβ”€β”€ authority/               #    Login + signup forwarding
β”‚   β”œβ”€β”€ slim-shady/profile/      #    Profile read/update proxy
β”‚   β”œβ”€β”€ soundgarden/tracks/      #    Upload proxy
β”‚   └── transport/hls/           #    HLS segment delivery
β”‚
└── infra/                       # πŸ—οΈ  Frontend infrastructure (see below)
    β”œβ”€β”€ shadcn/                  #    Shadcn component registry (relocated)
    β”œβ”€β”€ immer/                   #    Immer state updater type helpers
    β”œβ”€β”€ next/                    #    Next.js PageError + layout type helpers
    └── zod/                     #    Zod schema primitives

Each parallel slot is a fully independent React subtree. The player layout receives gallery, uploader, now-playing, and user-menu as props and places them into the grid β€” no slot ever knows the others exist. This means @gallery can refetch its track list, @uploader can swap between dropzone and reasoning mode, and the playback bar keeps playing without any of them interfering with each other.

Interface independence. Because slots are isolated subtrees, a loading state, error boundary, or re-render in one slot is completely invisible to all others. In a Spotify-like player this matters: the gallery can show a loading spinner while the playback bar continues playing uninterrupted. The uploader can crash and recover without touching the track metadata display. There is no shared render tree that a single suspended slot can block.

Streaming per slot. Each parallel slot wraps its own Suspense boundary, which means Next.js can stream each region independently from the server. The page shell and the playback bar appear instantly; the gallery β€” which needs a data fetch β€” streams in behind them. The user sees a live player immediately, not a blank screen waiting for all data to resolve.

Mobile collapse without state duplication. The @now-playing slot is itself nested: it owns three inner parallel slots (@track-metadata, @playback, @volume-bar), each independently rendered. On mobile, @volume-bar and @track-metadata are hidden via Tailwind breakpoints and their content surfaced inside the user-menu dropdown instead β€” atoms are read from two different places in the tree simultaneously, without moving or duplicating state. No route change, no re-mount, no prop tunneling.


app/infra/ β€” Frontend Infrastructure Layer πŸ—οΈ

The infra/ folder inside app/ mirrors the Clean Architecture convention used by backend services. It is the frontend's adapter layer β€” third-party tools that need to be isolated from product code live here, not at the root.

app/infra/
β”œβ”€β”€ shadcn/                      # Shadcn UI component registry
β”‚   └── components/
β”‚       β”œβ”€β”€ ui/                  # Base components (Button, Input, Card, Slider…)
β”‚       └── ai-elements/         # AI SDK UI components
β”‚           β”œβ”€β”€ reasoning.tsx    # Reasoning collapsible (Vercel AI Elements port)
β”‚           └── shimmer.tsx      # Streaming shimmer animation
β”œβ”€β”€ immer/
β”‚   └── state-updater.type.ts    # Typed Immer producer helpers
β”œβ”€β”€ next/
β”‚   └── page-error.types.ts      # Next.js error boundary prop types
└── zod/
    └── schema primitives        # Reusable Zod schemas

Why infra/ for Shadcn? The default shadcn init places components at components/ui/. That works until you have a large codebase where components/ becomes a bucket for everything. Moving Shadcn into infra/shadcn/ makes the intent explicit: these are third-party primitives under an adapter boundary. Product components that compose them live in feature-local lib/ui/ folders, not here. The path alias @shadcn points to app/infra/shadcn/ across the whole app.

The ai-elements/ sub-folder hosts a port of the Vercel AI Elements Reasoning component β€” the same collapsible reasoning block used in Vercel's AI chat interfaces β€” adapted to consume the Backstage websocket stream instead of an AI SDK message stream.


State Management with Jotai πŸ”¬

State is fine-grained by design β€” Jotai operates like a marionette at the global level, with each atom controlling exactly one wire: volume, playback position in milliseconds, current track metadata, the gallery list, the session, authentication status. Pulling one wire never disturbs the others. At the component level, React's native state API with Immer handles local mutations β€” no global store is involved unless the state genuinely needs to cross slot boundaries. There are no large reducers, no context blobs.

app/lib/state/
β”œβ”€β”€ atoms/
β”‚   β”œβ”€β”€ is-authenticated.atom.ts   # atom<boolean>
β”‚   β”œβ”€β”€ is-reasoning.atom.ts       # atom<boolean>  ← gates uploader ↔ reasoning swap
β”‚   β”œβ”€β”€ is-paused.atom.ts          # atom<boolean>
β”‚   β”œβ”€β”€ current-track.atom.ts      # atomWithImmer<CurrentTrack>
β”‚   β”œβ”€β”€ gallery.atom.ts            # atomWithImmer<GalleryTrack[]>
β”‚   β”œβ”€β”€ profile.atom.ts            # atomWithImmer<Profile>
β”‚   β”œβ”€β”€ session.atom.ts            # atomWithImmer<Session>
β”‚   β”œβ”€β”€ progress.atom.ts           # atom<Progress>  { milliseconds: number }
β”‚   β”œβ”€β”€ volume.atom.ts             # atom<number>
β”‚   └── theme.atom.ts              # atom<Theme>
β”œβ”€β”€ domain/                        # Frontend domain types
β”‚   β”œβ”€β”€ current-track.domain.ts
β”‚   β”œβ”€β”€ gallery-track.domain.ts
β”‚   β”œβ”€β”€ progress.domain.ts
β”‚   β”œβ”€β”€ volume.enum.ts             # Volume.Loud | Moderate | Quiet | Off
β”‚   └── …
β”œβ”€β”€ mocks/                         # Dev/seed data for all atoms
└── global.data.ts                 # Primitive initial values (isAuthenticated, isPaused…)

A few key decisions visible in the atom layer:

atomWithImmer for complex shapes. currentTrackAtom, galleryAtom, and profileAtom use atomWithImmer from jotai-immer β€” mutations are written as Immer draft producers so deeply nested updates (e.g. patching a track's album cover) stay readable without spread chains.

Plain atom for scalars. isReasoningAtom, isPausedAtom, volumeAtom, and progressAtom are plain Jotai atoms β€” no Immer overhead for simple boolean and number state.

isReasoningAtom as the uploader gate. A single boolean atom controls whether @uploader renders the file dropzone or the live reasoning pipeline UI β€” no prop drilling, no context, no route change. The @uploader slot reads this atom and swaps its view entirely based on it.

Global data as plain values. app/lib/state/global.data.ts exports JavaScript primitives that seed atom initial values. This keeps atoms themselves free of side-effects and makes the initial state legible at a glance.


app/lib/ β€” Global Feature Library πŸ“š

The top-level lib/ folder holds shared utilities and UI components that belong to the app but are not specific to any single route. It follows a layered structure:

app/lib/
β”œβ”€β”€ state/          # Atoms, domain types, mocks, global data (see above)
β”œβ”€β”€ hls/            # HLS player integration
β”‚   β”œβ”€β”€ hls.tsx                       # HLS provider component
β”‚   β”œβ”€β”€ hls-loader.hook.ts            # hls.js attach + lifecycle management
β”‚   β”œβ”€β”€ hls-media-session.hook.ts     # Media Session API wiring
β”‚   └── load-media.compute.ts         # URL resolution for HLS manifests
β”œβ”€β”€ template/       # Formatting, CSS class metadata, i18n
β”‚   β”œβ”€β”€ formatters/
β”‚   β”‚   β”œβ”€β”€ cn.fmt.ts                 # clsx + tailwind-merge utility
β”‚   β”‚   β”œβ”€β”€ msToTime.fmt.ts           # milliseconds β†’ MM:SS display
β”‚   β”‚   β”œβ”€β”€ currency.fmt.ts
β”‚   β”‚   └── to-initials.fmt.ts
β”‚   β”œβ”€β”€ classes/                      # CSS layers (imported in globals.css)
β”‚   β”‚   β”œβ”€β”€ base.css     root.css     app.css
β”‚   β”‚   β”œβ”€β”€ theme.css                 # Shadcn CSS variable overrides
β”‚   β”‚   β”œβ”€β”€ dark.css                  # Dark mode token overrides
β”‚   β”‚   └── neon.css                  # SynthWave gradient utilities (see below)
β”‚   └── i18n/
β”œβ”€β”€ ui/             # Shared server components (Header, Logo)
└── report/         # Logging utilities

Colocation pattern. Feature-specific code lives inside its route folder β€” not scattered across a global lib/. A good example is the login page's form/ folder: form.tsx, form.handlers.ts, form.mappers.ts, and form.types.ts all sit side by side in one directory. Everything that belongs to that form is in one place; you never have to hunt across the tree. The global lib/ only holds what is genuinely cross-cutting β€” if you delete a route, you delete its folder and nothing else breaks.


Theming: SynthWave Neon + Shadcn Overrides 🌈

The visual identity combines three layers:

1. @pack/neon-tokens (workspace package) defines the raw neon color scale β€” 28 tokens from --ps-neon-01 (deep violet) through --ps-neon-28 (warm amber), covering the full SynthWave spectrum. It also generates the gradient utilities:

/* packages/neon β€” consumed by apps/pulse via @pack/neon */
.bg-neon       { background: linear-gradient(135deg, var(--ps-neon-01) … var(--ps-neon-24)) }
.text-neon     { background-clip: text; color: transparent; background: var(--gradient-neon) }
.bg-neon-warm  { background-image: var(--gradient-neon-warm) }
.bg-neon-cool  { background-image: var(--gradient-neon-cool) }

2. lib/template/classes/theme.css overrides Shadcn's CSS variables (--background, --foreground, --primary, --border, etc.) to map onto the neon palette, so every Shadcn primitive β€” Button, Card, Slider, Input β€” automatically picks up the retrowave color scheme without per-component overrides.

3. lib/template/classes/neon.css (imported via lib/template) layers additional app-specific Tailwind utilities on top. The .glassy-surface class that gives the gallery and uploader cards their frosted-glass appearance lives here.

The result: the entire UI, including Shadcn primitives, renders in the neon theme without a single inline style or arbitrary Tailwind value.


Live Reasoning UI & Realtime Pipeline Visibility 🧠⚑

When a user uploads a track, the @uploader slot transitions from dropzone to a live pipeline feed β€” narrated, human-readable events appear in real time, one by one, as the track moves through fingerprinting, transcription, and AI reasoning. The user sees exactly what the platform is doing with their file, instantly and without polling.

Pulse reasoning UI β€” live pipeline event stream

Backstage is the broadcaster. Every microservice publishes events to NATS using a track.* subject pattern. Backstage subscribes to the entire track.> wildcard, projects each event into a MongoDB pipeline read model, and immediately broadcasts it over a Socket.IO /pipeline namespace. This makes Backstage the single source of realtime truth for the frontend β€” services don't need to know the UI exists.

NATS (track.> wildcard)
    β”‚  All track.* events from every microservice
    β–Ό
Backstage
    β”‚  Projects to MongoDB pipeline read model
    β”‚  Broadcasts pipeline.event over Socket.IO /pipeline namespace
    β–Ό
useReasoningSocket() hook (in @uploader slot)
    β”‚  WebSocket connection to Backstage /pipeline namespace
    β”‚  Accumulates events into an ordered list as they arrive
    β–Ό
ReasoningPipeline component
    β”‚  Passes accumulated content to <Reasoning isStreaming={…}>
    β”‚  Wraps each message in <Shimmer> while streaming
    β–Ό
User sees live narration β€” fingerprinting β†’ transcription β†’ GPT-4o verdict

The Reasoning component β€” ported from Vercel AI Elements and adapted in app/infra/shadcn/components/ai-elements/reasoning.tsx β€” is a collapsible panel with a <Shimmer> animated header that pulses while events are arriving. It uses streamdown for markdown-aware streaming text rendering and auto-closes after an idle timeout once the pipeline goes quiet.

Pipeline event messages are deliberately written as narration, not log lines:

Upload received. Let's see what you've got.
Checking file integrity... looks good.
Audio fingerprint computed.
Lyrics decoded. Mike Shinoda would approve.
AI cognition engine analyzing the track.
Track approved for the pipeline.
Audio transcoding in progress.
Track fully processed. Ready for playback.

If a track is rejected (post-2000 song detected, duplicate found, or metadata hacked), the reasoning feed surfaces the rejection reason directly in the UI β€” including a suitably sarcastic message when the AI catches someone trying to sneak in a Justin Bieber track.


Responsive Layout & Mobile πŸ“±

Tailwind breakpoints combined with Jotai's fine-grained atoms make the mobile experience a layout concern, not a state concern. Nothing is re-fetched or re-initialized when the viewport changes.

Desktop β€” Gallery and Uploader slotsΒ Β Β Mobile β€” collapsed volume and track metadata in dropdown

On desktop the player grid renders all four slots side by side. On mobile:

  • @uploader is hidden (mobile-hidden utility on its Card wrapper) β€” upload is a deliberate desktop-first action
  • @now-playing/@volume-bar collapses out of the playback bar
  • @now-playing/@track-metadata moves into the user-menu dropdown

The volume atom and current-track atom stay exactly where they are β€” atoms don't care about viewport. The user-menu slot reads volumeAtom and currentTrackAtom directly and renders them in the dropdown only when the volume bar slot is hidden. No duplication, no prop tunneling.


BFF Proxy Routes & HLS Delivery (under development) 🎡

Note: The HLS delivery and playback pipeline is currently under development and not yet functional end-to-end. The BFF proxy routes and hls.js integration are in place, but the full playback experience is not ready.

BFF routes in app/api/ are thin by design β€” they exist to own the service URL configuration and x-request-id header, not to add business logic:

  • api/authority/login and api/authority/signup β€” validate bodies with Zod, forward to Authority, normalize errors
  • api/slim-shady/profile β€” read/update proxy to Slim Shady
  • api/soundgarden/tracks β€” multipart upload proxy; triggers the slot swap to the reasoning view on success
  • api/transport/hls β€” HLS manifest and segment delivery; resolves object storage URLs and proxies segment bytes to the browser

HLS + Media Session API. The app/lib/hls/ layer wraps hls.js with two hooks:

hls-loader.hook.ts handles the hls.js instance lifecycle β€” attaching to the <audio> element, loading the manifest URL, and tearing down cleanly on unmount.

hls-media-session.hook.ts wires the Media Session API so that OS-level media controls (lock screen widget, headphone buttons, AirPods double-tap, notification shade on Android) stay in sync with the player state. Track title, artist, album art, and transport actions (play, pause, previous, next) are all registered through navigator.mediaSession and updated whenever currentTrackAtom changes.


Event Pipeline πŸ”„

The full track lifecycle β€” from the moment a user drops a file to the moment it plays β€” is an event graph across eight services.

Full Pipeline: Upload β†’ Playback

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                          USER UPLOADS TRACK                         β”‚
β”‚                          apps/pulse (BFF)                           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚  HTTP POST /api/soundgarden/tracks
                               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                           SOUNDGARDEN                               β”‚
β”‚                     Upload ingestion edge                           β”‚
β”‚                                                                     β”‚
β”‚  track.upload.received  β†’  track.upload.validated                   β”‚
β”‚  track.upload.stored    β†’  track.uploaded  ──────────────────────┐  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                                    β”‚
                               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚  track.uploaded
                               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                            PETRIFIED                                β”‚
β”‚                   Acoustic fingerprinting service                   β”‚
β”‚                                                                     β”‚
β”‚  Chromaprint fingerprint  β†’  audio hash  β†’  duplicate check         β”‚
β”‚  track.petrified.generated  /  track.duplicate.detected             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚                              β”‚
               β”‚  track.petrified.generated   β”‚  track.petrified.generated
               β–Ό                              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚       FORT MINOR         β”‚   β”‚            STEREO                     β”‚
β”‚  Transcription service   β”‚   β”‚  Waits for both signals              β”‚
β”‚                          β”‚   β”‚                                      β”‚
β”‚  OpenAI Whisper (audio)  β”‚   β”‚  β”Œβ”€β”€β”€ fingerprint state             β”‚
β”‚  + gpt-audio reasoning   β”‚   β”‚  └─── transcription state           β”‚
β”‚                          β”‚   β”‚            β”‚                         β”‚
β”‚  track.fort-minor.*      │──▢│  GPT-4o reasoning                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚                                      β”‚
                               β”‚  track.approved / track.rejected     β”‚
                               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                              β”‚
                          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚  track.approved
                          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                          MOCKINGBIRD                                β”‚
β”‚                       Transcoding worker                            β”‚
β”‚                                                                     β”‚
β”‚  Downloads source from MinIO                                        β”‚
β”‚  128 kbps MP3  +  320 kbps MP3  β†’  HLS segmentation                β”‚
β”‚                                                                     β”‚
β”‚  track.transcoding.started  β†’  track.transcoding.completed          β”‚
β”‚  track.hls.generated  ──────────────────────────────────────────┐  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                                    β”‚
                               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚  track.hls.generated
                               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        HYBRID STORAGE                               β”‚
β”‚                  HLS persistence + delivery sink                    β”‚
β”‚                                                                     β”‚
β”‚  Persists .m3u8 playlists + .ts segments to MinIO                  β”‚
β”‚  track.hls.stored  ─────────────────────────────────────────────┐  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                                    β”‚
                               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β”‚  track.hls.stored
                               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    TRACK READY FOR PLAYBACK                         β”‚
β”‚                                                                     β”‚
β”‚  apps/pulse requests HLS manifest via BFF                          β”‚
β”‚  hls.js fetches segments β†’ Media Session API wired                 β”‚
β”‚  OS media controls: artist / title / artwork / transport           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

           β”Œ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
                               BACKSTAGE
           β”‚          Observes ALL  track.*  events             β”‚
                  Projects to MongoDB pipeline read-model
           β”‚      Broadcasts  pipeline.event  over Socket.IO    β”‚
            ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─

Event Naming Conventions

All NATS subjects follow lowercase dot-delimited patterns:

Pattern Example
<domain>.<entity>.<state> authority.user.signed_up
<entity>.<subentity>.<state> user.profile.updated
track.<stage>.<state> track.upload.received

The NATS subject is the event name string β€” emit('track.uploaded', payload) publishes to NATS subject track.uploaded. No mapping layer.

Full Event Inventory

Subject Producer Consumers
authority.user.signed_up Authority Slim Shady
user.profile.created Slim Shady Authority
authority.user.logged_in Authority Backstage
authority.token.refreshed Authority Backstage
authority.user.logged_out Authority Backstage
track.upload.received Soundgarden Backstage
track.upload.validated Soundgarden Backstage
track.upload.stored Soundgarden Backstage
track.uploaded Soundgarden Petrified, Backstage
track.upload.failed Soundgarden Backstage
track.petrified.generated Petrified (7201) Fort Minor, Stereo, Backstage
track.petrified.song.unknown Petrified (7201) Backstage
track.duplicate.detected Petrified (7201) Backstage
track.petrified.failed Petrified (7201) Backstage
track.fort-minor.started Fort Minor (7202) Backstage
track.fort-minor.completed Fort Minor (7202) Stereo, Backstage
track.fort-minor.failed Fort Minor (7202) Backstage
track.stereo.started Stereo (7203) Backstage
track.approved Stereo (7203) Mockingbird, Backstage
track.rejected Stereo (7203) Backstage
track.stereo.failed Stereo (7203) Backstage
track.transcoding.started Mockingbird Backstage
track.transcoding.completed Mockingbird Backstage
track.transcoding.failed Mockingbird Backstage
track.hls.generated Mockingbird Hybrid Storage, Backstage
track.hls.stored Hybrid Storage Backstage

AI Architecture 🧠

AI Microservices

The AI cognition layer is split into three independent microservices, each with its own infrastructure, persistence, and deployment boundary:

domain/ai/
β”œβ”€β”€ petrified/       β†’ acoustic fingerprinting (Chromaprint) β€” Redis + MinIO
β”œβ”€β”€ fort-minor/      β†’ speech-to-text transcription (Whisper) β€” Redis + MinIO
└── stereo/          β†’ AI reasoning, approval/rejection (GPT-4o) β€” MongoDB

Petrified (port 7201) runs on every upload. It generates a Chromaprint fingerprint and an audio hash, checks for duplicates via its own Redis (petrified-redis), and emits the fingerprint payload that unblocks both Fort Minor and Stereo.

Fort Minor (port 7202) transcribes the audio. Using OpenAI's Whisper model (via the AI SDK), it extracts lyrics, detects language, and produces structured transcription segments. It has its own Redis (fort-minor-redis) for idempotency and MinIO (fort-minor-minio) for audio access.

Stereo (port 7203) is the decision engine. It waits until both fingerprint state and transcription are present, then runs GPT-4o reasoning over the combined signals to emit track.approved or track.rejected. It persists state in its own MongoDB (stereo-mongo). The pre-2000 product rule lives here β€” not as a hard-coded check but as a reasoning prompt, so the rule can be tuned, loosened, or entirely replaced without touching the pipeline plumbing.

Agent Shinoda (Agentic Development) πŸ€–

repos/agents/shinoda is a Mastra-based AI agent that acts as the platform's operational intelligence layer for developers. It is not a runtime microservice β€” it is a developer tool that connects to the same service APIs and event projections used by the platform.

repos/agents/
└── shinoda/
    └── src/
        β”œβ”€β”€ shinoda/
        β”‚   β”œβ”€β”€ shinoda.agent.ts       # Agent identity + system prompt
        β”‚   β”œβ”€β”€ tools/
        β”‚   β”‚   β”œβ”€β”€ analyse-pipeline   # Inspect pipeline state per trackId
        β”‚   β”‚   β”œβ”€β”€ inspect-events     # Query event history and detect gaps
        β”‚   β”‚   └── check-services     # Health-check all 9 platform services
        β”‚   β”œβ”€β”€ signals/
        β”‚   β”‚   β”œβ”€β”€ signal-bus.ts      # Typed EventEmitter singleton
        β”‚   β”‚   β”œβ”€β”€ anomaly-rules.ts   # Stuck track, gap, out-of-order detection
        β”‚   β”‚   └── monitor.ts         # Socket.IO + polling continuous monitor
        β”‚   β”œβ”€β”€ workflows/
        β”‚   β”‚   β”œβ”€β”€ debug-pipeline     # Multi-step diagnostic workflow
        β”‚   β”‚   └── health-pipeline    # Parallel service health checks
        β”‚   └── infra/mcp/
        β”‚       β”œβ”€β”€ mcp-client.ts      # Generic MCP client factory
        β”‚       └── observability-sink # Signal bus β†’ MCP server bridge
        β”œβ”€β”€ mastra/index.ts            # Mastra registration + signal subscribers + MCP wiring
        └── index.ts

Shinoda runs a local dev server via mastra dev on http://localhost:4111 and is grounded in:

  • context documents (repos/agents/.agents/context/) β€” the full AI pipeline context
  • plans (repos/agents/.agents/plans/) β€” implementation roadmaps per milestone
  • skills (repos/agents/.agents/skills/mastra/) β€” Mastra SDK references and migration guides

The agent is designed to answer questions like:

  • "Why was this track rejected?"
  • "What events were emitted for trackId X?"
  • "Is the pipeline stalled after fingerprinting?"

Read-only by default, not by design. Shinoda currently observes and diagnoses β€” it never modifies platform state. This is a deliberate starting point, not a permanent constraint. The signal layer (signal-bus.ts, anomaly-rules.ts) emits typed events like TRACK_STUCK and SERVICE_UNHEALTHY. The health pipeline workflow checks all 9 services in parallel and aggregates results. When MCP_SERVER_URL is configured, the ObservabilitySink automatically forwards all signals to an external MCP server for integration with OpenTelemetry collectors, bug trackers, alerting platforms, or any observability tooling.

Shinoda is developer/operator-facing, not end-user-facing. It uses openai/gpt-4o-mini by default (cost-efficient for operational tooling) with a clear upgrade path to GPT-4o for deeper reasoning tasks.


Runtime Infrastructure 🐳

Services at a Glance

Service Port Persistence Role Health
authority 7000 MongoDB (authority-mongo) Auth, JWT, OAuth, sessions /health
slim-shady 7400 MongoDB (slim-shady-mongo) User profiles /health
soundgarden 7100 MinIO (soundgarden-minio) Upload ingestion /health
petrified 7201 Redis (petrified-redis), MinIO (petrified-minio) Audio fingerprinting /health
fort-minor 7202 Redis (fort-minor-redis), MinIO (fort-minor-minio) AI transcription /health
stereo 7203 MongoDB (stereo-mongo) AI reasoning /health
mockingbird 7200 MinIO (mockingbird-minio) HLS transcoding /health
hybrid-storage 7300 MinIO (hybrid-storage-minio) HLS persistence /health
backstage 4001 MongoDB (backstage-mongo) Pipeline observation + Socket.IO /health
pulse 3000 Browser / Jotai Frontend + BFF β€”
shinoda 4111 β€” AI operations agent β€”

Infrastructure Dependencies

Every microservice owns its infrastructure β€” no shared databases or caches. The only shared component is NATS.

Dependency Instances Role
MongoDB authority-mongo, slim-shady-mongo, stereo-mongo, backstage-mongo Per-service persistence
Redis petrified-redis, fort-minor-redis Idempotency, audio hashes
NATS (JetStream) nats Shared async event plane
MinIO soundgarden-minio, petrified-minio, fort-minor-minio, mockingbird-minio, hybrid-storage-minio Per-service object storage

Tooling & DX βš™οΈ

One Command to Rule Them All

pnpm infra

This single command spins up the entire platform β€” all per-micro infrastructure (MongoDB Γ—4, Redis Γ—2, MinIO Γ—5, NATS) and all 11 application services β€” using the docker-compose.yml under repos/packages/env-orchestration/. No manual service wiring required. MinIO buckets are bootstrapped automatically by init containers on first run.

Reset and Bootstrap

pnpm dx:env:template

Scans the monorepo for every .env.template file and generates .env files from them. Run this once after cloning to get a working local environment. Re-running it is safe β€” it restores any accidentally deleted or corrupted .env files without touching values you've already customized.

Root package.json Scripts

Script Description
pnpm infra Raise the entire docker environment (infra + all services)
pnpm pulse Start the Pulse frontend in dev mode (requires infra already running)
pnpm dx:env:template Generate .env files from all .env.template files in the monorepo
pnpm build Build all packages and services through the Turbo build graph
pnpm lint Biome lint across the entire monorepo
pnpm format Biome format with VCS-aware ignore rules
pnpm typecheck TypeScript type-check across all workspaces

Turborepo Build Graph

Turbo treats shared packages as first-class build inputs. @pack/kernel must be built before any service that depends on it β€” Turbo infers and parallelizes this graph automatically. You never need to manually order package builds.

Testing Philosophy

There are no unit, integration, or end-to-end tests in this repository. This is intentional.

The backend was built to validate a wired event-driven architecture from the ground up. Every microservice exposes a standard GET /health endpoint returning { status: 'ok' }, and Docker Compose health checks enforce boot order. The Shinoda agent provides automated health monitoring β€” its health pipeline workflow checks all 9 services in parallel and emits SERVICE_UNHEALTHY signals for any failures. These signals can optionally be forwarded to external observability platforms via MCP.

Adding unit or integration tests at this stage of a personal MVP would mean a substantially larger codebase β€” more code to write, more code to maintain, and more code to refactor as the architecture evolves. In a microservices system where domain logic crosses service boundaries via NATS events, integration tests also require the full infrastructure to be running, which duplicates what pnpm infra already provides.

The trade-off is deliberate: move fast, validate the architecture, then layer in tests as the codebase stabilises.


Getting Started πŸš€

Prerequisites

Make sure you have the following installed:

  • Node.js >= 22.13.0
  • pnpm >= 10.30.3 (npm install -g pnpm)
  • Docker + Docker Compose

1. Clone the repository

git clone https://github.com/your-org/pulse.git
cd pulse

2. Install dependencies

pnpm install

3. Generate environment files

pnpm dx:env:template

This walks every workspace and generates .env files from .env.template. Fill in your secrets (OpenAI API key, MinIO credentials, JWT secrets) before starting services.

4. Raise the entire environment

pnpm infra

That's it. This command:

  1. πŸ”Ά Starts MongoDB Γ—4 (authority, slim-shady, stereo, backstage)
  2. πŸ”΄ Starts Redis Γ—2 (petrified, fort-minor)
  3. 🟒 Starts NATS with JetStream enabled
  4. πŸͺ£ Starts MinIO Γ—5 (soundgarden, petrified, fort-minor, mockingbird, hybrid-storage) and pre-creates buckets
  5. πŸš€ Starts all 11 application services with their health checks

Services are started in dependency order. Health checks ensure no service starts before its dependencies are ready.

5. Start the Agent (optional)

To run the Shinoda developer agent locally:

cd repos/agents/shinoda
pnpm dev       # starts mastra dev server at http://localhost:4111

Open http://localhost:4111 to interact with Shinoda β€” ask it about pipeline state, event history, or why a track was rejected.


Running in Development Mode πŸ› οΈ

With the full environment running via pnpm infra, the only application you need to start in dev is the frontend. From the repo root:

pnpm pulse

This starts the Pulse Next.js app in dev mode with hot reloading. All backend services are already running inside Docker via pnpm infra.

Service URLs (local)

Service URL Health
Pulse (frontend) http://localhost:3000 β€”
Authority http://localhost:7000 /health
Slim Shady http://localhost:7400 /health
Soundgarden http://localhost:7100 /health
Petrified http://localhost:7201 /health
Fort Minor http://localhost:7202 /health
Stereo http://localhost:7203 /health
Mockingbird http://localhost:7200 /health
Hybrid Storage http://localhost:7300 /health
Backstage (HTTP + WS) http://localhost:4001 /health
Shinoda Agent (Mastra) http://localhost:4111 β€”
NATS Monitor http://localhost:8222 β€”

About

The app that hates your > 2000s songs. 😀 (under development 🚧)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors