No description
  • TypeScript 82.2%
  • Python 17.8%
Find a file
Brian b5ae2ff10c
All checks were successful
Publish packages / publish-npm (push) Successful in 14s
Publish packages / publish-pypi (push) Successful in 16s
Add system-prompt cache_control support to claude-chat-client
kittycharactersheet's hand-rolled brainstorm loop caches its large,
stable game-ruleset text for an hour so multi-turn/tool-loop follow-ups
don't re-bill it as fresh input tokens every call. The client's system
option only took a plain string, with no way to set a cache_control
breakpoint, which would have been a real cost regression on migration.
Widen system to string | TextBlockParam[] and add cachedTextBlock() to
build the cached block.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 13:48:17 -06:00
.forgejo/workflows Fix pypi publish idempotency check: match this registry's 409 wording 2026-07-20 14:17:54 -06:00
packages Add system-prompt cache_control support to claude-chat-client 2026-07-21 13:48:17 -06:00
.gitignore Initial scaffold: shared Claude chat UI + backend clients 2026-07-20 13:18:28 -06:00
package.json Initial scaffold: shared Claude chat UI + backend clients 2026-07-20 13:18:28 -06:00
README.md Initial scaffold: shared Claude chat UI + backend clients 2026-07-20 13:18:28 -06:00

claudechatwindow

Shared packages extracted from the Claude chat feature that's been independently built (and independently bugged) in careboard, homeclaude, HomeManagement, and kittycharactersheet — plus the one about to be added to medsync.

Two packages, not one central service. Each app keeps its own API key and model (no shared credential, no proxy to run/secure/monitor), so per-app usage tracking works exactly as it does today. Fixes — like the Android speech-recognition duplication bug — land here once, and each app picks them up by bumping a dependency version.

  • packages/ui (@brian/claude-chat-ui) — React useSpeechToText hook, ChatInput, MessageList. See packages/ui/README.md.
  • packages/client-node (@brian/claude-chat-client) — Node/TS Anthropic client + tool-use loop, for the two Node backends. See packages/client-node/README.md.
  • packages/client-python (claude-chat-client-py, import name claude_chat_client) — Python equivalent, for the three Python backends. See packages/client-python/README.md.

All three were typechecked/built/import-tested in node:20-slim and python:3.12-slim containers (no Node/npm/pip available on this machine directly). Re-run that after any further changes:

docker run --rm -v "$(pwd)":/work -w /work node:20-slim \
  sh -c "npm install && npm run typecheck --workspaces && npm run build --workspaces"

docker run --rm -v "$(pwd)":/work -w /work python:3.12-slim \
  sh -c "pip install anthropic && pip install -e packages/client-python"

Publishing (Forgejo package registry)

.forgejo/workflows/publish.yml publishes all three packages to this Forgejo instance's own package registry on push to main — same git.thenymans.com/brian namespace the other apps already push Docker images to, reusing the same REGISTRY_USER/REGISTRY_PASSWORD repo secrets. Nothing has been pushed yet; before any consuming app's Docker build can succeed, this repo needs to actually exist on git.thenymans.com and have those secrets configured, and a push to main needs to run the workflow at least once so v0.1.0 of each package lands in the registry.

  • npm packages install from https://git.thenymans.com/api/packages/brian/npm/ (scoped as @brian/...).
  • The Python package installs from https://git.thenymans.com/api/packages/brian/pypi/simple/.
  • REGISTRY_PASSWORD is used both as the npm _authToken and the PyPI upload password — confirm that token actually has package write scope in Forgejo (Docker registry auth and package registry auth are separate permission checks even though the existing workflows reuse the same secret name for both).

What's not here yet

  • No unified chat-drawer component. careboard, homeclaude, HomeManagement, and kittycharactersheet each have differently-shaped chrome (bottom sheet / vanilla-JS monolith / drawer / floating launcher) and different wire formats (NDJSON vs. SSE-like). ChatInput and MessageList are the pieces common enough to extract without forcing a redesign; the surrounding layout stays app-specific for now.
  • homeclaude is vanilla JS/HTML, not React. @brian/claude-chat-client (Node) still applies to its backend. For the frontend, either bundle @brian/claude-chat-ui as a web component later, or leave homeclaude's hand-rolled speech code as the one holdout — it already has a correct fix in place, it's just not shared.

Per-app migration

homeclaude (server/index.js, already @anthropic-ai/sdk)

Backend only: replace the .messages.create(...) call sites (index.js lines 372, 446, 474, 2033) and getClient() with createClaudeClient({ apiKey, model }) from @brian/claude-chat-client, keeping its existing DB-backed key/model config as the source of those two values. Frontend (public/index.html) can stay as-is — it already has a correct, if unshared, fix for the voice bug.

kittycharactersheet (backend/anthropic.js, frontend/src/components/BrainstormChat.jsx)

Backend: swap runBrainstorm()'s hand-rolled stream/tool loop for @brian/claude-chat-client, keeping its existing 3-tier key resolution (per-user → admin default → env var) and its MODEL constant as the model passed in. Frontend: no voice input exists yet here — if you want to add it, wire up @brian/claude-chat-ui's ChatInput/useSpeechToText rather than writing a fourth version of the fix.

careboard (api/app/services/assistant.py, raw httpx)

Backend: replace _call_anthropic/run_turn in assistant.py with ClaudeClient from claude-chat-client-py, keeping the existing DB-backed settings_service lookups for anthropic_api_key / anthropic_model as what gets passed into ClaudeClientConfig. This also moves careboard off raw httpx onto the official SDK, matching the other Python apps. Frontend (web/src/lib/speech.ts, web/src/components/AssistantPanel.tsx): swap for @brian/claude-chat-ui; careboard's current continuous=false approach avoids the Android bug by never entering continuous mode, so switching to the shared useSpeechToText (continuous, with the fix applied) is a behavior upgrade, not just a dedupe — continuous mode means users don't have to tap the mic again between sentences.

HomeManagement (backend/app/routers/chat.py, frontend/src/components/ChatDrawer.tsx)

This one's already the closest match to both new packages — its streaming loop and its speech-recognition fix are what claude-chat-client-py and useSpeechToText are modeled on. Backend: replace _run_chat with ClaudeClient.chat(...), keeping HM_ANTHROPIC_API_KEY/HM_ANTHROPIC_MODEL (via appconfig.get_app_config) as the config source. Frontend: replace ChatDrawer.tsx's toggleListening (lines 111-164) with useSpeechToText/ChatInput.

medsync (new)

Since this is being built fresh rather than migrated, start directly on top of both packages instead of writing a fourth bespoke version:

  • Backend (backend/app/routes/): new chat router built on ClaudeClient from claude-chat-client-py, with its own MEDSYNC_ANTHROPIC_API_KEY/MEDSYNC_ANTHROPIC_MODEL config, following HomeManagement's appconfig pattern.
  • Frontend (frontend/src/components/): new chat panel built on ChatInput/MessageList/useSpeechToText from @brian/claude-chat-ui, styled to match medsync's existing ScanModal.jsx conventions.