ad2app-lib 1.9.2 → 1.9.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.
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Single source of truth for event names, event property shapes, and person
5
5
  * property keys across ALL surfaces (frontend, backend, landing, future MCP).
6
- * Spec: ad2appus/docs/posthog-implementation.md §3.
6
+ * Spec: ad2app-specs/analytics-posthog/posthog-implementation.md §3.
7
7
  *
8
8
  * Naming convention: snake_case, object_verb_past_tense.
9
9
  * Do NOT rename events after they ship — historical data does not migrate.
@@ -11,6 +11,15 @@
11
11
  /** Canonical PostHog event names. */
12
12
  export declare const EVENTS: {
13
13
  readonly LANDING_CTA_CLICKED: "landing_cta_clicked";
14
+ readonly CONNECT_ARTIFACT_COPIED: "connect_artifact_copied";
15
+ readonly FREE_SKILLS_REQUESTED: "free_skills_requested";
16
+ readonly ACCOUNT_CONNECT_BLOCKED: "account_connect_blocked";
17
+ readonly MCP_POST_BLOCKED_FREE_TIER: "mcp_post_blocked_free_tier";
18
+ readonly EMAIL_DELIVERED: "email_delivered";
19
+ readonly EMAIL_OPENED: "email_opened";
20
+ readonly EMAIL_CLICKED: "email_clicked";
21
+ readonly EMAIL_BOUNCED: "email_bounced";
22
+ readonly EMAIL_COMPLAINED: "email_complained";
14
23
  readonly SIGNED_UP: "signed_up";
15
24
  readonly PROFILE_COMPLETED: "profile_completed";
16
25
  readonly LOGGED_IN: "logged_in";
@@ -19,7 +28,6 @@ export declare const EVENTS: {
19
28
  readonly POST_SCHEDULED: "post_scheduled";
20
29
  readonly POST_PUBLISHED: "post_published";
21
30
  readonly ANALYTICS_VIEWED: "analytics_viewed";
22
- readonly TRIAL_STARTED: "trial_started";
23
31
  readonly LIMIT_REACHED: "limit_reached";
24
32
  readonly UPGRADE_CLICKED: "upgrade_clicked";
25
33
  readonly CHECKOUT_STARTED: "checkout_started";
@@ -27,11 +35,34 @@ export declare const EVENTS: {
27
35
  readonly SUBSCRIPTION_CANCELED: "subscription_canceled";
28
36
  };
29
37
  export type EventName = (typeof EVENTS)[keyof typeof EVENTS];
38
+ /** Shared shape for the Resend email lifecycle events (AD2-894). */
39
+ export interface EmailEventProperties {
40
+ email_id?: string;
41
+ subject?: string;
42
+ link?: string;
43
+ }
30
44
  /** Property shape per event. Keeps emitters honest across repos. */
31
45
  export interface EventProperties {
32
46
  [EVENTS.LANDING_CTA_CLICKED]: {
33
- location: 'hero' | 'pricing' | 'final_cta' | 'nav';
47
+ location: 'hero' | 'pricing' | 'final_cta' | 'nav' | 'ai_connector' | 'free_skills';
48
+ };
49
+ [EVENTS.CONNECT_ARTIFACT_COPIED]: {
50
+ target: 'app' | 'ask' | 'terminal' | 'editor';
51
+ };
52
+ [EVENTS.FREE_SKILLS_REQUESTED]: {
53
+ source: string;
54
+ };
55
+ [EVENTS.ACCOUNT_CONNECT_BLOCKED]: {
56
+ platform?: string;
34
57
  };
58
+ [EVENTS.MCP_POST_BLOCKED_FREE_TIER]: {
59
+ target_count?: number;
60
+ };
61
+ [EVENTS.EMAIL_DELIVERED]: EmailEventProperties;
62
+ [EVENTS.EMAIL_OPENED]: EmailEventProperties;
63
+ [EVENTS.EMAIL_CLICKED]: EmailEventProperties;
64
+ [EVENTS.EMAIL_BOUNCED]: EmailEventProperties;
65
+ [EVENTS.EMAIL_COMPLAINED]: EmailEventProperties;
35
66
  [EVENTS.SIGNED_UP]: {
36
67
  method: 'email' | 'google';
37
68
  role: string;
@@ -62,9 +93,6 @@ export interface EventProperties {
62
93
  [EVENTS.ANALYTICS_VIEWED]: {
63
94
  scope?: 'overview' | 'account' | 'post';
64
95
  };
65
- [EVENTS.TRIAL_STARTED]: {
66
- plan: string;
67
- };
68
96
  [EVENTS.LIMIT_REACHED]: {
69
97
  limit: 'x_link_posts' | 'accounts';
70
98
  plan: string;
@@ -94,6 +122,5 @@ export declare const PERSON_PROPS: {
94
122
  readonly ROLE: "role";
95
123
  readonly PLAN: "plan";
96
124
  readonly ACCOUNT_COUNT: "account_count";
97
- readonly TRIAL_ENDS_AT: "trial_ends_at";
98
125
  };
99
126
  export type PersonPropKey = (typeof PERSON_PROPS)[keyof typeof PERSON_PROPS];
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Single source of truth for event names, event property shapes, and person
6
6
  * property keys across ALL surfaces (frontend, backend, landing, future MCP).
7
- * Spec: ad2appus/docs/posthog-implementation.md §3.
7
+ * Spec: ad2app-specs/analytics-posthog/posthog-implementation.md §3.
8
8
  *
9
9
  * Naming convention: snake_case, object_verb_past_tense.
10
10
  * Do NOT rename events after they ship — historical data does not migrate.
@@ -15,6 +15,17 @@ exports.PERSON_PROPS = exports.EVENTS = void 0;
15
15
  exports.EVENTS = {
16
16
  // Acquisition (landing)
17
17
  LANDING_CTA_CLICKED: 'landing_cta_clicked',
18
+ CONNECT_ARTIFACT_COPIED: 'connect_artifact_copied', // /connect install-snippet copy
19
+ // Free Tier (skills lead magnet + connect wall)
20
+ FREE_SKILLS_REQUESTED: 'free_skills_requested', // email submitted for the free skills (AD2-889)
21
+ ACCOUNT_CONNECT_BLOCKED: 'account_connect_blocked', // free user hits the connect wall (AD2-892)
22
+ MCP_POST_BLOCKED_FREE_TIER: 'mcp_post_blocked_free_tier', // free user tries to post in the MCP (AD2-898)
23
+ // Email lifecycle (Resend webhook -> PostHog, AD2-894)
24
+ EMAIL_DELIVERED: 'email_delivered',
25
+ EMAIL_OPENED: 'email_opened',
26
+ EMAIL_CLICKED: 'email_clicked',
27
+ EMAIL_BOUNCED: 'email_bounced',
28
+ EMAIL_COMPLAINED: 'email_complained',
18
29
  // Activation (web app)
19
30
  SIGNED_UP: 'signed_up', // server-owned (backend, on user creation)
20
31
  PROFILE_COMPLETED: 'profile_completed', // the /complete-profile step (influencers)
@@ -24,8 +35,7 @@ exports.EVENTS = {
24
35
  POST_SCHEDULED: 'post_scheduled',
25
36
  POST_PUBLISHED: 'post_published', // server-confirmed (backend)
26
37
  ANALYTICS_VIEWED: 'analytics_viewed',
27
- // Revenue (backend = source of truth)
28
- TRIAL_STARTED: 'trial_started',
38
+ // Revenue (backend = source of truth). Paid-only product: no trial.
29
39
  LIMIT_REACHED: 'limit_reached', // cap hit (e.g. X link-post cap) — the upsell trigger
30
40
  UPGRADE_CLICKED: 'upgrade_clicked', // client intent
31
41
  CHECKOUT_STARTED: 'checkout_started',
@@ -36,7 +46,6 @@ exports.EVENTS = {
36
46
  exports.PERSON_PROPS = {
37
47
  EMAIL: 'email',
38
48
  ROLE: 'role',
39
- PLAN: 'plan', // 'trial' | 'starter' | 'all_platforms' | 'free'
49
+ PLAN: 'plan', // 'free' | 'starter' | 'pro' (SchedulingSubscriptionTier)
40
50
  ACCOUNT_COUNT: 'account_count',
41
- TRIAL_ENDS_AT: 'trial_ends_at',
42
51
  };
@@ -37,13 +37,27 @@ async function fetchCall(args) {
37
37
  }
38
38
  }
39
39
  const res = await fetch(fullPath, init);
40
- if (!res.ok)
41
- throw new Error(JSON.stringify(await res.json()));
40
+ // Read the body ONCE as text. res.json() returns a promise, so the old
41
+ // synchronous try/catch around it never caught a parse failure (the text()
42
+ // fallback was dead code), and a plain-text error body threw inside res.json().
43
+ const rawBody = await res.text();
44
+ if (!res.ok) {
45
+ let message = rawBody;
46
+ try {
47
+ message = JSON.stringify(JSON.parse(rawBody));
48
+ }
49
+ catch {
50
+ // non-JSON error body: keep the raw text
51
+ }
52
+ throw new Error(message || `Request failed with status ${res.status}`);
53
+ }
54
+ if (!rawBody)
55
+ return undefined;
42
56
  try {
43
- return res.json();
57
+ return JSON.parse(rawBody);
44
58
  }
45
59
  catch {
46
- return res.text();
60
+ return rawBody;
47
61
  }
48
62
  }
49
63
  function apiDriver(baseUrl) {
@@ -64,6 +78,7 @@ function apiDriver(baseUrl) {
64
78
  ...args[1],
65
79
  path: args[0],
66
80
  method: "put",
81
+ baseUrl,
67
82
  }),
68
83
  patch: (...args) => fetchCall({
69
84
  ...args[1],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ad2app-lib",
3
- "version": "1.9.2",
3
+ "version": "1.9.3",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "commonjs",