← Public packages
@kentcdodds/grok-bot
Wake a named Grok Bot from Kody by posting a prompt to that bot's own Cursor webhook.
src/roster-fingerprints.ts
142 lines · 4.2 KB · TypeScriptimport { plainClone } from './plain.ts'
import { listBotRecords, urlHostOf, type BotRecord } from './registry.ts'
export type BotUrlFingerprint = {
id: string
name: string
updatedAt: string
createdAt: string
channelId?: string
urlHost: string
/** Pathname with the final segment removed (e.g. `/automations/webhook`). */
pathPrefix: string
/** Number of non-empty path segments. */
pathDepth: number
/** Length of the final path segment (never the segment itself). */
lastSegLen: number
/** Stable non-reversible hex digest of the final path segment (SHA-256, 16 chars). */
lastSegHash: string
/** Full registered URL string length (shape only; never the URL). */
urlLen: number
hasQuery: boolean
}
export type RosterFingerprintsResult = {
bots: BotUrlFingerprint[]
/** Bot names that share the same pathPrefix+lastSegHash+lastSegLen+urlLen key. */
duplicateGroups: string[][]
}
function hexOf(buffer: ArrayBuffer): string {
return [...new Uint8Array(buffer)]
.map((byte) => byte.toString(16).padStart(2, '0'))
.join('')
}
async function hashLastSegment(segment: string): Promise<string> {
const digest = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(segment),
)
return hexOf(digest).slice(0, 16)
}
function pathParts(pathname: string): {
pathPrefix: string
lastSeg: string
pathDepth: number
} {
const normalized = pathname.replace(/\/+$/, '') || '/'
const depth = normalized === '/' ? 0 : normalized.split('/').filter(Boolean).length
const lastSlash = normalized.lastIndexOf('/')
if (lastSlash <= 0) {
return {
pathPrefix: '/',
lastSeg: lastSlash === 0 ? normalized.slice(1) : normalized,
pathDepth: depth,
}
}
return {
pathPrefix: normalized.slice(0, lastSlash) || '/',
lastSeg: normalized.slice(lastSlash + 1),
pathDepth: depth,
}
}
async function fingerprintOf(record: BotRecord): Promise<BotUrlFingerprint> {
const base: BotUrlFingerprint = {
id: record.id,
name: record.name,
updatedAt: record.updatedAt,
createdAt: record.createdAt,
urlHost: '',
pathPrefix: '',
pathDepth: 0,
lastSegLen: 0,
lastSegHash: '',
urlLen: 0,
hasQuery: false,
...(record.channelId ? { channelId: record.channelId } : {}),
}
const raw = typeof record.url === 'string' ? record.url.trim() : ''
if (!raw) return base
base.urlLen = raw.length
base.urlHost = urlHostOf(raw)
try {
const parsed = new URL(raw)
const parts = pathParts(parsed.pathname)
base.pathPrefix = parts.pathPrefix
base.pathDepth = parts.pathDepth
base.lastSegLen = parts.lastSeg.length
base.lastSegHash = parts.lastSeg
? await hashLastSegment(parts.lastSeg)
: ''
base.hasQuery = parsed.search.length > 1
} catch {
// Leave shape fields empty when the stored URL cannot be parsed.
}
return base
}
function fingerprintKey(bot: BotUrlFingerprint): string {
return `${bot.pathPrefix}|${bot.lastSegHash}|${bot.lastSegLen}|${bot.urlLen}`
}
/**
* Safe webhook-shape fingerprints for every registered Grok Bot.
*
* Never returns raw webhook URLs or Authorization material. Use this to
* compare Cole vs Patch path shape / detect duplicate webhook registrations
* without leaking secrets.
*
* @returns bots with host/path shape fields plus duplicateGroups of names
* @example
* import fingerprints from 'kody:@kentcdodds/grok-bot/roster-fingerprints'
* const { bots, duplicateGroups } = await fingerprints()
* // => { bots: [{ id, name, urlHost, pathPrefix, pathDepth, lastSegLen, lastSegHash, urlLen, hasQuery, ... }], duplicateGroups }
*/
export default async function rosterFingerprints(): Promise<RosterFingerprintsResult> {
const records = await listBotRecords()
const bots = await Promise.all(records.map((record) => fingerprintOf(record)))
bots.sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id))
const groups = new Map<string, string[]>()
for (const bot of bots) {
if (!bot.lastSegHash && bot.urlLen === 0) continue
const key = fingerprintKey(bot)
const names = groups.get(key) ?? []
names.push(bot.name)
groups.set(key, names)
}
const duplicateGroups = [...groups.values()]
.filter((names) => names.length > 1)
.map((names) => [...names].sort((a, b) => a.localeCompare(b)))
.sort((a, b) => a[0]!.localeCompare(b[0]!))
return plainClone({ bots, duplicateGroups })
}