<div align="center">
  <img src="assets/chron-icon-png-transperent.png" alt="Chron" width="96" />
  <h1>Chron</h1>
  <p>A timestamped audit trail for every AI conversation — stored locally, owned by you.</p>
</div>

AI tools show when you sent a message. Chron logs when the AI responded too — and keeps a permanent, queryable record of every exchange across every tool you use.

Works with Claude Desktop, Claude Code, Cursor, Windsurf, and any MCP-compatible AI tool.

---

## Why

AI tools produce no audit trail by default. You cannot answer:
- What did the AI say, and when exactly?
- How long did the AI take to respond?
- What was the full conversation that produced this output?
- What did I ask Claude last week about this codebase?

Chron fixes that. Every exchange is logged with a precise local datetime (including timezone offset) to a SQLite file you own. No cloud, no vendor lock-in, no data leaving your machine.

---

## Install

**MCP server** (for Claude Desktop, Claude Code, Cursor, Windsurf):

Add to your AI tool's MCP config:

```json
{
  "mcpServers": {
    "chron": {
      "command": "npx",
      "args": ["-y", "chron-mcp"]
    }
  }
}
```

**CLI** (for `chron history`, `chron connect`, `chron export`, etc.):

```bash
npm install -g chron-mcp
```

> `npx chron-mcp` starts the MCP server only — it does not put the `chron` CLI command in your PATH. You need a global install for the CLI.

First run creates `~/.chron/chron.db` automatically. No database setup, no env vars, no migrations.

---

## CLI

After `npm install -g chron-mcp`, the `chron` command is available globally:

```
Usage: chron <command> [options]

Commands:
  history         List sessions or show full log for a session
  report          Aggregate audit stats across sessions
  export          Export a session as markdown
  secrets         List detected secrets across sessions
  settings        View current configuration
  connect         Connect to a SIEM integration (crowdstrike, sentinel, splunk)

Options (history):
  --limit=<n>     Max sessions to show (default: 20)
  <id-prefix>     Show full log for the session with this ID prefix

Options (report):
  --since=<range> Filter by date: 7d, 30d, or YYYY-MM-DD (default: all time)

Options (export / secrets):
  <id-prefix>     Scope to a single session
```

---

## What it logs

Every exchange is recorded with precise local timestamps — user message when received, assistant response when sent:

```
[user: 2026-05-08 14:32:11 +02:00 | assistant: 2026-05-08 14:32:43 +02:00]

The main risks of deploying this contract are...
```

The gap between user and assistant timestamps is real generation time. Both are stored in your local SQLite DB with full timezone offset.

---

## Config by tool

### Claude Desktop

Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):

```json
{
  "mcpServers": {
    "chron": {
      "command": "npx",
      "args": ["-y", "chron-mcp"]
    }
  }
}
```

### Claude Code

```bash
claude mcp add chron -- npx -y chron-mcp
```

Then add the skill hook to `~/.claude/settings.json`:

```json
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "cat ~/.chron/chron.skill.md"
          }
        ]
      }
    ]
  }
}
```

### Cursor

Edit `~/.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "chron": {
      "command": "npx",
      "args": ["-y", "chron-mcp"]
    }
  }
}
```

### Windsurf

Edit `~/.codeium/windsurf/mcp_config.json`:

```json
{
  "mcpServers": {
    "chron": {
      "command": "npx",
      "args": ["-y", "chron-mcp"]
    }
  }
}
```

---

## Companion skill file

Chron ships with `skills/chron.skill.md` — a plain-text instruction file that tells the AI how to use the MCP tools automatically. Load it into your AI tool once. After that, the AI:

1. Creates or resumes a named session at the start of every conversation
2. Logs your message before it starts responding (captures the real user timestamp)
3. Logs its response after composing it (captures the real assistant timestamp)
4. Shows `[user: YYYY-MM-DD HH:MM:SS ±HH:MM | assistant: YYYY-MM-DD HH:MM:SS ±HH:MM]` at the top of every response
5. Retrieves prior session history so context is never lost across conversations

---

## MCP Tools

| Tool | Description |
|---|---|
| `init_session` | Initialize or resume a session — returns session_id, message count, and recent messages in one call |
| `start_session` | Create a new named audit session (legacy — prefer `init_session`) |
| `log_message` | Record a single message with the current local datetime |
| `log_exchange` | Log a user/assistant pair atomically (for batch imports) |
| `list_sessions` | List all sessions ordered by most recently active |
| `get_session_history` | Retrieve the full timestamped log for a session |
| `verify_session` | Verify the tamper-evident hash chain — detects any post-log edits |
| `scan_prompt` | Scan text for secrets (API keys, credentials) before logging — returns masked detections |
| `rehydrate_response` | Restore redacted placeholders in an assistant response back to their original values |

---

## Tamper-evident hash chaining

Every message is linked to the previous one via a SHA-256 chain:

```
content_hash = SHA256(session_id | role | content | created_at | prev_hash)
```

`verify_session` walks the chain and returns:
- `valid: true` — no messages were modified after logging
- `valid: false, first_break: <id>` — exact row ID of the first tampered message

This turns your local log into a verifiable audit artifact. Any edit to a stored message — content, timestamp, or role — breaks the chain and is detected immediately.

---

## CrowdStrike LogScale integration

Stream AI session telemetry directly from developer machines into your CrowdStrike LogScale repository. No relay service — events go straight to your own LogScale instance.

**What gets sent:** session starts, message counts (role only), and masked credential detections. Message content never leaves the machine.

### Setup

**1. Create a LogScale repository and ingest token**

In Falcon console → **Log Management** → **Repositories** → **New repository** (name it `ChronAIEvents`).  
Then **Settings** → **API Tokens** → **Add token** → select **Ingest** permission → copy the token.

Your ingest URL follows this pattern:
```
https://<your-cluster>.humio.com/api/v1/ingest/humio-structured
```

**2. Install the CLI and run the onboarding wizard**

```bash
npm install -g chron-mcp
chron connect crowdstrike
# Paste your ingest URL and token when prompted
# The wizard sends a test event and confirms before saving
```

**3. Add env vars to your MCP client config**

The wizard prints the exact block to paste. For Claude Code, add to the `chron` entry in `~/.claude.json`:

```json
{
  "mcpServers": {
    "chron": {
      "command": "npx",
      "args": ["-y", "chron-mcp"],
      "env": {
        "CHRON_LOGSCALE_URL": "https://<your-cluster>.humio.com/api/v1/ingest/humio-structured",
        "CHRON_LOGSCALE_TOKEN": "<your-ingest-token>"
      }
    }
  }
}
```

Restart your AI tool to pick up the new env vars.

**4. Import the pre-built dashboard**

In Falcon → **Log Management** → **Dashboards** → **Import**:
```
node_modules/chron-mcp/dashboards/logscale/chron-ai-activity.yaml
```

Full guide with SOC alert setup: [dashboards/logscale/README.md](dashboards/logscale/README.md)

### Env vars

| Env var | Description |
|---|---|
| `CHRON_LOGSCALE_URL` | LogScale ingest endpoint |
| `CHRON_LOGSCALE_TOKEN` | LogScale ingest token |

Both must be set for events to flow. If unset, the integration is silently disabled.

---

## Splunk integration

Stream AI session telemetry into Splunk via HTTP Event Collector (HEC). Works with Splunk Enterprise, Splunk Cloud, and a local Docker instance.

**What gets sent:** session starts, message counts (role only), and masked credential detections. Message content never leaves the machine.

### Setup

**Option A — Local Docker (fastest, no account needed)**

```bash
# Start a local Splunk instance (Apple Silicon: --platform linux/amd64 is required)
docker run -d --name splunk-chron \
  --platform linux/amd64 \
  -p 8000:8000 -p 8088:8088 \
  -e SPLUNK_START_ARGS=--accept-license \
  -e SPLUNK_GENERAL_TERMS=--accept-sgt-current-at-splunk-com \
  -e SPLUNK_PASSWORD=Admin1234! \
  -e SPLUNK_HEC_TOKEN=chron-test-token \
  splunk/splunk:latest

# Wait ~2 minutes, then watch until ready:
docker logs -f splunk-chron 2>&1 | grep -i "Ansible playbook complete"
```

**Option B — Splunk Enterprise or Cloud**

Settings → **Data Inputs** → **HTTP Event Collector** → **New Token** → name it `chron-ingest` → source type `chron:event` → copy the token.

**2. Install the CLI and run the onboarding wizard**

```bash
npm install -g chron-mcp
chron connect splunk
# For local Docker: URL = https://localhost:8088, Token = chron-test-token
# TLS verification is skipped automatically for localhost (self-signed cert)
```

**3. Add env vars to your MCP client config**

The wizard prints the exact block to paste. For Claude Code, add to `~/.claude.json`:

```json
{
  "mcpServers": {
    "chron": {
      "command": "npx",
      "args": ["-y", "chron-mcp"],
      "env": {
        "CHRON_SPLUNK_URL": "https://localhost:8088",
        "CHRON_SPLUNK_TOKEN": "chron-test-token",
        "CHRON_SPLUNK_INSECURE": "1"
      }
    }
  }
}
```

> Remove `CHRON_SPLUNK_INSECURE` for production Splunk instances with valid TLS certificates.

Restart your AI tool to pick up the new env vars.

**4. Search in Splunk**

Open [http://localhost:8000](http://localhost:8000) → **Search & Reporting** → run:
```
sourcetype="chron:event"
```

Events appear in real time as you have AI conversations.

Full guide with dashboard templates and alert setup: [dashboards/splunk/README.md](dashboards/splunk/README.md)

### Env vars

| Env var | Description |
|---|---|
| `CHRON_SPLUNK_URL` | Splunk HEC base URL, e.g. `https://localhost:8088` or `https://your-host:8088` |
| `CHRON_SPLUNK_TOKEN` | HEC ingest token |
| `CHRON_SPLUNK_INSECURE` | Set to `1` to skip TLS verification (local Docker with self-signed cert) |

`CHRON_SPLUNK_URL` and `CHRON_SPLUNK_TOKEN` must both be set for events to flow.

---

## Microsoft Sentinel integration

Stream AI session telemetry into your Microsoft Sentinel workspace via the Azure Monitor Logs Ingestion API. Events go directly from developer machines to your own Log Analytics workspace — no relay service.

**What gets sent:** session starts, message counts (role only), and masked credential detections. Message content never leaves the machine.

### Setup

**1. Register an Azure App**

Azure Portal → **Azure Active Directory** → **App registrations** → **New registration** → name it `chron-ingest`.  
Note the **Application (client) ID** and **Directory (tenant) ID**.  
Go to **Certificates & secrets** → **New client secret** → copy the value immediately.

**2. Create a custom table in Log Analytics**

Open your Log Analytics workspace → **Tables** → **Create** → **New custom log (DCR-based)** → name it `ChronEvents_CL`.

Add these columns (in addition to the auto-added `TimeGenerated`):

| Column | Type |
|---|---|
| `EventType` | string |
| `SessionIdPrefix` | string |
| `AiTool` | string |
| `OS` | string |
| `ChronVersion` | string |
| `Computer` | string |
| `Role` | string |
| `DetectionType` | string |
| `MaskedValue` | string |

The wizard creates a **Data Collection Endpoint (DCE)** and **Data Collection Rule (DCR)** — note both.

**3. Grant the App ingest permission**

Open the DCR → **Access control (IAM)** → **Add role assignment** → Role: **Monitoring Metrics Publisher** → Member: the `chron-ingest` app.

**4. Get the DCR Immutable ID**

Open the DCR → **Overview** → **JSON view** → copy the `immutableId` field (starts with `dcr-`).

**5. Install the CLI and run the onboarding wizard**

```bash
npm install -g chron-mcp
chron connect sentinel
# Enter tenant ID, client ID, client secret, DCE URL, DCR immutable ID, and stream name
# The wizard authenticates with Azure AD and sends a test event before saving
```

**6. Add env vars to your MCP client config**

The wizard prints the exact block to paste. For Claude Code, add to `~/.claude.json`:

```json
{
  "mcpServers": {
    "chron": {
      "command": "npx",
      "args": ["-y", "chron-mcp"],
      "env": {
        "CHRON_SENTINEL_TENANT_ID": "<your-tenant-id>",
        "CHRON_SENTINEL_CLIENT_ID": "<your-client-id>",
        "CHRON_SENTINEL_CLIENT_SECRET": "<your-client-secret>",
        "CHRON_SENTINEL_DCE": "https://<your-dce>.ingest.monitor.azure.com",
        "CHRON_SENTINEL_DCR_ID": "dcr-<your-immutable-id>",
        "CHRON_SENTINEL_STREAM": "Custom-ChronEvents_CL"
      }
    }
  }
}
```

Restart your AI tool to pick up the new env vars.

**7. Query in Sentinel**

Sentinel → **Logs** → paste any query from `dashboards/sentinel/`:
```kql
ChronEvents_CL
| where TimeGenerated > ago(24h)
| summarize count() by EventType, AiTool
```

Full guide with KQL queries and alert rule setup: [dashboards/sentinel/README.md](dashboards/sentinel/README.md)

### Env vars

| Env var | Description |
|---|---|
| `CHRON_SENTINEL_TENANT_ID` | Azure AD tenant ID |
| `CHRON_SENTINEL_CLIENT_ID` | App Registration client ID |
| `CHRON_SENTINEL_CLIENT_SECRET` | App Registration client secret |
| `CHRON_SENTINEL_DCE` | Data Collection Endpoint URL |
| `CHRON_SENTINEL_DCR_ID` | DCR Immutable ID (starts with `dcr-`) |
| `CHRON_SENTINEL_STREAM` | Stream name, e.g. `Custom-ChronEvents_CL` |

All six must be set for events to flow. Token refresh is automatic (1-hour Azure AD tokens, refreshed 60s before expiry).

---

## Configuration

| Env var | Default | Description |
|---|---|---|
| `CHRON_DB_PATH` | `~/.chron/chron.db` | Path to SQLite database file |
| `CHRON_TRANSPORT` | `stdio` | Set to `http` to enable HTTP+SSE mode |
| `CHRON_API_KEY` | _(none)_ | Bearer token for HTTP mode |
| `PORT` | `3001` | Port for HTTP mode |
| `CHRON_LOGSCALE_URL` | _(none)_ | LogScale ingest endpoint (enables CrowdStrike integration) |
| `CHRON_LOGSCALE_TOKEN` | _(none)_ | LogScale ingest token |
| `CHRON_SENTINEL_TENANT_ID` | _(none)_ | Azure AD tenant ID (enables Sentinel integration) |
| `CHRON_SENTINEL_CLIENT_ID` | _(none)_ | App Registration client ID |
| `CHRON_SENTINEL_CLIENT_SECRET` | _(none)_ | App Registration client secret |
| `CHRON_SENTINEL_DCE` | _(none)_ | Data Collection Endpoint URL |
| `CHRON_SENTINEL_DCR_ID` | _(none)_ | DCR Immutable ID |
| `CHRON_SENTINEL_STREAM` | _(none)_ | Stream name (e.g. `Custom-ChronEvents_CL`) |
| `CHRON_SPLUNK_URL` | _(none)_ | Splunk HEC base URL (enables Splunk integration) |
| `CHRON_SPLUNK_TOKEN` | _(none)_ | Splunk HEC ingest token |
| `CHRON_RELAY_URL` | _(none)_ | Generic relay endpoint (any SIEM or webhook) |
| `CHRON_RELAY_TOKEN` | _(none)_ | Bearer token for generic relay |

---

## HTTP+SSE mode (team / self-hosted)

For teams or remote use, run Chron as an HTTP server:

```bash
CHRON_TRANSPORT=http CHRON_API_KEY=your-key PORT=3001 npx chron-mcp
```

Point your MCP config at the URL:

```json
{
  "mcpServers": {
    "chron": {
      "url": "https://your-server/sse",
      "headers": {
        "Authorization": "Bearer your-key"
      }
    }
  }
}
```

---

## Your data

Your audit log lives at `~/.chron/chron.db` — a single SQLite file on your machine. Query it directly with any SQLite tool:

```bash
sqlite3 ~/.chron/chron.db \
  "SELECT s.title, m.role, m.content, m.created_at
   FROM messages m JOIN sessions s ON s.id = m.session_id
   ORDER BY m.created_at"
```

No cloud, no telemetry, no data leaving your machine. Change the location with `CHRON_DB_PATH`.

---

## License

Copyright (c) 2026 Nivaya. All rights reserved.

Source code is public for transparency only. Cloning, forking, modification, and redistribution are not permitted without explicit written permission. See [LICENSE](LICENSE) for full terms.
