Claude Transcripts docs GitHub
Work in progressUnder active development — not tested as ready for use. Breaking changes land without notice, stored data may need to be discarded between revisions, and there is no auth or security model. These docs describe the intended design as much as the current state.

Configuration

There are two layers of configuration, split by sensitivity:

LayerFileHoldsCommitted?
Top-level settingsconfig/config.template.jsonconfig/config.jsonNon-secret, deployment-wide defaults: database/bucket names, feature flags, tunables, service-menu URLsTemplate yes; the live config/config.json is .gitignored
Secrets & endpoints.env (per machine)Hosts, ports, credentials, S3 keysNo (.gitignored)

Copy the template to config/config.json to customise an instance; the loader falls back to the template, so zero-config development works out of the box. .env values override the matching defaults. Anything secret or per-deployment belongs in .env; anything stable and shareable belongs in config/, which is designed to grow into multiple files.

config/config.json

The committed template, in full — this is the current shape, not a target:

{
  "app": { "name": "claude-transcripts" },

  // CORE / system — dev-level settings & constants (not user-facing)
  "system": {
    "logging": { "chunk": { "maxEntriesPerChunk": 200, "flushIntervalMs": 15000 } },
    // session-lifecycle tunables. liveWindowMs: how long after its last activity a
    // still-open (no SessionEnd) session is treated as running/live before it reads
    // as incomplete/abandoned. Default 86_400_000 (24h). No live heartbeat exists,
    // so this is a recency heuristic; an abandoned session that gets new events
    // (within the window again) flips back to live automatically.
    // idleThresholdMs: gap between consecutive events above which the session counts
    // as idle when deriving *active* duration (vs total wall-clock runtime) on the
    // session detail. Default 300_000 (5 min) — a session left open in tmux stops
    // accruing active time past this gap.
    "sessions": { "liveWindowMs": 86400000, "idleThresholdMs": 300000 }
    // other tunables/constants live here
  },

  // NAMES — designed for MORE THAN ONE database, bucket and index from the start.
  // All three are namespaced so a deployment pointed at a store it doesn't
  // exclusively own can't collide with anything else there.
  "couchdb": {
    "databases": {
      "sessions": "claude-transcripts-sessions",   // the session corpus
      "appLogs":  "claude-transcripts-app-logs"    // operational logs (app-logging.md)
    }
  },
  "s3": {
    "buckets": {
      "sessions": "claude-transcripts-sessions"    // room for more buckets later
    }
  },
  "meilisearch": {
    "indexes": {
      "sessions": "claude-transcripts-sessions",   // session metadata
      "turns":    "claude-transcripts-turns"       // conversation content
    }
  },

  "features": {
    "s3Blobs": true,                 // upload transcript/summary blobs to S3
    "midFlightChunking": true,       // tail the transcript into CouchDB chunk docs during the session
    "couchFullContentChunks": true,  // embed parsed per-turn content in those chunks (ADR 0027)
    "meilisearch": true,             // full-text search over sessions + conversation content
    "secretsMasking": false          // mask secrets on write/read (future scope)
  },

  "servicesMenu": {                  // links shown in the webui Services menu
    "couchdbFauxton": "http://127.0.0.1:7652/_utils/",
    "garageWebui":    "http://127.0.0.1:7655/",
    "meilisearch":    "http://127.0.0.1:7656/"
  },

  // USER settings — reserved, empty for now
  "userSettings": {}
}

Feature flags. midFlightChunking + couchFullContentChunks + system.logging.chunk.* drive mid-flight transcript chunking (mid-flight-chunking.md, ADR 0027). They now default on, and quite a lot depends on that: content chunks are what make a live session's transcript readable before it ends, what the speaker-split views map over, and what content search indexes. Turn couchFullContentChunks off and chunks carry byte ranges only — transcripts then read from S3 (so only after the session ends) and contribute nothing to search. meilisearch gates search entirely (ADR 0009); secretsMasking remains a placeholder. Re-run the CLI's setup after changing flags so the hook's runtime config is rebaked.

Who reads what

Environment variables (.env)

See .env.template — one file for both the host-run webapi/webui/CLI and the Compose stack (the stack runner passes it through). The secret/endpoint variables are: COUCHDB_URL (full base URL — wins over COUCHDB_HOST/PORT), COUCHDB_HOST/PORT/USER/PASSWORD/DB, S3_ENDPOINT/REGION/ACCESS_KEY/SECRET_KEY/BUCKET, and the webapi/webui host/port settings.

Backend topology — bundled or external

The app container is told where its backends live purely through env, so the same image runs in two topologies (containers.md):

Not yet verified end to end — the plumbing is in place, but no external deployment has been exercised; expect rough edges (bucket + key creation is manual, bootstrap:garage only targets the bundled Garage, and a CouchDB path prefix depends on how the nano client joins the database name onto the base URL — untested).

Toggling optional components

Per tiers.md, several components are optional and switch off via config, losing only their feature:

Services menu

servicesMenu lists the backing-service admin dashboards surfaced in the webui (CouchDB Fauxton, Garage WebUI, Meilisearch). In the bundled stack these are local; with external backends, point them wherever the services live. Making this menu fully config-driven (rather than partly hard-coded in the webui today) is tracked in #14.

Design goal: everything configurable

The intent is that as much as possible is configurable — names, feature toggles, tunables, service URLs, and (per ADR 0017) the hook→action bindings — all flow from claude-transcripts.config.json (non-secret) + .env (secret), with no second config source. New knobs extend this file rather than introducing another.

Optional, on by default (features.meilisearch), and local-only in the bundled stack: Meilisearch is published on 127.0.0.1:7656, the same posture as the webapi. Indexing happens on your machine — the webapi follows CouchDB's change feed and writes to Meilisearch, both of them local; the hook never touches it. Turning the feature off costs you the search box and nothing else, since every index is derived from CouchDB and rebuildable with claude-transcripts reindex.

install creates and fills the indexes for you, and doctor checks that a session it just wrote is findable — so a broken index shows up at setup rather than the first time you search.

Pointing at an external Meilisearch

MEILI_HOST (and MEILI_API_KEY, for an instance with a master key) can point anywhere — but read ADR 0028 first, because Meilisearch is unlike the other backing services.

⚠️ The turns index holds conversation text. An external Meilisearch is the one configuration where this project's data leaves the machine it was recorded on.

CouchDB and Garage are stores: point at another one and the app works. Meilisearch is a derived index this app configures, feeds, and rebuilds — and reindex clears an index before repopulating it. That's safe only because the index names are namespaced (claude-transcripts-*) and therefore ours. If you change them, keep them distinct from anything else on that engine.

The bundled instance stays the default and the configuration we test.