@wizeworks/lens-cloud 0.0.0-stage → 0.3.0-alpha.1

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 (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -2
  3. package/dist/alerts/evaluate.d.ts +10 -0
  4. package/dist/alerts/evaluate.d.ts.map +1 -0
  5. package/dist/alerts/evaluate.js +66 -0
  6. package/dist/alerts/evaluate.js.map +1 -0
  7. package/dist/alerts/notify.d.ts +17 -0
  8. package/dist/alerts/notify.d.ts.map +1 -0
  9. package/dist/alerts/notify.js +46 -0
  10. package/dist/alerts/notify.js.map +1 -0
  11. package/dist/app.d.ts +18 -0
  12. package/dist/app.d.ts.map +1 -0
  13. package/dist/app.js +2 -0
  14. package/dist/app.js.map +1 -0
  15. package/dist/auth/crypto.d.ts +19 -0
  16. package/dist/auth/crypto.d.ts.map +1 -0
  17. package/dist/auth/crypto.js +33 -0
  18. package/dist/auth/crypto.js.map +1 -0
  19. package/dist/auth/principal.d.ts +33 -0
  20. package/dist/auth/principal.d.ts.map +1 -0
  21. package/dist/auth/principal.js +64 -0
  22. package/dist/auth/principal.js.map +1 -0
  23. package/dist/auth/roles.d.ts +10 -0
  24. package/dist/auth/roles.d.ts.map +1 -0
  25. package/dist/auth/roles.js +9 -0
  26. package/dist/auth/roles.js.map +1 -0
  27. package/dist/cli-args.d.ts +5 -0
  28. package/dist/cli-args.d.ts.map +1 -0
  29. package/dist/cli-args.js +45 -0
  30. package/dist/cli-args.js.map +1 -0
  31. package/dist/cli.d.ts +3 -0
  32. package/dist/cli.d.ts.map +1 -0
  33. package/dist/cli.js +28 -0
  34. package/dist/cli.js.map +1 -0
  35. package/dist/config.d.ts +33 -0
  36. package/dist/config.d.ts.map +1 -0
  37. package/dist/config.js +20 -0
  38. package/dist/config.js.map +1 -0
  39. package/dist/db/db.d.ts +19 -0
  40. package/dist/db/db.d.ts.map +1 -0
  41. package/dist/db/db.js +57 -0
  42. package/dist/db/db.js.map +1 -0
  43. package/dist/db/migrate.d.ts +6 -0
  44. package/dist/db/migrate.d.ts.map +1 -0
  45. package/dist/db/migrate.js +25 -0
  46. package/dist/db/migrate.js.map +1 -0
  47. package/dist/db/schema.d.ts +3 -0
  48. package/dist/db/schema.d.ts.map +1 -0
  49. package/dist/db/schema.js +200 -0
  50. package/dist/db/schema.js.map +1 -0
  51. package/dist/http/http.d.ts +17 -0
  52. package/dist/http/http.d.ts.map +1 -0
  53. package/dist/http/http.js +62 -0
  54. package/dist/http/http.js.map +1 -0
  55. package/dist/http/limiter.d.ts +11 -0
  56. package/dist/http/limiter.d.ts.map +1 -0
  57. package/dist/http/limiter.js +31 -0
  58. package/dist/http/limiter.js.map +1 -0
  59. package/dist/http/router.d.ts +21 -0
  60. package/dist/http/router.d.ts.map +1 -0
  61. package/dist/http/router.js +28 -0
  62. package/dist/http/router.js.map +1 -0
  63. package/dist/index.d.ts +7 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +4 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/jobs.d.ts +4 -0
  68. package/dist/jobs.d.ts.map +1 -0
  69. package/dist/jobs.js +42 -0
  70. package/dist/jobs.js.map +1 -0
  71. package/dist/repo/alerts.d.ts +48 -0
  72. package/dist/repo/alerts.d.ts.map +1 -0
  73. package/dist/repo/alerts.js +38 -0
  74. package/dist/repo/alerts.js.map +1 -0
  75. package/dist/repo/audit.d.ts +11 -0
  76. package/dist/repo/audit.d.ts.map +1 -0
  77. package/dist/repo/audit.js +17 -0
  78. package/dist/repo/audit.js.map +1 -0
  79. package/dist/repo/metric-read.d.ts +12 -0
  80. package/dist/repo/metric-read.d.ts.map +1 -0
  81. package/dist/repo/metric-read.js +11 -0
  82. package/dist/repo/metric-read.js.map +1 -0
  83. package/dist/repo/metric-write.d.ts +9 -0
  84. package/dist/repo/metric-write.d.ts.map +1 -0
  85. package/dist/repo/metric-write.js +53 -0
  86. package/dist/repo/metric-write.js.map +1 -0
  87. package/dist/repo/orgs.d.ts +47 -0
  88. package/dist/repo/orgs.d.ts.map +1 -0
  89. package/dist/repo/orgs.js +75 -0
  90. package/dist/repo/orgs.js.map +1 -0
  91. package/dist/repo/projects.d.ts +37 -0
  92. package/dist/repo/projects.d.ts.map +1 -0
  93. package/dist/repo/projects.js +57 -0
  94. package/dist/repo/projects.js.map +1 -0
  95. package/dist/repo/trace-read.d.ts +28 -0
  96. package/dist/repo/trace-read.d.ts.map +1 -0
  97. package/dist/repo/trace-read.js +76 -0
  98. package/dist/repo/trace-read.js.map +1 -0
  99. package/dist/repo/trace-write.d.ts +9 -0
  100. package/dist/repo/trace-write.d.ts.map +1 -0
  101. package/dist/repo/trace-write.js +82 -0
  102. package/dist/repo/trace-write.js.map +1 -0
  103. package/dist/repo/users.d.ts +24 -0
  104. package/dist/repo/users.d.ts.map +1 -0
  105. package/dist/repo/users.js +65 -0
  106. package/dist/repo/users.js.map +1 -0
  107. package/dist/routes/alerts.d.ts +4 -0
  108. package/dist/routes/alerts.d.ts.map +1 -0
  109. package/dist/routes/alerts.js +99 -0
  110. package/dist/routes/alerts.js.map +1 -0
  111. package/dist/routes/auth.d.ts +4 -0
  112. package/dist/routes/auth.d.ts.map +1 -0
  113. package/dist/routes/auth.js +106 -0
  114. package/dist/routes/auth.js.map +1 -0
  115. package/dist/routes/data.d.ts +7 -0
  116. package/dist/routes/data.d.ts.map +1 -0
  117. package/dist/routes/data.js +74 -0
  118. package/dist/routes/data.js.map +1 -0
  119. package/dist/routes/index.d.ts +5 -0
  120. package/dist/routes/index.d.ts.map +1 -0
  121. package/dist/routes/index.js +23 -0
  122. package/dist/routes/index.js.map +1 -0
  123. package/dist/routes/ingest.d.ts +8 -0
  124. package/dist/routes/ingest.d.ts.map +1 -0
  125. package/dist/routes/ingest.js +38 -0
  126. package/dist/routes/ingest.js.map +1 -0
  127. package/dist/routes/mcp.d.ts +4 -0
  128. package/dist/routes/mcp.d.ts.map +1 -0
  129. package/dist/routes/mcp.js +52 -0
  130. package/dist/routes/mcp.js.map +1 -0
  131. package/dist/routes/metrics.d.ts +13 -0
  132. package/dist/routes/metrics.d.ts.map +1 -0
  133. package/dist/routes/metrics.js +29 -0
  134. package/dist/routes/metrics.js.map +1 -0
  135. package/dist/routes/models.d.ts +4 -0
  136. package/dist/routes/models.d.ts.map +1 -0
  137. package/dist/routes/models.js +71 -0
  138. package/dist/routes/models.js.map +1 -0
  139. package/dist/routes/orgs.d.ts +4 -0
  140. package/dist/routes/orgs.d.ts.map +1 -0
  141. package/dist/routes/orgs.js +96 -0
  142. package/dist/routes/orgs.js.map +1 -0
  143. package/dist/routes/projects.d.ts +4 -0
  144. package/dist/routes/projects.d.ts.map +1 -0
  145. package/dist/routes/projects.js +54 -0
  146. package/dist/routes/projects.js.map +1 -0
  147. package/dist/routes/shares.d.ts +11 -0
  148. package/dist/routes/shares.d.ts.map +1 -0
  149. package/dist/routes/shares.js +61 -0
  150. package/dist/routes/shares.js.map +1 -0
  151. package/dist/scale-exec.d.ts +11 -0
  152. package/dist/scale-exec.d.ts.map +1 -0
  153. package/dist/scale-exec.js +26 -0
  154. package/dist/scale-exec.js.map +1 -0
  155. package/dist/server.d.ts +13 -0
  156. package/dist/server.d.ts.map +1 -0
  157. package/dist/server.js +95 -0
  158. package/dist/server.js.map +1 -0
  159. package/dist/settle.d.ts +26 -0
  160. package/dist/settle.d.ts.map +1 -0
  161. package/dist/settle.js +85 -0
  162. package/dist/settle.js.map +1 -0
  163. package/package.json +63 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WizeWorks LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,64 @@
1
- # Temporary Holding Version
1
+ # @wizeworks/lens-cloud
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The cloud service of [WizeLens](https://github.com/brandonkorous/wizelens): the full dashboard for many teams, with accounts,
4
+ projects, 30 days of history computed on the server, alerts, share links, and AI access through read keys.
5
+
6
+ ```bash
7
+ # Try it on your machine (Postgres in-process, data in ./.wizelens-cloud)
8
+ npx @wizeworks/lens-cloud
9
+
10
+ # For a team: Postgres in Docker
11
+ POSTGRES_PASSWORD=<a long secret> docker compose -f deploy/docker-compose.cloud.yml up -d
12
+ ```
13
+
14
+ Open http://localhost:5970/_wizelens/ and create the first account. Make an ingest key on the Team page, then on each app:
15
+
16
+ ```bash
17
+ WIZELENS_HUB=http://localhost:5970
18
+ WIZELENS_HUB_TOKEN=wl_ik_...
19
+ ```
20
+
21
+ ## Options
22
+
23
+ | Flag | Environment variable | Default |
24
+ |---|---|---|
25
+ | `--database <url>` | `DATABASE_URL` | `pglite:./.wizelens-cloud` (`postgres://…`, `pglite:<folder>`, or `memory`) |
26
+ | `--port <n>` | `PORT` | `5970` |
27
+ | `--host <addr>` | `WIZELENS_CLOUD_HOST` | `0.0.0.0` |
28
+ | `--public-url <url>` | `WIZELENS_CLOUD_PUBLIC_URL` | the request's own address |
29
+ | `--signup <invite\|open>` | `WIZELENS_CLOUD_SIGNUP` | `invite` |
30
+ | `--retention-days <n>` | `WIZELENS_CLOUD_RETENTION_DAYS` | `30` |
31
+ | `--allow-private-webhooks` | `WIZELENS_CLOUD_ALLOW_PRIVATE_WEBHOOKS=1` | off |
32
+
33
+ Put it behind https. With an https public URL, the sign-in cookie is marked secure. PGlite is for trials and tests; use Postgres
34
+ for real teams. Check a running cloud end to end with `node scripts/cloud-smoke.mjs <url>` from the repo (it needs a fresh database).
35
+
36
+ ## Stats
37
+
38
+ Pages are computed from stats tables, never from a sample of traces: per-trace facts (request, trace, log, and slow span rows) and
39
+ rollups in 10 s, 5 min, and 1 h buckets, so 30-day pages on busy projects answer in well under a second. A background job settles
40
+ traces a few seconds after their last span (every 3 s, one instance at a time through a Postgres advisory lock); pages say
41
+ "Preparing stats" while traces wait, for example right after an upgrade, when existing traces are backfilled newest first.
42
+ Alerts on traces read the same tables. See `docs/CONTRACT.md`, "Stats tables".
43
+
44
+ ## Metrics
45
+
46
+ Apps send OpenTelemetry metrics to `POST /v1/metrics` with the ingest key (JSON or protobuf; gauges, sums, histograms,
47
+ exponential histograms, and summaries). WizeLens add-ons send theirs in their batches. The Metrics page reads
48
+ `GET /_wizelens/api/models/metrics?project=&range=&env=&service=&metric=&group=&stat=&q=` (member or read key), computed from
49
+ metric rollups (per series, in 10 s, 5 min, and 1 h buckets, written as points arrive), so every range reads all of its points.
50
+ Points and rollups follow the organization's retention. Alert rules can watch
51
+ one metric: `{ "metric": "metric", "metricName": "process.cpu.utilization", "metricStat": "avg", "metricService": "", ... }`, with
52
+ `metricStat` one of `last`, `avg`, `max`, `p95`, `p99`, `rate`, and the threshold in the metric's own unit (seconds, bytes, or
53
+ 0 to 1 for ratios). Read keys get `list_metrics` and `get_metric` over MCP.
54
+
55
+ ## In code
56
+
57
+ ```ts
58
+ import { createCloud } from "@wizeworks/lens-cloud";
59
+
60
+ const cloud = await createCloud({ database: process.env.DATABASE_URL, publicUrl: "https://wizelens.example.com" });
61
+ console.log(await cloud.listen());
62
+ ```
63
+
64
+ The API, roles, keys, and storage are in [CONTRACT.md](https://github.com/brandonkorous/wizelens/blob/main/docs/CONTRACT.md#cloud-v07-phase-3). MIT licensed.
@@ -0,0 +1,10 @@
1
+ import type { CloudApp } from "../app.js";
2
+ import { type AlertRule } from "../repo/alerts.js";
3
+ import { type Notice } from "./notify.js";
4
+ /** Send a notice and keep it as an event. */
5
+ export declare function notify(app: CloudApp, rule: AlertRule, state: Notice["state"], value: number | null, now: number): Promise<string>;
6
+ /** Check one rule. It notifies only when the state changes: ok to firing, or firing to ok. */
7
+ export declare function evaluateRule(app: CloudApp, rule: AlertRule, now: number): Promise<void>;
8
+ /** Check every enabled rule once. One failing rule does not stop the others. */
9
+ export declare function evaluateAll(app: CloudApp): Promise<void>;
10
+ //# sourceMappingURL=evaluate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../../src/alerts/evaluate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAY,KAAK,SAAS,EAAgC,MAAM,mBAAmB,CAAC;AAG3F,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAyCnD,6CAA6C;AAC7C,wBAAsB,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKvI;AAED,8FAA8F;AAC9F,wBAAsB,YAAY,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM7F;AAED,gFAAgF;AAChF,wBAAsB,WAAW,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAK9D"}
@@ -0,0 +1,66 @@
1
+ import { alertBreached, alignNow, alertContext, alertMessage, alertPage, computePage, metricAlertMessage, metricAlertUnit, metricAlertValueOf } from "@wizeworks/lens-core";
2
+ import { addEvent, enabledRules, saveEvaluation } from "../repo/alerts.js";
3
+ import { loadMetricAlertData } from "../repo/metric-read.js";
4
+ import { cloudExecutor, projectRetentionMs } from "../scale-exec.js";
5
+ import { deliver } from "./notify.js";
6
+ /** Names for the notice: project and the dashboard link. */
7
+ async function noticeOf(app, rule, state, value, now) {
8
+ const [p] = await app.db.query("SELECT name FROM projects WHERE id = $1", [rule.projectId]);
9
+ const base = app.config.publicUrl ?? `http://localhost:${app.config.port}`;
10
+ return {
11
+ rule: rule.name,
12
+ project: p?.name ?? rule.projectId,
13
+ state,
14
+ message: await messageOf(app, rule, state, value, now),
15
+ value,
16
+ threshold: rule.threshold,
17
+ timeMs: now,
18
+ url: `${base}/_wizelens/#/alerts?project=${rule.projectId}`,
19
+ };
20
+ }
21
+ /** One OpenTelemetry metric over the rule's window (from its rollups), and a sample series for its unit. */
22
+ async function measureMetric(app, rule, now) {
23
+ const at = alignNow(now);
24
+ const q = { projectId: rule.projectId, env: rule.env || undefined, service: rule.metricService || undefined };
25
+ const data = await loadMetricAlertData(app.db, q, rule.metricName, { from: at - rule.windowMs, to: at }, at);
26
+ const stat = rule.metricStat || "avg";
27
+ return { value: metricAlertValueOf(data, stat), sample: data.series[0], stat };
28
+ }
29
+ async function messageOf(app, rule, state, value, now) {
30
+ if (rule.metric !== "metric")
31
+ return alertMessage(rule.metric, rule.op, value, rule.threshold, state);
32
+ const { sample, stat } = await measureMetric(app, rule, now);
33
+ const unit = sample ? metricAlertUnit(sample, stat) : "";
34
+ return metricAlertMessage(rule.metricName, stat, rule.op, value, rule.threshold, unit, state);
35
+ }
36
+ /** The metric over the rule's window: from the stats tables, or from the metric's points. */
37
+ async function measure(app, rule, now) {
38
+ if (rule.metric === "metric")
39
+ return (await measureMetric(app, rule, now)).value;
40
+ const retentionMs = await projectRetentionMs(app.db, rule.projectId, app.config.retentionDays);
41
+ return computePage(alertPage(rule.metric), alertContext(rule.windowMs, alignNow(now)), cloudExecutor(app.db, rule.projectId, retentionMs, rule.env || undefined));
42
+ }
43
+ /** Send a notice and keep it as an event. */
44
+ export async function notify(app, rule, state, value, now) {
45
+ const notice = await noticeOf(app, rule, state, value, now);
46
+ const delivery = await deliver(rule.channels, notice, app.config.fetch, app.config.allowPrivateWebhooks);
47
+ await addEvent(app.db, rule, { kind: state, value, message: notice.message, timeMs: now, delivery });
48
+ return delivery;
49
+ }
50
+ /** Check one rule. It notifies only when the state changes: ok to firing, or firing to ok. */
51
+ export async function evaluateRule(app, rule, now) {
52
+ const value = await measure(app, rule, now);
53
+ const firing = rule.metric === "metric" ? value !== null && (rule.op === ">" ? value > rule.threshold : value < rule.threshold) : alertBreached(rule.metric, rule.op, value, rule.threshold);
54
+ const next = firing ? "firing" : "ok";
55
+ await saveEvaluation(app.db, rule.id, next, value, now);
56
+ if (next !== rule.state)
57
+ await notify(app, rule, firing ? "fired" : "resolved", value, now);
58
+ }
59
+ /** Check every enabled rule once. One failing rule does not stop the others. */
60
+ export async function evaluateAll(app) {
61
+ const now = app.config.now();
62
+ for (const rule of await enabledRules(app.db)) {
63
+ await evaluateRule(app, rule, now).catch((err) => console.error(`[wizelens-cloud] alert ${rule.id} failed:`, err));
64
+ }
65
+ }
66
+ //# sourceMappingURL=evaluate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../../src/alerts/evaluate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,kBAAkB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE5K,OAAO,EAAE,QAAQ,EAAkB,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC3F,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,OAAO,EAAe,MAAM,aAAa,CAAC;AAEnD,4DAA4D;AAC5D,KAAK,UAAU,QAAQ,CAAC,GAAa,EAAE,IAAe,EAAE,KAAsB,EAAE,KAAoB,EAAE,GAAW;IAC/G,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,KAAK,CAAmB,yCAAyC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IAC9G,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,oBAAoB,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IAC3E,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,OAAO,EAAE,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC,SAAS;QAClC,KAAK;QACL,OAAO,EAAE,MAAM,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC;QACtD,KAAK;QACL,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,MAAM,EAAE,GAAG;QACX,GAAG,EAAE,GAAG,IAAI,+BAA+B,IAAI,CAAC,SAAS,EAAE;KAC5D,CAAC;AACJ,CAAC;AAED,4GAA4G;AAC5G,KAAK,UAAU,aAAa,CAAC,GAAa,EAAE,IAAe,EAAE,GAAW;IACtE,MAAM,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IACzB,MAAM,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,aAAa,IAAI,SAAS,EAAE,CAAC;IAC9G,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IAC7G,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,IAAI,KAAK,CAAC;IACtC,OAAO,EAAE,KAAK,EAAE,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;AACjF,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,GAAa,EAAE,IAAe,EAAE,KAAsB,EAAE,KAAoB,EAAE,GAAW;IAChH,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACtG,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;IAC7D,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACzD,OAAO,kBAAkB,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;AAChG,CAAC;AAED,6FAA6F;AAC7F,KAAK,UAAU,OAAO,CAAC,GAAa,EAAE,IAAe,EAAE,GAAW;IAChE,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,CAAC,MAAM,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;IACjF,MAAM,WAAW,GAAG,MAAM,kBAAkB,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAC/F,OAAO,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,WAAW,EAAE,IAAI,CAAC,GAAG,IAAI,SAAS,CAAC,CAAC,CAAC;AACpK,CAAC;AAED,6CAA6C;AAC7C,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,GAAa,EAAE,IAAe,EAAE,KAAsB,EAAE,KAAoB,EAAE,GAAW;IACpH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IACzG,MAAM,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;IACrG,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAa,EAAE,IAAe,EAAE,GAAW;IAC5E,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC7L,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;IACtC,MAAM,cAAc,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;IACxD,IAAI,IAAI,KAAK,IAAI,CAAC,KAAK;QAAE,MAAM,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;AAC9F,CAAC;AAED,gFAAgF;AAChF,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAa;IAC7C,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,MAAM,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;QAC9C,MAAM,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,0BAA0B,IAAI,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;IAC9H,CAAC;AACH,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { Channel } from "../repo/alerts.js";
2
+ export interface Notice {
3
+ rule: string;
4
+ project: string;
5
+ state: "fired" | "resolved" | "test";
6
+ message: string;
7
+ value: number | null;
8
+ threshold: number;
9
+ timeMs: number;
10
+ /** Link to the project in the dashboard. */
11
+ url: string;
12
+ }
13
+ /** A webhook may not point into the service's own network (cloud metadata, databases). Self-hosts can allow it. */
14
+ export declare function checkTarget(url: string, allowPrivate: boolean): Promise<string | null>;
15
+ /** Send to every channel. Returns one line per channel, for the event's delivery note. */
16
+ export declare function deliver(channels: readonly Channel[], n: Notice, http: typeof fetch, allowPrivate: boolean): Promise<string>;
17
+ //# sourceMappingURL=notify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/alerts/notify.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;CACb;AAKD,mHAAmH;AACnH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAY5F;AASD,0FAA0F;AAC1F,wBAAsB,OAAO,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAcjI"}
@@ -0,0 +1,46 @@
1
+ import { lookup } from "node:dns/promises";
2
+ import { isIP } from "node:net";
3
+ const PRIVATE_V4 = [/^10\./, /^127\./, /^169\.254\./, /^172\.(1[6-9]|2\d|3[01])\./, /^192\.168\./, /^0\./, /^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./];
4
+ const isPrivate = (ip) => (isIP(ip) === 6 ? /^(::1|fc|fd|fe80|::ffff:(10|127|169\.254|192\.168)\.)/i.test(ip) || ip === "::" : PRIVATE_V4.some((r) => r.test(ip)));
5
+ /** A webhook may not point into the service's own network (cloud metadata, databases). Self-hosts can allow it. */
6
+ export async function checkTarget(url, allowPrivate) {
7
+ let u;
8
+ try {
9
+ u = new URL(url);
10
+ }
11
+ catch {
12
+ return "Not a valid URL.";
13
+ }
14
+ if (u.protocol !== "https:" && !(allowPrivate && u.protocol === "http:"))
15
+ return "Use an https:// URL.";
16
+ if (allowPrivate)
17
+ return null;
18
+ const addresses = isIP(u.hostname) ? [u.hostname] : (await lookup(u.hostname, { all: true }).catch(() => [])).map((a) => a.address);
19
+ if (addresses.length === 0)
20
+ return "The host name does not resolve.";
21
+ return addresses.some(isPrivate) ? "Webhooks cannot point at private or local addresses." : null;
22
+ }
23
+ function bodyOf(channel, n) {
24
+ if (channel.kind === "webhook")
25
+ return n;
26
+ const icon = n.state === "fired" ? ":rotating_light:" : n.state === "resolved" ? ":white_check_mark:" : ":bell:";
27
+ const verb = n.state === "fired" ? "Alert" : n.state === "resolved" ? "Resolved" : "Test";
28
+ return { text: `${icon} *${verb}: ${n.rule}* (${n.project})\n${n.message}\n<${n.url}|Open WizeLens>` };
29
+ }
30
+ /** Send to every channel. Returns one line per channel, for the event's delivery note. */
31
+ export async function deliver(channels, n, http, allowPrivate) {
32
+ const results = await Promise.all(channels.map(async (c) => {
33
+ const problem = await checkTarget(c.url, allowPrivate);
34
+ if (problem)
35
+ return `${c.kind}: ${problem}`;
36
+ try {
37
+ const res = await http(c.url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(bodyOf(c, n)), signal: AbortSignal.timeout(5000), redirect: "error" });
38
+ return `${c.kind}: ${res.ok ? "sent" : `HTTP ${res.status}`}`;
39
+ }
40
+ catch (err) {
41
+ return `${c.kind}: ${err instanceof Error ? err.message : "failed"}`;
42
+ }
43
+ }));
44
+ return results.join("; ") || "no channels";
45
+ }
46
+ //# sourceMappingURL=notify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.js","sourceRoot":"","sources":["../../src/alerts/notify.ts"],"names":[],"mappings":"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"}
package/dist/app.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { CloudConfig } from "./config.js";
3
+ import type { Db } from "./db/db.js";
4
+ import type { RouteCtx } from "./http/router.js";
5
+ /** Everything a request handler needs. */
6
+ export interface CloudApp {
7
+ db: Db;
8
+ config: CloudConfig;
9
+ version: string;
10
+ startedMs: number;
11
+ /** The address for links: the public URL, or the one this request came in on. */
12
+ baseUrl(req: IncomingMessage): string;
13
+ }
14
+ export interface Ctx extends RouteCtx {
15
+ app: CloudApp;
16
+ }
17
+ export type { IncomingMessage, ServerResponse };
18
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,0CAA0C;AAC1C,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,EAAE,CAAC;IACP,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,OAAO,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAAC;CACvC;AAED,MAAM,WAAW,GAAI,SAAQ,QAAQ;IACnC,GAAG,EAAE,QAAQ,CAAC;CACf;AAED,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,CAAC"}
package/dist/app.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":""}
@@ -0,0 +1,19 @@
1
+ /** "scrypt$<salt>$<hash>", both base64url. */
2
+ export declare function hashPassword(password: string): Promise<string>;
3
+ export declare function checkPassword(password: string, stored: string): Promise<boolean>;
4
+ /** Kinds of secret tokens. The prefix tells a person what a token is for. */
5
+ export declare const TOKEN_PREFIX: {
6
+ readonly session: "wl_s_";
7
+ readonly invite: "wl_inv_";
8
+ readonly ingest: "wl_ik_";
9
+ readonly read: "wl_rk_";
10
+ readonly share: "wl_sh_";
11
+ };
12
+ export type TokenKind = keyof typeof TOKEN_PREFIX;
13
+ /** A new random token. Only its hash is stored. */
14
+ export declare const newToken: (kind: TokenKind) => string;
15
+ export declare const hashToken: (token: string) => string;
16
+ export declare const newId: () => string;
17
+ /** A URL-safe short name from free text, for example "My Shop!" -> "my-shop". */
18
+ export declare const slugOf: (text: string) => string;
19
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/auth/crypto.ts"],"names":[],"mappings":"AAOA,8CAA8C;AAC9C,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAIpE;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAMtF;AAED,6EAA6E;AAC7E,eAAO,MAAM,YAAY;aAAK,OAAO,EAAE,OAAO;aAAE,MAAM,EAAE,SAAS;aAAE,MAAM,EAAE,QAAQ;aAAE,IAAI,EAAE,QAAQ;aAAE,KAAK,EAAE,QAAQ;CAAW,CAAC;AAChI,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,YAAY,CAAC;AAElD,mDAAmD;AACnD,eAAO,MAAM,QAAQ,SAAU,SAAS,KAAG,MAAyE,CAAC;AAErH,eAAO,MAAM,SAAS,UAAW,MAAM,KAAG,MAA0D,CAAC;AAErG,eAAO,MAAM,KAAK,QAAO,MAAsB,CAAC;AAEhD,iFAAiF;AACjF,eAAO,MAAM,MAAM,SAAU,MAAM,KAAG,MAMX,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { createHash, randomBytes, randomUUID, scrypt, timingSafeEqual } from "node:crypto";
2
+ import { promisify } from "node:util";
3
+ const scryptAsync = promisify(scrypt);
4
+ const SCRYPT = { N: 16384, r: 8, p: 1, maxmem: 64 * 1024 * 1024 };
5
+ const KEY_LENGTH = 32;
6
+ /** "scrypt$<salt>$<hash>", both base64url. */
7
+ export async function hashPassword(password) {
8
+ const salt = randomBytes(16);
9
+ const key = await scryptAsync(password, salt, KEY_LENGTH, SCRYPT);
10
+ return `scrypt$${salt.toString("base64url")}$${key.toString("base64url")}`;
11
+ }
12
+ export async function checkPassword(password, stored) {
13
+ const [kind, salt, hash] = stored.split("$");
14
+ if (kind !== "scrypt" || !salt || !hash)
15
+ return false;
16
+ const expected = Buffer.from(hash, "base64url");
17
+ const key = await scryptAsync(password, Buffer.from(salt, "base64url"), expected.length, SCRYPT);
18
+ return key.length === expected.length && timingSafeEqual(key, expected);
19
+ }
20
+ /** Kinds of secret tokens. The prefix tells a person what a token is for. */
21
+ export const TOKEN_PREFIX = { session: "wl_s_", invite: "wl_inv_", ingest: "wl_ik_", read: "wl_rk_", share: "wl_sh_" };
22
+ /** A new random token. Only its hash is stored. */
23
+ export const newToken = (kind) => `${TOKEN_PREFIX[kind]}${randomBytes(24).toString("base64url")}`;
24
+ export const hashToken = (token) => createHash("sha256").update(token).digest("hex");
25
+ export const newId = () => randomUUID();
26
+ /** A URL-safe short name from free text, for example "My Shop!" -> "my-shop". */
27
+ export const slugOf = (text) => text
28
+ .toLowerCase()
29
+ .normalize("NFKD")
30
+ .replace(/[^a-z0-9]+/g, "-")
31
+ .replace(/^-+|-+$/g, "")
32
+ .slice(0, 40) || "item";
33
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/auth/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3F,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,CAAyF,CAAC;AAC9H,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;AAClE,MAAM,UAAU,GAAG,EAAE,CAAC;AAEtB,8CAA8C;AAC9C,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAgB;IACjD,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC7B,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;IAClE,OAAO,UAAU,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;AAC7E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB,EAAE,MAAc;IAClE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7C,IAAI,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IAChD,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjG,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,IAAI,eAAe,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAC1E,CAAC;AAED,6EAA6E;AAC7E,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAW,CAAC;AAGhI,mDAAmD;AACnD,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,IAAe,EAAU,EAAE,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;AAErH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAErG,MAAM,CAAC,MAAM,KAAK,GAAG,GAAW,EAAE,CAAC,UAAU,EAAE,CAAC;AAEhD,iFAAiF;AACjF,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,IAAY,EAAU,EAAE,CAC7C,IAAI;KACD,WAAW,EAAE;KACb,SAAS,CAAC,MAAM,CAAC;KACjB,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;KAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;KACvB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { Ctx } from "../app.js";
2
+ import { type ApiKey, type Project } from "../repo/projects.js";
3
+ import { type User } from "../repo/users.js";
4
+ import { type Permission, type Role } from "./roles.js";
5
+ export declare const SESSION_COOKIE = "wizelens_session";
6
+ /** Who is calling: a signed-in person, or a read key (AI agents and scripts). */
7
+ export type Principal = {
8
+ kind: "user";
9
+ user: User;
10
+ } | {
11
+ kind: "key";
12
+ key: ApiKey & {
13
+ orgId: string;
14
+ };
15
+ };
16
+ export declare function principalOf(ctx: Ctx): Promise<Principal | undefined>;
17
+ /** A signed-in person, or 401. Changes also need the dashboard header, so other sites cannot send them. */
18
+ export declare function requireUser(ctx: Ctx): Promise<User>;
19
+ /** The caller's role in an organization, when it allows `permission`; otherwise 403 (or 404 when not a member). */
20
+ export declare function requireOrg(ctx: Ctx, orgId: string, permission: Permission): Promise<{
21
+ user: User;
22
+ role: Role;
23
+ }>;
24
+ export interface ProjectAccess {
25
+ project: Project;
26
+ /** "user:<id>" or "key:<id>", for the audit log. */
27
+ actor: string;
28
+ role: Role;
29
+ principal: Principal;
30
+ }
31
+ /** Access to one project's data, for a person or a read key. */
32
+ export declare function requireProject(ctx: Ctx, projectId: string, permission?: Permission): Promise<ProjectAccess>;
33
+ //# sourceMappingURL=principal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"principal.d.ts","sourceRoot":"","sources":["../../src/auth/principal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAGrC,OAAO,EAAE,KAAK,MAAM,EAAc,KAAK,OAAO,EAAgB,MAAM,qBAAqB,CAAC;AAC1F,OAAO,EAAE,KAAK,IAAI,EAAiB,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAO,KAAK,UAAU,EAAE,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AAE7D,eAAO,MAAM,cAAc,qBAAqB,CAAC;AAEjD,iFAAiF;AACjF,MAAM,MAAM,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAExG,wBAAsB,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAU1E;AAED,2GAA2G;AAC3G,wBAAsB,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAMzD;AAED,mHAAmH;AACnH,wBAAsB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAMrH;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,IAAI,CAAC;IACX,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,gEAAgE;AAChE,wBAAsB,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAE,UAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAWzH"}
@@ -0,0 +1,64 @@
1
+ import { bearerOf, cookieOf, fail, HttpError } from "../http/http.js";
2
+ import { membership } from "../repo/orgs.js";
3
+ import { keyByToken, projectInOrg } from "../repo/projects.js";
4
+ import { userBySession } from "../repo/users.js";
5
+ import { can } from "./roles.js";
6
+ export const SESSION_COOKIE = "wizelens_session";
7
+ export async function principalOf(ctx) {
8
+ const now = ctx.app.config.now();
9
+ const bearer = bearerOf(ctx.req);
10
+ if (bearer?.startsWith("wl_rk_")) {
11
+ const key = await keyByToken(ctx.app.db, bearer, now);
12
+ return key?.kind === "read" ? { kind: "key", key } : undefined;
13
+ }
14
+ const token = cookieOf(ctx.req, SESSION_COOKIE);
15
+ const user = token ? await userBySession(ctx.app.db, token, now) : undefined;
16
+ return user ? { kind: "user", user } : undefined;
17
+ }
18
+ /** A signed-in person, or 401. Changes also need the dashboard header, so other sites cannot send them. */
19
+ export async function requireUser(ctx) {
20
+ const p = await principalOf(ctx);
21
+ if (p?.kind !== "user")
22
+ return fail(401, "Sign in first.");
23
+ const method = ctx.req.method ?? "GET";
24
+ if (method !== "GET" && ctx.req.headers["x-wizelens-client"] !== "dashboard")
25
+ fail(403, "Missing the X-WizeLens-Client header.");
26
+ return p.user;
27
+ }
28
+ /** The caller's role in an organization, when it allows `permission`; otherwise 403 (or 404 when not a member). */
29
+ export async function requireOrg(ctx, orgId, permission) {
30
+ const user = await requireUser(ctx);
31
+ const m = await membership(ctx.app.db, user.id, orgId);
32
+ if (!m)
33
+ fail(404, "Organization not found.");
34
+ if (!can(m.role, permission))
35
+ fail(403, `Your role (${m.role}) cannot do this.`);
36
+ return { user, role: m.role };
37
+ }
38
+ /** Access to one project's data, for a person or a read key. */
39
+ export async function requireProject(ctx, projectId, permission = "read") {
40
+ const p = await principalOf(ctx);
41
+ if (!p)
42
+ return fail(401, "Sign in, or send a read key.");
43
+ if (p.kind === "key")
44
+ return keyAccess(ctx, p, projectId, permission);
45
+ const [row] = await ctx.app.db.query("SELECT org_id FROM projects WHERE id = $1", [projectId]);
46
+ const m = row ? await membership(ctx.app.db, p.user.id, row.org_id) : undefined;
47
+ const project = m ? await projectInOrg(ctx.app.db, m.id, projectId) : undefined;
48
+ if (!m || !project)
49
+ return fail(404, "Project not found.");
50
+ if (!can(m.role, permission))
51
+ fail(403, `Your role (${m.role}) cannot do this.`);
52
+ if (permission !== "read")
53
+ await requireUser(ctx);
54
+ return { project, actor: `user:${p.user.id}`, role: m.role, principal: p };
55
+ }
56
+ async function keyAccess(ctx, p, projectId, permission) {
57
+ if (permission !== "read")
58
+ throw new HttpError(403, "Read keys can only read.");
59
+ const project = p.key.projectIds.includes(projectId) ? await projectInOrg(ctx.app.db, p.key.orgId, projectId) : undefined;
60
+ if (!project)
61
+ return fail(404, "Project not found.");
62
+ return { project, actor: `key:${p.key.id}`, role: "viewer", principal: p };
63
+ }
64
+ //# sourceMappingURL=principal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"principal.js","sourceRoot":"","sources":["../../src/auth/principal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /** Roles in an organization, from most to least power. */
2
+ export declare const ROLES: readonly ["owner", "admin", "member", "viewer"];
3
+ export type Role = (typeof ROLES)[number];
4
+ export declare const isRole: (v: unknown) => v is Role;
5
+ /** What each role may do. Read: data, members. Write: alerts, shares. Manage: projects, keys, members, settings. */
6
+ export type Permission = "read" | "write" | "manage" | "own";
7
+ export declare const can: (role: Role, permission: Permission) => boolean;
8
+ /** An admin may not give a role above their own. */
9
+ export declare const canGrant: (actor: Role, target: Role) => boolean;
10
+ //# sourceMappingURL=roles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roles.d.ts","sourceRoot":"","sources":["../../src/auth/roles.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,eAAO,MAAM,KAAK,YAAI,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAU,CAAC;AACrE,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1C,eAAO,MAAM,MAAM,MAAO,OAAO,KAAG,CAAC,IAAI,IAAyE,CAAC;AAEnH,oHAAoH;AACpH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;AAK7D,eAAO,MAAM,GAAG,SAAU,IAAI,cAAc,UAAU,KAAG,OAA2C,CAAC;AAErG,oDAAoD;AACpD,eAAO,MAAM,QAAQ,UAAW,IAAI,UAAU,IAAI,KAAG,OAAwC,CAAC"}
@@ -0,0 +1,9 @@
1
+ /** Roles in an organization, from most to least power. */
2
+ export const ROLES = ["owner", "admin", "member", "viewer"];
3
+ export const isRole = (v) => typeof v === "string" && ROLES.includes(v);
4
+ const LEVEL = { owner: 3, admin: 2, member: 1, viewer: 0 };
5
+ const NEEDS = { read: 0, write: 1, manage: 2, own: 3 };
6
+ export const can = (role, permission) => LEVEL[role] >= NEEDS[permission];
7
+ /** An admin may not give a role above their own. */
8
+ export const canGrant = (actor, target) => LEVEL[actor] >= LEVEL[target];
9
+ //# sourceMappingURL=roles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roles.js","sourceRoot":"","sources":["../../src/auth/roles.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAU,CAAC;AAGrE,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAU,EAAa,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAK,KAA2B,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAKnH,MAAM,KAAK,GAAyB,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;AACjF,MAAM,KAAK,GAA+B,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;AAEnF,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,IAAU,EAAE,UAAsB,EAAW,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC;AAErG,oDAAoD;AACpD,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAW,EAAE,MAAY,EAAW,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { CloudOptions } from "./config.js";
2
+ export declare const USAGE = "wizelens-cloud: the WizeLens service for many teams.\n\nUsage: wizelens-cloud [options]\n\n --database <url> postgres://..., pglite:<folder>, or memory.\n Default pglite:./.wizelens-cloud. (DATABASE_URL)\n --port <n> Default 5970. (PORT)\n --host <addr> Default 0.0.0.0. (WIZELENS_CLOUD_HOST)\n --public-url <url> The address people use, for links. (WIZELENS_CLOUD_PUBLIC_URL)\n --signup <invite|open> Who can make an account. Default invite. (WIZELENS_CLOUD_SIGNUP)\n --retention-days <n> Default for new organizations. Default 30. (WIZELENS_CLOUD_RETENTION_DAYS)\n --allow-private-webhooks Let alert webhooks reach private addresses. (WIZELENS_CLOUD_ALLOW_PRIVATE_WEBHOOKS=1)\n --help Show this help.\n";
3
+ /** Options from the command line, then from the environment. */
4
+ export declare function optionsFromArgs(argv: string[], env: NodeJS.ProcessEnv): CloudOptions | "help";
5
+ //# sourceMappingURL=cli-args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-args.d.ts","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAc,MAAM,aAAa,CAAC;AAE5D,eAAO,MAAM,KAAK,83BAajB,CAAC;AAKF,gEAAgE;AAChE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,YAAY,GAAG,MAAM,CAwB7F"}
@@ -0,0 +1,45 @@
1
+ import { parseArgs } from "node:util";
2
+ export const USAGE = `wizelens-cloud: the WizeLens service for many teams.
3
+
4
+ Usage: wizelens-cloud [options]
5
+
6
+ --database <url> postgres://..., pglite:<folder>, or memory.
7
+ Default pglite:./.wizelens-cloud. (DATABASE_URL)
8
+ --port <n> Default 5970. (PORT)
9
+ --host <addr> Default 0.0.0.0. (WIZELENS_CLOUD_HOST)
10
+ --public-url <url> The address people use, for links. (WIZELENS_CLOUD_PUBLIC_URL)
11
+ --signup <invite|open> Who can make an account. Default invite. (WIZELENS_CLOUD_SIGNUP)
12
+ --retention-days <n> Default for new organizations. Default 30. (WIZELENS_CLOUD_RETENTION_DAYS)
13
+ --allow-private-webhooks Let alert webhooks reach private addresses. (WIZELENS_CLOUD_ALLOW_PRIVATE_WEBHOOKS=1)
14
+ --help Show this help.
15
+ `;
16
+ const int = (v) => (v && /^\d+$/.test(v) ? Number(v) : undefined);
17
+ const signupOf = (v) => (v === "open" || v === "invite" ? v : undefined);
18
+ /** Options from the command line, then from the environment. */
19
+ export function optionsFromArgs(argv, env) {
20
+ const { values: a } = parseArgs({
21
+ args: argv,
22
+ options: {
23
+ database: { type: "string" },
24
+ port: { type: "string" },
25
+ host: { type: "string" },
26
+ "public-url": { type: "string" },
27
+ signup: { type: "string" },
28
+ "retention-days": { type: "string" },
29
+ "allow-private-webhooks": { type: "boolean" },
30
+ help: { type: "boolean", short: "h" },
31
+ },
32
+ });
33
+ if (a.help)
34
+ return "help";
35
+ return {
36
+ database: a.database ?? env.DATABASE_URL,
37
+ port: int(a.port ?? env.PORT),
38
+ host: a.host ?? env.WIZELENS_CLOUD_HOST,
39
+ publicUrl: a["public-url"] ?? env.WIZELENS_CLOUD_PUBLIC_URL,
40
+ signup: signupOf(a.signup ?? env.WIZELENS_CLOUD_SIGNUP),
41
+ retentionDays: int(a["retention-days"] ?? env.WIZELENS_CLOUD_RETENTION_DAYS),
42
+ allowPrivateWebhooks: a["allow-private-webhooks"] ?? env.WIZELENS_CLOUD_ALLOW_PRIVATE_WEBHOOKS === "1",
43
+ };
44
+ }
45
+ //# sourceMappingURL=cli-args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-args.js","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,MAAM,CAAC,MAAM,KAAK,GAAG;;;;;;;;;;;;;CAapB,CAAC;AAEF,MAAM,GAAG,GAAG,CAAC,CAAqB,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AACtF,MAAM,QAAQ,GAAG,CAAC,CAAqB,EAA0B,EAAE,CAAC,CAAC,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AAErH,gEAAgE;AAChE,MAAM,UAAU,eAAe,CAAC,IAAc,EAAE,GAAsB;IACpE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,SAAS,CAAC;QAC9B,IAAI,EAAE,IAAI;QACV,OAAO,EAAE;YACP,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAC5B,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACxB,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACxB,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAChC,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAC1B,gBAAgB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACpC,wBAAwB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;YAC7C,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;SACtC;KACF,CAAC,CAAC;IACH,IAAI,CAAC,CAAC,IAAI;QAAE,OAAO,MAAM,CAAC;IAC1B,OAAO;QACL,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,GAAG,CAAC,YAAY;QACxC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC;QAC7B,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,mBAAmB;QACvC,SAAS,EAAE,CAAC,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,yBAAyB;QAC3D,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,qBAAqB,CAAC;QACvD,aAAa,EAAE,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,GAAG,CAAC,6BAA6B,CAAC;QAC5E,oBAAoB,EAAE,CAAC,CAAC,wBAAwB,CAAC,IAAI,GAAG,CAAC,qCAAqC,KAAK,GAAG;KACvG,CAAC;AACJ,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ import { optionsFromArgs, USAGE } from "./cli-args.js";
3
+ import { CLOUD_VERSION, createCloud } from "./server.js";
4
+ async function main() {
5
+ const options = optionsFromArgs(process.argv.slice(2), process.env);
6
+ if (options === "help")
7
+ return void process.stdout.write(USAGE);
8
+ const cloud = await createCloud(options);
9
+ const url = await cloud.listen();
10
+ const db = cloud.app.db.kind === "postgres" ? "Postgres" : `PGlite (${cloud.app.config.database})`;
11
+ console.info([
12
+ `WizeLens Cloud ${CLOUD_VERSION}`,
13
+ ` Dashboard ${url}/_wizelens/`,
14
+ ` Add-ons WIZELENS_HUB=${url} WIZELENS_HUB_TOKEN=<ingest key>`,
15
+ ` OTLP/HTTP OTEL_EXPORTER_OTLP_ENDPOINT=${url} (header Authorization=Bearer <ingest key>)`,
16
+ ` Database ${db}`,
17
+ ].join("\n"));
18
+ if (cloud.app.db.kind === "pglite")
19
+ console.info(" Note: PGlite is for trials and tests. Use Postgres for real teams.");
20
+ const stop = () => void cloud.close().then(() => process.exit(0));
21
+ process.once("SIGINT", stop);
22
+ process.once("SIGTERM", stop);
23
+ }
24
+ main().catch((err) => {
25
+ console.error(`[wizelens-cloud] ${err instanceof Error ? err.message : String(err)}`);
26
+ process.exit(1);
27
+ });
28
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEzD,KAAK,UAAU,IAAI;IACjB,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACpE,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAChE,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC;IACjC,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,GAAG,CAAC;IACnG,OAAO,CAAC,IAAI,CACV;QACE,kBAAkB,aAAa,EAAE;QACjC,iBAAiB,GAAG,aAAa;QACjC,8BAA8B,GAAG,mCAAmC;QACpE,6CAA6C,GAAG,8CAA8C;QAC9F,iBAAiB,EAAE,EAAE;KACtB,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;IACF,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,CAAC,IAAI,CAAC,sEAAsE,CAAC,CAAC;IACzH,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAClE,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC7B,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAChC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,OAAO,CAAC,KAAK,CAAC,oBAAoB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACtF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}