costhawk 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 (171) hide show
  1. package/.claude/settings.local.json +32 -0
  2. package/STRATEGIC_PLAN_2025-12-01.md +934 -0
  3. package/costcanary/.claude/settings.local.json +9 -0
  4. package/costcanary/.env.production.template +38 -0
  5. package/costcanary/.eslintrc.json +22 -0
  6. package/costcanary/.nvmrc +1 -0
  7. package/costcanary/.prettierignore +11 -0
  8. package/costcanary/.prettierrc.json +12 -0
  9. package/costcanary/ADMIN_SETUP.md +68 -0
  10. package/costcanary/CLAUDE.md +228 -0
  11. package/costcanary/CLERK_SETUP.md +69 -0
  12. package/costcanary/DATABASE_SETUP.md +136 -0
  13. package/costcanary/DEMO_CHECKLIST.md +62 -0
  14. package/costcanary/DEPLOYMENT.md +31 -0
  15. package/costcanary/PRODUCTION_RECOVERY.md +109 -0
  16. package/costcanary/README.md +247 -0
  17. package/costcanary/STRIPE_SECURITY_AUDIT.md +123 -0
  18. package/costcanary/TESTING_ADMIN.md +92 -0
  19. package/costcanary/app/(auth)/sign-in/[[...sign-in]]/page.tsx +25 -0
  20. package/costcanary/app/(auth)/sign-up/[[...sign-up]]/page.tsx +25 -0
  21. package/costcanary/app/(dashboard)/dashboard/admin/page.tsx +260 -0
  22. package/costcanary/app/(dashboard)/dashboard/alerts/page.tsx +64 -0
  23. package/costcanary/app/(dashboard)/dashboard/api-keys/page.tsx +231 -0
  24. package/costcanary/app/(dashboard)/dashboard/billing/page.tsx +349 -0
  25. package/costcanary/app/(dashboard)/dashboard/layout.tsx +188 -0
  26. package/costcanary/app/(dashboard)/dashboard/page.tsx +13 -0
  27. package/costcanary/app/(dashboard)/dashboard/playground/page.tsx +605 -0
  28. package/costcanary/app/(dashboard)/dashboard/settings/page.tsx +86 -0
  29. package/costcanary/app/(dashboard)/dashboard/usage/page.tsx +354 -0
  30. package/costcanary/app/(dashboard)/dashboard/wrapped-keys/page.tsx +677 -0
  31. package/costcanary/app/(marketing)/page.tsx +90 -0
  32. package/costcanary/app/(marketing)/pricing/page.tsx +272 -0
  33. package/costcanary/app/admin/pricing-status/page.tsx +338 -0
  34. package/costcanary/app/api/admin/check-pricing/route.ts +127 -0
  35. package/costcanary/app/api/admin/debug/route.ts +44 -0
  36. package/costcanary/app/api/admin/fix-pricing/route.ts +216 -0
  37. package/costcanary/app/api/admin/pricing-jobs/[jobId]/route.ts +48 -0
  38. package/costcanary/app/api/admin/pricing-jobs/route.ts +45 -0
  39. package/costcanary/app/api/admin/trigger-pricing/route.ts +209 -0
  40. package/costcanary/app/api/admin/whoami/route.ts +44 -0
  41. package/costcanary/app/api/auth/clerk/[...nextjs]/route.ts +93 -0
  42. package/costcanary/app/api/debug/wrapped-key/route.ts +51 -0
  43. package/costcanary/app/api/debug-status/route.ts +9 -0
  44. package/costcanary/app/api/debug-version/route.ts +12 -0
  45. package/costcanary/app/api/health/route.ts +14 -0
  46. package/costcanary/app/api/health-simple/route.ts +18 -0
  47. package/costcanary/app/api/keys/route.ts +162 -0
  48. package/costcanary/app/api/keys/wrapped/[id]/revoke/route.ts +86 -0
  49. package/costcanary/app/api/keys/wrapped/[id]/rotate/route.ts +81 -0
  50. package/costcanary/app/api/keys/wrapped/route.ts +241 -0
  51. package/costcanary/app/api/optimizer/preview/route.ts +147 -0
  52. package/costcanary/app/api/optimizer/route.ts +118 -0
  53. package/costcanary/app/api/pricing/models/route.ts +102 -0
  54. package/costcanary/app/api/proxy/[...path]/route.ts +391 -0
  55. package/costcanary/app/api/proxy/anthropic/route.ts +539 -0
  56. package/costcanary/app/api/proxy/google/route.ts +395 -0
  57. package/costcanary/app/api/proxy/openai/route.ts +529 -0
  58. package/costcanary/app/api/simple-test/route.ts +7 -0
  59. package/costcanary/app/api/stripe/checkout/route.ts +201 -0
  60. package/costcanary/app/api/stripe/webhook/route.ts +392 -0
  61. package/costcanary/app/api/test-connection/route.ts +209 -0
  62. package/costcanary/app/api/test-proxy/route.ts +7 -0
  63. package/costcanary/app/api/test-simple/route.ts +20 -0
  64. package/costcanary/app/api/usage/current/route.ts +112 -0
  65. package/costcanary/app/api/usage/stats/route.ts +129 -0
  66. package/costcanary/app/api/usage/stream/route.ts +113 -0
  67. package/costcanary/app/api/usage/summary/route.ts +67 -0
  68. package/costcanary/app/api/usage/trend/route.ts +119 -0
  69. package/costcanary/app/api/ws/route.ts +23 -0
  70. package/costcanary/app/globals.css +280 -0
  71. package/costcanary/app/layout.tsx +87 -0
  72. package/costcanary/components/Header.tsx +85 -0
  73. package/costcanary/components/dashboard/AddApiKeyModal.tsx +264 -0
  74. package/costcanary/components/dashboard/dashboard-content.tsx +329 -0
  75. package/costcanary/components/landing/DashboardPreview.tsx +222 -0
  76. package/costcanary/components/landing/Features.tsx +238 -0
  77. package/costcanary/components/landing/Footer.tsx +83 -0
  78. package/costcanary/components/landing/Hero.tsx +193 -0
  79. package/costcanary/components/landing/Pricing.tsx +250 -0
  80. package/costcanary/components/landing/Testimonials.tsx +248 -0
  81. package/costcanary/components/theme-provider.tsx +8 -0
  82. package/costcanary/components/ui/alert.tsx +59 -0
  83. package/costcanary/components/ui/badge.tsx +36 -0
  84. package/costcanary/components/ui/button.tsx +56 -0
  85. package/costcanary/components/ui/card.tsx +79 -0
  86. package/costcanary/components/ui/dialog.tsx +122 -0
  87. package/costcanary/components/ui/input.tsx +22 -0
  88. package/costcanary/components/ui/label.tsx +26 -0
  89. package/costcanary/components/ui/progress.tsx +28 -0
  90. package/costcanary/components/ui/select.tsx +160 -0
  91. package/costcanary/components/ui/separator.tsx +31 -0
  92. package/costcanary/components/ui/switch.tsx +29 -0
  93. package/costcanary/components/ui/tabs.tsx +55 -0
  94. package/costcanary/components/ui/toast.tsx +127 -0
  95. package/costcanary/components/ui/toaster.tsx +35 -0
  96. package/costcanary/components/ui/use-toast.ts +189 -0
  97. package/costcanary/components.json +17 -0
  98. package/costcanary/debug-wrapped-keys.md +117 -0
  99. package/costcanary/fix-console.sh +30 -0
  100. package/costcanary/lib/admin-auth.ts +226 -0
  101. package/costcanary/lib/admin-security.ts +124 -0
  102. package/costcanary/lib/audit-events.ts +62 -0
  103. package/costcanary/lib/audit.ts +158 -0
  104. package/costcanary/lib/chart-colors.ts +152 -0
  105. package/costcanary/lib/cost-calculator.ts +212 -0
  106. package/costcanary/lib/db-utils.ts +325 -0
  107. package/costcanary/lib/db.ts +14 -0
  108. package/costcanary/lib/encryption.ts +120 -0
  109. package/costcanary/lib/kms.ts +358 -0
  110. package/costcanary/lib/model-alias.ts +180 -0
  111. package/costcanary/lib/pricing.ts +292 -0
  112. package/costcanary/lib/prisma.ts +52 -0
  113. package/costcanary/lib/railway-db.ts +157 -0
  114. package/costcanary/lib/sse-parser.ts +283 -0
  115. package/costcanary/lib/stripe-client.ts +81 -0
  116. package/costcanary/lib/stripe-server.ts +52 -0
  117. package/costcanary/lib/tokens.ts +396 -0
  118. package/costcanary/lib/usage-limits.ts +164 -0
  119. package/costcanary/lib/utils.ts +6 -0
  120. package/costcanary/lib/websocket.ts +153 -0
  121. package/costcanary/lib/wrapped-keys.ts +531 -0
  122. package/costcanary/market-research.md +443 -0
  123. package/costcanary/middleware.ts +48 -0
  124. package/costcanary/next.config.js +43 -0
  125. package/costcanary/nia-sources.md +151 -0
  126. package/costcanary/package-lock.json +12162 -0
  127. package/costcanary/package.json +92 -0
  128. package/costcanary/package.json.backup +89 -0
  129. package/costcanary/postcss.config.js +6 -0
  130. package/costcanary/pricing-worker/.env.example +8 -0
  131. package/costcanary/pricing-worker/README.md +81 -0
  132. package/costcanary/pricing-worker/package-lock.json +1109 -0
  133. package/costcanary/pricing-worker/package.json +26 -0
  134. package/costcanary/pricing-worker/railway.json +13 -0
  135. package/costcanary/pricing-worker/schema.prisma +326 -0
  136. package/costcanary/pricing-worker/src/index.ts +115 -0
  137. package/costcanary/pricing-worker/src/services/pricing-updater.ts +79 -0
  138. package/costcanary/pricing-worker/src/services/tavily-client.ts +474 -0
  139. package/costcanary/pricing-worker/test-tavily.ts +47 -0
  140. package/costcanary/pricing-worker/tsconfig.json +24 -0
  141. package/costcanary/prisma/migrations/001_add_stripe_fields.sql +26 -0
  142. package/costcanary/prisma/schema.prisma +326 -0
  143. package/costcanary/prisma/seed-pricing.ts +133 -0
  144. package/costcanary/public/costhawk-logo.png +0 -0
  145. package/costcanary/railway.json +30 -0
  146. package/costcanary/railway.toml +16 -0
  147. package/costcanary/research-nia.md +298 -0
  148. package/costcanary/research.md +411 -0
  149. package/costcanary/scripts/build-production.js +65 -0
  150. package/costcanary/scripts/check-current-pricing.ts +51 -0
  151. package/costcanary/scripts/check-pricing-data.ts +174 -0
  152. package/costcanary/scripts/create-stripe-prices.js +49 -0
  153. package/costcanary/scripts/fix-pricing-data.ts +135 -0
  154. package/costcanary/scripts/fix-pricing-db.ts +148 -0
  155. package/costcanary/scripts/postinstall.js +58 -0
  156. package/costcanary/scripts/railway-deploy.sh +52 -0
  157. package/costcanary/scripts/run-migration.js +61 -0
  158. package/costcanary/scripts/start-production.js +175 -0
  159. package/costcanary/scripts/test-wrapped-key.ts +85 -0
  160. package/costcanary/scripts/validate-deployment.js +176 -0
  161. package/costcanary/scripts/validate-production.js +119 -0
  162. package/costcanary/server.js.backup +38 -0
  163. package/costcanary/tailwind.config.ts +216 -0
  164. package/costcanary/test-pricing-status.sh +27 -0
  165. package/costcanary/tsconfig.json +42 -0
  166. package/docs/sessions/session-2025-12-01.md +570 -0
  167. package/executive-summary.md +302 -0
  168. package/index.js +1 -0
  169. package/nia-sources.md +163 -0
  170. package/package.json +16 -0
  171. package/research.md +750 -0
@@ -0,0 +1,174 @@
1
+ import { PrismaClient } from '@prisma/client';
2
+ import { config } from 'dotenv';
3
+
4
+ // Load env vars
5
+ config({ path: '.env.local' });
6
+
7
+ const prisma = new PrismaClient({
8
+ datasourceUrl: process.env.DATABASE_URL
9
+ });
10
+
11
+ async function checkPricingData() {
12
+ console.log('๐Ÿ” Checking Pricing Data in Database\n');
13
+ console.log('=' .repeat(80));
14
+
15
+ try {
16
+ // 1. Check PricingDiscoveryJob records
17
+ console.log('\n๐Ÿ“‹ PRICING DISCOVERY JOBS (Last 5):');
18
+ console.log('-'.repeat(40));
19
+
20
+ const jobs = await prisma.pricingDiscoveryJob.findMany({
21
+ orderBy: { createdAt: 'desc' },
22
+ take: 5
23
+ });
24
+
25
+ if (jobs.length === 0) {
26
+ console.log('โŒ No pricing discovery jobs found');
27
+ } else {
28
+ for (const job of jobs) {
29
+ console.log(`\nJob ID: ${job.id}`);
30
+ console.log(` Status: ${job.status}`);
31
+ console.log(` Created: ${job.createdAt.toISOString()}`);
32
+ console.log(` Started: ${job.startedAt?.toISOString() || 'N/A'}`);
33
+ console.log(` Completed: ${job.completedAt?.toISOString() || 'N/A'}`);
34
+ if (job.error) {
35
+ console.log(` Error: ${job.error}`);
36
+ }
37
+ if (job.metadata) {
38
+ console.log(` Metadata: ${JSON.stringify(job.metadata, null, 2)}`);
39
+ }
40
+ }
41
+ }
42
+
43
+ // 2. Check PricingVersion records
44
+ console.log('\n\n๐Ÿ’ฐ PRICING VERSIONS BY PROVIDER:');
45
+ console.log('-'.repeat(40));
46
+
47
+ const providers = ['OPENAI', 'ANTHROPIC', 'GOOGLE', 'GROK', 'MISTRAL', 'COHERE'];
48
+
49
+ for (const provider of providers) {
50
+ console.log(`\n${provider}:`);
51
+
52
+ const versions = await prisma.pricingVersion.findMany({
53
+ where: { provider: provider as any },
54
+ orderBy: { createdAt: 'desc' },
55
+ take: 2,
56
+ include: {
57
+ models: {
58
+ orderBy: { canonicalModel: 'asc' }
59
+ }
60
+ }
61
+ });
62
+
63
+ if (versions.length === 0) {
64
+ console.log(' โŒ No pricing versions found');
65
+ } else {
66
+ for (const version of versions) {
67
+ console.log(` Version ID: ${version.id}`);
68
+ console.log(` Active: ${version.isActive ? 'โœ…' : 'โŒ'}`);
69
+ console.log(` Created: ${version.createdAt.toISOString()}`);
70
+ console.log(` Note: ${version.note || 'N/A'}`);
71
+ console.log(` Models: ${version.models.length}`);
72
+
73
+ if (version.isActive && version.models.length > 0) {
74
+ console.log(' Sample prices:');
75
+ const sampleModels = version.models.slice(0, 3);
76
+ for (const model of sampleModels) {
77
+ console.log(` - ${model.canonicalModel}:`);
78
+ console.log(` Input: $${model.unitInputPer1k}/1K tokens`);
79
+ console.log(` Output: $${model.unitOutputPer1k}/1K tokens`);
80
+ }
81
+ if (version.models.length > 3) {
82
+ console.log(` ... and ${version.models.length - 3} more models`);
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ // 3. Check Model Aliases
90
+ console.log('\n\n๐Ÿ”— MODEL ALIASES (Sample):');
91
+ console.log('-'.repeat(40));
92
+
93
+ const aliases = await prisma.providerModelAlias.findMany({
94
+ take: 10,
95
+ orderBy: { updatedAt: 'desc' }
96
+ });
97
+
98
+ if (aliases.length === 0) {
99
+ console.log('โŒ No model aliases found');
100
+ } else {
101
+ for (const alias of aliases) {
102
+ console.log(`${alias.provider}: ${alias.alias} โ†’ ${alias.canonicalModel}`);
103
+ }
104
+ }
105
+
106
+ // 4. Summary Statistics
107
+ console.log('\n\n๐Ÿ“Š SUMMARY STATISTICS:');
108
+ console.log('-'.repeat(40));
109
+
110
+ const totalJobs = await prisma.pricingDiscoveryJob.count();
111
+ const completedJobs = await prisma.pricingDiscoveryJob.count({
112
+ where: { status: 'COMPLETED' }
113
+ });
114
+ const failedJobs = await prisma.pricingDiscoveryJob.count({
115
+ where: { status: 'FAILED' }
116
+ });
117
+
118
+ console.log(`Total Jobs: ${totalJobs}`);
119
+ console.log(`Completed: ${completedJobs}`);
120
+ console.log(`Failed: ${failedJobs}`);
121
+
122
+ const totalVersions = await prisma.pricingVersion.count();
123
+ const activeVersions = await prisma.pricingVersion.count({
124
+ where: { isActive: true }
125
+ });
126
+
127
+ console.log(`\nTotal Pricing Versions: ${totalVersions}`);
128
+ console.log(`Active Versions: ${activeVersions}`);
129
+
130
+ const totalModels = await prisma.pricingModelPrice.count();
131
+ console.log(`Total Model Prices: ${totalModels}`);
132
+
133
+ const totalAliases = await prisma.providerModelAlias.count();
134
+ console.log(`Total Model Aliases: ${totalAliases}`);
135
+
136
+ // 5. Check for recent updates
137
+ console.log('\n\n๐Ÿ• RECENT UPDATES (Last 24 hours):');
138
+ console.log('-'.repeat(40));
139
+
140
+ const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
141
+
142
+ const recentVersions = await prisma.pricingVersion.findMany({
143
+ where: {
144
+ createdAt: {
145
+ gte: oneDayAgo
146
+ }
147
+ },
148
+ include: {
149
+ models: true
150
+ }
151
+ });
152
+
153
+ if (recentVersions.length === 0) {
154
+ console.log('No pricing updates in the last 24 hours');
155
+ } else {
156
+ console.log(`Found ${recentVersions.length} new pricing versions:`);
157
+ for (const version of recentVersions) {
158
+ console.log(` - ${version.provider}: ${version.models.length} models`);
159
+ console.log(` Created: ${version.createdAt.toISOString()}`);
160
+ }
161
+ }
162
+
163
+ console.log('\n' + '='.repeat(80));
164
+ console.log('โœ… Pricing data check complete\n');
165
+
166
+ } catch (error) {
167
+ console.error('โŒ Error checking pricing data:', error);
168
+ } finally {
169
+ await prisma.$disconnect();
170
+ }
171
+ }
172
+
173
+ // Run the check
174
+ checkPricingData().catch(console.error);
@@ -0,0 +1,49 @@
1
+ // Script to create Stripe prices for existing products
2
+ // Run with: node scripts/create-stripe-prices.js
3
+
4
+ const Stripe = require('stripe');
5
+
6
+ // Use environment variable for security
7
+ const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY;
8
+ if (!STRIPE_SECRET_KEY) {
9
+ console.error('Please set STRIPE_SECRET_KEY environment variable');
10
+ process.exit(1);
11
+ }
12
+ const stripe = new Stripe(STRIPE_SECRET_KEY);
13
+
14
+ async function createPrices() {
15
+ try {
16
+ // Create price for Pro plan ($9.99/month)
17
+ const proPrice = await stripe.prices.create({
18
+ product: 'prod_SrwkcBaav5iyNu',
19
+ unit_amount: 999, // $9.99 in cents
20
+ currency: 'usd',
21
+ recurring: {
22
+ interval: 'month',
23
+ },
24
+ nickname: 'Pro Monthly',
25
+ });
26
+
27
+ console.log('Pro Price created:', proPrice.id);
28
+ console.log('Add to Railway: STRIPE_PRO_PRICE_ID=' + proPrice.id);
29
+
30
+ // Create price for Team plan ($29.99/month)
31
+ const teamPrice = await stripe.prices.create({
32
+ product: 'prod_SrwltQwNO8UCyL',
33
+ unit_amount: 2999, // $29.99 in cents
34
+ currency: 'usd',
35
+ recurring: {
36
+ interval: 'month',
37
+ },
38
+ nickname: 'Team Monthly',
39
+ });
40
+
41
+ console.log('Team Price created:', teamPrice.id);
42
+ console.log('Add to Railway: STRIPE_TEAM_PRICE_ID=' + teamPrice.id);
43
+
44
+ } catch (error) {
45
+ console.error('Error creating prices:', error.message);
46
+ }
47
+ }
48
+
49
+ createPrices();
@@ -0,0 +1,135 @@
1
+ // This script will run in production via Railway
2
+ // Execute with: railway run npx tsx scripts/fix-pricing-data.ts
3
+
4
+ import { PrismaClient, Provider } from '@prisma/client';
5
+
6
+ const prisma = new PrismaClient();
7
+
8
+ async function fixPricingData() {
9
+ console.log('๐Ÿ”ง Fixing pricing data...\n');
10
+
11
+ try {
12
+ // 1. Check current state
13
+ const currentPricing = await prisma.pricingModelPrice.findMany({
14
+ where: { provider: Provider.OPENAI }
15
+ });
16
+
17
+ console.log(`Current OpenAI models: ${currentPricing.length}`);
18
+ currentPricing.forEach((p: any) => {
19
+ console.log(` - ${p.canonicalModel}: $${p.unitInputPer1k}/1k input`);
20
+ });
21
+
22
+ // 2. Deactivate all current pricing versions
23
+ console.log('\n๐Ÿ“ฆ Deactivating current pricing versions...');
24
+ await prisma.pricingVersion.updateMany({
25
+ where: { isActive: true },
26
+ data: { isActive: false }
27
+ });
28
+
29
+ // 3. Delete bad pricing data (models with date names)
30
+ console.log('๐Ÿ—‘๏ธ Deleting bad pricing data...');
31
+ const badModels = await prisma.pricingModelPrice.deleteMany({
32
+ where: {
33
+ OR: [
34
+ { canonicalModel: { contains: '2024' } },
35
+ { canonicalModel: { contains: '2025' } },
36
+ { canonicalModel: 'at' },
37
+ { canonicalModel: { contains: '-09-' } },
38
+ { canonicalModel: { contains: '-06-' } },
39
+ ]
40
+ }
41
+ });
42
+ console.log(` Deleted ${badModels.count} bad model entries`);
43
+
44
+ // 4. Create proper OpenAI pricing
45
+ console.log('\nโœจ Creating correct OpenAI pricing...');
46
+ const openaiVersion = await prisma.pricingVersion.create({
47
+ data: {
48
+ provider: Provider.OPENAI,
49
+ isActive: true,
50
+ note: 'Official OpenAI pricing - manually fixed',
51
+ models: {
52
+ create: [
53
+ // GPT-4o models (latest)
54
+ { provider: Provider.OPENAI, canonicalModel: 'gpt-4o', unitInputPer1k: 0.005, unitOutputPer1k: 0.015 },
55
+ { provider: Provider.OPENAI, canonicalModel: 'gpt-4o-mini', unitInputPer1k: 0.00015, unitOutputPer1k: 0.0006 },
56
+
57
+ // GPT-4 Turbo
58
+ { provider: Provider.OPENAI, canonicalModel: 'gpt-4-turbo', unitInputPer1k: 0.01, unitOutputPer1k: 0.03 },
59
+ { provider: Provider.OPENAI, canonicalModel: 'gpt-4-turbo-preview', unitInputPer1k: 0.01, unitOutputPer1k: 0.03 },
60
+
61
+ // GPT-4
62
+ { provider: Provider.OPENAI, canonicalModel: 'gpt-4', unitInputPer1k: 0.03, unitOutputPer1k: 0.06 },
63
+ { provider: Provider.OPENAI, canonicalModel: 'gpt-4-32k', unitInputPer1k: 0.06, unitOutputPer1k: 0.12 },
64
+
65
+ // GPT-3.5
66
+ { provider: Provider.OPENAI, canonicalModel: 'gpt-3.5-turbo', unitInputPer1k: 0.0005, unitOutputPer1k: 0.0015 },
67
+ { provider: Provider.OPENAI, canonicalModel: 'gpt-3.5-turbo-16k', unitInputPer1k: 0.003, unitOutputPer1k: 0.004 },
68
+ ]
69
+ }
70
+ }
71
+ });
72
+
73
+ console.log(`โœ… Created pricing version ${openaiVersion.id} with proper model names`);
74
+
75
+ // 5. Create Anthropic pricing
76
+ console.log('\nโœจ Creating Anthropic pricing...');
77
+ const anthropicVersion = await prisma.pricingVersion.create({
78
+ data: {
79
+ provider: Provider.ANTHROPIC,
80
+ isActive: true,
81
+ note: 'Official Anthropic pricing - manually added',
82
+ models: {
83
+ create: [
84
+ // Claude 3.5
85
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-3.5-sonnet', unitInputPer1k: 0.003, unitOutputPer1k: 0.015 },
86
+
87
+ // Claude 3
88
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-opus', unitInputPer1k: 0.015, unitOutputPer1k: 0.075 },
89
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-sonnet', unitInputPer1k: 0.003, unitOutputPer1k: 0.015 },
90
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-haiku', unitInputPer1k: 0.00025, unitOutputPer1k: 0.00125 },
91
+
92
+ // Claude 2
93
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-2.1', unitInputPer1k: 0.008, unitOutputPer1k: 0.024 },
94
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-2.0', unitInputPer1k: 0.008, unitOutputPer1k: 0.024 },
95
+
96
+ // Claude Instant
97
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-instant-1.2', unitInputPer1k: 0.0008, unitOutputPer1k: 0.0024 },
98
+ ]
99
+ }
100
+ }
101
+ });
102
+
103
+ console.log(`โœ… Created Anthropic pricing version ${anthropicVersion.id}`);
104
+
105
+ // 6. Verify the fix
106
+ console.log('\n๐Ÿ” Verifying the fix...');
107
+ const fixedPricing = await prisma.pricingModelPrice.findMany({
108
+ where: {
109
+ versionId: { in: [openaiVersion.id, anthropicVersion.id] }
110
+ },
111
+ orderBy: { provider: 'asc' }
112
+ });
113
+
114
+ console.log(`\nFixed pricing data (${fixedPricing.length} models):`);
115
+ let currentProvider = '';
116
+ fixedPricing.forEach((p: any) => {
117
+ if (p.provider !== currentProvider) {
118
+ currentProvider = p.provider;
119
+ console.log(`\n${currentProvider}:`);
120
+ }
121
+ console.log(` - ${p.canonicalModel}: $${p.unitInputPer1k}/1k input, $${p.unitOutputPer1k}/1k output`);
122
+ });
123
+
124
+ console.log('\nโœ… Pricing data fixed successfully!');
125
+
126
+ } catch (error) {
127
+ console.error('โŒ Error fixing pricing data:', error);
128
+ throw error;
129
+ } finally {
130
+ await prisma.$disconnect();
131
+ }
132
+ }
133
+
134
+ // Run the fix
135
+ fixPricingData().catch(console.error);
@@ -0,0 +1,148 @@
1
+ /**
2
+ * One-time script to fix pricing database with REAL API model identifiers
3
+ * Run with: npx tsx scripts/fix-pricing-db.ts
4
+ */
5
+
6
+ import * as dotenv from 'dotenv';
7
+ dotenv.config({ path: '.env.local' });
8
+
9
+ import { PrismaClient } from '@prisma/client';
10
+
11
+ const prisma = new PrismaClient();
12
+
13
+ enum Provider {
14
+ OPENAI = 'OPENAI',
15
+ ANTHROPIC = 'ANTHROPIC',
16
+ GOOGLE = 'GOOGLE',
17
+ }
18
+
19
+ async function fixPricing() {
20
+ console.log('Starting pricing fix...\n');
21
+
22
+ try {
23
+ // 1. Deactivate all current pricing versions
24
+ const deactivated = await prisma.pricingVersion.updateMany({
25
+ where: { isActive: true },
26
+ data: { isActive: false }
27
+ });
28
+ console.log(`Deactivated ${deactivated.count} pricing versions`);
29
+
30
+ // 2. Delete ALL existing pricing data
31
+ const deleted = await prisma.pricingModelPrice.deleteMany({});
32
+ console.log(`Deleted ${deleted.count} old pricing entries\n`);
33
+
34
+ // 3. Create OpenAI pricing with REAL API model identifiers
35
+ console.log('Creating OpenAI pricing...');
36
+ const openaiVersion = await prisma.pricingVersion.create({
37
+ data: {
38
+ provider: Provider.OPENAI,
39
+ isActive: true,
40
+ note: 'Official OpenAI pricing - December 2025 (real API model IDs)',
41
+ models: {
42
+ create: [
43
+ { provider: Provider.OPENAI, canonicalModel: 'gpt-4o', unitInputPer1k: 0.0025, unitOutputPer1k: 0.01 },
44
+ { provider: Provider.OPENAI, canonicalModel: 'gpt-4o-mini', unitInputPer1k: 0.00015, unitOutputPer1k: 0.0006 },
45
+ { provider: Provider.OPENAI, canonicalModel: 'chatgpt-4o-latest', unitInputPer1k: 0.005, unitOutputPer1k: 0.015 },
46
+ { provider: Provider.OPENAI, canonicalModel: 'o1', unitInputPer1k: 0.015, unitOutputPer1k: 0.06 },
47
+ { provider: Provider.OPENAI, canonicalModel: 'o1-mini', unitInputPer1k: 0.003, unitOutputPer1k: 0.012 },
48
+ { provider: Provider.OPENAI, canonicalModel: 'o1-preview', unitInputPer1k: 0.015, unitOutputPer1k: 0.06 },
49
+ { provider: Provider.OPENAI, canonicalModel: 'gpt-4-turbo', unitInputPer1k: 0.01, unitOutputPer1k: 0.03 },
50
+ { provider: Provider.OPENAI, canonicalModel: 'gpt-4-turbo-preview', unitInputPer1k: 0.01, unitOutputPer1k: 0.03 },
51
+ { provider: Provider.OPENAI, canonicalModel: 'gpt-4', unitInputPer1k: 0.03, unitOutputPer1k: 0.06 },
52
+ { provider: Provider.OPENAI, canonicalModel: 'gpt-3.5-turbo', unitInputPer1k: 0.0005, unitOutputPer1k: 0.0015 },
53
+ { provider: Provider.OPENAI, canonicalModel: 'gpt-3.5-turbo-0125', unitInputPer1k: 0.0005, unitOutputPer1k: 0.0015 },
54
+ ]
55
+ }
56
+ },
57
+ include: { models: true }
58
+ });
59
+ console.log(` Created ${openaiVersion.models.length} OpenAI models`);
60
+
61
+ // 4. Create Anthropic pricing with all model identifiers
62
+ console.log('Creating Anthropic pricing...');
63
+ const anthropicVersion = await prisma.pricingVersion.create({
64
+ data: {
65
+ provider: Provider.ANTHROPIC,
66
+ isActive: true,
67
+ note: 'Official Anthropic pricing - December 2025 including Claude 4.5',
68
+ models: {
69
+ create: [
70
+ // Claude 4.5 models - FLAGSHIP (December 2025)
71
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-opus-4-5', unitInputPer1k: 0.005, unitOutputPer1k: 0.025 },
72
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-opus-4-5-20251101', unitInputPer1k: 0.005, unitOutputPer1k: 0.025 },
73
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-sonnet-4-5', unitInputPer1k: 0.003, unitOutputPer1k: 0.015 },
74
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-sonnet-4-5-20250929', unitInputPer1k: 0.003, unitOutputPer1k: 0.015 },
75
+ // Claude 3.5 Sonnet - widely used
76
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-5-sonnet-20241022', unitInputPer1k: 0.003, unitOutputPer1k: 0.015 },
77
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-5-sonnet-latest', unitInputPer1k: 0.003, unitOutputPer1k: 0.015 },
78
+ // Claude 3.5 Haiku
79
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-5-haiku-20241022', unitInputPer1k: 0.0008, unitOutputPer1k: 0.004 },
80
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-5-haiku-latest', unitInputPer1k: 0.0008, unitOutputPer1k: 0.004 },
81
+ // Claude 3 Opus
82
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-opus-20240229', unitInputPer1k: 0.015, unitOutputPer1k: 0.075 },
83
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-opus-latest', unitInputPer1k: 0.015, unitOutputPer1k: 0.075 },
84
+ // Claude 3 Sonnet
85
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-sonnet-20240229', unitInputPer1k: 0.003, unitOutputPer1k: 0.015 },
86
+ // Claude 3 Haiku - fastest
87
+ { provider: Provider.ANTHROPIC, canonicalModel: 'claude-3-haiku-20240307', unitInputPer1k: 0.00025, unitOutputPer1k: 0.00125 },
88
+ ]
89
+ }
90
+ },
91
+ include: { models: true }
92
+ });
93
+ console.log(` Created ${anthropicVersion.models.length} Anthropic models`);
94
+
95
+ // 5. Create Google Gemini pricing
96
+ console.log('Creating Google Gemini pricing...');
97
+ const googleVersion = await prisma.pricingVersion.create({
98
+ data: {
99
+ provider: Provider.GOOGLE,
100
+ isActive: true,
101
+ note: 'Official Google Gemini pricing - December 2025 (real API model IDs)',
102
+ models: {
103
+ create: [
104
+ { provider: Provider.GOOGLE, canonicalModel: 'gemini-2.0-flash-exp', unitInputPer1k: 0.0001, unitOutputPer1k: 0.0004 },
105
+ { provider: Provider.GOOGLE, canonicalModel: 'gemini-1.5-pro', unitInputPer1k: 0.00125, unitOutputPer1k: 0.005 },
106
+ { provider: Provider.GOOGLE, canonicalModel: 'gemini-1.5-pro-latest', unitInputPer1k: 0.00125, unitOutputPer1k: 0.005 },
107
+ { provider: Provider.GOOGLE, canonicalModel: 'gemini-1.5-flash', unitInputPer1k: 0.000075, unitOutputPer1k: 0.0003 },
108
+ { provider: Provider.GOOGLE, canonicalModel: 'gemini-1.5-flash-latest', unitInputPer1k: 0.000075, unitOutputPer1k: 0.0003 },
109
+ { provider: Provider.GOOGLE, canonicalModel: 'gemini-1.5-flash-8b', unitInputPer1k: 0.0000375, unitOutputPer1k: 0.00015 },
110
+ { provider: Provider.GOOGLE, canonicalModel: 'gemini-pro', unitInputPer1k: 0.0005, unitOutputPer1k: 0.0015 },
111
+ ]
112
+ }
113
+ },
114
+ include: { models: true }
115
+ });
116
+ console.log(` Created ${googleVersion.models.length} Google models`);
117
+
118
+ // Summary
119
+ console.log('\n=== PRICING FIX COMPLETE ===');
120
+ console.log(`Total models created: ${openaiVersion.models.length + anthropicVersion.models.length + googleVersion.models.length}`);
121
+ console.log('\nModels by provider:');
122
+
123
+ console.log('\nOpenAI:');
124
+ openaiVersion.models.forEach(m => console.log(` - ${m.canonicalModel}`));
125
+
126
+ console.log('\nAnthropic:');
127
+ anthropicVersion.models.forEach(m => console.log(` - ${m.canonicalModel}`));
128
+
129
+ console.log('\nGoogle:');
130
+ googleVersion.models.forEach(m => console.log(` - ${m.canonicalModel}`));
131
+
132
+ } catch (error) {
133
+ console.error('Error fixing pricing:', error);
134
+ throw error;
135
+ } finally {
136
+ await prisma.$disconnect();
137
+ }
138
+ }
139
+
140
+ fixPricing()
141
+ .then(() => {
142
+ console.log('\nDone! Pricing database has been fixed with real API model identifiers.');
143
+ process.exit(0);
144
+ })
145
+ .catch((error) => {
146
+ console.error('Failed to fix pricing:', error);
147
+ process.exit(1);
148
+ });
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Railway-Safe Postinstall Script
5
+ *
6
+ * Only generates Prisma client if we have a database URL.
7
+ * This prevents hanging during Railway's npm install phase.
8
+ */
9
+
10
+ const { execSync } = require('child_process');
11
+
12
+ function log(message, type = 'info') {
13
+ const timestamp = new Date().toISOString();
14
+ const prefix = type === 'error' ? 'โŒ' : type === 'success' ? 'โœ…' : 'โ„น๏ธ';
15
+ console.log(`${prefix} [${timestamp}] POSTINSTALL: ${message}`);
16
+ }
17
+
18
+ function main() {
19
+ log('Starting postinstall script...');
20
+
21
+ // Check if we're in Railway and have database access
22
+ const hasDatabase = process.env.DATABASE_URL && process.env.DATABASE_URL.length > 0;
23
+ const isRailway = process.env.RAILWAY_ENVIRONMENT || process.env.RAILWAY_SERVICE_NAME;
24
+ const isNixpacksBuild = process.env.NIXPACKS_BUILD || process.env.NIXPACKS_METADATA;
25
+
26
+ // Skip Prisma generation during Railway build phase (npm install)
27
+ if (isRailway && isNixpacksBuild && !hasDatabase) {
28
+ log('Railway build phase detected - skipping Prisma generate to prevent hanging');
29
+ log('Prisma client will be generated during the build script instead');
30
+ return;
31
+ }
32
+
33
+ if (isRailway && !hasDatabase) {
34
+ log('Railway environment detected but no DATABASE_URL - skipping Prisma generate');
35
+ log('Prisma client will be generated at runtime instead');
36
+ return;
37
+ }
38
+
39
+ if (!hasDatabase) {
40
+ log('No DATABASE_URL found - skipping Prisma generate (development mode)');
41
+ return;
42
+ }
43
+
44
+ try {
45
+ log('DATABASE_URL detected - generating Prisma client...');
46
+ execSync('npx prisma generate', {
47
+ stdio: 'inherit',
48
+ cwd: process.cwd()
49
+ });
50
+ log('Prisma client generated successfully', 'success');
51
+ } catch (error) {
52
+ log(`Prisma generate failed: ${error.message}`, 'error');
53
+ // Don't fail the install process - we'll handle this in build
54
+ log('Continuing with install - will retry during build phase');
55
+ }
56
+ }
57
+
58
+ main();
@@ -0,0 +1,52 @@
1
+ #!/bin/bash
2
+
3
+ # Railway Deployment Script
4
+ # This script runs AFTER deployment when database is accessible
5
+
6
+ set -e # Exit on any error
7
+
8
+ echo "๐Ÿš€ Starting Railway deployment process..."
9
+
10
+ # Check if we're in Railway environment
11
+ if [ -z "$RAILWAY_ENVIRONMENT" ]; then
12
+ echo "โŒ Not in Railway environment. This script is for Railway deployment only."
13
+ exit 1
14
+ fi
15
+
16
+ # Check required environment variables
17
+ required_vars=("DATABASE_URL" "CLERK_SECRET_KEY" "ENCRYPTION_KEY")
18
+ for var in "${required_vars[@]}"; do
19
+ if [ -z "${!var}" ]; then
20
+ echo "โŒ Missing required environment variable: $var"
21
+ exit 1
22
+ fi
23
+ done
24
+
25
+ echo "โœ… Environment variables validated"
26
+
27
+ # Generate Prisma client (should already be done, but ensure it's there)
28
+ echo "๐Ÿ“ฆ Generating Prisma client..."
29
+ npx prisma generate
30
+
31
+ # Push database schema (idempotent - safe to run multiple times)
32
+ echo "๐Ÿ—„๏ธ Pushing database schema..."
33
+ npx prisma db push --accept-data-loss
34
+
35
+ # Validate database connection
36
+ echo "๐Ÿ” Validating database connection..."
37
+ npx prisma db execute --stdin <<EOF
38
+ SELECT 1 as test;
39
+ EOF
40
+
41
+ echo "โœ… Database connection validated"
42
+
43
+ # Optional: Run any seeds or migrations
44
+ if [ "$RAILWAY_ENVIRONMENT" = "production" ]; then
45
+ echo "๐ŸŒฑ Running production setup..."
46
+ # Add any production-specific setup here
47
+ else
48
+ echo "๐Ÿงช Running non-production setup..."
49
+ # Add any staging/dev setup here
50
+ fi
51
+
52
+ echo "๐ŸŽ‰ Railway deployment completed successfully!"
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CRITICAL PRODUCTION MIGRATION SCRIPT
5
+ * Run this in Railway to fix database schema mismatch
6
+ *
7
+ * Usage: node scripts/run-migration.js
8
+ */
9
+
10
+ const { execSync } = require('child_process');
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ console.log('๐Ÿšจ CRITICAL PRODUCTION MIGRATION STARTING...');
15
+ console.log('This will add missing Stripe fields to the User table');
16
+
17
+ // Check if we're in production
18
+ if (process.env.NODE_ENV !== 'production') {
19
+ console.warn('โš ๏ธ WARNING: Not running in production environment');
20
+ console.log('Set NODE_ENV=production if this is intentional');
21
+ }
22
+
23
+ // Verify database connection
24
+ if (!process.env.DATABASE_URL) {
25
+ console.error('โŒ ERROR: DATABASE_URL not found');
26
+ console.error('Make sure environment variables are properly set');
27
+ process.exit(1);
28
+ }
29
+
30
+ try {
31
+ console.log('๐Ÿ“‹ Step 1: Generating Prisma client...');
32
+ execSync('npx prisma generate', { stdio: 'inherit' });
33
+
34
+ console.log('๐Ÿ“‹ Step 2: Checking current database schema...');
35
+ try {
36
+ execSync('npx prisma db pull --force', { stdio: 'inherit' });
37
+ console.log('โœ… Database connection verified');
38
+ } catch (error) {
39
+ console.warn('โš ๏ธ Could not pull schema, proceeding with migration...');
40
+ }
41
+
42
+ console.log('๐Ÿ“‹ Step 3: Applying migration to add Stripe fields...');
43
+
44
+ // Read and execute the migration SQL
45
+ const migrationPath = path.join(__dirname, '../prisma/migrations/001_add_stripe_fields.sql');
46
+ if (!fs.existsSync(migrationPath)) {
47
+ console.error('โŒ ERROR: Migration file not found at:', migrationPath);
48
+ process.exit(1);
49
+ }
50
+
51
+ console.log('๐Ÿ“‹ Step 4: Pushing schema changes to database...');
52
+ execSync('npx prisma db push --force-reset=false', { stdio: 'inherit' });
53
+
54
+ console.log('โœ… SUCCESS: Database migration completed!');
55
+ console.log('๐Ÿ“‹ Next: Update Railway environment variables with production keys');
56
+
57
+ } catch (error) {
58
+ console.error('โŒ MIGRATION FAILED:', error.message);
59
+ console.error('Contact support immediately - database may be in inconsistent state');
60
+ process.exit(1);
61
+ }