Config files

This page lists every file csuite reads or writes, in order of where operators encounter them. All csuite-owned files are written at 0o600 in directories created at 0o700.

csuite.json — team config

The authoritative source of team state that doesn’t live in the broker’s SQLite.

Created bycsuite setup (interactive wizard) or csuite serve (drops into the wizard if missing on a TTY)
Read bycsuite serve, csuite setup, csuite member, csuite enroll, csuite rotate, csuite quickstart
Written bycsuite setup, csuite member, csuite enroll, csuite rotate, broker on every member mutation through /members/*
Default path./csuite.json
Override--config-path flag, $CSUITE_CONFIG_PATH
Mode0o600

Shape

{
  "team": {
    "name": "platform-eng",
    "directive": "Ship the v2 ingest pipeline by Q3",
    "context": "Multi-paragraph team context...",
    "permissionPresets": {
      "admin": ["team.manage", "members.manage", "objectives.create",
                "objectives.cancel", "objectives.reassign",
                "objectives.watch", "activity.read"],
      "operator": ["objectives.create", "objectives.cancel"]
    }
  },
  "store": {
    "members": [
      {
        "name": "alice",
        "role": { "title": "director", "description": "..." },
        "instructions": "Personal directive prose...",
        "rawPermissions": ["admin"],
        "permissions": ["team.manage", "members.manage", "..."],
        "tokenHash": "sha256:<hex>",
        "totpSecret": { "v": 1, "ct": "<base64>", "iv": "..." }
      }
    ]
  },
  "https": {
    "port": 8718,
    "cert": "/path/to/fullchain.pem",
    "key": "/path/to/privkey.pem"
  },
  "webPush": {
    "vapidPublicKey": "<base64url>",
    "vapidPrivateKey": { "v": 1, "ct": "<base64>", "iv": "..." }
  },
  "jwt": {
    "issuer": "https://app.example.com",
    "audience": "team:<uuid>",
    "jwksUrl": "https://app.example.com/.well-known/jwks.json"
  },
  "files": {
    "root": "/var/lib/csuite-files",
    "maxFileSize": 26843545
  }
}

Notes:

  • permissionPresets are reusable bundles members can reference by name. The server resolves preset names → leaf permissions at load time; what reaches permissions is always the flat leaf list. Member entries store both rawPermissions (what the user typed) and permissions (resolved).
  • tokenHash is sha256:<hex> of the bearer plaintext. The plaintext itself is never persisted. Multi-token mode (added by csuite connect) stores additional rows in the broker’s SQLite, not in this file.
  • totpSecret and webPush.vapidPrivateKey are AES-256-GCM encrypted under the team’s KEK. Schema-versioned via the v field for future rotation.
  • https, webPush, jwt, files are all optional. Absent blocks silently disable the corresponding feature.
  • The first-run wizard auto-migrates plaintext fields it finds to hashed/encrypted form on first boot (the migration count is reported on startup).

Default path resolution

When --config-path and $CSUITE_CONFIG_PATH are both unset, the default depends on platform-specific helpers in csuite-server. Always run with an explicit --config-path in production deployments to avoid surprises.

csuite.json.kek — KEK file

The Key Encryption Key that wraps secrets in csuite.json.

Created byserver module (resolveKek) on first boot if missing
Read byevery command that loads csuite.json (setup, serve, member, enroll, rotate, quickstart)
Default path<config>.kek (alongside csuite.json)
Override$CSUITE_KEK env var (32-byte base64 of the key)
Mode0o600

The KEK is 32 raw bytes encoded base64 at rest (or injected via env var). It encrypts:

  • Each member’s totpSecret
  • The team’s webPush.vapidPrivateKey
  • The pending_enrollments row’s bearer plaintext (between approval and the device-side poll)

If you lose the KEK, those fields can’t be decrypted; you have to re-enroll TOTP for every member and regenerate the VAPID keypair. The bearer token hashes survive (they’re already SHA-256). For production deployments where this matters, inject $CSUITE_KEK from a secrets manager rather than relying on the auto-generated file.

auth.json — client-side bearer cache

Where csuite connect stashes the bearer token after a successful device-code enrollment.

Created bycsuite connect on approval
Read byevery CLI command’s auth resolver (after --token and $CSUITE_TOKEN)
Override$CSUITE_AUTH_CONFIG_PATH
Mode0o600
OSPath
Linux/BSD$XDG_CONFIG_HOME/csuite/auth.json (default ~/.config/csuite/auth.json)
macOS~/Library/Application Support/csuite/auth.json
Windows%APPDATA%\csuite\auth.json

Shape

{
  "schema": 1,
  "entries": [
    {
      "url": "https://broker.example.com",
      "token": "csuite_...",
      "savedAt": 1714255200000
    },
    {
      "url": "http://127.0.0.1:8717",
      "token": "csuite_...",
      "savedAt": 1714250000000
    }
  ]
}

One entry per broker URL; the most recent write for a given URL wins. Lookup is exact-match on URL — no trailing-slash normalization, no scheme upgrade. If you ran csuite connect against https://broker.example.com and then ran csuite push --url https://broker.example.com/, the trailing slash means we won’t find the saved entry. Use the URL form you connected with.

The CLI does NOT persist tokenId, label, or member name here — less metadata on disk = less to leak if the file is exfiltrated. The corresponding metadata lives server-side and can be queried via GET /members/:name/tokens.

.mcp.json — claude-code’s MCP server registry

The file claude-code reads to discover its MCP servers. The runner backs it up and rewrites it on every csuite claude-code invocation.

Path<cwd>/.mcp.json (where you invoke csuite claude-code)
Read byclaude-code itself
Backed up bythe runner — saves a copy to $TMPDIR/<pid>-mcp-<nonce>.json before rewriting
Restored bythe runner — on every exit path (normal, SIGINT, SIGTERM, uncaughtException)

What the runner writes

The runner adds (or replaces) a csuite entry under mcpServers, preserving everything else:

{
  "mcpServers": {
    "your-other-server": { ... },
    "csuite": {
      "command": "/path/to/node",
      "args": ["/path/to/cli/dist/index.js", "mcp-bridge"],
      "env": {
        "CSUITE_RUNNER_SOCKET": "/tmp/.csuite-runner-12345.sock"
      }
    }
  }
}

command is auto-detected from process.execPath (the node binary running the cli); args is auto-detected from process.argv[1] (the cli’s own entry script). This means claude spawns the same cli that spawned it — no $PATH lookup, works identically for global npm install, pnpm script, or development checkout.

Restore lifecycle

The pre-run .mcp.json (if any) is backed up byte-for-byte. On exit, the runner restores the backup atomically. If no .mcp.json existed before the run, the runner deletes the one it created. Both paths are idempotent — double-firing on SIGINT → process.exit() is safe.

The uncaughtException and unhandledRejection handlers also trigger a restore as a last-ditch safety net for crashes.

CODEX_HOME — ephemeral codex config

A temporary directory the codex runner creates per invocation to contain codex’s view of the world.

Path~/.cache/commandsuite/codex/csuite-codex-<random>/ (XDG-aware)
Created bycsuite codex on startup
Removed bycsuite codex on every exit path
Override parent$XDG_CACHE_HOME

Layout

~/.cache/commandsuite/codex/csuite-codex-<random>/
├── auth.json        ← symlink to ~/.codex/auth.json
└── config.toml      ← runner-written

The auth.json symlink ensures OAuth refreshes from the real codex login persist; the symlink is removed on cleanup but the real file isn’t.

The config.toml we generate:

# Auto-generated by csuite codex runner — do not edit.
# Lifetime: this entire CODEX_HOME directory is ephemeral.

[mcp_servers.csuite]
command = "/path/to/node"
args = ["/path/to/cli/dist/index.js", "mcp-bridge"]
enabled = true
default_tools_approval_mode = "approve"

[mcp_servers.csuite.env]
CSUITE_RUNNER_SOCKET = "/tmp/.csuite-runner-12345.sock"

Why ~/.cache/commandsuite/codex/ and not $TMPDIR: codex refuses to install helper binaries (apply-patch, etc.) under tmpfs and emits a warning that disables them. Cache directories don’t trigger that.

Session logs

Not config exactly, but it’s where to look for runner diagnostics.

csuite claude-code and csuite codex each write a structured-JSON log under:

~/.cache/commandsuite/session-claude-code-<pid>.log
~/.cache/commandsuite/session-codex-<pid>.log

Path is printed on startup so you can tail -f it for live diagnostics. Each line is one event — runner state changes, IPC frames, MCP requests / tool calls, trace uploads, broker errors.

The log path moves out of the way when you provide your own log callback (embedders / tests). The default behavior is chosen so claude’s TUI can own stderr without runner output corrupting its frame.

SQLite databases (server)

For completeness — not config files, but the broker’s persistent state lives here.

FileContents
csuite.dbtokens (multi-token), messages (event log), sessions, channels, members extra state, file metadata
csuite-activity.dbactivity stream — objective_open / objective_close markers, llm_exchange typed entries, opaque_http records

Both default to :memory: for ergonomics; set $CSUITE_DB_PATH (and optionally $CSUITE_ACTIVITY_DB_PATH) for real deployments. Both use WAL mode + busy_timeout=5000 + wal_autocheckpoint=1000 — see apps/server/src/db.ts.

csuite prune-traces --older-than <duration> is the maintenance command for the activity DB.