@uptimizr/mcp 1.1.1 → 1.2.0

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/AGENTS.md CHANGED
@@ -5,13 +5,18 @@
5
5
 
6
6
  ## What this package is
7
7
 
8
- A **read-only** Model Context Protocol (MCP) server over an Uptimizr collector's query API. It
9
- lets an agent query a consumer's **own** 3D analytics in natural language. Each tool maps to one
10
- documented collector read endpoint; the server is a thin wrapper that holds no business logic and
11
- performs `GET` requests only (ADR 0005, ADR 0017).
8
+ A Model Context Protocol (MCP) server over an Uptimizr collector's API. It lets an agent query a
9
+ consumer's **own** 3D analytics in natural language. Each analytics tool maps to one documented
10
+ collector read endpoint; the server is a thin wrapper that holds no business logic (ADR 0005,
11
+ ADR 0017).
12
12
 
13
- It connects **only to the collector's HTTP query API** (never to the database directly), so the
14
- collector remains the single gateway that enforces auth, per-project scoping, and privacy.
13
+ **Events are read-only.** Nothing here can write, alter or delete an analytics event, and there is
14
+ no ingestion tool. The one writable surface is **project metadata** — annotations, the glossary,
15
+ saved analyses and pinned dashboard panels — and it is gated: those tools are registered only when
16
+ the configured key holds the `annotate` capability (ADR 0051 §5/§7/§9). See "Metadata tools" below.
17
+
18
+ It connects **only to the collector's HTTP API** (never to the database directly), so the collector
19
+ remains the single gateway that enforces auth, per-project scoping, and privacy.
15
20
 
16
21
  ## Run
17
22
 
@@ -21,19 +26,42 @@ UPTIMIZR_COLLECTOR_URL="https://collect.example.com" UPTIMIZR_API_KEY="utk_…"
21
26
 
22
27
  ## Required key capability
23
28
 
24
- `UPTIMIZR_API_KEY` must hold the **`query`** capability — and only that. Mint one with
29
+ `UPTIMIZR_API_KEY` must hold the **`query`** capability. Mint one with
25
30
  `uptimizr new-key <projectId> --capabilities query --label "mcp-agent"` (`query` is the default).
26
- `GET /api/v1/whoami` reports what a key holds, so a client can check before it registers tools; an
27
- authenticated key missing a capability is refused with `403`, not `401`.
31
+ `GET /api/v1/whoami` reports what a key holds — the server calls it once at start-up for exactly
32
+ this reason — and an authenticated key missing a capability is refused with `403`, not `401`.
33
+
34
+ Add **`annotate`** only if you want the agent to leave notes, definitions, saved analyses and
35
+ pinned dashboard panels behind (`--capabilities query,annotate`). Without it the server starts
36
+ read-only and never offers those tools; with it, every write is bounded at the collector's edge and
37
+ recorded in the audit log.
38
+ **`query:raw` is optional, and off by default.** Every tool in the default catalog is an aggregate
39
+ read: there is no raw per-session, replay or live-follow tool, so a plain `query` key is all most
40
+ deployments should grant. A key that _does_ hold `query:raw` additionally gets the
41
+ **`session_narrative`** tool — an ordered, bounded account of what one session did — but only when
42
+ the server was told about the capability, and only on a collector running with
43
+ `ENABLE_RAW_SESSION_RETENTION`. `createMcpServer(client, { capabilities })` takes the set that
44
+ `GET /api/v1/whoami` reports; the `uptimizr-mcp` binary looks it up at start-up (best effort — if
45
+ the call fails it serves the `query` surface). Omitting the option serves the `query` surface too,
46
+ so a tool that would always answer `403` is never advertised. `annotate` (metadata writes) and
47
+ `ingest` are never used here. Prefer a dedicated, labelled key with its own `--rate-limit-max` /
48
+ `--rate-limit-window-ms` budget: the collector's agent audit log records activity per key id, which
49
+ is what makes an agent's reads reviewable.
50
+
51
+ ## Transports
52
+
53
+ `createMcpServer()` is transport-agnostic, and two transports serve the identical catalog,
54
+ resources and prompts:
28
55
 
29
- **`query:raw` is deliberately not needed.** Every tool here is an aggregate read; the server exposes
30
- no raw per-session, replay or live-follow tool, so granting its key `query:raw` widens the blast
31
- radius for nothing. `annotate` (metadata writes) and `ingest` are likewise never used. Prefer a
32
- dedicated, labelled key with its own `--rate-limit-max` / `--rate-limit-window-ms` budget: the
33
- collector's agent audit log records activity per key id, which is what makes an agent's reads
34
- reviewable.
56
+ - **stdio** — what this package's `bin` runs (`npx @uptimizr/mcp`), launched by a desktop client.
57
+ - **Collector-hosted Streamable HTTP** — a collector started with `COLLECTOR_MCP_HTTP=1` serves the
58
+ same server at `POST`/`GET`/`DELETE` `/mcp` (ADR 0051 §7). A remote client connects with a URL
59
+ plus `x-api-key` or `Authorization: Bearer <key>` — no local install — and every request is
60
+ re-authenticated, capped by `COLLECTOR_MCP_MAX_SESSIONS` and charged to the key's rate limit.
61
+ Tool calls are audited with `surface: "mcp-http"`; this package's own reads are plain HTTP reads
62
+ and are audited as `http`.
35
63
 
36
- ## Tools (read-only)
64
+ ## Analytics tools (read-only)
37
65
 
38
66
  <!-- generated:registry-tool-names:start — generated by `pnpm gen:docs`; edit the metric registry, not this table -->
39
67
 
@@ -43,22 +71,31 @@ reviewable.
43
71
  `position_heatmap`, `session_trajectory`, `aggregate_paths`, `scene_coverage`, `camera_distance`,
44
72
  `click_rays`, `flow_links`, `top_meshes`, `mesh_sources`, `mesh_trend`, `mesh_dwell`,
45
73
  `mesh_blind_spots`, `mesh_interaction_kinds`, `mesh_reachability`, `dead_clicks`, `rage_clicks`,
46
- `hover_dwell`, `interaction_sources`, `top_input_actions`, `camera_gestures`, `navigation_stats`,
47
- `backtrack_ratio`, `perf_summary`, `render_scale_truth`, `perf_distribution`, `fps_histogram`,
48
- `frame_time_percentiles`, `jank_rate`, `perf_churn`, `perf_by_device`, `perf_by_scene`,
49
- `perf_heatmap`, `compile_stalls`, `resource_summary`, `resource_percentiles`, `stability_counts`,
50
- `graphics_diagnostics`, `error_heatmap`, `rendering_technology`, `capability_changes`,
51
- `xr_rotation`, `xr_sources`, `xr_abandonment`, `xr_locomotion`, `xr_tracking_quality`,
52
- `boundary_heatmap`, `boundary_heatmap_stats`, `xr_boundary_contacts`,
53
- `ar_placement_time_to_place`, `ar_placement_attempts`, `ar_placement_surfaces`, `funnel`,
54
- `scene_retention`, `load_bounce_funnel`, `variant_leaderboard`
74
+ `hover_dwell`, `interaction_sources`, `top_input_actions`, `custom_event_vocabulary`,
75
+ `camera_gestures`, `navigation_stats`, `backtrack_ratio`, `perf_summary`, `render_scale_truth`,
76
+ `perf_distribution`, `fps_histogram`, `frame_time_percentiles`, `jank_rate`, `perf_churn`,
77
+ `perf_by_device`, `perf_by_scene`, `perf_heatmap`, `compile_stalls`, `resource_summary`,
78
+ `resource_percentiles`, `stability_counts`, `graphics_diagnostics`, `error_heatmap`,
79
+ `rendering_technology`, `capability_changes`, `xr_rotation`, `xr_sources`, `xr_abandonment`,
80
+ `xr_locomotion`, `xr_tracking_quality`, `boundary_heatmap`, `boundary_heatmap_stats`,
81
+ `xr_boundary_contacts`, `ar_placement_time_to_place`, `ar_placement_attempts`,
82
+ `ar_placement_surfaces`, `funnel`, `scene_retention`, `load_bounce_funnel`, `variant_leaderboard`,
83
+ `insight_baseline`, `insight_movers`, `insight_anomalies`, `insight_significance`,
84
+ `insight_scene_health`
85
+
86
+ Only on a key holding `query:raw`, and only when the collector runs with
87
+ `ENABLE_RAW_SESSION_RETENTION` (ADR 0003):
88
+
89
+ `session_narrative`
55
90
 
56
91
  <!-- generated:registry-tool-names:end -->
57
92
 
58
- **69 tools, generated** from the `@uptimizr/metrics` semantic metric registry (ADR 0051 §1) — one per
59
- metric the collector serves on a read endpoint. Names are the registry ids; the full table lives in
60
- [README.md](./README.md), and `uptimizr://capabilities` enumerates them at runtime with each tool's
61
- grain, column units and caveats.
93
+ **75 tools, generated** from the `@uptimizr/metrics` semantic metric registry (ADR 0051 §1) — one
94
+ per metric a plain `query` key may read, plus `session_narrative` above for a `query:raw` key. With
95
+ the `query` DSL tool and `list_subscriptions` below, a `query` key sees **77** tools in all. Names
96
+ are the registry ids; the full table lives in [README.md](./README.md), and
97
+ `uptimizr://capabilities` enumerates them at runtime with each tool's grain, column units and
98
+ caveats.
62
99
 
63
100
  Orientation: `list_sessions`, `list_scenes`, `session_meta`, `scene_representation`, `timeseries`,
64
101
  `event_counts`. Attention: the `*_heatmap` family, `mesh_dwell`, `mesh_blind_spots`, `hover_dwell`.
@@ -73,21 +110,66 @@ Navigation: `aggregate_paths`, `session_trajectory`, `scene_coverage`, `navigati
73
110
  `variant_leaderboard`.
74
111
 
75
112
  Most accept `since`/`until` (epoch ms) plus endpoint-specific filters (`scene`, `session`, `source`,
76
- `bins`, `cellSize`, `limit`, `cameraMode`, `region`, …). Every tool declares an `outputSchema` and
77
- returns `structuredContent` (`{ rows }`) alongside the JSON text — read the schema instead of
78
- guessing the row shape, and read the tool description for the metric's caveats before trusting a
79
- small sample.
113
+ `bins`, `cellSize`, `limit`, `cameraMode`, `region`, …). Every tool declares an `outputSchema`
114
+ covering all three `format` envelopes and returns the one you asked for as `structuredContent`
115
+ alongside the JSON text — read the schema instead of guessing the row shape, and read the tool
116
+ description for the metric's caveats before trusting a small sample.
117
+
118
+ ## The `query` tool (the query DSL)
119
+
120
+ One tool is **not** per-metric: `query` (ADR 0051 §3). Its input is the query DSL, so it runs any
121
+ metric in the catalog above with any filter that metric declares:
122
+
123
+ ```jsonc
124
+ {
125
+ "v": 1,
126
+ "metric": "mesh_sources",
127
+ "range": { "since": 1757000000000, "until": 1757600000000 },
128
+ "filters": { "scene": "lobby", "cameraMode": "first-person" },
129
+ "limit": 20,
130
+ "format": "summary",
131
+ }
132
+ ```
133
+
134
+ - **`range` is required** (both ends, epoch ms) — there is no unbounded query.
135
+ - **`format` defaults to `table`** here rather than `full`.
136
+ - The grammar is closed: metrics, dimensions and filters are exactly the vocabulary in the
137
+ `uptimizr://capabilities` resource. Naming something outside it is a `400` whose
138
+ `issues[].accepted` lists what would have worked — read it instead of guessing again.
139
+ - `dimensions` may be any subset a metric declares **when** its measure is a portable count —
140
+ event counts, mesh and interaction tallies, input actions, camera gestures. A spatial heatmap or a
141
+ percentile is computed at one fixed grain and refuses anything else, naming the grain it supports.
142
+ - **`compare`** — another `{ range }` or `{ segment }`; the result comes back joined on the
143
+ dimension key as `{ current, previous, delta, deltaPct }`, with a significance test where the
144
+ measure is a count and both windows clear the metric's minimum. Never subtract two results by hand.
145
+ - **`explain: true`** — the compiled plan instead of the rows: the tier, the SQL with its parameters
146
+ left unbound, `params` by name and type (never value), `rowsScanned`, and `warnings` (a capture
147
+ channel that produced nothing, a sample below the metric's minimum, a truncated result).
148
+ - **`drillQuery`** — every row of a `summary` carries the whole query narrowed to that row, ready to
149
+ send straight back.
150
+ - `order` takes a measure column, and only where the result is a ranked list.
151
+
152
+ Its `structuredContent` is `{ result }` rather than `{ rows }`, because what comes back depends on
153
+ the `format` asked for.
154
+
155
+ ## Non-metric reads
156
+
157
+ Alongside the generated per-metric tools, the server registers `list_subscriptions` (ADR 0051
158
+ §6): the project's standing conditional subscriptions, what each watches for, and how each last
159
+ went. It takes no arguments. It is read-only like everything else here — creating or deleting a
160
+ subscription needs the `annotate` capability and is done over plain HTTP, not through MCP.
80
161
 
81
162
  ## Result formats (`format`)
82
163
 
83
164
  Every **aggregate** tool takes a `format` argument choosing the envelope its rows arrive in
84
- (ADR 0051 §2). It filters nothing.
165
+ (ADR 0051 §2). It filters nothing. **Omit it and you get `table`** — the tools' own default,
166
+ sent explicitly, so the HTTP endpoints still default to `full` for every other client.
85
167
 
86
- | `format` | Returns | Use it when |
87
- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
88
- | `summary` | A bounded digest — `ranked` top rows, a `series` trend, merged spatial `clusters`, or a single `record` — with shares, a sample size, the metric's `caveats` and a templated `reading` sentence. | **The default choice for an agent.** Always, unless you specifically need the rows. |
89
- | `table` | `{ meta, rows }`: the same rows plus the metric, range, applied filters, sample size, row count, a truncation flag and the registry's limits. | You need every row _and_ the context to judge how far to trust it. |
90
- | `full` | The bare rows, unchanged. Today's default. | You are post-processing the rows yourself and already know the sample is adequate. |
168
+ | `format` | Returns | Use it when |
169
+ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
170
+ | `summary` | A bounded digest — `ranked` top rows, a `series` trend, merged spatial `clusters`, or a single `record` — with shares, a sample size, the metric's `caveats` and a templated `reading` sentence. | **Ask for it.** Whenever you do not need every row — always, for a heatmap or a leaderboard. |
171
+ | `table` | `{ meta, rows }`: the same rows plus the metric, range, applied filters, sample size, row count, a truncation flag and the registry's limits. | **What you get if you name no format.** Every row _and_ the context to judge it. |
172
+ | `full` | The bare rows, unchanged, with no envelope at all. | You are post-processing the rows yourself and already know the sample is adequate. |
91
173
 
92
174
  - `summary` is capped at the metric's `limits.maxSummaryRows`, so a 500-bin heatmap costs the same
93
175
  as a 5-bin one. That is the whole point: a heatmap, voxel cloud or long leaderboard returned as
@@ -97,30 +179,108 @@ Every **aggregate** tool takes a `format` argument choosing the envelope its row
97
179
  - Shares appear only where the measure can honestly be summed. A metric in FPS or a ratio reports
98
180
  `total: null` and no shares — do not compute a percentage from those rows.
99
181
  - Cluster coordinates are **grid indices**: multiply by the effective `cellSize` for world space.
182
+ When the scene has a registered proxy and named regions, each cluster of a world-space heatmap is
183
+ also **labelled** — `region` (the smallest containing region), `regions[]` (all of them),
184
+ `nearestMesh`, and `distance` in world units (`0` when the mesh box contains the hotspot) — and
185
+ `drill.region` becomes that region **id**, ready to pass straight back as the `region` argument.
186
+ Say the label, not the coordinate. A `null` means the scene registered nothing that could answer
187
+ and the `caveats` name which; inventing a landmark instead is a hallucination.
100
188
  - `session_meta` and `scene_representation` are single stored records, not aggregations, and take no
101
189
  `format`. Raw `/api/v1/sessions/:id/events` has an unrelated `format=json|ndjson`, and this server
102
190
  exposes no tool for it.
103
- - The catalog's default is still `full` — pass `format` explicitly.
191
+ - `table` is only self-_describing_, not bounded: its `meta` costs a fixed ~200 characters and the
192
+ rows are still all of them. When the answer could be large, ask for `summary`.
193
+
194
+ ## Metadata tools (`annotate` only)
195
+
196
+ Registered **only** when `GET /api/v1/whoami` reports the `annotate` capability, so a read-only key
197
+ yields a read-only server. They write project metadata and nothing else — no event is touched.
198
+
199
+ | Tool | What it does |
200
+ | ------------------ | ---------------------------------------------------------------------------------------------------------- |
201
+ | `annotate` | Pin a note to the project, a scene, a mesh, a region, a metric or a period of time (`targetKind`, `text`). |
202
+ | `define_term` | Record what a name means in this project (`term`, `meaning`). Idempotent — redefining replaces. |
203
+ | `save_analysis` | Store a titled question plus its conclusion (`title`, `query`, `conclusion?`). |
204
+ | `list_annotations` | Read the notes already left. Call it **before** explaining a spike someone may already have explained. |
205
+ | `list_glossary` | Read the project's vocabulary before interpreting mesh names, scene ids or custom events. |
206
+ | `list_analyses` | Read questions this project has asked before, and what they concluded. |
207
+ | `pin_panel` | Keep a question on the project's dashboard (`title`, `query`, `chart`, `encoding?`, `span?`, `note?`). |
208
+ | `unpin_panel` | Remove one pinned panel by its `id`. It goes for **everyone** on the project, not just you. |
209
+ | `list_panels` | Read the panels already pinned, oldest first. Call it **before** pinning, so you extend the grid. |
210
+
211
+ Bounds are enforced by the collector: `text` ≤ 2 000 characters, `meaning` ≤ 500, `title` ≤ 120,
212
+ `conclusion` ≤ 4 000, a panel `note` ≤ 500, and per project at most 500 annotations, 200 terms,
213
+ 200 analyses and 50 pinned panels (a write past a cap answers `409`). Stored rows record that an
214
+ **agent** wrote them; the collector decides that from the calling client, never from the payload.
215
+
216
+ `pin_panel` stores a **spec**, not code: a metric id, a chart name and some column names, which the
217
+ dashboard draws with panel components it already ships. Pass the same `query` document the `query`
218
+ tool takes, with `range` set to `"inherit"` so the panel follows the dashboard's own time filter
219
+ instead of freezing the window you asked in, and a `chart` the metric's grain supports —
220
+ `line`/`area` need a time-bucketed metric, `heatmap2d` a binned one, `world3d` a voxelised one,
221
+ `stat` a single-record one, `bar` a ranking, `table` anything. A spec that does not fit is a `400`
222
+ naming the charts that would have worked.
223
+
224
+ Use them at the end of an investigation, not for scratch state: a note that says what was concluded
225
+ and why is worth keeping, a note per query is noise.
104
226
 
105
227
  ## Resources and prompts
106
228
 
229
+ - `uptimizr://context` (`application/json`) — the **live project context document**
230
+ (`GET /api/v1/context`, ADR 0051 §5). **Read this first, before capabilities.** It describes the
231
+ project in front of you rather than the API: the scene ids with their labels and named region ids,
232
+ the custom events the application emits with the `props` keys and coarse types they carry, the top
233
+ meshes and bound input actions, data freshness (`lastEventAt`, `sessions24h`), whether raw session
234
+ retention is on, the store engine and collector version, the project glossary and recent
235
+ annotations, and `metrics.disabledByCapture` — the metrics that WILL return empty because every
236
+ capture channel feeding them is off. Bounded (<16 KB) and cached per project for ~30 s. Use the ids
237
+ and names it gives you; never infer a scene id, a region id or a custom-event name.
107
238
  - `uptimizr://capabilities` (`application/json`) — the machine-readable descriptor: schema version,
108
239
  the canonical event types, the tool catalog, the parameter-semantics glossary, and `metrics`, the
109
240
  whole registry with each metric's grain, column units, row JSON Schema, filters, limits,
110
241
  interpretation, caveats, source channels and related metrics. Served from the package; **no
111
- collector call**. Read it first — it is how to plan a query without trial and error.
242
+ collector call**. The companion to the context: it is how to plan a query without trial and error.
112
243
  - `uptimizr://scenes` (`application/json`) — the **live** list of scene ids with recent activity,
113
244
  fetched through the read-only query API. These are the valid values for the `scene` parameter, so
114
245
  resolve a user's scene name against it instead of guessing a string.
115
- - Prompts: `weekly_scene_health` (optional `scene`), `attention_hotspots` (required `scene`) and
116
- `xr_comfort_review` (optional `scene`). Each renders one user message that sequences the existing
117
- read-only tools; they fetch nothing themselves and name no exact arguments, so the agent still
118
- resolves the epoch-ms range and the filters.
246
+ - `uptimizr://skills` (`application/json`) — the packaged methodology skills below: name, title,
247
+ description, the tools each method relies on, the capabilities it needs and the arguments it
248
+ takes. Served from the package; **no collector call**. Use it when the client has no prompt
249
+ picker; the method itself comes from `prompts/get`.
250
+ - Prompts: one per packaged skill, listed below. Each opens by telling the agent to read
251
+ `uptimizr://context` first, then renders one user message that sequences the existing read-only
252
+ tools; they fetch nothing themselves and name no exact arguments, so the agent still resolves the
253
+ epoch-ms range and the filters.
254
+
255
+ ## Packaged methodology skills (ADR 0051 §7)
256
+
257
+ A prompt names tools; a **skill** carries the method. Each is an Agent Skills file,
258
+ `skills/<name>/SKILL.md`, shipped in this tarball (authored in `@uptimizr/agent-core`) and
259
+ registered here as a prompt template. The body is the investigation as numbered steps: orient on
260
+ `uptimizr://context`, start from the score, test a change before calling it real, date it, and end
261
+ with recommendations. Read one before improvising: `skills/weekly-scene-health/SKILL.md`.
262
+
263
+ <!-- generated:registry-skill-names:start — generated by `pnpm gen:docs`; edit the SKILL.md files, not this list -->
264
+
265
+ - `attention_hotspots` (scene (required), range) — Find where visitors look and click in a scene: view-direction concentration, gaze→mesh flow, the objects that draw the most interaction, and the ones nobody ever notices. USE FOR: deciding where to put a call to action, finding ignored or invisible content, explaining why an object gets no clicks, laying out a scene around what people actually look at.
266
+ Method: `skills/attention-hotspots/SKILL.md`. Tools: `camera_heatmap`, `flow_links`, `click_rays`, `top_meshes`, `mesh_dwell`, `mesh_blind_spots`, `query`.
267
+ - `conversion_investigation` (scene, range) — Find out where a funnel loses people and whether the loss is real: step-by-step drop-off, the bounce that happens before the funnel even starts, scene-to-scene retention, variant performance, and the interaction failures (dead clicks, rage clicks, unreachable meshes) that explain a stalled step. USE FOR: a funnel that converts worse than expected, an A/B variant comparison, "where do people drop off", diagnosing a step nobody completes.
268
+ Method: `skills/conversion-investigation/SKILL.md`. Tools: `funnel`, `load_bounce_funnel`, `scene_retention`, `variant_leaderboard`, `dead_clicks`, `rage_clicks`, `mesh_reachability`, `flow_links`, `insight_significance`, `insight_movers`, `query`.
269
+ - `performance_regression_triage` (scene, range) — Triage a frame-rate or stability regression: confirm it moved, date it, locate it (which scene, device class, place in the scene), and name the mechanism — jank, shader compile stalls, memory pressure, a render-scale change or a rendering-technology shift. USE FOR: "the app got slower", a FPS drop after a release, stutter reports, deciding whether a regression is real or noise.
270
+ Method: `skills/performance-regression-triage/SKILL.md`. Tools: `insight_movers`, `insight_anomalies`, `insight_significance`, `insight_baseline`, `perf_summary`, `perf_distribution`, `frame_time_percentiles`, `jank_rate`, `perf_by_device`, `perf_by_scene`, `perf_heatmap`, `compile_stalls`, `resource_percentiles`, `render_scale_truth`, `rendering_technology`, `query`.
271
+ - `weekly_scene_health` (scene, range) — A weekly health check for a scene (or the whole project): a weighted health score with every factor traced back to the metric behind it, what changed against last week, traffic, event mix, performance, and the most-interacted meshes. USE FOR: the recurring "how is the scene doing?" review, a scheduled weekly or monthly report, a first look at a project you do not know yet, deciding which scene to investigate next.
272
+ Method: `skills/weekly-scene-health/SKILL.md`. Tools: `insight_scene_health`, `insight_movers`, `insight_baseline`, `insight_significance`, `insight_anomalies`, `event_counts`, `timeseries`, `perf_summary`, `top_meshes`, `list_sessions`, `query`.
273
+ - `xr_comfort_audit` (scene, range) — Audit VR/AR comfort for a scene (or the whole project): rapid head rotation, locomotion style, tracking quality, guardian/boundary contacts, input-source mix, and the short sessions that mean someone took the headset off. USE FOR: motion-sickness complaints, immersive sessions that end early, choosing a locomotion scheme, checking whether a play space is big enough.
274
+ Method: `skills/xr-comfort-audit/SKILL.md`. Tools: `xr_rotation`, `xr_locomotion`, `xr_abandonment`, `xr_sources`, `xr_tracking_quality`, `xr_boundary_contacts`, `boundary_heatmap_stats`, `insight_scene_health`, `insight_movers`, `query`.
275
+
276
+ <!-- generated:registry-skill-names:end -->
119
277
 
120
278
  ## Rules for agents
121
279
 
122
- - **Read-only and privacy-preserving.** Never add ingestion, mutation, or raw per-session event
123
- tools here. No data leaves the consumer's infrastructure (ADR 0003).
280
+ - **Events are read-only; privacy-preserving throughout.** Never add an ingestion tool, an
281
+ event-mutating tool, or a raw per-session event tool here. The only writes are the `annotate`-gated
282
+ metadata tools above, and they must stay a separate `writeTools` export from `readTools` so the
283
+ read-only stance remains inspectable. No data leaves the consumer's infrastructure (ADR 0003).
124
284
  - The server talks only to the configured collector with the consumer's `x-api-key`; never hardcode
125
285
  or log credentials.
126
286
  - Keep it a thin wrapper: the `readTools` catalog (defined in `@uptimizr/agent-core`) is
@@ -130,13 +290,37 @@ Every **aggregate** tool takes a `format` argument choosing the envelope its row
130
290
  - **No database driver, ever.** This server talks to a collector over HTTP; it must stay installable
131
291
  with `npx`. Never add `@uptimizr/db` (or any package with a native/optional binary dependency) to
132
292
  `dependencies` — `src/__tests__/dependencies.test.ts` fails if you do.
293
+ - **Start from an insight primitive on an open-ended question.** "How are things?" does not mean
294
+ "call thirty tools": `insight_scene_health` says _which scene_ to look at (six weighted factors,
295
+ each naming the metric, raw value and project baseline behind it; 50 is the project norm, not a
296
+ pass mark), `insight_movers` compares every comparable metric with the previous equal window and
297
+ ranks the changes by how unusual each is, and `insight_baseline` says whether a level is outside
298
+ normal for that scene. Two fields decide whether a mover is reportable: `direction` is the
299
+ registry's opinion of what a _rise_ means (so a rise in a `down` metric is a regression, not an
300
+ improvement), and `aboveMinSample: false` means the delta is arithmetic but not evidence — those
301
+ rows are returned rather than dropped, and must never be reported as findings.
302
+ - **Then `insight_anomalies` to put a date on it.** `insight_movers` compares two windows you
303
+ chose; `insight_anomalies` walks one metric's whole series and names the buckets that do not
304
+ belong — `spike` / `drop` for a single bucket far from the ones before it, and `shift` at the
305
+ bucket where the level moved and _stayed_ moved, which is the shape a release regression has and
306
+ the one no per-bucket threshold can see. Quote `bucketStart`, and read `contributor`: where the
307
+ metric declares a dimension it can be split by, the row names the mesh, source, input action,
308
+ event type or scene holding the largest share of the excess. Its `z` is in standard deviations
309
+ while `insight_movers`' is the same ratio unscaled, so the two columns are not comparable.
310
+ - **Call `insight_significance` before calling a single change real.** It reports the effect, a 95%
311
+ interval and a p-value for one metric across two windows, with the test chosen from what the
312
+ measure is. Read `ci95` before `p` — an interval straddling 0 means you cannot tell yet — and
313
+ read `powerNote`, which distinguishes "no effect" from "not enough data". It compares two
314
+ _windows_, not two segments.
133
315
  - Tool definitions are pure (`buildRequest`) and must stay unit-testable without a live collector.
134
316
 
135
317
  ## Programmatic API
136
318
 
137
- `readMcpConfig()`, `createMcpServer(client)`, and the shared building blocks re-exported from
319
+ `readMcpConfig()`, `createMcpServer(client, options?)` (`options.capabilities` is the key’s
320
+ capability set from `/api/v1/whoami`; it gates the `query:raw` and `annotate` tools),
321
+ `buildCapabilities(options?)`, and the shared building blocks re-exported from
138
322
  [`@uptimizr/agent-core`](https://www.npmjs.com/package/@uptimizr/agent-core):
139
- `createCollectorClient(config)` and `readTools`.
323
+ `createCollectorClient(config)`, `readTools`, `rawTools` and `writeTools`.
140
324
 
141
325
  ## More
142
326
 
package/README.md CHANGED
@@ -1,14 +1,23 @@
1
1
  # @uptimizr/mcp
2
2
 
3
- A **read-only** [Model Context Protocol](https://modelcontextprotocol.io) server over an
4
- Uptimizr collector's query API. It lets an AI agent ask natural-language questions about your
3
+ A [Model Context Protocol](https://modelcontextprotocol.io) server over an Uptimizr
4
+ collector's API. It lets an AI agent ask natural-language questions about your
5
5
  3D analytics ("what was the most-clicked mesh this week?") and have them answered by querying
6
6
  **your own** collector — nothing is sent to any third party.
7
7
 
8
- The server is a thin wrapper: each tool maps one-to-one to a documented collector query endpoint
9
- (see [integration docs](https://github.com/RaananW/Uptimizr/blob/main/docs/integration.md)). It
10
- performs `GET` requests only — there are **no ingestion, mutation, or raw per-session event
11
- tools**.
8
+ The server is a thin wrapper: each analytics tool maps one-to-one to a documented collector query
9
+ endpoint (see
10
+ [integration docs](https://github.com/RaananW/Uptimizr/blob/main/docs/integration.md)) and performs
11
+ `GET` requests only. There is **no ingestion tool**, and nothing here can write, alter or delete an
12
+ analytics event — **events are read-only**. A key holding `query:raw` additionally gets
13
+ `session_narrative`, the compacted account of one session, on a collector running with
14
+ `ENABLE_RAW_SESSION_RETENTION`.
15
+
16
+ The one exception is gated: when the configured key holds the `annotate` capability the server also
17
+ registers the **project-metadata** tools `annotate`, `define_term` and `save_analysis` (plus
18
+ `list_annotations`, `list_glossary`, `list_analyses`), which leave notes, definitions and saved
19
+ analyses behind. The server asks `GET /api/v1/whoami` once at start-up, so a read-only key yields a
20
+ read-only server, and every metadata write is recorded in the project's agent audit log.
12
21
 
13
22
  ## How it connects
14
23
 
@@ -28,10 +37,10 @@ UPTIMIZR_API_KEY="utk_…" \
28
37
  npx @uptimizr/mcp
29
38
  ```
30
39
 
31
- | Environment variable | Required | Notes |
32
- | ------------------------ | -------- | -------------------------------------------------- |
33
- | `UPTIMIZR_COLLECTOR_URL` | yes | Base URL of **your** collector. |
34
- | `UPTIMIZR_API_KEY` | yes | Your project API key (`x-api-key`), read-only use. |
40
+ | Environment variable | Required | Notes |
41
+ | ------------------------ | -------- | ----------------------------------------------------------------------------------------------- |
42
+ | `UPTIMIZR_COLLECTOR_URL` | yes | Base URL of **your** collector. |
43
+ | `UPTIMIZR_API_KEY` | yes | Your project API key (`x-api-key`). `query` reads; add `annotate` to enable the metadata tools. |
35
44
 
36
45
  ### Configure an MCP client
37
46
 
@@ -76,14 +85,14 @@ For **GitHub Copilot CLI**, put the same entry in `~/.copilot/mcp-config.json` w
76
85
  ## Tools
77
86
 
78
87
  The catalog is **generated from the semantic metric registry** in `@uptimizr/metrics` (ADR 0051 §1):
79
- every aggregation the collector serves on a read endpoint is a tool — **69** of them. Each tool's
88
+ every metric the collector serves on a read endpoint is a tool — **76** of them, one table row
89
+ each, of which a plain `query` key sees 75 (`session_narrative` needs `query:raw`). Each tool's
80
90
  description carries the metric's interpretation notes and caveats, and each declares an MCP
81
- `outputSchema` for the rows it returns (results come back as both `content` text and
82
- `structuredContent`).
83
-
84
- The table is **generated** from the collector's semantic metric registry (ADR 0051) — one row per
85
- registered read metric. Read `uptimizr://capabilities` for each one's result grain, column units,
86
- row schema and caveats; the same registry drives the collector's `GET /api/v1/openapi.json`.
91
+ `outputSchema` covering every `format` envelope it can answer with — the rows, the `table`
92
+ envelope around them (the tools' default), or a `summary` digest. Results come back as both
93
+ `content` text and `structuredContent`. Read `uptimizr://capabilities` for each tool's result
94
+ grain, column units, row schema and caveats; the same registry drives the collector's
95
+ `GET /api/v1/openapi.json`.
87
96
 
88
97
  Most tools accept an optional time range (`since` / `until`, epoch ms) and the filters the
89
98
  underlying endpoint supports (`scene`, `session`, `source`, `bins`, `cellSize`, `interval`, `type`,
@@ -96,6 +105,7 @@ underlying endpoint supports (`scene`, `session`, `source`, `bins`, `cellSize`,
96
105
  | ---------------------------- | ---------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
97
106
  | `list_sessions` | `/api/v1/sessions` | Recent sessions | `since`, `until`, `bins`, `limit`, `cameraMode`, `format` |
98
107
  | `session_meta` | `/api/v1/sessions/:id/meta` | Session descriptor | `session` |
108
+ | `session_narrative` | `/api/v1/sessions/:id/narrative` | Session narrative | `session`, `minDwellMs`, `fpsThreshold`, `maxEntries`, `format` |
99
109
  | `scene_representation` | `/api/v1/scenes/:sceneId/representation` | Scene representation | `scene` |
100
110
  | `list_scenes` | `/api/v1/scenes` | Active scenes | `since`, `until`, `limit`, `format` |
101
111
  | `timeseries` | `/api/v1/timeseries` | Event volume over time | `since`, `until`, `interval`, `scene`, `type`, `format` |
@@ -127,6 +137,7 @@ underlying endpoint supports (`scene`, `session`, `source`, `bins`, `cellSize`,
127
137
  | `hover_dwell` | `/api/v1/hover/dwell` | Hover hesitation per object | `since`, `until`, `bins`, `limit`, `scene`, `session`, `source`, `cameraMode`, `format` |
128
138
  | `interaction_sources` | `/api/v1/interactions/sources` | Interactions by input source | `since`, `until`, `bins`, `limit`, `scene`, `session`, `source`, `cameraMode`, `format` |
129
139
  | `top_input_actions` | `/api/v1/input-actions/top` | Most-used shortcuts and actions | `since`, `until`, `bins`, `limit`, `scene`, `session`, `source`, `cameraMode`, `format` |
140
+ | `custom_event_vocabulary` | `/api/v1/vocabulary/custom-events` | Discovered custom-event vocabulary | `since`, `until`, `scene`, `limit`, `format` |
130
141
  | `camera_gestures` | `/api/v1/camera-gestures` | Camera navigation gestures | `since`, `until`, `bins`, `limit`, `scene`, `session`, `source`, `cameraMode`, `format` |
131
142
  | `navigation_stats` | `/api/v1/navigation` | Navigation effort per session | `since`, `until`, `moveThreshold`, `limit`, `scene`, `session`, `format` |
132
143
  | `backtrack_ratio` | `/api/v1/backtrack` | Path retrace / backtracking | `since`, `until`, `cellSize`, `limit`, `scene`, `session`, `format` |
@@ -163,6 +174,11 @@ underlying endpoint supports (`scene`, `session`, `source`, `bins`, `cellSize`,
163
174
  | `scene_retention` | `/api/v1/scene-retention` | Scene-to-scene retention | `since`, `until`, `limit`, `format` |
164
175
  | `load_bounce_funnel` | `/api/v1/load-bounce` | Load → bounce funnel | `since`, `until`, `scene`, `bands`, `format` |
165
176
  | `variant_leaderboard` | `/api/v1/variant-leaderboard` | Variant → conversion leaderboard | `since`, `until`, `scene`, `cameraMode`, `variant`, `conversion`, `limit`, `format` |
177
+ | `insight_baseline` | `/api/v1/insights/baseline` | Metric baseline | `metric`, `scene`, `window`, `bucket`, `since`, `until`, `format` |
178
+ | `insight_movers` | `/api/v1/insights/movers` | What changed | `scene`, `metrics`, `bucket`, `limit`, `since`, `until`, `refSince`, `refUntil`, `format` |
179
+ | `insight_anomalies` | `/api/v1/insights/anomalies` | Anomalous buckets | `metric`, `scene`, `window`, `bucket`, `sensitivity`, `since`, `until`, `format` |
180
+ | `insight_significance` | `/api/v1/insights/significance` | Statistical significance | `metric`, `scene`, `bucket`, `since`, `until`, `refSince`, `refUntil`, `format` |
181
+ | `insight_scene_health` | `/api/v1/insights/scene-health` | Scene health score | `scene`, `window`, `since`, `until`, `bucket`, `limit`, `weights`, `format` |
166
182
 
167
183
  <!-- generated:registry-tools:end -->
168
184
 
@@ -172,11 +188,59 @@ The server also exposes read-only **resources** for self-discovery — `uptimizr
172
188
  (a machine-readable descriptor of event types, the tool catalog, parameter semantics, and
173
189
  `metrics`: the collector's whole semantic metric registry, with each metric's result grain, column
174
190
  units, row JSON Schema, row limits, interpretation and caveats — ADR 0051) and
175
- `uptimizr://scenes` (live scene ids) — and curated **prompts** (`weekly_scene_health`,
176
- `attention_hotspots`, `xr_comfort_review`) that drive the tools above. A remote Streamable HTTP
177
- transport is a deferred, auth-gated follow-up (ADR 0050 §7). See the
191
+ `uptimizr://context` (the live project context document), `uptimizr://scenes` (live scene ids) and
192
+ `uptimizr://skills` (the methodology catalog below) — plus one curated **prompt per packaged
193
+ skill**, listed in the table below, that drives the tools above. See the
178
194
  [MCP guide](https://uptimizr.com/docs/guides/mcp/) for details.
179
195
 
196
+ ## Methodology skills
197
+
198
+ Each prompt template is a packaged **skill** — an Agent Skills file, `skills/<name>/SKILL.md`,
199
+ shipped in this package — so the method an agent follows is readable, forkable and identical to the
200
+ one `uptimizr agent report --skill` runs on your collector. The catalog is also served as the
201
+ `uptimizr://skills` resource. `*` marks a required argument.
202
+
203
+ <!-- generated:registry-skills:start — generated by `pnpm gen:docs`; edit the SKILL.md files, not this list -->
204
+
205
+ | Skill | Arguments | What it produces, and when to use it | Tools its method names |
206
+ | ------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
207
+ | `attention_hotspots` | `scene*`, `range?` | Find where visitors look and click in a scene: view-direction concentration, gaze→mesh flow, the objects that draw the most interaction, and the ones nobody ever notices. USE FOR: deciding where to put a call to action, finding ignored or invisible content, explaining why an object gets no clicks, laying out a scene around what people actually look at. | `camera_heatmap`, `flow_links`, `click_rays`, `top_meshes`, `mesh_dwell`, `mesh_blind_spots`, `query` |
208
+ | `conversion_investigation` | `scene?`, `range?` | Find out where a funnel loses people and whether the loss is real: step-by-step drop-off, the bounce that happens before the funnel even starts, scene-to-scene retention, variant performance, and the interaction failures (dead clicks, rage clicks, unreachable meshes) that explain a stalled step. USE FOR: a funnel that converts worse than expected, an A/B variant comparison, "where do people drop off", diagnosing a step nobody completes. | `funnel`, `load_bounce_funnel`, `scene_retention`, `variant_leaderboard`, `dead_clicks`, `rage_clicks`, `mesh_reachability`, `flow_links`, `insight_significance`, `insight_movers`, `query` |
209
+ | `performance_regression_triage` | `scene?`, `range?` | Triage a frame-rate or stability regression: confirm it moved, date it, locate it (which scene, device class, place in the scene), and name the mechanism — jank, shader compile stalls, memory pressure, a render-scale change or a rendering-technology shift. USE FOR: "the app got slower", a FPS drop after a release, stutter reports, deciding whether a regression is real or noise. | `insight_movers`, `insight_anomalies`, `insight_significance`, `insight_baseline`, `perf_summary`, `perf_distribution`, `frame_time_percentiles`, `jank_rate`, `perf_by_device`, `perf_by_scene`, `perf_heatmap`, `compile_stalls`, `resource_percentiles`, `render_scale_truth`, `rendering_technology`, `query` |
210
+ | `weekly_scene_health` | `scene?`, `range?` | A weekly health check for a scene (or the whole project): a weighted health score with every factor traced back to the metric behind it, what changed against last week, traffic, event mix, performance, and the most-interacted meshes. USE FOR: the recurring "how is the scene doing?" review, a scheduled weekly or monthly report, a first look at a project you do not know yet, deciding which scene to investigate next. | `insight_scene_health`, `insight_movers`, `insight_baseline`, `insight_significance`, `insight_anomalies`, `event_counts`, `timeseries`, `perf_summary`, `top_meshes`, `list_sessions`, `query` |
211
+ | `xr_comfort_audit` | `scene?`, `range?` | Audit VR/AR comfort for a scene (or the whole project): rapid head rotation, locomotion style, tracking quality, guardian/boundary contacts, input-source mix, and the short sessions that mean someone took the headset off. USE FOR: motion-sickness complaints, immersive sessions that end early, choosing a locomotion scheme, checking whether a play space is big enough. | `xr_rotation`, `xr_locomotion`, `xr_abandonment`, `xr_sources`, `xr_tracking_quality`, `xr_boundary_contacts`, `boundary_heatmap_stats`, `insight_scene_health`, `insight_movers`, `query` |
212
+
213
+ <!-- generated:registry-skills:end -->
214
+
215
+ ```bash
216
+ cat node_modules/@uptimizr/mcp/skills/weekly-scene-health/SKILL.md
217
+ ```
218
+
219
+ ## Hosted transport (Streamable HTTP)
220
+
221
+ The collector can serve **this same server** itself, over MCP's Streamable HTTP transport, so a
222
+ remote agent connects with a URL and a key instead of launching this package locally (ADR 0051 §7).
223
+ Start the collector with `COLLECTOR_MCP_HTTP=1` and point a client at `/mcp`:
224
+
225
+ ```jsonc
226
+ {
227
+ "mcpServers": {
228
+ "uptimizr": {
229
+ "type": "http",
230
+ "url": "https://collect.example.com/mcp",
231
+ "headers": { "Authorization": "Bearer utk_…" },
232
+ },
233
+ },
234
+ }
235
+ ```
236
+
237
+ `x-api-key` works in place of the bearer header, the key still needs only `query`, and the tools,
238
+ resources and prompts are identical — both transports are built by the same `createMcpServer()`
239
+ below. Tool calls made that way are recorded in the collector's audit log with
240
+ `surface: "mcp-http"`; calls from this package over stdio are ordinary HTTP reads, recorded as
241
+ `http`. See the
242
+ [hosted transport guide](https://uptimizr.com/docs/guides/mcp/#hosted-transport-streamable-http).
243
+
180
244
  ## Programmatic use
181
245
 
182
246
  ```ts
@@ -188,8 +252,14 @@ const server = createMcpServer(client);
188
252
  await server.connect(new StdioServerTransport());
189
253
  ```
190
254
 
191
- The package also exports `readTools`, `CollectorError`, `version`, and the related public types.
192
- The read-only tool catalog (`readTools`) and the `GET`-only collector client are defined in the
255
+ `createMcpServer(client, options?)` takes an optional `options.capabilities` — the capability set of
256
+ the API key the server instance is bound to. The collector-hosted transport passes the key it
257
+ resolved, so a session's surface can only narrow to what its key may do; omitting it (as the stdio
258
+ entry point does) registers the whole read catalog and leaves enforcement to the collector.
259
+
260
+ The package also exports `readTools`, `rawTools`, `writeTools`, `CollectorError`, `version`, and the
261
+ related public types. The read-only analytics catalog (`readTools`), the `annotate`-gated
262
+ `writeTools`, and the collector client are defined in the
193
263
  framework-agnostic [`@uptimizr/agent-core`](../agent-core/README.md) package and re-exported here,
194
264
  so the agent tool surface is defined once and shared across the MCP server, the dashboard assistant,
195
265
  and the demo assistant (ADR 0050). Building a non-MCP agent? Depend on `@uptimizr/agent-core`
package/dist/bin.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { createCollectorClient } from "@uptimizr/agent-core";
4
4
  import { readMcpConfig } from "./config.js";
5
- import { createMcpServer } from "./server.js";
5
+ import { createMcpServer, fetchKeyCapabilities } from "./server.js";
6
6
  /**
7
7
  * Entry point: read configuration from the environment, build a read-only
8
8
  * collector client and MCP server, and serve over stdio (the transport MCP
@@ -11,7 +11,14 @@ import { createMcpServer } from "./server.js";
11
11
  async function main() {
12
12
  const config = readMcpConfig();
13
13
  const client = createCollectorClient(config);
14
- const server = createMcpServer(client);
14
+ // Ask the collector what this key may do before building the server, so the
15
+ // capability-gated tools match the key: the raw-session tools of #314 only for
16
+ // `query:raw`, the metadata write tools of #310 only for `annotate`. Best
17
+ // effort — a collector older than `/api/v1/whoami`, an offline start or a
18
+ // transient failure yields "no extra capabilities", which serves the ordinary
19
+ // `query` surface this binary has always served.
20
+ const capabilities = await fetchKeyCapabilities(client);
21
+ const server = createMcpServer(client, { capabilities });
15
22
  const transport = new StdioServerTransport();
16
23
  await server.connect(transport);
17
24
  }
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;GAIG;AACH,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,MAAM,MAAM,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,OAAO,IAAI,CAAC,CAAC;IACnE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEpE;;;;GAIG;AACH,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,MAAM,MAAM,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC7C,4EAA4E;IAC5E,+EAA+E;IAC/E,0EAA0E;IAC1E,0EAA0E;IAC1E,8EAA8E;IAC9E,iDAAiD;IACjD,MAAM,YAAY,GAAG,MAAM,oBAAoB,CAAC,MAAM,CAAC,CAAC;IACxD,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,EAAE,EAAE,YAAY,EAAE,CAAC,CAAC;IACzD,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,OAAO,IAAI,CAAC,CAAC;IACnE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}