📡 CLI-first observability

Know what's broken.
Before your users do.

RukkiePulse is a terminal-first observability platform. Health checks, endpoint probing, distributed tracing — one command away.

$ rukkie scan
 
my-backend [dev]
 
🟢 auth-service   45ms    3/3 endpoints ok
🔴 payment-service —      0/2 endpoints ok  (connection refused)
🟡 graphql-api 920ms  1/2 endpoints ok
 
$ rukkie trace payment-service /charge
 
Root Cause:
  ❌ ChargeRepo.createTransaction
     Database timeout after 5000ms

Concurrent scanning

All services scanned in parallel. 50+ services in under 3 seconds.

🔍

Deep endpoint probing

Test REST endpoints and GraphQL queries, not just pings.

🔗

Distributed tracing

Root cause analysis across service boundaries via Jaeger.

📦

Zero-config agents

One line to instrument Node.js (Express/Fastify) or Python (FastAPI/Flask).

Quick Start

Get up and running in under 2 minutes.

1. Download & install

Download rukkie-setup.exe from the latest GitHub release. Run it — it installs the binary and adds it to your system PATH automatically.

↓ Download Installer

2. Create rukkie.yaml in your project

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

3. Log in and scan

$ rukkie login
Password: ••••••••
✅ Logged in to RukkiePulse

$ rukkie scan

Configuration

rukkie.yaml lives in the root of your project.

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
FieldDescription
projectDisplay name shown in CLI output
observability.jaeger.urlJaeger Query UI URL for rukkie trace
environments.<name>Named environment (dev, staging, production…)
services[].nameDisplay name
services[].urlBase URL of the service
services[].typeREST or GRAPHQL
endpoints[].pathURL path to probe (REST)
endpoints[].methodHTTP method — default: GET
endpoints[].bodyJSON request body
endpoints[].expect_statusExpected HTTP status — default: 200
endpoints[].queryGraphQL query string
endpoints[].expect_no_errorsFail if response contains errors

Commands

All commands require a rukkie.yaml in the current directory, except login and logout.

rukkie login Authenticate — required before all other commands
rukkie login

Prompts for a password, generates a local JWT, and stores the session at ~/.rukkie/config.yaml. Sessions last 30 days.

rukkie scan Scan all services: health + endpoint probes
rukkie scan
rukkie scan --env production
rukkie scan --errors-only
FlagDefaultDescription
--env, -edevEnvironment to use
--errors-onlyfalseShow only failing or degraded services
rukkie status Alias for scan — same flags
rukkie status
rukkie status --errors-only
rukkie inspect <service> Deep-dive into one service
rukkie inspect auth-service
rukkie inspect auth-service --env production

Shows health status, dependency connections, and a per-endpoint breakdown with latency and status codes.

rukkie trace <service> [endpoint] Fetch and display distributed traces from Jaeger
# Latest trace for a service
rukkie trace auth-service

# Filter to a specific endpoint
rukkie trace auth-service /login

# Specific trace by ID
rukkie trace auth-service --trace-id abc123def456

# Show last 5 traces
rukkie trace auth-service --last 5

# Render as flame graph
rukkie trace auth-service /login --flame
FlagDefaultDescription
--trace-idFetch a specific trace by ID
--last1Number of recent traces to show
--flamefalseRender as horizontal flame graph

⚠️ Requires Jaeger to be running and observability.jaeger.url set in rukkie.yaml.

rukkie watch Live-updating dashboard — refreshes on an interval
rukkie watch
rukkie watch --interval 5s
rukkie watch --env production --interval 30s
FlagDefaultDescription
--interval10sRefresh interval. Accepts 5s, 1m, etc.
--env, -edevEnvironment to use

Press Ctrl+C to stop.

rukkie logout Clear the stored session
rukkie logout

rukkie init

Run inside any backend project to create rukkie.yaml and print the integration snippet for that language.

$ cd my-backend-project
$ rukkie init

  ✅  Created rukkie.yaml for "my-backend-project" (node)

  Replace YOUR_API_KEY with a key from the dashboard:
  https://rukkiepulse-dashboard.netlify.app

  ── ESM ────────────────────────────────────────────────
  import { initRukkie } from 'rukkie-agent'

  initRukkie({
    serviceName: 'my-backend-project',
    apiKey: 'YOUR_API_KEY',
  })

  ── CommonJS ───────────────────────────────────────────
  const { initRukkie } = require('rukkie-agent')

  initRukkie({
    serviceName: 'my-backend-project',
    apiKey: 'YOUR_API_KEY',
  })

  Install:  npm install rukkie-agent

Auto-detects Node.js (package.json), Python (pyproject.toml / requirements.txt), and Go (go.mod).

The generated rukkie.yaml:

service:
  name: my-backend-project
  language: node
  apiKey: YOUR_API_KEY
observability:
  jaeger:
    url: http://localhost:16686
  collector: http://localhost:4317

Agent SDKs

Drop one line into your service. The agent auto-instruments requests, exposes /__rukkie/health, pushes traces to Jaeger, and pings the dashboard so you can see it as Live.

Connection status — once your API key is active in a service, the dashboard shows 🟢 Live (seen <5 min ago), 🟡 Recent (<1 h), or 🔴 Inactive (last seen >1 h). No config needed — the agent sends a heartbeat automatically on startup.

Node.js

npm install rukkie-agent
// ESM (recommended)
import { initRukkie } from 'rukkie-agent'

initRukkie({
  serviceName: 'auth-service',
  apiKey: 'rk_live_xxx',
})

// CommonJS
const { initRukkie } = require('rukkie-agent')

initRukkie({
  serviceName: 'auth-service',
  apiKey: 'rk_live_xxx',
})

Auto-detects Express and Fastify. Pass app as the second argument to enable middleware and GET /__rukkie/health.

Python

pip install rukkie-agent
# FastAPI / Flask
from rukkie_agent import init_rukkie

init_rukkie(
    service_name="auth-service",
    api_key="rk_live_xxx",
)

Auto-detects FastAPI and Flask. Same capabilities as the Node.js agent.

Jaeger Setup

Required for rukkie trace. Runs locally in Docker — no account or API key needed.

Start Jaeger (all-in-one)

docker run -d --name jaeger \
  -p 16686:16686 \
  -p 4317:4317 \
  jaegertracing/all-in-one:latest
PortPurpose
16686Jaeger UI + Query API (used by the CLI)
4317OTel gRPC endpoint (used by agents)

Add to rukkie.yaml:

observability:
  jaeger:
    url: http://localhost:16686

Set collectorUrl in your agents to http://localhost:4317 (this is already the default).

API Keys & Service Registration

Each backend service you monitor needs an API key so RukkiePulse can identify it. Keys are generated in the dashboard — they are shown only once, then stored as a secure hash.

RukkiePulse Dashboard

Register services, generate & revoke API keys, view all connected backends.

⚡ Open Dashboard →

Steps

  1. Open the dashboard and sign in.
  2. Click + New Service and fill in the service name and language.
  3. On the service page click + Generate API Key — copy the key immediately, it won't be shown again.
  4. Add the key to your service using the agent SDK:

Node.js

import { initRukkie } from 'rukkie-agent'

initRukkie({
  serviceName: 'auth-service',
  apiKey: 'rk_live_…',
})

Python

from rukkie_agent import init_rukkie

init_rukkie(
    service_name="auth-service",
    api_key="rk_live_…",
)

To rotate a key: go to the service page → click Revoke on the old key → click + Generate API Key for a new one.

Scanning Without rukkie.yaml

rukkie scan and rukkie watch now work from any directory. If no rukkie.yaml is found, they fall back to the cloud and fetch all services you have registered in the dashboard — no config file needed.

$ rukkie scan

  RukkiePulse  connected services

  ────────────────────────────────────────────────────────────────
  🟢  Clockee Server          node    Live       last seen 1m ago  2 key(s)
  🔴  My API                  python  Inactive   last seen 2026-03-10 14:30
  ⚫  New Service             go      Never connected
  ────────────────────────────────────────────────────────────────

  🟢 1 live  ·  🔴 1 inactive  ·  ⚫ 1 never connected

rukkie watch opens a live-updating TUI that refreshes from the cloud on the configured interval:

$ rukkie watch --interval 30s   # live dashboard, refreshes every 30s

The connection status is determined by the most recent heartbeat received from the service's API key:

  • 🟢 Live — heartbeat received < 5 minutes ago
  • 🟡 Recent — heartbeat received < 1 hour ago
  • 🔴 Inactive — no heartbeat for > 1 hour
  • Never connected — API key created but no heartbeat ever received

Full Integration Guide — Express + PostgreSQL

A step-by-step walkthrough of every change made to wire RukkiePulse observability deep into an existing Express server. All examples are taken from the Clockee Server integration (Node.js / Express 5 / PostgreSQL / Socket.io).

Step 1 — Install the agent

$ npm install rukkie-agent

Step 2 — Register the service & get an API key

  1. Sign in at the dashboard.
  2. Click + New Service — enter name and language.
  3. On the service page click + Generate API Key and copy it immediately.
  4. Add to your .env: RUKKIE_PULSE_API_KEY=rk_live_…

Step 3 — Bootstrap in app.js (before any middleware)

const { initRukkie } = require('rukkie-agent');

// Call BEFORE cors(), body-parser, and routes
initRukkie({
  serviceName: 'Clockee Server',
  apiKey: process.env.RUKKIE_PULSE_API_KEY,
  dependencies: {
    // Probed on every GET /__rukkie/health request
    database: async () => {
      const client = await pool.connect();
      await client.query('SELECT 1');
      client.release();
    },
  },
}, app);

This single call:

  • Boots the OTel SDK (HTTP + Express auto-instrumentation)
  • Fires a startup heartbeat so the dashboard shows the service as Live
  • Attaches per-request span middleware to Express
  • Registers GET /__rukkie/health that probes all listed dependencies

Step 4 — Create the observability tracer utility

Create middleware/observability/tracer.js — thin wrappers around the OTel API for custom spans:

const { withSpan, recordError } = require('./middleware/observability/tracer');

// Wrap any async operation in a named span
const result = await withSpan('my-operation', { 'custom.attr': 'value' }, async () => {
  return await doSomething();
});

// Record an error on the current active span (call from catch blocks)
catch (err) { recordError(err); throw err; }

Step 5 — Wrap the database pool for per-query tracing

In config/database.js, monkey-patch pool.query() so every SQL call creates an OTel span:

const { withSpan } = require('../middleware/observability/tracer');
const _originalQuery = pool.query.bind(pool);

pool.query = function tracedQuery(textOrConfig, values, callback) {
  // callback-style: pass through unchanged
  if (typeof values === 'function' || typeof callback === 'function') {
    return _originalQuery(textOrConfig, values ?? callback, callback);
  }
  // promise-style: wrap in a span
  const sql = typeof textOrConfig === 'string' ? textOrConfig : textOrConfig.text;
  return withSpan(`db.query ${sql.slice(0, 120)}`, {
    'db.system': 'postgresql',
    'db.statement': sql,
  }, () => values ? _originalQuery(textOrConfig, values) : _originalQuery(textOrConfig));
};

Every query through the pool now appears as a child span in Jaeger — you can see the exact SQL, duration, and any errors.

Step 6 — Record errors in the global error handler

Add recordError() to the top of your Express error handler:

const { recordError } = require('../observability/tracer');

const errorHandler = (err, req, res, next) => {
  recordError(err, {
    'http.route': req.path,
    'http.method': req.method,
    'error.user_id': req.user?.id || 'anonymous',
  });
  // ... rest of your handler
};

Step 7 — Add a periodic heartbeat in server.js

The agent pings once on startup. Add a 2-minute interval so the dashboard stays Live even between requests:

function startHeartbeat() {
  const apiKey = process.env.RUKKIE_PULSE_API_KEY;
  if (!apiKey) return;
  const ping = () => fetch(HEARTBEAT_URL, {
    method: 'POST',
    headers: { Authorization: `Bearer ${apiKey}` },
  }).catch(() => {});
  ping();
  setInterval(ping, 2 * 60 * 1000);
}

server.listen(PORT, () => {
  // ... existing startup logs
  startHeartbeat();
});

Step 8 — Create rukkie.yaml in the service root

service:
  name: Clockee Server
  language: node
  apiKey: rk_live_…
observability:
  jaeger:
    url: http://localhost:16686
  collector: http://localhost:4317

What gets traced after integration

  • Every HTTP request — method, route, status code, duration
  • Every PostgreSQL query — operation, SQL statement, database name
  • Every outgoing HTTP call — to Twilio, Cloudinary, Paystack, etc.
  • Every unhandled error — exception + stack recorded on the active span
  • Socket.io DB calls — because they use the same traced pool, all their queries appear in traces automatically