@nordsym/apiclaw 1.0.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.
Files changed (79) hide show
  1. package/.github/ISSUE_TEMPLATE/add-api.yml +123 -0
  2. package/BRIEFING.md +30 -0
  3. package/CONCEPT.md +494 -0
  4. package/README.md +272 -0
  5. package/backend/convex/README.md +90 -0
  6. package/backend/convex/_generated/api.d.ts +55 -0
  7. package/backend/convex/_generated/api.js +23 -0
  8. package/backend/convex/_generated/dataModel.d.ts +60 -0
  9. package/backend/convex/_generated/server.d.ts +143 -0
  10. package/backend/convex/_generated/server.js +93 -0
  11. package/backend/convex/apiKeys.ts +75 -0
  12. package/backend/convex/purchases.ts +74 -0
  13. package/backend/convex/schema.ts +45 -0
  14. package/backend/convex/transactions.ts +57 -0
  15. package/backend/convex/tsconfig.json +25 -0
  16. package/backend/convex/users.ts +94 -0
  17. package/backend/package-lock.json +521 -0
  18. package/backend/package.json +15 -0
  19. package/dist/credits.d.ts +54 -0
  20. package/dist/credits.d.ts.map +1 -0
  21. package/dist/credits.js +209 -0
  22. package/dist/credits.js.map +1 -0
  23. package/dist/discovery.d.ts +37 -0
  24. package/dist/discovery.d.ts.map +1 -0
  25. package/dist/discovery.js +109 -0
  26. package/dist/discovery.js.map +1 -0
  27. package/dist/index.d.ts +13 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +355 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/registry/apis.json +20894 -0
  32. package/dist/registry/parse_apis.py +146 -0
  33. package/dist/revenuecat.d.ts +61 -0
  34. package/dist/revenuecat.d.ts.map +1 -0
  35. package/dist/revenuecat.js +166 -0
  36. package/dist/revenuecat.js.map +1 -0
  37. package/dist/test.d.ts +6 -0
  38. package/dist/test.d.ts.map +1 -0
  39. package/dist/test.js +81 -0
  40. package/dist/test.js.map +1 -0
  41. package/dist/types.d.ts +96 -0
  42. package/dist/types.d.ts.map +1 -0
  43. package/dist/types.js +3 -0
  44. package/dist/types.js.map +1 -0
  45. package/dist/webhooks/revenuecat.d.ts +48 -0
  46. package/dist/webhooks/revenuecat.d.ts.map +1 -0
  47. package/dist/webhooks/revenuecat.js +119 -0
  48. package/dist/webhooks/revenuecat.js.map +1 -0
  49. package/docs/revenuecat-setup.md +89 -0
  50. package/landing/next-env.d.ts +5 -0
  51. package/landing/next.config.mjs +6 -0
  52. package/landing/package-lock.json +1666 -0
  53. package/landing/package.json +27 -0
  54. package/landing/postcss.config.js +6 -0
  55. package/landing/src/app/api/keys/route.ts +71 -0
  56. package/landing/src/app/api/log/route.ts +37 -0
  57. package/landing/src/app/api/stats/route.ts +37 -0
  58. package/landing/src/app/globals.css +261 -0
  59. package/landing/src/app/layout.tsx +37 -0
  60. package/landing/src/app/page.tsx +753 -0
  61. package/landing/src/app/page.tsx.bak +567 -0
  62. package/landing/src/components/AddKeyModal.tsx +159 -0
  63. package/landing/tailwind.config.ts +34 -0
  64. package/landing/tsconfig.json +20 -0
  65. package/newsletter-template.html +71 -0
  66. package/outreach/OUTREACH-SYSTEM.md +211 -0
  67. package/outreach/email-template.html +179 -0
  68. package/outreach/targets.md +133 -0
  69. package/package.json +39 -0
  70. package/src/credits.ts +261 -0
  71. package/src/discovery.ts +147 -0
  72. package/src/index.ts +396 -0
  73. package/src/registry/apis.json +20894 -0
  74. package/src/registry/parse_apis.py +146 -0
  75. package/src/revenuecat.ts +239 -0
  76. package/src/test.ts +97 -0
  77. package/src/types.ts +110 -0
  78. package/src/webhooks/revenuecat.ts +187 -0
  79. package/tsconfig.json +20 -0
@@ -0,0 +1,187 @@
1
+ // RevenueCat webhook handler for APIClaw
2
+ // Handles subscription lifecycle events and syncs to Convex
3
+
4
+ import { Hono } from 'hono';
5
+
6
+ const CONVEX_URL = process.env.CONVEX_URL || 'https://agile-crane-840.convex.cloud';
7
+
8
+ // RevenueCat webhook event types
9
+ type WebhookEventType =
10
+ | 'INITIAL_PURCHASE'
11
+ | 'RENEWAL'
12
+ | 'CANCELLATION'
13
+ | 'UNCANCELLATION'
14
+ | 'NON_RENEWING_PURCHASE'
15
+ | 'SUBSCRIPTION_PAUSED'
16
+ | 'SUBSCRIPTION_EXTENDED'
17
+ | 'EXPIRATION'
18
+ | 'BILLING_ISSUE'
19
+ | 'PRODUCT_CHANGE'
20
+ | 'TRANSFER';
21
+
22
+ interface RevenueCatWebhookEvent {
23
+ api_version: string;
24
+ event: {
25
+ type: WebhookEventType;
26
+ id: string;
27
+ app_id: string;
28
+ app_user_id: string;
29
+ original_app_user_id: string;
30
+ aliases: string[];
31
+ product_id: string;
32
+ entitlement_ids: string[];
33
+ period_type: string;
34
+ purchased_at_ms: number;
35
+ expiration_at_ms: number | null;
36
+ environment: 'SANDBOX' | 'PRODUCTION';
37
+ store: string;
38
+ is_trial_conversion?: boolean;
39
+ cancel_reason?: string;
40
+ price_in_purchased_currency?: number;
41
+ currency?: string;
42
+ };
43
+ }
44
+
45
+ interface UserSubscriptionUpdate {
46
+ userId: string;
47
+ isPro: boolean;
48
+ subscriptionStatus: 'active' | 'cancelled' | 'expired' | 'paused' | 'billing_issue';
49
+ productId: string | null;
50
+ expiresAt: number | null;
51
+ updatedAt: number;
52
+ eventType: WebhookEventType;
53
+ }
54
+
55
+ /**
56
+ * Update user subscription status in Convex
57
+ */
58
+ async function updateConvexUser(update: UserSubscriptionUpdate): Promise<boolean> {
59
+ try {
60
+ const response = await fetch(`${CONVEX_URL}/api/mutation`, {
61
+ method: 'POST',
62
+ headers: {
63
+ 'Content-Type': 'application/json',
64
+ },
65
+ body: JSON.stringify({
66
+ path: 'users:updateSubscription',
67
+ args: update,
68
+ }),
69
+ });
70
+
71
+ if (!response.ok) {
72
+ console.error('Convex update failed:', await response.text());
73
+ return false;
74
+ }
75
+
76
+ return true;
77
+ } catch (error) {
78
+ console.error('Error updating Convex:', error);
79
+ return false;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Process webhook event and determine subscription state
85
+ */
86
+ function processEvent(event: RevenueCatWebhookEvent['event']): UserSubscriptionUpdate {
87
+ const now = Date.now();
88
+ const expiresAt = event.expiration_at_ms;
89
+ const isExpired = expiresAt ? expiresAt < now : false;
90
+
91
+ let subscriptionStatus: UserSubscriptionUpdate['subscriptionStatus'] = 'active';
92
+ let isPro = true;
93
+
94
+ switch (event.type) {
95
+ case 'INITIAL_PURCHASE':
96
+ case 'RENEWAL':
97
+ case 'UNCANCELLATION':
98
+ case 'SUBSCRIPTION_EXTENDED':
99
+ subscriptionStatus = 'active';
100
+ isPro = true;
101
+ break;
102
+
103
+ case 'CANCELLATION':
104
+ // Still active until expiration
105
+ subscriptionStatus = 'cancelled';
106
+ isPro = !isExpired;
107
+ break;
108
+
109
+ case 'EXPIRATION':
110
+ subscriptionStatus = 'expired';
111
+ isPro = false;
112
+ break;
113
+
114
+ case 'SUBSCRIPTION_PAUSED':
115
+ subscriptionStatus = 'paused';
116
+ isPro = false;
117
+ break;
118
+
119
+ case 'BILLING_ISSUE':
120
+ subscriptionStatus = 'billing_issue';
121
+ // Keep Pro until grace period ends
122
+ isPro = !isExpired;
123
+ break;
124
+
125
+ case 'PRODUCT_CHANGE':
126
+ case 'TRANSFER':
127
+ subscriptionStatus = 'active';
128
+ isPro = event.entitlement_ids.includes('pro');
129
+ break;
130
+
131
+ default:
132
+ isPro = event.entitlement_ids.includes('pro') && !isExpired;
133
+ }
134
+
135
+ return {
136
+ userId: event.app_user_id,
137
+ isPro,
138
+ subscriptionStatus,
139
+ productId: event.product_id,
140
+ expiresAt: expiresAt,
141
+ updatedAt: now,
142
+ eventType: event.type,
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Create Hono router for webhook handling
148
+ */
149
+ export function createWebhookRouter(): Hono {
150
+ const app = new Hono();
151
+
152
+ // Health check
153
+ app.get('/webhooks/revenuecat/health', (c) => {
154
+ return c.json({ status: 'ok', service: 'revenuecat-webhook' });
155
+ });
156
+
157
+ // RevenueCat webhook endpoint
158
+ app.post('/webhooks/revenuecat', async (c) => {
159
+ try {
160
+ const body = await c.req.json<RevenueCatWebhookEvent>();
161
+
162
+ console.log(`[RevenueCat] Received ${body.event.type} for user ${body.event.app_user_id}`);
163
+
164
+ // Process the event
165
+ const update = processEvent(body.event);
166
+
167
+ // Update Convex
168
+ const success = await updateConvexUser(update);
169
+
170
+ if (success) {
171
+ console.log(`[RevenueCat] Updated user ${update.userId}: isPro=${update.isPro}`);
172
+ return c.json({ success: true });
173
+ } else {
174
+ return c.json({ success: false, error: 'Failed to update database' }, 500);
175
+ }
176
+ } catch (error) {
177
+ console.error('[RevenueCat] Webhook error:', error);
178
+ return c.json({ success: false, error: 'Internal error' }, 500);
179
+ }
180
+ });
181
+
182
+ return app;
183
+ }
184
+
185
+ // Export for testing
186
+ export { processEvent, updateConvexUser };
187
+ export type { RevenueCatWebhookEvent, UserSubscriptionUpdate };
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }