Skip to content

Commit d3b7ef6

Browse files
committed
scripts
1 parent 9099292 commit d3b7ef6

File tree

4 files changed

+238
-0
lines changed

4 files changed

+238
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ npm-app/src/__tests__/data/
2323
**.log
2424

2525
debug/
26+
docs/bot-detection.md
2627

2728
# Nx cache directories
2829
.nx/cache

scripts/ban-freebuff-bots.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { readFileSync } from 'fs'
2+
3+
import db from '@codebuff/internal/db'
4+
import * as schema from '@codebuff/internal/db/schema'
5+
import { eq, inArray, sql } from 'drizzle-orm'
6+
7+
const args = process.argv.slice(2).filter((a) => !a.startsWith('--'))
8+
const BAN_FILE =
9+
args[0] ?? '/Users/jahooma/codebuff/debug/freebuff-ban-candidates.txt'
10+
const DRY_RUN = !process.argv.includes('--commit')
11+
12+
function parseEmails(path: string): string[] {
13+
const emails: string[] = []
14+
for (const raw of readFileSync(path, 'utf8').split('\n')) {
15+
const line = raw.replace(/\r$/, '')
16+
if (!line || line.startsWith('#')) continue
17+
// Strip inline comments
18+
const code = line.split('#')[0].trim()
19+
if (!code) continue
20+
// The whole non-comment chunk IS the email (possibly with trailing whitespace)
21+
const email = code.trim()
22+
if (email.includes('@')) emails.push(email.toLowerCase())
23+
}
24+
return [...new Set(emails)]
25+
}
26+
27+
async function main() {
28+
const emails = parseEmails(BAN_FILE)
29+
console.log(`parsed ${emails.length} distinct emails from ${BAN_FILE}`)
30+
31+
// Look up users (case-insensitive match)
32+
const users = await db
33+
.select({
34+
id: schema.user.id,
35+
email: schema.user.email,
36+
name: schema.user.name,
37+
banned: schema.user.banned,
38+
created_at: schema.user.created_at,
39+
})
40+
.from(schema.user)
41+
.where(
42+
sql`lower(${schema.user.email}) IN (${sql.join(
43+
emails.map((e) => sql`${e}`),
44+
sql`, `,
45+
)})`,
46+
)
47+
48+
const foundEmails = new Set(users.map((u) => u.email.toLowerCase()))
49+
const missing = emails.filter((e) => !foundEmails.has(e))
50+
51+
console.log(`matched ${users.length} users in DB`)
52+
if (missing.length) {
53+
console.log(`\nNOT FOUND in user table (${missing.length}):`)
54+
for (const e of missing) console.log(` ${e}`)
55+
}
56+
57+
const alreadyBanned = users.filter((u) => u.banned)
58+
const toBan = users.filter((u) => !u.banned)
59+
console.log(`\nalready banned: ${alreadyBanned.length}`)
60+
console.log(`will ban: ${toBan.length}`)
61+
for (const u of toBan) {
62+
console.log(
63+
` ${u.email.padEnd(40)} "${u.name ?? ''}" (created ${u.created_at.toISOString()})`,
64+
)
65+
}
66+
67+
if (DRY_RUN) {
68+
console.log(
69+
`\nDRY RUN — pass --commit to actually set banned=true and delete free_session rows.`,
70+
)
71+
return
72+
}
73+
74+
if (toBan.length === 0) {
75+
console.log('\nnothing to do.')
76+
return
77+
}
78+
79+
const ids = toBan.map((u) => u.id)
80+
81+
const updated = await db
82+
.update(schema.user)
83+
.set({ banned: true })
84+
.where(inArray(schema.user.id, ids))
85+
.returning({ id: schema.user.id, email: schema.user.email })
86+
87+
console.log(`\n✅ banned ${updated.length} users`)
88+
89+
// Also clear their free_session rows so admitted slots free up immediately
90+
const deleted = await db
91+
.delete(schema.freeSession)
92+
.where(inArray(schema.freeSession.user_id, ids))
93+
.returning({ user_id: schema.freeSession.user_id })
94+
95+
console.log(`✅ deleted ${deleted.length} free_session rows`)
96+
}
97+
98+
main()
99+
.then(() => process.exit(0))
100+
.catch((err) => {
101+
console.error(err)
102+
process.exit(1)
103+
})

scripts/investigate-user.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import db from '@codebuff/internal/db'
2+
import * as schema from '@codebuff/internal/db/schema'
3+
import { sql, eq, desc } from 'drizzle-orm'
4+
5+
async function main() {
6+
const email = process.argv[2]
7+
if (!email) {
8+
console.error('usage: bun scripts/investigate-user.ts <email>')
9+
process.exit(1)
10+
}
11+
12+
const users = await db
13+
.select()
14+
.from(schema.user)
15+
.where(sql`lower(${schema.user.email}) = ${email.toLowerCase()}`)
16+
17+
if (users.length === 0) {
18+
console.log('user not found')
19+
return
20+
}
21+
const u = users[0]
22+
console.log('=== user ===')
23+
console.log(JSON.stringify({
24+
id: u.id,
25+
email: u.email,
26+
name: u.name,
27+
handle: u.handle,
28+
banned: u.banned,
29+
created_at: u.created_at,
30+
emailVerified: u.emailVerified,
31+
image: u.image,
32+
}, null, 2))
33+
34+
const accounts = await db
35+
.select()
36+
.from(schema.account)
37+
.where(eq(schema.account.userId, u.id))
38+
console.log('\n=== accounts ===')
39+
for (const a of accounts) {
40+
console.log(` provider=${a.provider} providerAccountId=${a.providerAccountId} scope=${a.scope ?? ''}`)
41+
}
42+
43+
const stats = await db
44+
.select({
45+
agent_id: schema.message.agent_id,
46+
count: sql<number>`COUNT(*)`,
47+
totalCost: sql<number>`SUM(${schema.message.cost})`,
48+
first: sql<string>`MIN(${schema.message.finished_at})`,
49+
last: sql<string>`MAX(${schema.message.finished_at})`,
50+
})
51+
.from(schema.message)
52+
.where(eq(schema.message.user_id, u.id))
53+
.groupBy(schema.message.agent_id)
54+
console.log('\n=== messages by agent ===')
55+
for (const s of stats) {
56+
console.log(` ${s.agent_id}: ${s.count} msgs, $${Number(s.totalCost).toFixed(2)}, ${s.first}${s.last}`)
57+
}
58+
59+
const repos = await db
60+
.select({
61+
repo_url: schema.message.repo_url,
62+
count: sql<number>`COUNT(*)`,
63+
})
64+
.from(schema.message)
65+
.where(eq(schema.message.user_id, u.id))
66+
.groupBy(schema.message.repo_url)
67+
.orderBy(desc(sql`COUNT(*)`))
68+
.limit(20)
69+
console.log('\n=== repos touched ===')
70+
for (const r of repos) {
71+
console.log(` ${r.count.toString().padStart(5)} ${r.repo_url ?? '(null)'}`)
72+
}
73+
74+
const sample = await db
75+
.select({
76+
finished_at: schema.message.finished_at,
77+
agent_id: schema.message.agent_id,
78+
repo_url: schema.message.repo_url,
79+
input_tokens: schema.message.input_tokens,
80+
output_tokens: schema.message.output_tokens,
81+
cost: schema.message.cost,
82+
lastMessage: schema.message.lastMessage,
83+
})
84+
.from(schema.message)
85+
.where(eq(schema.message.user_id, u.id))
86+
.orderBy(desc(schema.message.finished_at))
87+
.limit(5)
88+
console.log('\n=== 5 most recent messages (last user turn) ===')
89+
for (const m of sample) {
90+
console.log(`\n ${m.finished_at.toISOString()} agent=${m.agent_id} repo=${m.repo_url ?? ''} in=${m.input_tokens} out=${m.output_tokens} cost=$${Number(m.cost).toFixed(4)}`)
91+
const msg = m.lastMessage as any
92+
const content = typeof msg?.content === 'string' ? msg.content : JSON.stringify(msg?.content)?.slice(0, 500)
93+
console.log(` role=${msg?.role} content=${(content ?? '').slice(0, 500)}`)
94+
}
95+
96+
// Session/CLI usage
97+
const sessions = await db
98+
.select({
99+
type: schema.session.type,
100+
created_at: schema.session.created_at,
101+
fingerprint_id: schema.session.fingerprint_id,
102+
})
103+
.from(schema.session)
104+
.where(eq(schema.session.userId, u.id))
105+
.orderBy(desc(schema.session.created_at))
106+
.limit(10)
107+
console.log('\n=== recent sessions ===')
108+
for (const s of sessions) {
109+
console.log(` ${s.created_at.toISOString()} type=${s.type} fp=${s.fingerprint_id ?? ''}`)
110+
}
111+
}
112+
113+
main().then(() => process.exit(0)).catch((e) => { console.error(e); process.exit(1) })

scripts/unban-user.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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 main() {
6+
const emails = process.argv.slice(2).map((e) => e.toLowerCase())
7+
if (!emails.length) { console.error('usage: bun scripts/unban-user.ts <email> [<email> ...]'); process.exit(1) }
8+
9+
const res = await db
10+
.update(schema.user)
11+
.set({ banned: false })
12+
.where(sql`lower(${schema.user.email}) IN (${sql.join(emails.map((e) => sql`${e}`), sql`, `)})`)
13+
.returning({ id: schema.user.id, email: schema.user.email, banned: schema.user.banned })
14+
15+
console.log(`unbanned ${res.length} users:`)
16+
for (const r of res) console.log(` ${r.email}`)
17+
const missing = emails.filter((e) => !res.some((r) => r.email.toLowerCase() === e))
18+
if (missing.length) { console.log(`\nno match for:`); for (const m of missing) console.log(` ${m}`) }
19+
}
20+
21+
main().then(() => process.exit(0)).catch((e) => { console.error(e); process.exit(1) })

0 commit comments

Comments
 (0)