Skip to content

Feature: Live remote/multi-machine data collection (SSH & beyond) with per-source scope in Settings #249

Description

@hoangsonww

Summary

Today CCAM only monitors Claude Code sessions on the same machine the dashboard runs on. Many users run Claude Code on other machines — most commonly over SSH (dev boxes, cloud VMs, remote workstations) — while keeping the CCAM app on their local machine.

Request: let users collect Claude Code usage from additional places (SSH machines, and potentially other transports) and see that data live, in real time, in their local CCAM dashboard — alongside or instead of local data, user's choice.

Motivating use case

I use Claude Code on an SSH machine, but run the CCAM app on my laptop. I want to see the SSH machine's usage live in my local CCAM.

Proposed UX (new Settings section)

A new "Remote Data Sources" section on the Settings page where the user picks where their data comes from:

  • Local only (current behavior, default)
  • Specific remote source(s) — one or more SSH (or other) sources
  • All / both — local + every configured remote, merged

Changing the selection updates the entire dashboard immediately to reflect the chosen scope.


Investigation — is this feasible?

Yes. Real-time is achievable and SSH is the natural first transport. Below is an architecture-grounded analysis (file:line refs against current master-line code).

Current pipeline (single-machine by design)

Claude Code event
  -> scripts/hook-handler.js   POST http://127.0.0.1:<port>/api/hooks/event   (target host HARDCODED)
  -> server/routes/hooks.js    processEvent() transaction
  -> SQLite (~/.claude/agent-dashboard/dashboard.db, single shared file)
  -> server/websocket.js       broadcast (every client gets every message)
  -> React UI                  talks same-origin /api + /ws only

Blockers to remote collection

# Blocker Where Fix size
1 Hook POST target host hardcoded 127.0.0.1 scripts/hook-handler.js:76 (path /api/hooks/event also hardcoded :78) small
2 Server binds 127.0.0.1 by default (opt-in wider via DASHBOARD_HOST) server/lib/security.js:28, server/index.js:176 env only
3 Ingestion is unauthenticated even when DASHBOARD_TOKEN is set/hooks is in TOKEN_EXEMPT_PREFIXES server/lib/security.js:125 security must-fix before any network exposure
4 No machine / host / origin column in the schema — cannot tell which machine a session/event/agent came from server/db.js:136-407 additive, migration-safe
5 Client hardwired to same-origin /api + /ws; no "point elsewhere" setting client/src/lib/api.ts:100, client/src/hooks/useWebSocket.ts:55 UI work

Foundations that already exist (partial)

  • "Household hooks": the ingest route already accommodates hooks forwarded from other machines by an external forwarder — when a POST carries data.remote_custom_title / data.remote_ai_title, it is applied directly because the remote transcript isn't readable locally. server/routes/hooks.js:317-330. Proves inbound remote push is a designed-for case.
  • Remote-aware liveness reaper: forwarded sessions report a non-POSIX cwd (e.g. D:\Git\...) and are deliberately skipped by the reaper so they aren't falsely killed. server/routes/hooks.js:1424-1442.
  • Consolidation via export/import: server/lib/data-transfer.js — UUID-keyed, idempotent, session-atomic merge of another machine's DB. Offline (manual file transfer), not live. (Related work: Feature: Local-first backup, restore, and sync workflow #149.)
  • Session IDs are globally-unique UUIDs (data.session_id verbatim, server/routes/hooks.js:312), so merging data from multiple machines into the one shared DB cannot cause PK collisions — only the inability to distinguish origin (blocker Feature: Custom Dashboard Builder (Configurable Widgets & Layouts) #4).

Notable: the core SSH use case already works today with zero code (reverse tunnel)

On the SSH machine:

export CLAUDE_DASHBOARD_PORT=4820
ssh -R 4820:localhost:4820 you@remote     # remote:4820 -> local dashboard

Remote hooks POST to 127.0.0.1:4820, tunneled to the local dashboard, ingested and pushed live over the existing WebSocket. The only thing missing to make it first-class is a machine-origin tag (blocker #4) so remote and local sessions can be labeled, filtered, and toggled — which is the heart of this feature.


Approaches (design options — pick during implementation)

A — SSH tunnel + origin label (MVP). Document the reverse tunnel above; add a machine-origin tag + a source facet/filter in the UI. Transport is SSH (secure, no open port). Needs only #4 + UI. Smallest real feature.

B — Direct network push. Remote hooks POST straight to the local dashboard's LAN IP. Needs #1 + #2 + #3 (add ingest auth) + #4. Opens a network port — largest security surface.

C — Dashboard pulls over SSH (closest to the proposed UX). Server connects out via SSH to each configured remote, tails ~/.claude/projects/*.jsonl transcripts (tail -f stream or poll), and ingests. Settings → "add SSH source", toggle which are included. No changes required on the remote at all. Biggest build (SSH client / key management / streaming), cleanest UX. Needs #4.

D — ccam forward shipped remote agent. Generalize the "household hooks" forwarder into a first-class command the remote runs; forwards over HTTPS/WS with a token. Real-time, authenticated, origin-tagged. Medium build; requires a remote install step.

Real-time in all cases

  • A / B / D are push → instant via the existing WebSocket broadcast.
  • C is pull → near-real-time (tail-stream or short poll).
  • The WebSocket is broadcast-only, so no WS changes are needed — the client just filters incoming messages by the new source field.

Recommended shape (if built)

  1. Schema: add a nullable source column to sessions / agents / events, default "local" (additive, migration-safe per server/db.js conventions).
  2. Transport: ship A (documented SSH tunnel + label) as the MVP; consider C (SSH pull) as the full "add a source in Settings" experience.
  3. Security: any non-loopback path must authenticate ingestion — remove the /hooks token exemption for remote requests while keeping loopback exempt (server/lib/security.js:125).
  4. UI: new Settings "Remote Data Sources" section (slots alongside claude-home / import; the TOC auto-registers it — client/src/pages/Settings.tsx:67-82) + a cross-app source facet/filter mirroring the existing cwd facet pattern (client/src/pages/Sessions.tsx, api.sessions.facets). Regenerate screen snapshots (client/src/pages/__tests__/screens.snapshot.test.tsx).

Acceptance criteria (high-level)

  • User can configure one or more remote data sources (SSH first) from Settings.
  • User can choose scope: local only / specific source(s) / all — dashboard updates immediately.
  • Remote session/event/agent data is tagged with its origin machine and is filterable everywhere it's listed.
  • Remote data streams in live (real-time), consistent with local behavior.
  • Any network-exposed ingestion path is authenticated (no unauthenticated remote writes).
  • No regression to the local-only default (loopback, zero-config).
  • Docs updated (README + wiki + server/README.md) per the repo doc-sync policy.

Related


Investigation performed against the feat/data-export-import-roundtrip working tree; file:line refs reflect current code. No code changes were made as part of filing this issue.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdocumentationImprovements or additions to documentationenhancementNew feature or requestgood first issueGood for newcomershelp wantedExtra attention is neededquestionFurther information is requested

Projects

Status
Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions