@mushi-mushi/core 1.22.5 → 1.25.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/CONTRIBUTING.md CHANGED
@@ -76,6 +76,7 @@ packages/
76
76
  apps/
77
77
  admin/ Admin dashboard (React + Tailwind)
78
78
  docs/ Documentation site (Nextra — apps/docs)
79
+ testers/ Tester marketplace portal (Next.js)
79
80
  tooling/
80
81
  eslint-config/ Shared ESLint flat config
81
82
  tsconfig/ Shared TypeScript configs
@@ -118,15 +119,22 @@ If your PR changes **user-visible SDK or CLI behavior**, update docs in the **sa
118
119
  Guardrails (run locally before pushing):
119
120
 
120
121
  ```bash
121
- pnpm check:docs-stats # root README stat badges
122
+ pnpm check:docs-stats # root README stat badges + glance <sub> line
122
123
  pnpm check:sdk-version-matrix # apps/docs/content/sdks/index.mdx versions
123
124
  pnpm check:internal-doc-links # dead /foo links in MDX
124
- pnpm check:onboarding-drift # phantom MCP env vars / stale tool names
125
+ pnpm check:onboarding-drift # phantom MCP env vars / stale tool names / native install coords
126
+ pnpm check:catalog-sync # MCP catalog ↔ admin counts ↔ prose "N tools"
127
+ pnpm check:mcp-tools-doc # mcp-tools.generated.mdx stale check
125
128
  pnpm check:bundle-docs # @mushi-mushi/web size-limit vs README
129
+ pnpm check:docs-drift # versions + public docs links
126
130
  pnpm gen:llms-txt # regenerate apps/docs/public/llms.txt after nav changes
127
131
  pnpm gen:mcp-tools-doc # regenerate sdks/mcp-tools.generated.mdx from catalog.ts
128
132
  ```
129
133
 
134
+ **Same-PR rule:** if behavior, env vars, CLI commands, MCP tools, or install
135
+ coordinates change, update the matching docs in this PR and run
136
+ `pnpm check:docs-drift && pnpm check:onboarding-drift && pnpm check:catalog-sync`.
137
+
130
138
  README changes on npm packages only appear on [npmjs.com](https://www.npmjs.com) after the next **Changesets publish**.
131
139
 
132
140
  ## Changesets
package/README.md CHANGED
@@ -23,7 +23,7 @@ Core types, API client, and shared utilities for every Mushi SDK package.
23
23
 
24
24
  | Type | Purpose |
25
25
  |----------------------------|-----------------------------------------------------------------------------------------------|
26
- | `MushiPreset` | `'production-calm' \| 'beta-loud' \| 'internal-debug' \| 'manual-only'` posture bundles. |
26
+ | `MushiPreset` | Posture bundles: `'production-calm' \| 'beta-loud' \| 'internal-debug' \| 'manual-only'` plus the tiers `'minimal' \| 'standard' \| 'full'`. See [Config presets](#config-presets--precedence). |
27
27
  | `MushiWidgetAnchor` | Raw-CSS positioning (`top` / `right` / `bottom` / `left`) for the widget launcher. |
28
28
  | `MushiPrivacyConfig` | `maskSelectors`, `blockSelectors`, `allowUserRemoveScreenshot` for screenshot redaction. |
29
29
  | `MushiUrlMatcher` | `string \| RegExp` element used by `capture.ignoreUrls` and `apiCascade.ignoreUrls`. |
@@ -46,6 +46,61 @@ Constants: `MUSHI_INTERNAL_HEADER` (`'X-Mushi-Internal'`),
46
46
  `MUSHI_INTERNAL_INIT_MARKER`, and the `MushiInternalRequestKind` literal union
47
47
  are re-exported so framework adapters can build their own self-noise filters.
48
48
 
49
+ ## Config presets & precedence
50
+
51
+ Pass `preset` to pick a posture bundle instead of hand-assembling
52
+ `widget` / `capture` / `proactive` flags. `expandPreset(config)` expands the
53
+ preset into those nested objects; **your explicit config always wins** on every
54
+ key (the merge is per-sub-object, so overriding one `capture` flag keeps the
55
+ rest of the preset's defaults).
56
+
57
+ | Field | `minimal` | `standard` | `full` |
58
+ |----------------------|----------------|--------------------|------------------|
59
+ | Widget | on (`auto`) | on (SDK default) | on (`auto`) |
60
+ | `capture.console` | ✅ | default | ✅ |
61
+ | `capture.network` | ❌ | default | ✅ |
62
+ | `capture.performance`| ❌ | default | ✅ |
63
+ | `capture.screenshot` | `on-report` | default | `auto` |
64
+ | `capture.replay` | `off` | default | `lite` |
65
+ | Proactive triggers | none | default | all on |
66
+
67
+ - **`minimal`** — widget + console capture only, screenshot on report; no
68
+ network/performance/replay, no proactive nudges. Leanest footprint.
69
+ - **`standard`** — today's SDK defaults. Expansion is a **no-op** (the config
70
+ is returned untouched); the preset exists so the intent is explicit and
71
+ documented.
72
+ - **`full`** — every capture channel on (including a self-contained `lite`
73
+ session replay) and all proactive triggers. Loudest / most data.
74
+
75
+ The four legacy postures — `production-calm`, `beta-loud`, `internal-debug`,
76
+ `manual-only` — are still supported and expand the same way.
77
+
78
+ ### Precedence
79
+
80
+ Highest wins. `resolveEnvConfig()` only ever supplies
81
+ `projectId` / `apiKey` / `apiEndpoint`, so it never competes with a preset for
82
+ the nested option objects.
83
+
84
+ | Priority | Source | Example |
85
+ |----------|-------------------------------------|-------------------------------------------|
86
+ | 1 (wins) | Explicit config | `capture: { network: true }` |
87
+ | 2 | `preset` | `preset: 'minimal'` → `network: false` |
88
+ | 3 | Environment variables | `NEXT_PUBLIC_MUSHI_*` / `VITE_MUSHI_*` |
89
+ | 4 | SDK defaults | built-in fallbacks |
90
+
91
+ ### Validation
92
+
93
+ `validateConfig(config)` runs at init and **fails loud but never throws**: it
94
+ `console.error`s on unknown top-level keys (usually a typo) and on an invalid
95
+ `preset` value, then continues. Set `MUSHI_SILENT=1` to suppress the warnings.
96
+
97
+ ```typescript
98
+ import { expandPreset, validateConfig } from '@mushi-mushi/core';
99
+
100
+ validateConfig(config); // warns on typos / bad preset (never throws)
101
+ const resolved = expandPreset(config); // preset → nested objects, explicit wins
102
+ ```
103
+
49
104
  ## Usage
50
105
 
51
106
  ```typescript
package/SECURITY.md CHANGED
@@ -111,7 +111,7 @@ What we treat as in-scope attacker capabilities, and what we don't.
111
111
  | User pasting a Stripe / OpenAI / GitHub PAT into a bug report | ✅ | PII scrubber redacts ~15 vendor token formats client-side before the report leaves the device. Mirrors `packages/core/src/pii-scrubber.ts` across iOS, Android, Flutter, React Native. |
112
112
  | Stolen end-user device with the SDK installed | ⚠️ partial | Offline queue is AsyncStorage / Keychain / SharedPreferences — no app-level encryption beyond the OS default. Reports waiting to flush are vulnerable to a forensic attacker. |
113
113
  | Compromised Supabase service-role key | ❌ | Treated as a tier-0 incident; would require key rotation and audit-log forensics. Not defendable in software. |
114
- | Compromise of `kensaurus@gmail.com` | ❌ | Treated as a project-fork event; downstream consumers should pin to the last known-good version and follow the new release channel. |
114
+ | Compromise of `kensaurus@gmail.com` mail account | ❌ | Treated as a project-fork event; downstream consumers should pin to the last known-good version and follow the new release channel. |
115
115
  | Physical / OS-level attacker on an end-user device | ❌ | Out of scope. |
116
116
  | Malicious fork using the Mushi name to ship malware | ❌ (technical) ✅ (legal) | The MIT / AGPLv3 grant lets the fork exist; the trademark policy (`TRADEMARK.md`) makes shipping it under the Mushi name an infringement we will pursue. |
117
117
 
@@ -157,6 +157,29 @@ Structured fields you set explicitly (`metadata.userEmail`,
157
157
  opt-in attribution data, and silently rewriting them would break
158
158
  support workflows.
159
159
 
160
+ ### CLI funnel telemetry
161
+
162
+ The Mushi CLI sends two fire-and-forget signals to `/v1/cli/funnel` on your
163
+ project's endpoint to help us detect where the onboarding wizard drops off:
164
+
165
+ | Signal | When | What is sent |
166
+ |--------|------|-------------|
167
+ | `wizard_env_written` | After `npx mushi-mushi` writes `.env.local` | `event`, `source: "cli"` |
168
+ | `mcp_setup_done` | After `mushi setup --ide` verifies the MCP key | `event`, `source: "cli"` |
169
+
170
+ No file contents, stack traces, or identifiers beyond your project ID and API
171
+ key are sent. The key is already on the endpoint — this adds no new disclosure.
172
+
173
+ **Opt out** by setting `MUSHI_NO_TELEMETRY=1` in your shell or CI environment:
174
+
175
+ ```bash
176
+ MUSHI_NO_TELEMETRY=1 npx mushi-mushi
177
+ MUSHI_NO_TELEMETRY=1 mushi setup --ide cursor
178
+ ```
179
+
180
+ These signals never block or delay the wizard — they time out silently after
181
+ 4 seconds and errors are discarded.
182
+
160
183
  ### Where data lives
161
184
 
162
185
  - **Reports & telemetry** — Supabase Postgres in the `us-west-1` region.
package/dist/index.cjs CHANGED
@@ -360,6 +360,17 @@ function createApiClient(options) {
360
360
  );
361
361
  },
362
362
  // ─── Rewards program (P1) ──────────────────────────────────
363
+ /** POST /v1/sdk/session — lightweight session lifecycle events.
364
+ * Best-effort (1 retry, no offline queue) — mirrors postDiscoveryEvent. */
365
+ async postSessionEvent(payload) {
366
+ return request(
367
+ "POST",
368
+ "/v1/sdk/session",
369
+ payload,
370
+ 1,
371
+ "discovery"
372
+ );
373
+ },
363
374
  async submitActivity(userId, events, opts) {
364
375
  return request(
365
376
  "POST",
@@ -601,6 +612,219 @@ function resolveEnvConfig() {
601
612
  if (apiEndpoint) result.apiEndpoint = apiEndpoint;
602
613
  return result;
603
614
  }
615
+ var ENV_PREFIXES = ["VITE_", "NEXT_PUBLIC_", "NUXT_PUBLIC_", "EXPO_PUBLIC_", ""];
616
+ var FIELD_SUFFIX = {
617
+ projectId: "MUSHI_PROJECT_ID",
618
+ apiKey: "MUSHI_API_KEY"
619
+ };
620
+ function diagnoseEnvConfig() {
621
+ const resolved = resolveEnvConfig();
622
+ const missing = [];
623
+ if (!resolved.projectId) missing.push("projectId");
624
+ if (!resolved.apiKey) missing.push("apiKey");
625
+ if (missing.length === 0) {
626
+ return { resolved, missing, nearMisses: [], message: null };
627
+ }
628
+ const importMetaEnv = (() => {
629
+ try {
630
+ const env = undefined;
631
+ return typeof env === "object" && env !== null ? env : null;
632
+ } catch {
633
+ return null;
634
+ }
635
+ })();
636
+ const processEnv = (() => {
637
+ try {
638
+ return typeof process !== "undefined" && process.env ? process.env : null;
639
+ } catch {
640
+ return null;
641
+ }
642
+ })();
643
+ const nearMisses = [];
644
+ for (const field of missing) {
645
+ for (const prefix of ENV_PREFIXES) {
646
+ const key = `${prefix}${FIELD_SUFFIX[field]}`;
647
+ if (importMetaEnv && importMetaEnv[key]) {
648
+ nearMisses.push({ field, key, via: "import.meta.env" });
649
+ } else if (processEnv && processEnv[key]) {
650
+ nearMisses.push({ field, key, via: "process.env" });
651
+ }
652
+ }
653
+ }
654
+ const expectedPrefix = importMetaEnv ? "VITE_MUSHI_*" : "NEXT_PUBLIC_MUSHI_* (Next.js), EXPO_PUBLIC_MUSHI_* (Expo), or MUSHI_* (Node / React Native)";
655
+ const lines = [`[mushi] Missing ${missing.join(" and ")} \u2014 the SDK will not initialize.`];
656
+ for (const m of nearMisses) {
657
+ lines.push(
658
+ `[mushi] Found ${m.key} in ${m.via}, but this runtime did not resolve it \u2014 it is either the wrong prefix for your bundler or not exposed to the client bundle.`
659
+ );
660
+ }
661
+ lines.push(`[mushi] This runtime reads: ${expectedPrefix}. Set the matching variable(s) and restart your dev server.`);
662
+ if (nearMisses.length === 0) {
663
+ lines.push(
664
+ `[mushi] If you already set the variable, check the prefix: a mis-prefixed var is stripped from client bundles and resolves to undefined with no error.`
665
+ );
666
+ }
667
+ return { resolved, missing, nearMisses, message: lines.join("\n") };
668
+ }
669
+
670
+ // src/presets.ts
671
+ var KNOWN_PRESETS = [
672
+ "production-calm",
673
+ "beta-loud",
674
+ "internal-debug",
675
+ "manual-only",
676
+ "minimal",
677
+ "standard",
678
+ "full"
679
+ ];
680
+ var KNOWN_CONFIG_KEYS = [
681
+ "projectId",
682
+ "apiKey",
683
+ "apiEndpoint",
684
+ "timeout",
685
+ "maxRetries",
686
+ "circuitBreaker",
687
+ "preset",
688
+ "runtimeConfig",
689
+ "sentry",
690
+ "widget",
691
+ "capture",
692
+ "privacy",
693
+ "proactive",
694
+ "preFilter",
695
+ "integrations",
696
+ "offline",
697
+ "rewards",
698
+ "assistant",
699
+ "debug",
700
+ "enabled",
701
+ "appVersion",
702
+ "beforeSendFeedback",
703
+ "onCrashedLastRun"
704
+ ];
705
+ function presetDefaults(preset) {
706
+ switch (preset) {
707
+ case "manual-only":
708
+ return {
709
+ widget: { trigger: "manual", outdatedBanner: "console-only" },
710
+ capture: { console: true, network: true, performance: false, screenshot: "on-report", elementSelector: false },
711
+ proactive: { rageClick: false, longTask: false, apiCascade: false, errorBoundary: false }
712
+ };
713
+ case "beta-loud":
714
+ return {
715
+ widget: { trigger: "auto", outdatedBanner: "banner" },
716
+ capture: { console: true, network: true, performance: true, screenshot: "auto", elementSelector: true },
717
+ proactive: { rageClick: true, longTask: true, apiCascade: true, errorBoundary: true }
718
+ };
719
+ case "internal-debug":
720
+ return {
721
+ widget: { trigger: "auto", outdatedBanner: "banner", brandFooter: true },
722
+ capture: { console: true, network: true, performance: true, screenshot: "auto", elementSelector: true },
723
+ proactive: {
724
+ rageClick: true,
725
+ longTask: true,
726
+ apiCascade: true,
727
+ errorBoundary: true,
728
+ cooldown: { maxProactivePerSession: 10, dismissCooldownHours: 0, suppressAfterDismissals: 99 }
729
+ }
730
+ };
731
+ case "production-calm":
732
+ return {
733
+ widget: { trigger: "auto", outdatedBanner: "console-only" },
734
+ capture: { console: true, network: true, performance: false, screenshot: "on-report", elementSelector: false },
735
+ proactive: { rageClick: false, longTask: false, apiCascade: false, errorBoundary: false }
736
+ };
737
+ // Lean posture: widget on, console capture only, screenshot on-report,
738
+ // no network/performance/replay, no proactive nudges.
739
+ case "minimal":
740
+ return {
741
+ widget: { trigger: "auto" },
742
+ capture: {
743
+ console: true,
744
+ network: false,
745
+ performance: false,
746
+ screenshot: "on-report",
747
+ replay: "off",
748
+ elementSelector: false
749
+ },
750
+ proactive: { rageClick: false, longTask: false, apiCascade: false, errorBoundary: false }
751
+ };
752
+ // Everything-on posture: full capture (incl. self-contained `lite` replay)
753
+ // and every proactive trigger.
754
+ case "full":
755
+ return {
756
+ widget: { trigger: "auto" },
757
+ capture: {
758
+ console: true,
759
+ network: true,
760
+ performance: true,
761
+ screenshot: "auto",
762
+ replay: "lite",
763
+ elementSelector: true
764
+ },
765
+ proactive: { rageClick: true, longTask: true, apiCascade: true, errorBoundary: true }
766
+ };
767
+ // 'standard' and anything unrecognised → no expansion.
768
+ default:
769
+ return void 0;
770
+ }
771
+ }
772
+ function expandPreset(config) {
773
+ const defaults = presetDefaults(config.preset);
774
+ if (!defaults) return config;
775
+ return {
776
+ ...config,
777
+ widget: {
778
+ ...defaults.widget,
779
+ ...config.widget
780
+ },
781
+ capture: {
782
+ ...defaults.capture,
783
+ ...config.capture
784
+ },
785
+ proactive: {
786
+ ...defaults.proactive,
787
+ ...config.proactive,
788
+ cooldown: {
789
+ ...defaults.proactive?.cooldown,
790
+ ...config.proactive?.cooldown
791
+ }
792
+ }
793
+ };
794
+ }
795
+ function isSilenced() {
796
+ const truthy = (v) => v === "1" || v === "true";
797
+ try {
798
+ if (typeof process !== "undefined" && process.env && truthy(process.env.MUSHI_SILENT)) return true;
799
+ } catch {
800
+ }
801
+ try {
802
+ const env = undefined;
803
+ if (env && typeof env === "object" && (truthy(env.MUSHI_SILENT) || truthy(env.VITE_MUSHI_SILENT))) return true;
804
+ } catch {
805
+ }
806
+ return false;
807
+ }
808
+ function validateConfig(config) {
809
+ try {
810
+ if (isSilenced()) return;
811
+ if (!config || typeof config !== "object") return;
812
+ const unknownKeys = Object.keys(config).filter((key) => !KNOWN_CONFIG_KEYS.includes(key));
813
+ if (unknownKeys.length > 0) {
814
+ console.error(
815
+ `[mushi] Unknown config key${unknownKeys.length > 1 ? "s" : ""}: ${unknownKeys.map((k) => `\`${k}\``).join(", ")} \u2014 check for typos (see MushiConfig). Ignored.`
816
+ );
817
+ }
818
+ if (config.preset !== void 0 && !KNOWN_PRESETS.includes(config.preset)) {
819
+ console.error(
820
+ `[mushi] Unknown preset \`${String(config.preset)}\` \u2014 expected one of ${KNOWN_PRESETS.map(
821
+ (p) => `'${p}'`
822
+ ).join(", ")}. Preset ignored.`
823
+ );
824
+ }
825
+ } catch {
826
+ }
827
+ }
604
828
 
605
829
  // src/pre-filter.ts
606
830
  var DEFAULT_MIN_LENGTH = 10;
@@ -956,12 +1180,12 @@ function createOfflineQueue(config = {}) {
956
1180
  const n = row.attempts;
957
1181
  return typeof n === "number" && Number.isFinite(n) ? n : 0;
958
1182
  }
959
- function isExpired(row, now) {
1183
+ function isExpired(row, now2) {
960
1184
  const queuedAt = row.queuedAt;
961
1185
  if (!queuedAt) return false;
962
1186
  const ts = Date.parse(queuedAt);
963
1187
  if (Number.isNaN(ts)) return false;
964
- return now - ts > MAX_QUEUE_AGE_MS;
1188
+ return now2 - ts > MAX_QUEUE_AGE_MS;
965
1189
  }
966
1190
  function detectBackend() {
967
1191
  if (backendType) return backendType;
@@ -1140,10 +1364,10 @@ function createOfflineQueue(config = {}) {
1140
1364
  } else {
1141
1365
  rows = lsRead();
1142
1366
  }
1143
- const now = Date.now();
1367
+ const now2 = Date.now();
1144
1368
  const fresh = [];
1145
1369
  for (const row of rows) {
1146
- if (isExpired(row, now)) {
1370
+ if (isExpired(row, now2)) {
1147
1371
  const rowId = row.id;
1148
1372
  await removeRow(backend, rowId);
1149
1373
  queueLog.debug("Offline queue: evicting stale report", { id: rowId });
@@ -1608,6 +1832,111 @@ function randomSuffix() {
1608
1832
  return n.toString(36).padStart(6, "0").slice(0, 6);
1609
1833
  }
1610
1834
 
1835
+ // src/session-tracker.ts
1836
+ var HEARTBEAT_INTERVAL_MS = 6e4;
1837
+ var _client = null;
1838
+ var _sdkVersion;
1839
+ var _userIdHash = null;
1840
+ var _reporterTokenHash = null;
1841
+ var _pageViewCount = 0;
1842
+ var _heartbeatTimer = null;
1843
+ var _initialized = false;
1844
+ function now() {
1845
+ return (/* @__PURE__ */ new Date()).toISOString();
1846
+ }
1847
+ function currentRoute() {
1848
+ if (typeof window === "undefined") return "";
1849
+ return window.location.pathname + window.location.search;
1850
+ }
1851
+ function userAgent() {
1852
+ if (typeof navigator === "undefined") return null;
1853
+ return navigator.userAgent ?? null;
1854
+ }
1855
+ function buildPayload(kind, extra) {
1856
+ return {
1857
+ kind,
1858
+ session_id: getSessionId(),
1859
+ ts: now(),
1860
+ page_view_count: _pageViewCount,
1861
+ reporter_token_hash: _reporterTokenHash,
1862
+ user_id_hash: _userIdHash,
1863
+ user_agent: userAgent(),
1864
+ sdk_version: _sdkVersion,
1865
+ ...extra
1866
+ };
1867
+ }
1868
+ function send(payload) {
1869
+ if (!_client) return;
1870
+ _client.postSessionEvent(payload).catch(() => {
1871
+ });
1872
+ }
1873
+ function initSessionTracker(opts) {
1874
+ if (_initialized || typeof window === "undefined") return;
1875
+ _initialized = true;
1876
+ _client = opts.client;
1877
+ _sdkVersion = opts.sdkVersion;
1878
+ _reporterTokenHash = opts.reporterTokenHash ?? null;
1879
+ _userIdHash = opts.userIdHash ?? null;
1880
+ const entryRoute = currentRoute();
1881
+ _pageViewCount = 1;
1882
+ send(buildPayload("session_start", {
1883
+ route: entryRoute,
1884
+ referrer: typeof document !== "undefined" ? document.referrer ?? null : null
1885
+ }));
1886
+ _heartbeatTimer = setInterval(() => {
1887
+ send(buildPayload("session_heartbeat", { route: currentRoute() }));
1888
+ }, HEARTBEAT_INTERVAL_MS);
1889
+ const onHide = () => {
1890
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") {
1891
+ send(buildPayload("session_end", { route: currentRoute() }));
1892
+ }
1893
+ };
1894
+ if (typeof document !== "undefined") {
1895
+ document.addEventListener("visibilitychange", onHide, { passive: true });
1896
+ }
1897
+ if (typeof window !== "undefined") {
1898
+ window.addEventListener("pagehide", () => {
1899
+ send(buildPayload("session_end", { route: currentRoute() }));
1900
+ }, { passive: true });
1901
+ }
1902
+ patchHistoryForPageViews();
1903
+ }
1904
+ function trackPageView(route) {
1905
+ if (!_initialized || !_client) return;
1906
+ _pageViewCount += 1;
1907
+ send(buildPayload("page_view", { route: route ?? currentRoute() }));
1908
+ }
1909
+ function updateSessionIdentity(userIdHash) {
1910
+ _userIdHash = userIdHash;
1911
+ }
1912
+ function destroySessionTracker() {
1913
+ if (_heartbeatTimer != null) {
1914
+ clearInterval(_heartbeatTimer);
1915
+ _heartbeatTimer = null;
1916
+ }
1917
+ _initialized = false;
1918
+ _client = null;
1919
+ }
1920
+ var _historyPatched = false;
1921
+ function patchHistoryForPageViews() {
1922
+ if (_historyPatched || typeof history === "undefined") return;
1923
+ _historyPatched = true;
1924
+ const wrap = (original) => function(...args) {
1925
+ const result = original.apply(this, args);
1926
+ if (original === history.pushState) {
1927
+ _pageViewCount += 1;
1928
+ send(buildPayload("page_view", { route: currentRoute() }));
1929
+ }
1930
+ return result;
1931
+ };
1932
+ history.pushState = wrap(history.pushState);
1933
+ history.replaceState = wrap(history.replaceState);
1934
+ window.addEventListener("popstate", () => {
1935
+ _pageViewCount += 1;
1936
+ send(buildPayload("page_view", { route: currentRoute() }));
1937
+ }, { passive: true });
1938
+ }
1939
+
1611
1940
  // src/rate-limiter.ts
1612
1941
  var DEFAULT_MAX_BURST = 10;
1613
1942
  var DEFAULT_REFILL_RATE = 1;
@@ -1621,12 +1950,12 @@ function createRateLimiter(config = {}) {
1621
1950
  let tokens = maxBurst;
1622
1951
  let lastRefill = Date.now();
1623
1952
  function refill() {
1624
- const now = Date.now();
1625
- const elapsed = now - lastRefill;
1953
+ const now2 = Date.now();
1954
+ const elapsed = now2 - lastRefill;
1626
1955
  const refills = Math.floor(elapsed / refillIntervalMs);
1627
1956
  if (refills > 0) {
1628
1957
  tokens = Math.min(maxBurst, tokens + refills * refillRate);
1629
- lastRefill = now;
1958
+ lastRefill = now2;
1630
1959
  }
1631
1960
  }
1632
1961
  function tryConsume() {
@@ -1940,15 +2269,15 @@ function mushiTokens(mode) {
1940
2269
  // src/identity.ts
1941
2270
  var MUSHI_IDENTITY_TOKEN_PREFIX = "mushi.identity";
1942
2271
  function buildIdentityClaims(input) {
1943
- const now = Math.floor(Date.now() / 1e3);
2272
+ const now2 = Math.floor(Date.now() / 1e3);
1944
2273
  const ttl = input.ttlSeconds && input.ttlSeconds > 0 ? Math.min(input.ttlSeconds, 3600) : 600;
1945
2274
  return {
1946
2275
  projectId: input.projectId,
1947
2276
  sub: input.userId,
1948
2277
  ...input.email ? { email: input.email } : {},
1949
2278
  ...input.name ? { name: input.name } : {},
1950
- iat: now,
1951
- exp: now + ttl
2279
+ iat: now2,
2280
+ exp: now2 + ttl
1952
2281
  };
1953
2282
  }
1954
2283
  function base64UrlDecode(segment) {
@@ -1987,12 +2316,12 @@ var MULTI_TENANT_PARENT_HOSTS = /* @__PURE__ */ new Set(["kensaur.us", "www.kens
1987
2316
  var SLUG_DOMAIN_HINTS = {
1988
2317
  "glot-it": "glot.it",
1989
2318
  glotit: "glot.it",
1990
- "solo-boss-cloud": "sbc-front.vercel.app",
2319
+ "solo-boss-cloud": "solo-boss.kensaur.us",
1991
2320
  "mushi-mushi": "kensaur.us"
1992
2321
  };
1993
2322
  var SLUG_ICON_URL_HINTS = {
1994
2323
  "mushi-mushi": "https://kensaur.us/mushi-mushi/admin/favicon.svg",
1995
- "solo-boss-cloud": "https://sbc-front.vercel.app/favicon.svg",
2324
+ "solo-boss-cloud": "https://solo-boss.kensaur.us/favicon.svg",
1996
2325
  "glot-it": "https://kensaur.us/glot-it/apple-touch-icon.png",
1997
2326
  glotit: "https://kensaur.us/glot-it/apple-touch-icon.png"
1998
2327
  };
@@ -2206,7 +2535,10 @@ exports.createOfflineQueue = createOfflineQueue;
2206
2535
  exports.createPiiScrubber = createPiiScrubber;
2207
2536
  exports.createPreFilter = createPreFilter;
2208
2537
  exports.createRateLimiter = createRateLimiter;
2538
+ exports.destroySessionTracker = destroySessionTracker;
2539
+ exports.diagnoseEnvConfig = diagnoseEnvConfig;
2209
2540
  exports.estimateJsonBytes = estimateJsonBytes;
2541
+ exports.expandPreset = expandPreset;
2210
2542
  exports.faviconUrlCandidates = faviconUrlCandidates;
2211
2543
  exports.formatBytes = formatBytes;
2212
2544
  exports.getDeviceFingerprintHash = getDeviceFingerprintHash;
@@ -2214,6 +2546,7 @@ exports.getReporterToken = getReporterToken;
2214
2546
  exports.getSessionId = getSessionId;
2215
2547
  exports.githubRepoDomainHint = githubRepoDomainHint;
2216
2548
  exports.hmacSha256Hex = hmacSha256Hex;
2549
+ exports.initSessionTracker = initSessionTracker;
2217
2550
  exports.isLikelyGenericFavicon = isLikelyGenericFavicon;
2218
2551
  exports.isUntrustedFaviconUrl = isUntrustedFaviconUrl;
2219
2552
  exports.mushiPalette = mushiPalette;
@@ -2235,5 +2568,8 @@ exports.resolveWidgetAccent = resolveWidgetAccent;
2235
2568
  exports.safeWidgetHex = safeWidgetHex;
2236
2569
  exports.scrubPii = scrubPii;
2237
2570
  exports.sha256Hex = sha256Hex;
2571
+ exports.trackPageView = trackPageView;
2572
+ exports.updateSessionIdentity = updateSessionIdentity;
2573
+ exports.validateConfig = validateConfig;
2238
2574
  //# sourceMappingURL=index.cjs.map
2239
2575
  //# sourceMappingURL=index.cjs.map