Skip to content

Latest commit

 

History

History
362 lines (297 loc) · 14.5 KB

File metadata and controls

362 lines (297 loc) · 14.5 KB

ALOS — AnniLifeOS

Briefing for Claude Code


What is this

AnniLifeOS (ALOS) is a personal life OS — tasks, capture, reminders, finance, journal, goals, and AI assistant — built for self-hosting. It is part of a larger ecosystem of "Anni" projects all building toward an umbrella platform called YUME.

ALOS has two delivery surfaces that share one backend:

  • Web app (alos.yumehana.dev) — universal access, any device, any browser
  • Desktop app (Tauri, Win11 first) — same web frontend, but with a native window and access to local OS features: filesystem read/write, terminal commands, STT via local Whisper, tray icon, background processes

The distinction mirrors Claude.ai vs Claude desktop. Same brain, same features, but the desktop app can touch local system resources. Web is access everywhere, app is the power layer.


Ecosystem context

YUME (future umbrella — no code yet)
├── ALOS (this repo) — life OS
├── YUNA — notification backbone + Discord bot (separate repo: D:\.src\YUNA)
├── AnniWebsite — personal site (yumehana.dev, separate repo)
└── Other Anni projects (AnniWin11, AnniProxy, CZTimers, ...)

YUNA = "Your Unified Notification Assistant". Eventually ALOS sends events to YUNA, and YUNA decides how to deliver them (Discord DM, push notification, Win11 toast, Watch ping). For now, ALOS handles notifications itself — just keep the architecture clean enough that YUNA can be wired in later as a service.


Infrastructure

Everything self-hosted. No external cloud services except Cloudflare Tunnel (no open inbound ports) and third-party APIs (Claude, ElevenLabs, OAuth providers).

Browser / Tauri app
  └── Cloudflare Tunnel (HTTPS)
        └── nginx on RPi4
              ├── alos.yumehana.dev  →  /opt/anni/alos/       (Vite build)
              │                      →  127.0.0.1:4100         (ALOS Express API)
              └── (future: /api/whisper → ThinkPad Whisper service)

Storage: RPi4 with 2x 2TB hot-plug drives at /srv/storage
Auth DB: SQLite (shared with AnniWebsite)
ALOS DB: SQLite at /srv/storage/alos.db

The ThinkPad runs a Whisper STT service (FastAPI + faster-whisper). ALOS backend proxies STT requests to it. This is a future concern — scaffold the route but don't implement it in P0.


Auth — reuse AnniWebsite's system

AnniWebsite already has working OAuth (GitHub, Discord, Google) with session cookies on .yumehana.dev. ALOS shares this.

  • Session cookie domain: .yumehana.dev
  • Auth routes live on the AnniWebsite backend (yumehana.dev/api/auth/*)
  • ALOS backend validates sessions by calling /api/auth/me on the AnniWebsite backend (internal network call on RPi4)
  • GET /api/auth/me returns { id, username, email, role, avatar } or 401
  • Login flow: redirect to https://yumehana.dev/#/login?returnTo=https://alos.yumehana.dev
  • After OAuth, user lands back on ALOS with a valid .yumehana.dev session cookie

All ALOS API routes require auth. No public endpoints except health check.


Stack

Layer Tech Notes
Frontend Vite + Vanilla JS Same as AnniWebsite — no framework
Backend Node.js + Express Port 4100
Database SQLite via better-sqlite3 WAL mode, FK on
Desktop wrapper Tauri (Rust) Wrap the same Vite frontend
STT Whisper (faster-whisper on ThinkPad, FastAPI) Proxy via ALOS backend
AI Claude API (claude-sonnet-4-20250514) Anthropic SDK
Voice output ElevenLabs API Scaffold only in P0
Reverse proxy nginx Same setup as AnniWebsite
Tunnel Cloudflare Tunnel Already running on RPi4

Design system

Port exactly from D:\.src\AnniWebsite\client\src\styles\global.css and components.css. Do not invent a new design language.

Key tokens:

--bg:        #080c12   /* near-black */
--bg2:       #0d1320
--bg3:       #111927
--surface:   rgba(255,255,255,0.04)
--surface-h: rgba(255,255,255,0.07)
--border:    rgba(255,255,255,0.07)
--border-h:  rgba(99,210,190,0.35)
--text:      #dde6ef
--text-soft: #b0bec8
--muted:     #627585
--accent:    #63d2be   /* teal — primary accent */
--accent2:   #4fa8d8   /* blue */
--accent3:   #a78bfa   /* purple */
--radius:    14px
--radius-lg: 20px

--font-head: 'Syne', sans-serif       /* headings */
--font-body: 'DM Sans', sans-serif    /* body */
--font-mono: 'DM Mono', monospace     /* code, numbers */

Card style: background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); backdrop-filter: blur(12px); with hover lift.

Dark theme only for now. Custom cursor (dot + ring). Starfield canvas background.


Monorepo structure

AnniLifeOS/
├── client/                    # Vite frontend (shared by web + Tauri)
│   ├── index.html
│   ├── package.json
│   ├── vite.config.js
│   └── src/
│       ├── main.js            # Entry point
│       ├── lib/
│       │   ├── router.js      # Hash-based SPA router (same pattern as AnniWebsite)
│       │   ├── api.js         # All fetch calls to backend
│       │   ├── auth.js        # Auth state, login redirect, /api/auth/me check
│       │   ├── toast.js       # Toast notifications
│       │   └── theme.js       # Theme helpers
│       ├── effects/
│       │   ├── starfield.js   # Starfield canvas (port from AnniWebsite)
│       │   └── cursor.js      # Custom cursor (port from AnniWebsite)
│       ├── components/
│       │   ├── nav.js         # Top nav / sidebar
│       │   └── modal.js       # Modal utility
│       ├── pages/
│       │   ├── home.js        # Dashboard / today view
│       │   ├── capture.js     # Quick capture
│       │   ├── tasks.js       # Tasks + backlog
│       │   ├── finance.js     # Finance tracker
│       │   ├── journal.js     # Journal / daily notes
│       │   ├── goals.js       # Goals
│       │   └── ai.js          # AI chat
│       └── styles/
│           ├── global.css     # Tokens, reset (ported from AnniWebsite)
│           ├── components.css # Buttons, cards, nav (ported from AnniWebsite)
│           └── alos.css       # ALOS-specific styles
├── server/
│   ├── server.js              # Express setup, middleware, routes
│   ├── db/
│   │   ├── db.js              # SQLite singleton
│   │   └── schema.sql         # Full schema
│   ├── middleware/
│   │   └── auth.js            # Session validation (calls AnniWebsite /api/auth/me)
│   ├── routes/
│   │   ├── capture.js         # POST /api/capture
│   │   ├── tasks.js           # CRUD /api/tasks
│   │   ├── reminders.js       # CRUD /api/reminders
│   │   ├── finance.js         # CRUD /api/finance
│   │   ├── journal.js         # CRUD /api/journal
│   │   ├── goals.js           # CRUD /api/goals
│   │   ├── ai.js              # POST /api/ai/chat (Claude streaming)
│   │   ├── stt.js             # POST /api/stt (proxy to Whisper — scaffold only)
│   │   └── voice.js           # POST /api/voice (ElevenLabs — scaffold only)
│   ├── .env.example
│   └── alos.service           # systemd unit
├── app/                       # Tauri wrapper
│   ├── src-tauri/
│   │   ├── Cargo.toml
│   │   ├── tauri.conf.json    # Points webview at client dev server or built dist
│   │   └── src/
│   │       └── main.rs        # Tauri commands: fs read/write, shell exec, STT
│   └── package.json
├── nginx/
│   └── alos.yumehana.dev.nginx
├── deploy.sh
├── deploy.ps1
└── README.md

Database schema (P0)

-- Users are managed by AnniWebsite auth — store only what ALOS needs
CREATE TABLE users (
  id          TEXT PRIMARY KEY,  -- matches AnniWebsite user id
  username    TEXT NOT NULL,
  email       TEXT NOT NULL,
  created_at  INTEGER DEFAULT (unixepoch())
);

CREATE TABLE captures (
  id          TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(8)))),
  user_id     TEXT NOT NULL REFERENCES users(id),
  raw         TEXT NOT NULL,     -- original input
  parsed      TEXT,              -- JSON: {type, title, due, priority, tags}
  status      TEXT DEFAULT 'inbox',  -- inbox | processed | archived
  created_at  INTEGER DEFAULT (unixepoch())
);

CREATE TABLE tasks (
  id          TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(8)))),
  user_id     TEXT NOT NULL REFERENCES users(id),
  title       TEXT NOT NULL,
  notes       TEXT,
  priority    TEXT DEFAULT 'medium',  -- low | medium | high
  status      TEXT DEFAULT 'todo',    -- todo | doing | done
  due_at      INTEGER,
  created_at  INTEGER DEFAULT (unixepoch()),
  updated_at  INTEGER DEFAULT (unixepoch())
);

CREATE TABLE reminders (
  id          TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(8)))),
  user_id     TEXT NOT NULL REFERENCES users(id),
  title       TEXT NOT NULL,
  body        TEXT,
  remind_at   INTEGER NOT NULL,
  recurring   TEXT,   -- null | daily | weekly | monthly (cron-style later)
  done        INTEGER DEFAULT 0,
  created_at  INTEGER DEFAULT (unixepoch())
);

CREATE TABLE finance_entries (
  id          TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(8)))),
  user_id     TEXT NOT NULL REFERENCES users(id),
  amount      REAL NOT NULL,
  type        TEXT NOT NULL,   -- income | expense
  category    TEXT,
  note        TEXT,
  date        INTEGER NOT NULL,
  created_at  INTEGER DEFAULT (unixepoch())
);

CREATE TABLE journal_entries (
  id          TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(8)))),
  user_id     TEXT NOT NULL REFERENCES users(id),
  date        TEXT NOT NULL,   -- YYYY-MM-DD
  content     TEXT,
  mood        INTEGER,         -- 1-5
  created_at  INTEGER DEFAULT (unixepoch()),
  updated_at  INTEGER DEFAULT (unixepoch())
);

CREATE TABLE goals (
  id          TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(8)))),
  user_id     TEXT NOT NULL REFERENCES users(id),
  title       TEXT NOT NULL,
  area        TEXT,            -- health | finance | growth | projects | social
  horizon     TEXT,            -- week | month | quarter | year | life
  status      TEXT DEFAULT 'active',
  progress    INTEGER DEFAULT 0,  -- 0-100
  notes       TEXT,
  created_at  INTEGER DEFAULT (unixepoch()),
  updated_at  INTEGER DEFAULT (unixepoch())
);

CREATE TABLE ai_messages (
  id          TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(8)))),
  user_id     TEXT NOT NULL REFERENCES users(id),
  role        TEXT NOT NULL,   -- user | assistant
  content     TEXT NOT NULL,
  context     TEXT,            -- JSON: injected context snapshot at time of message
  tokens_used INTEGER,
  created_at  INTEGER DEFAULT (unixepoch())
);

P0 deliverable — what to build first

Goal: a working shell you can open, log into, and navigate. No broken state.

  1. Scaffold the monorepo with all folders and config files
  2. Port the design system CSS from AnniWebsite exactly
  3. Port starfield.js and cursor.js from AnniWebsite effects
  4. Build the SPA router (hash-based, same pattern as AnniWebsite)
  5. Build the auth flow — on load, call /api/auth/me; if 401, show login screen with redirect to AnniWebsite OAuth; if authed, show app
  6. Build the app shell — sidebar nav with all sections listed, top bar with user avatar, empty page stubs for each section
  7. Set up Express backend with SQLite, WAL mode, FK on, schema applied
  8. Implement the auth middleware (validate session via AnniWebsite /api/auth/me)
  9. Build the quick capture input — text field, Enter to submit, calls POST /api/capture, drops item into DB as status: inbox
  10. Build the inbox view — lists captures with status inbox, basic clear/archive actions
  11. Set up the Tauri project in app/ pointing at the Vite dev server for local dev
  12. Write nginx config for alos.yumehana.dev
  13. Write systemd service file
  14. Write deploy.sh / deploy.ps1

Do not build in P0: AI chat, STT, ElevenLabs, finance, journal, goals, recurring reminders, YUNA integration. Scaffold the routes and empty page stubs so the nav is complete, but implement only capture + tasks in P0.


AI integration notes (for P1+, context only)

  • Model: claude-sonnet-4-20250514
  • Streaming responses via SSE (text/event-stream)
  • Context injection: before each AI message, inject a JSON snapshot of the user's current tasks, reminders, recent captures, and goals. This is how the AI knows about your life.
  • Token budget tracking per user — store tokens_used on each ai_messages row, aggregate per month
  • Quick capture AI parsing: POST /api/capture should optionally call Claude to parse raw input into structured { type, title, due, priority, tags } JSON. In P0 just store raw input, add parsing in P1.
  • System prompt should be aware it is ALOS's embedded AI assistant, has access to the user's data, and should be concise and action-oriented

Security requirements

  • All API routes behind auth middleware — no exceptions except GET /health
  • Input validation on all routes (use zod or manual checks — no raw user strings into SQL)
  • Parameterised queries only — no string concatenation in SQL
  • Rate limiting on AI and STT routes
  • CORS: only allow alos.yumehana.dev and localhost origins
  • Session cookie: httpOnly: true, secure: true, sameSite: 'lax', domain: '.yumehana.dev'
  • No secrets in client bundle — all API keys backend only

Dev setup (target)

# Backend
cd server && npm install
cp .env.example .env
node server.js   # → http://127.0.0.1:4100

# Frontend
cd client && npm install
npm run dev      # → http://localhost:3100 (proxy /api/* to :4100)

# Tauri (desktop)
cd app && npm install
npm run tauri dev

.env needs:

SESSION_SECRET=
ANNI_AUTH_URL=http://127.0.0.1:4000   # AnniWebsite backend internal URL
CLAUDE_API_KEY=
ELEVENLABS_API_KEY=
WHISPER_URL=http://thinkpad-local:8000  # Whisper service (future)
DB_PATH=/srv/storage/alos.db
PORT=4100

Tone / philosophy

  • Minimal working system over perfect system. Ship something usable fast, iterate.
  • Every feature should be genuinely faster than doing it manually. If it's not, don't ship it.
  • The quick capture is the hero feature. Everything else is secondary.
  • Code should be clean and readable. Same patterns as AnniWebsite — consistent style matters because this will get big.
  • Comments where intent isn't obvious. No comments for the obvious.

ALOS v0.1 — part of the YUME ecosystem