import { kody, packageContext, packageStorage } from 'kody:runtime'
/** Saved package UUID from package_get_git_remote create. Fallback when packageContext is null. */
export const SAVED_PACKAGE_ID = '5bddf97f-09c7-41eb-8402-a7102432dd2e'
export const WEBHOOK_HOST = 'api2.cursor.sh'
export const BOT_PREFIX = 'bot:'
export const ALIAS_PREFIX = 'alias:'
const UUID_LIKE =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
export type BotRecord = {
id: string
name: string
aliases: string[]
url: string
secretName: string
channelId?: string
createdAt: string
updatedAt: string
}
export type BotSummary = {
id: string
name: string
aliases: string[]
secretName: string
pendingSecret: boolean
urlHost: string
channelId?: string
}
export function normalizeName(name: string): string {
return name.trim().toLowerCase()
}
export function isUuidLike(value: string): boolean {
return UUID_LIKE.test(value.trim())
}
export function secretNameFor(id: string): string {
return `grokBotWake.${id}`
}
export function botKey(id: string): string {
return `${BOT_PREFIX}${id}`
}
export function aliasKey(normalized: string): string {
return `${ALIAS_PREFIX}${normalized}`
}
export function savedPackageId(): string {
const fromContext = packageContext?.packageId?.trim()
return fromContext || SAVED_PACKAGE_ID
}
export function requireId(id: unknown): string {
if (typeof id !== 'string' || id.trim() === '') {
throw new Error('id is required and must be a UUID.')
}
const trimmed = id.trim()
if (!isUuidLike(trimmed)) {
throw new Error('id must be a UUID-like value (8-4-4-4-12 hex).')
}
return trimmed
}
export function requireName(name: unknown): string {
if (typeof name !== 'string' || name.trim() === '') {
throw new Error('name is required.')
}
return name.trim()
}
export function requirePrompt(prompt: unknown): string {
if (typeof prompt !== 'string' || prompt.trim() === '') {
throw new Error('prompt is required.')
}
return prompt
}
export function requireBotQuery(bot: unknown): string {
if (typeof bot !== 'string' || bot.trim() === '') {
throw new Error('bot is required (id or name).')
}
return bot.trim()
}
export function parseWebhookUrl(url: unknown): URL {
if (typeof url !== 'string' || url.trim() === '') {
throw new Error('url is required and must be https://api2.cursor.sh/…')
}
let parsed: URL
try {
parsed = new URL(url.trim())
} catch {
throw new Error('url must be a valid https URL on api2.cursor.sh')
}
if (parsed.protocol !== 'https:') {
throw new Error('url must use https.')
}
if (parsed.hostname !== WEBHOOK_HOST) {
throw new Error(`url host must be exactly ${WEBHOOK_HOST}.`)
}
return parsed
}
export function buildSaveUrl(input: {
secretName: string
name: string
packageId: string
}): string {
const url = new URL('https://kody.codes/account/secrets/new')
url.searchParams.set('name', input.secretName)
url.searchParams.set(
'description',
`Grok Bot wake sender key for ${input.name}`,
)
url.searchParams.set('allowedHosts', WEBHOOK_HOST)
url.searchParams.set('scope', 'package')
url.searchParams.set('packageId', input.packageId)
return url.toString()
}
export function parseBotRecord(value: unknown): BotRecord | null {
let parsed = value
if (typeof parsed === 'string') {
try {
parsed = JSON.parse(parsed)
} catch {
return null
}
}
if (!parsed || typeof parsed !== 'object') return null
const row = parsed as Record<string, unknown>
const id = typeof row.id === 'string' ? row.id.trim() : ''
const name = typeof row.name === 'string' ? row.name.trim() : ''
const url = typeof row.url === 'string' ? row.url.trim() : ''
const channelId = typeof row.channelId === 'string' ? row.channelId.trim() : ''
if (!id || !name) return null
const aliases = Array.isArray(row.aliases)
? row.aliases
.filter((item): item is string => typeof item === 'string')
.map((item) => normalizeName(item))
.filter(Boolean)
: [normalizeName(name)]
const uniqueAliases = Array.from(new Set(aliases))
if (!uniqueAliases.includes(normalizeName(name))) {
uniqueAliases.push(normalizeName(name))
}
return {
id,
name,
aliases: uniqueAliases,
url,
secretName: secretNameFor(id),
...(channelId ? { channelId } : {}),
createdAt:
typeof row.createdAt === 'string' ? row.createdAt : new Date().toISOString(),
updatedAt:
typeof row.updatedAt === 'string' ? row.updatedAt : new Date().toISOString(),
}
}
async function readAlias(normalized: string): Promise<string | null> {
const raw = await packageStorage().get(aliasKey(normalized))
if (typeof raw === 'string' && raw.trim()) return raw.trim()
if (raw && typeof raw === 'object' && 'id' in raw) {
const id = (raw as { id?: unknown }).id
return typeof id === 'string' ? id.trim() : null
}
return null
}
export async function resolveBot(bot: string): Promise<BotRecord | null> {
const raw = bot.trim()
if (!raw) return null
const bucket = packageStorage()
if (isUuidLike(raw)) {
const byId = parseBotRecord(await bucket.get(botKey(raw)))
if (byId) return byId
}
const idFromAlias = await readAlias(normalizeName(raw))
if (idFromAlias) {
const byAlias = parseBotRecord(await bucket.get(botKey(idFromAlias)))
if (byAlias) return byAlias
}
return parseBotRecord(await bucket.get(botKey(raw)))
}
export async function writeBotRecord(record: BotRecord): Promise<void> {
const bucket = packageStorage()
await bucket.set(botKey(record.id), {
id: record.id,
name: record.name,
aliases: record.aliases,
url: record.url,
secretName: record.secretName,
...(record.channelId ? { channelId: record.channelId } : {}),
createdAt: record.createdAt,
updatedAt: record.updatedAt,
})
for (const alias of record.aliases) {
await bucket.set(aliasKey(alias), record.id)
}
}
export async function deleteAlias(normalized: string): Promise<void> {
await packageStorage().delete(aliasKey(normalized))
}
export async function deleteBotRecord(record: BotRecord): Promise<void> {
const bucket = packageStorage()
await bucket.delete(botKey(record.id))
for (const alias of record.aliases) {
await bucket.delete(aliasKey(alias))
}
}
function secretNamesFrom(result: unknown): string[] {
if (!result || typeof result !== 'object') return []
const secrets = (result as { secrets?: unknown }).secrets
if (!Array.isArray(secrets)) return []
return secrets
.map((item) =>
item && typeof item === 'object' && typeof (item as { name?: unknown }).name === 'string'
? (item as { name: string }).name
: '',
)
.filter(Boolean)
}
export async function listKnownSecretNames(): Promise<Set<string> | null> {
try {
const names = new Set<string>()
const all = secretNamesFrom(await kody.secret_list({}))
for (const name of all) names.add(name)
try {
const scoped = secretNamesFrom(await kody.secret_list({ scope: 'package' }))
for (const name of scoped) names.add(name)
} catch {
// Package-scoped listing may not be visible from execute yet.
}
return names
} catch {
return null
}
}
export async function isSecretPending(secretName: string): Promise<boolean> {
const names = await listKnownSecretNames()
if (!names) return true
return !names.has(secretName)
}
function listEntries(listed: unknown): Array<{ key?: string; value?: unknown }> {
if (Array.isArray(listed)) {
return listed.filter((item) => item && typeof item === 'object') as Array<{
key?: string
value?: unknown
}>
}
if (listed && typeof listed === 'object') {
const entries = (listed as { entries?: unknown }).entries
if (Array.isArray(entries)) {
return entries.filter((item) => item && typeof item === 'object') as Array<{
key?: string
value?: unknown
}>
}
}
return []
}
export async function listBotRecords(): Promise<BotRecord[]> {
const listed = await packageStorage().list({ prefix: BOT_PREFIX })
const records: BotRecord[] = []
const seen = new Set<string>()
for (const entry of listEntries(listed)) {
const key = typeof entry.key === 'string' ? entry.key : ''
const value = 'value' in entry ? entry.value : entry
if (key && !key.startsWith(BOT_PREFIX)) continue
const record = parseBotRecord(value)
if (!record || seen.has(record.id)) continue
seen.add(record.id)
records.push(record)
}
return records
}
export function urlHostOf(url: string): string {
if (!url) return ''
try {
return new URL(url).hostname
} catch {
return WEBHOOK_HOST
}
}
export function missingBotError(bot: string): Error {
return new Error(
`No Grok Bot is registered for "${bot}". Call register({ id, name, url }) first.`,
)
}
export function missingSecretError(input: {
name: string
secretName: string
saveUrl: string
}): Error {
return new Error(
`Wake is refused until the sender key is saved for "${input.name}" as ${input.secretName}. Open ${input.saveUrl} and paste the key there (never in chat). Do not POST until that secret exists.`,
)
}