{"id":44719622,"url":"https://github.com/exospherehost/claudeye","last_synced_at":"2026-03-01T05:22:23.049Z","repository":{"id":337878898,"uuid":"1155609444","full_name":"exospherehost/claudeye","owner":"exospherehost","description":"Watchtower for Claude Code \u0026 Agents SDK - replay sessions, run custom evals, debug agent traces. Uncover, Understand, and Utilize","archived":false,"fork":false,"pushed_at":"2026-02-16T20:50:38.000Z","size":696,"stargazers_count":2,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-16T21:55:11.526Z","etag":null,"topics":["ai-agents","claude","claude-agents-sdk","claude-code","cli","eval","eval-framework","npm","observability","reliability","reliability-engineering"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/claudeye","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/exospherehost.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-11T17:47:06.000Z","updated_at":"2026-02-16T12:17:37.000Z","dependencies_parsed_at":"2026-02-17T17:00:37.797Z","dependency_job_id":null,"html_url":"https://github.com/exospherehost/claudeye","commit_stats":null,"previous_names":["exospherehost/claudeye"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/exospherehost/claudeye","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exospherehost%2Fclaudeye","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exospherehost%2Fclaudeye/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exospherehost%2Fclaudeye/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exospherehost%2Fclaudeye/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/exospherehost","download_url":"https://codeload.github.com/exospherehost/claudeye/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exospherehost%2Fclaudeye/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29550797,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-17T14:33:00.708Z","status":"ssl_error","status_checked_at":"2026-02-17T14:32:58.657Z","response_time":100,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","claude","claude-agents-sdk","claude-code","cli","eval","eval-framework","npm","observability","reliability","reliability-engineering"],"created_at":"2026-02-15T15:47:31.685Z","updated_at":"2026-03-01T05:22:23.020Z","avatar_url":"https://github.com/exospherehost.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"```\n  ____ _                 _\n / ___| | __ _ _   _  __| | ___ _   _  ___\n| |   | |/ _` | | | |/ _` |/ _ \\ | | |/ _ \\\n| |___| | (_| | |_| | (_| |  __/ |_| |  __/\n \\____|_|\\__,_|\\__,_|\\__,_|\\___|\\__, |\\___|\n                                |___/\n```\n\n# Claudeye: Watchtower for Claude Code and Claude Agents SDK\n\n**Uncover** what your agents did.\n**Understand** where they struggle.\n**Utilize** insights to improve.\n\n[![npm version](https://img.shields.io/npm/v/claudeye)](https://www.npmjs.com/package/claudeye)\n[![npm downloads](https://img.shields.io/npm/dm/claudeye)](https://www.npmjs.com/package/claudeye)\n[![node](https://img.shields.io/node/v/claudeye)](https://nodejs.org)\n[![CI](https://img.shields.io/github/actions/workflow/status/exospherehost/claudeye/ci.yml?branch=main\u0026label=CI)](https://github.com/exospherehost/claudeye/actions/workflows/ci.yml)\n[![TypeScript](https://img.shields.io/badge/Built%20with-TypeScript-blue)](https://www.typescriptlang.org/)\n[![Discord](https://badgen.net/discord/members/zT92CAgvkj)](https://discord.com/invite/zT92CAgvkj)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](./CONTRIBUTING.md)\n\n## What is Claudeye?\n\nOne command. Full visibility into every Claude agent session.\n\nClaudeye lets you replay agent executions, grade them with custom evals, and surface exactly where reliability breaks down, across both Claude Code and the Agents SDK. Deploy it locally or on your infrastructure. No setup, no config, just `claudeye` and you're in.\n\n## Quick Start\n\n```bash\nbun install -g claudeye \u0026\u0026 claudeye\n# or: npm install -g claudeye \u0026\u0026 claudeye\n```\n\nOpens your browser at `localhost:8020`. Reads from `~/.claude/projects` by default.\n\nWorks with [Claude Code](https://docs.anthropic.com/en/docs/claude-code) sessions and [Claude Agents SDK](https://github.com/anthropics/anthropic-sdk-python) logs.\n\n## Why Claudeye?\n\n| Feature | Claudeye | Langfuse | Dev-Agent-Lens | ccusage | Raw JSONL |\n|---------|:--------:|:--------:|:--------------:|:-------:|:---------:|\n| Local-first (no cloud) | **Yes** | Self-host option | Proxy required | Yes | Yes |\n| Session replay | **Yes** | Traces only | Traces only | No | Manual |\n| Custom evals | **Yes** | Limited | No | No | No |\n| Subagent expansion | **Yes** | No | No | No | No |\n| Zero config | **Yes** | Setup required | Proxy setup | Yes | N/A |\n| Visual dashboard | **Yes** | Yes | Yes (Phoenix) | CLI only | No |\n\n## Features\n\n### Uncover\n\n- **Projects \u0026 sessions browser** - filter by date range or keyword, paginated and sorted newest-first\n- **Full execution trace viewer** - every message, tool call, thinking block, and system event\n- **Nested subagent logs** - expand to see subagent executions inline, pre-loaded with the session\n- **Virtual scrolling** - handles sessions with thousands of entries without performance issues\n\n### Understand\n\n- **Session stats bar** - turns, tool calls, subagents, duration, and models at a glance\n- **Custom evals** - grade sessions with pass/fail results and 0-1 scores\n- **Per-eval recompute** - re-run a single eval without reprocessing all others\n- **Conditional evals** - gate evals globally or per-item, with session/subagent scope control\n\n### Utilize\n\n- **Custom enrichments** - compute metadata (token counts, quality signals, labels) as key-value pairs\n- **Custom actions** - on-demand tasks triggered from the dashboard via `app.action()` — generate summaries, export metrics, or run side-effects with full access to eval and enrichment results\n- **Alerts** - register callbacks via `app.alert()` that fire after all evals and enrichments complete (Slack webhooks, CI notifications, logging)\n- **Dashboard views \u0026 filters** - organize filters into named views, each with focused filter tiles (boolean toggles, range sliders, multi-select dropdowns) and a filterable sessions table\n- **Dashboard aggregates** - define cross-session summary tables with `app.dashboard.aggregate()`, using `{ collect, reduce }` for full control over output\n- **Unified queue** - all evals and enrichments (session, subagent, UI, background) go through a single priority queue with bounded concurrency, live tracking at `/queue`\n- **JSONL export** - download raw session logs\n- **Auto-refresh** - monitor live sessions at 5s, 10s, or 30s intervals\n- **Light/dark theme** - with system preference detection\n\n## CLI Reference\n\n| Flag | Description | Default |\n|------|-------------|---------|\n| `--projects-path, -p \u003cpath\u003e` | Path to Claude projects directory | `~/.claude/projects` |\n| `--port \u003cnumber\u003e` | Port to bind | `8020` |\n| `--host \u003caddress\u003e` | Host to bind (`0.0.0.0` for LAN) | `localhost` |\n| `--evals \u003cpath\u003e` | Path to evals/enrichments file | - |\n| `--auth-user \u003cuser:pass\u003e` | Add an auth user (repeatable) | - |\n| `--cache-path \u003cpath\u003e` | Custom cache directory | `~/.claudeye/cache` |\n| `--cache-clear` | Clear all cached results and exit | - |\n| `--no-open` | Don't auto-open the browser | - |\n| `--queue-interval \u003csecs\u003e` | Background scan interval in seconds | disabled |\n| `--queue-concurrency \u003cnum\u003e` | Max parallel items per batch | `2` |\n| `--queue-history-ttl \u003csecs\u003e` | Seconds to keep completed items | `3600` |\n| `--queue-max-sessions \u003cnum\u003e` | Max sessions to process per scan (0=unlimited) | `8` |\n| `--logging \u003clevel\u003e` | Log level: `info`, `warn`, `error` | `warn` |\n| `-h, --help` | Show help | - |\n\n### Examples\n\n```bash\n# Custom projects path\nclaudeye --projects-path /path/to/projects\n\n# Different port, no browser\nclaudeye --port 3000 --no-open\n\n# LAN access\nclaudeye --host 0.0.0.0\n\n# Load custom evals and enrichments\nclaudeye --evals ./my-evals.js\n\n# Password-protect the dashboard\nclaudeye --auth-user admin:secret\n\n# Multiple auth users\nclaudeye --auth-user admin:secret --auth-user viewer:readonly\n\n# Clear cached results\nclaudeye --cache-clear\n\n# Enable background queue processing (scan every 60 seconds)\nclaudeye --evals ./my-evals.js --queue-interval 60\n\n# Background processing with higher concurrency\nclaudeye --evals ./my-evals.js --queue-interval 30 --queue-concurrency 5\n```\n\n## Custom Evals \u0026 Enrichments\n\nDefine evals and enrichments in a single JS file and load with `--evals`:\n\n```js\nimport { createApp } from 'claudeye';\n\nconst app = createApp();\n\n// Evals: grade your sessions\napp.eval('under-50-turns', ({ stats }) =\u003e ({\n  pass: stats.turnCount \u003c= 50,\n  score: Math.max(0, 1 - stats.turnCount / 100),\n  message: `${stats.turnCount} turn(s)`,\n}));\n\napp.eval('tool-success', ({ entries }) =\u003e {\n  const results = entries.filter(e =\u003e e.type === 'tool_result');\n  const errors = results.filter(e =\u003e e.is_error);\n  const rate = results.length ? 1 - errors.length / results.length : 1;\n  return { pass: rate \u003e= 0.9, score: rate };\n});\n\n// Enrichments: add metadata to sessions\napp.enrich('session-summary', ({ entries, stats }) =\u003e ({\n  'Total Tokens': entries.reduce((s, e) =\u003e s + (e.usage?.total_tokens || 0), 0),\n  'Primary Model': stats.models[0] || 'unknown',\n  'Tool Calls': stats.toolCallCount,\n}));\n```\n\n```bash\nclaudeye --evals ./my-evals.js\n```\n\n### Evals\n\nEvals grade sessions with a pass/fail result and an optional 0-1 score. Each eval receives an `EvalContext` with the session's raw JSONL `entries` and computed `stats`. Return `{ pass, score?, message? }`.\n\n```js\napp.eval('has-final-response', ({ entries }) =\u003e {\n  const last = [...entries].reverse().find(e =\u003e e.type === 'assistant');\n  const hasText = last?.message?.content?.some?.(b =\u003e b.type === 'text');\n  return {\n    pass: !!hasText,\n    score: hasText ? 1.0 : 0,\n    message: hasText ? 'Session ended with a text response' : 'No final text response',\n  };\n});\n```\n\n[Read more: Evals API, EvalResult type, and advanced examples \u0026rarr;](docs/api-reference.md#appeval-name-fn-options)\n\n### Enrichments\n\nEnrichments compute key-value metadata displayed in the dashboard. Same `EvalContext` input, return a flat `Record\u003cstring, string | number | boolean\u003e`.\n\n```js\napp.enrich('session-overview', ({ entries, stats }) =\u003e ({\n  'Turns': stats.turnCount,\n  'Tool Calls': stats.toolCallCount,\n  'Models': stats.models.join(', ') || 'none',\n  'Total Tokens': entries.reduce((s, e) =\u003e s + (e.usage?.total_tokens || 0), 0),\n}));\n```\n\n[Read more: Enrichments API and EnrichmentResult type \u0026rarr;](docs/api-reference.md#appenrich-name-fn-options)\n\n### Actions\n\nActions are a flexible on-demand primitive — generate summaries, export metrics, run side-effects, or anything that doesn't fit the eval/enrichment model. Actions receive the full session context plus cached eval and enrichment results, and are triggered manually from the dashboard:\n\n```js\napp.action('session-summary', ({ stats, evalResults }) =\u003e {\n  const passCount = Object.values(evalResults).filter(r =\u003e r.pass).length;\n  return {\n    output: `${stats.turnCount} turns, ${passCount}/${Object.keys(evalResults).length} evals passed`,\n    data: { turns: stats.turnCount, evalsPassed: passCount },\n    status: 'success',\n  };\n});\n\n// Side-effect action (disable caching so it always re-runs)\napp.action('export-report', async ({ projectName, sessionId, stats }) =\u003e {\n  const fs = await import('fs/promises');\n  await fs.appendFile('reports.jsonl', JSON.stringify({ projectName, sessionId, turns: stats.turnCount }) + '\\n');\n  return { status: 'success', message: 'Report exported' };\n}, { cache: false });\n```\n\nActions support the same `condition`, `scope`, `subagentType`, and `cache` options as evals and enrichments.\n\n[Read more: Actions API, ActionContext, and ActionResult types \u0026rarr;](docs/api-reference.md#appaction-name-fn-options)\n\n### Dashboard Views \u0026 Filters\n\nOrganize dashboard filters into **named views** — each with a focused set of filters. Views appear as cards on `/dashboard` and link to `/dashboard/[viewName]`:\n\n```js\n// Named views with chainable .filter()\napp.dashboard.view('performance', { label: 'Performance Metrics' })\n  .filter('turn-count', ({ stats }) =\u003e stats.turnCount, { label: 'Turn Count' })\n  .filter('tool-calls', ({ stats }) =\u003e stats.toolCallCount, { label: 'Tool Calls' });\n\napp.dashboard.view('quality', { label: 'Quality Checks' })\n  .filter('has-errors', ({ entries }) =\u003e\n    entries.some(e =\u003e e.type === 'assistant' \u0026\u0026\n      Array.isArray(e.message?.content) \u0026\u0026\n      e.message.content.some(b =\u003e b.type === 'tool_use' \u0026\u0026 b.is_error)),\n    { label: 'Has Errors' })\n  .filter('primary-model', ({ stats }) =\u003e stats.models[0] || 'unknown',\n    { label: 'Primary Model' });\n\n// Backward-compat: app.dashboard.filter() still works (goes to \"default\" view)\napp.dashboard.filter('uses-subagents', ({ stats }) =\u003e stats.subagentCount \u003e 0,\n  { label: 'Uses Subagents' }\n);\n```\n\nFilter return types (`boolean`, `number`, `string`) auto-determine the UI control: toggle tiles, range sliders, or multi-select dropdowns. Values are computed server-side with an incremental index that only reprocesses new or changed sessions. Filtering and pagination happen server-side, returning only the matching page of results.\n\n[Read more: Dashboard Views API \u0026rarr;](docs/api-reference.md#appdashboardview-name-options)\n[Read more: Dashboard Filters API \u0026rarr;](docs/api-reference.md#appdashboardfilter-name-fn-options)\n\n### Dashboard Aggregates\n\nAggregates compute cross-session summaries. Provide a `{ collect, reduce }` object: `collect` runs per session returning key-value pairs, and `reduce` transforms all collected values into your output table:\n\n```js\napp.dashboard.view('quality')\n  .aggregate('eval-summary', {\n    collect: ({ evalResults }) =\u003e {\n      const result = {};\n      for (const [name, r] of Object.entries(evalResults)) {\n        result[`${name}_pass`] = r.pass;\n      }\n      return result;\n    },\n    reduce: (collected) =\u003e {\n      const evalNames = new Set();\n      for (const s of collected) {\n        for (const key of Object.keys(s.values)) {\n          if (key.endsWith('_pass')) evalNames.add(key.replace('_pass', ''));\n        }\n      }\n      return Array.from(evalNames).map(name =\u003e ({\n        'Eval': name,\n        'Pass Rate': collected.filter(s =\u003e s.values[`${name}_pass`]).length / collected.length,\n      }));\n    },\n  });\n```\n\nThe collect function receives an `AggregateContext` with log entries, stats, eval results, enrichment results, and filter values. Computation is incremental — only new/changed sessions are reprocessed.\n\n[Read more: Dashboard Aggregates API \u0026rarr;](docs/api-reference.md#appdashboardaggregate-name-definition-options)\n\n### Alerts\n\nAlerts fire after all evals and enrichments complete for a session. Use them for Slack webhooks, CI notifications, logging, or any post-processing:\n\n```js\napp.alert('slack-on-failure', async ({ projectName, sessionId, evalSummary }) =\u003e {\n  if (evalSummary \u0026\u0026 evalSummary.failCount \u003e 0) {\n    await fetch('https://hooks.slack.com/services/...', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({\n        text: `${evalSummary.failCount} evals failed for ${projectName}/${sessionId}`,\n      }),\n    });\n  }\n});\n\napp.alert('log-results', ({ projectName, sessionId, evalSummary, enrichSummary }) =\u003e {\n  console.log(`[${projectName}/${sessionId}] evals: ${evalSummary?.passCount ?? 0} pass, ${evalSummary?.failCount ?? 0} fail`);\n});\n```\n\nAlerts fire when all evals and enrichments for a session are complete — the unified queue checks after each item. This covers initial page loads, background processing, and all re-run actions. Each alert is individually error-isolated: a throwing callback never blocks other alerts or eval processing.\n\n[Read more: Alerts API and AlertContext type \u0026rarr;](docs/api-reference.md#appalert-name-fn)\n\n### Background Queue Processing\n\nEnable background processing to automatically scan and evaluate all sessions on a timer:\n\n```bash\nclaudeye --evals ./my-evals.js --queue-interval 60\n```\n\nUse `app.queueCondition()` to gate which sessions the background queue processes:\n\n```js\n// Only process sessions with more than 5 entries\napp.queueCondition(({ entries }) =\u003e entries.length \u003e 5);\n```\n\nThe condition receives the full `EvalContext` and returns a boolean. If false, the session is skipped entirely. Results are cached per-session and auto-invalidate when the session file or condition function changes.\n\nThe background processor scans all projects for uncached evals/enrichments and enqueues them individually at LOW priority. UI requests are enqueued at HIGH priority, jumping ahead of background work. Track all queue activity in real-time at `/queue` (three tabs: In Queue, Processing, Processed) or via the navbar dropdown. All queue settings are also available as environment variables (`CLAUDEYE_QUEUE_INTERVAL`, `CLAUDEYE_QUEUE_CONCURRENCY`, `CLAUDEYE_QUEUE_HISTORY_TTL`, `CLAUDEYE_QUEUE_MAX_SESSIONS`).\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `CLAUDEYE_QUEUE_INTERVAL` | Background scan interval in seconds | disabled |\n| `CLAUDEYE_QUEUE_CONCURRENCY` | Max parallel items per batch | `2` |\n| `CLAUDEYE_QUEUE_HISTORY_TTL` | Seconds to keep completed items | `3600` |\n| `CLAUDEYE_QUEUE_MAX_SESSIONS` | Max sessions to process per scan (0=unlimited) | `8` |\n\n### Conditions\n\nConditions gate when evals and enrichments run. Set a **global condition** with `app.condition()` to skip everything for certain sessions, or add a **per-item condition** in the options:\n\n```js\n// Global: skip empty sessions\napp.condition(({ entries }) =\u003e entries.length \u003e 0);\n\n// Per-eval: only check tool efficiency when tools were used\napp.eval('efficient-tools',\n  ({ stats }) =\u003e ({\n    pass: stats.toolCallCount \u003c= stats.turnCount * 2,\n    score: Math.max(0, 1 - (stats.toolCallCount / (stats.turnCount * 4))),\n  }),\n  { condition: ({ stats }) =\u003e stats.toolCallCount \u003e 0 }\n);\n```\n\n[Read more: Conditions, evaluation order, and UI behavior \u0026rarr;](docs/api-reference.md#appcondition-fn)\n\n### Subagent Scope\n\nEvals and enrichments run at the session level by default. Use the `scope` option to target subagent logs:\n\n| Scope | Session level | Subagent level |\n|-------|:---:|:---:|\n| `'session'` (default) | Yes | No |\n| `'subagent'` | No | Yes |\n| `'both'` | Yes | Yes |\n\n```js\n// Only runs for Explore subagents\napp.eval('explore-depth', ({ entries }) =\u003e ({\n  pass: entries.length \u003e 5,\n  score: Math.min(entries.length / 20, 1),\n}), { scope: 'subagent', subagentType: 'Explore' });\n```\n\nWhen running at subagent level, the context includes `source` (matching `entry._source`), `subagentType`, `subagentDescription`, and `parentSessionId`. Note that `entries` and `stats` include combined session + subagent data. Use `source` to filter when you need scope-specific data:\n\n```js\n// entries includes all data (session + subagents).\n// Use source to filter — it matches entry._source directly:\napp.eval('agent-check', ({ entries, source }) =\u003e {\n  const myEntries = entries.filter(e =\u003e e._source === source);\n  return { pass: myEntries.length \u003e 0 };\n}, { scope: 'subagent' });\n```\n\n[Read more: Subagent scope, filtering, caching, and edge cases \u0026rarr;](docs/api-reference.md#subagent-scope)\n\n### `app.listen()`\n\nYou can also run your evals file directly with `bun my-evals.js` (or `node my-evals.js`) if you include `app.listen()`. This spawns the dashboard as a child process. When loaded via `--evals`, `listen()` automatically becomes a no-op.\n\n[Full API reference: all types, interfaces, and detailed examples \u0026rarr;](docs/api-reference.md)\n\n## Caching\n\nCaching is **always on**. Results are cached to `~/.claudeye/cache/` and automatically invalidated when session logs or eval definitions change. Click **Re-run** in the dashboard to bypass the cache.\n\n```bash\nclaudeye --cache-path /tmp/cc  # Custom cache location\nclaudeye --cache-clear         # Clear cache and exit\n```\n\n## Authentication\n\nClaudeye ships with **opt-in** username/password auth. When no users are configured, everything works exactly as before — no login page, no blocking.\n\n### Enable via CLI\n\n```bash\n# Single user\nclaudeye --auth-user admin:secret\n\n# Multiple users\nclaudeye --auth-user admin:secret --auth-user viewer:readonly\n```\n\n### Enable via environment variable\n\n```bash\nCLAUDEYE_AUTH_USERS=admin:secret claudeye\nCLAUDEYE_AUTH_USERS=admin:secret,viewer:readonly claudeye\n```\n\n### Enable via the programmatic API\n\n```js\nimport { createApp } from 'claudeye';\n\nconst app = createApp();\n\napp.auth({ users: [\n  { username: 'admin', password: 'secret' },\n  { username: 'viewer', password: 'readonly' },\n] });\n\napp.listen();\n```\n\nAll three methods can be combined — users from CLI flags, the env var, and `app.auth()` are merged together.\n\nWhen auth is active, all UI routes redirect to `/login`. After signing in, a signed session cookie (24h expiry) grants access. A **Sign out** button appears in the navbar.\n\n## Deployment with PM2\n\nFor production deployments, use PM2 with Bun as the interpreter:\n\n```js\n// ecosystem.config.cjs\nmodule.exports = {\n  apps: [{\n    name: 'claudeye',\n    script: 'node_modules/.bin/next',\n    args: 'start',\n    interpreter: 'bun',\n    cwd: '/path/to/claudeye',\n    env: {\n      PORT: 8020,\n      HOSTNAME: '0.0.0.0',\n      CLAUDE_PROJECTS_PATH: '/home/user/.claude/projects',\n      CLAUDEYE_EVALS_MODULE: './my-evals.js',\n      CLAUDEYE_QUEUE_INTERVAL: '60',\n    },\n  }],\n};\n```\n\n```bash\n# Start\npm2 start ecosystem.config.cjs\n\n# Monitor\npm2 monit\n\n# Auto-restart on reboot\npm2 startup\npm2 save\n```\n\n## How It Works\n\n1. `createApp()` + `app.eval()` / `app.enrich()` / `app.action()` / `app.alert()` / `app.condition()` / `app.queueCondition()` / `app.dashboard.view()` / `app.dashboard.filter()` / `app.dashboard.aggregate()` register functions in global registries\n2. When you run `claudeye --evals ./my-file.js`, the server dynamically imports your file, populating the registries\n3. All eval/enrichment execution routes through a unified priority queue. Each individual eval and enrichment is a separate queue item. UI requests use HIGH priority; background scanning uses LOW priority\n4. Each item runs through: cache check → execute if uncached → cache result → check if session complete → fire alerts if complete\n5. The global condition is checked first. If it fails, everything is skipped\n6. Per-item conditions are checked individually. Skipped items don't block others\n7. Each function is individually error-isolated. If one throws, the others still run\n8. After all evals and enrichments complete, registered alerts fire with the complete `AlertContext` (eval summary + enrichment summary)\n9. Results are serialized and displayed in separate panels in the dashboard UI\n10. Named dashboard views (`/dashboard`) show a view index; each view (`/dashboard/[viewName]`) computes filter values incrementally (only new/changed sessions are processed), then filters and paginates server-side for efficiency\n11. Dashboard aggregates run a separate server action that collects per-session values (with eval/enrichment/filter results) and reduces them via user-defined reduce functions into sortable summary tables\n12. When `CLAUDEYE_QUEUE_INTERVAL` is set, a background processor scans for uncached items on a timer. Track queue state at `/queue` or via the navbar dropdown\n\n## Contributing\n\nContributions are welcome! To get started:\n\n```bash\ngit clone https://github.com/exospherehost/claudeye.git\ncd claudeye\nbun install\nbun run dev\n```\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md) for the full guide - available scripts, project structure, and PR guidelines.\n\nIf you find a bug or have a feature idea, [open an issue](https://github.com/exospherehost/claudeye/issues). Pull requests are appreciated. Please keep changes focused and include a clear description.\n\nBuilt by [exosphere.host](https://exosphere.host).\n\n## Community\n\n- [Discord](https://discord.com/invite/zT92CAgvkj) - get help and connect with other developers\n- [Issues](https://github.com/exospherehost/claudeye/issues) - bug reports and feature requests\n\n## License\n\nMIT + Commons Clause. See [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexospherehost%2Fclaudeye","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fexospherehost%2Fclaudeye","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexospherehost%2Fclaudeye/lists"}