@blotout/qinto-cli 0.1.18 → 0.1.20

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.
@@ -1,3 +1,3 @@
1
1
  # Worker artifact
2
2
 
3
- Generated by the "Release worker" action, or locally by `pnpm --filter @qinto/worker artifact` — do not edit.
3
+ Generated only by the "Release worker" action — never build it locally, and do not edit.
@@ -18959,7 +18959,7 @@ var uniqueOrIssue = (ctx, values, path, label) => {
18959
18959
  seen.add(value);
18960
18960
  }
18961
18961
  };
18962
- var PLATFORM_ZONES = ["blotout.io", "qinto.tech"];
18962
+ var PLATFORM_ZONES = ["platform.blotout.io", "qinto-api.blotout.io", "appui.blotout.io", "mcp.blotout.io", "qinto.tech"];
18963
18963
  var isPlatformHost = (host) => {
18964
18964
  return PLATFORM_ZONES.some((zone) => host === zone || host.endsWith(`.${zone}`));
18965
18965
  };
@@ -19528,10 +19528,13 @@ var variableSchema = external_exports.object({
19528
19528
  label: external_exports.string().min(1).max(100),
19529
19529
  required: external_exports.boolean().optional(),
19530
19530
  browser: external_exports.boolean().optional(),
19531
+ owner: external_exports.enum(["site", "developer"]).optional(),
19531
19532
  helpText: external_exports.string().min(1).max(MAX_VARIABLE_HELP_TEXT).optional(),
19532
19533
  helpUrl: httpsUrl("helpUrl").optional()
19533
19534
  }).strict().refine((variable) => !(variable.kind === "secret" && variable.browser === true), {
19534
19535
  message: "A secret variable can never be browser-exposed"
19536
+ }).refine((variable) => !(variable.required === true && variable.owner === "developer"), {
19537
+ message: "A developer-owned variable cannot be required"
19535
19538
  });
19536
19539
  var appVariableSchema = external_exports.object({
19537
19540
  name: external_exports.string().min(1).max(64).regex(VARIABLE_NAME),
@@ -19994,6 +19997,7 @@ var MAX_PUSHES_PER_INSTALL = 10;
19994
19997
  var MAX_CAPTURE_WRITES_PER_INSTALL = 10;
19995
19998
  var CONTEXT_LEVELS = ["private", "shared"];
19996
19999
  var PUSH_BROADCASTS = ["install", "app", "site"];
20000
+ var ACTION_SOURCES = ["website", "api", "app", "scheduled", "webhook", "tool", "workflow"];
19997
20001
  var MAX_PRIVATE_CONTEXT_ENTRY_BYTES = 32 * 1024;
19998
20002
  var MAX_SHARED_CONTEXT_ENTRY_BYTES = 4 * 1024;
19999
20003
  var MAX_PUSH_BYTES = 16 * 1024;
@@ -20069,6 +20073,9 @@ var envelopeRequestSchema = external_exports.object({
20069
20073
  userId: external_exports.string().min(1).max(64),
20070
20074
  payload: external_exports.record(external_exports.string(), external_exports.unknown()),
20071
20075
  receivedAt: external_exports.number().int(),
20076
+ // Optional on the wire: an app worker redeploys before its site worker, so this reader must
20077
+ // accept an envelope a site worker predating the field produces.
20078
+ source: external_exports.enum(ACTION_SOURCES).optional(),
20072
20079
  context: actionContextSchema.optional(),
20073
20080
  browser: external_exports.record(external_exports.string().min(1).max(64), external_exports.unknown()).optional(),
20074
20081
  origin: external_exports.object({ app: external_exports.string().min(1).max(30), install: external_exports.number().int().min(1), depth: external_exports.number().int().min(0) }).strict().optional()
@@ -20951,6 +20958,18 @@ var brokerPush = async (env, input2) => {
20951
20958
  data: input2.data
20952
20959
  });
20953
20960
  };
20961
+ var brokerTrack = async (env, input2) => {
20962
+ await askBroker(env, `apps.track ${input2.event.payload["event"]}`, {
20963
+ v: ENVELOPE_VERSION,
20964
+ kind: "track",
20965
+ slug: callerSlug(env),
20966
+ key: env.QINTO_INTERNAL_KEY,
20967
+ source: input2.source,
20968
+ userId: input2.userId,
20969
+ installId: input2.installId,
20970
+ event: input2.event
20971
+ });
20972
+ };
20954
20973
  var brokerContextRead = async (env, userId) => {
20955
20974
  const answer = await askBroker(env, "apps.context read", {
20956
20975
  v: ENVELOPE_VERSION,
@@ -21065,6 +21084,18 @@ var buildAppsClient = (env, envelope) => {
21065
21084
  };
21066
21085
  };
21067
21086
 
21087
+ // src/emitted-event.ts
21088
+ var checkEmittedEvent = (event) => {
21089
+ if (event.type !== "track") {
21090
+ throw new Error("only track events may be emitted");
21091
+ }
21092
+ const occurredAt = event.occurredAt ?? Date.now();
21093
+ if (!Number.isSafeInteger(occurredAt) || occurredAt < 0) {
21094
+ throw new Error("an emitted event occurredAt must be non-negative Unix milliseconds");
21095
+ }
21096
+ return { ...event, occurredAt };
21097
+ };
21098
+
21068
21099
  // src/oauth.ts
21069
21100
  var EXPIRY_FLOOR_MS = 1e3;
21070
21101
  var read = (env, id, ord, now) => {
@@ -21550,6 +21581,9 @@ var buildWorkflowContext = (env, stamp) => {
21550
21581
  broadcast: message.broadcast
21551
21582
  });
21552
21583
  },
21584
+ track: async (userId, event) => {
21585
+ await brokerTrack(env, { source: "workflow", userId, installId: install.id, event: checkEmittedEvent(event) });
21586
+ },
21553
21587
  apps
21554
21588
  };
21555
21589
  };
@@ -21623,15 +21657,9 @@ var buildContext = ({
21623
21657
  // and the extra entry keeps it visibly over while bounding memory.
21624
21658
  emit: (event) => {
21625
21659
  requireSubject(subject, "emit");
21626
- if (event.type !== "track") {
21627
- throw new Error("only track events may be emitted");
21628
- }
21629
- const occurredAt = event.occurredAt ?? Date.now();
21630
- if (!Number.isSafeInteger(occurredAt) || occurredAt < 0) {
21631
- throw new Error("an emitted event occurredAt must be non-negative Unix milliseconds");
21632
- }
21660
+ const checked = checkEmittedEvent(event);
21633
21661
  if (emits.length <= MAX_EMITS_PER_INSTALL) {
21634
- emits.push({ ...event, occurredAt });
21662
+ emits.push(checked);
21635
21663
  }
21636
21664
  },
21637
21665
  push: (message) => {
@@ -21802,6 +21830,9 @@ var dispatchSchedule = async (envelope, bundle, env) => {
21802
21830
  broadcast: message.broadcast
21803
21831
  });
21804
21832
  },
21833
+ track: async (userId, event2) => {
21834
+ await brokerTrack(env, { source: "scheduled", userId, installId: install.id, event: checkEmittedEvent(event2) });
21835
+ },
21805
21836
  context: (userId) => {
21806
21837
  return asyncContext(env, () => {
21807
21838
  return userId;
@@ -21919,6 +21950,9 @@ var buildContext2 = (envelope, env) => {
21919
21950
  identity: buildIdentity(env),
21920
21951
  apps: buildAppsClient(env, { depth: 0 }),
21921
21952
  query: buildQuery({ env, ticket: envelope.tool.queryTicket, installId: install.id }),
21953
+ track: async (userId, event) => {
21954
+ await brokerTrack(env, { source: "tool", userId, installId: install.id, event: checkEmittedEvent(event) });
21955
+ },
21922
21956
  fail: (message) => {
21923
21957
  return { kind: "tool_error", message: truncate(message) };
21924
21958
  }
@@ -22181,6 +22215,9 @@ var buildContext3 = ({
22181
22215
  broadcast: message.broadcast
22182
22216
  });
22183
22217
  },
22218
+ track: async (userId, event) => {
22219
+ await brokerTrack(env, { source: "webhook", userId, installId: install.id, event: checkEmittedEvent(event) });
22220
+ },
22184
22221
  context: (userId) => {
22185
22222
  return asyncContext(env, () => {
22186
22223
  return userId;
@@ -0,0 +1 @@
1
+ (function(){var e=()=>{},t=t=>{t.config?.autoCapture?.pageLoad===!0&&t.q.push([`track`,[`PageView`],e,e])},n=class extends Error{constructor(e){super(e),this.name=`QintoError`}},r=class extends n{constructor(e){super(e),this.name=`QintoTimeoutError`}},i=(e=Date.now())=>`evt_${globalThis.crypto.randomUUID()}_${e}`,a=(e,t)=>{if(e!==void 0)return e;let n=t?.orderId;return typeof n==`string`&&n!==``?n:i()},o=[`necessary`,`functional`,`analytics`,`marketing`,`salesOfData`],s=[`then`,`ready`,`config`],c=()=>{},l=(e,t)=>(...n)=>{let r=new Promise((r,i)=>{e.q.push([t,n,r,i])});return r.catch(c),r},u=e=>{let t=e;return t._q=1,t.q=[],new Proxy(t,{get:(e,n)=>typeof n!=`string`||s.includes(n)||n in t?Reflect.get(t,n):(t[n]=l(t,n),t[n])})},d=()=>{let e=document.currentScript?.dataset.qintoGlobal;return typeof e==`string`&&e!==``?e:`qinto`},f=e=>typeof e==`object`&&!!e&&e._q===1,p=(e=`qinto`)=>{let t=globalThis;if(e in t){let n=t[e];if(!f(n))return null;let r=Array.isArray(n.q)?n:u(n);return r!==n&&Object.defineProperty(t,e,{value:r,writable:!0,enumerable:!0,configurable:!0}),r}let n=u({});return Object.defineProperty(t,e,{value:n,writable:!0,enumerable:!0,configurable:!0}),n},m=(e,t)=>async(r,i)=>{let a=Reflect.get(e,r);if(typeof a!=`function`)throw new n(`[Qinto] ${t}.${r} is not a method of this tag.`);return a.apply(e,Array.from(i))},h=e=>()=>Promise.reject(e),g=(e,t)=>{let[n,r,i,a]=t;e(n,r).then(i,a)},ee=e=>{let t=[];return t.push=(...t)=>{for(let n of t)g(e,n);return 0},t},_=(e,t,n=`window.qinto`)=>{let r=e.ready;if(r!==void 0)return r;let i=e,a=e=>{let t=i.q;i.q=ee(e);for(let n of t)g(e,n)},o=t().then(e=>{a(m(e,n))},e=>{throw a(h(e)),e});return o.catch(c),i.ready=o,o},v=(e,t,n)=>n===void 0||!o.some(e=>e===n)?!1:n===`necessary`||e!==null&&e.categories[n]===!0&&e.apps[t]!==!1,y=()=>{},te=100,ne=1024,b=32768,x=(e,t,n)=>e.target.config?.consentConfiguration?.enabled!==!0||v(n,t,e.target.config?.apps?.[t]?.category),S=e=>Object.values(e.config?.apps??{}).some(e=>e.category!==`necessary`||e.capture!==void 0),C=(e,t)=>{let{app:n,install:r,topic:i,to:a}=e;return typeof n!=`string`||typeof i!=`string`||r!==void 0&&typeof r!=`string`||t&&!(Array.isArray(a)&&a.every(e=>typeof e==`string`))},re=(e,t,n,r)=>r===null?e.slug===t&&(n===void 0||n===e.installId):r.has(e.slug),w=(e,t)=>{console.error(`[Qinto] A handler of app "${e.slug}" (install "${e.installId}") failed.`,t)},T=(e,t,n)=>{for(let r of t)try{let t=r(n);t instanceof Promise&&t.catch(t=>{w(e,t)})}catch(t){w(e,t)}},ie=(e,t,n,r)=>{e.activated.add(r.id);let i={slug:t,installId:r.id,eventHandlers:[],consentHandlers:[],pushHandlers:[],sharedContextHandlers:[]};e.instances.push(i);let a={variables:r.variables,install:{id:r.id,label:r.label},getSession:async()=>{let n=await e.client.getSession();return e.target.config?.apps?.[t]?.geo===!0?n:{...n,ip:null,geo:null}},track:(t,n)=>{e.client.track(t,n).catch(y)},onEvent:e=>{i.eventHandlers.push(e)},onConsent:e=>{i.consentHandlers.push(e)},onPush:e=>{i.pushHandlers.push(e)},fetch:async(n,i)=>{let a=new URL(`${e.client.destination}/app/${t}${n}`);if(!n.startsWith(`/`)||!a.pathname.startsWith(`/app/${t}/`))throw TypeError(`[Qinto] App "${t}" fetched "${n}"; the path must be absolute and stay inside the app.`);let o=new Headers(i?.headers);return o.set(`x-qinto-install`,r.id),fetch(a,{...i,headers:o,credentials:`include`})},onSharedContext:n=>{if(e.target.config?.apps?.[t]?.sharedContext!==!0){console.error(`[Qinto] App "${t}" reads shared context without declaring the sharedContext capability.`);return}i.sharedContextHandlers.push(n),e.sharedContext!==null&&ae(e,t)&&T(i,[n],e.sharedContext)}};try{n(a)}catch(e){console.error(`[Qinto] App "${t}" failed to activate install "${r.id}".`,e)}},E=(e,t,n)=>{if(!x(e,t,e.consent))return;let r=((`_appInstalls`in e.target?e.target._appInstalls:void 0)?.[t]??[]).filter(t=>!e.activated.has(t.id));if(r.length===0)return;let i=n.factory;if(i===void 0){try{i=n.loader()}catch(e){i=null,console.error(`[Qinto] App "${t}" failed to load.`,e)}e.apps.set(t,{...n,factory:i})}if(i!==null)for(let n of r)ie(e,t,i,n)},ae=(e,t)=>x(e,t,e.consent)?e.sharedContextConsent===void 0||x(e,t,e.sharedContextConsent):!1,D=e=>{let{categories:t,apps:n}=e;if(typeof t==`object`&&t&&typeof n==`object`&&n)return{categories:t,apps:n}},O=(e,t)=>{let n=e;n.consent=t,n.sharedContextConsent=void 0;for(let n of e.instances)T(n,n.consentHandlers,t);e.onConsentSettled?.(t);for(let[t,n]of e.apps)E(e,t,n)},k=async e=>{try{O(e,await e.client.getConsent())}catch(e){console.error(`[Qinto] The apps consent read failed.`,e)}},A=e=>{let t;try{t=JSON.stringify(e)}catch{return}if(!(t===void 0||new TextEncoder().encode(t).length>ne))return JSON.parse(t)},j=e=>{let t={};for(let n of e){if(n.settled!==n.total||n.values.length!==1)continue;let e=A(n.values[0]);if(e===void 0)continue;let r={...t[n.instance.slug]??{},[n.instance.installId]:e},i={...t,[n.instance.slug]:r};new TextEncoder().encode(JSON.stringify(i)).length<=b&&(t[n.instance.slug]=r)}return Object.keys(t).length===0?void 0:t},M=async(e,t)=>{e.onPageEvent?.(e.consent);let n=[],r=[];for(let i of e.instances){if(!x(e,i.slug,e.consent))continue;if(e.target.config?.apps?.[i.slug]?.eventResults?.includes(t.type)!==!0){T(i,i.eventHandlers,t);continue}let a={instance:i,total:i.eventHandlers.length,settled:0,values:[]};n.push(a);for(let e of i.eventHandlers){let n;try{n=e(t)}catch(e){a.settled+=1,w(i,e);continue}r.push(Promise.resolve(n).then(e=>{a.settled+=1,e!==void 0&&a.values.push(e)},e=>{a.settled+=1,w(i,e)}))}}if(r.length>0){let e=y,t=new Promise(t=>{e=t}),n=setTimeout(e,te);await Promise.race([Promise.all(r),t]),clearTimeout(n)}return j(n)},N=e=>{let{client:t}=e;return{track:async(n,r,i)=>{let o=a(i?.eventId,r),s=t.actionContext(),c=await M(e,{type:`track`,event:n,eventId:o,properties:r??{}});return await t.submitTrack({event:n,eventId:o,properties:r,context:s,appResults:c}),{eventId:o}},identify:async(n,r)=>{let i=t.actionContext(),a=await M(e,{type:`identify`,event:`identify`,properties:n??{}});return t.submitIdentify({traits:n,id:r,context:i,appResults:a})},alias:t.alias,consent:async n=>{if(await t.submitConsent(n,t.actionContext()),e.onConsentMutation?.(),S(e.target)){let t=e;t.consent=null,await k(e)}},getAnonId:t.getAnonId,getConsent:t.getConsent,getSession:t.getSession,close:t.close}},P=(e,t)=>{let n=new Map,r=null,i=null,a=e=>{let[t,i]=e;if(n.has(t))return;let a={loader:i};n.set(t,a),r?.(t,a)},o=e,s=`_apps`in o?o._apps:void 0,c=Array.isArray(s)?s:[];c.push=(...e)=>{for(let t of e)a(t);return 0},o._apps=c;for(let e of c.splice(0))a(e);return{sharedContext:e=>{let{context:t}=e;if(typeof t!=`object`||!t||Array.isArray(t)){console.error(`[Qinto] The server sent a malformed shared context.`,e);return}let n=i;if(n===null)return;n.sharedContext=t;let r=D(e),a=r??n.consent;n.sharedContextConsent=r;for(let e of n.instances)x(n,e.slug,a)&&T(e,e.sharedContextHandlers,n.sharedContext)},push:e=>{let{app:t,install:n,topic:r,scope:a,to:o}=e,s=a===`site`;if(C(e,s)){console.error(`[Qinto] The server sent a malformed app push.`,e);return}let c=i;if(c===null)return;let l=s?new Set(o):null,u={topic:r,data:e.data,from:t};for(let e of c.instances)re(e,t,n,l)&&x(c,e.slug,c.consent)&&T(e,e.pushHandlers,u)},connect:async a=>{let o={target:e,client:a,apps:n,instances:[],activated:new Set,consent:null,sharedContext:i?.sharedContext??null,sharedContextConsent:i?.sharedContextConsent,onConsentSettled:t?.onConsentSettled,onConsentMutation:t?.onConsentMutation,onPageEvent:t?.onPageEvent};i=o,r=(e,t)=>{E(o,e,t),o.consent===null&&o.target.config?.consentConfiguration?.enabled===!0&&o.target.config?.apps?.[e]?.category!==`necessary`&&k(o)};for(let[e,t]of n)E(o,e,t);return e.config?.consentConfiguration?.enabled===!0&&S(e)?await k(o):o.onConsentSettled?.(o.consent),N(o)}}},F=512,I=864e5,L=e=>Object.fromEntries(Object.entries(e).map(([e,t])=>[e,Object.fromEntries(Object.entries(t).map(([e,t])=>[e,t.value]))])),R=(e,t)=>`qinto:cap:${encodeURIComponent(e)}:${encodeURIComponent(t)}`,z=e=>{try{let t=window.sessionStorage.getItem(e);if(t===null)return null;let n=JSON.parse(t);return typeof n.at!=`number`||typeof n.anonId!=`string`||typeof n.apps!=`object`||n.apps===null||Date.now()-n.at>I?null:{at:n.at,anonId:n.anonId,apps:n.apps}}catch{return null}},B=(e,t)=>{try{window.sessionStorage.setItem(e,JSON.stringify(t))}catch{}},V=e=>{try{window.sessionStorage.removeItem(e)}catch{}},H=e=>{try{return decodeURIComponent(e)}catch{return e}},U=()=>{let e={};for(let t of document.cookie.split(`;`)){let n=t.indexOf(`=`);n>0&&(e[t.slice(0,n).trim()]=H(t.slice(n+1)))}return e},W=(e,t)=>{let n={};for(let r of e.queryParams){let e=t.params.get(r);e!==null&&e.length<=F&&(n[r]={value:e,source:`query`})}for(let r of e.cookies){let e=t.cookies[r];e!==void 0&&e.length<=F&&(n[r]={value:e,source:`cookie`})}return n},G=(e,t)=>{let n={params:new URLSearchParams(window.location.search),cookies:U()},r={};for(let[i,a]of Object.entries(e.config?.apps??{})){if(a.capture===void 0||!v(t,i,a.category))continue;let e=W(a.capture,n);Object.keys(e).length>0&&(r[i]=e)}return r},K=(e,t)=>{let n={};for(let[r,i]of Object.entries(e)){let e=t[r]??{},a={};for(let[t,n]of Object.entries(i))(e[t]?.value!==n.value||e[t]?.source!==n.source)&&(a[t]=n);Object.keys(a).length>0&&(n[r]=a)}return n},oe=(e,t,n,r)=>{let i=R(r,n),a=z(i),o=a?.apps??{},s=a?.anonId??null,c=a===null,l={},u=0,d=(e,t)=>{s!==null&&s!==e.anonId&&(o={}),s=e.anonId;let n=new Set(Object.entries(e.accepted).flatMap(([e,t])=>t.map(t=>`${e}\u0000${t}`)));for(let[e,r]of Object.entries(t))for(let[t,i]of Object.entries(r))n.has(`${e}\u0000${t}`)&&(o[e]={...o[e],[t]:i});B(i,{at:Date.now(),anonId:s,apps:o})},f=null,p=()=>{u+=1,o={},l={},V(i)},m=n=>{if(f=n,!c)return;let r=K(G(e,n),{...o,...l});if(Object.keys(r).length===0)return;let i=u;l={...l,...r},t(L(r)).then(e=>{i===u&&d(e,r)}).catch(()=>{}).finally(()=>{l={}})};return{settled:e=>{m(e)},needsAnon:()=>!c,observedAnon:e=>{(e===null||s!==null&&s!==e)&&p(),s=e,c=!0,m(f)},clear:p}},q=Symbol.for(`qinto.connectGate`),J=()=>{},se=e=>{let t=globalThis,n=(t[q]??Promise.resolve()).then(e);return t[q]=n.then(J,J),n},Y=class extends n{constructor(e){super(e),this.name=`QintoProtocolError`}},ce=e=>{if(typeof e!=`string`)return null;let t;try{t=JSON.parse(e)}catch{return null}if(typeof t!=`object`||!t)return null;let n=t;return n.t===`ack`||n.t===`error`||n.t===`consentState`||n.t===`session`||n.t===`captureAck`||n.t===`appPush`||n.t===`sharedContext`?t:null},le=e=>{if(e.t===`consentState`)return{categories:e.categories,apps:e.apps};if(e.t===`session`)return{anonId:e.anonId,globalId:e.globalId,ip:e.ip,geo:e.geo};if(e.t===`captureAck`)return{anonId:e.anonId,accepted:e.accepted}},ue=e=>{let t=new Map,n=e=>{let n=t.get(e);if(n!==void 0)return clearTimeout(n.timer),t.delete(e),n};return{submit:n=>new Promise((i,a)=>{let o={frame:n,sent:e.send(n),resolve:i,reject:a,timer:setTimeout(()=>{t.delete(n.ref),a(new r(`The Qinto request timed out.`))},e.requestTimeoutMs)};t.set(n.ref,o)}),flush:()=>{for(let n of t.values())n.sent||=e.send(n.frame)},deliver:e=>{let t=e.ref===void 0?void 0:n(e.ref);if(t!==void 0){if(e.t===`error`){t.reject(new Y(e.message));return}t.resolve(le(e))}},rejectAll:e=>{let n=[...t.values()];t.clear();for(let t of n)clearTimeout(t.timer),t.reject(e)}}},de=e=>new WebSocket(e),fe=500,pe=3e4,me=`session not initialized`,he=(e,t)=>{let n=Math.min(fe*2**e,pe);return Math.round(n/2+n/2*t)},ge=(e,t)=>e.t===`appPush`?(t.onAppPush?.(e),!0):e.t===`sharedContext`&&(t.onSharedContext?.(e),!0),X=e=>{let t=`${e.destination.replace(/\/+$/,``)}/open`,n=null,r=null,i=0,a=null,o=ue({send:e=>n===null||n.readyState!==1?!1:(n.send(JSON.stringify(e)),!0),requestTimeoutMs:e.requestTimeoutMs}),s=t=>{e.onError?.(t)},c=()=>{if(r!==null&&(clearTimeout(r),r=null),n!==null){let e=n;n=null,e.close()}o.rejectAll(new Y(`connection closed`))},l=()=>{let u=de(t);n=u,u.onopen=()=>{i=0;let e=a;a=null,o.flush(),e?.resolve()},u.onmessage=t=>{let n=ce(t.data);if(n===null){s(new Y(`invalid message`));return}ge(n,e)||(`anonId`in n&&typeof n.anonId==`string`&&e.onAnonId?.(n.anonId),o.deliver(n),n.t===`error`&&n.message===me&&u.close())},u.onerror=()=>{s(new Y(`connection closed`))},u.onclose=()=>{if(n!==u)return;n=null;let e=a;if(e!==null){a=null,c(),e.reject(new Y(`connection closed`));return}o.rejectAll(new Y(`connection closed`)),r=setTimeout(()=>{r=null,l()},he(i,Math.random())),i+=1}};return{connect:()=>new Promise((e,t)=>{a={resolve:e,reject:t},l()}),submit:e=>o.submit(e),close:()=>{c()}}},_e=1e4,ve=()=>{let e=0;return()=>(e+=1,`q${e}`)},ye=(e,t,n)=>{let r=null,i=()=>{let e=document.referrer;return{occurredAt:Date.now(),page:{url:window.location.href,...e===``?{}:{referrer:e}}}},o=async()=>{let n=await e.submit({t:`getSession`,ref:t()});return r=n.anonId,n};return{destination:n.replace(/\/+$/,``),actionContext:i,submitIdentify:async({traits:n,id:r,context:i,appResults:a})=>{await e.submit({t:`identify`,ref:t(),traits:n,id:r,context:i,appResults:a})},submitTrack:async({event:n,eventId:r,properties:i,context:a,appResults:o})=>{await e.submit({t:`track`,ref:t(),event:n,eventId:r,properties:i,context:a,appResults:o})},submitConsent:async(n,r)=>{await e.submit({t:`consent`,ref:t(),categories:n.categories,apps:n.apps,context:r})},track:async(n,r,o)=>{let s=a(o?.eventId,r);return await e.submit({t:`track`,ref:t(),event:n,eventId:s,properties:r,context:i()}),{eventId:s}},alias:async n=>{await e.submit({t:`alias`,ref:t(),alias:n})},getConsent:async()=>await e.submit({t:`getConsent`,ref:t()}),getSession:o,getAnonId:async()=>(r===null&&await o(),r),close:async()=>{e.close()}}},be=async e=>{let t=X({destination:e.destination,requestTimeoutMs:e.requestTimeoutMs??_e,onError:e.onError,onAppPush:e.onAppPush,onSharedContext:e.onSharedContext,onAnonId:e.onAnonId});await se(()=>t.connect());let n=ve();return{client:ye(t,n,e.destination),submitCapture:async e=>await t.submit({t:`capture`,ref:n(),apps:e})}},xe=(e,t)=>{let n=null,r=P(e,{onConsentSettled:e=>{n?.settled(e)},onConsentMutation:()=>{n?.clear()},onPageEvent:e=>{n?.settled(e)}});return{connect:async i=>{let a=await be({...i,onAppPush:r.push,onSharedContext:r.sharedContext,onAnonId:e=>{n?.observedAnon(e)}});n=oe(e,a.submitCapture,i.destination,t);let o=await r.connect(a.client);return n.needsAnon()&&n.observedAnon((await a.client.getSession()).anonId),o}}},Z=d(),Q=p(Z),$=`window.${Z}`;if(Q!==null&&Q.ready===void 0){let e=xe(Q,Z);t(Q),_(Q,async()=>{let t=Q.config?.destination;if(typeof t!=`string`||t===``)throw Error(`[Qinto] ${$}.config.destination is missing.`);return e.connect({destination:t})},$)}})();
@@ -20360,7 +20360,7 @@ var uniqueOrIssue = /* @__PURE__ */ __name((ctx, values, path, label) => {
20360
20360
  seen.add(value);
20361
20361
  }
20362
20362
  }, "uniqueOrIssue");
20363
- var PLATFORM_ZONES = ["blotout.io", "qinto.tech"];
20363
+ var PLATFORM_ZONES = ["platform.blotout.io", "qinto-api.blotout.io", "appui.blotout.io", "mcp.blotout.io", "qinto.tech"];
20364
20364
  var isPlatformHost = /* @__PURE__ */ __name((host) => {
20365
20365
  return PLATFORM_ZONES.some((zone) => host === zone || host.endsWith(`.${zone}`));
20366
20366
  }, "isPlatformHost");
@@ -20992,10 +20992,13 @@ var variableSchema = external_exports.object({
20992
20992
  label: external_exports.string().min(1).max(100),
20993
20993
  required: external_exports.boolean().optional(),
20994
20994
  browser: external_exports.boolean().optional(),
20995
+ owner: external_exports.enum(["site", "developer"]).optional(),
20995
20996
  helpText: external_exports.string().min(1).max(MAX_VARIABLE_HELP_TEXT).optional(),
20996
20997
  helpUrl: httpsUrl("helpUrl").optional()
20997
20998
  }).strict().refine((variable) => !(variable.kind === "secret" && variable.browser === true), {
20998
20999
  message: "A secret variable can never be browser-exposed"
21000
+ }).refine((variable) => !(variable.required === true && variable.owner === "developer"), {
21001
+ message: "A developer-owned variable cannot be required"
20999
21002
  });
21000
21003
  var appVariableSchema = external_exports.object({
21001
21004
  name: external_exports.string().min(1).max(64).regex(VARIABLE_NAME),
@@ -21467,6 +21470,8 @@ var MAX_CAPTURE_WRITES_PER_INSTALL = 10;
21467
21470
  var MAX_CONTEXT_WRITES_PER_INSTALL = 2;
21468
21471
  var CONTEXT_LEVELS = ["private", "shared"];
21469
21472
  var PUSH_BROADCASTS = ["install", "app", "site"];
21473
+ var ACTION_SOURCES = ["website", "api", "app", "scheduled", "webhook", "tool", "workflow"];
21474
+ var APP_HANDLER_SOURCES = ["scheduled", "webhook", "tool", "workflow"];
21470
21475
  var MAX_PRIVATE_CONTEXT_ENTRY_BYTES = 32 * 1024;
21471
21476
  var MAX_SHARED_CONTEXT_ENTRY_BYTES = 4 * 1024;
21472
21477
  var MAX_PUSH_BYTES = 16 * 1024;
@@ -21542,6 +21547,9 @@ var envelopeRequestSchema = external_exports.object({
21542
21547
  userId: external_exports.string().min(1).max(64),
21543
21548
  payload: external_exports.record(external_exports.string(), external_exports.unknown()),
21544
21549
  receivedAt: external_exports.number().int(),
21550
+ // Optional on the wire: an app worker redeploys before its site worker, so this reader must
21551
+ // accept an envelope a site worker predating the field produces.
21552
+ source: external_exports.enum(ACTION_SOURCES).optional(),
21545
21553
  context: actionContextSchema.optional(),
21546
21554
  browser: external_exports.record(external_exports.string().min(1).max(64), external_exports.unknown()).optional(),
21547
21555
  origin: external_exports.object({ app: external_exports.string().min(1).max(30), install: external_exports.number().int().min(1), depth: external_exports.number().int().min(0) }).strict().optional()
@@ -26868,7 +26876,6 @@ var submitAction = /* @__PURE__ */ __name(async (env, userId, args, request) =>
26868
26876
  ...args,
26869
26877
  userId: resolved.id,
26870
26878
  identifier: resolved.identifier,
26871
- source: "http",
26872
26879
  receivedAt,
26873
26880
  context: serverActionContext(args.context, receivedAt),
26874
26881
  geo: request?.cf,
@@ -27159,6 +27166,90 @@ var lookupPersonForApp = /* @__PURE__ */ __name(async (env, slug, id, afterRowId
27159
27166
  return page === null ? null : personOf(globalId, page);
27160
27167
  }, "lookupPersonForApp");
27161
27168
 
27169
+ // src/apps/broker-fields.ts
27170
+ var identityFields = {
27171
+ // Derived, never a literal: the client stamps ENVELOPE_VERSION on every body, so a hardcoded
27172
+ // number here silently 400s the whole channel the next time the envelope version moves.
27173
+ v: external_exports.literal(ENVELOPE_VERSION),
27174
+ slug: external_exports.string().min(1).max(30),
27175
+ key: external_exports.string().min(1)
27176
+ };
27177
+
27178
+ // src/apps/emit-payload.ts
27179
+ var emitPayloadSchema = external_exports.object({
27180
+ event: external_exports.string().min(1),
27181
+ eventId: external_exports.string().min(1).optional(),
27182
+ properties: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
27183
+ }).strict();
27184
+ var emittedEventId = /* @__PURE__ */ __name((eventId, properties2) => {
27185
+ if (eventId !== void 0) {
27186
+ return eventId;
27187
+ }
27188
+ const orderId = properties2?.["orderId"];
27189
+ if (typeof orderId === "string" && orderId !== "") {
27190
+ return orderId;
27191
+ }
27192
+ return `evt_${ulid3()}`;
27193
+ }, "emittedEventId");
27194
+
27195
+ // src/apps/rate-limit.ts
27196
+ var limiterAbsenceLogged = false;
27197
+ var rateLimited = /* @__PURE__ */ __name(async (env, key, binding = "BROKER_RATE_LIMITER") => {
27198
+ const limiter = env[binding];
27199
+ if (limiter === void 0) {
27200
+ if (!limiterAbsenceLogged) {
27201
+ limiterAbsenceLogged = true;
27202
+ addSpanEvent("rate limiter binding is absent; limits are off", { level: "warn", attrs: { binding } });
27203
+ }
27204
+ return false;
27205
+ }
27206
+ const outcome = await limiter.limit({ key });
27207
+ return !outcome.success;
27208
+ }, "rateLimited");
27209
+
27210
+ // src/apps/broker-track.ts
27211
+ var trackBody = external_exports.object({
27212
+ ...identityFields,
27213
+ kind: external_exports.literal("track"),
27214
+ source: external_exports.enum(APP_HANDLER_SOURCES),
27215
+ userId: external_exports.string().min(1).max(64),
27216
+ installId: external_exports.string().min(1).max(64),
27217
+ event: emitSchema
27218
+ }).strict();
27219
+ var handleTrack = /* @__PURE__ */ __name(async (env, body) => {
27220
+ const app2 = siteRegistry().registry.apps[body.slug];
27221
+ if (app2 === void 0) {
27222
+ return Response.json({ error: "forbidden" }, { status: 403 });
27223
+ }
27224
+ if (!app2.installs.some((install) => install.active && install.id === body.installId)) {
27225
+ return Response.json({ error: "invalid_install" }, { status: 400 });
27226
+ }
27227
+ const parsed = emitPayloadSchema.safeParse(body.event.payload);
27228
+ if (!parsed.success) {
27229
+ return Response.json({ error: "invalid_event" }, { status: 400 });
27230
+ }
27231
+ if (!isCanonicalId(body.userId)) {
27232
+ return Response.json({ error: "invalid_user" }, { status: 400 });
27233
+ }
27234
+ if (await rateLimited(env, `${body.slug}:${body.installId}:track`)) {
27235
+ return Response.json({ error: "rate_limited" }, { status: 429 });
27236
+ }
27237
+ const site = ownSite(env);
27238
+ if (!await anonStub(env, body.userId).appAllowed(body.slug, site, currentTraceContext())) {
27239
+ return Response.json({ error: "forbidden" }, { status: 403 });
27240
+ }
27241
+ const { event, eventId, properties: properties2 } = parsed.data;
27242
+ const action = buildAction({
27243
+ type: "track",
27244
+ userId: body.userId,
27245
+ source: body.source,
27246
+ payload: { event, eventId: emittedEventId(eventId, properties2), properties: properties2 ?? {} },
27247
+ context: { source: "app", occurredAt: body.event.occurredAt, offline: false, replay: false }
27248
+ });
27249
+ await ingest(env, action, site);
27250
+ return Response.json({ accepted: true }, { status: 202 });
27251
+ }, "handleTrack");
27252
+
27162
27253
  // src/id-graph/anon/context.ts
27163
27254
  var LEVEL_KINDS = {
27164
27255
  private: { kv: "context_private_kv", text: "context_private_text" },
@@ -27596,6 +27687,7 @@ var eventAction = /* @__PURE__ */ __name((target, trigger, snapshot) => {
27596
27687
  userId: snapshot.anonId,
27597
27688
  payload: trigger.action.payload,
27598
27689
  receivedAt: trigger.action.receivedAt,
27690
+ source: trigger.action.source,
27599
27691
  ...buckets.includes("eventContext") && trigger.action.context !== void 0 ? { context: trigger.action.context } : {},
27600
27692
  ...trigger.action.browserResults?.[target.slug] === void 0 ? {} : { browser: trigger.action.browserResults[target.slug] },
27601
27693
  ...trigger.action.origin !== void 0 ? { origin: trigger.action.origin } : {}
@@ -27633,21 +27725,6 @@ var buildEnvelope = /* @__PURE__ */ __name((target, trigger, snapshot, context)
27633
27725
  ...trigger.kind === "event" ? { action: eventAction(target, trigger, snapshot) } : {}
27634
27726
  };
27635
27727
  }, "buildEnvelope");
27636
- var emitPayloadSchema = external_exports.object({
27637
- event: external_exports.string().min(1),
27638
- eventId: external_exports.string().min(1).optional(),
27639
- properties: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
27640
- }).strict();
27641
- var emittedEventId = /* @__PURE__ */ __name((eventId, properties2) => {
27642
- if (eventId !== void 0) {
27643
- return eventId;
27644
- }
27645
- const orderId = properties2?.["orderId"];
27646
- if (typeof orderId === "string" && orderId !== "") {
27647
- return orderId;
27648
- }
27649
- return `evt_${ulid3()}`;
27650
- }, "emittedEventId");
27651
27728
  var ingestEmit = /* @__PURE__ */ __name(async (ingest2, { slug, ord, depth, emit, anonId, geo, host, context }) => {
27652
27729
  const parsed = emitPayloadSchema.safeParse(emit.payload);
27653
27730
  if (!parsed.success) {
@@ -27826,31 +27903,9 @@ var invokeApp = /* @__PURE__ */ __name(async (env, input2) => {
27826
27903
  );
27827
27904
  }, "invokeApp");
27828
27905
 
27829
- // src/apps/rate-limit.ts
27830
- var limiterAbsenceLogged = false;
27831
- var rateLimited = /* @__PURE__ */ __name(async (env, key, binding = "BROKER_RATE_LIMITER") => {
27832
- const limiter = env[binding];
27833
- if (limiter === void 0) {
27834
- if (!limiterAbsenceLogged) {
27835
- limiterAbsenceLogged = true;
27836
- addSpanEvent("rate limiter binding is absent; limits are off", { level: "warn", attrs: { binding } });
27837
- }
27838
- return false;
27839
- }
27840
- const outcome = await limiter.limit({ key });
27841
- return !outcome.success;
27842
- }, "rateLimited");
27843
-
27844
27906
  // src/apps/broker.ts
27845
27907
  var MAX_RPC_DEPTH = 3;
27846
27908
  var BROKER_PATHS = ["/__qinto/push", "/__qinto/rpc"];
27847
- var identityFields = {
27848
- // Derived, never a literal: the client stamps ENVELOPE_VERSION on every body, so a hardcoded
27849
- // number here silently 400s the whole channel the next time the envelope version moves.
27850
- v: external_exports.literal(ENVELOPE_VERSION),
27851
- slug: external_exports.string().min(1).max(30),
27852
- key: external_exports.string().min(1)
27853
- };
27854
27909
  var pushBody = external_exports.object({
27855
27910
  ...identityFields,
27856
27911
  kind: external_exports.literal("push"),
@@ -27920,7 +27975,8 @@ var brokerBody = external_exports.discriminatedUnion("kind", [
27920
27975
  contextReadBody,
27921
27976
  contextWriteBody,
27922
27977
  factsReadBody,
27923
- identityReadBody
27978
+ identityReadBody,
27979
+ trackBody
27924
27980
  ]);
27925
27981
  var BODY_ENVELOPE_ALLOWANCE_BYTES = 2 * 1024;
27926
27982
  var MAX_BODY_BYTES = MAX_RPC_BYTES + BODY_ENVELOPE_ALLOWANCE_BYTES;
@@ -28307,6 +28363,9 @@ var handleBroker = /* @__PURE__ */ __name(async (request, env) => {
28307
28363
  case "identityRead": {
28308
28364
  return handleIdentityRead(env, body);
28309
28365
  }
28366
+ case "track": {
28367
+ return handleTrack(env, body);
28368
+ }
28310
28369
  }
28311
28370
  }, "handleBroker");
28312
28371
  var brokerRoutes = new Hono2();
@@ -28749,6 +28808,7 @@ var requireScope = /* @__PURE__ */ __name((bit) => {
28749
28808
  if ((entry.mask & bit) === 0) {
28750
28809
  return c.json({ error: "forbidden" }, 403);
28751
28810
  }
28811
+ c.set("actionSource", entry.app === null ? "api" : "app");
28752
28812
  return next();
28753
28813
  };
28754
28814
  }, "requireScope");
@@ -30461,14 +30521,14 @@ var open2 = /* @__PURE__ */ __name(async (c) => {
30461
30521
 
30462
30522
  // browser-artifact/meta.json
30463
30523
  var meta_default = {
30464
- version: "0.1.6",
30465
- gitSha: "c1c5efdcdd94c4d8871c27924e1941be3444effe",
30466
- builtAt: "2026-09-08T14:30:58.153Z",
30467
- hash: "2a40c5a8fc3a17ea4eaeb819842f609694ccaddd1fc563c95463cc92bd6690df"
30524
+ version: "0.1.10",
30525
+ gitSha: "f21182929c88fa0748f575164bde781b31f4fbdd",
30526
+ builtAt: "2026-09-23T15:34:39.048Z",
30527
+ hash: "d1804e7914363626fed0280696cad5e06728b899e23c8a5ee6dcced3b67cf831"
30468
30528
  };
30469
30529
 
30470
30530
  // src/paths/q-js.ts
30471
- import qScript from "./b6b625dbf1615da405354d9b8813ffcc4e753b93-q.js.txt";
30531
+ import qScript from "./7bc13565a6fa9d520d0ebed788d11591e8db5b44-q.js.txt";
30472
30532
 
30473
30533
  // src/apps-q.ts
30474
30534
  import appsQScript from "./apps-q.json";
@@ -30599,7 +30659,12 @@ var definition2 = createRoute({
30599
30659
  });
30600
30660
  var alias = createApiRouter().openapi(definition2, async (c) => {
30601
30661
  const body = c.req.valid("json");
30602
- const id = await submitAction(c.env, body.identifier, { type: "alias", payload: { alias: body.alias } }, c.req.raw);
30662
+ const id = await submitAction(
30663
+ c.env,
30664
+ body.identifier,
30665
+ { type: "alias", source: c.get("actionSource"), payload: { alias: body.alias } },
30666
+ c.req.raw
30667
+ );
30603
30668
  return respond(c, acceptedResponse, { ok: true, id });
30604
30669
  });
30605
30670
 
@@ -30701,7 +30766,7 @@ var consent = createApiRouter().openapi(definition4, async (c) => {
30701
30766
  const id = await submitAction(
30702
30767
  c.env,
30703
30768
  identifier,
30704
- { type: "consent", payload: { categories: categories ?? {}, apps: apps ?? {} }, context },
30769
+ { type: "consent", source: c.get("actionSource"), payload: { categories: categories ?? {}, apps: apps ?? {} }, context },
30705
30770
  c.req.raw
30706
30771
  );
30707
30772
  return respond(c, acceptedResponse, { ok: true, id });
@@ -30800,7 +30865,12 @@ var definition6 = createRoute({
30800
30865
  });
30801
30866
  var identify = createApiRouter().openapi(definition6, async (c) => {
30802
30867
  const { identifier, traits, context } = c.req.valid("json");
30803
- const id = await submitAction(c.env, identifier, { type: "identify", payload: { traits: traits ?? {} }, context }, c.req.raw);
30868
+ const id = await submitAction(
30869
+ c.env,
30870
+ identifier,
30871
+ { type: "identify", source: c.get("actionSource"), payload: { traits: traits ?? {} }, context },
30872
+ c.req.raw
30873
+ );
30804
30874
  return respond(c, acceptedResponse, { ok: true, id });
30805
30875
  });
30806
30876
 
@@ -30950,7 +31020,7 @@ var session = createApiRouter().openapi(definition9, async (c) => {
30950
31020
  });
30951
31021
 
30952
31022
  // src/paths/v1/track.ts
30953
- var trackBody = external_exports.object({
31023
+ var trackBody2 = external_exports.object({
30954
31024
  identifier: identifierField2,
30955
31025
  event: external_exports.string().min(1).openapi({
30956
31026
  description: "Name of the event the user performed.",
@@ -30982,7 +31052,7 @@ var definition10 = createRoute({
30982
31052
  body: {
30983
31053
  required: true,
30984
31054
  description: "The event to record.",
30985
- content: { "application/json": { schema: trackBody } }
31055
+ content: { "application/json": { schema: trackBody2 } }
30986
31056
  }
30987
31057
  },
30988
31058
  responses: {
@@ -30999,7 +31069,7 @@ var track = createApiRouter().openapi(definition10, async (c) => {
30999
31069
  const id = await submitAction(
31000
31070
  c.env,
31001
31071
  identifier,
31002
- { type: "track", payload: { event, eventId, properties: properties2 ?? {} }, context },
31072
+ { type: "track", source: c.get("actionSource"), payload: { event, eventId, properties: properties2 ?? {} }, context },
31003
31073
  c.req.raw
31004
31074
  );
31005
31075
  return respond(c, acceptedResponse, { ok: true, id });
@@ -31111,6 +31181,13 @@ var FanOut = class extends WorkerEntrypoint2 {
31111
31181
  // src/apps/storage-link-site.ts
31112
31182
  import { WorkerEntrypoint as WorkerEntrypoint3 } from "cloudflare:workers";
31113
31183
 
31184
+ // src/apps/action-source.ts
31185
+ var LEGACY_SOURCES = { ws: "website", http: "api" };
31186
+ var withCurrentSource = /* @__PURE__ */ __name((action) => {
31187
+ const current = LEGACY_SOURCES[action.source];
31188
+ return current === void 0 ? action : { ...action, source: current };
31189
+ }, "withCurrentSource");
31190
+
31114
31191
  // src/apps/transform.ts
31115
31192
  var TRANSFORM_TIMEOUT_MS = 500;
31116
31193
  var TRANSFORM_CHAIN_BUDGET_MS = 1e3;
@@ -31312,10 +31389,11 @@ var StorageLinkSite = class extends WorkerEntrypoint3 {
31312
31389
  __name(this, "StorageLinkSite");
31313
31390
  }
31314
31391
  fanOut(job) {
31315
- runFanOut(this.env, this.ctx, job);
31392
+ const trigger = job.trigger.kind === "event" ? { ...job.trigger, action: withCurrentSource(job.trigger.action) } : job.trigger;
31393
+ runFanOut(this.env, this.ctx, { ...job, trigger });
31316
31394
  }
31317
31395
  transform(input2) {
31318
- return runTransformChain(this.env, { ...input2, dataPoints: createDataPointBudget() });
31396
+ return runTransformChain(this.env, { ...input2, action: withCurrentSource(input2.action), dataPoints: createDataPointBudget() });
31319
31397
  }
31320
31398
  describe() {
31321
31399
  return describeSite(this.env);
@@ -32199,7 +32277,7 @@ var updateGeo = /* @__PURE__ */ __name((storage, geo, observedAt) => {
32199
32277
  record2[key] = { value, observedAt };
32200
32278
  mutated = true;
32201
32279
  if (GEO_SERIES_KEY_SET.has(key)) {
32202
- insertFact(storage.sql, { ...projectionFact(entry.key), value: entry.value, source: "ws", observedAt: entry.observedAt });
32280
+ insertFact(storage.sql, { ...projectionFact(entry.key), value: entry.value, source: "website", observedAt: entry.observedAt });
32203
32281
  appended.push(entry);
32204
32282
  }
32205
32283
  }
@@ -32610,7 +32688,7 @@ var actionFromFrame = /* @__PURE__ */ __name((frame, userId, connection = {}) =>
32610
32688
  type: "identify",
32611
32689
  userId,
32612
32690
  identifier,
32613
- source: "ws",
32691
+ source: "website",
32614
32692
  payload: { traits: frame.traits ?? {} },
32615
32693
  context: browserActionContext(frame.context, host, session2),
32616
32694
  browserResults: frame.appResults,
@@ -32622,7 +32700,7 @@ var actionFromFrame = /* @__PURE__ */ __name((frame, userId, connection = {}) =>
32622
32700
  return buildAction({
32623
32701
  type: "track",
32624
32702
  userId,
32625
- source: "ws",
32703
+ source: "website",
32626
32704
  payload: { event: frame.event, eventId: frame.eventId, properties: frame.properties ?? {} },
32627
32705
  context: browserActionContext(frame.context, host, session2),
32628
32706
  browserResults: frame.appResults,
@@ -32631,13 +32709,13 @@ var actionFromFrame = /* @__PURE__ */ __name((frame, userId, connection = {}) =>
32631
32709
  });
32632
32710
  }
32633
32711
  case "alias": {
32634
- return buildAction({ type: "alias", userId, source: "ws", payload: { alias: frame.alias }, geo, host });
32712
+ return buildAction({ type: "alias", userId, source: "website", payload: { alias: frame.alias }, geo, host });
32635
32713
  }
32636
32714
  case "consent": {
32637
32715
  return buildAction({
32638
32716
  type: "consent",
32639
32717
  userId,
32640
- source: "ws",
32718
+ source: "website",
32641
32719
  payload: { categories: frame.categories ?? {}, apps: frame.apps ?? {} },
32642
32720
  context: browserActionContext(frame.context, host, session2),
32643
32721
  geo,
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "main": {
3
- "version": "0.1.2",
4
- "gitSha": "3f472a5c25add00444d8766fdabc271b9b86438e",
5
- "builtAt": "2026-09-23T15:32:11.643Z",
3
+ "version": "0.1.4",
4
+ "gitSha": "84a03b1ccdff814840f560c7efd0f72837454227",
5
+ "builtAt": "2026-09-24T14:23:50.391Z",
6
6
  "compatibilityDate": "2026-06-23",
7
7
  "compatibilityFlags": [
8
8
  "nodejs_compat"
@@ -10,8 +10,8 @@
10
10
  "mainModule": "index.js",
11
11
  "modules": [
12
12
  {
13
- "name": "b6b625dbf1615da405354d9b8813ffcc4e753b93-q.js.txt",
14
- "path": "main/b6b625dbf1615da405354d9b8813ffcc4e753b93-q.js.txt",
13
+ "name": "7bc13565a6fa9d520d0ebed788d11591e8db5b44-q.js.txt",
14
+ "path": "main/7bc13565a6fa9d520d0ebed788d11591e8db5b44-q.js.txt",
15
15
  "contentType": "text/plain"
16
16
  },
17
17
  {
@@ -39,9 +39,9 @@
39
39
  ]
40
40
  },
41
41
  "app-runtime": {
42
- "version": "0.1.0",
43
- "gitSha": "029b959b64dccc88a7a1f0687d5fc000a5042aba",
44
- "builtAt": "2026-09-23T15:32:13.746Z",
42
+ "version": "0.1.2",
43
+ "gitSha": "5c24e6442ad668125bba59aaec084adb43b70d1d",
44
+ "builtAt": "2026-09-24T14:23:53.656Z",
45
45
  "compatibilityDate": "2026-06-23",
46
46
  "compatibilityFlags": [
47
47
  "nodejs_compat"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blotout/qinto-cli",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "type": "module",
5
5
  "description": "Qinto CLI: authenticate against the Qinto platform and operate it from the terminal or from an agent.",
6
6
  "repository": {
@@ -1 +0,0 @@
1
- (function(){var e=()=>{},t=t=>{t.config?.autoCapture?.pageLoad===!0&&t.q.push([`track`,[`PageView`],e,e])},n=class extends Error{constructor(e){super(e),this.name=`QintoError`}},r=class extends n{constructor(e){super(e),this.name=`QintoTimeoutError`}},i=(e=Date.now())=>`evt_${globalThis.crypto.randomUUID()}_${e}`,a=(e,t)=>{if(e!==void 0)return e;let n=t?.orderId;return typeof n==`string`&&n!==``?n:i()},o=[`necessary`,`functional`,`analytics`,`marketing`,`salesOfData`],s=[`then`,`ready`,`config`],c=()=>{},l=(e,t)=>(...n)=>{let r=new Promise((r,i)=>{e.q.push([t,n,r,i])});return r.catch(c),r},u=e=>{let t=e;return t._q=1,t.q=[],new Proxy(t,{get:(e,n)=>typeof n!=`string`||s.includes(n)||n in t?Reflect.get(t,n):(t[n]=l(t,n),t[n])})},d=()=>{let e=document.currentScript?.dataset.qintoGlobal;return typeof e==`string`&&e!==``?e:`qinto`},f=e=>typeof e==`object`&&!!e&&e._q===1,p=(e=`qinto`)=>{let t=globalThis;if(e in t){let n=t[e];if(!f(n))return null;let r=Array.isArray(n.q)?n:u(n);return r!==n&&Object.defineProperty(t,e,{value:r,writable:!0,enumerable:!0,configurable:!0}),r}let n=u({});return Object.defineProperty(t,e,{value:n,writable:!0,enumerable:!0,configurable:!0}),n},m=(e,t)=>async(r,i)=>{let a=Reflect.get(e,r);if(typeof a!=`function`)throw new n(`[Qinto] ${t}.${r} is not a method of this tag.`);return a.apply(e,Array.from(i))},h=e=>()=>Promise.reject(e),g=(e,t)=>{let[n,r,i,a]=t;e(n,r).then(i,a)},ee=e=>{let t=[];return t.push=(...t)=>{for(let n of t)g(e,n);return 0},t},_=(e,t,n=`window.qinto`)=>{let r=e.ready;if(r!==void 0)return r;let i=e,a=e=>{let t=i.q;i.q=ee(e);for(let n of t)g(e,n)},o=t().then(e=>{a(m(e,n))},e=>{throw a(h(e)),e});return o.catch(c),i.ready=o,o},v=(e,t,n)=>n===void 0||!o.some(e=>e===n)?!1:n===`necessary`||e!==null&&e.categories[n]===!0&&e.apps[t]!==!1,y=()=>{},te=100,ne=1024,b=32768,x=(e,t,n)=>e.target.config?.consentConfiguration?.enabled!==!0||v(n,t,e.target.config?.apps?.[t]?.category),S=e=>Object.values(e.config?.apps??{}).some(e=>e.category!==`necessary`||e.capture!==void 0),C=(e,t)=>{let{app:n,install:r,topic:i,to:a}=e;return typeof n!=`string`||typeof i!=`string`||r!==void 0&&typeof r!=`string`||t&&!(Array.isArray(a)&&a.every(e=>typeof e==`string`))},re=(e,t,n,r)=>r===null?e.slug===t&&(n===void 0||n===e.installId):r.has(e.slug),w=(e,t)=>{console.error(`[Qinto] A handler of app "${e.slug}" (install "${e.installId}") failed.`,t)},T=(e,t,n)=>{for(let r of t)try{let t=r(n);t instanceof Promise&&t.catch(t=>{w(e,t)})}catch(t){w(e,t)}},ie=(e,t,n,r)=>{e.activated.add(r.id);let i={slug:t,installId:r.id,eventHandlers:[],consentHandlers:[],pushHandlers:[],sharedContextHandlers:[]};e.instances.push(i);let a={variables:r.variables,install:{id:r.id,label:r.label},getSession:async()=>{let n=await e.client.getSession();return e.target.config?.apps?.[t]?.geo===!0?n:{...n,ip:null,geo:null}},track:(t,n)=>{e.client.track(t,n).catch(y)},onEvent:e=>{i.eventHandlers.push(e)},onConsent:e=>{i.consentHandlers.push(e)},onPush:e=>{i.pushHandlers.push(e)},onSharedContext:n=>{if(e.target.config?.apps?.[t]?.sharedContext!==!0){console.error(`[Qinto] App "${t}" reads shared context without declaring the sharedContext capability.`);return}i.sharedContextHandlers.push(n),e.sharedContext!==null&&ae(e,t)&&T(i,[n],e.sharedContext)}};try{n(a)}catch(e){console.error(`[Qinto] App "${t}" failed to activate install "${r.id}".`,e)}},E=(e,t,n)=>{if(!x(e,t,e.consent))return;let r=((`_appInstalls`in e.target?e.target._appInstalls:void 0)?.[t]??[]).filter(t=>!e.activated.has(t.id));if(r.length===0)return;let i=n.factory;if(i===void 0){try{i=n.loader()}catch(e){i=null,console.error(`[Qinto] App "${t}" failed to load.`,e)}e.apps.set(t,{...n,factory:i})}if(i!==null)for(let n of r)ie(e,t,i,n)},ae=(e,t)=>x(e,t,e.consent)?e.sharedContextConsent===void 0||x(e,t,e.sharedContextConsent):!1,D=e=>{let{categories:t,apps:n}=e;if(typeof t==`object`&&t&&typeof n==`object`&&n)return{categories:t,apps:n}},O=(e,t)=>{let n=e;n.consent=t,n.sharedContextConsent=void 0;for(let n of e.instances)T(n,n.consentHandlers,t);e.onConsentSettled?.(t);for(let[t,n]of e.apps)E(e,t,n)},k=async e=>{try{O(e,await e.client.getConsent())}catch(e){console.error(`[Qinto] The apps consent read failed.`,e)}},A=e=>{let t;try{t=JSON.stringify(e)}catch{return}if(!(t===void 0||new TextEncoder().encode(t).length>ne))return JSON.parse(t)},j=e=>{let t={};for(let n of e){if(n.settled!==n.total||n.values.length!==1)continue;let e=A(n.values[0]);if(e===void 0)continue;let r={...t[n.instance.slug]??{},[n.instance.installId]:e},i={...t,[n.instance.slug]:r};new TextEncoder().encode(JSON.stringify(i)).length<=b&&(t[n.instance.slug]=r)}return Object.keys(t).length===0?void 0:t},M=async(e,t)=>{e.onPageEvent?.(e.consent);let n=[],r=[];for(let i of e.instances){if(!x(e,i.slug,e.consent))continue;if(e.target.config?.apps?.[i.slug]?.eventResults?.includes(t.type)!==!0){T(i,i.eventHandlers,t);continue}let a={instance:i,total:i.eventHandlers.length,settled:0,values:[]};n.push(a);for(let e of i.eventHandlers){let n;try{n=e(t)}catch(e){a.settled+=1,w(i,e);continue}r.push(Promise.resolve(n).then(e=>{a.settled+=1,e!==void 0&&a.values.push(e)},e=>{a.settled+=1,w(i,e)}))}}if(r.length>0){let e=y,t=new Promise(t=>{e=t}),n=setTimeout(e,te);await Promise.race([Promise.all(r),t]),clearTimeout(n)}return j(n)},N=e=>{let{client:t}=e;return{track:async(n,r,i)=>{let o=a(i?.eventId,r),s=t.actionContext(),c=await M(e,{type:`track`,event:n,eventId:o,properties:r??{}});return await t.submitTrack({event:n,eventId:o,properties:r,context:s,appResults:c}),{eventId:o}},identify:async(n,r)=>{let i=t.actionContext(),a=await M(e,{type:`identify`,event:`identify`,properties:n??{}});return t.submitIdentify({traits:n,id:r,context:i,appResults:a})},alias:t.alias,consent:async n=>{if(await t.submitConsent(n,t.actionContext()),e.onConsentMutation?.(),S(e.target)){let t=e;t.consent=null,await k(e)}},getAnonId:t.getAnonId,getConsent:t.getConsent,getSession:t.getSession,close:t.close}},P=(e,t)=>{let n=new Map,r=null,i=null,a=e=>{let[t,i]=e;if(n.has(t))return;let a={loader:i};n.set(t,a),r?.(t,a)},o=e,s=`_apps`in o?o._apps:void 0,c=Array.isArray(s)?s:[];c.push=(...e)=>{for(let t of e)a(t);return 0},o._apps=c;for(let e of c.splice(0))a(e);return{sharedContext:e=>{let{context:t}=e;if(typeof t!=`object`||!t||Array.isArray(t)){console.error(`[Qinto] The server sent a malformed shared context.`,e);return}let n=i;if(n===null)return;n.sharedContext=t;let r=D(e),a=r??n.consent;n.sharedContextConsent=r;for(let e of n.instances)x(n,e.slug,a)&&T(e,e.sharedContextHandlers,n.sharedContext)},push:e=>{let{app:t,install:n,topic:r,scope:a,to:o}=e,s=a===`site`;if(C(e,s)){console.error(`[Qinto] The server sent a malformed app push.`,e);return}let c=i;if(c===null)return;let l=s?new Set(o):null,u={topic:r,data:e.data,from:t};for(let e of c.instances)re(e,t,n,l)&&x(c,e.slug,c.consent)&&T(e,e.pushHandlers,u)},connect:async a=>{let o={target:e,client:a,apps:n,instances:[],activated:new Set,consent:null,sharedContext:i?.sharedContext??null,sharedContextConsent:i?.sharedContextConsent,onConsentSettled:t?.onConsentSettled,onConsentMutation:t?.onConsentMutation,onPageEvent:t?.onPageEvent};i=o,r=(e,t)=>{E(o,e,t),o.consent===null&&o.target.config?.consentConfiguration?.enabled===!0&&o.target.config?.apps?.[e]?.category!==`necessary`&&k(o)};for(let[e,t]of n)E(o,e,t);return e.config?.consentConfiguration?.enabled===!0&&S(e)?await k(o):o.onConsentSettled?.(o.consent),N(o)}}},F=512,I=864e5,L=e=>Object.fromEntries(Object.entries(e).map(([e,t])=>[e,Object.fromEntries(Object.entries(t).map(([e,t])=>[e,t.value]))])),R=(e,t)=>`qinto:cap:${encodeURIComponent(e)}:${encodeURIComponent(t)}`,z=e=>{try{let t=window.sessionStorage.getItem(e);if(t===null)return null;let n=JSON.parse(t);return typeof n.at!=`number`||typeof n.anonId!=`string`||typeof n.apps!=`object`||n.apps===null||Date.now()-n.at>I?null:{at:n.at,anonId:n.anonId,apps:n.apps}}catch{return null}},B=(e,t)=>{try{window.sessionStorage.setItem(e,JSON.stringify(t))}catch{}},V=e=>{try{window.sessionStorage.removeItem(e)}catch{}},H=e=>{try{return decodeURIComponent(e)}catch{return e}},U=()=>{let e={};for(let t of document.cookie.split(`;`)){let n=t.indexOf(`=`);n>0&&(e[t.slice(0,n).trim()]=H(t.slice(n+1)))}return e},W=(e,t)=>{let n={};for(let r of e.queryParams){let e=t.params.get(r);e!==null&&e.length<=F&&(n[r]={value:e,source:`query`})}for(let r of e.cookies){let e=t.cookies[r];e!==void 0&&e.length<=F&&(n[r]={value:e,source:`cookie`})}return n},G=(e,t)=>{let n={params:new URLSearchParams(window.location.search),cookies:U()},r={};for(let[i,a]of Object.entries(e.config?.apps??{})){if(a.capture===void 0||!v(t,i,a.category))continue;let e=W(a.capture,n);Object.keys(e).length>0&&(r[i]=e)}return r},K=(e,t)=>{let n={};for(let[r,i]of Object.entries(e)){let e=t[r]??{},a={};for(let[t,n]of Object.entries(i))(e[t]?.value!==n.value||e[t]?.source!==n.source)&&(a[t]=n);Object.keys(a).length>0&&(n[r]=a)}return n},oe=(e,t,n,r)=>{let i=R(r,n),a=z(i),o=a?.apps??{},s=a?.anonId??null,c=a===null,l={},u=0,d=(e,t)=>{s!==null&&s!==e.anonId&&(o={}),s=e.anonId;let n=new Set(Object.entries(e.accepted).flatMap(([e,t])=>t.map(t=>`${e}\u0000${t}`)));for(let[e,r]of Object.entries(t))for(let[t,i]of Object.entries(r))n.has(`${e}\u0000${t}`)&&(o[e]={...o[e],[t]:i});B(i,{at:Date.now(),anonId:s,apps:o})},f=null,p=()=>{u+=1,o={},l={},V(i)},m=n=>{if(f=n,!c)return;let r=K(G(e,n),{...o,...l});if(Object.keys(r).length===0)return;let i=u;l={...l,...r},t(L(r)).then(e=>{i===u&&d(e,r)}).catch(()=>{}).finally(()=>{l={}})};return{settled:e=>{m(e)},needsAnon:()=>!c,observedAnon:e=>{(e===null||s!==null&&s!==e)&&p(),s=e,c=!0,m(f)},clear:p}},q=Symbol.for(`qinto.connectGate`),J=()=>{},se=e=>{let t=globalThis,n=(t[q]??Promise.resolve()).then(e);return t[q]=n.then(J,J),n},Y=class extends n{constructor(e){super(e),this.name=`QintoProtocolError`}},ce=e=>{if(typeof e!=`string`)return null;let t;try{t=JSON.parse(e)}catch{return null}if(typeof t!=`object`||!t)return null;let n=t;return n.t===`ack`||n.t===`error`||n.t===`consentState`||n.t===`session`||n.t===`captureAck`||n.t===`appPush`||n.t===`sharedContext`?t:null},le=e=>{if(e.t===`consentState`)return{categories:e.categories,apps:e.apps};if(e.t===`session`)return{anonId:e.anonId,globalId:e.globalId,ip:e.ip,geo:e.geo};if(e.t===`captureAck`)return{anonId:e.anonId,accepted:e.accepted}},ue=e=>{let t=new Map,n=e=>{let n=t.get(e);if(n!==void 0)return clearTimeout(n.timer),t.delete(e),n};return{submit:n=>new Promise((i,a)=>{let o={frame:n,sent:e.send(n),resolve:i,reject:a,timer:setTimeout(()=>{t.delete(n.ref),a(new r(`The Qinto request timed out.`))},e.requestTimeoutMs)};t.set(n.ref,o)}),flush:()=>{for(let n of t.values())n.sent||=e.send(n.frame)},deliver:e=>{let t=e.ref===void 0?void 0:n(e.ref);if(t!==void 0){if(e.t===`error`){t.reject(new Y(e.message));return}t.resolve(le(e))}},rejectAll:e=>{let n=[...t.values()];t.clear();for(let t of n)clearTimeout(t.timer),t.reject(e)}}},de=e=>new WebSocket(e),fe=500,pe=3e4,me=`session not initialized`,he=(e,t)=>{let n=Math.min(fe*2**e,pe);return Math.round(n/2+n/2*t)},ge=(e,t)=>e.t===`appPush`?(t.onAppPush?.(e),!0):e.t===`sharedContext`&&(t.onSharedContext?.(e),!0),X=e=>{let t=`${e.destination.replace(/\/+$/,``)}/open`,n=null,r=null,i=0,a=null,o=ue({send:e=>n===null||n.readyState!==1?!1:(n.send(JSON.stringify(e)),!0),requestTimeoutMs:e.requestTimeoutMs}),s=t=>{e.onError?.(t)},c=()=>{if(r!==null&&(clearTimeout(r),r=null),n!==null){let e=n;n=null,e.close()}o.rejectAll(new Y(`connection closed`))},l=()=>{let u=de(t);n=u,u.onopen=()=>{i=0;let e=a;a=null,o.flush(),e?.resolve()},u.onmessage=t=>{let n=ce(t.data);if(n===null){s(new Y(`invalid message`));return}ge(n,e)||(`anonId`in n&&typeof n.anonId==`string`&&e.onAnonId?.(n.anonId),o.deliver(n),n.t===`error`&&n.message===me&&u.close())},u.onerror=()=>{s(new Y(`connection closed`))},u.onclose=()=>{if(n!==u)return;n=null;let e=a;if(e!==null){a=null,c(),e.reject(new Y(`connection closed`));return}o.rejectAll(new Y(`connection closed`)),r=setTimeout(()=>{r=null,l()},he(i,Math.random())),i+=1}};return{connect:()=>new Promise((e,t)=>{a={resolve:e,reject:t},l()}),submit:e=>o.submit(e),close:()=>{c()}}},_e=1e4,ve=()=>{let e=0;return()=>(e+=1,`q${e}`)},ye=(e,t)=>{let n=`qinto:visit:${e.replace(/\/+$/,``)}:${t}`,r,i=!1;try{let e=sessionStorage.getItem(n),t=e===null?null:JSON.parse(e);t===null||typeof t.id!=`string`||!/^vis_[0-9a-f-]{36}$/.test(t.id)||!Number.isSafeInteger(t.startedAt)||t.startedAt<0?(r={id:`vis_${crypto.randomUUID()}`,startedAt:Date.now()},sessionStorage.setItem(n,JSON.stringify(r)),i=!0):r={id:t.id,startedAt:t.startedAt}}catch{r={id:`vis_${crypto.randomUUID()}`,startedAt:Date.now()},i=!0}return()=>{let e={...r,isNew:i};return i=!1,e}},be=(e,t,n,r)=>{let i=null,o=ye(n,r),s=()=>{let e=document.referrer;return{occurredAt:Date.now(),page:{url:window.location.href,...e===``?{}:{referrer:e}},session:o()}},c=async()=>{let n=await e.submit({t:`getSession`,ref:t()});return i=n.anonId,n};return{actionContext:s,submitIdentify:async({traits:n,id:r,context:i,appResults:a})=>{await e.submit({t:`identify`,ref:t(),traits:n,id:r,context:i,appResults:a})},submitTrack:async({event:n,eventId:r,properties:i,context:a,appResults:o})=>{await e.submit({t:`track`,ref:t(),event:n,eventId:r,properties:i,context:a,appResults:o})},submitConsent:async(n,r)=>{await e.submit({t:`consent`,ref:t(),categories:n.categories,apps:n.apps,context:r})},track:async(n,r,i)=>{let o=a(i?.eventId,r);return await e.submit({t:`track`,ref:t(),event:n,eventId:o,properties:r,context:s()}),{eventId:o}},alias:async n=>{await e.submit({t:`alias`,ref:t(),alias:n})},getConsent:async()=>await e.submit({t:`getConsent`,ref:t()}),getSession:c,getAnonId:async()=>(i===null&&await c(),i),close:async()=>{e.close()}}},xe=async e=>{let t=X({destination:e.destination,requestTimeoutMs:e.requestTimeoutMs??_e,onError:e.onError,onAppPush:e.onAppPush,onSharedContext:e.onSharedContext,onAnonId:e.onAnonId});await se(()=>t.connect());let n=ve();return{client:be(t,n,e.destination,e.globalName),submitCapture:async e=>await t.submit({t:`capture`,ref:n(),apps:e})}},Se=(e,t)=>{let n=null,r=P(e,{onConsentSettled:e=>{n?.settled(e)},onConsentMutation:()=>{n?.clear()},onPageEvent:e=>{n?.settled(e)}});return{connect:async i=>{let a=await xe({...i,globalName:t,onAppPush:r.push,onSharedContext:r.sharedContext,onAnonId:e=>{n?.observedAnon(e)}});n=oe(e,a.submitCapture,i.destination,t);let o=await r.connect(a.client);return n.needsAnon()&&n.observedAnon((await a.client.getSession()).anonId),o}}},Z=d(),Q=p(Z),$=`window.${Z}`;if(Q!==null&&Q.ready===void 0){let e=Se(Q,Z);t(Q),_(Q,async()=>{let t=Q.config?.destination;if(typeof t!=`string`||t===``)throw Error(`[Qinto] ${$}.config.destination is missing.`);return e.connect({destination:t})},$)}})();