{
  "project": "RukkiePulse",
  "description": "CLI-first observability platform for backend services. Health checks, endpoint probing, distributed tracing — one command away.",
  "version": "0.1.1",
  "urls": {
    "docs": "https://rukkiepulse.netlify.app",
    "dashboard": "https://rukkiepulse-dashboard.netlify.app",
    "github": "https://github.com/rukkiecodes/rukkiepulse",
    "releases": "https://github.com/rukkiecodes/rukkiepulse/releases/latest",
    "npm": "https://www.npmjs.com/package/rukkie-agent",
    "pypi": "https://pypi.org/project/rukkie-agent"
  },
  "installation": {
    "windows": {
      "steps": [
        "Download rukkie-setup.exe from https://github.com/rukkiecodes/rukkiepulse/releases/latest",
        "Run the installer — it installs the binary and adds it to PATH automatically",
        "Open a new terminal and run: rukkie login"
      ]
    }
  },
  "authentication": {
    "type": "JWT (local)",
    "note": "Credentials are hardcoded for single-user use. Session stored at ~/.rukkie/config.yaml. Sessions last 30 days.",
    "command": "rukkie login",
    "prompts_for": "password"
  },
  "rukkie_yaml": {
    "description": "Configuration file placed in the root of your project. Required by scan, status, inspect, trace, and watch commands.",
    "minimal_example": {
      "project": "my-backend",
      "observability": {
        "jaeger": {
          "url": "http://localhost:16686"
        }
      },
      "environments": {
        "dev": {
          "services": [
            {
              "name": "auth-service",
              "url": "http://localhost:3000",
              "type": "REST",
              "endpoints": [
                {
                  "path": "/health",
                  "method": "GET",
                  "expect_status": 200
                }
              ]
            }
          ]
        }
      }
    },
    "full_example": {
      "project": "my-backend",
      "observability": {
        "jaeger": {
          "url": "http://localhost:16686"
        }
      },
      "environments": {
        "dev": {
          "services": [
            {
              "name": "auth-service",
              "url": "http://localhost:3000",
              "type": "REST",
              "endpoints": [
                {
                  "path": "/login",
                  "method": "POST",
                  "body": "{\"email\":\"test@test.com\",\"password\":\"test\"}",
                  "expect_status": 200
                }
              ]
            },
            {
              "name": "graphql-api",
              "url": "http://localhost:4000/graphql",
              "type": "GRAPHQL",
              "endpoints": [
                {
                  "query": "{ __typename }",
                  "expect_no_errors": true
                }
              ]
            }
          ]
        },
        "production": {
          "services": [
            {
              "name": "auth-service",
              "url": "https://auth.myapp.com",
              "type": "REST"
            }
          ]
        }
      }
    },
    "rukkie_init_yaml": {
      "description": "Generated by 'rukkie init' inside a service project",
      "example": {
        "service": {
          "name": "my-backend-project",
          "language": "node",
          "apiKey": "YOUR_API_KEY"
        },
        "observability": {
          "jaeger": {
            "url": "http://localhost:16686"
          },
          "collector": "http://localhost:4317"
        }
      }
    },
    "fields": [
      { "field": "project", "description": "Display name shown in CLI output" },
      { "field": "observability.jaeger.url", "description": "Jaeger Query UI URL for rukkie trace" },
      { "field": "environments.<name>", "description": "Named environment (dev, staging, production…)" },
      { "field": "services[].name", "description": "Display name" },
      { "field": "services[].url", "description": "Base URL of the service" },
      { "field": "services[].type", "description": "REST or GRAPHQL" },
      { "field": "endpoints[].path", "description": "URL path to probe (REST)" },
      { "field": "endpoints[].method", "description": "HTTP method — default: GET" },
      { "field": "endpoints[].body", "description": "JSON request body" },
      { "field": "endpoints[].expect_status", "description": "Expected HTTP status — default: 200" },
      { "field": "endpoints[].query", "description": "GraphQL query string" },
      { "field": "endpoints[].expect_no_errors", "description": "Fail if response contains errors key" }
    ]
  },
  "commands": [
    {
      "name": "rukkie login",
      "description": "Authenticate — required before all other commands",
      "usage": "rukkie login",
      "notes": "Prompts for password, generates a local JWT, stores session at ~/.rukkie/config.yaml. Sessions last 30 days.",
      "flags": []
    },
    {
      "name": "rukkie logout",
      "description": "Clear the stored session",
      "usage": "rukkie logout",
      "flags": []
    },
    {
      "name": "rukkie scan",
      "description": "Scan all services: health check + endpoint probes. All services scanned concurrently.",
      "usage": [
        "rukkie scan",
        "rukkie scan --env production",
        "rukkie scan --errors-only"
      ],
      "flags": [
        { "flag": "--env, -e", "default": "dev", "description": "Environment to use" },
        { "flag": "--errors-only", "default": "false", "description": "Show only failing or degraded services" }
      ]
    },
    {
      "name": "rukkie status",
      "description": "Alias for rukkie scan — same flags",
      "usage": [
        "rukkie status",
        "rukkie status --errors-only"
      ],
      "flags": [
        { "flag": "--env, -e", "default": "dev", "description": "Environment to use" },
        { "flag": "--errors-only", "default": "false", "description": "Show only failing or degraded services" }
      ]
    },
    {
      "name": "rukkie inspect",
      "description": "Deep-dive into one service: health, dependencies, per-endpoint latency and status codes",
      "usage": [
        "rukkie inspect auth-service",
        "rukkie inspect auth-service --env production"
      ],
      "flags": [
        { "flag": "--env, -e", "default": "dev", "description": "Environment to use" }
      ]
    },
    {
      "name": "rukkie trace",
      "description": "Fetch and display distributed traces from Jaeger. Requires Jaeger running and observability.jaeger.url set.",
      "usage": [
        "rukkie trace auth-service",
        "rukkie trace auth-service /login",
        "rukkie trace auth-service --trace-id abc123def456",
        "rukkie trace auth-service --last 5",
        "rukkie trace auth-service /login --flame"
      ],
      "flags": [
        { "flag": "--trace-id", "default": "—", "description": "Fetch a specific trace by ID" },
        { "flag": "--last", "default": "1", "description": "Number of recent traces to show" },
        { "flag": "--flame", "default": "false", "description": "Render as horizontal flame graph" }
      ]
    },
    {
      "name": "rukkie watch",
      "description": "Live-updating dashboard that refreshes on an interval. Press Ctrl+C to stop.",
      "usage": [
        "rukkie watch",
        "rukkie watch --interval 5s",
        "rukkie watch --env production --interval 30s"
      ],
      "flags": [
        { "flag": "--interval", "default": "10s", "description": "Refresh interval. Accepts 5s, 1m, etc." },
        { "flag": "--env, -e", "default": "dev", "description": "Environment to use" }
      ]
    },
    {
      "name": "rukkie init",
      "description": "Create rukkie.yaml in the current project directory and print the integration snippet. Auto-detects language from package.json (Node.js), pyproject.toml/requirements.txt (Python), go.mod (Go).",
      "usage": "rukkie init",
      "flags": [],
      "output_example": {
        "node_esm": "import { initRukkie } from 'rukkie-agent'\n\ninitRukkie({\n  serviceName: 'my-project',\n  apiKey: 'YOUR_API_KEY',\n})",
        "node_cjs": "const { initRukkie } = require('rukkie-agent')\n\ninitRukkie({\n  serviceName: 'my-project',\n  apiKey: 'YOUR_API_KEY',\n})",
        "python": "from rukkie_agent import init_rukkie\n\ninit_rukkie(\n    service_name=\"my-project\",\n    api_key=\"YOUR_API_KEY\",\n)"
      }
    },
    {
      "name": "rukkie shell",
      "description": "Opens the interactive styled terminal (Bubbletea TUI). This is what the Windows installer launches.",
      "usage": "rukkie shell",
      "shell_navigation": {
        "built_ins": ["cd <dir>", "pwd", "clear", "cls", "exit", "quit", "login"],
        "system_passthrough": ["ls", "dir", "mkdir", "cat", "git", "npm", "node", "python", "python3", "go", "code", "and any other shell command"],
        "notes": "cd and pwd are built-ins that track working directory. All other commands are passed to cmd.exe (Windows) or sh (Unix) with the correct working directory set."
      }
    }
  ],
  "agents": {
    "description": "Drop-in SDKs that auto-instrument your service, expose /__rukkie/health, push traces to Jaeger, and send a heartbeat to the dashboard on startup.",
    "heartbeat": {
      "description": "On initRukkie() / init_rukkie(), the agent fires a background POST to the Supabase Edge Function at https://xqmjdjjwprnqogokoejz.supabase.co/functions/v1/heartbeat with the API key as a Bearer token. This updates last_used_at in the database so the dashboard shows connection status.",
      "connection_status": {
        "live": "Last seen < 5 minutes ago",
        "recent": "Last seen < 1 hour ago",
        "inactive": "Last seen > 1 hour ago",
        "not_connected": "No heartbeat received yet"
      }
    },
    "node": {
      "package": "rukkie-agent",
      "install": "npm install rukkie-agent",
      "npm_url": "https://www.npmjs.com/package/rukkie-agent",
      "frameworks": ["Express", "Fastify"],
      "usage_esm": "import { initRukkie } from 'rukkie-agent'\n\ninitRukkie({\n  serviceName: 'auth-service',\n  apiKey: 'rk_live_xxx',\n})",
      "usage_cjs": "const { initRukkie } = require('rukkie-agent')\n\ninitRukkie({\n  serviceName: 'auth-service',\n  apiKey: 'rk_live_xxx',\n})",
      "usage_with_app": "import express from 'express'\nimport { initRukkie } from 'rukkie-agent'\n\nconst app = express()\n\ninitRukkie({\n  serviceName: 'auth-service',\n  apiKey: 'rk_live_xxx',\n  dependencies: {\n    db: async () => checkDbConnection(),\n    redis: async () => checkRedisConnection(),\n  }\n}, app)",
      "notes": "Pass app as the second argument to enable request tracing middleware and auto-register GET /__rukkie/health. Without app, only OTel and heartbeat are set up."
    },
    "python": {
      "package": "rukkie-agent",
      "install": "pip install rukkie-agent",
      "pypi_url": "https://pypi.org/project/rukkie-agent",
      "frameworks": ["FastAPI", "Flask"],
      "usage": "from rukkie_agent import init_rukkie\n\ninit_rukkie(\n    service_name='auth-service',\n    api_key='rk_live_xxx',\n)",
      "usage_with_app": "from fastapi import FastAPI\nfrom rukkie_agent import init_rukkie\n\napp = FastAPI()\n\ninit_rukkie(\n    service_name='auth-service',\n    api_key='rk_live_xxx',\n    app=app,\n    dependencies={\n        'db': check_db_connection,\n        'redis': check_redis_connection,\n    }\n)"
    }
  },
  "api_keys": {
    "description": "API keys link backend services to the RukkiePulse dashboard. They authenticate the heartbeat and identify which service is connected.",
    "format": "rk_live_<64-hex-chars>",
    "storage": "Only the SHA-256 hash is stored in the database. The full key is shown once on creation and never again.",
    "management": {
      "dashboard_url": "https://rukkiepulse-dashboard.netlify.app",
      "steps": [
        "Sign in to the dashboard",
        "Click + New Service, enter name and language",
        "On the service page click + Generate API Key",
        "Copy the key immediately — it will not be shown again",
        "Add the key to your service via initRukkie({ apiKey: 'rk_live_...' })",
        "To rotate: Revoke the old key, then Generate API Key for a new one"
      ]
    }
  },
  "dashboard": {
    "url": "https://rukkiepulse-dashboard.netlify.app",
    "description": "Web UI to manage services and API keys. Built with Next.js, deployed to Netlify, backed by Supabase.",
    "features": [
      "Register backend services",
      "Generate and revoke API keys",
      "View connection status (Live / Recent / Inactive / Not connected)",
      "Auto-refreshes every 30 seconds",
      "Code snippet with ESM/CommonJS toggle for Node.js"
    ],
    "auth": "Supabase email/password authentication"
  },
  "infrastructure": {
    "jaeger": {
      "description": "Required for rukkie trace. Runs locally in Docker.",
      "docker_command": "docker run -d --name jaeger -p 16686:16686 -p 4317:4317 jaegertracing/all-in-one:latest",
      "ports": [
        { "port": 16686, "purpose": "Jaeger UI + Query API (used by rukkie trace)" },
        { "port": 4317, "purpose": "OTel gRPC endpoint (used by agents)" }
      ],
      "rukkie_yaml_config": {
        "observability": {
          "jaeger": {
            "url": "http://localhost:16686"
          }
        }
      }
    },
    "supabase": {
      "project_ref": "xqmjdjjwprnqogokoejz",
      "project_url": "https://xqmjdjjwprnqogokoejz.supabase.co",
      "tables": [
        {
          "name": "services",
          "columns": ["id (uuid)", "owner_id (uuid)", "name (text)", "description (text)", "language (node|python|go|other)", "created_at"]
        },
        {
          "name": "api_keys",
          "columns": ["id (uuid)", "service_id (uuid)", "label (text)", "key_prefix (text)", "key_hash (text)", "created_at", "last_used_at", "revoked_at"]
        }
      ],
      "edge_functions": [
        {
          "name": "heartbeat",
          "url": "https://xqmjdjjwprnqogokoejz.supabase.co/functions/v1/heartbeat",
          "method": "POST",
          "auth": "Bearer <api_key>",
          "description": "Validates API key by SHA-256 hash, updates last_used_at, returns service name"
        },
        {
          "name": "services-status",
          "url": "https://xqmjdjjwprnqogokoejz.supabase.co/functions/v1/services-status",
          "method": "GET",
          "auth": "x-rukkie-cli: rukkie-cli-v1-xqmjdjjwprnqogokoejz (CLI pre-shared secret)",
          "description": "Returns all registered services with their most recent last_used_at. Used by rukkie scan / rukkie watch when no rukkie.yaml is present."
        }
      ]
    }
  },
  "ci_cd": {
    "workflows": [
      { "file": ".github/workflows/ci.yml", "trigger": "push to any branch", "description": "Build and go vet" },
      { "file": ".github/workflows/release.yml", "trigger": "push tag v*", "description": "GoReleaser cross-platform build + Inno Setup Windows installer" },
      { "file": ".github/workflows/docs.yml", "trigger": "push to main (docs/ changes)", "description": "Deploy docs/ to GitHub Pages" },
      { "file": ".github/workflows/deploy-dashboard.yml", "trigger": "push to main", "description": "Build Next.js dashboard and deploy to Netlify via ZIP API" },
      { "file": ".github/workflows/deploy-functions.yml", "trigger": "push to main (supabase/functions/ changes)", "description": "Deploy Supabase Edge Functions" },
      { "file": ".github/workflows/publish-node.yml", "trigger": "push tag v*", "description": "Publish rukkie-agent to npm" },
      { "file": ".github/workflows/publish-python.yml", "trigger": "push tag v*", "description": "Publish rukkie-agent to PyPI" }
    ],
    "required_secrets": [
      "NETLIFY_AUTH_TOKEN",
      "NETLIFY_DASHBOARD_SITE_ID",
      "NEXT_PUBLIC_SUPABASE_URL",
      "NEXT_PUBLIC_SUPABASE_ANON_KEY",
      "SUPABASE_ACCESS_TOKEN",
      "NPM_TOKEN",
      "PYPI_TOKEN"
    ]
  },
  "real_world_integration": {
    "title": "Clockee Server — Full Integration Walkthrough",
    "description": "Step-by-step record of integrating RukkiePulse observability into the Clockee backend (Express.js + PostgreSQL + Socket.io).",
    "service": {
      "name": "Clockee Server",
      "language": "Node.js / Express 5",
      "database": "PostgreSQL (via pg pool)",
      "realtime": "Socket.io",
      "api_key_env": "RUKKIE_PULSE_API_KEY"
    },
    "steps": [
      {
        "step": 1,
        "title": "Install the agent",
        "command": "npm install rukkie-agent",
        "notes": "Already added to dependencies in clockee/backend/server/package.json"
      },
      {
        "step": 2,
        "title": "Register the service in the dashboard",
        "action": "Sign in at https://rukkiepulse-dashboard.netlify.app → + New Service → name: 'Clockee Server', language: node → Generate API Key → copy key"
      },
      {
        "step": 3,
        "title": "Add the API key to .env",
        "file": "clockee/backend/server/.env",
        "line": "RUKKIE_PULSE_API_KEY=rk_live_<your_key>"
      },
      {
        "step": 4,
        "title": "Bootstrap the agent in app.js",
        "file": "clockee/backend/server/app.js",
        "code": "const { initRukkie } = require('rukkie-agent');\n\n// Call BEFORE any route/middleware registration\ninitRukkie({\n  serviceName: 'Clockee Server',\n  apiKey: process.env.RUKKIE_PULSE_API_KEY,\n  dependencies: {\n    // RukkiePulse will probe this on /__rukkie/health\n    database: async () => {\n      const client = await pool.connect();\n      await client.query('SELECT 1');\n      client.release();\n    },\n  },\n}, app);",
        "what_this_does": [
          "Boots the OpenTelemetry SDK (HTTP + Express auto-instrumentation)",
          "Fires a one-shot heartbeat POST to the Supabase Edge Function",
          "Attaches per-request span middleware to Express",
          "Registers GET /__rukkie/health that checks all listed dependencies"
        ]
      },
      {
        "step": 5,
        "title": "Create the observability tracer utility",
        "file": "clockee/backend/server/middleware/observability/tracer.js",
        "description": "Thin wrappers around the OTel API (already initialised by initRukkie). Provides withSpan() and recordError() helpers for custom spans anywhere in the codebase.",
        "exports": [
          "withSpan(spanName, attributes, asyncFn) — wraps an async operation in an OTel span, sets OK/ERROR status automatically",
          "recordError(err, extra) — records an exception on the currently active span; call from catch blocks"
        ]
      },
      {
        "step": 6,
        "title": "Wrap the database pool for per-query tracing",
        "file": "clockee/backend/server/config/database.js",
        "description": "The pg Pool.query() method is monkey-patched to wrap every promise-style call in an OTel span via withSpan(). Each span carries db.system, db.operation, db.statement, and db.name attributes.",
        "example_span_name": "db.query SELECT id, email FROM users WHERE id = $1",
        "notes": "Callback-style queries are passed through unchanged to avoid breaking existing code."
      },
      {
        "step": 7,
        "title": "Record errors on active spans in the global error handler",
        "file": "clockee/backend/server/middleware/error/errorHandler.js",
        "code": "const { recordError } = require('../observability/tracer');\n\nconst errorHandler = (err, req, res, next) => {\n  recordError(err, {\n    'http.route': req.path,\n    'http.method': req.method,\n    'error.user_id': req.user?.id || 'anonymous',\n  });\n  // ... existing logger.error and response logic\n};",
        "what_this_does": "Attaches the exception and a stack trace to the OTel span that was opened for the failing request, so RukkiePulse (and Jaeger) can surface the error alongside the trace."
      },
      {
        "step": 8,
        "title": "Add a periodic heartbeat for live dashboard status",
        "file": "clockee/backend/server/server.js",
        "description": "A startHeartbeat() function is called once the HTTP server is listening. It fires a POST to the heartbeat Edge Function immediately and then every 2 minutes, keeping the dashboard status green without requiring user traffic.",
        "code": "function startHeartbeat() {\n  const apiKey = process.env.RUKKIE_PULSE_API_KEY;\n  if (!apiKey) return;\n  const ping = () => fetch(HEARTBEAT_URL, {\n    method: 'POST',\n    headers: { Authorization: `Bearer ${apiKey}` },\n  }).catch(() => {});\n  ping();\n  setInterval(ping, 2 * 60 * 1000);\n}"
      },
      {
        "step": 9,
        "title": "Create rukkie.yaml in the service root",
        "file": "clockee/backend/server/rukkie.yaml",
        "content": "service:\n  name: Clockee Server\n  language: node\n  apiKey: rk_live_<your_key>\nobservability:\n  jaeger:\n    url: http://localhost:16686\n  collector: http://localhost:4317",
        "notes": "This file lets 'rukkie scan', 'rukkie watch', and 'rukkie inspect' work when run from the clockee/backend/server/ directory. The CLI reads this file to identify the service."
      }
    ],
    "what_is_traced": [
      "Every HTTP request → method, route, status code, duration (via Express auto-instrumentation)",
      "Every PostgreSQL query → operation, sanitised SQL statement, database name (via pool.query wrapper)",
      "Every outgoing HTTP call → Twilio, Termii, Cloudinary, Paystack (via OTel HttpInstrumentation)",
      "Every unhandled error → exception + stack recorded on the active span",
      "WebSocket queries → Socket.io event handlers use the traced pool.query, so DB calls inside them are traced automatically"
    ],
    "dashboard_status": {
      "live": "Service is running and heartbeat was received < 5 minutes ago",
      "recent": "Heartbeat received within the last hour",
      "inactive": "No heartbeat for > 1 hour",
      "never_connected": "API key created but no heartbeat ever received"
    },
    "cli_commands_after_integration": [
      { "command": "rukkie scan", "notes": "Run from any directory — fetches all connected services from cloud if no rukkie.yaml is present" },
      { "command": "rukkie watch", "notes": "Live-updating dashboard; shows Clockee Server status updating in real-time" },
      { "command": "rukkie scan", "notes": "Run from clockee/backend/server/ to use the local rukkie.yaml for local health probing" }
    ]
  },
  "tech_stack": {
    "cli": "Go + Cobra framework",
    "tui": "Bubbletea + Lipgloss + Bubbles (Charmbracelet ecosystem)",
    "auth": "golang-jwt/jwt/v5 (local JWT, hardcoded credentials)",
    "tracing": "Jaeger via HTTP Query API",
    "otel": "OpenTelemetry (agents push to Jaeger collector on port 4317)",
    "installer": "Inno Setup (.exe for Windows)",
    "dashboard": "Next.js 15 (static export) + Supabase JS client",
    "database": "Supabase (PostgreSQL) with Row Level Security",
    "edge_functions": "Supabase Edge Functions (Deno)",
    "docs": "Static HTML + CSS deployed to Netlify",
    "node_agent": "TypeScript + OpenTelemetry SDK",
    "python_agent": "Python + OpenTelemetry SDK"
  }
}
