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

@razinmohammedpt/hackai-sdk

v2.0.0

Published

Fast, fully-typed TypeScript SDK for Hack Club AI — chat completions, responses, image generation, embeddings, OCR, moderations, Exa web search, and the full Replicate model catalog.

Readme

@razinmohammedpt/hackai-sdk

A fast, fully-typed TypeScript/JavaScript SDK for Hack Club AI — a free AI proxy for teens, built by Hack Club.

Wraps the entire Hack Club AI API: chat completions, the Responses API, image generation, image/PDF inputs, embeddings, model listing, token stats, content moderation, OCR, Exa-powered web search, healthchecks, and every model in the Replicate directory (text-to-speech, speech-to-text, OCR, upscaling, image utilities, music generation, and more) with typed helper methods.

  • Zero runtime dependencies for the core client — built on native fetch, no bloat.
  • True streaming (async generators over SSE) for chat completions, the Responses API, and Exa answers — no buffering.
  • One dependency (replicate, the official client) powers client.replicate.*, wired to Hack Club AI's Replicate proxy.
  • Dual ESM + CommonJS build with full .d.ts types.
  • Automatic real USD cost on every non-streaming chat/responses/embeddings call — see Cost tracking.

Install

npm install @razinmohammedpt/hackai-sdk

Quick start

Get an API key from the Hack Club AI dashboard (teens 18 and under only — see Rules below).

import { HackAI } from "@razinmohammedpt/hackai-sdk";

const client = new HackAI({ apiKey: process.env.HACKCLUB_AI_API_KEY });

const completion = await client.chat.completions.create({
  model: "qwen/qwen3-32b",
  messages: [{ role: "user", content: "Tell me a joke." }],
});

console.log(completion.choices[0].message.content);

If you omit apiKey, the client reads it from the HACKCLUB_AI_API_KEY environment variable.

Table of contents

Chat Completions

const completion = await client.chat.completions.create({
  model: "qwen/qwen3-32b",
  messages: [{ role: "user", content: "Hello!" }],
  temperature: 0.7,
  max_tokens: 500,
});

Streaming

const stream = await client.chat.completions.create({
  model: "qwen/qwen3-32b",
  messages: [{ role: "user", content: "Write a short story." }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}

Responses API

Simple string input:

const response = await client.responses.create({
  model: "qwen/qwen3-32b",
  input: "What is the meaning of life?",
  max_output_tokens: 9000,
});

console.log(response.output[0].content[0].text);

Multi-turn conversations (the Responses API is stateless — always send full history, and include id/status on assistant messages):

const response = await client.responses.create({
  model: "qwen/qwen3-32b",
  input: [
    { type: "message", role: "user", content: [{ type: "input_text", text: "Capital of France?" }] },
    {
      type: "message",
      role: "assistant",
      id: "msg_abc123",
      status: "completed",
      content: [{ type: "output_text", text: "Paris.", annotations: [] }],
    },
    { type: "message", role: "user", content: [{ type: "input_text", text: "And its population?" }] },
  ],
});

Streaming works the same way — pass stream: true and for await the result.

Image generation

Uses google/gemini-2.5-flash-image-preview ("Nano Banana") or google/gemini-3-pro-image-preview, via chat completions:

const completion = await client.chat.completions.create({
  model: "google/gemini-2.5-flash-image-preview",
  messages: [{ role: "user", content: "Make a picture of a sunset over mountains" }],
  modalities: ["image", "text"],
  image_config: { aspect_ratio: "16:9" },
});

const dataUrl = completion.choices[0].message.images?.[0].image_url.url; // data:image/png;base64,...

Image inputs (vision)

const completion = await client.chat.completions.create({
  model: "google/gemini-2.5-flash",
  messages: [
    {
      role: "user",
      content: [
        { type: "text", text: "What's in this image?" },
        { type: "image_url", image_url: { url: "https://example.com/image.jpg" } },
      ],
    },
  ],
});

Base64 also works: image_url: { url: "data:image/jpeg;base64,..." }.

PDF inputs

const completion = await client.chat.completions.create({
  model: "qwen/qwen3-32b",
  messages: [
    {
      role: "user",
      content: [
        { type: "text", text: "Summarize this document" },
        { type: "file", file: { filename: "bitcoin.pdf", file_data: "https://bitcoin.org/bitcoin.pdf" } },
      ],
    },
  ],
  plugins: [{ id: "file-parser", pdf: { engine: "pdf-text" } }],
});

Reuse message.annotations from the response in follow-up requests to skip re-parsing the same PDF.

Embeddings

const result = await client.embeddings.create({
  model: "qwen/qwen3-embedding-8b",
  input: "The quick brown fox jumps over the lazy dog",
});

console.log(result.data[0].embedding);

const availableModels = await client.embeddings.listModels();

Models

const { data: models } = await client.models.list();

Token stats

const stats = await client.stats.get();
// { totalRequests, totalTokens, totalPromptTokens, totalCompletionTokens }

Cost tracking

Every non-streaming call to chat.completions.create(), responses.create(), and embeddings.create() returns a cost field with the real USD price of that specific generation — computed from the response's usage and the model's live per-token pricing (pulled from GET /proxy/v1/models / GET /proxy/v1/embeddings/models, the same OpenRouter-compatible pricing HCAI exposes).

const completion = await client.chat.completions.create({
  model: "qwen/qwen3-32b",
  messages: [{ role: "user", content: "Tell me a joke." }],
});

console.log(completion.usage);
// { prompt_tokens: 20, completion_tokens: 15, total_tokens: 35 }

console.log(completion.cost);
// {
//   currency: "USD",
//   promptCost: 0.0000016,
//   completionCost: 0.0000042,
//   totalCost: 0.0000058,
//   pricePerPromptToken: 0.00000008,
//   pricePerCompletionToken: 0.00000028,
// }

The first call for a given model triggers one extra request to fetch the pricing table; it's cached on the client instance after that, so subsequent calls add no latency. If pricing can't be resolved (unknown model, network hiccup), cost is simply undefined — it never fails the underlying call.

You can also look up pricing or compute cost yourself:

import { computeCost } from "@razinmohammedpt/hackai-sdk";

const pricing = await client.models.getPricing("qwen/qwen3-32b");
// { prompt: "0.00000008", completion: "0.00000028" }

const cost = computeCost(pricing, { prompt_tokens: 20, completion_tokens: 15, total_tokens: 35 });

This is useful for streaming responses, where cost isn't attached automatically since usage typically only arrives (if at all) in the final chunk — capture that usage yourself and pass it to computeCost().

Moderations

const result = await client.moderations.create({ input: "some text to classify" });
console.log(result.results[0].flagged, result.results[0].categories);

OCR

const result = await client.ocr.create({
  document: { type: "image_url", image_url: "https://example.com/receipt.png" },
  table_format: "markdown",
});

console.log(result.pages[0].markdown);

Supports image_url, document_url (PDFs), and previously-uploaded file documents; JSON-schema-structured document_annotation_format output; and page selection, header/footer extraction, and embedded-image extraction.

Exa web search

const results = await client.exa.search({ query: "Hack Club projects", numResults: 5 });
const similar = await client.exa.findSimilar({ url: "https://hackclub.com/", numResults: 5 });
const contents = await client.exa.contents({ urls: ["https://hackclub.com/"] });
const answer = await client.exa.answer({ query: "What is Hack Club?" });

// Streaming answers:
const stream = await client.exa.answer({ query: "What is Hack Club?", stream: true });
for await (const chunk of stream) console.log(chunk);

Healthcheck

const health = await client.up();
// { status: "up" | "down", balanceRemaining, dailyKeyUsageRemaining, timestamp }

up() resolves with the health body even when Hack Club AI reports itself as down — it never throws.

Replicate models

client.replicate wraps every model in the Hack Club AI Replicate directory using the official replicate client under the hood, pointed at HCAI's proxy. Each model has a typed helper grouped by category, plus a generic run() escape hatch for anything not yet wrapped (or for future models).

Every call returns { output, metrics, cost }:

// Typed helper:
const result = await client.replicate.tts.chatterboxPro({
  voice: "William (Whispering)",
  prompt: "Poppin' bottles in the ice, like a blizzard.",
});

console.log(result.output);   // whatever the model returns — a URL, array of URLs, etc.
console.log(result.metrics);  // { predictTimeSeconds, totalTimeSeconds } once the run completes
console.log(result.cost);     // always undefined today — see note below

// Generic escape hatch — works for ANY Replicate model:
const generic = await client.replicate.run("owner/model-name", { some: "input" });

On cost: unlike chat/responses/embeddings, Replicate doesn't expose per-token pricing or a dollar figure anywhere in its API — even through Hack Club AI's proxy, a completed prediction has no cost/price/billing field, only wall-clock metrics. cost is kept as an explicit field (always undefined) for shape consistency with the rest of the SDK, so a real value can populate here automatically in the future without another breaking change — it is not a placeholder for a number we simply haven't computed yet.

Version pinning: HCAI's Replicate proxy only supports version-pinned prediction creation, not Replicate's unversioned owner/name shortcut. Passing a bare "owner/name" (as the typed helpers do) triggers one extra models.get() call to resolve and cache the latest version id on first use; pass "owner/name:version" yourself to skip that resolution.

Text to Speech — client.replicate.tts

| Method | Model | | --- | --- | | speechTurbo | minimax/speech-02-turbo | | speech28Turbo | minimax/speech-2.8-turbo | | speech28Hd | minimax/speech-2.8-hd | | chatterboxPro | resemble-ai/chatterbox-pro | | dia | zsxkib/dia | | xttsV2 | lucataco/xtts-v2 | | qwen3Tts | qwen/qwen3-tts | | realtimeTts15Mini | inworld/realtime-tts-1.5-mini | | realtimeTts15Max | inworld/realtime-tts-1.5-max |

Speech to Text — client.replicate.stt

| Method | Model | | --- | --- | | incrediblyFastWhisper | vaibhavs10/incredibly-fast-whisper | | parakeetRnnt11b | nvidia/parakeet-rnnt-1.1b |

OCR — client.replicate.ocr

| Method | Model | | --- | --- | | glm4v9b | cuuupid/glm-4v-9b | | deepseekOcr | lucataco/deepseek-ocr | | textExtractOcr | abiruyt/text-extract-ocr |

Image upscaling — client.replicate.upscale

| Method | Model | | --- | --- | | magicImageRefiner | fermatresearch/magic-image-refiner | | recraftCrispUpscale | recraft-ai/recraft-crisp-upscale | | upscaler | google/upscaler |

Image utilities — client.replicate.imageUtils

| Method | Model | | --- | --- | | removeBg | lucataco/remove-bg | | backgroundRemover | 851-labs/background-remover | | icLightBackground | zsxkib/ic-light-background | | robustVideoMatting | arielreplicate/robust_video_matting | | rembgVideo | lucataco/rembg-video | | nsfwImageDetection | falcons-ai/nsfw_image_detection |

Music generation — client.replicate.music

| Method | Model | | --- | --- | | lyria2 | google/lyria-2 | | musicgen | meta/musicgen | | music15 | minimax/music-1.5 |

Specialized image models — client.replicate.image

| Method | Model | | --- | --- | | rdPlus | retro-diffusion/rd-plus |

Audio — client.replicate.audio

| Method | Model | | --- | --- | | samAudioLarge | geopti/sam-audio-large | | voiceCloning | minimax/voice-cloning |

Every helper accepts the same input object you'd pass to Replicate's API for that model — check the model directory for each model's exact input/output schema.

Error handling

Every non-2xx response throws a HackAIError:

import { HackAI, HackAIError } from "@razinmohammedpt/hackai-sdk";

try {
  await client.chat.completions.create({ model: "qwen/qwen3-32b", messages: [] });
} catch (err) {
  if (err instanceof HackAIError) {
    console.error(err.status, err.statusText, err.body);
  }
}

Rules & limits

Hack Club AI is a free service for teens 18 and under, run as a charity. Please respect their rules: no coding agents, no reselling, no proxying access to others, and follow the Code of Conduct. Rate limits: 450 requests / 30 min for chat completions & embeddings, 600 requests / 30 min for moderations.

License

MIT