# Telescope Documentation (Full)
> Consolidated documentation for context window loading.
---
# Getting Started
# Getting Started
Everything you need to add `fluttersdk_telescope` to your project, wire the watchers,
and start inspecting runtime state from the CLI or an MCP-connected AI agent.
## Pick your path
- [**Installation**](installation): Add fluttersdk_telescope to a Flutter project step by step.
- [**Quickstart**](quickstart): 3-step end-to-end walkthrough from install to first MCP tool call.
## What is fluttersdk_telescope?
Telescope is a passive runtime inspector for Flutter apps. It registers a set of watchers and VM
Service extensions at startup, captures every HTTP request, log line, exception, `debugPrint` call,
DB query, and Magic-framework lifecycle event into ring buffers, then surfaces those buffers through
CLI commands and MCP tools without ever modifying app behavior.
One `telescope:install` command wires everything end-to-end. Telescope ships its own
bootstrap CLI entry point so the command works from a fresh consumer without any prior
`fluttersdk_artisan` wiring:
```bash
flutter pub add fluttersdk_telescope
dart run fluttersdk_telescope telescope:install
```
The command scaffolds the consumer artisan harness if it is missing, runs
`plugin:install fluttersdk_telescope`, and patches `lib/main.dart` so
`TelescopePlugin.install()` runs at startup inside a `kDebugMode` guard. Release builds
tree-shake the entire subsystem; there is zero production overhead.
After install, the artisan native AOT launcher at `./bin/fsa` gives ~110ms warm startup
for every subsequent telescope command (`./bin/fsa telescope:tail`, etc.).
## Requirements
| Dependency | Minimum Version | Notes |
|:-----------|:----------------|:------|
| Dart SDK | `>= 3.4.0` | Required. |
| Flutter SDK | `>= 3.22.0` | Required. Telescope needs the Flutter runtime for VM Service extensions. |
| fluttersdk_artisan | `^0.0.2` | Pulled in transitively by telescope; the install command and MCP tools work without prior setup. |
| Magic stack | optional | Enables 6 additional watchers: HTTP facade, models, cache, events, gates, queries. |
Telescope is a debug-only package. The `kDebugMode` gate at the consumer install site is
load-bearing: all release-mode tree-shaking depends on it.
## What gets captured
Out of the box after `telescope:install`, Telescope captures:
- **Logs**: all `package:logging` records via `LogWatcher` (auto-installed).
- **Exceptions**: uncaught errors via `ExceptionWatcher` (opt-in, chain-preserves Sentry/Bugsnag).
- **debugPrint output**: via `DumpWatcher` (opt-in, chain-preserves prior override).
With the Magic stack (`MagicTelescopeIntegration.install()`):
- **HTTP traffic** through the Magic `Http` facade.
- **Model lifecycle**: create, save, delete events on Magic Eloquent models.
- **Cache operations**: hit, miss, put, forget, flush via the `Cache` facade.
- **In-app events** dispatched through `Event.dispatch`.
- **Gate checks**: every `Gate.allows` / `Gate.denies` call with result + user id.
- **DB queries**: SQL, bindings, and execution time via the magic database connector.
## Next steps
- New here? Start with [Installation](installation).
- Already installed? Run the [Quickstart](quickstart).
- Full reference at [fluttersdk.com/telescope](https://fluttersdk.com/telescope).
---
# Installation
# Installation
- [Requirements](#requirements)
- [Option A: one-shot install via artisan](#option-a-one-shot-install)
- [Option B: manual wiring](#option-b-manual-wiring)
- [Wire the Artisan provider](#wire-the-artisan-provider)
- [Verify installation](#verify-installation)
`fluttersdk_telescope` works from a fresh Flutter project. The recommended install path
uses telescope's own bootstrap entry point (`dart run fluttersdk_telescope telescope:install`)
which carries the artisan substrate, so no prior `fluttersdk_artisan` setup is required.
## Requirements
| Dependency | Minimum Version | Notes |
|:-----------|:----------------|:------|
| Dart SDK | `>= 3.4.0` | |
| Flutter SDK | `>= 3.22.0` | VM Service extensions require the Flutter runtime. |
| fluttersdk_artisan | `^0.0.2` | Pulled in transitively by telescope; no manual setup needed for the install path. |
| Magic stack | optional | Enables 6 additional Magic-specific watchers. |
## Option A: one-shot self-bootstrap install (recommended)
Add `fluttersdk_telescope` to your project, then bootstrap via telescope's own CLI entry
point. The standalone binary carries the artisan substrate, so this works from a completely
fresh consumer with no prior `fluttersdk_artisan` wiring:
```bash
flutter pub add fluttersdk_telescope
dart run fluttersdk_telescope telescope:install
```
`flutter pub add` writes the dependency to `pubspec.yaml` and runs `dart pub get` in one
step. The `dart run` line then invokes telescope's standalone CLI to scaffold and patch
the consumer.
The command performs three operations in order:
1. Scaffolds the consumer artisan harness (`bin/dispatcher.dart`, `lib/app/_plugins.g.dart`)
if it is missing. This is a no-op when the harness is already present.
2. Runs `plugin:install fluttersdk_telescope`, which registers `TelescopeArtisanProvider`
in `.artisan/plugins.json` and refreshes the codegen barrel.
3. Patches `lib/main.dart` to call `TelescopePlugin.install()` before `runApp`. When using
the Magic framework, the patch places the call before `Magic.init()` so the Http facade
is wired before MagicTelescopeIntegration runs. The patch is wrapped in a `kDebugMode`
guard automatically.
The command is idempotent. Re-running it when the files are already patched is safe.
### After install: prefer the artisan fast-cli
The artisan scaffold ships a precompiled launcher at `./bin/fsa` (native AOT, ~110ms warm
startup). For everyday telescope work, use it instead of `dart run`:
```bash
./bin/fsa telescope:tail
./bin/fsa telescope:requests
./bin/fsa telescope:clear
```
`dart run fluttersdk_telescope ` and `dart run fluttersdk_artisan ` keep working
as ~3-second cold-start fallbacks; they are useful in CI or before the AOT bundle is built.
## Option B: manual wiring
Use this path when you need fine-grained control over the install, or when the automated
patch cannot locate the correct anchor in `lib/main.dart`.
### 1. Add the dependency
Add `fluttersdk_telescope` to `pubspec.yaml`:
```yaml
dependencies:
fluttersdk_telescope: ^0.0.3
```
Then fetch dependencies:
```bash
dart pub get
```
### 2. Wire TelescopePlugin in lib/main.dart
Install Telescope before `runApp`, wrapped in `kDebugMode`. When using the Magic framework,
place the call before `Magic.init()` so the Http facade is wired before
MagicTelescopeIntegration runs:
```dart
import 'package:flutter/foundation.dart';
import 'package:fluttersdk_telescope/telescope.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
if (kDebugMode) {
// 1. Install Telescope core (LogWatcher auto-installs, VM extensions register).
TelescopePlugin.install();
// 2. Opt-in to exception and debugPrint capture.
TelescopePlugin.registerWatcher(ExceptionWatcher());
TelescopePlugin.registerWatcher(DumpWatcher());
}
// 3. Framework init (e.g. Magic.init()) runs after Telescope so the Http facade
// is wired before MagicTelescopeIntegration tries to wrap it.
await Magic.init(configFactories: [...]);
if (kDebugMode) {
// 4. Magic-specific adapters resolve framework internals from the IoC container;
// they must run after Magic.init().
MagicTelescopeIntegration.install();
}
runApp(MyApp());
}
```
For vanilla Flutter (no Magic stack), omit `Magic.init()` and the
`MagicTelescopeIntegration.install()` call. Wire `DioHttpAdapter` instead if you use Dio:
```dart
if (kDebugMode) {
TelescopePlugin.install();
TelescopePlugin.registerHttpAdapter(DioHttpAdapter(dio));
TelescopePlugin.registerWatcher(ExceptionWatcher());
TelescopePlugin.registerWatcher(DumpWatcher());
}
```
## Wire the Artisan provider
If you used Option A (`telescope:install`), the provider registration is written
automatically: `plugin:install fluttersdk_telescope` adds `FluttersdkTelescopeArtisanProvider`
to `lib/app/_plugins.g.dart`, which the scaffolded `bin/dispatcher.dart` reads via
`plugins.autoDiscoveredProviders()`. No manual edit is required.
If you used Option B (manual wiring), import the provider in your `bin/dispatcher.dart`
yourself:
```dart
import 'package:fluttersdk_artisan/artisan.dart';
import 'package:fluttersdk_telescope/cli.dart' show FluttersdkTelescopeArtisanProvider;
Future main(List args) async {
exit(await runArtisan(
args,
baseProviders: [
FluttersdkTelescopeArtisanProvider(),
// ...other providers (DuskArtisanProvider, etc.)
],
));
}
```
## Verify installation
Start your Flutter app and confirm Telescope is active by tailing the log buffer:
```bash
./bin/fsa telescope:tail
```
Expected output on a running app shows the most recent log records from the ring buffer.
If you see `Error: no running app found`, the artisan state file is missing: run
`./bin/fsa start` first, then retry `telescope:tail`.
To confirm all 6 CLI commands are registered, list the artisan command catalog:
```bash
./bin/fsa list
```
The output includes the `telescope:` namespace with `telescope:install`, `telescope:tail`,
`telescope:requests`, `telescope:queries`, `telescope:caches`, and `telescope:clear`. If
the fast-cli is missing for any reason, the same commands run via `dart run fluttersdk_telescope list`
or `dart run fluttersdk_artisan list`.
---
# Quickstart
# Quickstart
A 3-step walkthrough taking a fresh Flutter project from zero to a working Telescope setup
with runtime buffers populated and the first MCP tool call returning live data.
Prerequisites: Flutter SDK 3.22+, Dart SDK 3.4+, a Flutter project (any vanilla
`flutter create` output works). `fluttersdk_artisan` is pulled in transitively by
telescope; you do not need to install it separately. The `telescope:install` command
scaffolds `bin/dispatcher.dart` for you on first run.
---
### 1. Install Telescope
Add the dependency, then run the one-shot install through telescope's own bootstrap entry
point. The install command scaffolds the artisan harness if needed, registers the plugin,
and patches `lib/main.dart` with the `kDebugMode`-guarded install call:
```bash
flutter pub add fluttersdk_telescope
dart run fluttersdk_telescope telescope:install
```
The command outputs a summary of every file it touched. For a fresh project the summary
looks like:
```
[plugin:install] registered TelescopeArtisanProvider
[main.dart] injected TelescopePlugin.install() before framework init
[main.dart] injected MagicTelescopeIntegration.install() after Magic.init() (when using Magic framework)
telescope:install done
```
When using the Magic framework, `MagicTelescopeIntegration.install()` is also injected after
`Magic.init()` so all 9 watchers activate automatically. On a vanilla Flutter app only
the core watchers (`LogWatcher`, plus any you opt into manually) are wired.
Verify the provider registered correctly. From now on, the artisan fast-cli at `./bin/fsa`
(native AOT, ~110ms warm) is the recommended entry point for every subsequent command:
```bash
./bin/fsa list
```
You should see the `telescope:` namespace with 6 commands: `telescope:install`,
`telescope:tail`, `telescope:requests`, `telescope:queries`, `telescope:caches`,
`telescope:clear`. The same surface is also reachable via the ~3s cold-start fallbacks
`dart run fluttersdk_telescope list` and `dart run fluttersdk_artisan list`.
---
### 2. Start the app and generate traffic
Boot the Flutter app via artisan so the VM Service URI is recorded to the state file.
Artisan resolves the running instance from this file for every subsequent CLI and MCP call.
```bash
./bin/fsa start --device=chrome
```
The command scrapes the VM Service URI from `flutter run` output, normalizes it to a
WebSocket address, and writes the full process state (PID, URI, device, project root)
to `~/.artisan/state.json`.
With the app running, navigate the UI to generate HTTP traffic, logs, and any exceptions.
When using the Magic framework, every HTTP call through the `Http` facade, every model save,
and every cache read goes straight into the Telescope ring buffers. On a vanilla Flutter app,
`package:logging` output and `debugPrint` calls fill the log and dump buffers.
You can also exercise the watchers programmatically from the app:
```dart
// Generates a LogRecordEntry in the console buffer.
Logger('MyFeature').info('quickstart test log');
// Generates a DumpRecord in the dump buffer.
debugPrint('quickstart test dump');
```
---
### 3. Query the buffers from Claude Code (MCP)
Start the artisan MCP server. When your project has `.mcp.json` wired with
`dart run fluttersdk_artisan:mcp` as the entry point (written by
`fluttersdk_artisan mcp:install`), Claude Code launches the server automatically on attach.
To start it manually:
```bash
./bin/fsa mcp:serve
```
Inside a Claude Code session, the 9 `telescope_*` tools are now available. A typical
inspection flow:
```
[agent] telescope_tail {}
```
Returns the most recent log records from the ring buffer, newest first. Filter by level:
```
[agent] telescope_tail { "level": "warning", "limit": 20 }
```
Inspect HTTP traffic:
```
[agent] telescope_requests {}
```
Returns every captured HTTP request with method, URL, status code, duration in ms, request
headers, and a body snippet. Useful for verifying what the app actually sent to the API.
Check for uncaught exceptions:
```
[agent] telescope_exceptions {}
```
Review DB queries with timings:
```
[agent] telescope_queries {}
```
Clear all buffers before the next test scenario:
```
[agent] telescope_clear {}
```
All 9 tools follow the same pattern: they query the `ext.telescope.*` VM Service
extensions registered by `registerAllTelescopeExtensions()` at startup. No source changes
are needed between queries; the buffers update passively as the app runs.
---
## What's next?
- Read the full [Installation](installation) doc for manual wiring options and the
`DioHttpAdapter` setup for vanilla Flutter.
- Browse the watcher catalog at [fluttersdk.com/telescope/watchers](https://fluttersdk.com/telescope/watchers)
to learn which watchers are opt-in and how chain-preservation works with Sentry and Bugsnag.
- See the full MCP tool input schemas at [fluttersdk.com/telescope/mcp](https://fluttersdk.com/telescope/mcp).
---
All three steps above use only the `fluttersdk_telescope ^0.0.3` package and its
`fluttersdk_artisan` dependency. No additional packages are required for the core
watcher surface. Magic-specific watchers activate automatically when the Magic stack
is present and `MagicTelescopeIntegration.install()` is called.
---
# Watchers
# Watchers
Telescope ships 9 watcher units across three categories. Each unit implements one of two contracts:
`TelescopeWatcher` (for event-driven and hook-based capture) or `TelescopeHttpAdapter` (for HTTP
traffic capture). All units feed the matching ring buffer inside `TelescopeStore` and expose their
data via a VM Service extension method.
## kDebugMode gating
The consumer is responsible for gating the entire telescope install behind `kDebugMode`:
```dart
if (kDebugMode) {
TelescopePlugin.install();
// register additional watchers here
}
```
This single gate tree-shakes the entire subsystem in release builds (dart2js for web, dart2native
for mobile/desktop AOT). Individual watchers do not need their own gate except `DumpWatcher`, which
has an additional internal guard (see below).
## Chain-preserve pattern
Any watcher that overrides a global Dart/Flutter hook saves the previous handler before replacing it
and calls it inside the new handler body. On `uninstall()`, the previous handler is restored
symmetrically. This is the Sentry/Bugsnag coexistence contract: telescope captures the record AND
forwards to whatever was registered before it. The numbered step comments in `ExceptionWatcher` are
the canonical reference for this pattern:
```dart
// 1. Chain-preserve FlutterError.onError (sync framework errors).
_previousOnError = FlutterError.onError;
FlutterError.onError = (details) {
TelescopeStore.recordException(...);
_previousOnError?.call(details);
};
// 2. Chain-preserve PlatformDispatcher.instance.onError (async + isolate errors).
_previousPlatformOnError = PlatformDispatcher.instance.onError;
PlatformDispatcher.instance.onError = (error, stack) {
TelescopeStore.recordException(...);
return _previousPlatformOnError?.call(error, stack) ?? true;
};
```
---
## Framework-agnostic watchers
These three units are part of the `fluttersdk_telescope` core package. They have no dependency on
Magic or any other application framework.
### LogWatcher
| Field | Value |
|---|---|
| Contract | `TelescopeWatcher` |
| Name | `log` |
| Auto-install | Yes (auto-registered by `TelescopePlugin.install()`) |
| Ring buffer | `TelescopeStore._logs` |
| VM extension | `ext.telescope.console` |
| Opt-out | Call `TelescopePlugin.install()` then remove it by not calling `LogWatcher().install()` directly; or skip the default auto-install path and register manually |
Subscribes to `Logger.root.onRecord` from `package:logging`. Every log record that flows through
the root logger (at any level) is converted to a `LogRecordEntry` and pushed to the store.
`hierarchicalLoggingEnabled` is set to `true` during install so named child loggers (`Logger('http')`,
`Logger('auth')`, etc.) funnel through root. Install is idempotent: a second call when the
subscription is already live is a no-op.
If the app does not use `package:logging`, the watcher is dormant (no records flow through
`Logger.root`). It is still registered because adding `package:logging` later immediately benefits
from capture without any telescope config change.
Registration (auto-installed, shown for completeness):
```dart
TelescopePlugin.install(); // LogWatcher registered automatically
```
Manual opt-out (skip auto-registration) is not exposed in the V1 API; file a feature request if
selective watcher exclusion is needed.
---
### ExceptionWatcher
| Field | Value |
|---|---|
| Contract | `TelescopeWatcher` |
| Name | `exception` |
| Auto-install | No (opt-in via `TelescopePlugin.registerWatcher(ExceptionWatcher())` after install) |
| Ring buffer | `TelescopeStore._exceptions` |
| VM extension | `ext.telescope.exceptions` |
| Opt-out | Simply do not register; absent registration means no error capture. To coexist with Sentry / Bugsnag, register telescope FIRST so the chain-preserve wraps the next handler. |
Hooks both `FlutterError.onError` (synchronous framework and widget errors) and
`PlatformDispatcher.instance.onError` (asynchronous errors, isolate errors, plugin-originated
errors). Both hooks follow the chain-preserve pattern.
The `PlatformDispatcher.onError` handler returns `_previousPlatformOnError?.call(...) ?? true`.
When a previously-registered handler (e.g. Sentry) returns `false` to signal a fatal error and let
the native crash reporter take over, telescope preserves that semantics. When no previous handler
exists, the default `true` (handled) matches pre-install behavior.
On `uninstall()`, both handlers are restored to exactly the values they held before `install()` was
called. Calling `install()` while already installed is a no-op.
Registration (opt-in; add after `TelescopePlugin.install()`):
```dart
TelescopePlugin.install();
TelescopePlugin.registerWatcher(ExceptionWatcher());
```
Coexistence with Sentry (the chain-preserve contract means order matters; install Sentry first):
```dart
SentryFlutter.init((_) {}); // registers FlutterError.onError + PlatformDispatcher.onError
if (kDebugMode) {
TelescopePlugin.install(); // wraps Sentry's handlers; Sentry still fires
}
```
---
### DumpWatcher
| Field | Value |
|---|---|
| Contract | `TelescopeWatcher` |
| Name | `dump` |
| Auto-install | No (opt-in via `TelescopePlugin.registerWatcher(DumpWatcher())`) |
| Ring buffer | `TelescopeStore._dumps` |
| VM extension | `ext.telescope.dumps` |
| Opt-out | Do not register it; it is never auto-installed |
Captures all `debugPrint` output by replacing the global `debugPrint` callback with an interceptor.
The interceptor records a `DumpRecord` AND calls the previous `debugPrint` value (chain-preserve).
On `uninstall()`, the previous callback is restored exactly.
Internal `kDebugMode` gate: `install()` is a no-op in release builds unless `allowInRelease` is set
to `true` before calling `install()`. This gate is load-bearing: in release builds `debugPrint`
itself is a no-op, so capture would produce empty records; the gate makes the intent explicit and
ensures AOT tree-shaking eliminates the interceptor.
Registration:
```dart
if (kDebugMode) {
TelescopePlugin.install();
TelescopePlugin.registerWatcher(DumpWatcher()); // opt-in
}
```
Opt-out: simply omit the `registerWatcher(DumpWatcher())` call.
---
## HTTP adapters
HTTP adapters implement `TelescopeHttpAdapter` (three methods: `name`, `install`, `uninstall`, plus
the optional `pendingCount` getter). They feed `TelescopeStore.recordHttp` and their in-flight
counts sum into `TelescopeStore.pendingHttpCount` (consumed by Dusk's `wait_for_network_idle`).
### DioHttpAdapter
| Field | Value |
|---|---|
| Contract | `TelescopeHttpAdapter` |
| Name | `dio` |
| Auto-install | No (opt-in via `TelescopePlugin.registerHttpAdapter(DioHttpAdapter())`) |
| Ring buffer | `TelescopeStore._http` |
| VM extension | `ext.telescope.requests` |
| Opt-out | Do not register it |
Vanilla Dio adapter for Flutter apps that use raw Dio instances (not the Magic Http facade). V1
ships as a stub: the actual Dio interceptor subclass requires `package:dio`, which is not in
telescope's own pubspec to keep the core package HTTP-library-agnostic. The consumer adds `package:dio`
to their own pubspec and wires the adapter by calling the static helper:
```dart
// consumer pubspec: dio: ^5.x
import 'package:fluttersdk_telescope/telescope.dart';
// After constructing your Dio instance:
dio.interceptors.add(_MyTelescopeInterceptor());
class _MyTelescopeInterceptor extends Interceptor {
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
DioHttpAdapter.recordRequest(
url: response.requestOptions.uri.toString(),
method: response.requestOptions.method,
statusCode: response.statusCode ?? 0,
durationMs: ...,
);
handler.next(response);
}
}
// Register so the adapter shows up in the store's adapter list:
TelescopePlugin.registerHttpAdapter(DioHttpAdapter());
```
V1.x will move the Dio-coupled glue into a `fluttersdk_telescope_dio` sub-package so the core stays
HTTP-library-agnostic.
---
### MagicHttpFacadeAdapter
| Field | Value |
|---|---|
| Contract | `TelescopeHttpAdapter` |
| Name | `magic_http_facade` |
| Auto-install | Yes (registered by `MagicTelescopeIntegration.install()`) |
| Ring buffer | `TelescopeStore._http` |
| VM extension | `ext.telescope.requests` |
| Opt-out | Call `TelescopePlugin.install()` without calling `MagicTelescopeIntegration.install()` |
Captures every request flowing through Magic's `network` driver by registering a
`MagicNetworkInterceptor` on the driver. The interceptor pairs `onRequest` (start stopwatch) with
`onResponse`/`onError` (stop stopwatch, emit `HttpRequestRecord`) via a FIFO in-flight list.
Attribution is heuristic (`attributedHeuristically: true`) because the `MagicNetworkInterceptor`
contract does not carry a correlation handle across `onRequest`/`onResponse` callbacks; best-effort
matching by call order.
`pendingCount` returns the current length of the in-flight FIFO, surfaced into
`TelescopeStore.pendingHttpCount` for Dusk's network-idle detection.
Registration (via `MagicTelescopeIntegration`, the only documented entry point):
```dart
if (kDebugMode) {
TelescopePlugin.install();
MagicTelescopeIntegration.install(); // registers MagicHttpFacadeAdapter + 5 watchers
}
```
`install()` is a no-op if `Magic.bound('network')` returns false (called too early, before
`Magic.init()` completes). Call `MagicTelescopeIntegration.install()` after `Magic.init()`.
---
## Magic-stack watchers
These five watchers are shipped inside the `magic` package via `MagicTelescopeIntegration`. They
are not part of the `fluttersdk_telescope` core. All five are registered by a single
`MagicTelescopeIntegration.install()` call.
### MagicModelWatcher
| Field | Value |
|---|---|
| Contract | `TelescopeWatcher` |
| Name | `magic_model` |
| Auto-install | Yes (via `MagicTelescopeIntegration.install()`) |
| Ring buffer | `TelescopeStore._models` |
| VM extension | `ext.telescope.requests` (via models key; MCP tool: `telescope_models`) |
| Opt-out | Do not call `MagicTelescopeIntegration.install()` |
Subscribes to `ModelCreated`, `ModelSaved`, and `ModelDeleted` events dispatched by Magic's
`EventDispatcher`. Each event is converted to a `MagicModelRecord` carrying: `modelClass` (runtime
type name), `event` tag (`created`/`saved`/`deleted`), `modelKey` (stringified primary key), `time`,
and `attributes` (snapshot of `model.attributes` at capture time).
Registration:
```dart
if (kDebugMode) {
TelescopePlugin.install();
MagicTelescopeIntegration.install(); // MagicModelWatcher is included
}
```
`uninstall()` is a no-op: `EventDispatcher` has no per-listener removal API. Tests that need a
clean dispatcher call `EventDispatcher.instance.clear()` in their `setUp`.
---
### MagicCacheWatcher
| Field | Value |
|---|---|
| Contract | `TelescopeWatcher` |
| Name | `magic_cache` |
| Auto-install | Yes (via `MagicTelescopeIntegration.install()`) |
| Ring buffer | `TelescopeStore._caches` |
| VM extension | `ext.telescope.caches` |
| Opt-out | Do not call `MagicTelescopeIntegration.install()` |
Subscribes to five cache lifecycle events emitted by Magic's `CacheManager`: `CacheHit`, `CacheMiss`,
`CachePut`, `CacheForget`, and `CacheFlush`. Each event is converted to a `MagicCacheRecord` carrying
`operation` (`hit`/`miss`/`put`/`forget`/`flush`), `key` (or `*` for flush), `time`, and `ttl`
(present only for `CachePut`).
Registration:
```dart
if (kDebugMode) {
TelescopePlugin.install();
MagicTelescopeIntegration.install(); // MagicCacheWatcher is included
}
```
`uninstall()` is a no-op (same `EventDispatcher` constraint as `MagicModelWatcher`).
---
### MagicEventWatcher
| Field | Value |
|---|---|
| Contract | `TelescopeWatcher` |
| Name | `magic_event` |
| Auto-install | Yes (via `MagicTelescopeIntegration.install()`) |
| Ring buffer | `TelescopeStore._events` |
| VM extension | `ext.telescope.events` |
| Opt-out | Do not call `MagicTelescopeIntegration.install()` |
Subscribes to a curated set of Magic app-lifecycle events: `AuthLogin`, `AuthLogout`, `AuthFailed`,
`AuthRestored` (auth lifecycle), `DatabaseConnected` (database connection), and `GateAbilityDefined`,
`GateBeforeRegistered` (gate definition). Model lifecycle events are excluded (owned by
`MagicModelWatcher`) and gate-result events are excluded (owned by `MagicGateWatcher`) to keep each
record on a single channel.
Current payload is the empty map for every event type. Per-event field extraction is deferred to a
follow-up release while the `EventRecord` wire shape stabilises.
Registration:
```dart
if (kDebugMode) {
TelescopePlugin.install();
MagicTelescopeIntegration.install(); // MagicEventWatcher is included
}
```
`uninstall()` is a no-op (same `EventDispatcher` constraint).
---
### MagicGateWatcher
| Field | Value |
|---|---|
| Contract | `TelescopeWatcher` |
| Name | `magic_gate` |
| Auto-install | Yes (via `MagicTelescopeIntegration.install()`) |
| Ring buffer | `TelescopeStore._gates` |
| VM extension | `ext.telescope.gates` |
| Opt-out | Do not call `MagicTelescopeIntegration.install()` |
Subscribes to `GateAccessChecked`, which covers both `Gate.allows` and `Gate.denies` outcomes via
its `allowed: bool` field. `GateAccessDenied` is intentionally not subscribed to avoid
double-recording every denial.
Each event is converted to a `GateRecord` with two shape coercions:
- `arguments` (single dynamic on the event) is wrapped into `List