No description
  • TypeScript 46.3%
  • JavaScript 39.9%
  • CSS 13.2%
  • Dockerfile 0.5%
Find a file
Brian 4db6d05850
All checks were successful
Build and Deploy / build (push) Successful in 26s
Build and Deploy / deploy (push) Successful in 18s
Adopt shared claude-chat-client / @brian/claude-chat-ui packages
Moves both chat routes' Anthropic call/tool-loop and the composer's
voice input onto packages shared with careboard/HomeManagement/
kittycharactersheet, so fixes (like the Android dictation-duplication
bug) are maintained in one place instead of separately in each app.

- server/index.js: both /api/chats/:id/messages and
  /api/shared/:id/messages replace their hand-rolled while(keepLooping)
  streaming/tool-loop with ClaudeClient.chat() from
  @brian/claude-chat-client, loaded via a lazy `await import(...)`
  (getClaudeClient()) since the package is ESM-only and this file is
  CommonJS - the same interop pattern already used here for mupdf.
  buildAttachmentBlock()/storedAttachmentBlock() are left untouched:
  they already produce the exact same content-block shapes the shared
  client's new image/pdf/text helpers do, so there's no benefit to
  replacing already-correct code. Required three additions to
  claude-chat-client to preserve exact behavior: onUsage (every round
  is separately billed, not just the final one), onToolUseBatch (one
  combined "fetching weather - searching web" status line instead of
  one per call, plus the exact assistant content array including any
  preamble text), and onToolResultBatch (for persisting the tool_result
  turn homeclaude's own richer message history needs). Also fixed the
  Node client's tool execution from parallel (Promise.all) to
  sequential, matching every real hand-rolled loop it replaces.

- client/src/features/chat/components/Composer.tsx: useVoiceInput
  (hand-rolled, but had already independently converged on the same
  last-entry-only + fresh-instance-per-restart fix as the shared hook)
  replaced with @brian/claude-chat-ui's useSpeechToText. Deleted
  useVoiceInput.ts and the now-fully-dead speech-recognition.d.ts
  ambient types file.

- Dockerfile, .forgejo/workflows/build-deploy.yml: both the client
  build stage (@brian/claude-chat-ui) and the backend stage
  (@brian/claude-chat-client) now depend on git.thenymans.com's npm
  registry rather than npmjs.com, threaded through as a BuildKit secret
  (docker buildx build, not plain docker build). Validated structurally
  against the real registry with dummy credentials (genuine 401s, not
  path/syntax errors).

No existing test suite in this app to lean on, so this leaned harder on
syntax checks, an isolated CJS/ESM interop test mirroring the exact
dynamic-import pattern used, and manual code review preserving every
original behavior than the careboard/HomeManagement migrations did.
2026-07-20 23:20:46 -06:00
.forgejo/workflows Adopt shared claude-chat-client / @brian/claude-chat-ui packages 2026-07-20 23:20:46 -06:00
client Adopt shared claude-chat-client / @brian/claude-chat-ui packages 2026-07-20 23:20:46 -06:00
server Adopt shared claude-chat-client / @brian/claude-chat-ui packages 2026-07-20 23:20:46 -06:00
.dockerignore Initial commit 2026-05-27 11:20:49 -06:00
.env.example Add support for image generation 2026-06-16 08:35:30 -06:00
.gitignore Migrate frontend to React + TypeScript with a visual refresh 2026-07-20 20:41:50 -06:00
.npmrc Adopt shared claude-chat-client / @brian/claude-chat-ui packages 2026-07-20 23:20:46 -06:00
docker-compose.dev.yml Migrate frontend to React + TypeScript with a visual refresh 2026-07-20 20:41:50 -06:00
docker-compose.yml Add OIDC single sign-on (link existing accounts; auto-provision new ones) 2026-06-09 12:21:47 -06:00
Dockerfile Adopt shared claude-chat-client / @brian/claude-chat-ui packages 2026-07-20 23:20:46 -06:00
package-lock.json Migrate frontend to React + TypeScript with a visual refresh 2026-07-20 20:41:50 -06:00
package.json Adopt shared claude-chat-client / @brian/claude-chat-ui packages 2026-07-20 23:20:46 -06:00
README.md Add support for image generation 2026-06-16 08:35:30 -06:00

Claude Family Chat

A self-hosted chat app that lets your family share access to Claude — each person gets their own private conversation history. Your API key stays on the server.

Quick Start

1. Get an API key

Sign up at console.anthropic.com and create an API key.

2. Edit docker-compose.yml

Open docker-compose.yml and fill in:

ANTHROPIC_API_KEY: "sk-ant-your-actual-key"
USERS: '{"alice":"password1","bob":"password2"}'
SESSION_SECRET: "any-long-random-string"

3. Run it

docker compose up -d

The app is now running at http://localhost:3000

On your home network, other devices can reach it at http://YOUR-LOCAL-IP:3000 (Find your IP with ipconfig on Windows or ifconfig / ip addr on Mac/Linux)


The Admin Page

Admins get a dedicated page at /admin (a ⚙ Admin link appears in the sidebar). From there you can, without ever editing the deploy or restarting:

  • Manage users — add a user directly (username + temporary password), or generate a one-time invite link (/join?token=…) the new person opens to set their own password. Delete users and grant/revoke admin.
  • View usage & cost — per-user token usage and pergroup-chat usage (group chats are tracked separately), with period filters and cost estimates.
  • Manage shared chats — create, edit (title / members / prompt), and delete.
  • Edit configuration — the default system prompt, model, max-history, and the Anthropic & Brave API keys (masked, with reveal). Changes take effect immediately.

Everything here is stored in the SQLite database (/data/app.db).


Managing Users

Users and the admin flag are stored in a small SQLite database (/data/app.db), with passwords hashed (scrypt) — they're never stored in plaintext. Day-to-day user management happens on the Admin Page; the environment variables below are only used to bootstrap an empty database on first run.

First run: set USERS and ADMIN_USER so the first accounts (including your admin) exist. On the very first boot with an empty database, these are imported automatically:

USERS: '{"mom":"secure-pass","dad":"another-pass","kid1":"their-pass"}'
ADMIN_USER: 'mom'

After the first boot the database is the source of truth — changes to USERS / ADMIN_USER are ignored once any user exists. Manage users from the Admin Page instead. (To re-seed from scratch, wipe the data volume with docker compose down -v.)


Single sign-on (OIDC)

You can let people sign in through an OpenID Connect identity provider (Authentik, Authelia, Keycloak, Zitadel, Google, …) instead of — or in addition to — a username and password.

How it behaves:

  • New people are provisioned automatically. The first time someone signs in through your provider, an account is created for them (the username is derived from their preferred_username/email). On a brand-new install, the very first person to sign in this way becomes the admin.
  • Existing users can link their account. A password user opens the in-app Account panel (the person icon next to Sign out) and clicks Link single sign-on; from then on they can log in either way. Linking is the only way to attach SSO to an existing account — signing in with an unlinked provider identity always creates a separate account.
  • OIDC-only accounts have no password until the user sets one in the Account panel. They can't unlink SSO until they do (it'd lock them out).

⚠️ Provisioning is open: anyone your identity provider authenticates gets an account here. Restrict who can authenticate (or which apps they can reach) at the provider, not in this app.

Setup: create an OIDC/OAuth2 confidential client in your provider with the redirect URI:

https://YOUR-DOMAIN/api/auth/oidc/callback

Then fill in the Single sign-on (OIDC) section on the Admin Page (or seed it with the OIDC_* env vars on first boot). Once the issuer, client ID, and client secret are all set, a Sign in with single sign-on button appears on the login page. Requested scopes are openid email profile.


Environment Variables

Most settings now live in the database and are edited on the Admin Page. The variables below seed the database on first boot only (when their value isn't set in the DB yet); afterward the DB wins and the admin page is where you change them. Only the infrastructure variables (SESSION_SECRET, DB_FILE, PORT) are read from the environment on every boot.

Variable Default Description
ANTHROPIC_API_KEY (required first boot) Your Anthropic API key. Seeds the DB, then editable on the Admin Page
USERS {} JSON object of username→password pairs. First boot only — ignored once any user exists
ADMIN_USER (none) Username to mark as admin when seeding. First boot only
BRAVE_API_KEY (none) Brave Search API key (enables web search). Seeds the DB, then editable on the Admin Page
CLAUDE_MODEL claude-sonnet-4-6 Default model. Seeds the DB, then editable on the Admin Page
MAX_HISTORY 100 Max messages kept per chat. Seeds the DB, then editable on the Admin Page
SYSTEM_PROMPT (helpful-assistant prompt with tool guidance) Default system prompt. Seeds the DB, then editable on the Admin Page
OIDC_ISSUER (none) OIDC provider issuer URL. Seeds the DB, then editable on the Admin Page
OIDC_CLIENT_ID (none) OIDC client ID. Seeds the DB, then editable on the Admin Page
OIDC_CLIENT_SECRET (none) OIDC client secret. Seeds the DB, then editable on the Admin Page
OIDC_BUTTON_LABEL Single sign-on Label on the sign-on button. Seeds the DB, then editable on the Admin Page
OIDC_BASE_URL (auto-detected) Override for the base URL used to build the redirect URI. Seeds the DB, then editable on the Admin Page
COMFYUI_URL (none) ComfyUI HTTP API URL. When set, Claude gets a generate_image tool (the server must be able to reach this URL). Seeds the DB, then editable on the Admin Page
COMFYUI_CHECKPOINT (none) Checkpoint for the built-in default txt2img workflow. Seeds the DB, then editable on the Admin Page
COMFYUI_STEPS 25 Sampler steps for the built-in workflow. Seeds the DB, then editable on the Admin Page
COMFYUI_CFG 7 CFG scale for the built-in workflow. Seeds the DB, then editable on the Admin Page
SESSION_SECRET change-me-in-production Secret for signing session cookies — change this! Read from env every boot
DB_FILE /data/app.db Path to the SQLite database. Read from env every boot
PORT 3000 Port the server listens on. Read from env every boot

Data & Privacy

  • Conversation histories are saved in a Docker volume (chat-data) — they persist across restarts
  • User accounts and runtime config (API keys, system prompt, model) live in a SQLite database (/data/app.db) on the same volume; passwords are hashed (scrypt), never stored in plaintext
  • Each user's history is completely isolated from other users
  • Your API key is only on the server; it's never sent to the browser (admins can reveal it on the Admin Page)
  • To wipe all history and accounts: docker compose down -v (deletes the volume)

Monitoring Costs

Keep an eye on your API usage at console.anthropic.com/usage. Set a spending limit under Settings → Limits to avoid surprises.


Updating

docker compose pull   # if using a registry image
docker compose up -d --build

Running on a Raspberry Pi or Home Server

Works great on any always-on machine. Just open port 3000 in your firewall/router if you want access outside your home network (consider adding HTTPS via a reverse proxy like Caddy or nginx in that case).