Skip to content

Commit cb070f6

Browse files
committed
script for freebuff usage
1 parent 67e6256 commit cb070f6

File tree

1 file changed

+100
-0
lines changed

1 file changed

+100
-0
lines changed

scripts/top-freebuff-users.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { db } from '@codebuff/internal/db'
2+
import * as schema from '@codebuff/internal/db/schema'
3+
import { sql } from 'drizzle-orm'
4+
5+
async function topFreebuffUsers() {
6+
const hoursBack = parseInt(process.argv[2] || '72')
7+
const limit = parseInt(process.argv[3] || '200')
8+
const cutoff = new Date(Date.now() - hoursBack * 60 * 60 * 1000)
9+
10+
console.log(`\nTop ${limit} Freebuff-only users by message count (last ${hoursBack} hours)`)
11+
console.log(`Since: ${cutoff.toISOString()}`)
12+
console.log('Excluding users with any base2 or base2-max messages in this period')
13+
console.log('─'.repeat(90))
14+
15+
// Count messages per user where the agent is base2-free
16+
const results = await db
17+
.select({
18+
userId: schema.message.user_id,
19+
email: schema.user.email,
20+
messageCount: sql<string>`COUNT(*)`,
21+
totalCredits: sql<string>`COALESCE(SUM(${schema.message.credits}), 0)`,
22+
totalCost: sql<string>`COALESCE(SUM(${schema.message.cost}), 0)`,
23+
lastMessage: sql<string>`MAX(${schema.message.finished_at})`,
24+
})
25+
.from(schema.message)
26+
.leftJoin(schema.user, sql`${schema.message.user_id} = ${schema.user.id}`)
27+
.where(
28+
sql`${schema.message.finished_at} >= ${cutoff.toISOString()}
29+
AND ${schema.message.agent_id} = 'base2-free'
30+
AND ${schema.message.user_id} NOT IN (
31+
SELECT ${schema.message.user_id}
32+
FROM ${schema.message}
33+
WHERE ${schema.message.agent_id} IN ('base2', 'base2-max')
34+
AND ${schema.message.finished_at} >= ${cutoff.toISOString()}
35+
)`,
36+
)
37+
.groupBy(schema.message.user_id, schema.user.email)
38+
.orderBy(sql`COUNT(*) DESC`)
39+
.limit(limit)
40+
41+
if (results.length === 0) {
42+
console.log('\nNo Freebuff (base2-free) messages found in this time range.')
43+
return
44+
}
45+
46+
// Print header
47+
console.log(
48+
`\n${'#'.padStart(4)} ${'Email'.padEnd(40)} ${'Messages'.padStart(10)} ${'Credits'.padStart(10)} ${'Cost'.padStart(10)} ${'Last Active'.padStart(20)}`,
49+
)
50+
console.log('─'.repeat(100))
51+
52+
let totalMessages = 0
53+
let totalCost = 0
54+
55+
for (let i = 0; i < results.length; i++) {
56+
const r = results[i]
57+
const msgCount = parseInt(r.messageCount)
58+
const cost = parseFloat(r.totalCost)
59+
const credits = parseInt(r.totalCredits)
60+
totalMessages += msgCount
61+
totalCost += cost
62+
63+
const emailDisplay = r.email
64+
? r.email.length > 38
65+
? r.email.slice(0, 35) + '...'
66+
: r.email
67+
: r.userId ?? 'unknown'
68+
69+
const lastActive = r.lastMessage
70+
? new Date(r.lastMessage).toISOString().replace('T', ' ').slice(0, 16)
71+
: 'N/A'
72+
73+
console.log(
74+
`${String(i + 1).padStart(4)} ${emailDisplay.padEnd(40)} ${msgCount.toLocaleString().padStart(10)} ${credits.toLocaleString().padStart(10)} ${('$' + cost.toFixed(2)).padStart(10)} ${lastActive.padStart(20)}`,
75+
)
76+
}
77+
78+
console.log('─'.repeat(100))
79+
console.log(
80+
`\nTotal: ${results.length} users, ${totalMessages.toLocaleString()} messages, $${totalCost.toFixed(2)} cost`,
81+
)
82+
83+
const highUsageEmails = results
84+
.filter((r) => parseInt(r.messageCount) >= 50 && r.email)
85+
.map((r) => r.email)
86+
87+
if (highUsageEmails.length > 0) {
88+
console.log(`\n── Users with ≥50 messages (${highUsageEmails.length}) ──`)
89+
console.log(highUsageEmails.join(', '))
90+
} else {
91+
console.log('\nNo users with ≥50 messages.')
92+
}
93+
}
94+
95+
topFreebuffUsers()
96+
.then(() => process.exit(0))
97+
.catch((err) => {
98+
console.error(err)
99+
process.exit(1)
100+
})

0 commit comments

Comments
 (0)