@crvouga/mockingbird-service-slack 1.4.0 → 1.5.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.
@@ -540,10 +540,10 @@ var OpenAPIReferenceError = class extends Error {
540
540
  };
541
541
  var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
542
542
  var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
543
- var resolveRef = (document2, ref) => {
543
+ var resolveRef = (document3, ref) => {
544
544
  if (!ref.startsWith("#/"))
545
545
  throw new OpenAPIReferenceError(ref);
546
- let cursor = document2;
546
+ let cursor = document3;
547
547
  for (const raw of ref.slice(2).split("/")) {
548
548
  const segment = unescapePointer(raw);
549
549
  if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
@@ -555,14 +555,14 @@ var resolveRef = (document2, ref) => {
555
555
  throw new OpenAPIReferenceError(ref);
556
556
  return cursor;
557
557
  };
558
- var deref = (document2, value) => {
558
+ var deref = (document3, value) => {
559
559
  let current = value;
560
560
  const seen = /* @__PURE__ */ new Set();
561
561
  while (isReference(current)) {
562
562
  if (seen.has(current.$ref))
563
563
  throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
564
564
  seen.add(current.$ref);
565
- current = resolveRef(document2, current.$ref);
565
+ current = resolveRef(document3, current.$ref);
566
566
  }
567
567
  return current;
568
568
  };
@@ -580,36 +580,36 @@ var HTTP_METHODS = [
580
580
  ];
581
581
 
582
582
  // ../../openapi/core/dist/document.js
583
- var mergeParameters = (document2, item, own) => {
583
+ var mergeParameters = (document3, item, own) => {
584
584
  const merged = /* @__PURE__ */ new Map();
585
585
  for (const raw of item.parameters ?? []) {
586
- const parameter = deref(document2, raw);
586
+ const parameter = deref(document3, raw);
587
587
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
588
588
  }
589
589
  for (const raw of own ?? []) {
590
- const parameter = deref(document2, raw);
590
+ const parameter = deref(document3, raw);
591
591
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
592
592
  }
593
593
  return [...merged.values()];
594
594
  };
595
- var listOperations = (document2) => {
595
+ var listOperations = (document3) => {
596
596
  const operations = [];
597
- for (const [path, item] of Object.entries(document2.paths)) {
597
+ for (const [path, item] of Object.entries(document3.paths)) {
598
598
  for (const method of HTTP_METHODS) {
599
599
  const operation = item[method];
600
600
  if (operation?.operationId === void 0)
601
601
  continue;
602
602
  const responses = {};
603
603
  for (const [status, response] of Object.entries(operation.responses)) {
604
- responses[status] = deref(document2, response);
604
+ responses[status] = deref(document3, response);
605
605
  }
606
606
  operations.push({
607
607
  operationId: operation.operationId,
608
608
  method,
609
609
  path,
610
610
  operation,
611
- parameters: mergeParameters(document2, item, operation.parameters),
612
- requestBody: operation.requestBody === void 0 ? void 0 : deref(document2, operation.requestBody),
611
+ parameters: mergeParameters(document3, item, operation.parameters),
612
+ requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
613
613
  responses
614
614
  });
615
615
  }
@@ -1193,6 +1193,1232 @@ var clearNamespace = (sqlite, namespace) => {
1193
1193
  });
1194
1194
  };
1195
1195
 
1196
+ // ../../ui/dist/reset.js
1197
+ var CSS_RESET = `*,
1198
+ *::before,
1199
+ *::after {
1200
+ box-sizing: border-box;
1201
+ margin: 0;
1202
+ padding: 0;
1203
+ }
1204
+
1205
+ * {
1206
+ scrollbar-width: thin;
1207
+ scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
1208
+ }
1209
+
1210
+ *::-webkit-scrollbar {
1211
+ width: 12px;
1212
+ height: 12px;
1213
+ }
1214
+
1215
+ *::-webkit-scrollbar-track,
1216
+ *::-webkit-scrollbar-corner {
1217
+ background: Canvas;
1218
+ }
1219
+
1220
+ *::-webkit-scrollbar-thumb {
1221
+ background: color-mix(in srgb, CanvasText 35%, Canvas);
1222
+ border: 3px solid Canvas;
1223
+ border-radius: 999px;
1224
+ }
1225
+
1226
+ html {
1227
+ hanging-punctuation: first allow-end last;
1228
+ -webkit-text-size-adjust: none;
1229
+ text-size-adjust: none;
1230
+ tab-size: 4;
1231
+ font-family:
1232
+ ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
1233
+ "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
1234
+ font-size: 16px;
1235
+ line-height: 1.5;
1236
+ -webkit-font-smoothing: antialiased;
1237
+ -moz-osx-font-smoothing: grayscale;
1238
+ text-rendering: optimizeLegibility;
1239
+ -webkit-tap-highlight-color: transparent;
1240
+ scroll-behavior: smooth;
1241
+ background: Canvas;
1242
+ color: CanvasText;
1243
+ }
1244
+
1245
+ body {
1246
+ min-height: 100svh;
1247
+ line-height: 1.5;
1248
+ background: Canvas;
1249
+ color: CanvasText;
1250
+ }
1251
+
1252
+ h1 {
1253
+ font-size: 2em;
1254
+ }
1255
+
1256
+ h2 {
1257
+ font-size: 1.5em;
1258
+ }
1259
+
1260
+ h3 {
1261
+ font-size: 1.17em;
1262
+ }
1263
+
1264
+ h4 {
1265
+ font-size: 1em;
1266
+ }
1267
+
1268
+ h5 {
1269
+ font-size: 0.83em;
1270
+ }
1271
+
1272
+ h6 {
1273
+ font-size: 0.67em;
1274
+ }
1275
+
1276
+ h1,
1277
+ h2,
1278
+ h3,
1279
+ h4,
1280
+ h5,
1281
+ h6 {
1282
+ font-weight: 700;
1283
+ line-height: 1.2;
1284
+ text-wrap: balance;
1285
+ overflow-wrap: break-word;
1286
+ }
1287
+
1288
+ p,
1289
+ li,
1290
+ figcaption,
1291
+ dd,
1292
+ dt,
1293
+ blockquote {
1294
+ overflow-wrap: break-word;
1295
+ }
1296
+
1297
+ p,
1298
+ li,
1299
+ figcaption,
1300
+ blockquote {
1301
+ text-wrap: pretty;
1302
+ }
1303
+
1304
+ ul,
1305
+ ol,
1306
+ menu {
1307
+ list-style: none;
1308
+ }
1309
+
1310
+ a {
1311
+ color: inherit;
1312
+ text-decoration: underline;
1313
+ text-decoration-thickness: 1px;
1314
+ text-underline-offset: 0.15em;
1315
+ text-decoration-skip-ink: auto;
1316
+ }
1317
+
1318
+ b,
1319
+ strong {
1320
+ font-weight: 700;
1321
+ }
1322
+
1323
+ small {
1324
+ font-size: 0.875em;
1325
+ }
1326
+
1327
+ code,
1328
+ kbd,
1329
+ samp,
1330
+ pre {
1331
+ font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1332
+ font-size: 1em;
1333
+ }
1334
+
1335
+ sub,
1336
+ sup {
1337
+ font-size: 75%;
1338
+ line-height: 0;
1339
+ position: relative;
1340
+ vertical-align: baseline;
1341
+ }
1342
+
1343
+ sub {
1344
+ bottom: -0.25em;
1345
+ }
1346
+
1347
+ sup {
1348
+ top: -0.5em;
1349
+ }
1350
+
1351
+ abbr[title] {
1352
+ text-decoration: underline dotted;
1353
+ text-underline-offset: 0.15em;
1354
+ }
1355
+
1356
+ mark {
1357
+ background: Mark;
1358
+ color: MarkText;
1359
+ }
1360
+
1361
+ hr {
1362
+ height: 0;
1363
+ border: 0;
1364
+ border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1365
+ color: inherit;
1366
+ }
1367
+
1368
+ img,
1369
+ picture,
1370
+ video,
1371
+ canvas {
1372
+ display: block;
1373
+ max-width: 100%;
1374
+ }
1375
+
1376
+ img,
1377
+ video {
1378
+ height: auto;
1379
+ }
1380
+
1381
+ svg {
1382
+ overflow: hidden;
1383
+ }
1384
+
1385
+ iframe {
1386
+ border: 0;
1387
+ max-width: 100%;
1388
+ }
1389
+
1390
+ table {
1391
+ border-collapse: collapse;
1392
+ border-spacing: 0;
1393
+ }
1394
+
1395
+ fieldset {
1396
+ border: 0;
1397
+ min-inline-size: 0;
1398
+ }
1399
+
1400
+ legend {
1401
+ padding: 0;
1402
+ }
1403
+
1404
+ button,
1405
+ input,
1406
+ select,
1407
+ textarea,
1408
+ optgroup {
1409
+ font: inherit;
1410
+ letter-spacing: inherit;
1411
+ color: inherit;
1412
+ margin: 0;
1413
+ vertical-align: middle;
1414
+ }
1415
+
1416
+ button,
1417
+ input[type="button"],
1418
+ input[type="submit"],
1419
+ input[type="reset"] {
1420
+ appearance: none;
1421
+ background: none;
1422
+ border: 0;
1423
+ padding: 0;
1424
+ cursor: pointer;
1425
+ text-align: center;
1426
+ }
1427
+
1428
+ input,
1429
+ textarea,
1430
+ select {
1431
+ background-color: Canvas;
1432
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1433
+ border-radius: 0;
1434
+ padding: 0.5em 0.65em;
1435
+ }
1436
+
1437
+ input[type="checkbox"],
1438
+ input[type="radio"],
1439
+ input[type="range"],
1440
+ input[type="file"],
1441
+ input[type="color"],
1442
+ input[type="button"],
1443
+ input[type="submit"],
1444
+ input[type="reset"],
1445
+ input[type="image"],
1446
+ input[type="hidden"] {
1447
+ background: none;
1448
+ border: 0;
1449
+ padding: 0;
1450
+ border-radius: 0;
1451
+ }
1452
+
1453
+ input[type="checkbox"],
1454
+ input[type="radio"] {
1455
+ accent-color: CanvasText;
1456
+ }
1457
+
1458
+ input[type="search"] {
1459
+ appearance: none;
1460
+ background-color: Canvas;
1461
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1462
+ }
1463
+
1464
+ input[type="search"]::-webkit-search-decoration,
1465
+ input[type="search"]::-webkit-search-cancel-button,
1466
+ input[type="search"]::-webkit-search-results-button,
1467
+ input[type="search"]::-webkit-search-results-decoration {
1468
+ -webkit-appearance: none;
1469
+ display: none;
1470
+ }
1471
+
1472
+ input[type="number"] {
1473
+ appearance: textfield;
1474
+ background-color: Canvas;
1475
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1476
+ }
1477
+
1478
+ input[type="number"]::-webkit-inner-spin-button,
1479
+ input[type="number"]::-webkit-outer-spin-button {
1480
+ -webkit-appearance: none;
1481
+ margin: 0;
1482
+ }
1483
+
1484
+ textarea {
1485
+ resize: vertical;
1486
+ }
1487
+
1488
+ ::placeholder {
1489
+ opacity: 1;
1490
+ color: color-mix(in srgb, CanvasText 45%, transparent);
1491
+ }
1492
+
1493
+ :focus-visible {
1494
+ outline: 2px solid CanvasText;
1495
+ outline-offset: 2px;
1496
+ }
1497
+
1498
+ :focus:not(:focus-visible) {
1499
+ outline: none;
1500
+ }
1501
+
1502
+ :disabled,
1503
+ [disabled] {
1504
+ cursor: default;
1505
+ opacity: 0.6;
1506
+ }
1507
+
1508
+ label,
1509
+ summary,
1510
+ select,
1511
+ [role="button"] {
1512
+ cursor: pointer;
1513
+ }
1514
+
1515
+ summary {
1516
+ display: list-item;
1517
+ }
1518
+
1519
+ details {
1520
+ display: block;
1521
+ }
1522
+
1523
+ dialog {
1524
+ margin: auto;
1525
+ padding: 1rem;
1526
+ border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1527
+ background: Canvas;
1528
+ color: CanvasText;
1529
+ max-width: calc(100vw - 2rem);
1530
+ max-height: calc(100svh - 2rem);
1531
+ }
1532
+
1533
+ dialog:not([open]) {
1534
+ display: none;
1535
+ }
1536
+
1537
+ ::backdrop {
1538
+ background: color-mix(in srgb, CanvasText 40%, transparent);
1539
+ }
1540
+
1541
+ [hidden] {
1542
+ display: none !important;
1543
+ }
1544
+
1545
+ ::selection {
1546
+ background: Highlight;
1547
+ color: HighlightText;
1548
+ }
1549
+
1550
+ @media (prefers-reduced-motion: reduce) {
1551
+ *,
1552
+ *::before,
1553
+ *::after {
1554
+ animation-duration: 0.01ms !important;
1555
+ animation-iteration-count: 1 !important;
1556
+ transition-duration: 0.01ms !important;
1557
+ scroll-behavior: auto !important;
1558
+ }
1559
+ }
1560
+
1561
+ @media (pointer: coarse) {
1562
+ input,
1563
+ select,
1564
+ textarea {
1565
+ font-size: max(16px, 1em);
1566
+ }
1567
+ }`;
1568
+
1569
+ // ../core/dist/admin-client.js
1570
+ function isRecord2(value) {
1571
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1572
+ }
1573
+ function mountAdminBrand(doc, host, service, catalog) {
1574
+ const clip = (value, max) => {
1575
+ if (typeof value !== "string")
1576
+ return null;
1577
+ const trimmed = value.trim();
1578
+ if (trimmed === "")
1579
+ return null;
1580
+ return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
1581
+ };
1582
+ const httpUrl = (value) => {
1583
+ if (typeof value !== "string" || value.length > 2e3)
1584
+ return null;
1585
+ try {
1586
+ const url = new URL(value);
1587
+ if (url.protocol !== "https:" && url.protocol !== "http:")
1588
+ return null;
1589
+ return url.href;
1590
+ } catch {
1591
+ return null;
1592
+ }
1593
+ };
1594
+ const hexColor = (value) => {
1595
+ if (typeof value !== "string")
1596
+ return null;
1597
+ const raw = value.trim();
1598
+ return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
1599
+ };
1600
+ const hostname = (value) => {
1601
+ try {
1602
+ return new URL(value).hostname.replace(/^www\./, "");
1603
+ } catch {
1604
+ return value;
1605
+ }
1606
+ };
1607
+ host.replaceChildren();
1608
+ host.hidden = true;
1609
+ if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
1610
+ return;
1611
+ const row = catalog[service];
1612
+ if (!isRecord2(row))
1613
+ return;
1614
+ const vendor = clip(row.vendor, 80);
1615
+ const website = httpUrl(row.website);
1616
+ const guide = httpUrl(row.guide);
1617
+ const docs = httpUrl(row.docs);
1618
+ const logo = httpUrl(row.logo);
1619
+ const color = hexColor(row.color);
1620
+ const description = clip(row.description, 280);
1621
+ if (vendor === null || website === null && guide === null && docs === null)
1622
+ return;
1623
+ const add = (node) => {
1624
+ host.append(node);
1625
+ return node;
1626
+ };
1627
+ if (logo !== null) {
1628
+ const img = doc.createElement("img");
1629
+ img.alt = "";
1630
+ img.src = logo;
1631
+ img.width = 16;
1632
+ img.height = 16;
1633
+ img.decoding = "async";
1634
+ img.referrerPolicy = "no-referrer";
1635
+ if (color !== null)
1636
+ img.style.borderColor = color;
1637
+ add(img);
1638
+ }
1639
+ const name = doc.createElement("span");
1640
+ name.className = "vendor-name";
1641
+ name.textContent = vendor;
1642
+ if (description !== null)
1643
+ name.title = description;
1644
+ add(name);
1645
+ const sep = () => {
1646
+ const node = doc.createElement("span");
1647
+ node.className = "vendor-sep";
1648
+ node.setAttribute("aria-hidden", "true");
1649
+ node.textContent = "\xB7";
1650
+ return node;
1651
+ };
1652
+ const link = (href, label, title) => {
1653
+ const node = doc.createElement("a");
1654
+ node.className = "vendor-link";
1655
+ node.href = href;
1656
+ node.target = "_blank";
1657
+ node.rel = "noopener noreferrer";
1658
+ node.referrerPolicy = "no-referrer";
1659
+ node.textContent = label;
1660
+ node.title = title;
1661
+ return node;
1662
+ };
1663
+ if (website !== null) {
1664
+ add(sep());
1665
+ add(link(website, hostname(website), `${vendor} website`));
1666
+ }
1667
+ if (guide !== null) {
1668
+ add(sep());
1669
+ add(link(guide, "Docs", `Docs for ${vendor}`));
1670
+ }
1671
+ if (docs !== null) {
1672
+ add(sep());
1673
+ add(link(docs, "API", `${vendor} API reference`));
1674
+ }
1675
+ host.removeAttribute("hidden");
1676
+ host.hidden = false;
1677
+ }
1678
+ function startAdminBrand(env) {
1679
+ const requested = env.params.get("brands");
1680
+ let brandsUrl = env.brandsUrl;
1681
+ if (requested) {
1682
+ try {
1683
+ const url = new URL(requested, env.location.href);
1684
+ const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
1685
+ if ((url.protocol === "https:" || url.protocol === "http:") && local)
1686
+ brandsUrl = url.href;
1687
+ } catch {
1688
+ brandsUrl = env.brandsUrl;
1689
+ }
1690
+ }
1691
+ const vendorHost = env.$("vendor");
1692
+ const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
1693
+ const brandService = brandRoot?.getAttribute("data-service") || "";
1694
+ if (vendorHost === null)
1695
+ return;
1696
+ env.fetch(brandsUrl, {
1697
+ headers: { accept: "application/json" },
1698
+ signal: AbortSignal.timeout(5e3)
1699
+ }).then(async (response) => {
1700
+ if (!response.ok)
1701
+ return null;
1702
+ const catalog = await response.json();
1703
+ return catalog;
1704
+ }).then((catalog) => {
1705
+ mountAdminBrand(env.document, vendorHost, brandService, catalog);
1706
+ }).catch(() => {
1707
+ });
1708
+ }
1709
+ function faultPresetList(value) {
1710
+ if (!isRecord2(value))
1711
+ throw new Error("Fault presets response was not an object");
1712
+ if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
1713
+ return { presets: [] };
1714
+ }
1715
+ if (!Array.isArray(value.presets))
1716
+ throw new Error("Fault presets response was not a list");
1717
+ return {
1718
+ presets: value.presets.flatMap((item) => {
1719
+ if (!isRecord2(item) || typeof item.name !== "string")
1720
+ return [];
1721
+ return [{ name: item.name }];
1722
+ })
1723
+ };
1724
+ }
1725
+ function bootAdmin(config) {
1726
+ const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
1727
+ const sources = ["declared", "inferred", "mixed"];
1728
+ const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
1729
+ const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
1730
+ const readJson2 = (text2) => {
1731
+ const value = JSON.parse(text2);
1732
+ return value;
1733
+ };
1734
+ const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
1735
+ switch (char) {
1736
+ case "&":
1737
+ return "&amp;";
1738
+ case "<":
1739
+ return "&lt;";
1740
+ case ">":
1741
+ return "&gt;";
1742
+ case '"':
1743
+ return "&quot;";
1744
+ default:
1745
+ return "&#39;";
1746
+ }
1747
+ });
1748
+ const el = (id, kind) => {
1749
+ const node = document.getElementById(id);
1750
+ if (!(node instanceof kind))
1751
+ throw new Error(`admin shell is missing #${id}`);
1752
+ return node;
1753
+ };
1754
+ const errorMessage = (data, fallback) => {
1755
+ if (!isRecord2(data))
1756
+ return fallback;
1757
+ const error = data.error;
1758
+ if (!isRecord2(error) || typeof error.message !== "string" || error.message === "") {
1759
+ return fallback;
1760
+ }
1761
+ return error.message;
1762
+ };
1763
+ const requireList = (value, label) => {
1764
+ if (value === void 0 || value === null)
1765
+ return [];
1766
+ if (!Array.isArray(value))
1767
+ throw new Error(`${label} was not a list`);
1768
+ return value;
1769
+ };
1770
+ const state = {
1771
+ namespace: "default",
1772
+ key: sessionStorage.getItem("mockingbird-admin-key") || "",
1773
+ view: "overview",
1774
+ collection: "",
1775
+ shape: null,
1776
+ route: null,
1777
+ editing: null
1778
+ };
1779
+ const standard = new Set(config.standardRoutes);
1780
+ const banner = el("banner", HTMLElement);
1781
+ const keyInput = el("admin-key", HTMLInputElement);
1782
+ keyInput.value = state.key;
1783
+ const params = new URLSearchParams(location.search);
1784
+ const queryKey = params.get("key");
1785
+ if (queryKey) {
1786
+ state.key = queryKey;
1787
+ keyInput.value = state.key;
1788
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
1789
+ params.delete("key");
1790
+ const next = params.toString();
1791
+ history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
1792
+ }
1793
+ const queryNamespace = params.get("namespace");
1794
+ if (queryNamespace)
1795
+ state.namespace = queryNamespace;
1796
+ const showError = (error) => {
1797
+ banner.textContent = error instanceof Error ? error.message : String(error);
1798
+ banner.classList.add("show");
1799
+ };
1800
+ const clearError = () => {
1801
+ banner.classList.remove("show");
1802
+ };
1803
+ const api = async (method, path, body) => {
1804
+ const headers = { accept: "application/json" };
1805
+ if (state.key !== "")
1806
+ headers[config.adminKeyHeader] = state.key;
1807
+ if (state.namespace !== "")
1808
+ headers["x-mockingbird-namespace"] = state.namespace;
1809
+ if (body !== void 0)
1810
+ headers["content-type"] = "application/json";
1811
+ const init = { method, headers };
1812
+ if (body !== void 0)
1813
+ init.body = JSON.stringify(body);
1814
+ const response = await fetch(config.adminPrefix + path, init);
1815
+ const text2 = await response.text();
1816
+ let data = null;
1817
+ if (text2 !== "") {
1818
+ try {
1819
+ data = readJson2(text2);
1820
+ } catch {
1821
+ data = { raw: text2 };
1822
+ }
1823
+ }
1824
+ if (response.status === 401) {
1825
+ keyInput.focus();
1826
+ throw new Error(errorMessage(data, "Admin key required"));
1827
+ }
1828
+ if (!response.ok) {
1829
+ throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
1830
+ }
1831
+ return data;
1832
+ };
1833
+ const originGet = async (path) => {
1834
+ const response = await fetch(path, { headers: { accept: "application/json" } });
1835
+ if (!response.ok)
1836
+ throw new Error(`${response.status} ${path}`);
1837
+ const value = await response.json();
1838
+ return value;
1839
+ };
1840
+ const panelApi = {
1841
+ get: (path) => api("GET", path),
1842
+ send: (method, path, body) => api(method, path, body),
1843
+ namespace: () => state.namespace
1844
+ };
1845
+ const isPanelScript = (value) => typeof value === "function";
1846
+ const setTheme = (theme) => {
1847
+ if (theme)
1848
+ document.documentElement.dataset.theme = theme;
1849
+ else
1850
+ delete document.documentElement.dataset.theme;
1851
+ };
1852
+ const storedTheme = localStorage.getItem("mockingbird-admin-theme");
1853
+ if (storedTheme)
1854
+ setTheme(storedTheme);
1855
+ el("theme", HTMLButtonElement).addEventListener("click", () => {
1856
+ const current = document.documentElement.dataset.theme;
1857
+ const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
1858
+ const next = dark ? "light" : "dark";
1859
+ localStorage.setItem("mockingbird-admin-theme", next);
1860
+ setTheme(next);
1861
+ });
1862
+ const show = (view) => {
1863
+ state.view = view;
1864
+ for (const button of document.querySelectorAll("#nav button")) {
1865
+ if (!(button instanceof HTMLButtonElement))
1866
+ continue;
1867
+ button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
1868
+ }
1869
+ for (const section of document.querySelectorAll("main .view")) {
1870
+ if (!(section instanceof HTMLElement))
1871
+ continue;
1872
+ section.hidden = section.id !== `view-${view}`;
1873
+ }
1874
+ };
1875
+ el("nav", HTMLElement).addEventListener("click", (event) => {
1876
+ const target = event.target;
1877
+ if (!(target instanceof Element))
1878
+ return;
1879
+ const button = target.closest("button[data-view]");
1880
+ if (!(button instanceof HTMLButtonElement))
1881
+ return;
1882
+ const view = button.dataset.view;
1883
+ if (view === void 0)
1884
+ return;
1885
+ show(view);
1886
+ refresh().catch(showError);
1887
+ });
1888
+ const cards = (items) => `<div class="grid">${items.map((item) => `<article class="card"><div class="k">${item.k}</div><div class="v">${item.v}</div></article>`).join("")}</div>`;
1889
+ const table = (columns, rows) => {
1890
+ if (rows.length === 0)
1891
+ return '<p class="empty">Nothing here yet.</p>';
1892
+ const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
1893
+ const body = rows.map((row) => {
1894
+ const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
1895
+ const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
1896
+ return `<tr${attrs}>${cells}</tr>`;
1897
+ }).join("");
1898
+ return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
1899
+ };
1900
+ const preview = (value) => {
1901
+ const text2 = JSON.stringify(value) ?? "null";
1902
+ return esc(text2.length > 180 ? `${text2.slice(0, 180)}\u2026` : text2);
1903
+ };
1904
+ const when = (epoch) => {
1905
+ const date = new Date(epoch);
1906
+ return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
1907
+ };
1908
+ const clockCard = (epoch) => {
1909
+ const date = new Date(epoch);
1910
+ if (Number.isNaN(date.getTime()))
1911
+ return esc(String(epoch));
1912
+ const iso = date.toISOString();
1913
+ return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
1914
+ };
1915
+ const decodeClock = (value) => {
1916
+ if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
1917
+ throw new Error("Clock response was not a clock");
1918
+ }
1919
+ return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
1920
+ };
1921
+ const decodeField = (value) => {
1922
+ if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
1923
+ return void 0;
1924
+ }
1925
+ const field = {
1926
+ name: value.name,
1927
+ kind: value.kind,
1928
+ optional: value.optional === true
1929
+ };
1930
+ if (typeof value.description === "string")
1931
+ field.description = value.description;
1932
+ if (Array.isArray(value.fields)) {
1933
+ field.fields = value.fields.flatMap((item) => {
1934
+ const child = decodeField(item);
1935
+ return child === void 0 ? [] : [child];
1936
+ });
1937
+ }
1938
+ return field;
1939
+ };
1940
+ const decodeCollection = (value) => {
1941
+ if (!isRecord2(value) || typeof value.name !== "string")
1942
+ return void 0;
1943
+ const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
1944
+ const field = decodeField(item);
1945
+ return field === void 0 ? [] : [field];
1946
+ }) : [];
1947
+ const collection = {
1948
+ name: value.name,
1949
+ label: typeof value.label === "string" ? value.label : value.name,
1950
+ count: typeof value.count === "number" ? value.count : 0,
1951
+ source: isSource(value.source) ? value.source : "inferred",
1952
+ fields
1953
+ };
1954
+ if (typeof value.description === "string")
1955
+ collection.description = value.description;
1956
+ return collection;
1957
+ };
1958
+ const decodeState = (value) => {
1959
+ if (!isRecord2(value) || !Array.isArray(value.collections)) {
1960
+ throw new Error("State response has no collection list");
1961
+ }
1962
+ return {
1963
+ namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
1964
+ storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
1965
+ collections: value.collections.flatMap((item) => {
1966
+ const collection = decodeCollection(item);
1967
+ return collection === void 0 ? [] : [collection];
1968
+ })
1969
+ };
1970
+ };
1971
+ const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
1972
+ if (!isRecord2(item) || typeof item.id !== "string")
1973
+ return [];
1974
+ return [
1975
+ {
1976
+ id: item.id,
1977
+ seq: typeof item.seq === "number" ? item.seq : 0,
1978
+ value: item.value,
1979
+ attrs: ` class="clickable" data-id="${esc(item.id)}"`
1980
+ }
1981
+ ];
1982
+ });
1983
+ const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
1984
+ const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
1985
+ if (!isRecord2(item))
1986
+ return [];
1987
+ return [
1988
+ {
1989
+ at: textOf(item.at),
1990
+ method: textOf(item.method),
1991
+ path: textOf(item.path),
1992
+ status: textOf(item.status),
1993
+ durationMs: textOf(item.durationMs),
1994
+ operationId: textOf(item.operationId)
1995
+ }
1996
+ ];
1997
+ });
1998
+ const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
1999
+ if (!isRecord2(item))
2000
+ return [];
2001
+ return [
2002
+ {
2003
+ id: textOf(item.id),
2004
+ method: typeof item.method === "string" ? item.method : void 0,
2005
+ pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
2006
+ operationId: typeof item.operationId === "string" ? item.operationId : void 0,
2007
+ status: typeof item.status === "number" ? item.status : void 0,
2008
+ effect: typeof item.effect === "string" ? item.effect : void 0,
2009
+ drop: typeof item.drop === "boolean" ? item.drop : void 0,
2010
+ latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
2011
+ }
2012
+ ];
2013
+ });
2014
+ const loadNamespaces = async () => {
2015
+ const data = await api("GET", "/namespaces");
2016
+ const select = el("namespace", HTMLSelectElement);
2017
+ const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
2018
+ const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
2019
+ const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
2020
+ if (!names.includes(state.namespace))
2021
+ names.unshift(state.namespace);
2022
+ select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
2023
+ select.value = state.namespace;
2024
+ };
2025
+ const renderOverview = async () => {
2026
+ const [health, shape, clock, journal] = await Promise.all([
2027
+ originGet("/health"),
2028
+ api("GET", "/state"),
2029
+ api("GET", "/clock"),
2030
+ api("GET", "/requests?limit=8")
2031
+ ]);
2032
+ if (!isRecord2(health))
2033
+ throw new Error("Health response was not an object");
2034
+ const decoded = decodeState(shape);
2035
+ state.shape = decoded;
2036
+ const time = decodeClock(clock);
2037
+ el("overview-cards", HTMLElement).innerHTML = cards([
2038
+ {
2039
+ k: "Service",
2040
+ v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
2041
+ },
2042
+ {
2043
+ k: "Status",
2044
+ v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
2045
+ },
2046
+ { k: "Namespace", v: esc(state.namespace) },
2047
+ { k: "Collections", v: String(decoded.collections.length) },
2048
+ {
2049
+ k: "Records",
2050
+ v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
2051
+ },
2052
+ { k: "Clock", v: clockCard(time.now) }
2053
+ ]);
2054
+ el("overview-requests", HTMLElement).innerHTML = table([
2055
+ { label: "Method", cell: (row) => esc(row.method) },
2056
+ { label: "Path", cell: (row) => esc(row.path) },
2057
+ {
2058
+ label: "Status",
2059
+ cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
2060
+ },
2061
+ { label: "Operation", cell: (row) => esc(row.operationId) }
2062
+ ], decodeJournal(journal));
2063
+ };
2064
+ const schemaChips = (collection) => {
2065
+ if (collection.fields.length === 0) {
2066
+ return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
2067
+ }
2068
+ return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
2069
+ };
2070
+ const renderState = async () => {
2071
+ const shape = decodeState(await api("GET", "/state"));
2072
+ state.shape = shape;
2073
+ const host = el("collections", HTMLElement);
2074
+ if (shape.collections.length === 0) {
2075
+ host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
2076
+ el("schema", HTMLElement).innerHTML = "";
2077
+ el("records", HTMLElement).innerHTML = "";
2078
+ return;
2079
+ }
2080
+ if (!shape.collections.some((item) => item.name === state.collection)) {
2081
+ const first = shape.collections[0];
2082
+ if (first === void 0)
2083
+ return;
2084
+ state.collection = first.name;
2085
+ }
2086
+ host.innerHTML = shape.collections.map((item) => `<button type="button" class="collection" data-collection="${esc(item.name)}" aria-current="${item.name === state.collection ? "true" : "false"}"><strong>${esc(item.label)}</strong><small>${esc(item.count)} \xB7 ${esc(item.source)}</small></button>`).join("");
2087
+ const selected = shape.collections.find((item) => item.name === state.collection);
2088
+ if (selected === void 0)
2089
+ return;
2090
+ const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
2091
+ el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
2092
+ const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
2093
+ el("records", HTMLElement).innerHTML = table([
2094
+ { label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
2095
+ { label: "Seq", cell: (row) => esc(row.seq) },
2096
+ { label: "Value", cell: (row) => preview(row.value) }
2097
+ ], decodeRecords(page));
2098
+ };
2099
+ const renderClock = async () => {
2100
+ const clock = decodeClock(await api("GET", "/clock"));
2101
+ el("clock-readout", HTMLElement).innerHTML = `<div class="k">Now</div><div class="v">${esc(when(clock.now))}</div><p>${esc(clock.now)} ms \xB7 offset ${esc(clock.offsetMs)} ms ${clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>"}</p>`;
2102
+ const freeze = el("freeze", HTMLButtonElement);
2103
+ freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
2104
+ freeze.dataset.frozen = clock.frozen ? "1" : "0";
2105
+ };
2106
+ const renderFaults = async () => {
2107
+ const [faults, presets] = await Promise.all([
2108
+ api("GET", "/faults"),
2109
+ api("GET", "/faults/presets").catch(() => ({ presets: [] }))
2110
+ ]);
2111
+ const select = el("fault-preset", HTMLSelectElement);
2112
+ const current = select.value;
2113
+ const list2 = faultPresetList(presets).presets;
2114
+ select.innerHTML = `<option value="">Custom rule</option>${list2.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
2115
+ select.value = current;
2116
+ el("fault-list", HTMLElement).innerHTML = table([
2117
+ { label: "Id", cell: (row) => esc(row.id) },
2118
+ {
2119
+ label: "Match",
2120
+ cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
2121
+ },
2122
+ {
2123
+ label: "Effect",
2124
+ cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
2125
+ }
2126
+ ], decodeFaults(faults));
2127
+ };
2128
+ const renderJournal = async () => {
2129
+ const journal = await api("GET", "/requests?limit=100");
2130
+ el("journal", HTMLElement).innerHTML = table([
2131
+ { label: "When", cell: (row) => esc(row.at) },
2132
+ { label: "Method", cell: (row) => esc(row.method) },
2133
+ { label: "Path", cell: (row) => esc(row.path) },
2134
+ { label: "Status", cell: (row) => esc(row.status) },
2135
+ { label: "ms", cell: (row) => esc(row.durationMs) }
2136
+ ], decodeJournal(journal));
2137
+ };
2138
+ const renderRoutes = async () => {
2139
+ const data = await api("GET", "/");
2140
+ if (!isRecord2(data))
2141
+ throw new Error("Route list was not an object");
2142
+ const routes = requireList(data.routes, "Routes").map((route) => String(route));
2143
+ el("route-list", HTMLElement).innerHTML = routes.map((route) => {
2144
+ const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
2145
+ const parts = route.split(" ");
2146
+ const method = parts[0] ?? "";
2147
+ const path = parts.slice(1).join(" ");
2148
+ return `<div class="route"><span class="method">${esc(method)}</span><span>${esc(path)}${extra}</span><button class="btn" type="button" data-route="${esc(route)}">Use</button></div>`;
2149
+ }).join("");
2150
+ };
2151
+ const renderers = {
2152
+ overview: renderOverview,
2153
+ state: renderState,
2154
+ clock: renderClock,
2155
+ faults: renderFaults,
2156
+ journal: renderJournal,
2157
+ routes: renderRoutes
2158
+ };
2159
+ async function refresh() {
2160
+ clearError();
2161
+ const render = renderers[state.view];
2162
+ if (render)
2163
+ await render();
2164
+ }
2165
+ el("namespace", HTMLSelectElement).addEventListener("change", () => {
2166
+ state.namespace = el("namespace", HTMLSelectElement).value;
2167
+ refresh().catch(showError);
2168
+ });
2169
+ keyInput.addEventListener("change", () => {
2170
+ state.key = keyInput.value.trim();
2171
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
2172
+ refresh().catch(showError);
2173
+ });
2174
+ el("refresh", HTMLButtonElement).addEventListener("click", () => {
2175
+ refresh().catch(showError);
2176
+ });
2177
+ el("collections", HTMLElement).addEventListener("click", (event) => {
2178
+ const target = event.target;
2179
+ if (!(target instanceof Element))
2180
+ return;
2181
+ const button = target.closest("[data-collection]");
2182
+ if (!(button instanceof HTMLElement))
2183
+ return;
2184
+ const collection = button.dataset.collection;
2185
+ if (collection === void 0)
2186
+ return;
2187
+ state.collection = collection;
2188
+ renderState().catch(showError);
2189
+ });
2190
+ el("records", HTMLElement).addEventListener("click", (event) => {
2191
+ const target = event.target;
2192
+ if (!(target instanceof Element))
2193
+ return;
2194
+ const row = target.closest("[data-id]");
2195
+ if (!(row instanceof HTMLElement))
2196
+ return;
2197
+ const id = row.dataset.id;
2198
+ if (id === void 0)
2199
+ return;
2200
+ openEditor(id).catch(showError);
2201
+ });
2202
+ const editor = el("editor", HTMLDialogElement);
2203
+ const editorId = el("editor-id", HTMLInputElement);
2204
+ const editorValue = el("editor-value", HTMLTextAreaElement);
2205
+ const editorDelete = el("editor-delete", HTMLButtonElement);
2206
+ async function openEditor(id) {
2207
+ state.editing = id;
2208
+ el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
2209
+ editorId.value = id ?? "";
2210
+ editorId.readOnly = id !== null;
2211
+ editorDelete.hidden = id === null;
2212
+ if (id) {
2213
+ const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
2214
+ if (!isRecord2(record))
2215
+ throw new Error("Record response was not an object");
2216
+ editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
2217
+ } else {
2218
+ editorValue.value = "{\n \n}";
2219
+ }
2220
+ editor.showModal();
2221
+ }
2222
+ el("new-record", HTMLButtonElement).addEventListener("click", () => {
2223
+ openEditor(null).catch(showError);
2224
+ });
2225
+ el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
2226
+ const submitter = event.submitter;
2227
+ if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
2228
+ return;
2229
+ event.preventDefault();
2230
+ let value;
2231
+ try {
2232
+ value = readJson2(editorValue.value);
2233
+ } catch {
2234
+ showError(new Error("Value is not JSON"));
2235
+ return;
2236
+ }
2237
+ const id = editorId.value.trim();
2238
+ const path = `/state/${encodeURIComponent(state.collection)}`;
2239
+ const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
2240
+ request.then(() => {
2241
+ editor.close();
2242
+ return renderState();
2243
+ }).catch(showError);
2244
+ });
2245
+ editorDelete.addEventListener("click", () => {
2246
+ const id = state.editing;
2247
+ if (!id)
2248
+ return;
2249
+ api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
2250
+ editor.close();
2251
+ return renderState();
2252
+ }).catch(showError);
2253
+ });
2254
+ for (const button of document.querySelectorAll("[data-advance]")) {
2255
+ if (!(button instanceof HTMLButtonElement))
2256
+ continue;
2257
+ button.addEventListener("click", () => {
2258
+ const raw = button.dataset.advance;
2259
+ if (raw === void 0)
2260
+ return;
2261
+ api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
2262
+ });
2263
+ }
2264
+ el("freeze", HTMLButtonElement).addEventListener("click", () => {
2265
+ const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
2266
+ api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
2267
+ });
2268
+ el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
2269
+ api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
2270
+ });
2271
+ el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
2272
+ event.preventDefault();
2273
+ const raw = el("clock-instant", HTMLInputElement).value.trim();
2274
+ const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
2275
+ api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
2276
+ });
2277
+ el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
2278
+ event.preventDefault();
2279
+ const preset = el("fault-preset", HTMLSelectElement).value;
2280
+ if (preset !== "") {
2281
+ api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
2282
+ return;
2283
+ }
2284
+ const status = Number(el("fault-status", HTMLInputElement).value);
2285
+ if (!Number.isFinite(status)) {
2286
+ showError(new Error("Status must be a number, or choose a preset"));
2287
+ return;
2288
+ }
2289
+ const path = el("fault-path", HTMLInputElement).value;
2290
+ const method = el("fault-method", HTMLInputElement).value;
2291
+ api("POST", "/faults", {
2292
+ status,
2293
+ ...path !== "" ? { pathPrefix: path } : {},
2294
+ ...method !== "" ? { method } : {}
2295
+ }).then(() => renderFaults()).catch(showError);
2296
+ });
2297
+ el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2298
+ api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2299
+ });
2300
+ el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2301
+ api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2302
+ });
2303
+ el("route-list", HTMLElement).addEventListener("click", (event) => {
2304
+ const target = event.target;
2305
+ if (!(target instanceof Element))
2306
+ return;
2307
+ const button = target.closest("[data-route]");
2308
+ if (!(button instanceof HTMLElement))
2309
+ return;
2310
+ const route = button.dataset.route;
2311
+ if (route === void 0)
2312
+ return;
2313
+ state.route = route;
2314
+ el("route-title", HTMLElement).textContent = route;
2315
+ const method = route.split(" ")[0] ?? "";
2316
+ el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
2317
+ });
2318
+ el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
2319
+ event.preventDefault();
2320
+ const send = async () => {
2321
+ if (state.route === null)
2322
+ throw new Error("Choose a route first");
2323
+ const parts = state.route.split(" ");
2324
+ const method = parts[0];
2325
+ const path = parts.slice(1).join(" ");
2326
+ if (method === void 0)
2327
+ throw new Error("Choose a route first");
2328
+ const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
2329
+ let body;
2330
+ const routeBody = el("route-body", HTMLTextAreaElement);
2331
+ if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
2332
+ try {
2333
+ body = readJson2(routeBody.value);
2334
+ } catch {
2335
+ throw new Error("Body is not JSON");
2336
+ }
2337
+ }
2338
+ const data = await api(method, concrete, body);
2339
+ el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
2340
+ };
2341
+ send().catch(showError);
2342
+ });
2343
+ const decodePanel = (value) => {
2344
+ if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
2345
+ return void 0;
2346
+ }
2347
+ const panel = {
2348
+ id: value.id,
2349
+ title: value.title,
2350
+ description: typeof value.description === "string" ? value.description : "",
2351
+ html: typeof value.html === "string" ? value.html : ""
2352
+ };
2353
+ if (typeof value.script === "string")
2354
+ panel.script = value.script;
2355
+ return panel;
2356
+ };
2357
+ const mountPanels = (panels) => {
2358
+ const nav = el("nav", HTMLElement);
2359
+ const views = el("views", HTMLElement);
2360
+ for (const panel of panels) {
2361
+ const button = document.createElement("button");
2362
+ button.type = "button";
2363
+ button.dataset.view = panel.id;
2364
+ button.textContent = panel.title;
2365
+ nav.append(button);
2366
+ const section = document.createElement("section");
2367
+ section.className = "view";
2368
+ section.id = `view-${panel.id}`;
2369
+ section.hidden = true;
2370
+ const title = document.createElement("h2");
2371
+ title.textContent = panel.title;
2372
+ const lede = document.createElement("p");
2373
+ lede.className = "lede";
2374
+ lede.textContent = panel.description;
2375
+ const body = document.createElement("div");
2376
+ body.className = "panel-body";
2377
+ body.id = `panel-${panel.id}`;
2378
+ body.innerHTML = panel.html;
2379
+ section.append(title, lede, body);
2380
+ views.append(section);
2381
+ if (panel.script === void 0)
2382
+ continue;
2383
+ try {
2384
+ const compiled = new Function("root", "api", panel.script);
2385
+ if (!isPanelScript(compiled))
2386
+ throw new Error("panel script did not compile");
2387
+ compiled(body, panelApi);
2388
+ } catch (error) {
2389
+ const note = document.createElement("p");
2390
+ note.className = "banner show";
2391
+ note.textContent = error instanceof Error ? error.message : String(error);
2392
+ body.append(note);
2393
+ }
2394
+ }
2395
+ };
2396
+ startAdminBrand({
2397
+ document,
2398
+ $: (id) => document.getElementById(id),
2399
+ params,
2400
+ location: { href: location.href, origin: location.origin },
2401
+ fetch,
2402
+ brandsUrl: config.brandsUrl
2403
+ });
2404
+ loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
2405
+ const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
2406
+ const decoded = decodePanel(panel);
2407
+ return decoded === void 0 ? [] : [decoded];
2408
+ });
2409
+ mountPanels(panels);
2410
+ return refresh();
2411
+ }).catch(showError);
2412
+ }
2413
+ var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2414
+ var adminClientSource = (config) => {
2415
+ const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2416
+ return `<script>
2417
+ ${clientPrelude}
2418
+ bootAdmin(${payload});
2419
+ </script>`;
2420
+ };
2421
+
1196
2422
  // ../core/dist/surface.js
1197
2423
  var defineMock = (runtime) => runtime;
1198
2424
  var STANDARD_ADMIN_ROUTES = [
@@ -1261,94 +2487,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
1261
2487
  return "&#39;";
1262
2488
  }
1263
2489
  });
2490
+ var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
1264
2491
  var renderAdminDocument = (service) => {
1265
2492
  const name = escapeHtml(service);
1266
- const standard = JSON.stringify(STANDARD_ADMIN_ROUTES);
2493
+ const script = adminClientSource({
2494
+ standardRoutes: STANDARD_ADMIN_ROUTES,
2495
+ adminPrefix: ADMIN_PREFIX,
2496
+ adminKeyHeader: ADMIN_KEY_HEADER,
2497
+ brandsUrl: ADMIN_BRANDS_URL,
2498
+ service
2499
+ });
1267
2500
  return `<!DOCTYPE html>
1268
2501
  <html lang="en">
1269
2502
  <head>
1270
2503
  <meta charset="utf-8">
1271
2504
  <meta name="viewport" content="width=device-width, initial-scale=1">
1272
2505
  <meta name="color-scheme" content="light dark">
1273
- <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%233d4f3a'/%3E%3C/svg%3E">
2506
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='2' fill='%2327272a'/%3E%3C/svg%3E">
1274
2507
  <title>${name} admin</title>
1275
2508
  <style>
2509
+ ${CSS_RESET}
1276
2510
  :root {
1277
2511
  color-scheme: light;
1278
- --bg: #f6f5f2;
2512
+ --bg: #fafafa;
1279
2513
  --bg-elev: #ffffff;
1280
- --bg-muted: #eeece6;
1281
- --bg-inset: #f3f1eb;
1282
- --line: #e2dfd6;
1283
- --line-strong: #d0ccc1;
1284
- --ink: #1c1915;
1285
- --muted: #5e584f;
1286
- --faint: #8a8378;
1287
- --accent: #3d4f3a;
1288
- --accent-ink: #f7f6f2;
1289
- --accent-soft: #e5ece2;
1290
- --ok: #1f7a45;
1291
- --ok-soft: #e5f4eb;
1292
- --warn: #9a6700;
1293
- --warn-soft: #fbf3df;
1294
- --err: #a33b32;
1295
- --err-soft: #fdecea;
1296
- --shadow: 0 1px 2px rgb(28 25 21 / 0.05), 0 12px 32px rgb(28 25 21 / 0.06);
1297
- --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
2514
+ --bg-muted: #f4f4f5;
2515
+ --bg-inset: #f4f4f5;
2516
+ --line: #dedee3;
2517
+ --line-strong: #c4c4cc;
2518
+ --ink: #18181b;
2519
+ --muted: #62626b;
2520
+ --faint: #71717a;
2521
+ --accent: #27272a;
2522
+ --accent-ink: #ffffff;
2523
+ --accent-soft: #f4f4f5;
2524
+ --ok: #166534;
2525
+ --ok-soft: #f0fdf4;
2526
+ --warn: #a16207;
2527
+ --warn-soft: #fefce8;
2528
+ --err: #a82d32;
2529
+ --err-soft: #fef2f2;
2530
+ --shadow: none;
2531
+ --sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1298
2532
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1299
- --radius: 14px;
1300
- --header: 60px;
1301
- --nav: 220px;
2533
+ --radius: 10px;
2534
+ --header: 56px;
2535
+ --nav: 200px;
1302
2536
  }
1303
2537
  :root[data-theme="dark"] {
1304
2538
  color-scheme: dark;
1305
- --bg: #141311;
1306
- --bg-elev: #1e1c19;
1307
- --bg-muted: #26231f;
1308
- --bg-inset: #181715;
1309
- --line: #34302b;
1310
- --line-strong: #4a453d;
1311
- --ink: #f4f1ea;
1312
- --muted: #c4bdb1;
1313
- --faint: #8f877c;
1314
- --accent: #c5d5b8;
1315
- --accent-ink: #1a2118;
1316
- --accent-soft: #2a3328;
1317
- --ok: #8fd6a8;
1318
- --ok-soft: #1a2e22;
1319
- --warn: #f0cc78;
1320
- --warn-soft: #332911;
1321
- --err: #f0a8a2;
1322
- --err-soft: #3a201e;
1323
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2539
+ --bg: #111113;
2540
+ --bg-elev: #19191c;
2541
+ --bg-muted: #242428;
2542
+ --bg-inset: #111113;
2543
+ --line: #36363c;
2544
+ --line-strong: #52525b;
2545
+ --ink: #f4f4f5;
2546
+ --muted: #a9a9b2;
2547
+ --faint: #71717a;
2548
+ --accent: #e4e4e7;
2549
+ --accent-ink: #18181b;
2550
+ --accent-soft: #242428;
2551
+ --ok: #86efac;
2552
+ --ok-soft: #14241b;
2553
+ --warn: #fde68a;
2554
+ --warn-soft: #2a2410;
2555
+ --err: #ffaaaa;
2556
+ --err-soft: #2c1516;
2557
+ --shadow: none;
1324
2558
  }
1325
2559
  @media (prefers-color-scheme: dark) {
1326
2560
  :root:not([data-theme="light"]) {
1327
2561
  color-scheme: dark;
1328
- --bg: #141311;
1329
- --bg-elev: #1e1c19;
1330
- --bg-muted: #26231f;
1331
- --bg-inset: #181715;
1332
- --line: #34302b;
1333
- --line-strong: #4a453d;
1334
- --ink: #f4f1ea;
1335
- --muted: #c4bdb1;
1336
- --faint: #8f877c;
1337
- --accent: #c5d5b8;
1338
- --accent-ink: #1a2118;
1339
- --accent-soft: #2a3328;
1340
- --ok: #8fd6a8;
1341
- --ok-soft: #1a2e22;
1342
- --warn: #f0cc78;
1343
- --warn-soft: #332911;
1344
- --err: #f0a8a2;
1345
- --err-soft: #3a201e;
1346
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2562
+ --bg: #111113;
2563
+ --bg-elev: #19191c;
2564
+ --bg-muted: #242428;
2565
+ --bg-inset: #111113;
2566
+ --line: #36363c;
2567
+ --line-strong: #52525b;
2568
+ --ink: #f4f4f5;
2569
+ --muted: #a9a9b2;
2570
+ --faint: #71717a;
2571
+ --accent: #e4e4e7;
2572
+ --accent-ink: #18181b;
2573
+ --accent-soft: #242428;
2574
+ --ok: #86efac;
2575
+ --ok-soft: #14241b;
2576
+ --warn: #fde68a;
2577
+ --warn-soft: #2a2410;
2578
+ --err: #ffaaaa;
2579
+ --err-soft: #2c1516;
2580
+ --shadow: none;
1347
2581
  }
1348
2582
  }
1349
- * { box-sizing: border-box; }
1350
- html, body { margin: 0; height: 100%; }
2583
+ /* Fill the viewport, then grow with the content so the background covers the scroll. */
2584
+ html { height: 100%; }
1351
2585
  body {
2586
+ margin: 0;
2587
+ min-height: 100%;
2588
+ display: flex;
2589
+ flex-direction: column;
1352
2590
  font-family: var(--sans);
1353
2591
  background: var(--bg);
1354
2592
  color: var(--ink);
@@ -1357,24 +2595,37 @@ var renderAdminDocument = (service) => {
1357
2595
  padding-right: env(safe-area-inset-right);
1358
2596
  -webkit-text-size-adjust: 100%;
1359
2597
  }
1360
- button, input, select, textarea { font: inherit; color: inherit; }
1361
- button { cursor: pointer; }
1362
2598
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1363
- @media (prefers-reduced-motion: reduce) {
1364
- * { scroll-behavior: auto !important; transition: none !important; }
1365
- }
1366
- .app { min-height: 100%; display: flex; flex-direction: column; }
2599
+ .app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
1367
2600
  header.top {
1368
2601
  position: sticky; top: 0; z-index: 5;
1369
2602
  display: flex; align-items: center; gap: 12px;
1370
2603
  min-height: var(--header); padding: 10px 16px;
1371
- background: color-mix(in srgb, var(--bg-elev) 92%, transparent);
2604
+ background: var(--bg-elev);
1372
2605
  border-bottom: 1px solid var(--line);
1373
- backdrop-filter: blur(10px);
1374
- }
1375
- .brand { display: flex; flex-direction: column; min-width: 0; margin-right: auto; }
1376
- .brand strong { font-size: 15px; letter-spacing: -0.01em; }
1377
- .brand span { color: var(--faint); font-size: 12px; }
2606
+ min-width: 0; max-width: 100%;
2607
+ }
2608
+ .brand {
2609
+ display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
2610
+ min-width: 0; max-width: 100%; margin-right: auto;
2611
+ }
2612
+ .brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
2613
+ .brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
2614
+ .brand-id span { color: var(--faint); font-size: 12px; }
2615
+ .vendor {
2616
+ display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
2617
+ min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
2618
+ }
2619
+ .vendor[hidden] { display: none; }
2620
+ /* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
2621
+ .vendor img {
2622
+ width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
2623
+ border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
2624
+ }
2625
+ .vendor-name { color: var(--ink); font-weight: 600; }
2626
+ .vendor-sep { color: var(--faint); }
2627
+ .vendor-link { color: inherit; text-decoration: none; }
2628
+ .vendor-link:hover { color: var(--ink); text-decoration: underline; }
1378
2629
  .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
1379
2630
  label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
1380
2631
  label.field input, label.field select, .controls input[type="password"] {
@@ -1392,7 +2643,7 @@ var renderAdminDocument = (service) => {
1392
2643
  record or the full nav row would widen the page past the viewport. */
1393
2644
  .layout {
1394
2645
  display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
1395
- flex: 1; min-height: 0; min-width: 0;
2646
+ flex: 1 0 auto; min-width: 0;
1396
2647
  }
1397
2648
  nav.side {
1398
2649
  border-right: 1px solid var(--line); padding: 12px;
@@ -1446,8 +2697,8 @@ var renderAdminDocument = (service) => {
1446
2697
  .banner.show { display: block; }
1447
2698
  .empty { color: var(--muted); padding: 24px 8px; }
1448
2699
  dialog {
1449
- border: 1px solid var(--line); border-radius: 16px; padding: 0; background: var(--bg-elev);
1450
- color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: var(--shadow);
2700
+ border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
2701
+ color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: none;
1451
2702
  }
1452
2703
  dialog::backdrop { background: rgb(20 19 17 / 0.45); }
1453
2704
  dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
@@ -1474,7 +2725,7 @@ var renderAdminDocument = (service) => {
1474
2725
  padding-top: max(10px, env(safe-area-inset-top));
1475
2726
  }
1476
2727
  input, select, textarea { font-size: 16px; }
1477
- .controls { width: 100%; }
2728
+ .brand, .vendor, .controls { width: 100%; }
1478
2729
  label.field, label.field select, label.field input { flex: 1; min-width: 0; }
1479
2730
  .layout { grid-template-columns: minmax(0, 1fr); }
1480
2731
  nav.side {
@@ -1505,8 +2756,11 @@ var renderAdminDocument = (service) => {
1505
2756
  <div class="app" data-service="${name}" data-mockingbird-admin>
1506
2757
  <header class="top">
1507
2758
  <div class="brand">
1508
- <strong>${name}</strong>
1509
- <span>Mockingbird admin</span>
2759
+ <div class="brand-id">
2760
+ <strong>${name}</strong>
2761
+ <span>Admin</span>
2762
+ </div>
2763
+ <div class="vendor" id="vendor" hidden></div>
1510
2764
  </div>
1511
2765
  <div class="controls">
1512
2766
  <label class="field">Namespace
@@ -1636,460 +2890,7 @@ var renderAdminDocument = (service) => {
1636
2890
  </div>
1637
2891
  </form>
1638
2892
  </dialog>
1639
- <script>
1640
- (() => {
1641
- const STANDARD = new Set(${standard})
1642
- const state = {
1643
- namespace: "default",
1644
- key: sessionStorage.getItem("mockingbird-admin-key") || "",
1645
- view: "overview",
1646
- collection: "",
1647
- shape: null,
1648
- route: null,
1649
- editing: null
1650
- }
1651
- const $ = (id) => document.getElementById(id)
1652
- const banner = $("banner")
1653
- const keyInput = $("admin-key")
1654
- keyInput.value = state.key
1655
- const params = new URLSearchParams(location.search)
1656
- if (params.get("key")) {
1657
- state.key = params.get("key")
1658
- keyInput.value = state.key
1659
- sessionStorage.setItem("mockingbird-admin-key", state.key)
1660
- params.delete("key")
1661
- const next = params.toString()
1662
- history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
1663
- }
1664
- if (params.get("namespace")) state.namespace = params.get("namespace")
1665
-
1666
- const showError = (error) => {
1667
- banner.textContent = error instanceof Error ? error.message : String(error)
1668
- banner.classList.add("show")
1669
- }
1670
- const clearError = () => banner.classList.remove("show")
1671
-
1672
- async function originGet(path) {
1673
- const response = await fetch(path, { headers: { accept: "application/json" } })
1674
- if (!response.ok) throw new Error(response.status + " " + path)
1675
- return response.json()
1676
- }
1677
- async function api(method, path, body) {
1678
- const headers = { accept: "application/json" }
1679
- if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
1680
- if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
1681
- if (body !== undefined) headers["content-type"] = "application/json"
1682
- const response = await fetch("${ADMIN_PREFIX}" + path, {
1683
- method,
1684
- headers,
1685
- body: body === undefined ? undefined : JSON.stringify(body)
1686
- })
1687
- const text = await response.text()
1688
- let data = null
1689
- if (text) {
1690
- try { data = JSON.parse(text) } catch { data = { raw: text } }
1691
- }
1692
- if (response.status === 401) {
1693
- keyInput.focus()
1694
- throw new Error((data && data.error && data.error.message) || "Admin key required")
1695
- }
1696
- if (!response.ok) {
1697
- throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
1698
- }
1699
- return data
1700
- }
1701
- const panelApi = {
1702
- get: (path) => api("GET", path),
1703
- send: (method, path, body) => api(method, path, body),
1704
- namespace: () => state.namespace
1705
- }
1706
-
1707
- function setTheme(theme) {
1708
- if (theme) document.documentElement.dataset.theme = theme
1709
- else delete document.documentElement.dataset.theme
1710
- }
1711
- const storedTheme = localStorage.getItem("mockingbird-admin-theme")
1712
- if (storedTheme) setTheme(storedTheme)
1713
- $("theme").addEventListener("click", () => {
1714
- const current = document.documentElement.dataset.theme
1715
- const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
1716
- const next = dark ? "light" : "dark"
1717
- localStorage.setItem("mockingbird-admin-theme", next)
1718
- setTheme(next)
1719
- })
1720
-
1721
- function show(view) {
1722
- state.view = view
1723
- for (const button of document.querySelectorAll("#nav button")) {
1724
- button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
1725
- }
1726
- for (const section of document.querySelectorAll("main .view")) {
1727
- section.hidden = section.id !== "view-" + view
1728
- }
1729
- }
1730
- document.getElementById("nav").addEventListener("click", (event) => {
1731
- const button = event.target.closest("button[data-view]")
1732
- if (!button) return
1733
- show(button.dataset.view)
1734
- refresh().catch(showError)
1735
- })
1736
-
1737
- function cards(items) {
1738
- return '<div class="grid">' + items.map((item) =>
1739
- '<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
1740
- ).join("") + "</div>"
1741
- }
1742
- function table(columns, rows) {
1743
- if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
1744
- const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
1745
- const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
1746
- '<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
1747
- ).join("") + "</tr>").join("")
1748
- return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
1749
- }
1750
- const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
1751
- "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
1752
- }[char]))
1753
- const preview = (value) => {
1754
- const text = JSON.stringify(value)
1755
- return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
1756
- }
1757
- const when = (epoch) => {
1758
- const date = new Date(epoch)
1759
- return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
1760
- }
1761
- const clockCard = (epoch) => {
1762
- const date = new Date(epoch)
1763
- if (Number.isNaN(date.getTime())) return esc(String(epoch))
1764
- const iso = date.toISOString()
1765
- return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
1766
- }
1767
-
1768
- async function loadNamespaces() {
1769
- const data = await api("GET", "/namespaces")
1770
- const select = $("namespace")
1771
- const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
1772
- if (!names.includes(state.namespace)) names.unshift(state.namespace)
1773
- select.innerHTML = names.map((name) =>
1774
- '<option value="' + esc(name) + '">' + esc(name) + "</option>"
1775
- ).join("")
1776
- select.value = state.namespace
1777
- }
1778
-
1779
- async function renderOverview() {
1780
- const [health, shape, clock, journal] = await Promise.all([
1781
- originGet("/health"),
1782
- api("GET", "/state"),
1783
- api("GET", "/clock"),
1784
- api("GET", "/requests?limit=8")
1785
- ])
1786
- state.shape = shape
1787
- $("overview-cards").innerHTML = cards([
1788
- { k: "Service", v: esc(health.service || "${name}") },
1789
- { k: "Status", v: esc(health.status || "ok") },
1790
- { k: "Namespace", v: esc(state.namespace) },
1791
- { k: "Collections", v: String(shape.collections.length) },
1792
- { k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
1793
- { k: "Clock", v: clockCard(clock.now) }
1794
- ])
1795
- const requests = (journal && journal.requests) || []
1796
- $("overview-requests").innerHTML = table(
1797
- [
1798
- { label: "Method", cell: (row) => esc(row.method) },
1799
- { label: "Path", cell: (row) => esc(row.path) },
1800
- { label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
1801
- { label: "Operation", cell: (row) => esc(row.operationId || "") }
1802
- ],
1803
- requests
1804
- )
1805
- }
1806
-
1807
- function schemaChips(collection) {
1808
- if (!collection || collection.fields.length === 0) {
1809
- return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
1810
- }
1811
- return '<div class="chips">' + collection.fields.map((field) =>
1812
- '<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
1813
- ).join("") + "</div>"
1814
- }
1815
-
1816
- async function renderState() {
1817
- const shape = await api("GET", "/state")
1818
- state.shape = shape
1819
- const host = $("collections")
1820
- if (shape.collections.length === 0) {
1821
- host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
1822
- $("schema").innerHTML = ""
1823
- $("records").innerHTML = ""
1824
- return
1825
- }
1826
- if (!shape.collections.some((item) => item.name === state.collection)) {
1827
- state.collection = shape.collections[0].name
1828
- }
1829
- host.innerHTML = shape.collections.map((item) =>
1830
- '<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
1831
- (item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
1832
- '</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
1833
- ).join("")
1834
- const selected = shape.collections.find((item) => item.name === state.collection)
1835
- $("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
1836
- (selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
1837
- schemaChips(selected)
1838
- const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
1839
- const rows = page.records.map((record) => ({
1840
- ...record,
1841
- attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
1842
- }))
1843
- $("records").innerHTML = table(
1844
- [
1845
- { label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
1846
- { label: "Seq", cell: (row) => esc(row.seq) },
1847
- { label: "Value", cell: (row) => preview(row.value) }
1848
- ],
1849
- rows
1850
- )
1851
- }
1852
-
1853
- async function renderClock() {
1854
- const clock = await api("GET", "/clock")
1855
- $("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
1856
- "</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
1857
- (clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
1858
- $("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
1859
- $("freeze").dataset.frozen = clock.frozen ? "1" : "0"
1860
- }
1861
-
1862
- async function renderFaults() {
1863
- const [faults, presets] = await Promise.all([
1864
- api("GET", "/faults"),
1865
- api("GET", "/faults/presets").catch(() => ({ presets: [] }))
1866
- ])
1867
- const select = $("fault-preset")
1868
- const current = select.value
1869
- const list = presets.presets || []
1870
- select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
1871
- '<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
1872
- ).join("")
1873
- select.value = current
1874
- const rules = faults.faults || []
1875
- $("fault-list").innerHTML = table(
1876
- [
1877
- { label: "Id", cell: (row) => esc(row.id) },
1878
- { label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
1879
- { label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
1880
- ],
1881
- rules
1882
- )
1883
- }
1884
-
1885
- async function renderJournal() {
1886
- const journal = await api("GET", "/requests?limit=100")
1887
- $("journal").innerHTML = table(
1888
- [
1889
- { label: "When", cell: (row) => esc(row.at || "") },
1890
- { label: "Method", cell: (row) => esc(row.method) },
1891
- { label: "Path", cell: (row) => esc(row.path) },
1892
- { label: "Status", cell: (row) => esc(row.status) },
1893
- { label: "ms", cell: (row) => esc(row.durationMs) }
1894
- ],
1895
- journal.requests || []
1896
- )
1897
- }
1898
-
1899
- async function renderRoutes() {
1900
- const data = await api("GET", "/")
1901
- const routes = data.routes || []
1902
- $("route-list").innerHTML = routes.map((route) => {
1903
- const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
1904
- return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
1905
- '</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
1906
- '</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
1907
- }).join("")
1908
- }
1909
-
1910
- const renderers = {
1911
- overview: renderOverview,
1912
- state: renderState,
1913
- clock: renderClock,
1914
- faults: renderFaults,
1915
- journal: renderJournal,
1916
- routes: renderRoutes
1917
- }
1918
-
1919
- async function refresh() {
1920
- clearError()
1921
- if (state.view in renderers) await renderers[state.view]()
1922
- }
1923
-
1924
- $("namespace").addEventListener("change", () => {
1925
- state.namespace = $("namespace").value
1926
- refresh().catch(showError)
1927
- })
1928
- keyInput.addEventListener("change", () => {
1929
- state.key = keyInput.value.trim()
1930
- sessionStorage.setItem("mockingbird-admin-key", state.key)
1931
- refresh().catch(showError)
1932
- })
1933
- $("refresh").addEventListener("click", () => refresh().catch(showError))
1934
-
1935
- $("collections").addEventListener("click", (event) => {
1936
- const button = event.target.closest("[data-collection]")
1937
- if (!button) return
1938
- state.collection = button.dataset.collection
1939
- renderState().catch(showError)
1940
- })
1941
- $("records").addEventListener("click", (event) => {
1942
- const row = event.target.closest("[data-id]")
1943
- if (!row) return
1944
- openEditor(row.dataset.id).catch(showError)
1945
- })
1946
-
1947
- const editor = $("editor")
1948
- async function openEditor(id) {
1949
- state.editing = id || null
1950
- $("editor-title").textContent = id ? "Edit record" : "New record"
1951
- $("editor-id").value = id || ""
1952
- $("editor-id").readOnly = Boolean(id)
1953
- $("editor-delete").hidden = !id
1954
- if (id) {
1955
- const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
1956
- $("editor-value").value = JSON.stringify(record.value, null, 2)
1957
- } else {
1958
- $("editor-value").value = "{\\n \\n}"
1959
- }
1960
- editor.showModal()
1961
- }
1962
- $("new-record").addEventListener("click", () => openEditor(null).catch(showError))
1963
- $("editor-form").addEventListener("submit", (event) => {
1964
- const submitter = event.submitter
1965
- if (!submitter || submitter.value !== "save") return
1966
- event.preventDefault()
1967
- let value
1968
- try { value = JSON.parse($("editor-value").value) }
1969
- catch { showError(new Error("Value is not JSON")); return }
1970
- const id = $("editor-id").value.trim()
1971
- const path = "/state/" + encodeURIComponent(state.collection)
1972
- const request = state.editing
1973
- ? api("PUT", path + "/" + encodeURIComponent(id), { value })
1974
- : api("POST", path, { id: id || undefined, value })
1975
- request.then(() => { editor.close(); return renderState() }).catch(showError)
1976
- })
1977
- $("editor-delete").addEventListener("click", () => {
1978
- const id = state.editing
1979
- if (!id) return
1980
- api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
1981
- .then(() => { editor.close(); return renderState() })
1982
- .catch(showError)
1983
- })
1984
-
1985
- document.querySelectorAll("[data-advance]").forEach((button) => {
1986
- button.addEventListener("click", () => {
1987
- api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
1988
- })
1989
- })
1990
- $("freeze").addEventListener("click", () => {
1991
- const frozen = $("freeze").dataset.frozen === "1"
1992
- api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
1993
- })
1994
- $("clock-reset").addEventListener("click", () => {
1995
- api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
1996
- })
1997
- $("clock-set").addEventListener("submit", (event) => {
1998
- event.preventDefault()
1999
- const raw = $("clock-instant").value.trim()
2000
- const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
2001
- api("POST", "/clock", { set }).then(renderClock).catch(showError)
2002
- })
2003
-
2004
- $("fault-form").addEventListener("submit", (event) => {
2005
- event.preventDefault()
2006
- const preset = $("fault-preset").value
2007
- const body = preset
2008
- ? { preset }
2009
- : {
2010
- status: Number($("fault-status").value),
2011
- ...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
2012
- ...($("fault-method").value ? { method: $("fault-method").value } : {})
2013
- }
2014
- if (!preset && !Number.isFinite(body.status)) {
2015
- showError(new Error("Status must be a number, or choose a preset"))
2016
- return
2017
- }
2018
- api("POST", "/faults", body).then(renderFaults).catch(showError)
2019
- })
2020
- $("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
2021
- $("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
2022
-
2023
- $("route-list").addEventListener("click", (event) => {
2024
- const button = event.target.closest("[data-route]")
2025
- if (!button) return
2026
- state.route = button.dataset.route
2027
- $("route-title").textContent = state.route
2028
- const method = state.route.split(" ")[0]
2029
- $("route-body").disabled = method === "GET" || method === "DELETE"
2030
- })
2031
- $("route-form").addEventListener("submit", async (event) => {
2032
- event.preventDefault()
2033
- if (!state.route) return showError(new Error("Choose a route first"))
2034
- const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
2035
- const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
2036
- let body
2037
- if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
2038
- try { body = JSON.parse($("route-body").value) }
2039
- catch { return showError(new Error("Body is not JSON")) }
2040
- }
2041
- try {
2042
- const data = await api(method, concrete, body)
2043
- $("route-result").textContent = JSON.stringify(data, null, 2)
2044
- } catch (error) {
2045
- showError(error)
2046
- }
2047
- })
2048
-
2049
- function mountPanels(panels) {
2050
- const nav = $("nav")
2051
- const views = $("views")
2052
- for (const panel of panels) {
2053
- const button = document.createElement("button")
2054
- button.type = "button"
2055
- button.dataset.view = panel.id
2056
- button.textContent = panel.title
2057
- nav.append(button)
2058
- const section = document.createElement("section")
2059
- section.className = "view"
2060
- section.id = "view-" + panel.id
2061
- section.hidden = true
2062
- const title = document.createElement("h2")
2063
- title.textContent = panel.title
2064
- const lede = document.createElement("p")
2065
- lede.className = "lede"
2066
- lede.textContent = panel.description || ""
2067
- const body = document.createElement("div")
2068
- body.className = "panel-body"
2069
- body.id = "panel-" + panel.id
2070
- body.innerHTML = panel.html || ""
2071
- section.append(title, lede, body)
2072
- views.append(section)
2073
- if (panel.script) {
2074
- try {
2075
- const run = new Function("root", "api", panel.script)
2076
- run(body, panelApi)
2077
- } catch (error) {
2078
- const note = document.createElement("p")
2079
- note.className = "banner show"
2080
- note.textContent = error instanceof Error ? error.message : String(error)
2081
- body.append(note)
2082
- }
2083
- }
2084
- }
2085
- }
2086
-
2087
- loadNamespaces()
2088
- .then(() => api("GET", "/ui/manifest"))
2089
- .then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
2090
- .catch(showError)
2091
- })()
2092
- </script>
2893
+ ${script}
2093
2894
  </body>
2094
2895
  </html>`;
2095
2896
  };
@@ -2106,10 +2907,10 @@ var json3 = (body) => new Response(JSON.stringify(body), {
2106
2907
  });
2107
2908
  var adminUiRoutes = (service, ui) => {
2108
2909
  const shell = () => renderAdminDocument(service);
2109
- const document2 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
2910
+ const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
2110
2911
  return {
2111
- "GET /ui": () => htmlResponse(document2()),
2112
- "GET /ui/": () => htmlResponse(document2()),
2912
+ "GET /ui": () => htmlResponse(document3()),
2913
+ "GET /ui/": () => htmlResponse(document3()),
2113
2914
  "GET /ui/manifest": () => json3({
2114
2915
  service,
2115
2916
  panels: (ui?.panels ?? []).map((panel) => ({
@@ -2135,11 +2936,11 @@ var EXTENSION_KEYS = {
2135
2936
  };
2136
2937
 
2137
2938
  // ../../openapi/metadata/dist/read.js
2138
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2939
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2139
2940
  var extensionOf = (holder, key) => holder[key];
2140
2941
  var operationMetadata = (operation) => {
2141
2942
  const raw = extensionOf(operation, EXTENSION_KEYS.operation);
2142
- const ext = isRecord2(raw) ? raw : {};
2943
+ const ext = isRecord3(raw) ? raw : {};
2143
2944
  const supported = ext.supported ?? true;
2144
2945
  const parity = ext.parity ?? {};
2145
2946
  return {
@@ -2175,9 +2976,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
2175
2976
  this.name = "OperationRegistryError";
2176
2977
  }
2177
2978
  };
2178
- var verifyOperations = (document2, handlers) => {
2979
+ var verifyOperations = (document3, handlers) => {
2179
2980
  const problems = [];
2180
- const operations = listOperations(document2);
2981
+ const operations = listOperations(document3);
2181
2982
  const seen = /* @__PURE__ */ new Set();
2182
2983
  for (const operation of operations) {
2183
2984
  if (seen.has(operation.operationId))
@@ -2293,7 +3094,7 @@ var SAMPLE = 25;
2293
3094
  var WALK_LIMIT = 1e4;
2294
3095
  var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
2295
3096
  var fail = (status, message) => ({ ok: false, status, message });
2296
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3097
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2297
3098
  var kindOf = (value) => {
2298
3099
  if (value === null)
2299
3100
  return "null";
@@ -2392,7 +3193,7 @@ var absorb = (accum, value) => {
2392
3193
  const kind = kindOf(value);
2393
3194
  accum.kinds.add(kind);
2394
3195
  accum.present++;
2395
- if (!isRecord3(value))
3196
+ if (!isRecord4(value))
2396
3197
  return;
2397
3198
  for (const [key, child] of Object.entries(value)) {
2398
3199
  const nested = accum.nested.get(key) ?? blankAccum(key);
@@ -2417,7 +3218,7 @@ var inferFields = (records) => {
2417
3218
  if (records.length === 0)
2418
3219
  return [];
2419
3220
  const sample = records.slice(0, SAMPLE);
2420
- const objects = sample.filter((record) => isRecord3(record.value));
3221
+ const objects = sample.filter((record) => isRecord4(record.value));
2421
3222
  if (objects.length === 0) {
2422
3223
  const kinds = new Set(sample.map((record) => kindOf(record.value)));
2423
3224
  const list2 = [...kinds];
@@ -2604,7 +3405,7 @@ var patchStateRecord = (scope, name, id, value) => {
2604
3405
  const parsed = jsonValue(value);
2605
3406
  if (!parsed.ok)
2606
3407
  return parsed;
2607
- const next = isRecord3(current.value.value) && isRecord3(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
3408
+ const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
2608
3409
  return replaceStateRecord(scope, name, id, next);
2609
3410
  };
2610
3411
  var deleteStateRecord = (scope, name, id) => {
@@ -2672,7 +3473,7 @@ var stateAdminRoutes = (routes) => {
2672
3473
  const scope = open(namespace);
2673
3474
  if (!scope.ok)
2674
3475
  return fromResult(scope, 201);
2675
- if (!isRecord3(body) || !("value" in body)) {
3476
+ if (!isRecord4(body) || !("value" in body)) {
2676
3477
  return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
2677
3478
  }
2678
3479
  const id = body.id;
@@ -2690,7 +3491,7 @@ var stateAdminRoutes = (routes) => {
2690
3491
  const scope = open(namespace);
2691
3492
  if (!scope.ok)
2692
3493
  return fromResult(scope, 200);
2693
- if (!isRecord3(body) || !("value" in body))
3494
+ if (!isRecord4(body) || !("value" in body))
2694
3495
  return adminError2(400, 'expected {"value": <json>}');
2695
3496
  const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2696
3497
  if (replaced.ok)
@@ -2701,7 +3502,7 @@ var stateAdminRoutes = (routes) => {
2701
3502
  const scope = open(namespace);
2702
3503
  if (!scope.ok)
2703
3504
  return fromResult(scope, 200);
2704
- if (!isRecord3(body) || !("value" in body))
3505
+ if (!isRecord4(body) || !("value" in body))
2705
3506
  return adminError2(400, 'expected {"value": <json>}');
2706
3507
  const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2707
3508
  if (patched.ok)
@@ -2721,14 +3522,14 @@ var stateAdminRoutes = (routes) => {
2721
3522
  };
2722
3523
 
2723
3524
  // ../core/dist/version.js
2724
- var PACKAGE_VERSION = true ? "1.4.0" : UNRELEASED_VERSION;
3525
+ var PACKAGE_VERSION = true ? "1.5.0" : UNRELEASED_VERSION;
2725
3526
 
2726
3527
  // ../core/dist/webhooks.js
2727
3528
  var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
2728
3529
  var adminError3 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
2729
- var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3530
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2730
3531
  var parseEndpoint = (value) => {
2731
- if (!isRecord4(value) || typeof value.url !== "string")
3532
+ if (!isRecord5(value) || typeof value.url !== "string")
2732
3533
  return "each endpoint needs a url";
2733
3534
  try {
2734
3535
  new URL(value.url);
@@ -2745,12 +3546,12 @@ var parseEndpoint = (value) => {
2745
3546
  const events = value.events ?? value.enabledEvents;
2746
3547
  if (Array.isArray(events))
2747
3548
  endpoint.events = events.map(String);
2748
- if (isRecord4(value.tags)) {
3549
+ if (isRecord5(value.tags)) {
2749
3550
  endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
2750
3551
  }
2751
3552
  if (typeof value.account === "string")
2752
3553
  endpoint.tags = { ...endpoint.tags, account: value.account };
2753
- if (isRecord4(value.headers)) {
3554
+ if (isRecord5(value.headers)) {
2754
3555
  endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
2755
3556
  }
2756
3557
  return endpoint;
@@ -2777,7 +3578,7 @@ var webhookAdminRoutes = (hub) => ({
2777
3578
  return json5(200, { status: "ok" });
2778
3579
  },
2779
3580
  "POST /webhooks/faults": ({ body, namespace }) => {
2780
- if (!isRecord4(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
3581
+ if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
2781
3582
  return adminError3(400, "mode must be duplicate, reorder or drop");
2782
3583
  }
2783
3584
  const fault = { mode: body.mode };
@@ -2793,7 +3594,7 @@ var webhookAdminRoutes = (hub) => ({
2793
3594
  }))
2794
3595
  }),
2795
3596
  "PUT /webhook-endpoints": ({ body, namespace }) => {
2796
- const list2 = Array.isArray(body) ? body : isRecord4(body) ? body.endpoints : void 0;
3597
+ const list2 = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
2797
3598
  if (!Array.isArray(list2))
2798
3599
  return adminError3(400, "expected [{url, secret?, events?}]");
2799
3600
  const parsed = [];
@@ -2863,8 +3664,8 @@ var DroppedConnectionError = class extends TypeError {
2863
3664
  this.name = "TypeError";
2864
3665
  }
2865
3666
  };
2866
- var operationMatcher = (document2) => {
2867
- const matchers = listOperations(document2).map((operation) => ({
3667
+ var operationMatcher = (document3) => {
3668
+ const matchers = listOperations(document3).map((operation) => ({
2868
3669
  operationId: operation.operationId,
2869
3670
  method: operation.method.toUpperCase(),
2870
3671
  pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
@@ -2959,10 +3760,10 @@ var createRuntime = (options) => {
2959
3760
  const ensureBranch = (namespace, branch2, at) => {
2960
3761
  if (!BRANCH_PATTERN2.test(branch2))
2961
3762
  throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
2962
- const history = timeline(namespace);
3763
+ const history2 = timeline(namespace);
2963
3764
  if (branch2 === "main") {
2964
3765
  if (at !== void 0) {
2965
- const point = history.checkout("main", at);
3766
+ const point = history2.checkout("main", at);
2966
3767
  restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
2967
3768
  captured.set(namespace, point.value.snapshot);
2968
3769
  rng.setState(point.value.rngState);
@@ -2975,10 +3776,10 @@ var createRuntime = (options) => {
2975
3776
  return namespace;
2976
3777
  }
2977
3778
  const storage = physicalBranch(namespace, branch2);
2978
- if (!history.hasBranch(branch2)) {
3779
+ if (!history2.hasBranch(branch2)) {
2979
3780
  if (at === void 0)
2980
- history.commit(capture(namespace));
2981
- const point = history.fork(branch2, at === void 0 ? {} : { from: at });
3781
+ history2.commit(capture(namespace));
3782
+ const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
2982
3783
  const branchRng = createRng(options.seed ?? 0);
2983
3784
  if (point)
2984
3785
  branchRng.setState(point.value.rngState);
@@ -2987,8 +3788,8 @@ var createRuntime = (options) => {
2987
3788
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
2988
3789
  if (point)
2989
3790
  captured.set(storage, point.value.snapshot);
2990
- } else if (at !== void 0 && history.head(branch2)?.id !== at) {
2991
- const point = history.checkout(branch2, at);
3791
+ } else if (at !== void 0 && history2.head(branch2)?.id !== at) {
3792
+ const point = history2.checkout(branch2, at);
2992
3793
  if (!instances.has(storage)) {
2993
3794
  const branchRng = createRng(options.seed ?? 0);
2994
3795
  branchRng.setState(point.value.rngState);
@@ -2999,7 +3800,7 @@ var createRuntime = (options) => {
2999
3800
  captured.set(storage, point.value.snapshot);
3000
3801
  } else {
3001
3802
  if (!instances.has(storage)) {
3002
- const point = history.head(branch2);
3803
+ const point = history2.head(branch2);
3003
3804
  const branchRng = createRng(options.seed ?? 0);
3004
3805
  if (point)
3005
3806
  branchRng.setState(point.value.rngState);
@@ -3023,8 +3824,8 @@ var createRuntime = (options) => {
3023
3824
  const checkout = (checkpointId, checkoutOptions = {}) => {
3024
3825
  const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
3025
3826
  const branchName = checkoutOptions.branch ?? "main";
3026
- const history = timeline(namespace);
3027
- const point = history.checkout(branchName, checkpointId);
3827
+ const history2 = timeline(namespace);
3828
+ const point = history2.checkout(branchName, checkpointId);
3028
3829
  const storage = ensureBranch(namespace, branchName);
3029
3830
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
3030
3831
  captured.set(storage, point.value.snapshot);
@@ -3074,9 +3875,9 @@ var createRuntime = (options) => {
3074
3875
  instance(name);
3075
3876
  restoreNamespace(sqlite, storageNamespace(name), from);
3076
3877
  captured.set(name, from);
3077
- const history = timelines.get(name);
3078
- if (history)
3079
- history.commit(capture(name), { branch: "main" });
3878
+ const history2 = timelines.get(name);
3879
+ if (history2)
3880
+ history2.commit(capture(name), { branch: "main" });
3080
3881
  else
3081
3882
  timeline(name);
3082
3883
  };
@@ -3305,10 +4106,10 @@ var createRuntime = (options) => {
3305
4106
  },
3306
4107
  release: (name, checkpointId) => timeline(name).release(checkpointId),
3307
4108
  inspect: (name) => {
3308
- const history = timeline(name);
4109
+ const history2 = timeline(name);
3309
4110
  return {
3310
- branches: history.branches(),
3311
- checkpoints: history.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
4111
+ branches: history2.branches(),
4112
+ checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
3312
4113
  id,
3313
4114
  branch: branchName,
3314
4115
  parent,
@@ -3323,11 +4124,11 @@ var createRuntime = (options) => {
3323
4124
  } : {},
3324
4125
  routes: {
3325
4126
  ...credentialRoutes(credentials),
3326
- ...options.presets ? presetRoutes(options.presets, runtime) : {},
3327
4127
  ...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
3328
4128
  ...options.admin?.(runtime) ?? {},
3329
- // Standard state and UI routes win a colliding key. A service adds its own keys
3330
- // beside these; it does not replace `/state` or `/ui`.
4129
+ // Shell routes win a colliding key. Fault presets are one of them: the admin page
4130
+ // maps `presets`, and a service route must not replace that list with a record.
4131
+ ...options.presets ? presetRoutes(options.presets, runtime) : {},
3331
4132
  ...stateAdminRoutes({
3332
4133
  open: stateScope,
3333
4134
  afterWrite: (namespace) => {
@@ -3402,9 +4203,12 @@ var credentialRoutes = (registry) => ({
3402
4203
  }
3403
4204
  });
3404
4205
  var presetRoutes = (presets, runtime) => ({
3405
- "GET /faults/presets": () => adminJson(200, {
3406
- presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
3407
- }),
4206
+ "GET /faults/presets": () => {
4207
+ const body = {
4208
+ presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
4209
+ };
4210
+ return adminJson(200, body);
4211
+ },
3408
4212
  "POST /faults/presets/:name": ({ params, body, namespace }) => {
3409
4213
  const name = params.name;
3410
4214
  if (!presets[name])
@@ -3415,7 +4219,7 @@ var presetRoutes = (presets, runtime) => ({
3415
4219
  });
3416
4220
 
3417
4221
  // src/generated/openapi.ts
3418
- var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Slack incoming webhooks and Web API (Mockingbird subset)","description":"Stateful mock subset of Slack: incoming webhooks (\`POST /services/T/B/X\`) and the Web API\\nmethods our apps call. Every message lands in an outbox a suite can assert on. Hand-authored\\nfrom Slack's documented wire shapes and the consumers (backend \`postSlackWebhook\`, EMR\\n\`slack-web-api.ts\`, request-intake \`SlackClient\`, release-conductor).\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from https://api.slack.com/methods and https://api.slack.com/messaging/webhooks (Slack publishes no maintained OpenAPI for these methods)."}},"servers":[{"url":"https://slack.com"},{"url":"https://hooks.slack.com"}],"security":[{"bearerAuth":[]}],"paths":{"/services/{team}/{bot}/{secret}":{"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","pattern":"^T[A-Z0-9]{2,12}$"}},{"name":"bot","in":"path","required":true,"schema":{"type":"string","pattern":"^B[A-Z0-9]{2,12}$"}},{"name":"secret","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9]{8,24}$"}}],"post":{"operationId":"PostIncomingWebhook","summary":"Incoming webhook: post a message to the hook's channel","description":"Answers \`200 ok\` (text/plain). Bad payloads get Slack's plain-text error codes (\`invalid_payload\`, \`no_text\`, \`invalid_blocks\`); an unregistered hook is \`404 no_service\` once any hook is registered. Also accepts form bodies with a \`payload=<json>\` field.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","maxLength":400},"blocks":{"type":"array","maxItems":3,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["section","divider","header","context"]},"text":{"type":"object","properties":{"type":{"type":"string","enum":["mrkdwn","plain_text"]},"text":{"type":"string","maxLength":200}}}}}},"thread_ts":{"type":"string","maxLength":24},"username":{"type":"string","maxLength":40},"icon_emoji":{"type":"string","maxLength":40},"unfurl_links":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Posted","content":{"text/plain":{"schema":{"type":"string","enum":["ok"]}}}},"400":{"description":"\`invalid_payload\`, \`no_text\`, \`invalid_blocks\`, \`too_many_attachments\`","content":{"text/plain":{"schema":{"type":"string"}}}},"403":{"description":"\`action_prohibited\`, \`posting_to_general_channel_denied\`","content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"\`no_service\`, \`channel_not_found\`","content":{"text/plain":{"schema":{"type":"string"}}}},"410":{"description":"\`channel_is_archived\`","content":{"text/plain":{"schema":{"type":"string"}}}},"429":{"description":"\`rate_limited\`, with \`retry-after\`","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"text/plain":{"schema":{"type":"string"}}}},"500":{"description":"\`internal_error\`","content":{"text/plain":{"schema":{"type":"string"}}}},"503":{"description":"\`service_unavailable\`","content":{"text/plain":{"schema":{"type":"string"}}}}},"security":[]}},"/api/chat.postMessage":{"post":{"operationId":"ChatPostMessage","summary":"chat.postMessage: post a message (or a thread reply) to a channel","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"text":{"type":"string","maxLength":400},"blocks":{"type":"string","description":"JSON-encoded blocks","maxLength":2000},"thread_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},"required":["channel"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"text":{"type":"string","maxLength":400},"blocks":{"type":"array","maxItems":3,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["section","divider","header","context"]},"text":{"type":"object","properties":{"type":{"type":"string","enum":["mrkdwn","plain_text"]},"text":{"type":"string","maxLength":200}}}}}},"thread_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"mrkdwn":{"type":"boolean"},"unfurl_links":{"type":"boolean"}},"required":["channel"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostMessageResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/chat.update":{"post":{"operationId":"ChatUpdate","summary":"chat.update: edit a message the bot posted","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"text":{"type":"string","maxLength":400}},"required":["channel","ts"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"text":{"type":"string","maxLength":400},"blocks":{"type":"array","maxItems":3,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["section","divider","header","context"]},"text":{"type":"object","properties":{"type":{"type":"string","enum":["mrkdwn","plain_text"]},"text":{"type":"string","maxLength":200}}}}}}},"required":["channel","ts"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/chat.postEphemeral":{"post":{"operationId":"ChatPostEphemeral","summary":"chat.postEphemeral: a message only one user sees","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"user":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}},"text":{"type":"string","maxLength":400}},"required":["channel","user"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"user":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}},"text":{"type":"string","maxLength":400},"blocks":{"type":"array","maxItems":3,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["section","divider","header","context"]},"text":{"type":"object","properties":{"type":{"type":"string","enum":["mrkdwn","plain_text"]},"text":{"type":"string","maxLength":200}}}}}},"thread_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},"required":["channel","user"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EphemeralResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/chat.getPermalink":{"get":{"operationId":"ChatGetPermalinkGet","summary":"chat.getPermalink (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"channel","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"}},{"name":"message_ts","in":"query","required":false,"schema":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PermalinkResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"ChatGetPermalink","summary":"chat.getPermalink: the permalink of a message","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"message_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},"required":["channel","message_ts"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"message_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},"required":["channel","message_ts"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PermalinkResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/reactions.add":{"post":{"operationId":"ReactionsAdd","summary":"reactions.add: react to a message","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"name":{"type":"string","enum":["eyes","white_check_mark","x","thumbsup","rotating_light"]}},"required":["channel","timestamp","name"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"name":{"type":"string","enum":["eyes","white_check_mark","x","thumbsup","rotating_light"]}},"required":["channel","timestamp","name"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/reactions.remove":{"post":{"operationId":"ReactionsRemove","summary":"reactions.remove: take a reaction back","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"name":{"type":"string","enum":["eyes","white_check_mark","x","thumbsup","rotating_light"]}},"required":["name"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"name":{"type":"string","enum":["eyes","white_check_mark","x","thumbsup","rotating_light"]}},"required":["name"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/reactions.get":{"get":{"operationId":"ReactionsGetGet","summary":"reactions.get (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"channel","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"}},{"name":"timestamp","in":"query","required":false,"schema":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},{"name":"full","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReactionsGetResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"ReactionsGet","summary":"reactions.get: a message and its reactions","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"full":{"type":"string","enum":["true","false"]}},"required":["channel","timestamp"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"full":{"type":"boolean"}},"required":["channel","timestamp"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReactionsGetResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/auth.test":{"get":{"operationId":"AuthTestGet","summary":"auth.test (GET)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTestResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"AuthTest","summary":"auth.test: who the token belongs to","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{}}},"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTestResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/conversations.join":{"post":{"operationId":"ConversationsJoin","summary":"conversations.join: the bot joins a public channel","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"}},"required":["channel"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"}},"required":["channel"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JoinResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/users.info":{"get":{"operationId":"UsersInfoGet","summary":"users.info (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"user","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"UsersInfo","summary":"users.info: a user's profile","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"user":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}}},"required":["user"]}},"application/json":{"schema":{"type":"object","properties":{"user":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}}},"required":["user"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/users.lookupByEmail":{"get":{"operationId":"UsersLookupByEmailGet","summary":"users.lookupByEmail (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"email","in":"query","required":false,"schema":{"type":"string","format":"email","maxLength":80}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"UsersLookupByEmail","summary":"users.lookupByEmail: find a user by email","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":80}},"required":["email"]}},"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":80}},"required":["email"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/files.info":{"get":{"operationId":"FilesInfoGet","summary":"files.info (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"file","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"file","missing":"F00MISSING"}}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"FilesInfo","summary":"files.info: a file's metadata and private download URL","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"file":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"file","missing":"F00MISSING"}}},"required":["file"]}},"application/json":{"schema":{"type":"object","properties":{"file":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"file","missing":"F00MISSING"}}},"required":["file"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/views.open":{"post":{"operationId":"ViewsOpen","summary":"views.open: open a modal for a trigger","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"trigger_id":{"type":"string","minLength":1,"maxLength":40},"view":{"type":"string","description":"JSON-encoded view","maxLength":2000}},"required":["trigger_id","view"]}},"application/json":{"schema":{"type":"object","properties":{"trigger_id":{"type":"string","minLength":1,"maxLength":40},"view":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["modal","home"]},"callback_id":{"type":"string","maxLength":40},"title":{"type":"object","properties":{"type":{"type":"string","enum":["plain_text"]},"text":{"type":"string","maxLength":24}}},"blocks":{"type":"array","maxItems":3,"items":{"type":"object"}},"private_metadata":{"type":"string","maxLength":200}}}},"required":["trigger_id","view"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Envelope":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"},"error":{"type":"string"},"warning":{"type":"string"},"needed":{"type":"string"},"provided":{"type":"string"},"response_metadata":{"type":"object","properties":{"messages":{"type":"array","items":{"type":"string"}},"warnings":{"type":"array","items":{"type":"string"}}}}}},"ErrorEnvelope":{"type":"object","required":["ok","error"],"properties":{"ok":{"type":"boolean","enum":[false]},"error":{"type":"string"}}},"Message":{"type":"object","required":["type","ts"],"properties":{"type":{"type":"string","enum":["message"]},"subtype":{"type":"string"},"text":{"type":"string"},"user":{"type":"string"},"bot_id":{"type":"string"},"app_id":{"type":"string"},"team":{"type":"string"},"ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"thread_ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"blocks":{"type":"array","items":{"type":"object"}},"edited":{"type":"object","properties":{"user":{"type":"string"},"ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}}}},"reactions":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"count":{"type":"integer"},"users":{"type":"array","items":{"type":"string"}}}}}}},"PostMessageResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"channel":{"type":"string"},"ts":{"type":"string","x-mockingbird-resource":{"type":"message","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"message":{"$ref":"#/components/schemas/Message"}}}]},"ReactionsGetResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"type":{"type":"string","enum":["message"]},"channel":{"type":"string"},"message":{"$ref":"#/components/schemas/Message"}}}]},"UpdateResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"channel":{"type":"string"},"ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"text":{"type":"string"},"message":{"$ref":"#/components/schemas/Message"}}}]},"EphemeralResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"message_ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}}}}]},"PermalinkResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"channel":{"type":"string"},"permalink":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}}]},"AuthTestResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"url":{"type":"string"},"team":{"type":"string"},"user":{"type":"string"},"team_id":{"type":"string"},"user_id":{"type":"string"},"bot_id":{"type":"string"},"is_enterprise_install":{"type":"boolean"}}}]},"Channel":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"is_channel":{"type":"boolean"},"is_private":{"type":"boolean"},"is_archived":{"type":"boolean"},"is_member":{"type":"boolean"},"created":{"type":"integer"}}},"JoinResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"channel":{"$ref":"#/components/schemas/Channel"}}}]},"User":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"user","identity":true}},"team_id":{"type":"string"},"name":{"type":"string"},"real_name":{"type":"string"},"deleted":{"type":"boolean"},"is_bot":{"type":"boolean"},"tz":{"type":"string"},"profile":{"type":"object","properties":{"email":{"type":"string"},"real_name":{"type":"string"},"display_name":{"type":"string"}}}}},"UserResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}}}]},"File":{"type":"object","required":["id"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"file","identity":true}},"name":{"type":"string"},"title":{"type":"string"},"mimetype":{"type":"string"},"filetype":{"type":"string"},"size":{"type":"integer"},"created":{"type":"integer"},"url_private":{"type":"string"},"url_private_download":{"type":"string"},"permalink":{"type":"string"}}},"FileResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"file":{"$ref":"#/components/schemas/File"}}}]},"ViewResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"view":{"type":"object","properties":{"id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"type":{"type":"string"},"team_id":{"type":"string"},"callback_id":{"type":"string"},"hash":{"type":"string","x-mockingbird-volatile":{"kind":"opaque"}},"blocks":{"type":"array","items":{"type":"object"}},"state":{"type":"object"},"private_metadata":{"type":"string"},"title":{"type":"object"},"app_id":{"type":"string"},"bot_id":{"type":"string"}}}}}]}}}}`);
4222
+ var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Slack incoming webhooks and Web API (Mockingbird subset)","description":"Stateful mock subset of Slack: incoming webhooks (\`POST /services/T/B/X\`) and the Web API\\nmethods our apps call. Every message lands in an outbox a suite can assert on. Hand-authored\\nfrom Slack's documented wire shapes and the consumers (backend \`postSlackWebhook\`, EMR\\n\`slack-web-api.ts\`, request-intake \`SlackClient\`, release-conductor).\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from https://api.slack.com/methods and https://api.slack.com/messaging/webhooks (Slack publishes no maintained OpenAPI for these methods)."}},"servers":[{"url":"https://slack.com"},{"url":"https://hooks.slack.com"}],"security":[{"bearerAuth":[]}],"paths":{"/services/{team}/{bot}/{secret}":{"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","pattern":"^T[A-Z0-9]{2,12}$"}},{"name":"bot","in":"path","required":true,"schema":{"type":"string","pattern":"^B[A-Z0-9]{2,12}$"}},{"name":"secret","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9]{8,24}$"}}],"post":{"operationId":"PostIncomingWebhook","summary":"Incoming webhook: post a message to the hook's channel","description":"Answers \`200 ok\` (text/plain). Bad payloads get Slack's plain-text error codes (\`invalid_payload\`, \`no_text\`, \`invalid_blocks\`); an unregistered hook is \`404 no_service\` once any hook is registered. Also accepts form bodies with a \`payload=<json>\` field.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","maxLength":400},"blocks":{"type":"array","maxItems":3,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["section","divider","header","context"]},"text":{"type":"object","properties":{"type":{"type":"string","enum":["mrkdwn","plain_text"]},"text":{"type":"string","maxLength":200}}}}}},"thread_ts":{"type":"string","maxLength":24},"username":{"type":"string","maxLength":40},"icon_emoji":{"type":"string","maxLength":40},"unfurl_links":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Posted","content":{"text/plain":{"schema":{"type":"string","enum":["ok"]}}}},"400":{"description":"\`invalid_payload\`, \`no_text\`, \`invalid_blocks\`, \`too_many_attachments\`","content":{"text/plain":{"schema":{"type":"string"}}}},"403":{"description":"\`action_prohibited\`, \`posting_to_general_channel_denied\`","content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"\`no_service\`, \`channel_not_found\`","content":{"text/plain":{"schema":{"type":"string"}}}},"410":{"description":"\`channel_is_archived\`","content":{"text/plain":{"schema":{"type":"string"}}}},"429":{"description":"\`rate_limited\`, with \`retry-after\`","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"text/plain":{"schema":{"type":"string"}}}},"500":{"description":"\`internal_error\`","content":{"text/plain":{"schema":{"type":"string"}}}},"503":{"description":"\`service_unavailable\`","content":{"text/plain":{"schema":{"type":"string"}}}}},"security":[]}},"/api/chat.postMessage":{"post":{"operationId":"ChatPostMessage","summary":"chat.postMessage: post a message (or a thread reply) to a channel","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"text":{"type":"string","maxLength":400},"blocks":{"type":"string","description":"JSON-encoded blocks","maxLength":2000},"thread_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},"required":["channel"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"text":{"type":"string","maxLength":400},"blocks":{"type":"array","maxItems":3,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["section","divider","header","context"]},"text":{"type":"object","properties":{"type":{"type":"string","enum":["mrkdwn","plain_text"]},"text":{"type":"string","maxLength":200}}}}}},"thread_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"mrkdwn":{"type":"boolean"},"unfurl_links":{"type":"boolean"}},"required":["channel"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostMessageResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/chat.update":{"post":{"operationId":"ChatUpdate","summary":"chat.update: edit a message the bot posted","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"text":{"type":"string","maxLength":400}},"required":["channel","ts"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"text":{"type":"string","maxLength":400},"blocks":{"type":"array","maxItems":3,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["section","divider","header","context"]},"text":{"type":"object","properties":{"type":{"type":"string","enum":["mrkdwn","plain_text"]},"text":{"type":"string","maxLength":200}}}}}}},"required":["channel","ts"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/chat.postEphemeral":{"post":{"operationId":"ChatPostEphemeral","summary":"chat.postEphemeral: a message only one user sees","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"user":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}},"text":{"type":"string","maxLength":400}},"required":["channel","user"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"user":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}},"text":{"type":"string","maxLength":400},"blocks":{"type":"array","maxItems":3,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["section","divider","header","context"]},"text":{"type":"object","properties":{"type":{"type":"string","enum":["mrkdwn","plain_text"]},"text":{"type":"string","maxLength":200}}}}}},"thread_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},"required":["channel","user"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EphemeralResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/chat.getPermalink":{"get":{"operationId":"ChatGetPermalinkGet","summary":"chat.getPermalink (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"channel","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"}},{"name":"message_ts","in":"query","required":false,"schema":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PermalinkResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"ChatGetPermalink","summary":"chat.getPermalink: the permalink of a message","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"message_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},"required":["channel","message_ts"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"message_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},"required":["channel","message_ts"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PermalinkResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/reactions.add":{"post":{"operationId":"ReactionsAdd","summary":"reactions.add: react to a message","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"name":{"type":"string","enum":["eyes","white_check_mark","x","thumbsup","rotating_light"]}},"required":["channel","timestamp","name"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"name":{"type":"string","enum":["eyes","white_check_mark","x","thumbsup","rotating_light"]}},"required":["channel","timestamp","name"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/reactions.remove":{"post":{"operationId":"ReactionsRemove","summary":"reactions.remove: take a reaction back","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"name":{"type":"string","enum":["eyes","white_check_mark","x","thumbsup","rotating_light"]}},"required":["name"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"name":{"type":"string","enum":["eyes","white_check_mark","x","thumbsup","rotating_light"]}},"required":["name"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/reactions.get":{"get":{"operationId":"ReactionsGetGet","summary":"reactions.get (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"channel","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"}},{"name":"timestamp","in":"query","required":false,"schema":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},{"name":"full","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReactionsGetResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"ReactionsGet","summary":"reactions.get: a message and its reactions","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"full":{"type":"string","enum":["true","false"]}},"required":["channel","timestamp"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"full":{"type":"boolean"}},"required":["channel","timestamp"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReactionsGetResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/auth.test":{"get":{"operationId":"AuthTestGet","summary":"auth.test (GET)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTestResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"AuthTest","summary":"auth.test: who the token belongs to","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{}}},"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTestResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/conversations.join":{"post":{"operationId":"ConversationsJoin","summary":"conversations.join: the bot joins a public channel","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"}},"required":["channel"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"}},"required":["channel"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JoinResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/users.info":{"get":{"operationId":"UsersInfoGet","summary":"users.info (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"user","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"UsersInfo","summary":"users.info: a user's profile","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"user":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}}},"required":["user"]}},"application/json":{"schema":{"type":"object","properties":{"user":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}}},"required":["user"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/users.lookupByEmail":{"get":{"operationId":"UsersLookupByEmailGet","summary":"users.lookupByEmail (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"email","in":"query","required":false,"schema":{"type":"string","format":"email","maxLength":80}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"UsersLookupByEmail","summary":"users.lookupByEmail: find a user by email","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":80}},"required":["email"]}},"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":80}},"required":["email"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/files.info":{"get":{"operationId":"FilesInfoGet","summary":"files.info (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"file","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"file","missing":"F00MISSING"}}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"FilesInfo","summary":"files.info: a file's metadata and private download URL","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"file":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"file","missing":"F00MISSING"}}},"required":["file"]}},"application/json":{"schema":{"type":"object","properties":{"file":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"file","missing":"F00MISSING"}}},"required":["file"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/views.open":{"post":{"operationId":"ViewsOpen","summary":"views.open: open a modal for a trigger","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"trigger_id":{"type":"string","minLength":1,"maxLength":40},"view":{"type":"string","description":"JSON-encoded view","maxLength":2000}},"required":["trigger_id","view"]}},"application/json":{"schema":{"type":"object","properties":{"trigger_id":{"type":"string","minLength":1,"maxLength":40},"view":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["modal","home"]},"callback_id":{"type":"string","maxLength":40},"title":{"type":"object","properties":{"type":{"type":"string","enum":["plain_text"]},"text":{"type":"string","maxLength":24}}},"blocks":{"type":"array","maxItems":3,"items":{"type":"object"}},"private_metadata":{"type":"string","maxLength":200}}}},"required":["trigger_id","view"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Envelope":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"},"error":{"type":"string"},"warning":{"type":"string"},"needed":{"type":"string"},"provided":{"type":"string"},"response_metadata":{"type":"object","properties":{"messages":{"type":"array","items":{"type":"string"}},"warnings":{"type":"array","items":{"type":"string"}}}}}},"ErrorEnvelope":{"type":"object","required":["ok","error"],"properties":{"ok":{"type":"boolean","enum":[false]},"error":{"type":"string"}}},"Message":{"type":"object","required":["type","ts"],"properties":{"type":{"type":"string","enum":["message"]},"subtype":{"type":"string"},"text":{"type":"string"},"user":{"type":"string"},"bot_id":{"type":"string"},"app_id":{"type":"string"},"team":{"type":"string"},"ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"thread_ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"blocks":{"type":"array","items":{"type":"object"}},"edited":{"type":"object","properties":{"user":{"type":"string"},"ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}}}},"reactions":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"count":{"type":"integer"},"users":{"type":"array","items":{"type":"string"}}}}}}},"PostMessageResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"channel":{"type":"string"},"ts":{"type":"string","x-mockingbird-resource":{"type":"message","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"message":{"$ref":"#/components/schemas/Message"}}}]},"ReactionsGetResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"type":{"type":"string","enum":["message"]},"channel":{"type":"string"},"message":{"$ref":"#/components/schemas/Message"}}}]},"UpdateResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"channel":{"type":"string"},"ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"text":{"type":"string"},"message":{"$ref":"#/components/schemas/Message"}}}]},"EphemeralResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"message_ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}}}}]},"PermalinkResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"channel":{"type":"string"},"permalink":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}}]},"AuthTestResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"url":{"type":"string"},"team":{"type":"string"},"user":{"type":"string"},"team_id":{"type":"string"},"user_id":{"type":"string"},"bot_id":{"type":"string"},"is_enterprise_install":{"type":"boolean"}}}]},"Channel":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"is_channel":{"type":"boolean"},"is_private":{"type":"boolean"},"is_archived":{"type":"boolean"},"is_member":{"type":"boolean"},"created":{"type":"integer"}}},"JoinResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"channel":{"$ref":"#/components/schemas/Channel"}}}]},"User":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"user","identity":true}},"team_id":{"type":"string"},"name":{"type":"string"},"real_name":{"type":"string"},"deleted":{"type":"boolean"},"is_bot":{"type":"boolean"},"tz":{"type":"string"},"profile":{"type":"object","properties":{"email":{"type":"string"},"real_name":{"type":"string"},"display_name":{"type":"string"}}}}},"UserResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}}}]},"File":{"type":"object","required":["id"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"file","identity":true}},"name":{"type":"string"},"title":{"type":"string"},"mimetype":{"type":"string"},"filetype":{"type":"string"},"size":{"type":"integer"},"created":{"type":"integer"},"url_private":{"type":"string"},"url_private_download":{"type":"string"},"permalink":{"type":"string"}}},"FileResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"file":{"$ref":"#/components/schemas/File"}}}]},"ViewResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"view":{"type":"object","properties":{"id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"type":{"type":"string"},"team_id":{"type":"string"},"callback_id":{"type":"string"},"hash":{"type":"string","x-mockingbird-volatile":{"kind":"opaque"}},"blocks":{"type":"array","items":{"type":"object"}},"state":{"type":"object"},"private_metadata":{"type":"string"},"title":{"type":"object"},"app_id":{"type":"string"},"bot_id":{"type":"string"}}}}}]}}}}`);
3419
4223
  var operationIds = ["PostIncomingWebhook", "ChatPostMessage", "ChatUpdate", "ChatPostEphemeral", "ChatGetPermalinkGet", "ChatGetPermalink", "ReactionsAdd", "ReactionsRemove", "ReactionsGetGet", "ReactionsGet", "AuthTestGet", "AuthTest", "ConversationsJoin", "UsersInfoGet", "UsersInfo", "UsersLookupByEmailGet", "UsersLookupByEmail", "FilesInfoGet", "FilesInfo", "ViewsOpen"];
3420
4224
  var supportedOperationIds = ["PostIncomingWebhook", "ChatPostMessage", "ChatUpdate", "ChatPostEphemeral", "ChatGetPermalinkGet", "ChatGetPermalink", "ReactionsAdd", "ReactionsRemove", "ReactionsGetGet", "ReactionsGet", "AuthTestGet", "AuthTest", "ConversationsJoin", "UsersInfoGet", "UsersInfo", "UsersLookupByEmailGet", "UsersLookupByEmail", "FilesInfoGet", "FilesInfo", "ViewsOpen"];
3421
4225
 
@@ -3580,8 +4384,8 @@ var SLACK_PRESETS = {
3580
4384
  };
3581
4385
  var json6 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
3582
4386
  var adminError4 = (status, message) => json6(status, { error: { type: "mockingbird_admin", message } });
3583
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3584
- var list = (body, key) => Array.isArray(body) ? body : isRecord5(body) && Array.isArray(body[key]) ? body[key] : void 0;
4387
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4388
+ var list = (body, key) => Array.isArray(body) ? body : isRecord6(body) && Array.isArray(body[key]) ? body[key] : void 0;
3585
4389
  var hookSuffix = (value) => {
3586
4390
  let path = value;
3587
4391
  try {
@@ -3618,8 +4422,8 @@ var adminRoutes = (runtime) => ({
3618
4422
  hooks: runtime.instance(namespace).state.hooks.list({ order: "oldest" }).map((row) => ({ ...row.value, url: `/services/${row.value.path}` }))
3619
4423
  }),
3620
4424
  "POST /hooks": ({ body, namespace }) => {
3621
- const entries = list(body, "hooks") ?? (isRecord5(body) ? [body] : void 0);
3622
- if (!entries || entries.some((e) => !isRecord5(e) || typeof e.path !== "string")) {
4425
+ const entries = list(body, "hooks") ?? (isRecord6(body) ? [body] : void 0);
4426
+ if (!entries || entries.some((e) => !isRecord6(e) || typeof e.path !== "string")) {
3623
4427
  return adminError4(400, 'expected {"path": "T\u2026/B\u2026/X\u2026", "channel"?: "C\u2026"} or {"hooks": [...]}');
3624
4428
  }
3625
4429
  const api = runtime.instance(namespace);
@@ -3646,8 +4450,8 @@ var adminRoutes = (runtime) => ({
3646
4450
  channels: runtime.instance(namespace).state.channels.list({ order: "oldest" }).map((r) => r.value)
3647
4451
  }),
3648
4452
  "POST /channels": ({ body, namespace }) => {
3649
- const entries = list(body, "channels") ?? (isRecord5(body) ? [body] : void 0);
3650
- if (!entries || entries.some((e) => !isRecord5(e) || typeof e.name !== "string")) {
4453
+ const entries = list(body, "channels") ?? (isRecord6(body) ? [body] : void 0);
4454
+ if (!entries || entries.some((e) => !isRecord6(e) || typeof e.name !== "string")) {
3651
4455
  return adminError4(
3652
4456
  400,
3653
4457
  'expected {"id"?, "name", "is_private"?, "is_archived"?, "is_member"?}'
@@ -3673,8 +4477,8 @@ var adminRoutes = (runtime) => ({
3673
4477
  users: runtime.instance(namespace).state.users.list({ order: "oldest" }).map((r) => r.value)
3674
4478
  }),
3675
4479
  "POST /users": ({ body, namespace }) => {
3676
- const entries = list(body, "users") ?? (isRecord5(body) ? [body] : void 0);
3677
- if (!entries || entries.some((e) => !isRecord5(e) || typeof e.id !== "string")) {
4480
+ const entries = list(body, "users") ?? (isRecord6(body) ? [body] : void 0);
4481
+ if (!entries || entries.some((e) => !isRecord6(e) || typeof e.id !== "string")) {
3678
4482
  return adminError4(400, 'expected {"id": "U\u2026", "name"?, "real_name"?, "email"?}');
3679
4483
  }
3680
4484
  const api = runtime.instance(namespace);
@@ -3695,8 +4499,8 @@ var adminRoutes = (runtime) => ({
3695
4499
  return json6(201, { users: saved });
3696
4500
  },
3697
4501
  "POST /files": ({ body, namespace }) => {
3698
- const entries = list(body, "files") ?? (isRecord5(body) ? [body] : void 0);
3699
- if (!entries || entries.some((e) => !isRecord5(e) || typeof e.id !== "string")) {
4502
+ const entries = list(body, "files") ?? (isRecord6(body) ? [body] : void 0);
4503
+ if (!entries || entries.some((e) => !isRecord6(e) || typeof e.id !== "string")) {
3700
4504
  return adminError4(400, 'expected {"id": "F\u2026", "name"?, "mimetype"?, "size"?}');
3701
4505
  }
3702
4506
  const api = runtime.instance(namespace);
@@ -3722,7 +4526,7 @@ var adminRoutes = (runtime) => ({
3722
4526
  }),
3723
4527
  "GET /settings": ({ namespace }) => json6(200, runtime.instance(namespace).state.current()),
3724
4528
  "PUT /settings": ({ body, namespace }) => {
3725
- if (!isRecord5(body)) return adminError4(400, "expected a JSON object");
4529
+ if (!isRecord6(body)) return adminError4(400, "expected a JSON object");
3726
4530
  const patch = {};
3727
4531
  for (const key of [
3728
4532
  "teamId",
@@ -3750,7 +4554,7 @@ var adminRoutes = (runtime) => ({
3750
4554
  });
3751
4555
  var createRuntime2 = (options = {}) => createRuntime({
3752
4556
  name: SLACK_NAMESPACE,
3753
- document,
4557
+ document: document2,
3754
4558
  ...options.sqlite ? { sqlite: options.sqlite } : {},
3755
4559
  ...options.clock ? { clock: options.clock } : {},
3756
4560
  ...options.seed !== void 0 ? { seed: options.seed } : {},
@@ -3776,7 +4580,7 @@ var slackCredential = (request) => {
3776
4580
  const path = new URL(request.url).pathname;
3777
4581
  return WEBHOOK_PATH.exec(path.replace(/^\/ns\/[^/]+/, ""))?.[1];
3778
4582
  };
3779
- var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4583
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3780
4584
  var text = (status, body, headers = {}) => new Response(body, {
3781
4585
  status,
3782
4586
  headers: { "content-type": "text/plain; charset=utf-8", ...headers }
@@ -3799,7 +4603,7 @@ var readArgs = (context) => {
3799
4603
  const contentType = context.request.headers.get("content-type") ?? "";
3800
4604
  let json7 = false;
3801
4605
  if (body.kind === "json") {
3802
- if (!isRecord6(body.value)) throw new SlackError("invalid_json");
4606
+ if (!isRecord7(body.value)) throw new SlackError("invalid_json");
3803
4607
  Object.assign(args, body.value);
3804
4608
  json7 = true;
3805
4609
  } else if (body.kind === "form") {
@@ -3825,7 +4629,7 @@ var checkBlocks = (blocks) => {
3825
4629
  if (blocks === void 0 || blocks === null || blocks === "") return null;
3826
4630
  if (!Array.isArray(blocks) || blocks.length > 50) throw new SlackError("invalid_blocks");
3827
4631
  for (const block of blocks) {
3828
- if (!isRecord6(block) || typeof block.type !== "string") throw new SlackError("invalid_blocks");
4632
+ if (!isRecord7(block) || typeof block.type !== "string") throw new SlackError("invalid_blocks");
3829
4633
  }
3830
4634
  return blocks;
3831
4635
  };
@@ -3882,7 +4686,7 @@ var SlackAPI = class {
3882
4686
  ViewsOpen: api((args) => this.openView(args))
3883
4687
  });
3884
4688
  this.service = createService({
3885
- document,
4689
+ document: document2,
3886
4690
  handlers,
3887
4691
  sqlite,
3888
4692
  namespace,
@@ -4012,7 +4816,7 @@ var SlackAPI = class {
4012
4816
  return text(400, "invalid_payload");
4013
4817
  }
4014
4818
  } else return text(400, "invalid_payload");
4015
- if (!isRecord6(payload)) return text(400, "invalid_payload");
4819
+ if (!isRecord7(payload)) return text(400, "invalid_payload");
4016
4820
  let blocks;
4017
4821
  try {
4018
4822
  blocks = checkBlocks(payload.blocks);
@@ -4278,7 +5082,7 @@ var SlackAPI = class {
4278
5082
  openView(args) {
4279
5083
  if (!str(args.trigger_id)) throw new SlackError("invalid_arguments");
4280
5084
  const view = args.view;
4281
- if (!isRecord6(view) || view.type !== "modal" && view.type !== "home") {
5085
+ if (!isRecord7(view) || view.type !== "modal" && view.type !== "home") {
4282
5086
  throw new SlackError("invalid_arguments", {
4283
5087
  response_metadata: { messages: ["[ERROR] view.type must be modal"] }
4284
5088
  });
@@ -4302,7 +5106,7 @@ var SlackAPI = class {
4302
5106
  };
4303
5107
 
4304
5108
  export {
4305
- document,
5109
+ document2 as document,
4306
5110
  operationIds,
4307
5111
  supportedOperationIds,
4308
5112
  DEFAULT_SETTINGS,
@@ -4315,4 +5119,4 @@ export {
4315
5119
  slackCredential,
4316
5120
  SlackAPI
4317
5121
  };
4318
- //# sourceMappingURL=chunk-N46A45RB.js.map
5122
+ //# sourceMappingURL=chunk-XLLGBYTB.js.map