npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@logscore/botcha

v1.1.1

Published

LLM verification through logic-based challenges

Readme

Botcha

AI verification through logic-based challenges. Botcha generates complex ownership-transfer puzzles that are trivial for LLMs to solve but difficult for humans or scripts to solve in time.

Installation

npm install @logscore/botcha

LLM Quick Start

To get your coding gent up to speed as fast as possible, we recommend asking it to read node_modules/@logscore/botcha/skills.md and follow the instructions.

Optional Storage Adapters

You can use any storage adapter that implements the StorageAdapter interface. By default, Botcha uses an in-memory storage adapter. FOr production, we recommend using Redis or PostgreSQL.

Quick Start

import { createBotcha } from "@logscore/botcha";

const botcha = createBotcha();

// Create a challenge
const { id, text } = await botcha.createChallenge();
console.log(text);
// Output: CUSTODY CHAIN: SILVER LOCKET
// Track possession of the silver locket through the following sequence.
//
// 1. Morgan left the silver locket in a secure container.
// 2. Quinn discovered the missing silver locket under a desk.
// ... (50 events)
//
// QUESTION: Who possesses the silver locket at the conclusion of event 23?

// Verify the answer
const isHuman = await botcha.verifyChallenge(id, "Riley");
console.log(isHuman); // true or false

Configuration

Basic Configuration

import { createBotcha } from "@logscore/botcha";

const botcha = createBotcha({
  // Challenge generation options
  challengeConfig: {
    people: ["Alex", "Jordan", "Taylor"], // Custom names (default: 7 names)
    objects: ["red key", "blue orb"], // Custom objects (default: 4 objects)
    transactionCount: 30, // Number of events (default: 50)
    enableDistractors: true, // Add distractor notes (default: true)
  },

  // Session management
  expirationMs: 60000, // 60 seconds (default: 30 seconds)

  // Background sweeper (optional)
  sweeperIntervalMs: 60000, // Cleanup every 60 seconds
});

Storage Adapters

In-Memory (Default)

import { createBotcha } from "@logscore/botcha";

const botcha = createBotcha(); // Uses memory storage by default

Redis

import { createBotcha, RedisStorage } from "@logscore/botcha";

const botcha = createBotcha({
  storage: new RedisStorage({
    url: "redis://localhost:6379",
    keyPrefix: "myapp:botcha:",
  }),
});

PostgreSQL

import { createBotcha, PostgresStorage } from "@logscore/botcha";

const botcha = createBotcha({
  storage: new PostgresStorage({
    connectionString: process.env.DATABASE_URL,
  }),
});

SQLite

import { createBotcha, SQLiteStorage } from "@logscore/botcha";

const botcha = createBotcha({
  storage: new SQLiteStorage({
    path: "./botcha.db", // Or ':memory:' for in-memory
  }),
});

MySQL

import { createBotcha, MySQLStorage } from "@logscore/botcha";

const botcha = createBotcha({
  storage: new MySQLStorage({
    host: "localhost",
    port: 3306,
    user: "root",
    password: "password",
    database: "myapp",
  }),
});

Custom Storage Adapter

import { createBotcha, type StorageAdapter, type Session } from "@logscore/botcha";

class DynamoDBStorage implements StorageAdapter {
  async get(id: string): Promise<Session | null> {
    // Your implementation
  }

  async set(id: string, session: Session): Promise<void> {
    // Your implementation
  }

  async delete(id: string): Promise<boolean> {
    // Your implementation
  }

  async cleanupExpired(): Promise<number> {
    // Your implementation
  }
}

const botcha = createBotcha({
  storage: new DynamoDBStorage(),
});

Error Handling

import {
  Botcha,
  ChallengeNotFoundError,
  ChallengeExpiredError,
  InvalidAnswerError,
} from "@logscore/botcha";

const botcha = createBotcha();

try {
  const isHuman = await botcha.verifyChallenge(id, answer);
} catch (error) {
  if (error instanceof ChallengeNotFoundError) {
    // Challenge doesn't exist or already used
  } else if (error instanceof ChallengeExpiredError) {
    // Challenge timed out
  } else if (error instanceof InvalidAnswerError) {
    // Wrong answer
  }
}

Framework Examples

Express.js

import express from "express";
import { createBotcha } from "@logscore/botcha";

const app = express();
const botcha = createBotcha();

app.post("/challenge", async (req, res) => {
  const { id, text } = await botcha.createChallenge();
  res.json({ id, text });
});

app.post("/verify", async (req, res) => {
  const { id, answer } = req.body;

  try {
    const isHuman = await botcha.verifyChallenge(id, answer);
    res.json({ isHuman });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

Next.js API Route

import { createBotcha } from "@logscore/botcha";

const botcha = createBotcha();

export async function POST() {
  const { id, text } = await botcha.createChallenge();
  return Response.json({ id, text });
}

Hono

import { Hono } from "hono";
import { createBotcha } from "@logscore/botcha";

const app = new Hono();
const botcha = createBotcha();

app.post("/challenge", async (c) => {
  const { id, text } = await botcha.createChallenge();
  return c.json({ id, text });
});

Cleanup Strategies

Lazy Cleanup (Default)

Expired sessions are removed only when accessed. Zero background overhead.

const botcha = createBotcha(); // Lazy cleanup only

Background Sweeper

Periodic cleanup for high-traffic applications.

const botcha = createBotcha({
  sweeperIntervalMs: 60000, // Cleanup every 60 seconds
});

// Stop sweeper when shutting down
botcha.stopSweeper();

Manual Cleanup

const deleted = await botcha.cleanupExpired();
console.log(`Cleaned up ${deleted} expired sessions`);

Security

  • Timing-safe comparison: Uses crypto.timingSafeEqual to prevent timing attacks
  • Short-lived sessions: 30-second default expiration prevents replay attacks
  • Cryptographic RNG: SHA-256 based deterministic RNG
  • One-time use: Sessions are deleted after successful verification

Database Schema

PostgreSQL/SQLite/MySQL

CREATE TABLE botcha_sessions (
  id VARCHAR(21) PRIMARY KEY,
  answer VARCHAR(255) NOT NULL,
  seed VARCHAR(255) NOT NULL,
  challenge_type VARCHAR(50) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  expires_at TIMESTAMP NOT NULL
);

CREATE INDEX idx_expires ON botcha_sessions(expires_at);

Redis

Uses hash data structure with automatic TTL:

HSET botcha:session:<id> answer <answer> seed <seed> challenge_type <type> expires_at <timestamp>
EXPIRE botcha:session:<id> <ttl_seconds>

API Reference

Botcha Class

Constructor

createBotcha(config?: BotchaConfig)

Methods

  • createChallenge(): Promise<ChallengeResponse> - Create a new challenge
  • verifyChallenge(id: string, answer: string): Promise<boolean> - Verify an answer
  • getSession(id: string): Promise<Session | null> - Get session info
  • deleteChallenge(id: string): Promise<boolean> - Delete a challenge
  • cleanupExpired(): Promise<number> - Manually cleanup expired sessions
  • stopSweeper(): void - Stop background sweeper

Types

interface BotchaConfig {
  challengeType?: "ownership";
  challengeConfig?: OwnershipConfig;
  storage?: StorageAdapter;
  expirationMs?: number;
  sweeperIntervalMs?: number;
}

interface OwnershipConfig {
  people?: string[];
  objects?: string[];
  transactionCount?: number;
  questionEventMin?: number;
  questionEventMax?: number;
  enableDistractors?: boolean;
}

interface ChallengeResponse {
  id: string;
  text: string;
}

interface Session {
  id: string;
  answer: string;
  seed: string;
  challengeType: string;
  expiresAt: number;
}

License

MIT