@bitam/ebavel-apps 0.0.0-stage → 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.
package/dist/ebavel.js ADDED
@@ -0,0 +1,3 @@
1
+ /*! @bitam/ebavel-apps 0.1.0 */
2
+ "use strict";(()=>{var oe=["NOT_CONNECTED","DECLINED","TOOL_ERROR","UNSUPPORTED","TIMEOUT","ABORTED","INVALID_ARGS"],q=e=>typeof e=="string"&&oe.includes(e);var f=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),w=(e,t=500)=>e.length>t?`${e.slice(0,t)}…`:e,i=class extends Error{code;details;constructor(t,n,u){super(w(n)),this.name="EbavelAppError",this.code=t,u!==void 0&&(this.details=u)}};function V(e,t="TOOL_ERROR"){return e instanceof i?e:e instanceof Error?new i(t,e.message):f(e)&&typeof e.message=="string"?new i(t,e.message):new i(t,typeof e=="string"?e:"Error desconocido")}function se(e){return(Array.isArray(e.content)?e.content:[]).find(u=>f(u)&&u.type==="text"&&typeof u.text=="string")?.text}function H(e,t){if(f(e)){let n=Array.isArray(e.errors)?e.errors[0]:void 0;if(f(n)&&typeof n.message=="string")return typeof n.code=="string"?`${n.code}: ${n.message}`:n.message;if(f(e.error)&&typeof e.error.message=="string")return e.error.message;if(typeof e.error=="string")return e.error;if(typeof e.message=="string")return e.message}return t}function j(e){if(!f(e))throw new i("TOOL_ERROR","La tool devolvió una respuesta que no es un objeto.");let t=e.payload??e.structuredContent,n=se(e);if(t==null&&n!==void 0)try{t=JSON.parse(n)}catch{throw e.isError===!0?new i("TOOL_ERROR",w(n)):new i("TOOL_ERROR","La tool devolvió un texto que no es JSON.")}if(e.isError===!0)throw new i("TOOL_ERROR",H(t,n??"La tool devolvió un error."),t);if(t==null)throw new i("TOOL_ERROR","La tool no devolvió un resultado.");return t}function U(e){if(!f(e))throw new i("TOOL_ERROR","La respuesta de la tool no es un objeto.");if(e.success===!1||e.error!==void 0)throw new i("TOOL_ERROR",H(e,"La tool devolvió un error."),e);return e}var ie=new Set(["localhost","127.0.0.1"]),ae=()=>!1;function A(e){if(typeof e!="string"||e.length>4096)return null;try{let t=new URL(e);return t.protocol==="https:"||ae()&&t.protocol==="http:"&&ie.has(t.hostname)?t.toString():null}catch{return null}}var G=["query","describe_form"],ue={bad_request:"TOOL_ERROR",server_not_connected:"NOT_CONNECTED",needs_reauth:"NOT_CONNECTED",selection_required:"NOT_CONNECTED",not_in_manifest:"DECLINED",approval_required:"DECLINED",consent_required:"DECLINED",blocked_by_policy:"DECLINED"};function C(e){if(e instanceof i)return e;let t=f(e)&&typeof e.code=="string"?e.code:void 0,n=f(e)&&typeof e.message=="string"||e instanceof Error?e.message:typeof e=="string"?e:"Error de Claude",u=(t!==void 0?ue[t]:void 0)??"TOOL_ERROR",a=u==="NOT_CONNECTED"?" Conecta (o vuelve a autorizar) el conector eBavel en claude.ai.":u==="DECLINED"?" Acepta el permiso de conectores que muestra claude.ai al abrir el artifact.":"";return new i(u,`${t?`[${t}] `:""}${n}${a}`)}var ce=e=>f(e)&&typeof e.callTool=="function"&&typeof e.listTools=="function";function le(e){let t=f(e)&&Array.isArray(e.servers)?e.servers:[],n=[],u=[];for(let a of t){if(!f(a)||typeof a.server!="string")continue;n.push(a.server);let d=new Set((Array.isArray(a.tools)?a.tools:[]).map(o=>f(o)?o.name:o));G.every(o=>d.has(o))&&u.push(a.server)}return{all:n,matching:u}}function $(e){let t,n,u=()=>(t??=(async()=>{let o;try{o=await e.claude.use("mcp")}catch(p){throw t=void 0,C(p)}if(!ce(o))throw t=void 0,new i("NOT_CONNECTED",'Claude no expone el acceso a conectores MCP (`window.claude.use("mcp")`). Declara el conector eBavel al crear el artifact.');return o})(),t);async function a(o){let p=e.getServer();if(p!==void 0)return p;if(n!==void 0)return n;let l;try{l=await o.listTools()}catch(v){throw C(v)}let{all:y,matching:h}=le(l),g=h[0];if(h.length===1&&g!==void 0)return n=g,g;let E=v=>v.length>0?v.map(T=>`«${T}»`).join(", "):"ninguno";throw new i("NOT_CONNECTED",h.length===0?`No hay ningún conector con las tools ${G.join(" y ")}. Conectores disponibles: ${E(y)}. Conecta eBavel en claude.ai o usa ebavel.configure({ server }).`:`Hay más de un conector eBavel candidato: ${E(h)}. Elige uno con ebavel.configure({ server: '…' }).`)}async function d(o,p){let l=await u(),y=await a(l),h;try{h=await l.callTool(y,o,p)}catch(g){throw C(g)}return j(h)}return{host:"claude",canExport:!0,query:o=>d("query",o),async export(o){let{html:p,title:l}=o.source(o.title),y=U(await d("export_dashboard",{html:p,title:l})),h=A(y.importUrl);if(h===null)throw new i("TOOL_ERROR","La respuesta de export_dashboard no trae un enlace https válido.");let g=typeof y.expiresAt=="string"?w(y.expiresAt,64):void 0;return{importUrl:h,...g!==void 0?{expiresAt:g}:{},opened:e.open(h)}},openLink(o){let p=A(o);return p===null?Promise.reject(new i("INVALID_ARGS","Solo se pueden abrir enlaces https.")):Promise.resolve({opened:e.open(p),url:p})}}}var de=4,N=()=>new i("ABORTED","La operación se canceló."),_=class{constructor(t=de){this.max=t}max;active=0;waiting=[];pump(){for(;this.active<this.max&&this.waiting.length>0;)this.waiting.shift().start()}acquire(t){return t?.aborted?Promise.reject(N()):new Promise((n,u)=>{if(this.active<this.max)return this.active++,n();let a={start:()=>{t?.removeEventListener("abort",a.cancel),this.active++,n()},cancel:()=>{let d=this.waiting.indexOf(a);d>=0&&this.waiting.splice(d,1),u(N())}};t?.addEventListener("abort",a.cancel,{once:!0}),this.waiting.push(a)})}async run(t,n){await this.acquire(n.signal);let u=!1,a=()=>{u||(u=!0,this.active--,this.pump())},d;try{d=t()}catch(o){throw a(),o}return d.then(a,a),new Promise((o,p)=>{let{signal:l}=n,y=()=>{g(),p(N())},h=setTimeout(()=>{g(),a(),p(new i("TIMEOUT",`El anfitrión no respondió en ${Math.round(n.timeoutMs/1e3)} s.`))},n.timeoutMs),g=()=>{clearTimeout(h),l?.removeEventListener("abort",y)};l?.addEventListener("abort",y,{once:!0}),d.then(E=>{g(),o(E)},E=>{g(),p(E instanceof Error?E:new Error(String(E)))})})}};var pe=/^--[a-z0-9-]+$/,fe=/^[^;{}<>\\]{1,200}$/,me=/url\s*\(|image-set\s*\(|@import/i,ge=200,he=/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8}){0,3}$/,D=e=>e==="light"||e==="dark"?e:void 0,b=e=>typeof e=="string"&&he.test(e)?e:void 0;function I(e){let t={};if(!f(e))return t;let n=0;for(let[u,a]of Object.entries(e)){if(n>=ge)break;typeof a=="string"&&pe.test(u)&&fe.test(a)&&!me.test(a)&&(t[u]=a,n++)}return t}var B=1,We=128*1024,ye=64;var F=e=>({ebavel:B,...e}),P=e=>typeof e=="string"&&e.length>0&&e.length<=ye;function z(e){if(!f(e)||e.ebavel!==B)return null;switch(e.type){case"ready":{let t=D(e.theme),n=b(e.lang);return!P(e.id)||!t||typeof e.canExport!="boolean"?null:{type:"ready",id:e.id,theme:t,lang:n??"es",vars:I(e.vars),canExport:e.canExport}}case"result":return P(e.id)&&"value"in e?{type:"result",id:e.id,value:e.value}:null;case"error":return!P(e.id)||!q(e.code)||typeof e.message!="string"?null:{type:"error",id:e.id,code:e.code,message:w(e.message),..."details"in e?{details:e.details}:{}};case"theme":{let t=D(e.theme);return t?{type:"theme",theme:t,lang:b(e.lang)??"es",vars:I(e.vars)}:null}default:return null}}var Ee=[0,200,500,1e3,2e3,3500],ve=4e3;function Re(){let e=Math.random().toString(36).slice(2,8),t=0;return()=>`c${++t}-${e}`}function we(e,t){let n=e.document.documentElement,u=-1,a=!1,d=()=>{a=!1;let l=Math.min(Math.ceil(n.getBoundingClientRect().height),ve);l>0&&l!==u&&(u=l,t({type:"size",height:l}))},o=()=>{a||(a=!0,typeof e.requestAnimationFrame=="function"?e.requestAnimationFrame(d):e.setTimeout(d,16))},p=e.ResizeObserver;if(typeof p=="function"){let l=new p(o);l.observe(n),e.document.body?l.observe(e.document.body):e.addEventListener("DOMContentLoaded",()=>l.observe(e.document.body),{once:!0})}e.addEventListener("load",o,{once:!0}),o()}function X(e,t){let n=e.parent,u=Re(),a=new Map,d=new Set,o=[],p=!1,l=!1,y,h,g=new Promise(r=>y=r),E=new Promise(r=>h=r),v=r=>{n.postMessage(F(r),"*")},T=r=>new Promise((s,c)=>{let m=r(u());a.set(m.id,{resolve:s,reject:c});try{v(m)}catch(R){a.delete(m.id),c(V(R))}}),k=r=>({host:"mcp-apps",canExport:r,async query(s){let c=await T(m=>({type:"query",id:m,args:s}));if(c.type!=="result")throw new i("TOOL_ERROR","Respuesta inesperada del runner.");return c.value},async export(){let s=await T(R=>({type:"export",id:R})),c=s.type==="result"&&typeof s.value=="object"&&s.value!==null?s.value:void 0,m=c?A(c.importUrl):null;if(!c||m===null)throw new i("TOOL_ERROR","Respuesta de exportación inválida.");return{importUrl:m,...typeof c.expiresAt=="string"?{expiresAt:c.expiresAt}:{},opened:c.opened===!0}},async openLink(s){let c=A(s);if(c===null)throw new i("INVALID_ARGS","Solo se pueden abrir enlaces https.");let m=await T(S=>({type:"openLink",id:S,url:c}));return{opened:(m.type==="result"&&typeof m.value=="object"&&m.value!==null?m.value:void 0)?.opened===!0,url:c}}}),O=()=>{for(let r of o)e.clearTimeout(r);o.length=0,p||e.removeEventListener("message",x),h()};function x(r){if(r.source!==e.parent)return;let s=z(r.data);if(!s)return;if(s.type==="theme"){t.onTheme({theme:s.theme,lang:s.lang,vars:s.vars});return}if(s.type==="ready"){if(!d.has(s.id)||p)return;p=!0,we(e,v);let m={conductor:k(s.canExport),theme:s.theme,lang:s.lang,vars:s.vars};for(let R of o)e.clearTimeout(R);o.length=0,l?t.onLate(m):(l=!0,y(m)),h();return}let c=a.get(s.id);c&&(a.delete(s.id),s.type==="error"?c.reject(new i(s.code,s.message,s.details)):c.resolve(s))}e.addEventListener("message",x);for(let r of Ee){if(r>=t.retryMs)break;let s=()=>{if(p)return;let c=u();d.add(c);try{v({type:"hello",id:c})}catch{}};r===0?s():o.push(e.setTimeout(s,r))}return o.push(e.setTimeout(()=>{l||(l=!0,y(null))},Math.min(t.helloTimeoutMs,t.retryMs))),o.push(e.setTimeout(O,t.retryMs)),{first:g,settled:E}}var W="ebavel-source",Q=512*1024,Te=120;var Ae=/["'`]\s*cred\s*["'`]|(?:^|[^\w$.-])cred\s*:/,Oe=e=>e.includes("__FRAME_")||Ae.test(e),xe='Para exportar desde Claude, envuelve tu dashboard en <template id="ebavel-source"> (ver SDK.md). El SDK nunca envía el documento completo: trae credenciales de Claude.',be=e=>new TextEncoder().encode(e).length,M=e=>w(e.replace(/\s+/g," ").trim()||"Dashboard",Te);function K(e){let t=e.document,n,u=()=>{let d=Array.from(t.querySelectorAll(`[id="${W}"]`));if(d.length>1)return{kind:"duplicate"};let o=d[0];return o!==void 0&&o.tagName==="TEMPLATE"&&(o.parentElement===t.head||o.parentElement===t.body)?{kind:"html",html:o.innerHTML}:{kind:"none"}},a=()=>M(t.title||t.querySelector("h1")?.textContent||"");return{capture(){n??=u()},get(d){if(n??=u(),n.kind==="duplicate")throw new i("INVALID_ARGS",`Hay más de un elemento con id="${W}": solo puede haber un <template>.`);if(n.kind==="none")throw new i("INVALID_ARGS",xe);let o=d===void 0?a():M(d),p=t.createElement("title");p.textContent=o;let l=`<!doctype html>
3
+ <html lang="${b(t.documentElement.lang)??"es"}"><head><meta charset="utf-8">${p.outerHTML}</head><body>${n.html}</body></html>`;if(Oe(l))throw new i("INVALID_ARGS","El HTML a exportar contiene `__FRAME_` o una llave `cred` (la credencial del frame de Claude, o texto que se le parece); no se envió ni se modificó nada.");if(be(l)>Q)throw new i("INVALID_ARGS",`El HTML supera el máximo de ${Q/1024} KB.`);return{html:l,title:o}}}}function J(e){let t=new Set;return{apply(n,u){let a=e.documentElement;a.setAttribute("data-theme",n),a.style.colorScheme=n;let d=new Set;for(let[o,p]of Object.entries(u))a.style.setProperty(o,p),d.add(o);for(let o of t)d.has(o)||a.style.removeProperty(o);t=d}}}function L(e){try{return typeof e.matchMedia=="function"&&e.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}catch{return"light"}}var Y="0.1.0";var te=Symbol.for("@bitam/ebavel-apps"),ke=6e4,_e=1500,Me=5e3,Z=64*1024,ee=e=>{let t=e.claude;return f(t)&&typeof t.use=="function"?t:void 0},Le="No hay anfitrión: abre el dashboard como artifact de Claude (con el conector eBavel) o dentro del runner MCP Apps.";function Se(e,t){try{if(e?.navigator?.userActivation?.isActive!==!0)return!1;let n=e.open(t,"_blank");if(!n)return!1;try{n.opener=null}catch{}return!0}catch{return!1}}function Ce(e){if(!f(e))throw new i("INVALID_ARGS","ebavel.query espera un objeto: { queries: { alias: { entity, … } } }.");let t;try{t=JSON.stringify(e)}catch{throw new i("INVALID_ARGS","Los argumentos de ebavel.query no se pueden serializar a JSON.")}if(t.length>Z)throw new i("INVALID_ARGS",`Los argumentos de ebavel.query superan ${Z/1024} KB.`)}function Ne(e,t={}){let n={timeoutMs:ke},u=new _,a=new Set,d=e?L(e):"light",o=e?J(e.document):void 0,p=e?K(e):void 0,l=null,y,h=!1;if(e&&p){let r=e.document;r.readyState==="loading"?r.addEventListener("DOMContentLoaded",()=>p.capture(),{once:!0}):p.capture()}let g=(r,s)=>{if(o?.apply(r,s),r!==d){d=r;for(let c of[...a])try{c(r)}catch{}}},E=()=>(e?.document.documentElement.lang||e?.navigator?.language||"es").slice(0,35);function v(r){if(!(!e||h)){h=!0,g(L(e),{});try{e.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>g(L(e),{}))}catch{}l=$({claude:r,getServer:()=>n.server,open:s=>Se(e,s)})}}async function T(){if(!e)return{host:"none",theme:d,lang:"es",canExport:!1};let r=ee(e);if(r)return v(r),{host:"claude",theme:d,lang:E(),canExport:!0};if(e.parent&&e.parent!==e){y=X(e,{helloTimeoutMs:t.helloTimeoutMs??_e,retryMs:t.helloRetryMs??Me,onTheme:c=>g(c.theme,c.vars),onLate:c=>{l=c.conductor,g(c.theme,c.vars)}});let s=await y.first;if(s)return l=s.conductor,g(s.theme,s.vars),{host:"mcp-apps",theme:d,lang:s.lang,canExport:s.conductor.canExport}}return{host:"none",theme:d,lang:E(),canExport:!1}}let k=T(),O=async()=>{if(await k,l)return l;let r=e?ee(e):void 0;if(r&&v(r),!l&&y&&await y.settled,!l)throw new i("NOT_CONNECTED",Le);return l},x={version:Y,configure(r){if(!f(r))throw new i("INVALID_ARGS","ebavel.configure espera un objeto.");if(r.server!==void 0){if(typeof r.server!="string"||r.server.trim()==="")throw new i("INVALID_ARGS","`server` debe ser el nombre del conector (texto no vacío).");n.server=r.server}if(r.timeoutMs!==void 0){if(typeof r.timeoutMs!="number"||!Number.isFinite(r.timeoutMs)||r.timeoutMs<100||r.timeoutMs>6e5)throw new i("INVALID_ARGS","`timeoutMs` debe estar entre 100 y 600000.");n.timeoutMs=r.timeoutMs}},ready:k,async query(r,s){Ce(r);let c=s?.signal;if(c?.aborted)throw new i("ABORTED","La operación se canceló.");let m=await O();return await u.run(()=>m.query(r),{...c?{signal:c}:{},timeoutMs:n.timeoutMs})},async export(r){let s=r?.title;if(s!==void 0&&typeof s!="string")throw new i("INVALID_ARGS","`title` debe ser un texto.");let c=typeof s=="string"?M(s):void 0,m=r?.signal;if(m?.aborted)throw new i("ABORTED","La operación se canceló.");let R=await O();if(!R.canExport)throw new i("UNSUPPORTED","Este dashboard ya está guardado: no se puede exportar desde aquí.");let S={source:re=>{if(!p)throw new i("INVALID_ARGS","No hay documento del que exportar.");return p.get(re)},title:c};return u.run(()=>R.export(S),{...m?{signal:m}:{},timeoutMs:n.timeoutMs})},get theme(){return d},onThemeChange(r){if(typeof r!="function")throw new i("INVALID_ARGS","onThemeChange espera una función.");return a.add(r),()=>{a.delete(r)}},async openLink(r){return(await O()).openLink(r)}};return Object.defineProperty(x,te,{value:!0}),x}function ne(e=typeof window>"u"?void 0:window,t){let n=e?.ebavel;if(f(n)&&n[te]===!0)return n;let u=Ne(e,t);return e&&Object.defineProperty(e,"ebavel",{value:u,configurable:!0,writable:!0,enumerable:!1}),u}ne();})();
@@ -0,0 +1,160 @@
1
+ type Theme = 'light' | 'dark';
2
+
3
+ /** Dónde corre el dashboard. `none`: ni artifact de Claude ni runner MCP Apps (p. ej. un archivo abierto suelto). */
4
+ type HostKind = 'claude' | 'mcp-apps' | 'none';
5
+ interface ReadyInfo {
6
+ host: HostKind;
7
+ theme: Theme;
8
+ /** Idioma del anfitrión (`es`, `en-US`…); `es` si no lo informa. */
9
+ lang: string;
10
+ /** `false` si `ebavel.export()` responderá `UNSUPPORTED` (p. ej. en la plataforma, donde el dashboard ya está guardado) o no hay anfitrión. */
11
+ canExport: boolean;
12
+ }
13
+ interface ConfigureOptions {
14
+ /** Nombre del conector eBavel en Claude (el que ve el usuario en claude.ai). Si no se fija, se descubre con `listTools()`. */
15
+ server?: string;
16
+ /** Tiempo máximo por llamada, en ms (por omisión 60 000). */
17
+ timeoutMs?: number;
18
+ }
19
+ interface QueryOptions {
20
+ signal?: AbortSignal;
21
+ }
22
+ interface ExportOptions {
23
+ /** Título del dashboard (texto; se acota a 120 caracteres). Por omisión `<title>`, el primer `<h1>` o «Dashboard». Solo Claude: en MCP Apps lo fija el borrador. */
24
+ title?: string;
25
+ signal?: AbortSignal;
26
+ }
27
+ interface OpenLinkResult {
28
+ /** `true` si el anfitrión abrió el enlace; si no (sin gesto del usuario, o el usuario canceló), muéstralo para copiarlo. */
29
+ opened: boolean;
30
+ url: string;
31
+ }
32
+ interface ExportResult {
33
+ /** Enlace de importación de un solo uso (caduca en minutos). */
34
+ importUrl: string;
35
+ expiresAt?: string;
36
+ /**
37
+ * `true` si el enlace se abrió. En Claude solo se abre con un gesto reciente del usuario (`navigator.userActivation`); en MCP
38
+ * Apps, si el usuario pulsó «Abrir enlace» en la barra del runner. Si es `false`, **muestra `importUrl`** para copiarlo.
39
+ */
40
+ opened: boolean;
41
+ }
42
+ /** Argumentos de la tool `query` de `mcp3` (`{ queries: { alias: { entity, … } }, dryRun? }`). */
43
+ type QueryArgs = Record<string, unknown>;
44
+ interface QueryColumn {
45
+ key: string;
46
+ name: string;
47
+ type: string;
48
+ }
49
+ /** Resultado de una query. Un valor de catálogo es `{ key, label }`; un vacío es `null` (cero ≠ null). */
50
+ interface QueryOk {
51
+ ok: true;
52
+ kind: 'list' | 'aggregate';
53
+ columns: QueryColumn[];
54
+ rows: Array<Record<string, unknown>>;
55
+ total: number;
56
+ totalKind: 'rows' | 'groups';
57
+ pagination: {
58
+ pageSize: number;
59
+ currentPage: number;
60
+ totalPages: number;
61
+ };
62
+ /** Hay más filas de las devueltas: el resultado está incompleto. */
63
+ hasMore: boolean;
64
+ outputTruncated: boolean;
65
+ warnings?: Array<{
66
+ code: string;
67
+ message: string;
68
+ }>;
69
+ hint?: string;
70
+ }
71
+ interface QueryFail {
72
+ ok: false;
73
+ skipped?: true;
74
+ error: {
75
+ code: string;
76
+ message: string;
77
+ backendCode?: number;
78
+ hint?: string;
79
+ };
80
+ }
81
+ /** El `payload` de la tool `query`, tal cual lo ve el modelo. Revisa `success` y, por alias, `results[alias].ok`. */
82
+ interface QueryResponse {
83
+ success: boolean;
84
+ allSucceeded?: boolean;
85
+ results?: Record<string, QueryOk | QueryFail>;
86
+ errors?: Array<{
87
+ code: string;
88
+ message: string;
89
+ }>;
90
+ [key: string]: unknown;
91
+ }
92
+ interface EbavelSdk {
93
+ /** Versión del SDK. */
94
+ readonly version: string;
95
+ /** Fija el conector y los tiempos. Se puede llamar antes de la primera consulta. */
96
+ configure(options: ConfigureOptions): void;
97
+ /** Se cumple cuando se detectó el anfitrión (nunca se rechaza: sin anfitrión, `host: 'none'`). */
98
+ readonly ready: Promise<ReadyInfo>;
99
+ /** Llama a la tool `query` de `mcp3`. Devuelve su `payload`. Rechaza con `EbavelAppError`. */
100
+ query(args: QueryArgs, options?: QueryOptions): Promise<QueryResponse>;
101
+ /** Exporta el dashboard a la plataforma y abre el enlace de importación. */
102
+ export(options?: ExportOptions): Promise<ExportResult>;
103
+ /** Tema actual del anfitrión (`prefers-color-scheme` mientras no haya otra información). */
104
+ readonly theme: Theme;
105
+ /** Avisa cuando cambia el tema. Devuelve la función que cancela la suscripción. */
106
+ onThemeChange(listener: (theme: Theme) => void): () => void;
107
+ /**
108
+ * Pide abrir un enlace `https:`. En Claude solo se abre con un gesto reciente del usuario; en MCP Apps el runner pide
109
+ * confirmación (una solicitud a la vez, ≤ 5 por minuto: si no, `DECLINED`) y se resuelve cuando el usuario decide.
110
+ */
111
+ openLink(url: string): Promise<OpenLinkResult>;
112
+ }
113
+ declare global {
114
+ interface Window {
115
+ /** Lo define el SDK al cargarse (una sola vez, aunque se cargue dos veces). */
116
+ ebavel?: EbavelSdk;
117
+ }
118
+ }
119
+
120
+ /** Marca de las instancias del SDK: una segunda carga la reconoce y no reinicializa nada. */
121
+ declare const SDK_MARK: unique symbol;
122
+ interface SdkOptions {
123
+ helloTimeoutMs?: number;
124
+ helloRetryMs?: number;
125
+ }
126
+ /** Crea una instancia del SDK ligada a `win` (sin `win`: sin anfitrión, útil en Node o en pruebas). */
127
+ declare function createSdk(win?: Window, options?: SdkOptions): EbavelSdk;
128
+ /** La instancia del documento: si ya hay una (por cargar el SDK dos veces) se reutiliza; si no, se crea y se instala en `window.ebavel`. */
129
+ declare function getEbavel(win?: Window | undefined, options?: SdkOptions): EbavelSdk;
130
+
131
+ declare const ERROR_CODES: readonly ["NOT_CONNECTED", "DECLINED", "TOOL_ERROR", "UNSUPPORTED", "TIMEOUT", "ABORTED", "INVALID_ARGS"];
132
+ /**
133
+ * - `NOT_CONNECTED`: no hay anfitrión, o el conector no está conectado / no se puede elegir / necesita autorizar de nuevo.
134
+ * - `DECLINED`: el anfitrión o el usuario negó el permiso.
135
+ * - `TOOL_ERROR`: la tool o el transporte fallaron (`details` trae el cuerpo del error si lo hay).
136
+ * - `UNSUPPORTED`: este anfitrión o este dashboard no puede hacerlo (p. ej. exportar un dashboard ya guardado).
137
+ * - `TIMEOUT`: no hubo respuesta a tiempo (`configure({ timeoutMs })`).
138
+ * - `ABORTED`: la `AbortSignal` del llamador se activó.
139
+ * - `INVALID_ARGS`: argumentos inválidos (o un HTML que no se puede exportar de forma segura).
140
+ */
141
+ type ErrorCode = (typeof ERROR_CODES)[number];
142
+ declare const isErrorCode: (v: unknown) => v is ErrorCode;
143
+ declare class EbavelAppError extends Error {
144
+ readonly code: ErrorCode;
145
+ /** Cuerpo del error de la tool (p. ej. `{ success: false, errors: [...] }`), si lo hubo. */
146
+ readonly details?: unknown;
147
+ constructor(code: ErrorCode, message: string, details?: unknown);
148
+ }
149
+
150
+ declare const PROTOCOL_VERSION = 1;
151
+
152
+ declare const VERSION = "0.1.0";
153
+
154
+ declare const ebavel: EbavelSdk;
155
+
156
+ /** CDNs de los que un dashboard puede cargar scripts, estilos, fuentes e imágenes (con versión fija). */
157
+ declare const CDN_ORIGINS: readonly string[];
158
+
159
+ export { CDN_ORIGINS, ERROR_CODES, EbavelAppError, PROTOCOL_VERSION, SDK_MARK, VERSION, createSdk, ebavel, getEbavel, isErrorCode };
160
+ export type { ConfigureOptions, EbavelSdk, ErrorCode, ExportOptions, ExportResult, HostKind, OpenLinkResult, QueryArgs, QueryColumn, QueryFail, QueryOk, QueryOptions, QueryResponse, ReadyInfo, Theme };