@api-doc-tor/express 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/chat-proxy.d.ts +12 -0
  2. package/dist/chat-proxy.d.ts.map +1 -0
  3. package/dist/chat-proxy.js +44 -0
  4. package/dist/chat-proxy.js.map +1 -0
  5. package/dist/docs-access-express.d.ts +16 -0
  6. package/dist/docs-access-express.d.ts.map +1 -0
  7. package/dist/docs-access-express.js +187 -0
  8. package/dist/docs-access-express.js.map +1 -0
  9. package/dist/index.d.ts +64 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +134 -0
  12. package/dist/index.js.map +1 -0
  13. package/package.json +36 -0
  14. package/static/assets/index-2Nv3pxOm.css +1 -0
  15. package/static/assets/index-6tehJlKh.js +214 -0
  16. package/static/assets/index-B148KlKZ.js +247 -0
  17. package/static/assets/index-B7AxOqhh.js +247 -0
  18. package/static/assets/index-BAVM9zLs.css +1 -0
  19. package/static/assets/index-BD4qyClR.js +216 -0
  20. package/static/assets/index-BZaQe_xi.css +1 -0
  21. package/static/assets/index-BjWlpH_h.js +216 -0
  22. package/static/assets/index-BmiFV0rx.css +1 -0
  23. package/static/assets/index-BqIvYIJT.js +247 -0
  24. package/static/assets/index-BwOiuEB4.js +216 -0
  25. package/static/assets/index-C372jkHK.js +214 -0
  26. package/static/assets/index-C9S9wEor.js +217 -0
  27. package/static/assets/index-Cd9PPQz6.css +1 -0
  28. package/static/assets/index-Cf2mt1FM.css +1 -0
  29. package/static/assets/index-Cfn42GaL.css +1 -0
  30. package/static/assets/index-Ckm4kbAk.css +1 -0
  31. package/static/assets/index-CzGmm9SR.css +1 -0
  32. package/static/assets/index-D0jOSazL.js +187 -0
  33. package/static/assets/index-D3-lCeHu.js +214 -0
  34. package/static/assets/index-DDRNEd4n.js +247 -0
  35. package/static/assets/index-DOyViYzn.js +247 -0
  36. package/static/assets/index-DZsbTHIg.js +214 -0
  37. package/static/assets/index-DaYbWEw0.js +190 -0
  38. package/static/assets/index-Djqsib7P.css +1 -0
  39. package/static/assets/index-Ds4IYSZ4.css +1 -0
  40. package/static/assets/index-Dzo0Xwn5.css +1 -0
  41. package/static/assets/index-L47Yb4hH.js +187 -0
  42. package/static/assets/index-NKfaEJl0.css +1 -0
  43. package/static/assets/index-U9Pdo8dD.js +214 -0
  44. package/static/assets/index-UVEGH1D3.css +1 -0
  45. package/static/assets/index-mZYiGRwU.css +1 -0
  46. package/static/assets/index-pOjZ5m-I.js +247 -0
  47. package/static/assets/index-rCcLuTWw.js +216 -0
  48. package/static/index.html +21 -0
@@ -0,0 +1,12 @@
1
+ import { type Request, type RequestHandler, type Response, type Router } from "express";
2
+ export type { ChatProvider, ChatMessage } from "@api-doc-tor/core";
3
+ /**
4
+ * Proxy LLM: el cliente envía la clave del usuario en `X-API-Doc-Tor-LLM-Key` (no se persiste en servidor).
5
+ */
6
+ export declare function handleChatProxy(req: Request, res: Response): Promise<void>;
7
+ export declare const chatProxyJsonMiddleware: RequestHandler;
8
+ /** POST `__chat` — acceso público (sin docsAccess). */
9
+ export declare function registerChatRoutes(router: Router): void;
10
+ /** POST `__chat` — solo si `check(req)` es true (p. ej. sesión docsAccess). */
11
+ export declare function registerChatRoutesProtected(router: Router, check: (req: Request) => boolean): void;
12
+ //# sourceMappingURL=chat-proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-proxy.d.ts","sourceRoot":"","sources":["../src/chat-proxy.ts"],"names":[],"mappings":"AACA,OAAgB,EAAE,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjG,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAQnE;;GAEG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAehF;AAED,eAAO,MAAM,uBAAuB,EAAE,cAA+C,CAAC;AAEtF,uDAAuD;AACvD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAEvD;AAED,+EAA+E;AAC/E,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,GAAG,IAAI,CAYlG"}
@@ -0,0 +1,44 @@
1
+ import { runLlmProxy, LLM_KEY_HEADER } from "@api-doc-tor/core";
2
+ import express from "express";
3
+ function getApiKey(req) {
4
+ const h = req.headers[LLM_KEY_HEADER];
5
+ if (typeof h === "string" && h.trim())
6
+ return h.trim();
7
+ return null;
8
+ }
9
+ /**
10
+ * Proxy LLM: el cliente envía la clave del usuario en `X-API-Doc-Tor-LLM-Key` (no se persiste en servidor).
11
+ */
12
+ export async function handleChatProxy(req, res) {
13
+ const apiKey = getApiKey(req);
14
+ if (!apiKey) {
15
+ res.status(401).type("application/json").send(JSON.stringify({
16
+ error: "missing_api_key",
17
+ message: "Cabecera X-API-Doc-Tor-LLM-Key requerida",
18
+ }));
19
+ return;
20
+ }
21
+ const body = req.body;
22
+ const result = await runLlmProxy(apiKey, body);
23
+ res.status(result.status).type("application/json").send(result.body);
24
+ }
25
+ export const chatProxyJsonMiddleware = express.json({ limit: "4mb" });
26
+ /** POST `__chat` — acceso público (sin docsAccess). */
27
+ export function registerChatRoutes(router) {
28
+ router.post("/__chat", chatProxyJsonMiddleware, (req, res) => void handleChatProxy(req, res));
29
+ }
30
+ /** POST `__chat` — solo si `check(req)` es true (p. ej. sesión docsAccess). */
31
+ export function registerChatRoutesProtected(router, check) {
32
+ router.post("/__chat", chatProxyJsonMiddleware, (req, res) => {
33
+ if (!check(req)) {
34
+ const base = req.baseUrl || "/";
35
+ res
36
+ .status(401)
37
+ .type("application/json")
38
+ .send(JSON.stringify({ error: "unauthorized", loginPath: `${base}/__auth/login` }));
39
+ return;
40
+ }
41
+ void handleChatProxy(req, res);
42
+ });
43
+ }
44
+ //# sourceMappingURL=chat-proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-proxy.js","sourceRoot":"","sources":["../src/chat-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAA4B,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,OAA0E,MAAM,SAAS,CAAC;AAIjG,SAAS,SAAS,CAAC,GAAY;IAC7B,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IACtC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE;QAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IACvD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAY,EAAE,GAAa;IAC/D,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAC3C,IAAI,CAAC,SAAS,CAAC;YACb,KAAK,EAAE,iBAAiB;YACxB,OAAO,EAAE,0CAA0C;SACpD,CAAC,CACH,CAAC;QACF,OAAO;IACT,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,CAAC,IAA2B,CAAC;IAC7C,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC/C,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAmB,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;AAEtF,uDAAuD;AACvD,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,uBAAuB,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,KAAK,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAChG,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,2BAA2B,CAAC,MAAc,EAAE,KAAgC;IAC1F,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,uBAAuB,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC3D,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YAChB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC;YAChC,GAAG;iBACA,MAAM,CAAC,GAAG,CAAC;iBACX,IAAI,CAAC,kBAAkB,CAAC;iBACxB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,GAAG,IAAI,eAAe,EAAE,CAAC,CAAC,CAAC;YACtF,OAAO;QACT,CAAC;QACD,KAAK,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { type DocsAccessConfig } from "@api-doc-tor/core/docs-access";
2
+ import type { OpenAPIV3 } from "openapi-types";
3
+ import { type Router } from "express";
4
+ export declare function attachDocsAccessToRouter(router: Router, options: {
5
+ document: OpenAPIV3.Document;
6
+ docsAccess: DocsAccessConfig;
7
+ branding?: import("@api-doc-tor/core/ui-branding").ApiDocTorUiBranding;
8
+ buildUiConfigJson: (branding?: import("@api-doc-tor/core/ui-branding").ApiDocTorUiBranding) => unknown;
9
+ presetBundle?: unknown;
10
+ specName: string;
11
+ presetName: string;
12
+ uiConfigName: string;
13
+ hasPreset: boolean;
14
+ staticRoot: string;
15
+ }): void;
16
+ //# sourceMappingURL=docs-access-express.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs-access-express.d.ts","sourceRoot":"","sources":["../src/docs-access-express.ts"],"names":[],"mappings":"AACA,OAAO,EAML,KAAK,gBAAgB,EAEtB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAgB,EAA+B,KAAK,MAAM,EAAE,MAAM,SAAS,CAAC;AAwC5E,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;IACP,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC;IAC7B,UAAU,EAAE,gBAAgB,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,+BAA+B,EAAE,mBAAmB,CAAC;IACvE,iBAAiB,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,+BAA+B,EAAE,mBAAmB,KAAK,OAAO,CAAC;IACvG,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,GACA,IAAI,CAmKN"}
@@ -0,0 +1,187 @@
1
+ import { assertOpenAPI3 } from "@api-doc-tor/core";
2
+ import { filterOpenAPIDocumentForUser, findDocsUser, resolveSessionSecret, signDocsSession, verifyDocsSession, } from "@api-doc-tor/core/docs-access";
3
+ import { join } from "node:path";
4
+ import express from "express";
5
+ import { registerChatRoutesProtected } from "./chat-proxy.js";
6
+ const COOKIE_DEFAULT = "api_doc_tor_session";
7
+ function escapeHtml(s) {
8
+ return s
9
+ .replace(/&/g, "&amp;")
10
+ .replace(/</g, "&lt;")
11
+ .replace(/>/g, "&gt;")
12
+ .replace(/"/g, "&quot;");
13
+ }
14
+ function getCookie(req, name) {
15
+ const raw = req.headers.cookie;
16
+ if (!raw)
17
+ return undefined;
18
+ for (const part of raw.split(";")) {
19
+ const i = part.indexOf("=");
20
+ if (i === -1)
21
+ continue;
22
+ const k = part.slice(0, i).trim();
23
+ if (k !== name)
24
+ continue;
25
+ return decodeURIComponent(part.slice(i + 1).trim());
26
+ }
27
+ return undefined;
28
+ }
29
+ function getUserFromRequest(req, cfg, secret, cookieName) {
30
+ const token = getCookie(req, cookieName);
31
+ if (!token)
32
+ return null;
33
+ const v = verifyDocsSession(token, secret);
34
+ if (!v)
35
+ return null;
36
+ const u = cfg.users.find((x) => x.username === v.username);
37
+ return u ?? null;
38
+ }
39
+ export function attachDocsAccessToRouter(router, options) {
40
+ const cfg = options.docsAccess;
41
+ const { secret } = resolveSessionSecret(cfg.sessionSecret);
42
+ const cookieName = cfg.sessionCookieName ?? COOKIE_DEFAULT;
43
+ const maxAge = cfg.sessionMaxAgeSec ?? 60 * 60 * 24 * 7;
44
+ const loginTitle = cfg.loginTitle ?? "Acceso a la documentación";
45
+ const parseLogin = [
46
+ express.json(),
47
+ express.urlencoded({ extended: false }),
48
+ ];
49
+ router.post("/__auth/login", ...parseLogin, (req, res) => {
50
+ const body = req.body;
51
+ const username = typeof body.username === "string" ? body.username : "";
52
+ const password = typeof body.password === "string" ? body.password : "";
53
+ const redirectRaw = typeof body.redirect === "string" ? body.redirect : "";
54
+ const user = findDocsUser(cfg, username, password);
55
+ if (!user) {
56
+ if (req.is("application/json")) {
57
+ res.status(401).type("application/json").send(JSON.stringify({ error: "invalid_credentials" }));
58
+ return;
59
+ }
60
+ res.status(401).type("text/html")
61
+ .send(`<!DOCTYPE html><html><head><meta charset="utf-8"><title>Error</title></head><body>
62
+ <p>Credenciales inválidas.</p><p><a href="${req.baseUrl}/__auth/login">Volver</a></p></body></html>`);
63
+ return;
64
+ }
65
+ const token = signDocsSession(user.username, secret, maxAge);
66
+ const base = req.baseUrl || "/";
67
+ res.cookie(cookieName, token, {
68
+ httpOnly: true,
69
+ sameSite: "lax",
70
+ path: base,
71
+ maxAge: maxAge * 1000,
72
+ });
73
+ let target = `${base}/`;
74
+ if (redirectRaw.startsWith("/") && (redirectRaw.startsWith(base) || redirectRaw.startsWith(`${base}/`))) {
75
+ target = redirectRaw;
76
+ }
77
+ else if (redirectRaw.startsWith("/") && !redirectRaw.includes("..")) {
78
+ target = redirectRaw;
79
+ }
80
+ if (req.is("application/json")) {
81
+ res.type("application/json").send(JSON.stringify({ ok: true, username: user.username }));
82
+ return;
83
+ }
84
+ res.redirect(302, target);
85
+ });
86
+ router.get("/__auth/login", (req, res) => {
87
+ if (getUserFromRequest(req, cfg, secret, cookieName)) {
88
+ const q = typeof req.query.redirect === "string" ? req.query.redirect : "";
89
+ const base = req.baseUrl || "/";
90
+ const safe = q.startsWith("/") && !q.includes("..") ? q : `${base}/`;
91
+ res.redirect(302, safe);
92
+ return;
93
+ }
94
+ const redirect = typeof req.query.redirect === "string" ? escapeHtml(req.query.redirect) : `${req.baseUrl}/`;
95
+ const html = `<!DOCTYPE html>
96
+ <html lang="es">
97
+ <head>
98
+ <meta charset="utf-8"/>
99
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
100
+ <title>${escapeHtml(loginTitle)}</title>
101
+ <style>
102
+ body { font-family: system-ui, sans-serif; background: #0f172a; color: #e2e8f0; margin: 0; min-height: 100vh; display: flex; align-items: center; justify-content: center; }
103
+ form { background: #1e293b; padding: 1.5rem; border-radius: 12px; min-width: 280px; }
104
+ label { display: block; margin-bottom: 0.5rem; font-size: 0.875rem; }
105
+ input { width: 100%; box-sizing: border-box; padding: 0.5rem; margin-bottom: 1rem; border-radius: 8px; border: 1px solid #475569; background: #0f172a; color: inherit; }
106
+ button { width: 100%; padding: 0.6rem; border-radius: 8px; border: none; background: #38bdf8; color: #0f172a; font-weight: 600; cursor: pointer; }
107
+ h1 { font-size: 1.125rem; margin: 0 0 1rem; }
108
+ </style>
109
+ </head>
110
+ <body>
111
+ <form method="post" action="${req.baseUrl}/__auth/login">
112
+ <h1>${escapeHtml(loginTitle)}</h1>
113
+ <input type="hidden" name="redirect" value="${redirect}"/>
114
+ <label>Usuario<input name="username" autocomplete="username" required/></label>
115
+ <label>Contraseña<input name="password" type="password" autocomplete="current-password" required/></label>
116
+ <button type="submit">Entrar</button>
117
+ </form>
118
+ </body>
119
+ </html>`;
120
+ res.type("text/html").send(html);
121
+ });
122
+ router.post("/__auth/logout", (req, res) => {
123
+ const base = req.baseUrl || "/";
124
+ res.clearCookie(cookieName, { path: base });
125
+ if (req.is("application/json")) {
126
+ res.type("application/json").send(JSON.stringify({ ok: true }));
127
+ return;
128
+ }
129
+ res.redirect(302, `${base}/__auth/login`);
130
+ });
131
+ router.get("/__auth/session", (req, res) => {
132
+ const user = getUserFromRequest(req, cfg, secret, cookieName);
133
+ if (!user) {
134
+ res.status(401).type("application/json").send(JSON.stringify({ error: "unauthorized" }));
135
+ return;
136
+ }
137
+ res.type("application/json").send(JSON.stringify({
138
+ username: user.username,
139
+ allowedTags: user.allowedTags ?? null,
140
+ allowedPathPrefixes: user.allowedPathPrefixes ?? null,
141
+ }));
142
+ });
143
+ registerChatRoutesProtected(router, (req) => !!getUserFromRequest(req, cfg, secret, cookieName));
144
+ router.use((req, res, next) => {
145
+ const user = getUserFromRequest(req, cfg, secret, cookieName);
146
+ if (user) {
147
+ req.docsAccessUser = user;
148
+ return next();
149
+ }
150
+ const wantsJson = req.path.endsWith(".json") ||
151
+ (req.accepts("json") && !req.accepts("html")) ||
152
+ (req.path.includes("/assets/") && !req.accepts("html"));
153
+ if (wantsJson) {
154
+ res.status(401).type("application/json").send(JSON.stringify({ error: "unauthorized", loginPath: `${req.baseUrl}/__auth/login` }));
155
+ return;
156
+ }
157
+ const here = req.originalUrl.split("?")[0] ?? "";
158
+ res.redirect(302, `${req.baseUrl}/__auth/login?redirect=${encodeURIComponent(here)}`);
159
+ });
160
+ router.get(`/${options.specName}`, (req, res) => {
161
+ const user = req.docsAccessUser;
162
+ const doc = filterOpenAPIDocumentForUser(options.document, user);
163
+ assertOpenAPI3(doc);
164
+ res.type("application/json").send(JSON.stringify(doc, null, 2));
165
+ });
166
+ if (options.hasPreset) {
167
+ router.get(`/${options.presetName}`, (_req, res) => {
168
+ res.type("application/json").send(JSON.stringify(options.presetBundle, null, 2));
169
+ });
170
+ }
171
+ router.get(`/${options.uiConfigName}`, (_req, res) => {
172
+ const cfgJson = options.buildUiConfigJson(options.branding);
173
+ res.type("application/json").send(JSON.stringify({ ...cfgJson, docsAuthRequired: true }, null, 2));
174
+ });
175
+ router.use(express.static(options.staticRoot, { index: false }));
176
+ router.get(/.*/, (req, res) => {
177
+ if (req.path.includes("/assets/")) {
178
+ res.status(404).send("Not Found");
179
+ return;
180
+ }
181
+ res.sendFile(join(options.staticRoot, "index.html"), (err) => {
182
+ if (err)
183
+ res.status(500).send(err.message);
184
+ });
185
+ });
186
+ }
187
+ //# sourceMappingURL=docs-access-express.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs-access-express.js","sourceRoot":"","sources":["../src/docs-access-express.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import { type DocsAccessConfig } from "@api-doc-tor/core/docs-access";
2
+ import { type ApiDocTorPresetInput } from "@api-doc-tor/core/preset";
3
+ import { type ApiDocTorUiBranding } from "@api-doc-tor/core/ui-branding";
4
+ import type { OpenAPIV3 } from "openapi-types";
5
+ import type { Server } from "node:http";
6
+ import { type Application, type Router } from "express";
7
+ export type { ApiDocTorPresetInput } from "@api-doc-tor/core/preset";
8
+ export type { ApiDocTorUiBranding } from "@api-doc-tor/core/ui-branding";
9
+ export type { DocsAccessConfig, DocsAccessUser } from "@api-doc-tor/core/docs-access";
10
+ export interface ApiDocTorExpressOptions {
11
+ document: OpenAPIV3.Document;
12
+ /**
13
+ * URL base de la API real (p. ej. producción). Se inyecta como primer `servers[].url`
14
+ * en el OpenAPI servido para “Probar”, codegen y exports contra un backend existente.
15
+ */
16
+ publicUrl?: string;
17
+ path?: string;
18
+ specFileName?: string;
19
+ uiConfigFileName?: string;
20
+ branding?: ApiDocTorUiBranding;
21
+ presetFileName?: string;
22
+ preset?: ApiDocTorPresetInput;
23
+ /**
24
+ * Acceso a la documentación por usuario/contraseña (JSON).
25
+ * Si está activo, `openapi.json` y la SPA quedan protegidos; cada usuario puede filtrar por tags/prefijos de ruta.
26
+ */
27
+ docsAccess?: DocsAccessConfig;
28
+ staticRoot?: string;
29
+ }
30
+ export declare function apiDocTorRouter(options: ApiDocTorExpressOptions): Router;
31
+ export declare function setupApiDocTorExpress(app: Application, options: ApiDocTorExpressOptions): void;
32
+ /**
33
+ * Crea una aplicación Express que **solo** monta la documentación (OpenAPI + SPA).
34
+ * Úsala cuando no tengas otra app: `createApiDocTorApp(opts).listen(3333)`.
35
+ */
36
+ export declare function createApiDocTorApp(options: ApiDocTorExpressOptions): Application;
37
+ /** Opciones extra para arrancar el servidor sin configurar `listen` a mano. */
38
+ export type ListenApiDocTorStandaloneOptions = ApiDocTorExpressOptions & {
39
+ /** Puerto; por defecto `process.env.PORT` o `3333`. */
40
+ port?: number;
41
+ /** Host; por defecto `process.env.HOST` o `0.0.0.0`. */
42
+ host?: string;
43
+ };
44
+ export type ListenApiDocTorStandaloneResult = {
45
+ app: Application;
46
+ server: Server;
47
+ port: number;
48
+ host: string;
49
+ /** URL típica para abrir el navegador (path de docs incluido). */
50
+ docsUrl: string;
51
+ /** Igual que `publicUrl` en opciones, normalizada; solo si se pasó. */
52
+ apiPublicUrl?: string;
53
+ };
54
+ /**
55
+ * Crea la app, escucha en puerto/host y resuelve cuando el servidor está listo.
56
+ * Pensado para “solo documentación”: no hace falta otra aplicación Express.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * await listenApiDocTorStandalone({ document: myOpenApi, path: "/docs" });
61
+ * ```
62
+ */
63
+ export declare function listenApiDocTorStandalone(options: ListenApiDocTorStandaloneOptions): Promise<ListenApiDocTorStandaloneResult>;
64
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAC3F,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAqB,KAAK,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAC5F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAgB,EAAE,KAAK,WAAW,EAA+B,KAAK,MAAM,EAAE,MAAM,SAAS,CAAC;AAS9F,YAAY,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACrE,YAAY,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACzE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAEtF,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC;IAC7B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B;;;OAGG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AASD,wBAAgB,eAAe,CAAC,OAAO,EAAE,uBAAuB,GAAG,MAAM,CAyDxE;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAG9F;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,uBAAuB,GAAG,WAAW,CAIhF;AAED,+EAA+E;AAC/E,MAAM,MAAM,gCAAgC,GAAG,uBAAuB,GAAG;IACvE,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG;IAC5C,GAAG,EAAE,WAAW,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAiBF;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,gCAAgC,GACxC,OAAO,CAAC,+BAA+B,CAAC,CAyB1C"}
package/dist/index.js ADDED
@@ -0,0 +1,134 @@
1
+ import { applyPublicUrlToDocument, assertOpenAPI3 } from "@api-doc-tor/core";
2
+ import { isDocsAccessEnabled } from "@api-doc-tor/core/docs-access";
3
+ import { createExportBundleFromPreset, isPresetNonEmpty, } from "@api-doc-tor/core/preset";
4
+ import { buildUiConfigJson } from "@api-doc-tor/core/ui-branding";
5
+ import express from "express";
6
+ import { existsSync } from "node:fs";
7
+ import { dirname, join } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import { registerChatRoutes } from "./chat-proxy.js";
10
+ import { attachDocsAccessToRouter } from "./docs-access-express.js";
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ function resolveStaticRoot(custom) {
13
+ if (custom)
14
+ return custom;
15
+ const pkgStatic = join(__dirname, "..", "static");
16
+ if (existsSync(join(pkgStatic, "index.html")))
17
+ return pkgStatic;
18
+ return join(__dirname, "..", "..", "ui", "dist");
19
+ }
20
+ export function apiDocTorRouter(options) {
21
+ const document = applyPublicUrlToDocument(options.document, options.publicUrl);
22
+ const specName = options.specFileName ?? "openapi.json";
23
+ const presetName = options.presetFileName ?? "preset.json";
24
+ const uiConfigName = options.uiConfigFileName ?? "ui-config.json";
25
+ const staticRoot = resolveStaticRoot(options.staticRoot);
26
+ const router = express.Router();
27
+ const hasPreset = isPresetNonEmpty(options.preset);
28
+ const presetBundle = hasPreset ? createExportBundleFromPreset(options.preset) : undefined;
29
+ if (isDocsAccessEnabled(options.docsAccess)) {
30
+ attachDocsAccessToRouter(router, {
31
+ document,
32
+ docsAccess: options.docsAccess,
33
+ branding: options.branding,
34
+ buildUiConfigJson: (b) => buildUiConfigJson(b),
35
+ presetBundle,
36
+ specName,
37
+ presetName,
38
+ uiConfigName,
39
+ hasPreset,
40
+ staticRoot,
41
+ });
42
+ return router;
43
+ }
44
+ registerChatRoutes(router);
45
+ router.get(`/${specName}`, (_req, res) => {
46
+ assertOpenAPI3(document);
47
+ res.type("application/json").send(JSON.stringify(document, null, 2));
48
+ });
49
+ if (hasPreset) {
50
+ router.get(`/${presetName}`, (_req, res) => {
51
+ res.type("application/json").send(JSON.stringify(presetBundle, null, 2));
52
+ });
53
+ }
54
+ router.get(`/${uiConfigName}`, (_req, res) => {
55
+ const cfg = buildUiConfigJson(options.branding);
56
+ res.type("application/json").send(JSON.stringify(cfg, null, 2));
57
+ });
58
+ router.use(express.static(staticRoot, { index: false }));
59
+ router.get(/.*/, (req, res) => {
60
+ if (req.path.includes("/assets/")) {
61
+ res.status(404).send("Not Found");
62
+ return;
63
+ }
64
+ res.sendFile(join(staticRoot, "index.html"), (err) => {
65
+ if (err)
66
+ res.status(500).send(err.message);
67
+ });
68
+ });
69
+ return router;
70
+ }
71
+ export function setupApiDocTorExpress(app, options) {
72
+ const base = options.path ?? "/docs";
73
+ app.use(base, apiDocTorRouter(options));
74
+ }
75
+ /**
76
+ * Crea una aplicación Express que **solo** monta la documentación (OpenAPI + SPA).
77
+ * Úsala cuando no tengas otra app: `createApiDocTorApp(opts).listen(3333)`.
78
+ */
79
+ export function createApiDocTorApp(options) {
80
+ const app = express();
81
+ setupApiDocTorExpress(app, options);
82
+ return app;
83
+ }
84
+ function resolveListenPort(opts) {
85
+ if (opts.port != null && Number.isFinite(opts.port))
86
+ return opts.port;
87
+ const env = process.env.PORT;
88
+ if (env != null && env !== "") {
89
+ const n = Number(env);
90
+ if (Number.isFinite(n))
91
+ return n;
92
+ }
93
+ return 3333;
94
+ }
95
+ function resolveListenHost(opts) {
96
+ if (opts.host != null && opts.host !== "")
97
+ return opts.host;
98
+ return process.env.HOST != null && process.env.HOST !== "" ? process.env.HOST : "0.0.0.0";
99
+ }
100
+ /**
101
+ * Crea la app, escucha en puerto/host y resuelve cuando el servidor está listo.
102
+ * Pensado para “solo documentación”: no hace falta otra aplicación Express.
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * await listenApiDocTorStandalone({ document: myOpenApi, path: "/docs" });
107
+ * ```
108
+ */
109
+ export function listenApiDocTorStandalone(options) {
110
+ const app = createApiDocTorApp(options);
111
+ const port = resolveListenPort(options);
112
+ const host = resolveListenHost(options);
113
+ const path = options.path ?? "/docs";
114
+ return new Promise((resolve, reject) => {
115
+ const server = app.listen(port, host, () => {
116
+ const addr = server.address();
117
+ const realPort = typeof addr === "object" && addr && "port" in addr ? addr.port : port;
118
+ const displayHost = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
119
+ const docsUrl = `http://${displayHost}:${realPort}${path.endsWith("/") ? path.slice(0, -1) : path}`;
120
+ const rawPu = options.publicUrl?.trim();
121
+ const apiPublicUrl = rawPu ? rawPu.replace(/\/$/, "") : undefined;
122
+ resolve({
123
+ app,
124
+ server,
125
+ port: realPort,
126
+ host,
127
+ docsUrl,
128
+ ...(apiPublicUrl != null ? { apiPublicUrl } : {}),
129
+ });
130
+ });
131
+ server.once("error", reject);
132
+ });
133
+ }
134
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@api-doc-tor/express",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "static"
16
+ ],
17
+ "dependencies": {
18
+ "express": "^4.21.1",
19
+ "openapi-types": "^12.1.3",
20
+ "@api-doc-tor/core": "0.1.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/express": "^5.0.0",
24
+ "@types/node": "^22.10.0",
25
+ "tsx": "^4.19.2",
26
+ "typescript": "^5.7.2"
27
+ },
28
+ "peerDependencies": {
29
+ "express": "^4 || ^5"
30
+ },
31
+ "scripts": {
32
+ "prebuild": "pnpm --filter @api-doc-tor/ui run build && node scripts/copy-ui.mjs",
33
+ "build": "tsc -p tsconfig.json",
34
+ "test": "node --import tsx/esm --test test/setup.test.ts"
35
+ }
36
+ }
@@ -0,0 +1 @@
1
+ *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:DM Sans,system-ui,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.mb-2{margin-bottom:.5rem}.ml-2{margin-left:.5rem}.mt-1{margin-top:.25rem}.mt-4{margin-top:1rem}.block{display:block}.flex{display:flex}.hidden{display:none}.h-32{height:8rem}.max-h-64{max-height:16rem}.max-h-96{max-height:24rem}.min-h-0{min-height:0px}.min-h-screen{min-height:100vh}.w-12{width:3rem}.w-16{width:4rem}.w-20{width:5rem}.w-24{width:6rem}.w-64{width:16rem}.w-full{width:100%}.min-w-\[120px\]{min-width:120px}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-lg{border-radius:.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-accent{--tw-border-opacity: 1;border-color:rgb(56 189 248 / var(--tw-border-opacity, 1))}.border-red-500\/30{border-color:#ef44444d}.border-surface-700{--tw-border-opacity: 1;border-color:rgb(36 48 68 / var(--tw-border-opacity, 1))}.bg-accent{--tw-bg-opacity: 1;background-color:rgb(56 189 248 / var(--tw-bg-opacity, 1))}.bg-accent\/20{background-color:#38bdf833}.bg-accent\/30{background-color:#38bdf84d}.bg-surface-700{--tw-bg-opacity: 1;background-color:rgb(36 48 68 / var(--tw-bg-opacity, 1))}.bg-surface-800{--tw-bg-opacity: 1;background-color:rgb(26 34 48 / var(--tw-bg-opacity, 1))}.bg-surface-900{--tw-bg-opacity: 1;background-color:rgb(18 23 31 / var(--tw-bg-opacity, 1))}.bg-surface-900\/50{background-color:#12171f80}.bg-surface-900\/80{background-color:#12171fcc}.bg-surface-950{--tw-bg-opacity: 1;background-color:rgb(12 15 20 / var(--tw-bg-opacity, 1))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.text-left{text-align:left}.text-center{text-align:center}.font-display{font-family:Outfit,system-ui,sans-serif}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.text-accent{--tw-text-opacity: 1;color:rgb(56 189 248 / var(--tw-text-opacity, 1))}.text-emerald-300\/90{color:#6ee7b7e6}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.text-slate-200{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity, 1))}.text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity, 1))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity, 1))}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.text-surface-950{--tw-text-opacity: 1;color:rgb(12 15 20 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-90{opacity:.9}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}body{margin:0}.hover\:bg-surface-700:hover{--tw-bg-opacity: 1;background-color:rgb(36 48 68 / var(--tw-bg-opacity, 1))}.hover\:bg-surface-800:hover{--tw-bg-opacity: 1;background-color:rgb(26 34 48 / var(--tw-bg-opacity, 1))}@media(min-width:768px){.md\:w-64{width:16rem}.md\:flex-row{flex-direction:row}.md\:flex-col{flex-direction:column}.md\:border-b-0{border-bottom-width:0px}.md\:border-r{border-right-width:1px}}@media(min-width:1024px){.lg\:max-h-none{max-height:none}.lg\:w-72{width:18rem}.lg\:flex-row{flex-direction:row}.lg\:border-b-0{border-bottom-width:0px}.lg\:border-r{border-right-width:1px}}