@weiyentan/opencode-plugin-awx 0.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +262 -0
  3. package/dist/auth.d.ts +112 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +180 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/client.d.ts +148 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +334 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/contracts/job-detail.d.ts +141 -0
  12. package/dist/contracts/job-detail.d.ts.map +1 -0
  13. package/dist/contracts/job-detail.js +98 -0
  14. package/dist/contracts/job-detail.js.map +1 -0
  15. package/dist/contracts/sync-project.d.ts +31 -0
  16. package/dist/contracts/sync-project.d.ts.map +1 -0
  17. package/dist/contracts/sync-project.js +30 -0
  18. package/dist/contracts/sync-project.js.map +1 -0
  19. package/dist/index.d.ts +16 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +754 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/job-status.d.ts +116 -0
  24. package/dist/job-status.d.ts.map +1 -0
  25. package/dist/job-status.js +168 -0
  26. package/dist/job-status.js.map +1 -0
  27. package/dist/launch.d.ts +76 -0
  28. package/dist/launch.d.ts.map +1 -0
  29. package/dist/launch.js +133 -0
  30. package/dist/launch.js.map +1 -0
  31. package/dist/list-projects.d.ts +63 -0
  32. package/dist/list-projects.d.ts.map +1 -0
  33. package/dist/list-projects.js +84 -0
  34. package/dist/list-projects.js.map +1 -0
  35. package/dist/list-templates.d.ts +60 -0
  36. package/dist/list-templates.d.ts.map +1 -0
  37. package/dist/list-templates.js +120 -0
  38. package/dist/list-templates.js.map +1 -0
  39. package/dist/metrics.d.ts +174 -0
  40. package/dist/metrics.d.ts.map +1 -0
  41. package/dist/metrics.js +275 -0
  42. package/dist/metrics.js.map +1 -0
  43. package/dist/transforms.d.ts +52 -0
  44. package/dist/transforms.d.ts.map +1 -0
  45. package/dist/transforms.js +108 -0
  46. package/dist/transforms.js.map +1 -0
  47. package/package.json +56 -0
@@ -0,0 +1,84 @@
1
+ /* ── Timeout budget ─────────────────────────────────────────────── */
2
+ /**
3
+ * Calculate per-page timeout budget.
4
+ *
5
+ * Formula: totalTimeout / (maxPages + 1)
6
+ * The +1 accounts for any overhead/rounding and ensures we never
7
+ * exceed the total tool timeout even if all pages are fetched.
8
+ *
9
+ * @param totalTimeout Total tool timeout in milliseconds
10
+ * @param maxPages Maximum number of pages to fetch
11
+ * @returns Per-page budget in milliseconds (rounded down)
12
+ */
13
+ export function calcPageBudget(totalTimeout, maxPages) {
14
+ return Math.floor(totalTimeout / (maxPages + 1));
15
+ }
16
+ /* ── Pagination logic ───────────────────────────────────────────── */
17
+ /**
18
+ * Fetch paginated projects from the AWX API, consolidate results,
19
+ * and return them sorted by name.
20
+ *
21
+ * @param client The AWX HTTP client
22
+ * @param options Pagination and timeout options
23
+ * @returns Consolidated, sorted list of projects
24
+ */
25
+ export async function listProjects(client, options) {
26
+ const maxPages = options?.maxPages ?? 5;
27
+ const pageSize = options?.pageSize ?? 50;
28
+ const totalTimeout = options?.timeout ?? 30_000;
29
+ const toolAbortSignal = options?.abortSignal;
30
+ const pageBudget = calcPageBudget(totalTimeout, maxPages);
31
+ const allProjects = [];
32
+ let page = 1;
33
+ let hasMore = true;
34
+ while (hasMore && page <= maxPages) {
35
+ // Check if tool has been aborted
36
+ if (toolAbortSignal?.aborted) {
37
+ throw toolAbortSignal.reason ?? new DOMException("Aborted", "AbortError");
38
+ }
39
+ // Create per-page timeout signal
40
+ const pageController = new AbortController();
41
+ const pageTimer = setTimeout(() => pageController.abort(new DOMException(`Page ${page} timed out after ${pageBudget}ms`, "TimeoutError")), pageBudget);
42
+ // Wire tool abort signal to page controller
43
+ const abortHandler = () => {
44
+ clearTimeout(pageTimer);
45
+ pageController.abort(toolAbortSignal.reason ?? new DOMException("Aborted", "AbortError"));
46
+ };
47
+ if (toolAbortSignal && !toolAbortSignal.aborted) {
48
+ toolAbortSignal.addEventListener("abort", abortHandler, { once: true });
49
+ }
50
+ try {
51
+ // Build request path with pagination parameters
52
+ const path = `/api/v2/projects/?page=${page}&page_size=${pageSize}`;
53
+ const response = await client.request("awx-list-projects", path, { headers: { "Content-Type": "application/json" } }, pageController.signal);
54
+ if (!response.ok) {
55
+ throw new Error(`Failed to fetch projects: ${response.status} ${response.statusText}`);
56
+ }
57
+ const data = await response.json();
58
+ allProjects.push(...data.results);
59
+ if (!data.next) {
60
+ hasMore = false;
61
+ }
62
+ page++;
63
+ }
64
+ finally {
65
+ clearTimeout(pageTimer);
66
+ if (toolAbortSignal && !toolAbortSignal.aborted) {
67
+ toolAbortSignal.removeEventListener("abort", abortHandler);
68
+ }
69
+ pageController.abort(); // prevent any lingering signal listeners
70
+ }
71
+ }
72
+ // Sort consolidated results by name (case-insensitive alphanumeric)
73
+ allProjects.sort((a, b) => a.name.localeCompare(b.name));
74
+ const output = {
75
+ count: allProjects.length,
76
+ results: allProjects,
77
+ };
78
+ // If we exhausted the page cap but there are more pages, warn
79
+ if (page > maxPages && hasMore) {
80
+ output.warning = "More items exist. Increase max-pages or use a filter.";
81
+ }
82
+ return output;
83
+ }
84
+ //# sourceMappingURL=list-projects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-projects.js","sourceRoot":"","sources":["../src/list-projects.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * list-templates.ts — AWX job template listing with pagination consolidation
3
+ *
4
+ * Fetches job templates from the AWX /api/v2/job_templates/ endpoint,
5
+ * iterates through pages up to a configurable cap, sorts results by name,
6
+ * and enforces a per-page timeout budget.
7
+ *
8
+ * ## Timeout Budget
9
+ *
10
+ * Per-page timeout = toolTimeoutMs / (maxPages + 1).
11
+ * This ensures that even if every page times out sequentially, we never
12
+ * exceed the tool-level timeout (the +1 accounts for the tool overhead).
13
+ *
14
+ * ## Page Cap
15
+ *
16
+ * When maxPages > 0 and the cap is reached with more pages available,
17
+ * the output includes a warning field. The caller can detect truncation
18
+ * by checking for the presence of `warning`.
19
+ */
20
+ import type { AwxClient } from "./client.js";
21
+ /** A simplified template result returned to the caller */
22
+ export interface TemplateResult {
23
+ id: number;
24
+ name: string;
25
+ description: string;
26
+ }
27
+ /** Output of the list-templates operation */
28
+ export interface ListTemplatesOutput {
29
+ count: number;
30
+ results: TemplateResult[];
31
+ warning?: string;
32
+ }
33
+ /** Options for listTemplates */
34
+ export interface ListTemplatesOptions {
35
+ /** Number of items per page (default: 50) */
36
+ pageSize?: number;
37
+ /**
38
+ * Maximum pages to fetch.
39
+ * 0 = no cap (fetch all pages).
40
+ * Default: 5 (5 pages × 50 items = 250 max).
41
+ */
42
+ maxPages?: number;
43
+ }
44
+ /**
45
+ * List AWX job templates with pagination consolidation.
46
+ *
47
+ * Fetches templates from `/api/v2/job_templates/`, iterating through
48
+ * pages up to the configured `maxPages` cap. Each page request gets a
49
+ * timeout budget of `toolTimeoutMs / (maxPages + 1)`.
50
+ *
51
+ * Results are sorted by name (alphanumeric, case-insensitive) before returning.
52
+ *
53
+ * @param client The AWX HTTP client
54
+ * @param toolTimeoutMs Tool-level timeout in ms (used for per-page budget)
55
+ * @param options Optional: pageSize, maxPages
56
+ * @param abortSignal Optional tool context abort signal for cancellation
57
+ * @returns Consolidated, sorted template list with count and optional warning
58
+ */
59
+ export declare function listTemplates(client: AwxClient, toolTimeoutMs: number, options?: ListTemplatesOptions, abortSignal?: AbortSignal): Promise<ListTemplatesOutput>;
60
+ //# sourceMappingURL=list-templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-templates.d.ts","sourceRoot":"","sources":["../src/list-templates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,6CAA6C;AAC7C,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,gCAAgC;AAChC,MAAM,WAAW,oBAAoB;IACnC,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAkFD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,SAAS,EACjB,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,oBAAoB,EAC9B,WAAW,CAAC,EAAE,WAAW,GACxB,OAAO,CAAC,mBAAmB,CAAC,CAiE9B"}
@@ -0,0 +1,120 @@
1
+ // ── Helpers ───────────────────────────────────────────────────────
2
+ /**
3
+ * Combine multiple AbortSignals into one — aborts if ANY source aborts.
4
+ * Uses native AbortSignal.any() on Node 20+, falls back to manual wiring.
5
+ */
6
+ function anyAbortSignal(signals) {
7
+ if (typeof AbortSignal.any === "function") {
8
+ return AbortSignal.any(signals);
9
+ }
10
+ const controller = new AbortController();
11
+ for (const signal of signals) {
12
+ if (signal.aborted) {
13
+ controller.abort(signal.reason ?? new DOMException("Aborted", "AbortError"));
14
+ return controller.signal;
15
+ }
16
+ signal.addEventListener("abort", () => {
17
+ controller.abort(signal.reason ?? new DOMException("Aborted", "AbortError"));
18
+ }, { once: true });
19
+ }
20
+ return controller.signal;
21
+ }
22
+ /**
23
+ * Create an AbortSignal that fires after `ms` milliseconds.
24
+ * Uses setTimeout + AbortController for Node 18+ compatibility.
25
+ */
26
+ function createTimeoutSignal(ms) {
27
+ const controller = new AbortController();
28
+ const timer = setTimeout(() => controller.abort(new DOMException("Page timeout.", "TimeoutError")), ms);
29
+ return {
30
+ signal: controller.signal,
31
+ clear: () => clearTimeout(timer),
32
+ };
33
+ }
34
+ /**
35
+ * Extract path + query string from a full AWX API URL.
36
+ * AWX pagination returns absolute URLs in `next` — we need just the path.
37
+ */
38
+ function extractPath(fullUrl) {
39
+ try {
40
+ const url = new URL(fullUrl);
41
+ return url.pathname + (url.search || "");
42
+ }
43
+ catch {
44
+ // If it's already just a path, return as-is
45
+ return fullUrl;
46
+ }
47
+ }
48
+ /** Map a raw AWX template item to the simplified result shape */
49
+ function mapTemplate(item) {
50
+ return {
51
+ id: item.id,
52
+ name: item.name,
53
+ description: item.description ?? "",
54
+ };
55
+ }
56
+ // ── Core Logic ────────────────────────────────────────────────────
57
+ /**
58
+ * List AWX job templates with pagination consolidation.
59
+ *
60
+ * Fetches templates from `/api/v2/job_templates/`, iterating through
61
+ * pages up to the configured `maxPages` cap. Each page request gets a
62
+ * timeout budget of `toolTimeoutMs / (maxPages + 1)`.
63
+ *
64
+ * Results are sorted by name (alphanumeric, case-insensitive) before returning.
65
+ *
66
+ * @param client The AWX HTTP client
67
+ * @param toolTimeoutMs Tool-level timeout in ms (used for per-page budget)
68
+ * @param options Optional: pageSize, maxPages
69
+ * @param abortSignal Optional tool context abort signal for cancellation
70
+ * @returns Consolidated, sorted template list with count and optional warning
71
+ */
72
+ export async function listTemplates(client, toolTimeoutMs, options, abortSignal) {
73
+ const pageSize = options?.pageSize ?? 50;
74
+ const maxPages = options?.maxPages ?? 5;
75
+ // Per-page timeout budget: divide tool timeout by (maxPages + 1).
76
+ // The +1 provides a safety margin for tool overhead after the last page.
77
+ const effectiveMaxPages = Math.max(1, maxPages);
78
+ const perPageBudget = Math.floor(toolTimeoutMs / (effectiveMaxPages + 1));
79
+ const allResults = [];
80
+ let nextPage = null;
81
+ let pagesFetched = 0;
82
+ do {
83
+ pagesFetched++;
84
+ // Build URL for current page
85
+ const path = nextPage
86
+ ? extractPath(nextPage)
87
+ : `/api/v2/job_templates/?page_size=${pageSize}`;
88
+ // Fetch this page with per-page timeout
89
+ const { signal: pageSignal, clear: clearTimeout_ } = createTimeoutSignal(perPageBudget);
90
+ const combinedSignal = abortSignal
91
+ ? anyAbortSignal([abortSignal, pageSignal])
92
+ : pageSignal;
93
+ try {
94
+ const response = await client.request("awx-list-templates", path, undefined, combinedSignal);
95
+ if (!response.ok) {
96
+ throw new Error(`AWX API error: ${response.status} ${response.statusText}`);
97
+ }
98
+ const data = (await response.json());
99
+ allResults.push(...data.results.map(mapTemplate));
100
+ nextPage = data.next;
101
+ }
102
+ finally {
103
+ clearTimeout_();
104
+ }
105
+ // Stop when no more pages OR we've hit the cap.
106
+ // maxPages <= 0 means "no cap" (fetch all).
107
+ } while (nextPage !== null && (maxPages <= 0 || pagesFetched < maxPages));
108
+ // Sort consolidated results by name (alphanumeric, case-insensitive)
109
+ allResults.sort((a, b) => a.name.localeCompare(b.name));
110
+ const output = {
111
+ count: allResults.length,
112
+ results: allResults,
113
+ };
114
+ // If there are more pages but we hit the cap, emit a warning
115
+ if (nextPage !== null && maxPages > 0 && pagesFetched >= maxPages) {
116
+ output.warning = `Page cap of ${maxPages} page${maxPages !== 1 ? "s" : ""} reached. Some results may be omitted.`;
117
+ }
118
+ return output;
119
+ }
120
+ //# sourceMappingURL=list-templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-templates.js","sourceRoot":"","sources":["../src/list-templates.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,174 @@
1
+ /**
2
+ * metrics.ts — Per-tool counters with file-backed durability
3
+ *
4
+ * Provides structured metrics for operational visibility and phase-gating:
5
+ * - Per-tool call count
6
+ * - Per-tool error count
7
+ * - Per-tool latency accumulation (ms)
8
+ * - Token expiry events (401 detection)
9
+ * - PowerShell fallback count (for deprecation monitoring)
10
+ *
11
+ * ## Durability Model
12
+ *
13
+ * Counters are file-backed so they survive plugin reloads. This is required
14
+ * for the Phase 2→3 gate which demands 14 consecutive days of zero PowerShell
15
+ * AWX calls.
16
+ *
17
+ * **File format:** JSON at a configurable path (default: `.metrics/metrics.json`).
18
+ * **Atomic writes:** Data is written to a `.tmp` file first, then renamed over
19
+ * the target — preventing corruption on partial writes.
20
+ * **Merge-on-load:** When `load()` is called, disk values are merged with
21
+ * in-memory counters using `Math.max()` — counters never decrease.
22
+ * **Missing file:** Treated as a fresh start (no error thrown).
23
+ *
24
+ * ## Integration Point
25
+ *
26
+ * Metrics hook into the client module at pipeline boundaries — not inside
27
+ * individual middleware. The `client.ts` request function calls `recordCall`,
28
+ * `recordError`, and `recordTokenExpiry` at the top level.
29
+ *
30
+ * ```typescript
31
+ * // In client.ts (pipeline boundary):
32
+ * const start = Date.now();
33
+ * try {
34
+ * const response = await fetch(...);
35
+ * metrics.recordCall(toolName, Date.now() - start);
36
+ * if (response.status === 401) metrics.recordTokenExpiry(toolName);
37
+ * return response;
38
+ * } catch (err) {
39
+ * metrics.recordError(toolName);
40
+ * throw err;
41
+ * }
42
+ * ```
43
+ */
44
+ /** Per-tool metrics counters */
45
+ export interface ToolMetrics {
46
+ /** Total number of tool calls (successful or not) */
47
+ callCount: number;
48
+ /** Total number of errors (any error, including HTTP and network errors) */
49
+ errorCount: number;
50
+ /** Accumulated latency in milliseconds (sum of all call latencies) */
51
+ totalLatencyMs: number;
52
+ /** Number of 401 Unauthorized responses (token expiry detection) */
53
+ tokenExpiryEvents: number;
54
+ /** Number of times the plugin fell back to PowerShell for this tool */
55
+ psFallbackCount: number;
56
+ }
57
+ /** Create a zeroed ToolMetrics object */
58
+ export declare function createDefaultMetrics(): ToolMetrics;
59
+ /**
60
+ * Thread-safe (in-memory) metrics store with file-backed durability.
61
+ *
62
+ * All recording methods are synchronous and fast — they mutate an in-memory
63
+ * Map. Persistence is explicit (call `persist()`) so the caller controls when
64
+ * to flush to disk.
65
+ *
66
+ * The durability model is **additive-merge**: on `load()`, existing in-memory
67
+ * counters are never decreased. This prevents race conditions where an
68
+ * in-memory increment during a concurrent persist window would be lost.
69
+ */
70
+ export declare class MetricsStore {
71
+ /** Per-tool counters, keyed by tool name */
72
+ private counters;
73
+ /** File path for persistence (default: `.metrics/metrics.json`) */
74
+ private persistPath;
75
+ /**
76
+ * @param persistPath - Absolute or relative path to the metrics JSON file.
77
+ * Defaults to `.metrics/metrics.json` relative to the current working directory.
78
+ */
79
+ constructor(persistPath?: string);
80
+ /** Get or create the ToolMetrics entry for a tool name */
81
+ private ensure;
82
+ /**
83
+ * Record a tool call with its latency.
84
+ * Called by the client at the pipeline boundary after a successful (or
85
+ * error-handled) fetch completes.
86
+ *
87
+ * @param toolName - The registered tool name (e.g., "list-templates", "launch-job")
88
+ * @param latencyMs - Round-trip latency in milliseconds
89
+ */
90
+ recordCall(toolName: string, latencyMs: number): void;
91
+ /**
92
+ * Record an error for a tool.
93
+ * Called by the client when a fetch request fails (any error: 4xx, 5xx,
94
+ * network error, timeout, abort).
95
+ */
96
+ recordError(toolName: string): void;
97
+ /**
98
+ * Record a token expiry event (HTTP 401).
99
+ * Called by the client when a request returns 401 Unauthorized, indicating
100
+ * the bearer token has expired or been revoked.
101
+ */
102
+ recordTokenExpiry(toolName: string): void;
103
+ /**
104
+ * Record a PowerShell fallback for a tool.
105
+ * Called when the plugin tool cannot complete the operation and falls back
106
+ * to the legacy PowerShell script. This counter is tracked per-tool for
107
+ * granular deprecation monitoring (Phase 2→3 gate requires zero PS calls
108
+ * across all tools for 14 consecutive days).
109
+ */
110
+ recordPsFallback(toolName: string): void;
111
+ /**
112
+ * Get metrics for a specific tool.
113
+ * Returns a default zeroed object if the tool has never been recorded.
114
+ */
115
+ getMetrics(toolName: string): ToolMetrics;
116
+ /**
117
+ * Get all tracked tools' metrics as a plain object.
118
+ * Returns deep copies — mutations do not affect the store.
119
+ */
120
+ getAllMetrics(): Record<string, ToolMetrics>;
121
+ /**
122
+ * Persist current metrics to disk using an atomic write strategy.
123
+ *
124
+ * **Atomicity**: Data is written to a `.tmp` file first, then renamed
125
+ * over the target. If the process crashes during the write, the original
126
+ * file remains intact.
127
+ *
128
+ * **Directory creation**: The parent directory is created recursively if
129
+ * it doesn't exist.
130
+ */
131
+ persist(): Promise<void>;
132
+ /**
133
+ * Load metrics from disk and merge with in-memory counters.
134
+ *
135
+ * **Merge strategy (additive)**: For each tool, each counter is set to
136
+ * `Math.max(inMemory, onDisk)`. This ensures:
137
+ * - Counters never decrease (no lost increments).
138
+ * - Concurrent increments during a load window are preserved.
139
+ *
140
+ * **Missing file**: Treated as a fresh start (no error). In-memory
141
+ * counters are left unchanged — a subsequent `persist()` will create
142
+ * the file.
143
+ */
144
+ load(): Promise<void>;
145
+ /**
146
+ * Reset all counters to zero.
147
+ * Useful for testing or for clearing metrics between sessions.
148
+ */
149
+ reset(): void;
150
+ }
151
+ /**
152
+ * Set up periodic persistence for a MetricsStore.
153
+ *
154
+ * Starts a `setInterval` that calls `store.persist()` at the given interval.
155
+ * Returns a `clear()` function that stops the interval and does a final
156
+ * persist to ensure in-memory counters are flushed to disk.
157
+ *
158
+ * This is the integration point for the plugin lifecycle:
159
+ * 1. Plugin's `server()` creates a `MetricsStore` and calls `store.load()`.
160
+ * 2. Plugin's `server()` calls this helper to start periodic persistence.
161
+ * 3. Plugin's `dispose()` hook calls `clear()` to stop the interval and
162
+ * perform a final persist.
163
+ *
164
+ * @param store - The MetricsStore to persist periodically
165
+ * @param intervalMs - Interval in milliseconds (default: 30_000 = 30s)
166
+ * @param onError - Optional callback invoked when a persist attempt fails.
167
+ * Receives the error object so the caller can surface
168
+ * failures (e.g., via app logging) without crashing the
169
+ * interval.
170
+ */
171
+ export declare function setupMetricsPersistence(store: MetricsStore, intervalMs?: number, onError?: (err: unknown) => void): {
172
+ clear: () => Promise<void>;
173
+ };
174
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAIH,gCAAgC;AAChC,MAAM,WAAW,WAAW;IAC1B,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,cAAc,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uEAAuE;IACvE,eAAe,EAAE,MAAM,CAAC;CACzB;AAWD,yCAAyC;AACzC,wBAAgB,oBAAoB,IAAI,WAAW,CAQlD;AAID;;;;;;;;;;GAUG;AACH,qBAAa,YAAY;IACvB,4CAA4C;IAC5C,OAAO,CAAC,QAAQ,CAAuC;IAEvD,mEAAmE;IACnE,OAAO,CAAC,WAAW,CAAS;IAE5B;;;OAGG;gBACS,WAAW,CAAC,EAAE,MAAM;IAMhC,0DAA0D;IAC1D,OAAO,CAAC,MAAM;IAWd;;;;;;;OAOG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAMrD;;;;OAIG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKnC;;;;OAIG;IACH,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKzC;;;;;;OAMG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAOxC;;;OAGG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW;IAOzC;;;OAGG;IACH,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC;IAU5C;;;;;;;;;OASG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB9B;;;;;;;;;;;OAWG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAoC3B;;;OAGG;IACH,KAAK,IAAI,IAAI;CAGd;AAID;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,YAAY,EACnB,UAAU,GAAE,MAAe,EAC3B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GAC/B;IAAE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAwBhC"}