@skillsmith/mcp-server 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/__tests__/recommend-online-path.test.js +2 -0
  4. package/dist/src/__tests__/recommend-online-path.test.js.map +1 -1
  5. package/dist/src/__tests__/recommend.test.js +10 -2
  6. package/dist/src/__tests__/recommend.test.js.map +1 -1
  7. package/dist/src/__tests__/search-installable.test.js +5 -0
  8. package/dist/src/__tests__/search-installable.test.js.map +1 -1
  9. package/dist/src/__tests__/search-online-path.test.js +1 -0
  10. package/dist/src/__tests__/search-online-path.test.js.map +1 -1
  11. package/dist/src/__tests__/search.test.js +3 -0
  12. package/dist/src/__tests__/search.test.js.map +1 -1
  13. package/dist/src/index.js +9 -50
  14. package/dist/src/index.js.map +1 -1
  15. package/dist/src/middleware/license.gate.d.ts +2 -2
  16. package/dist/src/middleware/license.gate.d.ts.map +1 -1
  17. package/dist/src/middleware/license.gate.js +43 -1
  18. package/dist/src/middleware/license.gate.js.map +1 -1
  19. package/dist/src/middleware/telemetry-consent.d.ts +88 -0
  20. package/dist/src/middleware/telemetry-consent.d.ts.map +1 -0
  21. package/dist/src/middleware/telemetry-consent.js +177 -0
  22. package/dist/src/middleware/telemetry-consent.js.map +1 -0
  23. package/dist/src/middleware/telemetry-consent.test.d.ts +12 -0
  24. package/dist/src/middleware/telemetry-consent.test.d.ts.map +1 -0
  25. package/dist/src/middleware/telemetry-consent.test.js +291 -0
  26. package/dist/src/middleware/telemetry-consent.test.js.map +1 -0
  27. package/dist/src/tools/__meta__/telemetry-coverage.test.d.ts +25 -0
  28. package/dist/src/tools/__meta__/telemetry-coverage.test.d.ts.map +1 -0
  29. package/dist/src/tools/__meta__/telemetry-coverage.test.js +210 -0
  30. package/dist/src/tools/__meta__/telemetry-coverage.test.js.map +1 -0
  31. package/dist/src/tools/analytics.d.ts +15 -28
  32. package/dist/src/tools/analytics.d.ts.map +1 -1
  33. package/dist/src/tools/analytics.js +26 -4
  34. package/dist/src/tools/analytics.js.map +1 -1
  35. package/dist/src/tools/analytics.supabase.service.d.ts +103 -0
  36. package/dist/src/tools/analytics.supabase.service.d.ts.map +1 -0
  37. package/dist/src/tools/analytics.supabase.service.js +171 -0
  38. package/dist/src/tools/analytics.supabase.service.js.map +1 -0
  39. package/dist/src/tools/analytics.supabase.service.test.d.ts +10 -0
  40. package/dist/src/tools/analytics.supabase.service.test.d.ts.map +1 -0
  41. package/dist/src/tools/analytics.supabase.service.test.js +375 -0
  42. package/dist/src/tools/analytics.supabase.service.test.js.map +1 -0
  43. package/dist/src/tools/analyze.d.ts +6 -19
  44. package/dist/src/tools/analyze.d.ts.map +1 -1
  45. package/dist/src/tools/analyze.js +8 -1
  46. package/dist/src/tools/analyze.js.map +1 -1
  47. package/dist/src/tools/apply-namespace-rename.d.ts +1 -8
  48. package/dist/src/tools/apply-namespace-rename.d.ts.map +1 -1
  49. package/dist/src/tools/apply-namespace-rename.js +8 -1
  50. package/dist/src/tools/apply-namespace-rename.js.map +1 -1
  51. package/dist/src/tools/apply-recommended-edit.d.ts +1 -6
  52. package/dist/src/tools/apply-recommended-edit.d.ts.map +1 -1
  53. package/dist/src/tools/apply-recommended-edit.js +8 -1
  54. package/dist/src/tools/apply-recommended-edit.js.map +1 -1
  55. package/dist/src/tools/audit-tools.d.ts +20 -3
  56. package/dist/src/tools/audit-tools.d.ts.map +1 -1
  57. package/dist/src/tools/audit-tools.js +20 -3
  58. package/dist/src/tools/audit-tools.js.map +1 -1
  59. package/dist/src/tools/compare.d.ts +5 -21
  60. package/dist/src/tools/compare.d.ts.map +1 -1
  61. package/dist/src/tools/compare.js +8 -1
  62. package/dist/src/tools/compare.js.map +1 -1
  63. package/dist/src/tools/compliance-tools.d.ts +5 -1
  64. package/dist/src/tools/compliance-tools.d.ts.map +1 -1
  65. package/dist/src/tools/compliance-tools.js +8 -1
  66. package/dist/src/tools/compliance-tools.js.map +1 -1
  67. package/dist/src/tools/get-skill.d.ts +1 -20
  68. package/dist/src/tools/get-skill.d.ts.map +1 -1
  69. package/dist/src/tools/get-skill.js +8 -1
  70. package/dist/src/tools/get-skill.js.map +1 -1
  71. package/dist/src/tools/index-local.d.ts +1 -15
  72. package/dist/src/tools/index-local.d.ts.map +1 -1
  73. package/dist/src/tools/index-local.js +8 -1
  74. package/dist/src/tools/index-local.js.map +1 -1
  75. package/dist/src/tools/install.d.ts +1 -17
  76. package/dist/src/tools/install.d.ts.map +1 -1
  77. package/dist/src/tools/install.js +8 -1
  78. package/dist/src/tools/install.js.map +1 -1
  79. package/dist/src/tools/integration-tools.d.ts +14 -2
  80. package/dist/src/tools/integration-tools.d.ts.map +1 -1
  81. package/dist/src/tools/integration-tools.js +14 -2
  82. package/dist/src/tools/integration-tools.js.map +1 -1
  83. package/dist/src/tools/outdated.d.ts +3 -14
  84. package/dist/src/tools/outdated.d.ts.map +1 -1
  85. package/dist/src/tools/outdated.js +8 -1
  86. package/dist/src/tools/outdated.js.map +1 -1
  87. package/dist/src/tools/outdated.test.js +3 -0
  88. package/dist/src/tools/outdated.test.js.map +1 -1
  89. package/dist/src/tools/publish-private.d.ts +4 -7
  90. package/dist/src/tools/publish-private.d.ts.map +1 -1
  91. package/dist/src/tools/publish-private.js +8 -1
  92. package/dist/src/tools/publish-private.js.map +1 -1
  93. package/dist/src/tools/publish.d.ts +9 -11
  94. package/dist/src/tools/publish.d.ts.map +1 -1
  95. package/dist/src/tools/publish.js +8 -1
  96. package/dist/src/tools/publish.js.map +1 -1
  97. package/dist/src/tools/rbac-tools.d.ts +20 -3
  98. package/dist/src/tools/rbac-tools.d.ts.map +1 -1
  99. package/dist/src/tools/rbac-tools.js +20 -3
  100. package/dist/src/tools/rbac-tools.js.map +1 -1
  101. package/dist/src/tools/rbac-tools.test.js +2 -0
  102. package/dist/src/tools/rbac-tools.test.js.map +1 -1
  103. package/dist/src/tools/recommend.d.ts +9 -9
  104. package/dist/src/tools/recommend.d.ts.map +1 -1
  105. package/dist/src/tools/recommend.js +8 -1
  106. package/dist/src/tools/recommend.js.map +1 -1
  107. package/dist/src/tools/registry-tools.d.ts +10 -8
  108. package/dist/src/tools/registry-tools.d.ts.map +1 -1
  109. package/dist/src/tools/registry-tools.js +14 -2
  110. package/dist/src/tools/registry-tools.js.map +1 -1
  111. package/dist/src/tools/search.d.ts +3 -42
  112. package/dist/src/tools/search.d.ts.map +1 -1
  113. package/dist/src/tools/search.js +10 -24
  114. package/dist/src/tools/search.js.map +1 -1
  115. package/dist/src/tools/skill-audit.d.ts +3 -13
  116. package/dist/src/tools/skill-audit.d.ts.map +1 -1
  117. package/dist/src/tools/skill-audit.js +8 -1
  118. package/dist/src/tools/skill-audit.js.map +1 -1
  119. package/dist/src/tools/skill-diff.d.ts +9 -11
  120. package/dist/src/tools/skill-diff.d.ts.map +1 -1
  121. package/dist/src/tools/skill-diff.js +8 -1
  122. package/dist/src/tools/skill-diff.js.map +1 -1
  123. package/dist/src/tools/skill-inventory-audit.d.ts +1 -6
  124. package/dist/src/tools/skill-inventory-audit.d.ts.map +1 -1
  125. package/dist/src/tools/skill-inventory-audit.js +8 -1
  126. package/dist/src/tools/skill-inventory-audit.js.map +1 -1
  127. package/dist/src/tools/skill-pack-audit.d.ts +4 -12
  128. package/dist/src/tools/skill-pack-audit.d.ts.map +1 -1
  129. package/dist/src/tools/skill-pack-audit.js +8 -1
  130. package/dist/src/tools/skill-pack-audit.js.map +1 -1
  131. package/dist/src/tools/skill-rescan.d.ts +3 -11
  132. package/dist/src/tools/skill-rescan.d.ts.map +1 -1
  133. package/dist/src/tools/skill-rescan.js +8 -1
  134. package/dist/src/tools/skill-rescan.js.map +1 -1
  135. package/dist/src/tools/skill-rescan.test.js +2 -0
  136. package/dist/src/tools/skill-rescan.test.js.map +1 -1
  137. package/dist/src/tools/skill-updates.d.ts +3 -12
  138. package/dist/src/tools/skill-updates.d.ts.map +1 -1
  139. package/dist/src/tools/skill-updates.js +8 -1
  140. package/dist/src/tools/skill-updates.js.map +1 -1
  141. package/dist/src/tools/sso-tools.d.ts +9 -8
  142. package/dist/src/tools/sso-tools.d.ts.map +1 -1
  143. package/dist/src/tools/sso-tools.js +14 -2
  144. package/dist/src/tools/sso-tools.js.map +1 -1
  145. package/dist/src/tools/suggest.d.ts +9 -17
  146. package/dist/src/tools/suggest.d.ts.map +1 -1
  147. package/dist/src/tools/suggest.js +8 -1
  148. package/dist/src/tools/suggest.js.map +1 -1
  149. package/dist/src/tools/team-workspace.d.ts +11 -16
  150. package/dist/src/tools/team-workspace.d.ts.map +1 -1
  151. package/dist/src/tools/team-workspace.js +14 -2
  152. package/dist/src/tools/team-workspace.js.map +1 -1
  153. package/dist/src/tools/uninstall.d.ts +4 -10
  154. package/dist/src/tools/uninstall.d.ts.map +1 -1
  155. package/dist/src/tools/uninstall.js +8 -1
  156. package/dist/src/tools/uninstall.js.map +1 -1
  157. package/dist/src/tools/validate.d.ts +5 -23
  158. package/dist/src/tools/validate.d.ts.map +1 -1
  159. package/dist/src/tools/validate.js +8 -1
  160. package/dist/src/tools/validate.js.map +1 -1
  161. package/dist/src/validation.d.ts +2 -2
  162. package/dist/src/validation.d.ts.map +1 -1
  163. package/dist/src/validation.js +6 -0
  164. package/dist/src/validation.js.map +1 -1
  165. package/dist/src/webhooks/stripe-webhook-endpoint.d.ts +24 -14
  166. package/dist/src/webhooks/stripe-webhook-endpoint.d.ts.map +1 -1
  167. package/dist/src/webhooks/stripe-webhook-endpoint.js.map +1 -1
  168. package/dist/tests/compare.test.js +3 -0
  169. package/dist/tests/compare.test.js.map +1 -1
  170. package/dist/tests/e2e/compare.e2e.test.js +3 -1
  171. package/dist/tests/e2e/compare.e2e.test.js.map +1 -1
  172. package/dist/tests/e2e/recommend.e2e.test.js +6 -1
  173. package/dist/tests/e2e/recommend.e2e.test.js.map +1 -1
  174. package/dist/tests/e2e/skill-flow.e2e.test.js +8 -0
  175. package/dist/tests/e2e/skill-flow.e2e.test.js.map +1 -1
  176. package/dist/tests/e2e/suggest.e2e.test.js +5 -1
  177. package/dist/tests/e2e/suggest.e2e.test.js.map +1 -1
  178. package/dist/tests/integration/analyze.integration.test.js +7 -0
  179. package/dist/tests/integration/analyze.integration.test.js.map +1 -1
  180. package/dist/tests/integration/recommend.integration.test.js +6 -0
  181. package/dist/tests/integration/recommend.integration.test.js.map +1 -1
  182. package/dist/tests/integration/validate.integration.test.js +7 -1
  183. package/dist/tests/integration/validate.integration.test.js.map +1 -1
  184. package/dist/tests/performance/search-performance.test.js +9 -1
  185. package/dist/tests/performance/search-performance.test.js.map +1 -1
  186. package/dist/tests/recommend.test.js +11 -2
  187. package/dist/tests/recommend.test.js.map +1 -1
  188. package/dist/tests/startup-probe.test.js +9 -4
  189. package/dist/tests/startup-probe.test.js.map +1 -1
  190. package/dist/tests/unit/skill-pack-audit.test.js +14 -1
  191. package/dist/tests/unit/skill-pack-audit.test.js.map +1 -1
  192. package/dist/tests/validate.test.js +28 -8
  193. package/dist/tests/validate.test.js.map +1 -1
  194. package/package.json +2 -2
  195. package/server.json +2 -2
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Telemetry Consent Gate — SMI-5019 W2.S4
3
+ *
4
+ * For MCP-only clients (Cursor, Continue, Copilot users without a CLI install)
5
+ * we cannot rely on a CLI first-run prompt or a VS Code toast. Per user
6
+ * decision U5 in the implementation plan, the consent surface is the web
7
+ * dashboard at https://skillsmith.app/account/telemetry.
8
+ *
9
+ * This module supplies the MCP-side half of that flow:
10
+ *
11
+ * 1. On every tool call, resolve the calling anonymous_id's preference from
12
+ * `user_telemetry_preferences` (RLS-scoped via the same anon-key client
13
+ * used elsewhere in this package).
14
+ * 2. If the row is missing, signal `consent_required:true` + the privacy URL
15
+ * in the response envelope so the client can prompt the user to open the
16
+ * dashboard.
17
+ * 3. Cache the resolved state per process (Map keyed by anonymous_id) so
18
+ * repeated calls within a session don't re-query, and so two parallel
19
+ * calls from the same unrecognized anonymous_id observe identical state.
20
+ * 4. Suppress telemetry writes (consult `shouldEmitTelemetry`) for that
21
+ * anonymous_id until the preference resolves to `enabled:true`.
22
+ *
23
+ * SMI-5016 (`packages/core/src/telemetry/wrap.ts`) and SMI-5017 (tool /
24
+ * command dispatchers) are wave-sibling deliverables — this module
25
+ * deliberately stays out of those files.
26
+ */
27
+ /**
28
+ * Canonical absolute URL of the consent dashboard. Must remain stable across
29
+ * surfaces so MCP clients can deep-link to a known landing page.
30
+ */
31
+ export declare const TELEMETRY_PRIVACY_URL = "https://skillsmith.app/account/telemetry";
32
+ /**
33
+ * Result of resolving the consent state for a given anonymous_id.
34
+ */
35
+ export interface ConsentState {
36
+ /** True iff a preference row was found AND `enabled = true`. */
37
+ enabled: boolean;
38
+ /**
39
+ * True iff there is no row for this anonymous_id yet (the user hasn't
40
+ * visited the consent page). Surface this in the response envelope so the
41
+ * client can prompt the user.
42
+ */
43
+ consentRequired: boolean;
44
+ /** Stable URL to direct the user to when `consentRequired` is true. */
45
+ privacyUrl: string;
46
+ }
47
+ /**
48
+ * Resolve the consent state for `anonymousId`, caching the result for the
49
+ * lifetime of the process. Concurrent calls share one in-flight query.
50
+ *
51
+ * Passing `null`/`undefined`/empty triggers the no-id branch — telemetry is
52
+ * suppressed but no prompt is shown (there's nothing to link the user's
53
+ * eventual web-dashboard choice back to).
54
+ */
55
+ export declare function resolveConsent(anonymousId: string | null | undefined): Promise<ConsentState>;
56
+ /**
57
+ * Convenience: true iff telemetry may be emitted for this anonymous_id.
58
+ * Wraps `resolveConsent` for callers that only need the boolean.
59
+ */
60
+ export declare function shouldEmitTelemetry(anonymousId: string | null | undefined): Promise<boolean>;
61
+ /**
62
+ * Invalidate the cache entry for `anonymousId`. Called by the consent page
63
+ * after a successful save would, in a future iteration, ping an MCP refresh
64
+ * endpoint — for now this is exposed for tests and for the explicit
65
+ * resync-on-rotate UI in the consent page.
66
+ */
67
+ export declare function invalidateConsentCache(anonymousId?: string): void;
68
+ /**
69
+ * Augment an existing MCP tool response with a `consent_required` envelope
70
+ * when the user has not yet visited the consent page.
71
+ *
72
+ * The MCP `CallToolResult` shape is `{ content: [{ type: 'text', text: <json> }] }`.
73
+ * We parse `text`, splice in the consent fields, and re-serialize. If parsing
74
+ * fails for any reason (binary content, malformed payload), we return the
75
+ * response untouched — telemetry consent is a soft signal and must never
76
+ * corrupt a successful tool result.
77
+ *
78
+ * Idempotent: calling this twice on the same response is a no-op once the
79
+ * fields are already present.
80
+ */
81
+ export declare function annotateResponseWithConsent<T extends {
82
+ content?: unknown;
83
+ }>(response: T, consent: ConsentState): T;
84
+ /**
85
+ * Test-only helper. Not exported from the package index.
86
+ */
87
+ export declare function _resetConsentCacheForTests(): void;
88
+ //# sourceMappingURL=telemetry-consent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-consent.d.ts","sourceRoot":"","sources":["../../../src/middleware/telemetry-consent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH;;;GAGG;AACH,eAAO,MAAM,qBAAqB,6CAA6C,CAAA;AAE/E;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,gEAAgE;IAChE,OAAO,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,eAAe,EAAE,OAAO,CAAA;IACxB,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAA;CACnB;AA6ED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,CAS5F;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACrC,OAAO,CAAC,OAAO,CAAC,CAIlB;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAMjE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,SAAS;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,EACzE,QAAQ,EAAE,CAAC,EACX,OAAO,EAAE,YAAY,GACpB,CAAC,CA8BH;AAED;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD"}
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Telemetry Consent Gate — SMI-5019 W2.S4
3
+ *
4
+ * For MCP-only clients (Cursor, Continue, Copilot users without a CLI install)
5
+ * we cannot rely on a CLI first-run prompt or a VS Code toast. Per user
6
+ * decision U5 in the implementation plan, the consent surface is the web
7
+ * dashboard at https://skillsmith.app/account/telemetry.
8
+ *
9
+ * This module supplies the MCP-side half of that flow:
10
+ *
11
+ * 1. On every tool call, resolve the calling anonymous_id's preference from
12
+ * `user_telemetry_preferences` (RLS-scoped via the same anon-key client
13
+ * used elsewhere in this package).
14
+ * 2. If the row is missing, signal `consent_required:true` + the privacy URL
15
+ * in the response envelope so the client can prompt the user to open the
16
+ * dashboard.
17
+ * 3. Cache the resolved state per process (Map keyed by anonymous_id) so
18
+ * repeated calls within a session don't re-query, and so two parallel
19
+ * calls from the same unrecognized anonymous_id observe identical state.
20
+ * 4. Suppress telemetry writes (consult `shouldEmitTelemetry`) for that
21
+ * anonymous_id until the preference resolves to `enabled:true`.
22
+ *
23
+ * SMI-5016 (`packages/core/src/telemetry/wrap.ts`) and SMI-5017 (tool /
24
+ * command dispatchers) are wave-sibling deliverables — this module
25
+ * deliberately stays out of those files.
26
+ */
27
+ import { getSupabaseClient } from '../supabase-client.js';
28
+ /**
29
+ * Canonical absolute URL of the consent dashboard. Must remain stable across
30
+ * surfaces so MCP clients can deep-link to a known landing page.
31
+ */
32
+ export const TELEMETRY_PRIVACY_URL = 'https://skillsmith.app/account/telemetry';
33
+ /**
34
+ * Per-process cache keyed by anonymous_id. We deliberately use a single shared
35
+ * Map so two parallel `withConsentGate` invocations for the same
36
+ * anonymous_id observe identical state — the constraint flagged in the spec.
37
+ *
38
+ * Stored value is a Promise (not the resolved ConsentState) so concurrent
39
+ * lookups share one in-flight Supabase query.
40
+ */
41
+ const consentCache = new Map();
42
+ const DEFAULT_CONSENT_REQUIRED = {
43
+ enabled: false,
44
+ consentRequired: true,
45
+ privacyUrl: TELEMETRY_PRIVACY_URL,
46
+ };
47
+ const DEFAULT_NO_ID = {
48
+ enabled: false,
49
+ consentRequired: false,
50
+ privacyUrl: TELEMETRY_PRIVACY_URL,
51
+ };
52
+ /**
53
+ * Look up the consent row for `anonymousId` and translate it into a
54
+ * `ConsentState`. Falls back to "consent required" on any error so we never
55
+ * silently emit telemetry from an unknown identity.
56
+ */
57
+ async function fetchConsentState(anonymousId) {
58
+ let client;
59
+ try {
60
+ client = (await getSupabaseClient());
61
+ }
62
+ catch {
63
+ // Supabase isn't configured in this environment (e.g. offline CLI run);
64
+ // safest interpretation is "no consent → no emit" but also "no need to
65
+ // prompt the user" because the network surface isn't reachable anyway.
66
+ return { ...DEFAULT_NO_ID };
67
+ }
68
+ try {
69
+ const { data, error } = await client
70
+ .from('user_telemetry_preferences')
71
+ .select('enabled')
72
+ .eq('anonymous_id', anonymousId)
73
+ .maybeSingle();
74
+ if (error || !data) {
75
+ return { ...DEFAULT_CONSENT_REQUIRED };
76
+ }
77
+ return {
78
+ enabled: data.enabled === true,
79
+ consentRequired: false,
80
+ privacyUrl: TELEMETRY_PRIVACY_URL,
81
+ };
82
+ }
83
+ catch {
84
+ return { ...DEFAULT_CONSENT_REQUIRED };
85
+ }
86
+ }
87
+ /**
88
+ * Resolve the consent state for `anonymousId`, caching the result for the
89
+ * lifetime of the process. Concurrent calls share one in-flight query.
90
+ *
91
+ * Passing `null`/`undefined`/empty triggers the no-id branch — telemetry is
92
+ * suppressed but no prompt is shown (there's nothing to link the user's
93
+ * eventual web-dashboard choice back to).
94
+ */
95
+ export function resolveConsent(anonymousId) {
96
+ if (!anonymousId) {
97
+ return Promise.resolve({ ...DEFAULT_NO_ID });
98
+ }
99
+ const cached = consentCache.get(anonymousId);
100
+ if (cached)
101
+ return cached;
102
+ const promise = fetchConsentState(anonymousId);
103
+ consentCache.set(anonymousId, promise);
104
+ return promise;
105
+ }
106
+ /**
107
+ * Convenience: true iff telemetry may be emitted for this anonymous_id.
108
+ * Wraps `resolveConsent` for callers that only need the boolean.
109
+ */
110
+ export async function shouldEmitTelemetry(anonymousId) {
111
+ if (!anonymousId)
112
+ return false;
113
+ const state = await resolveConsent(anonymousId);
114
+ return state.enabled;
115
+ }
116
+ /**
117
+ * Invalidate the cache entry for `anonymousId`. Called by the consent page
118
+ * after a successful save would, in a future iteration, ping an MCP refresh
119
+ * endpoint — for now this is exposed for tests and for the explicit
120
+ * resync-on-rotate UI in the consent page.
121
+ */
122
+ export function invalidateConsentCache(anonymousId) {
123
+ if (anonymousId === undefined) {
124
+ consentCache.clear();
125
+ return;
126
+ }
127
+ consentCache.delete(anonymousId);
128
+ }
129
+ /**
130
+ * Augment an existing MCP tool response with a `consent_required` envelope
131
+ * when the user has not yet visited the consent page.
132
+ *
133
+ * The MCP `CallToolResult` shape is `{ content: [{ type: 'text', text: <json> }] }`.
134
+ * We parse `text`, splice in the consent fields, and re-serialize. If parsing
135
+ * fails for any reason (binary content, malformed payload), we return the
136
+ * response untouched — telemetry consent is a soft signal and must never
137
+ * corrupt a successful tool result.
138
+ *
139
+ * Idempotent: calling this twice on the same response is a no-op once the
140
+ * fields are already present.
141
+ */
142
+ export function annotateResponseWithConsent(response, consent) {
143
+ if (!consent.consentRequired)
144
+ return response;
145
+ const content = response.content;
146
+ if (!Array.isArray(content) || content.length === 0)
147
+ return response;
148
+ const first = content[0];
149
+ if (!first || first.type !== 'text' || typeof first.text !== 'string')
150
+ return response;
151
+ let parsed;
152
+ try {
153
+ parsed = JSON.parse(first.text);
154
+ }
155
+ catch {
156
+ return response;
157
+ }
158
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
159
+ return response;
160
+ }
161
+ const annotated = parsed;
162
+ // Idempotency: if the caller has already added these, leave them alone.
163
+ if ('consent_required' in annotated && 'privacy_url' in annotated)
164
+ return response;
165
+ annotated.consent_required = true;
166
+ annotated.privacy_url = consent.privacyUrl;
167
+ const nextContent = [...content];
168
+ nextContent[0] = { ...first, text: JSON.stringify(annotated, null, 2) };
169
+ return { ...response, content: nextContent };
170
+ }
171
+ /**
172
+ * Test-only helper. Not exported from the package index.
173
+ */
174
+ export function _resetConsentCacheForTests() {
175
+ consentCache.clear();
176
+ }
177
+ //# sourceMappingURL=telemetry-consent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-consent.js","sourceRoot":"","sources":["../../../src/middleware/telemetry-consent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @fileoverview Tests for the telemetry consent gate — SMI-5019 W2
3
+ *
4
+ * Mocking style matches analytics.supabase.service.test.ts:
5
+ * vi.mock('../supabase-client.js') at module scope, then
6
+ * vi.mocked(getSupabaseClient).mockResolvedValue / mockRejectedValue per test.
7
+ *
8
+ * `_resetConsentCacheForTests()` is called in beforeEach so every test starts
9
+ * with an empty process-level cache.
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=telemetry-consent.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-consent.test.d.ts","sourceRoot":"","sources":["../../../src/middleware/telemetry-consent.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,291 @@
1
+ /**
2
+ * @fileoverview Tests for the telemetry consent gate — SMI-5019 W2
3
+ *
4
+ * Mocking style matches analytics.supabase.service.test.ts:
5
+ * vi.mock('../supabase-client.js') at module scope, then
6
+ * vi.mocked(getSupabaseClient).mockResolvedValue / mockRejectedValue per test.
7
+ *
8
+ * `_resetConsentCacheForTests()` is called in beforeEach so every test starts
9
+ * with an empty process-level cache.
10
+ */
11
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
12
+ import { resolveConsent, shouldEmitTelemetry, invalidateConsentCache, annotateResponseWithConsent, _resetConsentCacheForTests, TELEMETRY_PRIVACY_URL, } from './telemetry-consent.js';
13
+ // ============================================================================
14
+ // Supabase module mock
15
+ // ============================================================================
16
+ vi.mock('../supabase-client.js', () => ({
17
+ getSupabaseClient: vi.fn(),
18
+ }));
19
+ import { getSupabaseClient } from '../supabase-client.js';
20
+ const mockGetClient = vi.mocked(getSupabaseClient);
21
+ // ============================================================================
22
+ // Helper — build a chainable Supabase query mock
23
+ // ============================================================================
24
+ /**
25
+ * Creates a mock Supabase client whose `.from().select().eq().maybeSingle()`
26
+ * chain resolves with `resolvedValue`. Returns the `maybeSingle` spy so
27
+ * callers can assert call counts.
28
+ */
29
+ function createQueryMock(resolvedValue) {
30
+ const maybeSingle = vi.fn().mockResolvedValue(resolvedValue);
31
+ const eq = vi.fn().mockReturnValue({ maybeSingle });
32
+ const select = vi.fn().mockReturnValue({ eq });
33
+ const from = vi.fn().mockReturnValue({ select });
34
+ const client = { from };
35
+ return { client, from, select, eq, maybeSingle };
36
+ }
37
+ // ============================================================================
38
+ // Setup
39
+ // ============================================================================
40
+ beforeEach(() => {
41
+ _resetConsentCacheForTests();
42
+ vi.clearAllMocks();
43
+ });
44
+ afterEach(() => {
45
+ _resetConsentCacheForTests();
46
+ });
47
+ // ============================================================================
48
+ // (1) Default-no-id: empty / null / undefined anonymous_id
49
+ // ============================================================================
50
+ describe('resolveConsent — default-no-id branch', () => {
51
+ it('returns DEFAULT_NO_ID for empty string without querying Supabase', async () => {
52
+ const state = await resolveConsent('');
53
+ expect(state.enabled).toBe(false);
54
+ expect(state.consentRequired).toBe(false);
55
+ expect(state.privacyUrl).toBe(TELEMETRY_PRIVACY_URL);
56
+ expect(mockGetClient).not.toHaveBeenCalled();
57
+ });
58
+ it('returns DEFAULT_NO_ID for null without querying Supabase', async () => {
59
+ const state = await resolveConsent(null);
60
+ expect(state.enabled).toBe(false);
61
+ expect(state.consentRequired).toBe(false);
62
+ expect(mockGetClient).not.toHaveBeenCalled();
63
+ });
64
+ it('returns DEFAULT_NO_ID for undefined without querying Supabase', async () => {
65
+ const state = await resolveConsent(undefined);
66
+ expect(state.enabled).toBe(false);
67
+ expect(state.consentRequired).toBe(false);
68
+ expect(mockGetClient).not.toHaveBeenCalled();
69
+ });
70
+ it('shouldEmitTelemetry returns false for empty string', async () => {
71
+ expect(await shouldEmitTelemetry('')).toBe(false);
72
+ expect(mockGetClient).not.toHaveBeenCalled();
73
+ });
74
+ });
75
+ // ============================================================================
76
+ // (2) Unknown anonymous_id — no row in DB
77
+ // ============================================================================
78
+ describe('resolveConsent — unknown anonymous_id (no row)', () => {
79
+ it('returns consent_required: true when Supabase returns no row', async () => {
80
+ const { client } = createQueryMock({ data: null, error: null });
81
+ mockGetClient.mockResolvedValue(client);
82
+ const state = await resolveConsent('unknown-xyz');
83
+ expect(state.consentRequired).toBe(true);
84
+ expect(state.enabled).toBe(false);
85
+ expect(state.privacyUrl).toBe(TELEMETRY_PRIVACY_URL);
86
+ });
87
+ it('shouldEmitTelemetry returns false for unknown anonymous_id', async () => {
88
+ const { client } = createQueryMock({ data: null, error: null });
89
+ mockGetClient.mockResolvedValue(client);
90
+ expect(await shouldEmitTelemetry('unknown-xyz')).toBe(false);
91
+ });
92
+ });
93
+ // ============================================================================
94
+ // (3) Enabled preference
95
+ // ============================================================================
96
+ describe('resolveConsent — enabled preference', () => {
97
+ it('returns consentRequired: false and enabled: true when row has enabled=true', async () => {
98
+ const { client } = createQueryMock({ data: { enabled: true }, error: null });
99
+ mockGetClient.mockResolvedValue(client);
100
+ const state = await resolveConsent('user-abc');
101
+ expect(state.enabled).toBe(true);
102
+ expect(state.consentRequired).toBe(false);
103
+ expect(state.privacyUrl).toBe(TELEMETRY_PRIVACY_URL);
104
+ });
105
+ it('shouldEmitTelemetry returns true when preference is enabled', async () => {
106
+ const { client } = createQueryMock({ data: { enabled: true }, error: null });
107
+ mockGetClient.mockResolvedValue(client);
108
+ expect(await shouldEmitTelemetry('user-abc')).toBe(true);
109
+ });
110
+ });
111
+ // ============================================================================
112
+ // (4) Disabled preference
113
+ // ============================================================================
114
+ describe('resolveConsent — disabled preference', () => {
115
+ it('returns consentRequired: false and enabled: false when row has enabled=false', async () => {
116
+ const { client } = createQueryMock({ data: { enabled: false }, error: null });
117
+ mockGetClient.mockResolvedValue(client);
118
+ const state = await resolveConsent('user-abc');
119
+ // User has answered — no prompt needed, but telemetry is off.
120
+ expect(state.consentRequired).toBe(false);
121
+ expect(state.enabled).toBe(false);
122
+ });
123
+ it('shouldEmitTelemetry returns false when preference is disabled', async () => {
124
+ const { client } = createQueryMock({ data: { enabled: false }, error: null });
125
+ mockGetClient.mockResolvedValue(client);
126
+ expect(await shouldEmitTelemetry('user-abc')).toBe(false);
127
+ });
128
+ });
129
+ // ============================================================================
130
+ // (5) Idempotent under concurrent calls — single in-flight query
131
+ // ============================================================================
132
+ describe('resolveConsent — concurrent call deduplication', () => {
133
+ it('issues exactly one Supabase query for two parallel calls on the same id', async () => {
134
+ const { client, maybeSingle } = createQueryMock({ data: { enabled: true }, error: null });
135
+ mockGetClient.mockResolvedValue(client);
136
+ const [s1, s2] = await Promise.all([resolveConsent('user-def'), resolveConsent('user-def')]);
137
+ // Both calls must have resolved to the same consent state.
138
+ expect(s1).toEqual(s2);
139
+ // The DB was only hit once — the cache stored the in-flight Promise.
140
+ expect(maybeSingle).toHaveBeenCalledTimes(1);
141
+ });
142
+ });
143
+ // ============================================================================
144
+ // (6) Fail-safe on Supabase error
145
+ // ============================================================================
146
+ describe('resolveConsent — fail-safe on Supabase error', () => {
147
+ it('returns consent_required: true when maybeSingle rejects', async () => {
148
+ const maybeSingle = vi.fn().mockRejectedValue(new Error('network error'));
149
+ const eq = vi.fn().mockReturnValue({ maybeSingle });
150
+ const select = vi.fn().mockReturnValue({ eq });
151
+ const from = vi.fn().mockReturnValue({ select });
152
+ const client = { from };
153
+ mockGetClient.mockResolvedValue(client);
154
+ const state = await resolveConsent('user-ghi');
155
+ // Fail-safe: consent_required must be true — never silently emit.
156
+ expect(state.consentRequired).toBe(true);
157
+ expect(state.enabled).toBe(false);
158
+ });
159
+ it('returns consent_required: true when query returns an error object', async () => {
160
+ const { client } = createQueryMock({ data: null, error: { message: 'permission denied' } });
161
+ mockGetClient.mockResolvedValue(client);
162
+ const state = await resolveConsent('user-ghi');
163
+ expect(state.consentRequired).toBe(true);
164
+ expect(state.enabled).toBe(false);
165
+ });
166
+ it('shouldEmitTelemetry returns false when Supabase errors', async () => {
167
+ const { client } = createQueryMock({ data: null, error: { message: 'db error' } });
168
+ mockGetClient.mockResolvedValue(client);
169
+ expect(await shouldEmitTelemetry('user-ghi')).toBe(false);
170
+ });
171
+ it('returns no-id state (no prompt) when getSupabaseClient itself throws', async () => {
172
+ // This covers the "Supabase not configured" offline branch.
173
+ mockGetClient.mockRejectedValue(new Error('Supabase not configured'));
174
+ const state = await resolveConsent('user-ghi-offline');
175
+ // Offline: suppress telemetry but do NOT demand consent (no network surface).
176
+ expect(state.enabled).toBe(false);
177
+ // consentRequired is false in the offline/unconfigured branch — matches DEFAULT_NO_ID.
178
+ expect(state.consentRequired).toBe(false);
179
+ });
180
+ });
181
+ // ============================================================================
182
+ // (7) Cache invalidation
183
+ // ============================================================================
184
+ describe('invalidateConsentCache', () => {
185
+ it('causes next resolveConsent call to re-query Supabase after invalidation', async () => {
186
+ const { client, maybeSingle } = createQueryMock({ data: { enabled: true }, error: null });
187
+ mockGetClient.mockResolvedValue(client);
188
+ // First call — populates cache.
189
+ await resolveConsent('user-jkl');
190
+ expect(maybeSingle).toHaveBeenCalledTimes(1);
191
+ // Invalidate.
192
+ invalidateConsentCache('user-jkl');
193
+ // Second call — must re-query.
194
+ await resolveConsent('user-jkl');
195
+ expect(maybeSingle).toHaveBeenCalledTimes(2);
196
+ });
197
+ it('does not affect cache entries for other anonymous_ids', async () => {
198
+ const { client, maybeSingle } = createQueryMock({ data: { enabled: true }, error: null });
199
+ mockGetClient.mockResolvedValue(client);
200
+ await resolveConsent('user-jkl');
201
+ await resolveConsent('user-other');
202
+ expect(maybeSingle).toHaveBeenCalledTimes(2);
203
+ invalidateConsentCache('user-jkl');
204
+ // Only user-jkl re-queries; user-other is still cached.
205
+ await resolveConsent('user-jkl');
206
+ await resolveConsent('user-other');
207
+ expect(maybeSingle).toHaveBeenCalledTimes(3);
208
+ });
209
+ it('clears entire cache when called without argument', async () => {
210
+ const { client, maybeSingle } = createQueryMock({ data: { enabled: true }, error: null });
211
+ mockGetClient.mockResolvedValue(client);
212
+ await resolveConsent('user-a');
213
+ await resolveConsent('user-b');
214
+ expect(maybeSingle).toHaveBeenCalledTimes(2);
215
+ invalidateConsentCache();
216
+ await resolveConsent('user-a');
217
+ await resolveConsent('user-b');
218
+ expect(maybeSingle).toHaveBeenCalledTimes(4);
219
+ });
220
+ });
221
+ // ============================================================================
222
+ // (8–11) annotateResponseWithConsent
223
+ // ============================================================================
224
+ /** Minimal MCP CallToolResult-shaped envelope. */
225
+ function makeEnvelope(text) {
226
+ return { content: [{ type: 'text', text }] };
227
+ }
228
+ const UNRESOLVED_CONSENT = {
229
+ enabled: false,
230
+ consentRequired: true,
231
+ privacyUrl: TELEMETRY_PRIVACY_URL,
232
+ };
233
+ const RESOLVED_CONSENT = {
234
+ enabled: true,
235
+ consentRequired: false,
236
+ privacyUrl: TELEMETRY_PRIVACY_URL,
237
+ };
238
+ describe('annotateResponseWithConsent', () => {
239
+ it('(8) splices consent_required and privacy_url when consent is unresolved', () => {
240
+ const envelope = makeEnvelope(JSON.stringify({ result: 'ok' }));
241
+ const out = annotateResponseWithConsent(envelope, UNRESOLVED_CONSENT);
242
+ const parsed = JSON.parse(out.content[0].text);
243
+ expect(parsed.result).toBe('ok');
244
+ expect(parsed.consent_required).toBe(true);
245
+ expect(parsed.privacy_url).toBe(TELEMETRY_PRIVACY_URL);
246
+ });
247
+ it('(9) returns envelope unchanged when consent is resolved (passthrough)', () => {
248
+ const text = JSON.stringify({ result: 'ok' });
249
+ const envelope = makeEnvelope(text);
250
+ const out = annotateResponseWithConsent(envelope, RESOLVED_CONSENT);
251
+ // Same reference (or at minimum identical content) — nothing added.
252
+ expect(out).toBe(envelope);
253
+ const parsed = JSON.parse(out.content[0].text);
254
+ expect(parsed).not.toHaveProperty('consent_required');
255
+ });
256
+ it('(10) is idempotent — does not re-annotate if fields already present', () => {
257
+ const alreadyAnnotated = { result: 'ok', consent_required: true, privacy_url: 'https://x.y' };
258
+ const envelope = makeEnvelope(JSON.stringify(alreadyAnnotated));
259
+ const out = annotateResponseWithConsent(envelope, UNRESOLVED_CONSENT);
260
+ // Must return the same reference — no mutation of pre-existing annotation.
261
+ expect(out).toBe(envelope);
262
+ const parsed = JSON.parse(out.content[0].text);
263
+ // privacy_url should remain the original value, not overwritten.
264
+ expect(parsed.privacy_url).toBe('https://x.y');
265
+ });
266
+ it('(11) returns envelope unchanged when text is malformed JSON (no throw)', () => {
267
+ const envelope = makeEnvelope('not-valid-json{{');
268
+ expect(() => annotateResponseWithConsent(envelope, UNRESOLVED_CONSENT)).not.toThrow();
269
+ const out = annotateResponseWithConsent(envelope, UNRESOLVED_CONSENT);
270
+ expect(out).toBe(envelope);
271
+ });
272
+ it('returns envelope unchanged when content array is empty', () => {
273
+ const envelope = { content: [] };
274
+ const out = annotateResponseWithConsent(envelope, UNRESOLVED_CONSENT);
275
+ expect(out).toBe(envelope);
276
+ });
277
+ it('returns envelope unchanged when first content item is not type=text', () => {
278
+ const envelope = { content: [{ type: 'image', url: 'https://example.com/img.png' }] };
279
+ const out = annotateResponseWithConsent(envelope, UNRESOLVED_CONSENT);
280
+ expect(out).toBe(envelope);
281
+ });
282
+ });
283
+ // ============================================================================
284
+ // (12) Privacy URL literal
285
+ // ============================================================================
286
+ describe('TELEMETRY_PRIVACY_URL', () => {
287
+ it('equals the canonical consent dashboard URL', () => {
288
+ expect(TELEMETRY_PRIVACY_URL).toBe('https://skillsmith.app/account/telemetry');
289
+ });
290
+ });
291
+ //# sourceMappingURL=telemetry-consent.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-consent.test.js","sourceRoot":"","sources":["../../../src/middleware/telemetry-consent.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * SMI-5018 W2.S3 — MCP-tree telemetry coverage snapshot test.
3
+ *
4
+ * Scope: `packages/mcp-server/src/tools/` only (v1).
5
+ * CLI + VS Code trees are NOT checked here — they are blocked by SMI-5040
6
+ * (anonymous-closure incompatibility). When SMI-5040 lands this test will
7
+ * be extended to cover those trees.
8
+ *
9
+ * Risk guarded (plan line 798, risk #8):
10
+ * "A new dispatcher ships without a telemetry wrap."
11
+ *
12
+ * Strategy: explicit allowlist (40 entries) cross-checked against the live
13
+ * withTelemetry import-site count. Allowlist chosen over heuristic-walk
14
+ * because it is trivially auditable — each entry maps 1-to-1 to a
15
+ * `grep "= withTelemetry"` result, and the SOURCE_FILE_COUNT sentinel
16
+ * independently guards against drift in either direction.
17
+ *
18
+ * When you add a new dispatcher:
19
+ * 1. Wrap it with withTelemetry in its source file (as SMI-5017 did).
20
+ * 2. Add its export name to EXPECTED_DISPATCHERS below.
21
+ * 3. Update SOURCE_FILE_COUNT if the dispatcher lives in a new file.
22
+ * The test will fail in CI until all three steps are done.
23
+ */
24
+ export {};
25
+ //# sourceMappingURL=telemetry-coverage.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-coverage.test.d.ts","sourceRoot":"","sources":["../../../../src/tools/__meta__/telemetry-coverage.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG"}