Aumik internal handbook

The team's single reference for how Aumik Labs builds: our stack, how to get set up, how we ship, the platform we run on, and the tooling. Pick a card or use the sidebar.

The platform in one diagram

          +------------------- aumik-agents (Bun · Mastra) --------------------+
 chat     |  agents + tools  ·  BOOKING service (central)  ·  comms-manager    |
 widget → |  REST: /api/scheduling/{resource}/...  ·  /api/agents/{id}/stream  |
(widgets) +----------------+--------------------------------------+------------+
                           | one booking API for every product    | email/SMS/WhatsApp
 aumik-ui (web, Tamagui) --+                                       ▼
 apps/* + @aumik/portal  --+                          Resend · Meta WhatsApp · SMS
 aumik-native → aumik-player (DOOH TV player)            (orchestrated by Novu)
                           |
      +--------------------+-- shared box (Hetzner · Uncloud) ------------------+
      |  Postgres 16 + pgvector  ·  Redis 7  ·  Infisical  ·  Caddy  · apps     |
      +-----------------------------------------------------------------------+

How we're organized

Aumik runs in four role tracks. Most people live mainly in one:

TrackWhat they do
DevScaffold, build, deploy, and migrate Aumik apps.
ContentBooks, marketing video, and song videos (Tirhut Press, Balmukund).
POPortfolio + team rituals — standups, milestones, access.
OpsDeploy-time plumbing — secrets, infra.
Where this lives: this handbook is the team-facing view. Cross-cutting business decisions, confidential client/partner material, and per-person notes are not here — they live in the operator's private control plane.

Dev onboarding

Getting a new dev or intern productive: access, the lay of the land, and how work actually moves.

1. Get access

  1. Send your GitHub handle to your PO — you'll be added to the aumik-labs org and granted collaborator access on the repos you're assigned to.
  2. You'll be invited to Slack#aumik-dev is the main dev channel, #standup is where the async daily standup runs.
  3. Clone the repo(s) you own. Always check the brand index for the canonical remote before pushing (a few repos were renamed and some local clones still point at old slugs).

2. Learn the lay of the land

Read the Monorepos page first — most work happens in three runtime-split monorepos, not one-repo-per-app. Then skim the brand index for what exists. Each app/brand may carry its own CLAUDE.md with stack specifics — read it before working in that repo.

3. Roles & how work flows

  • Apijay sets direction at the milestone level.
  • Aniket (aniket-j4332, lead dev) breaks milestones into per-repo dev issues, assigns devs, and runs PR review → sign-off → release to prod.
  • Devs own the implementation on their assigned repos (available evenings IST + flexible).

The flow:

  1. Milestones / tracking stories live in aumik-infra, assigned to Aniket.
  2. Aniket creates dev issues kept closest to their repo (per-app repo, aumik-ui, aumik-native, aumik-agents, aumik-infra), assigns the owner, and tracks them on the board.
  3. The cross-cutting tracking story is the one exception to "closest to the repo" — it lives where the program does.

4. The board

Everything is tracked on Aumik Dev — org Project #9 (github.com/orgs/aumik-labs/projects/9). Columns: Todo → In Progress → In Review → Done. Every dev issue goes here; per-person boards are superseded.

Quality Gate. Merging to prod requires 1 approval from Aniket + 1 of Omkar / Achal / Deepak, plus lint/typecheck/unit/security + Argos visual regression on affected apps. (Reusable workflow_call, aumik-infra#11.)

5. How we sync

Two channels of record: GitHub issues are the work log; Slack is for "look now" — and they point at each other. The full conventions (issue comments, PR keywords, which channel for what, async standup) are in Working model.

6. The build philosophy

  • Clickable HTML prototypes first. A single self-contained clickable HTML mock (hosted at proto.aumik.co) is the pre-dev design artifact devs build from.
  • Know what you're building. Shared building blocks (notifications, booking, auth, the portal) are platform foundations — invest and do them right, don't rush. One-off, single-brand features still get simple first cuts.
  • Isolate concurrent work with git worktrees rather than switching branches in a shared checkout.

Working model — issues & Slack

How we plan, track, and communicate. Two channels of record: GitHub issues for the work, Slack for "look now." Keep them pointing at each other.

GitHub issues & comments are the source of truth

  • Work lives in issues, not chat. Every substantive, multi-step piece of work has a tracking issue. If one doesn't exist, open it and use it as the log.
  • Post progress as issue comments at meaningful milestones — what you're doing, what's done, what's blocked, with links to PRs/commits. Comment at inflection points, not every step.
  • Issues live closest to the repo doing the work (the app repo, aumik-ui, aumik-native, aumik-agents, aumik-infra). The one exception is the cross-cutting tracking story, which lives where the program does (usually aumik-infra) and is assigned to Aniket.
  • Everything is on the board: Aumik Dev — org Project #9 (Todo → In Progress → In Review → Done).

Linking PRs to issues

  • Close the issue from its PR with a keyword in the PR description: Closes #12 (same repo) or Closes aumik-labs/<repo>#12 (cross-repo).
  • Closes / Fixes / Resolves auto-close on merge; a bare #12 only cross-references.
  • Milestone naming: Week of YYYY-MM-DD (Mon→Sun); Friday-EOD retro paragraph on the milestone.

Slack — read & post

Slack is for "look now," and it always points back to the issue. The channels you'll use:

ChannelUse it for
#devDay-to-day dev chatter, questions, quick coordination.
#aumik-devUrgent + cross-cutting: prod incidents, "I'm blocking a teammate," changes others must act on before they deploy. @-mention the owner; link the issue.
#standupAsync daily standup (Mon–Fri): reply in-thread to the morning prompt with Did / Doing / Blocked. A digest is collated in the evening.
#engineering · #all-aumik-handsBroader engineering and company-wide.

Posting conventions

  • Urgent only in #aumik-dev. "Urgent" = blocking someone, a prod incident, or a deploy-affecting change. Routine progress stays on the issue.
  • @-mention the owner and link the issue/PR — the Slack note is a pointer; the issue is the record.
  • Use threads. Reply in-thread to keep channels readable; broadcast to the channel only when it needs everyone's eyes.
  • Never paste secrets into Slack or issues (see Secrets).

Reading / finding information

  • Channels are your sources — search Slack for prior context, but treat the linked issue/PR as authoritative (chat goes stale).
  • The evening digest in #standup is the daily snapshot of who's doing what + current blockers.
Automated via Claude: the team runs Slack read/post through Claude skills — aumik-po-daily-standup posts the standup prompt and collates the evening digest, and aumik-po-standup-triage reads that digest and routes action items. See Using aumik-skills. (Claude can also read/post Slack directly via the Slack connector once it's enabled in your setup.)

Tech stack

What we build with, and — more importantly — which stack to reach for given the kind of app.

The app stack

The default full-app stack is Tamagui Takeout v2:

LayerTool
App frameworkOne (Vite-based RN + web, replaces Next.js + Expo Router)
UITamagui
Realtime syncZero (Rocicorp)
AuthBetter Auth
ORMDrizzle
Package managerBun (not pnpm/npm)

Other stacks you'll see:

  • Agents (aumik-agents): Bun · Mastra · Drizzle — the HTTP agent backend.
  • Cross-platform / TV (aumik-native): ReNative — uses yarn, not bun (ReNative breaks on bun workspace:*).
  • Lovable exports (aumik-astro, aumik-dev, aumik-food, aumik-cleaning): Vite · React · Shadcn · Supabase — being converted to Takeout v2, with the original Lovable export kept as a read-only reference.

Which stack / where to host — the tier model

Hosting follows the tier of the app, not a one-size template. Each app declares its tier.

Tier A — marketing / SSG-only

No backend. Cloudflare Pages (~$0–5/mo). Promote to Tier B the moment it needs an API or auth.

Tier B — full Takeout apps (web + mobile + auth + realtime) — the default

Uncloud on Hetzner. One shared VPS hosts multiple Aumik apps as Docker services behind Caddy (~$5–20/mo per VPS regardless of app count, until traffic forces a split). Mobile via EAS Build; OTA via Hot Updater. This is the portfolio default for full apps.

Tier C — escape hatch

SST / AWS. Reserved for any single app that demonstrably needs Aurora auto-scaling, AWS-native compliance, or an enterprise contract requiring AWS. Not the default — currently zero apps live here.

Why Uncloud is the default: it's the officially-stable Takeout deploy path (SST/AWS is upstream "work in progress"), its cost shape (~$5–20/mo/VPS shared) beats ~$140–300/mo/app on AWS, and Docker + Caddy is a far smaller ops surface than ECS + Aurora + Zero services + Lambda + VPC.

Monorepos — what goes where

Three runtime-split monorepos. Pick by runtime, not by product.

MonorepoRuntimeHoldsToolchain
aumik-uiWeb (Tamagui/One)All web UI apps + shared @aumik/* packages (widgets, portal, tsconfig, eslint-config)bun · Tamagui · Turbo
aumik-nativeReNative (TV/mobile)Cross-platform/TV apps — first app aumik-playeryarn · Metro · Turbo
aumik-agentsBun · MastraShared AI backend: agents, tools, the booking service, comms-managerbun · Mastra · Drizzle

Web apps gather into aumik-ui one PR per app (pattern proven on hospitality, aumik-ui#1): app under apps/<name>/, the Takeout framework hoisted to shared packages/, app overrides moved to the root package.json, seed bun.lock from the app's lockfile.

App inventory gathering into aumik-ui: hospitality (pilot, done) · assistants · alumni · food · dev · health · cleaning · astro.

aumik-dooh spans two monorepos

aumik-dooh is the DOOH (digital signage) product, split by runtime:

  • aumik-playeraumik-native — the on-screen player: installs on ReNative TV platforms (Tizen/webOS/Android TV/Fire TV); activate a screen → it plays scheduled videos/images. (Not a CMS.)
  • doohaumik-ui — the web management portal, two-sided: SSP (screen owners: activate/manage screens, playlists, scheduling, layouts, widgets) + DSP (advertisers: buy inventory, run campaigns). Mocked at the dooh prototype.
  • dooh-landing — marketing site. Not an aumik-ui app — it's a clickable lo-fi prototype in aumik-prototypes (per the ADLC prototype convention); rebuilt as a real app only if/when productized.

@aumik/portal — the shared self-service shell

Every Aumik product gets a customer self-service portal. Rather than per-product rebuilds, there's a generalized @aumik/portal package in aumik-ui: Better Auth customer accounts (phone-OTP) and self-service surfaces (starting with My Bookings). Each product mounts it scoped to its {resource}. Mocked in the scheduling prototype.

CI/CD

Per-repo hand-rolled CI is deferred — it lands as the reusable Aumik Quality Gate workflow_call (aumik-infra#11) + self-hosted Argos CI (aumik-infra#6), consumed by each monorepo. See Dev onboarding.

Running an app locally

How to boot any aumik-ui app (Takeout / One + Zero) on your machine — the two run modes, the ports, and the errors that actually stop you.

Prerequisites

  • Node 24.3.0 — pinned in each app's .nvmrc. Run nvm use first. Node 25 gives 500s on SSR — this is the #1 "it won't start" cause.
  • bun (not pnpm) — aumik-ui is packageManager: bun@1.3.x. Install deps once with bun install from the repo root.
  • Docker — only for the full run mode (see below). Not needed for bun lite.

The local stack per app

A Takeout app is more than a web server. Booting it fully means four pieces:

PieceWhat it doesDefault port
web (One/Vite)The app itself — SSR + client8081
PostgresSource-of-truth DB + auth5433
Zero (zero-cache)Sync engine — every list/query in the UI streams over its WebSocket4848
MinIOS3-compatible media storage9200
The web server boots without a database. If Postgres or Zero is down, the page still loads — but sign-in fails and every data-backed list comes up empty, with the console spamming WebSocket … refused. An empty screen is almost always a backend problem, not a UI bug.

Two run modes

1. Lite (no Docker) — recommended for UI work. One embedded process (orez) provides Postgres + Zero + MinIO, auto-migrates, and starts the web server:

cd ~/Dev/aumik-repo/aumik-ui
nvm use                       # → 24.3.0
cd apps/<app>
bun lite

2. Full (Docker) — when you need the real containerized stack. Backend in one terminal, web in another:

# terminal 1 — brings up pgdb, migrates, then full compose (pgdb + zero + minio)
bun backend
# terminal 2 — web/One server
bun dev

Or bring up just what you need (e.g. the sync engine) with compose directly — but pass both env files so the per-app port offsets apply (see below):

docker compose --env-file .env --env-file .env.development up -d pgdb zero
bun db:migrate                # ensure the app's zero publication exists

Ports & PORT_OFFSET

So multiple apps run side by side, each app offsets the defaults. dooh, for example, uses PORT_OFFSET = +5 → web 8086, Postgres 5438, Zero 4853, MinIO 9205. The values live in apps/<app>/.env.development (VITE_PORT_POSTGRES, VITE_PORT_ZERO, …). Always drive Docker through the bun scripts (or pass --env-file .env --env-file .env.development) — a bare docker compose up only reads .env and falls back to the defaults, colliding with whatever other app holds 5433/4848/9200.

Troubleshooting

SymptomCauseFix
500s on SSR / white page on bootWrong Node versionnvm use (must be 24.3.0, not 25)
connect ECONNREFUSED 127.0.0.1:54xx in the server log; sign-in failsPostgres/backend not runningStart Docker + bun backend, or use bun lite
Lists empty; console loops WebSocket … :48xx … ERR_CONNECTION_REFUSEDZero (zero-cache) not runningdocker compose --env-file .env --env-file .env.development up -d zero
Zero container crash-loops with Unknown or invalid publications. Specified: [zero_<other-app>]Stale clone values — .env.development still has another app's ZERO_APP_ID / ZERO_APP_PUBLICATIONS (scaffolds are cloned from a sibling app)Set both to this app (e.g. ZERO_APP_ID=doohportal, ZERO_APP_PUBLICATIONS=zero_doohportal), wipe zero's replica volume (docker volume rm <project>_zero_data), re-migrate, restart zero
Bind for 0.0.0.0:5433 failed: port is already allocatedCompose using default ports (env files not loaded), colliding with another app's stackRun via bun scripts, or pass --env-file .env --env-file .env.development so the offset ports apply
Two container sets (myapp-* and my-app-*) fighting over the same portsCompose project-name normalization differs; pin it via COMPOSE_PROJECT_NAME in .env.developmentdocker compose down both, remove stray containers, bring up one clean stack
WARN vs ERROR in zero logs. A transient WARN … no such table: _zero.tableMetadata during startup is normal — it's the status publisher racing the initial replication sync. Only an "level":"ERROR" that ends in the container exiting is a real failure.

Working inside a Takeout app? Each app carries its own .claude/skills/takeout-* patterns (the source of truth for models/queries/routes), plus the aumik-dev-takeout skill for the Aumik overlays. And do real PR work in an isolated worktree off origin/mainaumik-ui main is branch-protected. See Using aumik-skills.

Booking & notifications

Two shared building blocks — platform foundations reused across every product, not one-offs. Tracking epic: aumik-infra#18. The booking/scheduling UX is mocked at the scheduling prototype.

Booking system — we build our own (no Cal.com)

Why not Cal.com: its API + API-key generation is Enterprise-gated even self-hosted (sales-quoted, ≥30-user minimum); only embed + webhooks are free. Not worth a paid EE license for one widget. So aumik-infra#5 (Cal.com) is closed and cal.* can be decommissioned.

Design

  • Centralized in aumik-agents — one booking service / DB / API, multi-tenant by {resource}, serving the chat widget, the agent (Level-3 auto-book via Mastra tools), and every product's @aumik/portal.
  • packages/db (Drizzle, own aumik_agents DB): resources, availability_rules + availability_overrides, bookings, notifications, notification_prefs.
  • packages/booking — pure service: availability (UTC-stored, TZ/DST-correct), a DB-level double-booking guard (EXCLUDE USING gist over tstzrange), .ics generation.
  • apps/api — Hono REST alongside Mastra: GET /api/scheduling/{resource}/availability, POST …/book, PATCH …/bookings/{id} (reschedule), POST …/cancel.
  • No external calendar sync (no Google/Outlook two-way) — deliberate scope cut. .ics links only; revisit with Nylas/Cronofy only if customers demand it.

Get-it-right list: UTC + timezones/DST · DB-level conflict guard + idempotent book · reschedule/cancel state machine.

Notifications — engine is Novu (decided)

Channels: WhatsApp (Meta Cloud API) + SMS (gateway, default MSG91) are primary; email = Resend (domain aumik.co) for OTP + confirmations/fallback.

  • One engine for email + SMS + WhatsApp across all products: built-in preferences, digests / retries / delivery logs, an in-app <Inbox/> for @aumik/portal, and workflows-as-code (Novu Framework, fits Mastra). Adoption tracked in aumik-infra#19.
  • Integration: comms-manager emits booking domain events (booking.confirmed, booking.reminder, booking.cancelled) → Novu workflows fan out by channel and honor per-user prefs. Novu's delay/digest handles 24h/2h reminders (no hand-rolled queue for notifications).
  • Self-host footprint on the box: API + Worker + WebSocket + Dashboard + MongoDB + Redis, co-tenant under Uncloud.
Lead-time warning: start the WhatsApp Business + message-template approval and SMS gateway accounts on day 1 — they have external approval lead time that will otherwise block launch.

Voice calling agent

A self-hosted AI agent that places and answers real phone calls (PSTN) — reminders, scheduling, reception, later marketing. One shared voice layer across products, not a per-product dialer. Tracking epic: aumik-infra#64.

Platform — dograh (decided)

We self-host dograh (BSD-2, open source; the self-hosted Vapi/Retell alternative). Docker stack: api + ui + workers + Pipecat media pipeline, with its own Postgres/Redis/MinIO.

  • Why: no per-minute SaaS lock-in; calls, recordings and transcripts stay on our infra; BYO LLM/STT/TTS (Claude for reasoning, Deepgram STT, Cartesia/Sarvam TTS); built-in telephony (Twilio, Vonage, Plivo, Telnyx…), human call-transfer, voicemail detection, and a visual workflow builder.
  • Not the same as widget voice: the <VoiceCallButton/> browser voice (WebRTC + Sarvam, in aumik-widgets) is a separate, complementary track. This page is about phone calls.
  • Languages: dograh's bundled zero-key stack is English-only. Target is English + Spanish (Hindi as a stretch) — ES/HI need our own provider keys (Deepgram STT covers EN/ES/HI; Cartesia/Deepgram TTS for ES, Sarvam for HI).

How it plugs into the platform

              Phone (PSTN) ── telephony provider (trunk)
                                      │
   ┌────────────── dograh (self-hosted, Docker) ──────────────┐
   │  api · builder UI · workers · Pipecat (STT → LLM → TTS)  │
   └──────┬────────────────────────────────────────┬──────────┘
          │ (2) HTTP-API tools: act mid-call        ▲ (1) POST to trigger endpoint
          ▼                                         │        → outbound call
   aumik-agents REST (Hono)              Novu workflow "voice" step
   GET …/availability · POST …/book  ◄── booking.reminder event
   PATCH …/bookings/{id}                 (same bus as WhatsApp/SMS/email)
  • (1) Outbound trigger: a dograh trigger node exposes an API endpoint; the Novu booking.reminder workflow (see Booking) gains a voice step that POSTs to it. Same event bus, no parallel scheduler.
  • (2) In-call actions: dograh agent nodes attach HTTP-API tools / webhook nodes that call the booking REST routes — look up, confirm, reschedule — plus call-transfer for human handoff and structured extraction of intent. (dograh also ships an MCP server, but that's a workspace-management surface for coding agents, not the mid-call tool path.)

Phases

  • Phase 0 — local spike done: dograh booted locally, browser Web Call validated end-to-end in English on the bundled zero-key stack (TTS heard, transcript read). Deepgram key sourced for the Spanish turn.
  • Phase 1 — platform on the box live: voice.aumik.co behind Caddy (stack in aumik-infra/voice/, PR #68). Still open from this phase: SSO gate (rides Authentik #63), backups for /mnt/voice/* (recordings = customer data), image pinning, telephony number.
  • Phase 2 — standalone pilot receptionist live: the Cleaning Receptionist (below) answers, checks real availability, books, and can hand off to a human — for cleaning ops' first customer. v1 scope is browser/app Web Call; Twilio PSTN wiring + first US number is the remaining half. EN live, ES next. Tracked in aumik-infra#69.
  • Phase 3 — integrated beachhead (after booking #18 lands): reminder calls triggered off booking.reminder that read + update real bookings by voice, with human handoff.
  • Phase 4 — marketing calls (gated): only after compliance work — consent capture, DNC/opt-out scrubbing, recording disclosure, quiet hours, rate limits.

Phase 2 as built — the Cleaning Receptionist

The pilot flow running today. An inbound caller reaches the dograh workflow "Cleaning Receptionist" on voice.aumik.co; mid-call it calls real booking APIs and writes real calendar events:

   Caller — browser Web Call today · Twilio PSTN next
                        │
   dograh workflow "Cleaning Receptionist"  (voice.aumik.co)
   tools: check_availability · book_appointment · transfer_to_human (→ owner's phone)
                        │  HTTP-API tools, bearer VOICE_TOOLS_TOKEN
                        ▼  (overlay-internal: agents.internal:4111)
   aumik-agents (Mastra server.apiRoutes)
   GET /scheduling/cleaning/availability · POST /scheduling/cleaning/book
                        │  CalDAV (zero-dep raw fetch; 409 on taken slots)
                        ▼
   Radicale CalDAV — caldav.aumik.co /cleaning-agent/cleaning/  (each booking = an .ics event)
  • Workflow-as-code: the workflow + tools live in aumik-infra/voice/workflows/cleaning-receptionist.json, pushed idempotently with push.sh (upserts tools, publishes). Booking routes: PR aumik-agents#15. Note Mastra reserves /api/*, hence the unprefixed /scheduling/… paths.
  • Human handoff: transfer_to_human forwards the call to the operator's phone when the caller asks for a person or the agent gets stuck.

Viewing the bookings calendar (cleaning)

Every booking the agent makes lands as an event in the self-hosted Radicale calendar. Subscribe once and it stays live on Mac/iPhone:

  • Apple Calendar → add a CalDAV account, Account Type Advanced: server caldav.aumik.co · user cleaning-agent · port 443 + SSL · Server Path /cleaning-agent/.
  • ⚠ The path is the principal, not the calendar: entering /cleaning-agent/cleaning/ fails with "No calendar home was specified". Point at /cleaning-agent/ and the cleaning calendar is auto-discovered.
  • Password: ICAL_CLEANING_AGENT_PASSWORD in the gitignored aumik-infra/ical/.env (ask Apijay). Quick checks without a client: Radicale's web UI at caldav.aumik.co/.web/, or an authed GET on the collection returns the whole calendar as .ics.

Status & open decisions

Status (2026-07-04): platform live at voice.aumik.co (dograh v1.40.0, isolated stack under Uncloud — runbook in aumik-infra/voice/README.md), and the Cleaning Receptionist is live: web-call bookings verified end-to-end in English, first real event landed in the cleaning calendar. Next: Spanish turn (Deepgram STT + ES-capable TTS), then Twilio wiring + first US number.

  • Owner — Phase 1 (platform) was built out with Deepak; Phase 2 (aumik-infra#69) is Apijay's, solo, as of 2026-07-04. Aniket reviews per the Quality Gate.
  • Beachhead — reminder/booking calls to our own customers (lowest regulatory risk); language target EN+ES points at the US cleaning market.
  • Telephony providerdecided: Twilio (US-lead, EN/ES). Wiring + first number tracked in aumik-infra#69.
Compliance rule of thumb: calling your own booked customers about their appointment is service messaging — low risk. Cold outbound marketing is a different regulatory regime (TCPA/DNC in the US) — it stays out of scope until the Phase-4 gate is designed.

Infrastructure & deploy

One shared box runs everything. Know how it's laid out — and the rules that keep it alive — before you deploy.

The shared box

A single Hetzner server runs everything via Uncloud (lightweight Docker orchestration). All apps are co-tenants on it. Shared services, defined in aumik-infra/compose.yaml and deployed with uc deploy -y:

  • Postgres 16 + pgvector — shared DB server; each app gets its own DB + role (aumik_hospitality, aumik_food, aumik_law, infisical, …).
  • Redis 7 — shared cache + queue backbone (BullMQ jobs).
  • Infisical — secret store (see Secrets).
  • Caddy — reverse proxy + automatic TLS.

Other co-tenant services: Postiz (social scheduler), Radicale (CalDAV/CardDAV, caldav.aumik.co), prototypes (proto.aumik.co), and this handbook (docs.aumik.co). Cal.com is being decommissioned (see Booking).

Domains

Rule: always use *.aumik.co for any Aumik service (e.g. proto.aumik.co, docs.aumik.co). The cluster also has a *.uncld.dev address, but it's a volatile fallback that changes whenever the cluster is re-initialized — never hardcode it as a published URL.

Hard rules — read before touching the box

🚫 Never run uc machine init against the box. It re-initializes the cluster and wipes orchestration state (this has caused a production outage).
🚫 Never uc deploy --recreate on the shared infra compose. It corrupts the shared Postgres WAL for every app.
⚠ App data lives in the shared Postgres. Confirm backup status (aumik-hospitality#4) before any destructive operation.
✅ App deploys are co-tenant: own image / services / subdomain / DBs; never touch shared state beyond adding your own DB.

Deploying an app

  • Tier-B apps: the CI contract centers on a single orchestration command, bun ops release (build → deploy → migrate), with secrets injected from Infisical. Reference: aumik-hospitality (live Tier-B + CI/CD reference). Copy its shape rather than inventing one.
  • Static sites (this handbook, the prototypes): an nginx:alpine co-tenant serves a /srv/<name> dir; the repo is rsync'd up and the service redeployed via Uncloud. One-liner deploy lives in each repo (e.g. ./docs-site/deploy.sh in aumik-infra).
  • Shared infra changes: from the aumik-infra repo, source the gitignored bootstrap .env, then uc deploy -y (never --recreate) and uc caddy deploy for DNS + TLS.

Disaster recovery

  • Cluster wiped, data survived (common case): run the infra recovery script — it validates secrets, aborts if the Postgres data dir is gone, reserves a domain, deploys without --recreate, and health-checks. See RECOVERY.md in aumik-infra.
  • Box totaled (no surviving data): blocked until DB backups land (aumik-hospitality#4).

Secrets

All secrets live in Infisical (self-hosted on the box). Never hard-code them; never paste them into Slack or issues.

Pulling secrets

Use the aumik-ops-secrets-pull skill (wraps the Infisical CLI) to generate a local .env / .env.production for deploys:

aumik-ops-secrets-pull <project-slug> <env> [<out-path>]

It needs the machine-identity credentials (set as env vars); project IDs are cached locally.

What lives where

  • The shared app-secrets project (Development env) holds shared keys — e.g. Resend (email; sending domain aumik.co, verified), OpenRouter (LLM routing for widgets/agents), Postiz DB. Notification provider creds (Meta WhatsApp, SMS gateway) live here too once wired.
  • Bootstrap secrets that Infisical itself depends on can't live inside Infisical (chicken-and-egg). They live only in a gitignored .env in the aumik-infra repo, mirrored to 1Password + iCloud Keychain. See the infra repo for the exact set and rotation steps.
Sourcing for deploys: bootstrap secrets are fed to uc deploy by sourcing the infra .env into the shell (the pinned Uncloud version has no uc secret subcommand). Everything else is pulled from Infisical at deploy time.

DevOps

Operational reference for the things that keep services reachable — DNS first. Add ops runbooks here as they stabilize.

DNS — aumik.co

DNS is hosted at WordPress.com (ns1/ns2/ns3.wordpress.com). As of 2026-07-04 records are scriptable via the WordPress.com MCP connector (Apijay's Claude session; first use was the voice record) — the panel remains as fallback. Changes still go through the org owner (Apijay); devs don't have panel or MCP access. Every box-served hostname is an A record to the Hetzner box 178.105.125.211.

HostTypeValueServes
mail.aumik.coA178.105.125.211Stalwart mail (SMTP/IMAP)
webmail.aumik.coA178.105.125.211Roundcube webmail (shared social inbox)
postiz.aumik.coA178.105.125.211Postiz social scheduler
infisical.aumik.coA178.105.125.211Infisical secrets (canonical host)
proto.aumik.coA178.105.125.211Clickable prototypes
docs.aumik.coA178.105.125.211This handbook
hospitality-staging.aumik.coA178.105.125.211Hospitality staging (auto-deploy pilot)
voice.aumik.coA178.105.125.211dograh voice-calling platform
alumni.aumik.coA178.105.125.211Alumni app
aumik.coMX10 mail.aumik.co.Inbound mail → Stalwart
aumik.coTXTv=spf1 include:_spf.resend.com ~allFrom-domain SPF (Resend)
send.aumik.coTXTv=spf1 include:amazonses.com ~allReturn-path SPF → 10/10 deliverability
_dmarc.aumik.coTXTv=DMARC1; p=quarantine;DMARC policy
resend._domainkey.aumik.coTXT(DKIM public key)Resend DKIM signing
Verify a record actually saved — query WordPress.com's authoritative NS directly (bypasses caching / propagation lag):
dig +short webmail.aumik.co A @ns1.wordpress.com · dig +short send.aumik.co TXT @ns1.wordpress.com
Two panel gotchas: enter the host label only (webmail, not the FQDN — it appends the domain), and paste TXT values unquoted (the panel adds its own quotes). If the authoritative NS is empty a minute after saving, it didn't commit.

Brand / repo index

What each product is, its stack, and what stage it's at. Stage: live in production · active work in progress · scaffold early/greenfield.

Products

ProductWhat it isStackStage
aumik-hospitalityReal-estate / boutique zen-wellness brand + investor tools. CI/CD reference.Takeout v2live
aumik-astroPremium self-service astrology portal (single-guru booking, AI-fuelled). Not a marketplace. · prototype ↗Vite·React·Shadcn → Takeoutactive
aumik-assistantsConsumer SaaS frontend for sector assistants (law → insurance/medical/home); brain is aumik-agents.Takeout v2active
aumik-alumniConnects colleges with alumni; USP is official event photos / memories. · prototype ↗Tamagui·Supabaseactive
aumik-cleaningSelf-service marketplace to book cleaning services (customers ↔ providers).Vite·React·Shadcnactive
aumik-devMove businesses off Joomla/WordPress onto custom AI-enabled builds; GTM via preview sites. · prototype ↗Vite·React·Shadcn → Takeoutscaffold
aumik-foodVegan/veg tiffin-ordering (aumikfood.com): order flow, milk orders, admin.Vite·React·Shadcn → Takeoutscaffold
aumik-doohDigital out-of-home: screens + install, CMS (dooh in aumik-ui), TV player (aumik-player in aumik-native). · prototype ↗ReNative + webscaffold
aumik-healthClinical-research benchmarking SaaS; benchmark any health activity, public self-enroll.Takeoutscaffold
aumik-marketingCross-product campaign & ad-creative tracker (one issue per campaign). CRM product moved into aumik-ui.docs / trackeractive

Shared layers

LayerWhat it isStackStage
aumik-agentsThe brains — Mastra HTTP API (law/insurance/medical/home + shared) + booking service. Apps consume over HTTP.Bun·Mastra·Drizzleactive
aumik-uiWeb UI monorepo — all web apps + @aumik/widgets + @aumik/portal.Bun·Tamagui·Turboactive
aumik-nativeReNative monorepo — first app is aumik-player (TV player).yarn·ReNativeactive
aumik-skillsShared Claude Code skills + ADLC commands. See Using aumik-skills.Pythonactive
aumik-infraShared infra compose (Postgres + Redis + Infisical) on Hetzner via Uncloud. Tooling, not a product.Docker·Uncloudlive
aumik-prototypesClickable lo-fi wireframes, one per product — the spec the dev team builds from. proto.aumik.co.static HTMLlive
aumik-docsThis handbook. docs.aumik.co.static HTMLlive
Before you push: a few repos were renamed on GitHub and some local clones / deploy configs still point at old slugs. Verify the remote (git remote -v) before pushing or running gh against a repo.

Using the aumik-skills repo

Aumik's reusable Claude Code skills and ADLC commands — write them once here, consume them from any brand repo instead of re-implementing per repo.

Find your skills by role

Skills are named aumik-<role>-<task>. The prefix is organization, not access control — find your role's row and ignore the rest.

RolePrefixSkills today
Devaumik-dev-*aumik-dev-init-brand
Contentaumik-content-*plan-script, scene-to-video, generate-book, song-to-video
POaumik-po-*aumik-po-daily-standup
Opsaumik-ops-*aumik-ops-secrets-pull

Layout

  • skills/<full-name>/SKILL.md — the skill itself (required).
  • skills/<full-name>/references/*.md — optional contextual docs (linked from SKILL.md).
  • skills/<full-name>/scripts/*.py — ported Python scripts.
  • .claude/commands/<full-name>.md — thin slash-command wrapper.
  • aumik_skills/lib/ — shared Python primitives (flux, brand_presets, book_lib, ffmpeg_helpers, secrets).

Consume it from a brand repo

pip install -e ../../aumik-skills
ln -s ../../aumik-skills/skills .claude/skills-shared

# symlink only your role's commands — keeps .claude/commands/ scoped:
ln -s ../../aumik-skills/.claude/commands/aumik-content-*.md .claude/commands/

Skills import from aumik_skills.lib, never from a sibling skill's scripts/.

When does a skill belong here?

  • Cross-brand only — a skill lives here if ≥2 brands would benefit. Single-brand skills stay in that brand's own .claude/skills/.
  • No brand-specific paths — take paths as arguments; don't assume a repo's layout.
  • Defer to the brand's own CLAUDE.md for operating rules — skills here are libraries, not policy.

Claude Code setup

Set up Claude Code the way the team runs it — the same plugins and memory layer, so skills and cross-session memory work out of the box.

1. Install Claude Code

Install the CLI and sign in (see the official Claude Code docs for your platform). Confirm it runs with claude in a repo.

2. Add the plugin marketplaces

Inside Claude Code, use the /plugin command to add the two marketplaces we use:

/plugin marketplace add anthropics/claude-plugins-official
/plugin marketplace add thedotmack/claude-mem

3. Install the plugins

/plugin install superpowers@claude-plugins-official
/plugin install claude-mem@thedotmack
/plugin install context7@claude-plugins-official
/plugin install code-review@claude-plugins-official
/plugin install claude-md-management@claude-plugins-official
  • claude-mem — persistent cross-session memory. It captures observations as you work and surfaces relevant ones in later sessions, so Claude remembers prior decisions across the codebase.
  • superpowers — the team's skills layer (brainstorming, systematic-debugging, TDD, writing-plans, and more). Claude invokes the right skill for the task automatically.
  • context7 — fetches current library/framework docs on demand instead of relying on training data.
  • code-review / claude-md-management — PR review and CLAUDE.md upkeep helpers.

4. Confirm settings

Your ~/.claude/settings.json should end up with the plugins enabled, e.g.:

{
  "model": "opus[1m]",
  "enabledPlugins": {
    "superpowers@claude-plugins-official": true,
    "claude-mem@thedotmack": true,
    "context7@claude-plugins-official": true
  },
  "extraKnownMarketplaces": {
    "thedotmack": { "source": { "source": "github", "repo": "thedotmack/claude-mem" } }
  }
}

5. Per-repo: pull in shared skills

For Aumik repos, also wire in the shared skills as shown in Using aumik-skills — that gives you the aumik-<role>-* commands on top of the superpowers skills.

Verify before relying on exact versions: plugin versions move (this team is on superpowers v6.x and claude-mem v13.x). If a command name has changed, run /plugin with no arguments to browse the current marketplace listings.