@adonis-agora/telescope 0.8.4 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @adonis-agora/telescope
2
2
 
3
+ ## 0.8.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [#30](https://github.com/DavideCarvalho/adonis-agora-telescope/pull/30) [`ede467a`](https://github.com/DavideCarvalho/adonis-agora-telescope/commit/ede467ac92daa97b829129751f01a41bea759329) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Add TanStack Intent AI-agent skills. Ship six SKILL.md guides inside both published
8
+ packages (`packages/*/skills/**`, now included in the `files` array): five core
9
+ skills for `@adonis-agora/telescope` (setup, watchers, storage/retention,
10
+ alerts/AI/client-errors, dashboard access control + MCP) and one for
11
+ `@adonis-agora/telescope-ui` (the React console + `/client`). Adds
12
+ `_artifacts/` domain map, skill spec and skill tree at the repo root, a
13
+ `tanstack-intent` keyword and `@tanstack/intent` devDependency to both packages,
14
+ and an `.github/workflows/check-skills.yml` CI validation workflow.
15
+
3
16
  ## 0.8.4
4
17
 
5
18
  ### Patch Changes
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export declare const VERSION = "0.8.4";
2
+ export declare const VERSION = "0.8.5";
3
3
  export { TelescopeAiDiagnoser } from './telescope_ai_diagnoser.js';
4
4
  export type { AnthropicMessagesClient, DiagnoseOptions, TelescopeAiDiagnoserOptions, } from './telescope_ai_diagnoser.js';
5
5
  export { normalizeConfidence, parseDiagnosis } from './diagnoser.js';
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export const VERSION = '0.8.4';
2
+ export const VERSION = '0.8.5';
3
3
  // — diagnoser —
4
4
  export { TelescopeAiDiagnoser } from './telescope_ai_diagnoser.js';
5
5
  // — diagnosis model + parsing —
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export declare const VERSION = "0.8.4";
2
+ export declare const VERSION = "0.8.5";
3
3
  export { consoleChannel, customChannel, slackChannel, webhookChannel, } from './alert_channel.js';
4
4
  export type { AlertChannel, ChannelFetch, ConsoleSink } from './alert_channel.js';
5
5
  export { formatSlackMessage } from './slack_format.js';
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export const VERSION = '0.8.4';
2
+ export const VERSION = '0.8.5';
3
3
  // — channels —
4
4
  export { consoleChannel, customChannel, slackChannel, webhookChannel, } from './alert_channel.js';
5
5
  // — slack formatting —
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export declare const VERSION = "0.8.4";
2
+ export declare const VERSION = "0.8.5";
3
3
  export { EntryType, isBatchOrigin } from './entry.js';
4
4
  export type { BatchOrigin, BuiltinEntryType, Entry, RecordInput } from './entry.js';
5
5
  export type { EntryQuery, TelescopeStore } from './store.js';
package/dist/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export const VERSION = '0.8.4';
2
+ export const VERSION = '0.8.5';
3
3
  // — entry model —
4
4
  export { EntryType, isBatchOrigin } from './entry.js';
5
5
  export { InMemoryTelescopeStore } from './stores/memory.js';
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export declare const VERSION = "0.8.4";
2
+ export declare const VERSION = "0.8.5";
3
3
  export { defineConfig, resolveConfig, MCP_TOOL_NAMES } from './define_config.js';
4
4
  export type { McpToolName, ResolvedTelescopeMcpConfig, TelescopeMcpConfig, } from './define_config.js';
5
5
  export { MCP_INTERNAL_ERROR, MCP_METHOD_NOT_FOUND, MCP_TOOLS, MCP_UNAUTHORIZED, TelescopeMcpServer, } from './server.js';
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export const VERSION = '0.8.4';
2
+ export const VERSION = '0.8.5';
3
3
  // — config —
4
4
  export { defineConfig, resolveConfig, MCP_TOOL_NAMES } from './define_config.js';
5
5
  // — server (plain, testable JSON-RPC surface) —
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export declare const VERSION = "0.8.4";
2
+ export declare const VERSION = "0.8.5";
3
3
  export { defineConfig, resolveConfig } from './define_config.js';
4
4
  export type { ResolvedTelescopeCpuProfilingConfig, TelescopeCpuProfilingConfig, } from './define_config.js';
5
5
  export { CPU_PROFILE_ENTRY_TYPE, ProfilerService, } from './profiler_service.js';
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export const VERSION = '0.8.4';
2
+ export const VERSION = '0.8.5';
3
3
  export { defineConfig, resolveConfig } from './define_config.js';
4
4
  export { CPU_PROFILE_ENTRY_TYPE, ProfilerService, } from './profiler_service.js';
5
5
  export { CpuProfiler, defaultSessionFactory } from './cpu-profiler.js';
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export declare const VERSION = "0.8.4";
2
+ export declare const VERSION = "0.8.5";
3
3
  export { defineConfig, resolveConfig, normalizePath, defaultAuthorize } from './define_config.js';
4
4
  export type { AuthorizeHook, ReplayConfig, ResolvedTelescopeUiConfig, TelescopeUiConfig, UiCredentials, } from './define_config.js';
5
5
  export { resolveDashboardAuth, performLogin, readSession, sanitizeReturnTo, decideDashboardAuth, SESSION_COOKIE_NAME, } from './auth.js';
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export const VERSION = '0.8.4';
2
+ export const VERSION = '0.8.5';
3
3
  // — config —
4
4
  export { defineConfig, resolveConfig, normalizePath, defaultAuthorize } from './define_config.js';
5
5
  // — built-in `dashboardAuth` login screen (optional; opt-in via `config/telescope_ui.ts`) —
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export declare const VERSION = "0.8.4";
2
+ export declare const VERSION = "0.8.5";
3
3
  export type { EmitterLike, Watcher } from './emitter.js';
4
4
  export { resolveStore, safeRecord } from './record.js';
5
5
  export { buildQueryEntry, DB_QUERY_EVENT, LucidQueryWatcher } from './lucid_query_watcher.js';
@@ -1,5 +1,5 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export const VERSION = '0.8.4';
2
+ export const VERSION = '0.8.5';
3
3
  // — runtime recording (advanced) —
4
4
  export { resolveStore, safeRecord } from './record.js';
5
5
  // — Lucid query watcher (the headline watcher) —
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/telescope",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
4
4
  "description": "Laravel Telescope-style headless observability for AdonisJS — records HTTP requests and every Agora diagnostics event as queryable entries, plus opt-in subpaths for per-technology watchers, a web dashboard, AI exception diagnosis, and alerting.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -97,7 +97,8 @@
97
97
  "files": [
98
98
  "dist/",
99
99
  "README.md",
100
- "CHANGELOG.md"
100
+ "CHANGELOG.md",
101
+ "skills"
101
102
  ],
102
103
  "//sideEffects": "registry.ts publishes the telescope runtime handle on a Symbol.for() global slot at import time — process-global state a bundler told sideEffects:false could tree-shake. The rest is pure.",
103
104
  "sideEffects": [
@@ -134,6 +135,7 @@
134
135
  "@adonisjs/core": "7.4.0",
135
136
  "@adonisjs/lucid": "22.4.2",
136
137
  "@anthropic-ai/sdk": "0.116.0",
138
+ "@tanstack/intent": "^0.3.2",
137
139
  "@types/better-sqlite3": "7.6.13",
138
140
  "@types/node": "20.19.43",
139
141
  "better-sqlite3": "12.11.1",
@@ -152,7 +154,8 @@
152
154
  "observability",
153
155
  "diagnostics",
154
156
  "tracing",
155
- "agora"
157
+ "agora",
158
+ "tanstack-intent"
156
159
  ],
157
160
  "scripts": {
158
161
  "build": "tsc -p tsconfig.json && pnpm run copy:stubs && pnpm run copy:assets",
@@ -0,0 +1,280 @@
1
+ ---
2
+ name: telescope-access-mcp
3
+ description: >-
4
+ Dashboard access control and the MCP endpoint in @adonis-agora/telescope —
5
+ config/telescope_ui.ts authorize(ctx) hook vs credentials { token, basic },
6
+ dashboardAuth { secret, ttl, login } login screen, defaultAuthorize (allow outside
7
+ production, deny in production), 401-vs-403 fail-closed guard semantics,
8
+ redirect-wins behaviour, replay/queueActions/cpuProfiling.armEnabled mutation
9
+ gates, and config/telescope_mcp.ts JSON-RPC tools behind the same guard. Use for
10
+ "lock down the dashboard", "dashboard open in production", "401 on /telescope",
11
+ "login wall", "expose telescope to Claude Code", "replay returns 403".
12
+ license: MIT
13
+ metadata:
14
+ type: core
15
+ library: "@adonis-agora/telescope"
16
+ library_version: "0.8.4"
17
+ framework: adonisjs
18
+ sources:
19
+ - "DavideCarvalho/adonis-telescope:docs/dashboard/auth.mdx"
20
+ - "DavideCarvalho/adonis-telescope:docs/packages/mcp.mdx"
21
+ - "DavideCarvalho/adonis-telescope:docs/packages/ui.mdx"
22
+ ---
23
+
24
+ # Dashboard access control & MCP
25
+
26
+ Every route serving recorded data runs an `authorize` guard first — every
27
+ `<path>/api/*` endpoint, the SSE stream, metrics and the SPA routes. Out of the box
28
+ the policy is **allow outside production, deny in production** until a credential or
29
+ hook permits it. The MCP subpath reuses exactly the same guard.
30
+
31
+ ## Setup
32
+
33
+ The simplest production gate is a built-in credential; either `token` or `basic`,
34
+ or both:
35
+
36
+ ```ts
37
+ // config/telescope_ui.ts
38
+ import env from '#start/env'
39
+ import { defineConfig } from '@adonis-agora/telescope/ui'
40
+
41
+ export default defineConfig({
42
+ enabled: true,
43
+ path: '/telescope',
44
+ credentials: { token: env.get('TELESCOPE_UI_TOKEN') },
45
+ })
46
+ ```
47
+
48
+ ```sh
49
+ curl -H 'Authorization: Bearer s3cr3t' http://localhost:3333/telescope/api/entries
50
+ open 'http://localhost:3333/telescope?token=s3cr3t' # ?token= is convenient but leaky
51
+ ```
52
+
53
+ Source: `docs/dashboard/auth.mdx` (Built-in credentials).
54
+
55
+ ## Core patterns
56
+
57
+ ### Pattern 1 — delegate to your app's auth
58
+
59
+ When you set `authorize`, the built-in `credentials` gate is ignored — your hook IS
60
+ the whole policy. It receives your **real** `HttpContext` through a framework-light
61
+ declared type, so narrow it to reach `auth`/`session` at runtime.
62
+
63
+ ```ts
64
+ // config/telescope_ui.ts
65
+ export default defineConfig({
66
+ async authorize(ctx) {
67
+ const { auth } = ctx as unknown as {
68
+ auth: { check(): Promise<boolean>; user?: { role?: string } }
69
+ }
70
+ await auth.check()
71
+ return auth.user?.role === 'admin'
72
+ },
73
+ })
74
+ ```
75
+
76
+ Guard outcomes: denied with no credential → `401` (+ `WWW-Authenticate`); denied
77
+ with a rejected credential → `403`; a throwing hook → `403` (fail-closed).
78
+
79
+ Source: `docs/dashboard/auth.mdx` (Delegate to your app's auth, Guard behaviour).
80
+
81
+ ### Pattern 2 — a real login wall with dashboardAuth
82
+
83
+ For shared human access: a server-rendered login page plus a signed session cookie.
84
+ It composes WITH `authorize` (both must pass). Missing `secret` or `login` throws at
85
+ boot rather than shipping an un-mintable gate.
86
+
87
+ ```ts
88
+ // config/telescope_ui.ts
89
+ import env from '#start/env'
90
+ import User from '#models/user'
91
+ import hash from '@adonisjs/core/services/hash'
92
+ import { defineConfig } from '@adonis-agora/telescope/ui'
93
+
94
+ export default defineConfig({
95
+ dashboardAuth: {
96
+ secret: env.get('TELESCOPE_DASHBOARD_SECRET'), // HMAC-SHA256 key, 32+ bytes — required
97
+ ttl: '8h',
98
+ async login(username, password) {
99
+ const user = await User.findBy('email', username)
100
+ if (!user || !(await hash.verify(user.password, password))) return null
101
+ if (!user.isAdmin) return null
102
+ return { id: String(user.id), name: user.fullName } // session user, or null to deny
103
+ },
104
+ },
105
+ })
106
+ ```
107
+
108
+ This mounts unguarded-by-necessity `GET/POST <path>/login` and `GET <path>/logout`
109
+ routes (they mint/clear the session only — no recorded data sits behind them).
110
+ An unauthenticated page navigation gets a `302` to the login; an unauthenticated API
111
+ call gets a plain `401`.
112
+
113
+ Source: `docs/dashboard/auth.mdx` (Built-in login screen).
114
+
115
+ ### Pattern 3 — redirect instead of answering JSON
116
+
117
+ If the hook sets a `location` header and then returns `false`, the guard leaves the
118
+ redirect alone instead of overwriting it with a `403` body.
119
+
120
+ ```ts
121
+ authorize: (ctx) => {
122
+ const { auth } = ctx as unknown as { auth: { user?: { isAdmin?: boolean } } }
123
+ if (auth.user?.isAdmin === true) return true
124
+ ctx.response.redirect('/login?next=/telescope')
125
+ return false // redirect wins only when the hook returns false
126
+ }
127
+ ```
128
+
129
+ Source: `docs/dashboard/auth.mdx` (Redirecting instead of answering JSON).
130
+
131
+ ### Pattern 4 — expose the store to coding agents over MCP
132
+
133
+ ```ts
134
+ // config/telescope_mcp.ts
135
+ import env from '#start/env'
136
+ import { defineConfig } from '@adonis-agora/telescope/mcp'
137
+
138
+ export default defineConfig({
139
+ enabled: true,
140
+ path: '/telescope/mcp',
141
+ credentials: { token: env.get('TELESCOPE_MCP_TOKEN') }, // same guard as the dashboard
142
+ // tools: ['list_entries', 'get_trace', 'get_health'], // hand agents a narrower surface
143
+ })
144
+ ```
145
+
146
+ ```sh
147
+ claude mcp add --transport http telescope https://your-app.example.com/telescope/mcp \
148
+ --header 'Authorization: Bearer <TELESCOPE_MCP_TOKEN>'
149
+ ```
150
+
151
+ Six read tools ship: `list_entries`, `get_entry`, `get_trace`, `get_waterfall`,
152
+ `get_health`, `diagnose_exception`. Redaction and sampling are never bypassed — an
153
+ agent sees exactly what the dashboard sees.
154
+
155
+ Source: `docs/packages/mcp.mdx`.
156
+
157
+ ### Pattern 5 — gate the dangerous mutations
158
+
159
+ Replay, CPU-arm and queue retry/enqueue are each disabled by default and answer
160
+ `403` until their ui-config gate is flipped — even when the underlying feature is
161
+ enabled.
162
+
163
+ ```ts
164
+ export default defineConfig({
165
+ replay: { enabled: true }, // POST <path>/api/requests/:id/replay — re-runs a real request!
166
+ cpuProfiling: { armEnabled: true }, // POST <path>/api/profiles/arm
167
+ queueActions: { enabled: true }, // queue console retry/enqueue
168
+ })
169
+ ```
170
+
171
+ Source: `docs/packages/ui.mdx` (Configuration, Request replay),
172
+ `docs/packages/cpu-profiling.mdx` (arming callout).
173
+
174
+ ## Common mistakes
175
+
176
+ ### CRITICAL Relying on an obscure path instead of the guard
177
+
178
+ ```ts
179
+ // Wrong — renaming the prefix is defense in depth, not authentication:
180
+ defineConfig({ path: '/__telescope' }) // production: denied anyway without a credential/hook
181
+ ```
182
+
183
+ ```ts
184
+ // Correct — make a deliberate choice for production:
185
+ defineConfig({
186
+ path: '/__telescope',
187
+ credentials: { token: env.get('TELESCOPE_UI_TOKEN') },
188
+ })
189
+ ```
190
+
191
+ Mechanism: the default policy denies production until a credential or `authorize`
192
+ hook permits it — but that deny is the *default*, never a guarantee you can skip
193
+ configuring; security notes call this out explicitly.
194
+ Source: `docs/dashboard/auth.mdx` (default policy warn + Security notes).
195
+
196
+ ### MEDIUM Setting credentials AND an authorize hook expecting both to run
197
+
198
+ ```ts
199
+ // Wrong — the token gate silently stops mattering once authorize is set.
200
+ defineConfig({
201
+ credentials: { token: env.get('TELESCOPE_UI_TOKEN') },
202
+ authorize: () => true, // now the whole policy; credentials ignored
203
+ })
204
+ ```
205
+
206
+ ```ts
207
+ // Correct — pick one: credentials for simple gates, authorize for real policies.
208
+ defineConfig({ credentials: { basic: { username: 'admin', password: env.get('TELESCOPE_UI_PASSWORD') } } })
209
+ ```
210
+
211
+ Mechanism: the docs state the built-in `credentials` gate is ignored whenever
212
+ `authorize` is set — your hook replaces it entirely (`dashboardAuth`, by contrast,
213
+ composes with `authorize`).
214
+ Source: `docs/dashboard/auth.mdx` (Delegate to your app's auth).
215
+
216
+ ### MEDIUM Reaching for ctx.auth without narrowing the context
217
+
218
+ ```ts
219
+ // Wrong — does not type-check: the declared parameter is a minimal HTTP slice.
220
+ authorize: (ctx) => ctx.auth.user?.isAdmin === true
221
+ ```
222
+
223
+ ```ts
224
+ // Correct — narrow to the slice you rely on; the REAL HttpContext passes through.
225
+ authorize: (ctx) => {
226
+ const { auth } = ctx as unknown as { auth: { user?: { isAdmin?: boolean } } }
227
+ return auth.user?.isAdmin === true
228
+ }
229
+ ```
230
+
231
+ Mechanism: the guard's declared shape is framework-light (`request.qs()`,
232
+ `request.header()`, `response`) so any server can satisfy it; `auth`/`session`
233
+ exist at runtime only after narrowing.
234
+ Source: `docs/dashboard/auth.mdx` (narrowing info callout).
235
+
236
+ ### HIGH Exposing MCP in production without a token credential
237
+
238
+ ```ts
239
+ // Wrong — relies on the dev-open default; prod denials surface as opaque JSON-RPC errors.
240
+ defineConfig({ enabled: true, path: '/telescope/mcp' })
241
+ ```
242
+
243
+ ```ts
244
+ // Correct — a token/basic credential for production.
245
+ defineConfig({
246
+ path: '/telescope/mcp',
247
+ credentials: { token: env.get('TELESCOPE_MCP_TOKEN') },
248
+ })
249
+ ```
250
+
251
+ Mechanism: MCP has no transport-level 401 — denial rides inside the JSON-RPC
252
+ envelope as `-32001` (`MCP_UNAUTHORIZED`), which compliant clients just report as a
253
+ failed call.
254
+ Source: `docs/packages/mcp.mdx` (guard info callout).
255
+
256
+ ### MEDIUM Configuring dashboardAuth without secret or login
257
+
258
+ ```ts
259
+ // Wrong — both keys are required; this throws at boot.
260
+ defineConfig({ dashboardAuth: { secret: env.get('TELESCOPE_DASHBOARD_SECRET') } })
261
+ defineConfig({ dashboardAuth: { login: verifyCreds } })
262
+ ```
263
+
264
+ ```ts
265
+ // Correct — non-empty secret AND a login hook.
266
+ defineConfig({
267
+ dashboardAuth: {
268
+ secret: env.get('TELESCOPE_DASHBOARD_SECRET'),
269
+ ttl: '8h',
270
+ login: verifyCreds,
271
+ },
272
+ })
273
+ ```
274
+
275
+ Mechanism: the cookie can never be minted without a signing key and a validator, so
276
+ the config fails closed at startup rather than shipping an un-loginable wall.
277
+ Source: `docs/dashboard/auth.mdx` (Built-in login screen table + warn callout).
278
+
279
+ See also: `telescope-ui-dashboard/SKILL.md` — the SPA behind these gates;
280
+ `telescope-alerts-ai/SKILL.md` — the diagnose route this guard covers.