@mushi-mushi/core 1.23.0 → 1.26.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
@@ -127,4 +127,4 @@ MIT
127
127
  <!-- mushi-readme-stats-footer -->
128
128
  ---
129
129
 
130
- <sub>Monorepo scale (June 2026): 51 edge functions · 298 SQL migrations · 13 outbound plugins · 11 inbound adapters · 19 pipeline agents. Canonical counts: <a href="https://github.com/kensaurus/mushi-mushi/blob/master/docs/stats.md">docs/stats.md</a> · <code>pnpm docs-stats</code></sub>
130
+ <sub>Monorepo scale (July 2026): 51 edge functions · 323 SQL migrations · 13 outbound plugins · 11 inbound adapters · 19 pipeline agents. Canonical counts: <a href="https://github.com/kensaurus/mushi-mushi/blob/master/docs/stats.md">docs/stats.md</a> · <code>pnpm docs-stats</code></sub>
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",
@@ -1169,12 +1180,12 @@ function createOfflineQueue(config = {}) {
1169
1180
  const n = row.attempts;
1170
1181
  return typeof n === "number" && Number.isFinite(n) ? n : 0;
1171
1182
  }
1172
- function isExpired(row, now) {
1183
+ function isExpired(row, now2) {
1173
1184
  const queuedAt = row.queuedAt;
1174
1185
  if (!queuedAt) return false;
1175
1186
  const ts = Date.parse(queuedAt);
1176
1187
  if (Number.isNaN(ts)) return false;
1177
- return now - ts > MAX_QUEUE_AGE_MS;
1188
+ return now2 - ts > MAX_QUEUE_AGE_MS;
1178
1189
  }
1179
1190
  function detectBackend() {
1180
1191
  if (backendType) return backendType;
@@ -1353,10 +1364,10 @@ function createOfflineQueue(config = {}) {
1353
1364
  } else {
1354
1365
  rows = lsRead();
1355
1366
  }
1356
- const now = Date.now();
1367
+ const now2 = Date.now();
1357
1368
  const fresh = [];
1358
1369
  for (const row of rows) {
1359
- if (isExpired(row, now)) {
1370
+ if (isExpired(row, now2)) {
1360
1371
  const rowId = row.id;
1361
1372
  await removeRow(backend, rowId);
1362
1373
  queueLog.debug("Offline queue: evicting stale report", { id: rowId });
@@ -1821,6 +1832,111 @@ function randomSuffix() {
1821
1832
  return n.toString(36).padStart(6, "0").slice(0, 6);
1822
1833
  }
1823
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
+
1824
1940
  // src/rate-limiter.ts
1825
1941
  var DEFAULT_MAX_BURST = 10;
1826
1942
  var DEFAULT_REFILL_RATE = 1;
@@ -1834,12 +1950,12 @@ function createRateLimiter(config = {}) {
1834
1950
  let tokens = maxBurst;
1835
1951
  let lastRefill = Date.now();
1836
1952
  function refill() {
1837
- const now = Date.now();
1838
- const elapsed = now - lastRefill;
1953
+ const now2 = Date.now();
1954
+ const elapsed = now2 - lastRefill;
1839
1955
  const refills = Math.floor(elapsed / refillIntervalMs);
1840
1956
  if (refills > 0) {
1841
1957
  tokens = Math.min(maxBurst, tokens + refills * refillRate);
1842
- lastRefill = now;
1958
+ lastRefill = now2;
1843
1959
  }
1844
1960
  }
1845
1961
  function tryConsume() {
@@ -2003,8 +2119,10 @@ var MUSHI_COLORS_LIGHT = {
2003
2119
  accent: "#E03C2C",
2004
2120
  accentWash: "rgba(224,60,44,0.08)",
2005
2121
  accentInk: "#7A1F15",
2006
- ok: "#16A34A",
2007
- danger: "#DC2626"
2122
+ /** Aligned with brand.tokens.json `jade` (was Tailwind green-600 — drifted). */
2123
+ ok: "#2D7A55",
2124
+ /** Aligned with brand.tokens.json `viz-danger` (was Tailwind red-600 — drifted). */
2125
+ danger: "#B83232"
2008
2126
  };
2009
2127
  var MUSHI_COLORS_DARK = {
2010
2128
  paper: "#0F0E0C",
@@ -2018,8 +2136,10 @@ var MUSHI_COLORS_DARK = {
2018
2136
  accent: "#FF5A47",
2019
2137
  accentWash: "rgba(255,90,71,0.12)",
2020
2138
  accentInk: "#FFE5E0",
2021
- ok: "#4ADE80",
2022
- danger: "#F87171"
2139
+ /** Aligned with brand.tokens.json `dark-jade`. */
2140
+ ok: "#3A9E6E",
2141
+ /** Aligned with brand.tokens.json `dark-viz-danger`. */
2142
+ danger: "#E05555"
2023
2143
  };
2024
2144
  var MUSHI_TIER_COLORS = {
2025
2145
  free: MUSHI_COLORS_LIGHT.inkMuted,
@@ -2035,6 +2155,11 @@ var MUSHI_BANNER_NEON = {
2035
2155
  };
2036
2156
  var MUSHI_ON_ACCENT = "#FAF7F0";
2037
2157
  var MUSHI_INVERSE = "#ffffff";
2158
+ var MUSHI_CONTROL_DISABLED = {
2159
+ light: "#d1d1d6",
2160
+ dark: "#3a3a3c"
2161
+ };
2162
+ var MUSHI_SHADOW_INK = "#000000";
2038
2163
  var MUSHI_ACCENT_SHADOW = {
2039
2164
  light: "#9A2A1E",
2040
2165
  dark: "#7A1F15"
@@ -2100,6 +2225,40 @@ var MUSHI_MOTION = {
2100
2225
  durationFast: 200,
2101
2226
  durationPanel: 300
2102
2227
  };
2228
+ var MUSHI_DURATION = {
2229
+ /** Fastest — tooltip show, chip hover tint */
2230
+ instant: 120,
2231
+ /** Default micro-interaction — most hover / focus transitions */
2232
+ fast: 200,
2233
+ /** Button bloom, option-row wipe */
2234
+ base: 220,
2235
+ /** Panel enter / exit */
2236
+ panel: 300,
2237
+ /** Deliberate — success stamp press, page-level transitions */
2238
+ slow: 420,
2239
+ /** Stamp ring draw, long ambient animations */
2240
+ ring: 700
2241
+ };
2242
+ var MUSHI_BORDER = {
2243
+ hairline: 1,
2244
+ regular: 1.5,
2245
+ strong: 2,
2246
+ /** Hanko stamp border (FAB inset accent stripe, stamp ring) */
2247
+ stamp: 3
2248
+ };
2249
+ var MUSHI_OPACITY = {
2250
+ disabled: 0.4,
2251
+ muted: 0.6,
2252
+ scrim: 0.72,
2253
+ overlay: 0.88
2254
+ };
2255
+ var MUSHI_LETTER_SPACING = {
2256
+ tight: "-0.01em",
2257
+ normal: "0",
2258
+ label: "0.02em",
2259
+ stamp: "0.08em",
2260
+ wide: "0.16em"
2261
+ };
2103
2262
  var MUSHI_GEOMETRY = {
2104
2263
  bannerHeight: 36,
2105
2264
  fabSize: 52,
@@ -2145,6 +2304,7 @@ function mushiTokens(mode) {
2145
2304
  type: MUSHI_TYPE,
2146
2305
  z: MUSHI_Z,
2147
2306
  motion: MUSHI_MOTION,
2307
+ duration: MUSHI_DURATION,
2148
2308
  geometry: MUSHI_GEOMETRY,
2149
2309
  copy: MUSHI_COPY
2150
2310
  };
@@ -2153,15 +2313,15 @@ function mushiTokens(mode) {
2153
2313
  // src/identity.ts
2154
2314
  var MUSHI_IDENTITY_TOKEN_PREFIX = "mushi.identity";
2155
2315
  function buildIdentityClaims(input) {
2156
- const now = Math.floor(Date.now() / 1e3);
2316
+ const now2 = Math.floor(Date.now() / 1e3);
2157
2317
  const ttl = input.ttlSeconds && input.ttlSeconds > 0 ? Math.min(input.ttlSeconds, 3600) : 600;
2158
2318
  return {
2159
2319
  projectId: input.projectId,
2160
2320
  sub: input.userId,
2161
2321
  ...input.email ? { email: input.email } : {},
2162
2322
  ...input.name ? { name: input.name } : {},
2163
- iat: now,
2164
- exp: now + ttl
2323
+ iat: now2,
2324
+ exp: now2 + ttl
2165
2325
  };
2166
2326
  }
2167
2327
  function base64UrlDecode(segment) {
@@ -2200,12 +2360,12 @@ var MULTI_TENANT_PARENT_HOSTS = /* @__PURE__ */ new Set(["kensaur.us", "www.kens
2200
2360
  var SLUG_DOMAIN_HINTS = {
2201
2361
  "glot-it": "glot.it",
2202
2362
  glotit: "glot.it",
2203
- "solo-boss-cloud": "sbc-front.vercel.app",
2363
+ "solo-boss-cloud": "solo-boss.kensaur.us",
2204
2364
  "mushi-mushi": "kensaur.us"
2205
2365
  };
2206
2366
  var SLUG_ICON_URL_HINTS = {
2207
2367
  "mushi-mushi": "https://kensaur.us/mushi-mushi/admin/favicon.svg",
2208
- "solo-boss-cloud": "https://sbc-front.vercel.app/favicon.svg",
2368
+ "solo-boss-cloud": "https://solo-boss.kensaur.us/favicon.svg",
2209
2369
  "glot-it": "https://kensaur.us/glot-it/apple-touch-icon.png",
2210
2370
  glotit: "https://kensaur.us/glot-it/apple-touch-icon.png"
2211
2371
  };
@@ -2387,21 +2547,27 @@ exports.MAX_SCREENSHOT_DATA_URL_BYTES = MAX_SCREENSHOT_DATA_URL_BYTES;
2387
2547
  exports.MUSHI_ACCENT_SHADOW = MUSHI_ACCENT_SHADOW;
2388
2548
  exports.MUSHI_BANNER_BRAND_BORDER = MUSHI_BANNER_BRAND_BORDER;
2389
2549
  exports.MUSHI_BANNER_NEON = MUSHI_BANNER_NEON;
2550
+ exports.MUSHI_BORDER = MUSHI_BORDER;
2390
2551
  exports.MUSHI_COLORS_DARK = MUSHI_COLORS_DARK;
2391
2552
  exports.MUSHI_COLORS_LIGHT = MUSHI_COLORS_LIGHT;
2553
+ exports.MUSHI_CONTROL_DISABLED = MUSHI_CONTROL_DISABLED;
2392
2554
  exports.MUSHI_COPY = MUSHI_COPY;
2555
+ exports.MUSHI_DURATION = MUSHI_DURATION;
2393
2556
  exports.MUSHI_GEOMETRY = MUSHI_GEOMETRY;
2394
2557
  exports.MUSHI_IDENTITY_TOKEN_PREFIX = MUSHI_IDENTITY_TOKEN_PREFIX;
2395
2558
  exports.MUSHI_INTERNAL_HEADER = MUSHI_INTERNAL_HEADER;
2396
2559
  exports.MUSHI_INTERNAL_INIT_MARKER = MUSHI_INTERNAL_INIT_MARKER;
2397
2560
  exports.MUSHI_INVERSE = MUSHI_INVERSE;
2561
+ exports.MUSHI_LETTER_SPACING = MUSHI_LETTER_SPACING;
2398
2562
  exports.MUSHI_MOTION = MUSHI_MOTION;
2399
2563
  exports.MUSHI_ON_ACCENT = MUSHI_ON_ACCENT;
2564
+ exports.MUSHI_OPACITY = MUSHI_OPACITY;
2400
2565
  exports.MUSHI_PROJECT_HEADER = MUSHI_PROJECT_HEADER;
2401
2566
  exports.MUSHI_RADIUS = MUSHI_RADIUS;
2402
2567
  exports.MUSHI_REPORTER_STATUS = MUSHI_REPORTER_STATUS;
2403
2568
  exports.MUSHI_SDK_PACKAGE_HEADER = MUSHI_SDK_PACKAGE_HEADER;
2404
2569
  exports.MUSHI_SDK_VERSION_HEADER = MUSHI_SDK_VERSION_HEADER;
2570
+ exports.MUSHI_SHADOW_INK = MUSHI_SHADOW_INK;
2405
2571
  exports.MUSHI_SPACING = MUSHI_SPACING;
2406
2572
  exports.MUSHI_TIER_COLORS = MUSHI_TIER_COLORS;
2407
2573
  exports.MUSHI_TYPE = MUSHI_TYPE;
@@ -2419,6 +2585,7 @@ exports.createOfflineQueue = createOfflineQueue;
2419
2585
  exports.createPiiScrubber = createPiiScrubber;
2420
2586
  exports.createPreFilter = createPreFilter;
2421
2587
  exports.createRateLimiter = createRateLimiter;
2588
+ exports.destroySessionTracker = destroySessionTracker;
2422
2589
  exports.diagnoseEnvConfig = diagnoseEnvConfig;
2423
2590
  exports.estimateJsonBytes = estimateJsonBytes;
2424
2591
  exports.expandPreset = expandPreset;
@@ -2429,6 +2596,7 @@ exports.getReporterToken = getReporterToken;
2429
2596
  exports.getSessionId = getSessionId;
2430
2597
  exports.githubRepoDomainHint = githubRepoDomainHint;
2431
2598
  exports.hmacSha256Hex = hmacSha256Hex;
2599
+ exports.initSessionTracker = initSessionTracker;
2432
2600
  exports.isLikelyGenericFavicon = isLikelyGenericFavicon;
2433
2601
  exports.isUntrustedFaviconUrl = isUntrustedFaviconUrl;
2434
2602
  exports.mushiPalette = mushiPalette;
@@ -2450,6 +2618,8 @@ exports.resolveWidgetAccent = resolveWidgetAccent;
2450
2618
  exports.safeWidgetHex = safeWidgetHex;
2451
2619
  exports.scrubPii = scrubPii;
2452
2620
  exports.sha256Hex = sha256Hex;
2621
+ exports.trackPageView = trackPageView;
2622
+ exports.updateSessionIdentity = updateSessionIdentity;
2453
2623
  exports.validateConfig = validateConfig;
2454
2624
  //# sourceMappingURL=index.cjs.map
2455
2625
  //# sourceMappingURL=index.cjs.map