npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dudousxd/adonis-telescope

v0.6.0

Published

An elegant debug assistant for AdonisJS — requests, queries, exceptions, jobs, mail, events, logs and more. Zero config.

Readme

@dudousxd/adonis-telescope

An elegant debug assistant for AdonisJS — inspired by Laravel Telescope.

Records requests, database queries, exceptions, validation failures, logs, events, mail, queue jobs, model writes, authorization checks, Transmit broadcasts, outgoing HTTP calls, rate-limit hits, cache, Redis and browser errors — and presents everything in a beautiful live dashboard at /telescope, with batch correlation (everything that happened during one request, grouped with a waterfall view).

Zero configuration. Install it, register the provider, done. Every official AdonisJS package is detected and instrumented automatically.

Installation

node ace add @dudousxd/adonis-telescope

Or manually:

pnpm add @dudousxd/adonis-telescope
// adonisrc.ts
providers: [
  // ...
  () => import('@dudousxd/adonis-telescope/telescope_provider'),
]

Start your server and open /telescope. That's it.

What gets captured (zero config)

| Watcher | Source | Mechanism | |---|---|---| | Requests | @adonisjs/http-server | adonisjs.http.request tracing channel + http:request_completed event | | Queries | @adonisjs/lucid | db:query event (debug force-enabled per connection) | | Exceptions | exception handler | patched report() on your handler class | | Validation | @vinejs/vine | E_VALIDATION_ERROR reports, with the failed fields | | Throttle | @adonisjs/limiter | E_TOO_MANY_REQUESTS reports | | Logs | @adonisjs/logger | pino write hook (covers ctx.logger children) | | Events | @adonisjs/events | emitter.onAny() (framework noise filtered) | | Mail | @adonisjs/mail | mail:sent / mail:queued / queued:mail:error | | Jobs | @adonisjs/queue | boringqueue.job.* tracing channels | | Models | @adonisjs/lucid | model writes synthesized from db:query, with changed columns | | Authorization | @adonisjs/bouncer | authorization:finished event (ability, result, user) | | Broadcasts | @adonisjs/transmit | Transmit lifecycle hooks (broadcast/subscribe/unsubscribe) | | HTTP client | global fetch / undici | undici:request:* diagnostics channels | | Commands | ace | console batch per command execution | | Cache | @adonisjs/cache | cache:* events (no-op when not installed) | | Redis | @adonisjs/redis | instrumented sendCommand (no-op when not installed) | | Client errors | your frontend | public rate-limited ingestion endpoint | | Translations | @adonisjs/i18n | i18n:missing:translation event | | Locks | @adonisjs/lock | instrumented acquire/run/release with wait time (contention) | | Drive | @adonisjs/drive | instrumented disk operations with timing | | Renders | Edge / Inertia | template/component, duration, Inertia props size |

Every entry recorded during a request/job/command is correlated into a batch via AsyncLocalStorage — open any entry and see the full timeline of what happened around it.

Dashboard

  • Overview — throughput chart, p50/p95/p99, slowest requests/queries, N+1 suspects (same query shape repeated ≥5× inside one request), failing validations, exception families, cache hit rate, telescope health (recorded/dropped/storage/live viewers).
  • Live tail — new entries are pushed over SSE and flash in at the top of the list (polling fallback included).
  • Per-type lists — full-text search, cursor pagination.
  • Entry drawer — type-specific details (SQL highlighting, stack traces with app-frame emphasis, mail HTML preview, validation field errors, model change diffs), raw JSON, batch waterfall.
  • Routes page — Pulse-style per-route table (requests, errors, avg, p95) with drill-down into the matching requests.
  • Query EXPLAIN — one click runs EXPLAIN (read-only, never executes) on the captured SQL and shows the plan.
  • Copy as cURL / Replay — reproduce any captured request instantly, or replay it server-side with one click (tagged replay).
  • HAR export — download a batch as a .har file and open it in the browser DevTools network panel.
  • Filter chips, time range (1h/6h/24h), light theme, j/k keyboard navigation.
  • Queues — live queue depth per status from the @adonisjs/queue database driver, with retry/remove for failed jobs.
  • AI diagnosis — see below.

MCP server — let your coding agent debug from real data

Telescope exposes an MCP endpoint at /telescope/mcp (streamable HTTP, stateless) so Claude Code, Cursor and friends can query what actually happened: "why is POST /checkout slow?" → the agent pulls the batch waterfall with every query.

claude mcp add --transport http telescope http://localhost:3333/telescope/mcp

Tools: list_entries, get_entry (with the full batch), get_batch, get_stats, diagnose_exception.

Auth: mcp: { token } (falls back to dashboard.token) as a Bearer token; outside production it works without one. Disable with mcp: false.

Production-grade capture

  • Sampling — keep a fraction per type, but never lose the interesting ones:

    sampling: {
      cache: 0.1,
      query: { rate: 0.5, keepSlowMs: 1000, keepErrors: true },
    }
  • Overload guard — recording auto-pauses when the event loop p99 lag crosses 200ms (configurable via overloadProtection) and resumes when the process recovers.

  • Server vitals — CPU, heap, RSS and event loop lag sampled every 10s, charted on the overview.

  • Per-route aggregates — Pulse-style table (requests, errors, avg, p95 per route) with drill-down into the matching requests.

Distributed tracing

Every request, job and command is a trace. Batches always carry a W3C trace id — inherited from an incoming traceparent, adopted from the active OpenTelemetry span, or derived from the batch id. The Traces page lists every flow; the detail view is a Jaeger-style waterfall: a hierarchical span tree on a shared time axis with collapsible nodes, per-type colors and error highlighting.

With tracePropagation (on by default) outgoing fetch calls carry a traceparent header — two services running telescope (or anything OTel-compatible) join up into one trace, and downstream requests nest under the http_client call that triggered them in the waterfall.

OpenTelemetry (@adonisjs/otel)

Running the first-party OTel package? Telescope batches automatically adopt the active span's trace id (zero config). And one line puts your custom spans (record(), @span) into the waterfall with their real otel parent/child ids:

// config/otel.ts
import { defineConfig } from '@adonisjs/otel'
import { telescopeSpanProcessor } from '@dudousxd/adonis-telescope/otel'

export default defineConfig({
  spanProcessors: [telescopeSpanProcessor()],
})

SDK instrumentation spans (http/lucid/redis) are skipped by default — telescope's own watchers already cover those. Opt in with telescopeSpanProcessor({ includeInstrumentationSpans: true }).

AI exception diagnosis (Vercel AI SDK — provider agnostic)

One click on an exception sends the error + stack + the request and queries from the same batch to an LLM and renders probable cause / where to look / suggested fix.

Powered by the Vercel AI SDK (optional peer). Telescope itself knows nothing about providers — pass any AI SDK LanguageModel:

pnpm add ai @ai-sdk/anthropic   # or @ai-sdk/openai, @ai-sdk/google, ollama-ai-provider, …
import { anthropic } from '@ai-sdk/anthropic'
// import { openai } from '@ai-sdk/openai'
// import { google } from '@ai-sdk/google'

export default defineConfig({
  ai: { model: anthropic('claude-sonnet-4-6') },
  // ai: { model: openai('gpt-5') },
  // ai: { model: google('gemini-2.5-flash') },
  // ai: { model: 'anthropic/claude-sonnet-4-6' }, // AI Gateway model-id string
})
  • Any gateway / local model: a string model + baseURL hits any OpenAI-compatible endpoint — OpenRouter, LiteLLM, Groq, LM Studio, vLLM, a corporate proxy:

    ai: {
      model: 'anthropic/claude-sonnet-4.6',
      baseURL: 'https://openrouter.ai/api/v1',
      apiKey: env.get('OPENROUTER_API_KEY'),
    }
  • Zero config: with no model set, telescope auto-detects whichever credential the environment already carries — ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, OPENROUTER_API_KEY or AI_GATEWAY_API_KEY — and uses the matching installed @ai-sdk/* package.

  • No AI SDK at all: bring your own function:

    ai: {
      diagnose: async ({ exception, batch }) => '…markdown…',
    }

Dashboard auth

By default the dashboard is enabled outside production and denied in production. Three ways to open it up safely:

1. Custom authorizer (recommended — integrates with anything):

export default defineConfig({
  enabled: true,
  authorize: async (ctx) => {
    const user = ctx.auth?.user
    return user?.isAdmin === true
  },
})

2. Redirect-based auth — e.g. @dudousxd/adonis-authkit:

Return { redirectTo } and browsers are sent to your login page while API calls get a 401:

import { hasAccountSession, consoleLoginUrl } from '@dudousxd/adonis-authkit-server'

export default defineConfig({
  enabled: true,
  authorize: async (ctx) =>
    hasAccountSession(ctx) ? true : { redirectTo: consoleLoginUrl('/telescope') },

  /**
   * Agents have no browser session — give the MCP endpoint its own
   * Bearer token while humans go through the authkit login:
   */
  mcp: { token: env.get('TELESCOPE_TOKEN') },
})

3. Built-in token login (zero wiring — great for staging):

export default defineConfig({
  enabled: true,
  dashboard: { token: env.get('TELESCOPE_TOKEN') },
})

Visitors get a login screen; the token is stored in an encrypted cookie (signed with your appKey, 8h TTL, /telescope/logout to clear).

Persistent storage (multi-process)

The default storage is an in-memory ring buffer. Switch to the database storage to survive restarts and share entries between the web server and queue workers:

export default defineConfig({
  storage: 'database', // creates the telescope_entries table automatically
  database: { connection: 'postgres', table: 'telescope_entries' },
  prune: { olderThanHours: 24 }, // retention, checked hourly
})

Or Redis — uses your app's own @adonisjs/redis connection by default, or any ioredis-compatible client you pass:

export default defineConfig({
  storage: 'redis',
  redis: { connection: 'main' },        // app's @adonisjs/redis
  // redis: { client: myIoredis },      // …or bring your own client
})

Or implement the TelescopeStorage interface for anything else.

Alerts

Fire a webhook the first time a new exception family shows up:

export default defineConfig({
  alerts: {
    slack: env.get('SLACK_WEBHOOK_URL'),     // formatted Slack message
    discord: env.get('DISCORD_WEBHOOK_URL'), // formatted Discord message
    email: '[email protected]',                // via the app's own @adonisjs/mail
    // or: webhook: 'https://…'              // raw JSON POST

    /** Threshold rules, evaluated every minute with cooldowns: */
    rules: {
      errorRatePercent: 5,   // 5xx rate over the window
      slowRouteMs: 2000,     // route average duration
      jobsFailed: 3,         // failed jobs in the window
      windowMinutes: 5,
      cooldownMinutes: 15,
    },
  },
})

Export to Monocle (or any OTLP backend)

Telescope is local-first, but everything it captures can be shipped to Monocle — the AdonisJS observability platform — or any OTLP/HTTP-compatible backend, as proper OpenTelemetry traces + logs (requests/jobs become root spans, queries/HTTP calls become child spans, everything else becomes correlated log records).

Zero config: set MONOCLE_API_KEY in the environment and entries start flowing. Or tune it:

export default defineConfig({
  export: {
    monocle: {
      apiKey: env.get('MONOCLE_API_KEY'),
      // endpoint: 'https://ingest.monocle.sh',  // any OTLP/HTTP backend
    },
    // …or ship batches anywhere yourself:
    // sink: async (entries) => myPipeline.push(entries),
  },
})

Browser (client) errors

A public, rate-limited endpoint ingests frontend errors. Drop this in your app shell:

window.addEventListener('error', (event) => {
  const payload = {
    message: event.message,
    name: event.error?.name,
    stack: event.error?.stack,
    url: location.href,
  }
  navigator.sendBeacon?.(
    '/telescope/client-errors?e=' +
      btoa(JSON.stringify(payload)).replace(/\+/g, '-').replace(/\//g, '_')
  )
})

They show up as client_error entries with stack traces.

telescope.dump()

The dd() of telescope — from anywhere in your code:

import telescope from '@dudousxd/adonis-telescope/services/main'

telescope.dump({ cart, totals }, 'checkout-debug')

Shows up on the dashboard, correlated to the active request.

Custom watchers

Anything the built-ins don't cover can be captured with the same API the built-in watchers use:

import type { TelescopeWatcher, WatcherContext } from '@dudousxd/adonis-telescope/types'

class StripeWatcher implements TelescopeWatcher {
  readonly type = 'stripe' // shows up automatically in the dashboard

  async register(ctx: WatcherContext) {
    const emitter = await ctx.app.container.make('emitter')
    emitter.on('stripe:webhook', (payload) => {
      ctx.record({
        type: 'stripe',
        content: { event: payload.type, id: payload.id },
        tags: [`event:${payload.type}`],
      })
    })
  }
}

export default defineConfig({
  custom: [new StripeWatcher()],
})

The WatcherContext also exposes startBatch(origin) / runInBatch() for entry-point watchers (things that should group sub-entries, the way requests and jobs do), and currentBatch() for correlation.

Configuration reference

Everything works without a config file. Create config/telescope.ts only when you want to tune things:

import { defineConfig } from '@dudousxd/adonis-telescope'

export default defineConfig({
  enabled: true,              // default: !app.inProduction
  path: 'telescope',          // dashboard mount path
  maxEntries: 10_000,         // in-memory ring buffer capacity
  ignorePaths: ['/health'],   // never record these requests
  redact: { keys: ['ssn'] },  // extra sensitive keys (merged with defaults)

  watchers: {
    queries: { slowMs: 150, nPlusOneThreshold: 5 },
    logs: { minLevel: 'info' },
    exceptions: { capture4xx: false },
    requests: { recordResponse: true, slowMs: 1000 },
    events: { ignore: [/^internal:/] },
    // any watcher: false to disable
    // validation / throttle / models / bouncer / transmit / commands: on by default
  },

  resolveUser: (ctx) => ctx.auth?.user,
})

Safety

  • Recording is exception-safe: a failing watcher can never break your app.
  • Telescope's own work (storage writes, webhooks, AI calls) is flagged internally so it never records itself.
  • Sensitive keys (password, authorization, cookie, tokens, …) are redacted before storage; payloads are size-bounded and detached from live object graphs.
  • EXPLAIN never uses ANALYZE — captured queries are never re-executed.
  • The telescope routes themselves are never recorded.