🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@ridit/hackclub-ai-sdk

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ridit/hackclub-ai-sdk - npm Package Compare versions

Comparing version
0.1.1
to
0.1.2
+54
src/utils/replicate.ts
import type {
HackclubReplicateSTTModel,
HackclubReplicateTTSModel,
} from "./models";
export const SSTInputMap: Record<HackclubReplicateSTTModel, string> = {
"vaibhavs10/incredibly-fast-whisper": "audio",
"nvidia/parakeet-rnnt-1.1b": "audio_file",
};
export const TSSInputMap: Record<HackclubReplicateTTSModel, string> = {
"resemble-ai/chatterbox-pro": "prompt",
"minimax/speech-02-turbo": "text",
"zsxkib/dia": "text",
"lucataco/xtts-v2": "speaker",
"qwen/qwen3-tts": "text",
};
export const SSTModelMap: Record<
HackclubReplicateSTTModel,
`${string}/${string}`
> = {
"vaibhavs10/incredibly-fast-whisper":
"vaibhavs10/incredibly-fast-whisper:3ab86df6c8f54c11309d4d1f930ac292bad43ace52d10c80d87eb258b3c9f79c",
"nvidia/parakeet-rnnt-1.1b":
"nvidia/parakeet-rnnt-1.1b:73ddbebaef172a47c8dfdd79381f110bfdc7691bcc7a4edde82f0a39e380ce50",
};
export const TTSModelMap: Record<
HackclubReplicateTTSModel,
`${string}/${string}`
> = {
"zsxkib/dia":
"zsxkib/dia:2119e338ca5c0dacd3def83158d6c80d431f2ac1024146d8cca9220b74385599",
"resemble-ai/chatterbox-pro": "resemble-ai/chatterbox-pro",
"minimax/speech-02-turbo": "minimax/speech-02-turbo",
"lucataco/xtts-v2":
"lucataco/xtts-v2:684bc3855b37866c0c65add2ff39c78f3dea3f4ff103a436465326e0f438d55e",
"qwen/qwen3-tts": "qwen/qwen3-tts",
};
export const BgRemovalModelMap: Record<string, `${string}/${string}`> = {
"lucataco/remove-bg":
"lucataco/remove-bg:95fcc2a26d3899cd6c2691c900465aaeff466285a65c14638cc5f36f34befaf1",
"851-labs/background-remover":
"851-labs/background-remover:a029dff38972b5fda4ec5d75d7d1cd25aeff621d2cf4946a41055d7db66b80bc",
};
export const UpscaleModelMap: Record<string, `${string}/${string}`> = {
"google/upscaler": "google/upscaler",
"recraft-ai/recraft-crisp-upscale": "recraft-ai/recraft-crisp-upscale",
"fermatresearch/magic-image-refiner":
"fermatresearch/magic-image-refiner:507ddf6f977a7e30e46c0daefd30de7d563c72322f9e4cf7cbac52ef0f667b13",
};
export const minimaxVoices = [
// English
"English_Trustworth_Man",
"English_Aussie_Bloke",
"English_CalmWoman",
"English_UpsetGirl",
"English_Gentle-voiced_man",
"English_Whispering_girl",
"English_Diligent_Man",
"English_Graceful_Lady",
"English_ReservedYoungMan",
"English_PlayfulGirl",
"English_ManWithDeepVoice",
"English_MaturePartner",
"English_FriendlyPerson",
"English_MatureBoss",
"English_Debator",
"English_LovelyGirl",
"English_Steadymentor",
"English_Deep-VoicedGentleman",
"English_Wiselady",
"English_CaptivatingStoryteller",
"English_DecentYoungMan",
"English_SentimentalLady",
"English_ImposingManner",
"English_SadTeen",
"English_PassionateWarrior",
"English_WiseScholar",
"English_Soft-spokenGirl",
"English_SereneWoman",
"English_ConfidentWoman",
"English_PatientMan",
"English_Comedian",
"English_BossyLeader",
"English_Strong-WilledBoy",
"English_StressedLady",
"English_AssertiveQueen",
"English_AnimeCharacter",
"English_Jovialman",
"English_WhimsicalGirl",
"English_Kind-heartedGirl",
// Chinese
"Chinese (Mandarin)_Reliable_Executive",
"Chinese (Mandarin)_News_Anchor",
"Chinese (Mandarin)_Unrestrained_Young_Man",
"Chinese (Mandarin)_Mature_Woman",
"Arrogant_Miss",
"Robot_Armor",
"Chinese (Mandarin)_Kind-hearted_Antie",
"Chinese (Mandarin)_Refreshing_Young_Man",
"Chinese (Mandarin)_HK_Flight_Attendant",
"Chinese (Mandarin)_Humorous_Elder",
"Chinese (Mandarin)_Gentleman",
"Chinese (Mandarin)_Warm_Bestie",
"Chinese (Mandarin)_Stubborn_Friend",
"Chinese (Mandarin)_Sweet_Lady",
"Chinese (Mandarin)_Southern_Young_Man",
"Chinese (Mandarin)_Wise_Women",
"Chinese (Mandarin)_Gentle_Youth",
"Chinese (Mandarin)_Warm_Girl",
"Chinese (Mandarin)_Male_Announcer",
"Chinese (Mandarin)_Kind-hearted_Elder",
"Chinese (Mandarin)_Cute_Spirit",
"Chinese (Mandarin)_Radio_Host",
"Chinese (Mandarin)_Lyrical_Voice",
"Chinese (Mandarin)_Straightforward_Boy",
"Chinese (Mandarin)_Sincere_Adult",
"Chinese (Mandarin)_Gentle_Senior",
"Chinese (Mandarin)_Crisp_Girl",
"Chinese (Mandarin)_Pure-hearted_Boy",
"Chinese (Mandarin)_Soft_Girl",
"Chinese (Mandarin)_IntellectualGirl",
"Chinese (Mandarin)_Warm_HeartedGirl",
"Chinese (Mandarin)_Laid_BackGirl",
"Chinese (Mandarin)_ExplorativeGirl",
"Chinese (Mandarin)_Warm-HeartedAunt",
"Chinese (Mandarin)_BashfulGirl",
// Japanese
"Japanese_IntellectualSenior",
"Japanese_DecisivePrincess",
"Japanese_LoyalKnight",
"Japanese_DominantMan",
"Japanese_SeriousCommander",
"Japanese_ColdQueen",
"Japanese_DependableWoman",
"Japanese_GentleButler",
"Japanese_KindLady",
"Japanese_CalmLady",
"Japanese_OptimisticYouth",
"Japanese_GenerousIzakayaOwner",
"Japanese_SportyStudent",
"Japanese_InnocentBoy",
"Japanese_GracefulMaiden",
// Cantonese
"Cantonese_ProfessionalHost(F)",
"Cantonese_GentleLady",
"Cantonese_ProfessionalHost(M)",
"Cantonese_PlayfulMan",
"Cantonese_CuteGirl",
"Cantonese_KindWoman",
// Korean
"Korean_SweetGirl",
"Korean_CheerfulBoyfriend",
"Korean_EnchantingSister",
"Korean_ShyGirl",
"Korean_ReliableSister",
"Korean_StrictBoss",
"Korean_SassyGirl",
"Korean_ChildhoodFriendGirl",
"Korean_PlayboyCharmer",
"Korean_ElegantPrincess",
"Korean_BraveFemaleWarrior",
"Korean_BraveYouth",
"Korean_CalmLady",
"Korean_EnthusiasticTeen",
"Korean_SoothingLady",
"Korean_IntellectualSenior",
"Korean_LonelyWarrior",
"Korean_MatureLady",
"Korean_InnocentBoy",
"Korean_CharmingSister",
"Korean_AthleticStudent",
"Korean_BraveAdventurer",
"Korean_CalmGentleman",
"Korean_WiseElf",
"Korean_CheerfulCoolJunior",
"Korean_DecisiveQueen",
"Korean_ColdYoungMan",
"Korean_MysteriousGirl",
"Korean_QuirkyGirl",
"Korean_ConsiderateSenior",
"Korean_CheerfulLittleSister",
"Korean_DominantMan",
"Korean_AirheadedGirl",
"Korean_ReliableYouth",
"Korean_FriendlyBigSister",
"Korean_GentleBoss",
"Korean_ColdGirl",
"Korean_HaughtyLady",
"Korean_CharmingElderSister",
"Korean_IntellectualMan",
"Korean_CaringWoman",
"Korean_WiseTeacher",
"Korean_ConfidentBoss",
"Korean_AthleticGirl",
"Korean_PossessiveMan",
"Korean_GentleWoman",
"Korean_CockyGuy",
"Korean_ThoughtfulWoman",
"Korean_OptimisticYouth",
// Spanish
"Spanish_SereneWoman",
"Spanish_MaturePartner",
"Spanish_CaptivatingStoryteller",
"Spanish_Narrator",
"Spanish_WiseScholar",
"Spanish_Kind-heartedGirl",
"Spanish_DeterminedManager",
"Spanish_BossyLeader",
"Spanish_ReservedYoungMan",
"Spanish_ConfidentWoman",
"Spanish_ThoughtfulMan",
"Spanish_Strong-WilledBoy",
"Spanish_SophisticatedLady",
"Spanish_RationalMan",
"Spanish_AnimeCharacter",
"Spanish_Deep-tonedMan",
"Spanish_Fussyhostess",
"Spanish_SincereTeen",
"Spanish_FrankLady",
"Spanish_Comedian",
"Spanish_Debator",
"Spanish_ToughBoss",
"Spanish_Wiselady",
"Spanish_Steadymentor",
"Spanish_Jovialman",
"Spanish_SantaClaus",
"Spanish_Rudolph",
"Spanish_Intonategirl",
"Spanish_Arnold",
"Spanish_Ghost",
"Spanish_HumorousElder",
"Spanish_EnergeticBoy",
"Spanish_WhimsicalGirl",
"Spanish_StrictBoss",
"Spanish_ReliableMan",
"Spanish_SereneElder",
"Spanish_AngryMan",
"Spanish_AssertiveQueen",
"Spanish_CaringGirlfriend",
"Spanish_PowerfulSoldier",
"Spanish_PassionateWarrior",
"Spanish_ChattyGirl",
"Spanish_RomanticHusband",
"Spanish_CompellingGirl",
"Spanish_PowerfulVeteran",
"Spanish_SensibleManager",
"Spanish_ThoughtfulLady",
// French
"French_Male_Speech_New",
"French_Female_News Anchor",
"French_CasualMan",
"French_MovieLeadFemale",
"French_FemaleAnchor",
"French_MaleNarrator",
// Indonesian
"Indonesian_SweetGirl",
"Indonesian_ReservedYoungMan",
"Indonesian_CharmingGirl",
"Indonesian_CalmWoman",
"Indonesian_ConfidentWoman",
"Indonesian_CaringMan",
"Indonesian_BossyLeader",
"Indonesian_DeterminedBoy",
"Indonesian_GentleGirl",
// German
"German_FriendlyMan",
"German_SweetLady",
"German_PlayfulMan",
// Russian
"Russian_HandsomeChildhoodFriend",
"Russian_BrightHeroine",
"Russian_AmbitiousWoman",
"Russian_ReliableMan",
"Russian_CrazyQueen",
"Russian_PessimisticGirl",
"Russian_AttractiveGuy",
"Russian_Bad-temperedBoy",
// Italian
"Italian_BraveHeroine",
"Italian_Narrator",
"Italian_WanderingSorcerer",
"Italian_DiligentLeader",
// Others
"Dutch_kindhearted_girl",
"Dutch_bossy_leader",
"Vietnamese_kindhearted_girl",
"Arabic_CalmWoman",
"Arabic_FriendlyGuy",
"Turkish_CalmWoman",
"Turkish_Trustworthyman",
"Ukrainian_CalmWoman",
] as const;
export const qwenVoices = ["Cherry", "Ethan", "Serena", "Dylan"] as const;
export type MinimaxVoice = (typeof minimaxVoices)[number];
export type QwenVoice = (typeof qwenVoices)[number];
+1
-1
{
"name": "@ridit/hackclub-ai-sdk",
"version": "0.1.1",
"version": "0.1.2",
"description": "One key. Every model. Hack Club powered.",

@@ -5,0 +5,0 @@ "author": "Ridit Jangra <riditjangra09@gmail.com> (https://ridit.space)",

+214
-73
# @ridit/hackclub-ai-sdk
A lightweight TypeScript SDK for the [Hack Club AI](https://ai.hackclub.com) API — **free AI credits for hackers**. Get text and image generation with a clean, type-safe interface.
A lightweight TypeScript SDK for the [Hack Club AI](https://ai.hackclub.com) API — **free AI credits for hackers**. Text, image, speech, music, and more with a clean, type-safe interface.

@@ -9,7 +9,9 @@ ## Why use this?

- **Zero config** — Just an API key, no complex setup
- **Type-safe** — Full TypeScript support with autocomplete for models
- **Simple API** — `generateText()` and `generateImage()` — that's it
- **Auto-save images** — Optional disk saving with custom filenames
- **Multiple models** — Access to Google, OpenAI, DeepSeek, Qwen, and more
- **Type-safe** — Full TypeScript support with autocomplete for models and voices
- **Batteries included** — Text, streaming, images, TTS, STT, background removal, upscaling, music
- **Auto-save** — Optional disk saving across all media methods
- **Multiple models** — Access to Google, OpenAI, DeepSeek, Qwen, Replicate, and more
---
## Quick Start

@@ -39,41 +41,64 @@

```typescript
import { HackclubProvider } from "hackclub-ai-sdk";
import { HackclubProvider } from "@ridit/hackclub-ai-sdk";
// Initialize with your API key
const ai = new HackclubProvider(process.env.API_KEY ?? "");
const ai = new HackclubProvider(process.env.HACKCLUB_API_KEY ?? "");
// Generate text
const answer = await ai.generateText("Explain quantum computing in simple terms");
const answer = await ai.generateText(
"Explain quantum computing in simple terms",
);
console.log(answer);
// Generate and save an image
const { url } = await ai.generateImage("a cat astronaut floating in space", undefined, {
save: true,
filename: "space-cat.png"
});
// Stream text
const stream = await ai.streamText("Tell me a story");
for await (const chunk of stream) {
process.stdout.write(chunk);
}
```
---
## API Reference
### `HackclubProvider(apiKey: string)`
### `HackclubProvider(apiKey?: string)`
Main class for interacting with the Hack Club AI API.
Main class. If no key is passed, falls back to `HACKCLUB_API_KEY` env variable.
```typescript
const ai = new HackclubProvider("your-api-key-here");
// or
const ai = new HackclubProvider(); // uses process.env.HACKCLUB_API_KEY
```
### `generateText(prompt: string, model?: HackclubModel): Promise<string>`
---
### Text
#### `generateText(prompt, model?, options?): Promise<string>`
Generates text from a prompt.
```typescript
// Simple text generation
const text = await ai.generateText("Write a haiku about TypeScript");
// With a specific model
// With model + options
const code = await ai.generateText(
"Write a React hook for debouncing",
"deepseek/deepseek-v3" // Great for coding tasks
"deepseek/deepseek-v3",
{
systemPrompt: "You are an expert TypeScript developer",
temperature: 0.7,
maxSteps: 3,
tools: { ... },
}
);
// Multi-turn conversation
const reply = await ai.generateText("", "google/gemini-2.5-flash", {
messages: [
{ role: "user", content: "What is TypeScript?" },
{ role: "assistant", content: "TypeScript is..." },
{ role: "user", content: "Give me an example" },
]
});
```

@@ -83,74 +108,182 @@

### `generateImage(prompt: string, model?: HackclubImageModel, options?): Promise<{ url: string; mimeType: string }>`
#### `streamText(prompt, model?, options?): Promise<AsyncIterable<string>>`
Same options as `generateText`, returns a token stream.
```typescript
const stream = await ai.streamText("Tell me a story");
for await (const chunk of stream) {
process.stdout.write(chunk);
}
```
---
### Images
#### `generateImage(prompt, model?, options?): Promise<{ url: string; mimeType: string }>`
Generates an image from a prompt.
```typescript
// Basic image generation
const { url, mimeType } = await ai.generateImage("sunset over mountains");
const { url } = await ai.generateImage("sunset over mountains");
// Auto-save to disk
await ai.generateImage("cyberpunk city at night", undefined, {
save: true // → saves as generated-1234567890.png
});
// Custom filename and aspect ratio
// With options
await ai.generateImage("cinematic movie poster", undefined, {
aspect_ratio: "21:9",
save: true,
filename: "movie-poster.png"
filename: "movie-poster.png",
});
```
| Option | Type | Default | Description |
| -------------- | --------- | ----------------------------- | ------------------------------- |
| `aspect_ratio` | `string` | `"16:9"` | e.g. `"1:1"`, `"4:3"`, `"21:9"` |
| `save` | `boolean` | `false` | Auto-save to disk |
| `filename` | `string` | `generated-{timestamp}.{ext}` | Custom filename |
**Default model:** `google/gemini-2.5-flash-image`
#### Image Options
#### `removeBg(imageUrl, model?, options?): Promise<string>`
Remove the background from an image.
| Option | Type | Default | Description |
| -------------- | --------- | ----------------------------- | ------------------------------------------------------------------ |
| `aspect_ratio` | `string` | `"16:9"` | Aspect ratio of generated image (e.g., `"1:1"`, `"4:3"`, `"21:9"`) |
| `save` | `boolean` | `false` | Auto-save image to disk |
| `filename` | `string` | `generated-{timestamp}.{ext}` | Custom filename (only when `save: true`) |
```typescript
const url = await ai.removeBg("https://example.com/photo.jpg");
// Save result
const url = await ai.removeBg(
"https://example.com/photo.jpg",
"851-labs/background-remover",
{
save: true,
filename: "no-bg.png",
},
);
```
## Available Models
**Available models:** `lucataco/remove-bg` (default), `851-labs/background-remover`
### Text Models (`HackclubModel`)
#### `upscale(imageUrl, model?, options?): Promise<string>`
Upscale or refine an image.
| Model | Best For | Notes |
| ------------------------- | --------------------- | ----------------------------- |
| `google/gemini-2.5-flash` | General use | **Default**, fast and capable |
| `deepseek/deepseek-v3` | Code, reasoning | Great for programming tasks |
| `openai/gpt-4o` | Complex reasoning | OpenAI's flagship model |
| `qwen/qwen3.5-397b-a17b` | Chinese, multilingual | Strong for non-English |
| `moonshotai/kimi-k2.5` | Long context | Up to 128K tokens |
```typescript
// 4x upscale
const url = await ai.upscale(
"https://example.com/image.png",
"google/upscaler",
{
upscale_factor: "x4",
save: true,
},
);
// AI refinement with prompt
const url = await ai.upscale(
"https://example.com/image.png",
"fermatresearch/magic-image-refiner",
{
prompt: "UHD 4k, highly detailed",
save: true,
},
);
```
*Full list in `[src/utils/models.ts](src/utils/models.ts)`*
| Option | Type | Default | Description |
| ---------------- | -------------- | -------------------------- | --------------------------------------------- |
| `upscale_factor` | `"x2" \| "x4"` | `"x2"` | Only for `google/upscaler` |
| `prompt` | `string` | — | Only for `fermatresearch/magic-image-refiner` |
| `save` | `boolean` | `false` | Auto-save to disk |
| `filename` | `string` | `upscaled-{timestamp}.png` | Custom filename |
### Image Models (`HackclubImageModel`)
**Available models:** `google/upscaler` (default), `recraft-ai/recraft-crisp-upscale`, `fermatresearch/magic-image-refiner`
---
| Model | Notes |
| --------------------------------------- | ------------------------- |
| `google/gemini-2.5-flash-image` | **Default**, high quality |
| `google/gemini-3.1-flash-image-preview` | Latest Google model |
### Audio
#### `speak(text, model?, options?): Promise<string>`
## Advanced Examples
Convert text to speech. Returns a URL to the generated audio file.
### Streaming Responses (Coming Soon)
```typescript
const url = await ai.speak("Hello from Hack Club!");
// With voice + save
const url = await ai.speak("Hello!", "minimax/speech-02-turbo", {
voice: "English_CalmWoman",
save: true,
filename: "hello.mp3",
});
```
**Available models:** `minimax/speech-02-turbo` (default), `resemble-ai/chatterbox-pro`, `zsxkib/dia`, `lucataco/xtts-v2`, `qwen/qwen3-tts`
**Available voices (minimax):** 300+ voices across English, Chinese, Japanese, Korean, Spanish, French, German, and more. Full list in `src/utils/voices.ts`.
#### `transcribe(audioFile, model?): Promise<string>`
Transcribe audio to text. Accepts a public URL or base64 data URI.
```typescript
// Future feature - stream tokens as they arrive
for await (const token of ai.streamText("Tell me a story")) {
process.stdout.write(token);
}
// From URL
const text = await ai.transcribe("https://example.com/audio.mp3");
// From local file
import { readFileSync } from "fs";
const base64 = readFileSync("audio.mp3").toString("base64");
const text = await ai.transcribe(`data:audio/mp3;base64,${base64}`);
```
### Error Handling
**Available models:** `vaibhavs10/incredibly-fast-whisper` (default), `nvidia/parakeet-rnnt-1.1b`
---
### Music
#### `generateMusic(prompt, options?): Promise<string>`
Generate 48kHz stereo music using Google Lyria 2.
```typescript
const url = await ai.generateMusic(
"Chill lofi beats with piano and rain sounds",
);
// Save to disk
const url = await ai.generateMusic("Epic orchestral battle music", {
save: true,
filename: "battle.wav",
});
```
---
## Available Models
### Text Models (`HackclubModel`)
| Model | Best For |
| ------------------------- | --------------------- |
| `google/gemini-2.5-flash` | General use (default) |
| `deepseek/deepseek-v3.2` | Code, reasoning |
| `openai/gpt-5-mini` | Fast, capable |
| `moonshotai/kimi-k2.5` | Long context |
| `qwen/qwen3-235b-a22b` | Multilingual |
_Full list in `src/utils/models.ts`_
### Image Models (`HackclubImageModel`)
| Model | Notes |
| --------------------------------------- | ------- |
| `google/gemini-2.5-flash-image` | Default |
| `google/gemini-3.1-flash-image-preview` | Latest |
---
## Error Handling
```typescript
try {

@@ -160,23 +293,23 @@ const result = await ai.generateText("Generate some text");

console.error("API error:", error);
// Handle rate limits, invalid API key, etc.
// Handle rate limits, invalid API key, network errors, etc.
}
```
> Replicate support is also coming soon!
---
### Environment Setup
## Environment Setup
```bash
# .env file
API_KEY=sk-hc-v1-your-key-here
# .env
HACKCLUB_API_KEY=sk-hc-v1-your-key-here
```
```typescript
// In your code
import { config } from "dotenv";
config();
import { HackclubProvider } from "@ridit/hackclub-ai-sdk";
const ai = new HackclubProvider(process.env.API_KEY!);
const ai = new HackclubProvider(); // auto-reads HACKCLUB_API_KEY
```
---
## Project Structure

@@ -186,11 +319,15 @@

src/
├── index.ts # Main exports
├── provider.ts # HackclubProvider class
├── index.ts # Main exports
├── provider.ts # HackclubProvider class
├── utils/
│ ├── models.ts # Model definitions
│ └── url.ts # API endpoints
│ ├── models.ts # Model definitions
│ ├── voices.ts # Voice definitions
│ ├── replicate.ts # Replicate model/input maps
│ └── url.ts # API endpoints
└── types/
└── types.ts # Type definitions
└── types.ts # Type definitions
```
---
## Development

@@ -211,2 +348,4 @@

---
## Contributing

@@ -222,2 +361,4 @@

---
## License

@@ -229,2 +370,2 @@

Built with ❤️ by [Ridit](https://github.com/ridit-jangra) for the Hack Club community.
Built with ❤️ by [Ridit](https://github.com/ridit-jangra) for the Hack Club community.
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
import { generateText as generateTextAISDK } from "ai";
import type { HackclubImageModel, HackclubModel } from "./utils/models";
import {
generateText as generateTextAISDK,
stepCountIs,
streamText as streamTextAISDK,
} from "ai";
import type {
HackclubImageModel,
HackclubModel,
HackclubReplicateImageUtilModel,
HackclubReplicateSTTModel,
HackclubReplicateTTSModel,
HackclubReplicateUpscaleModel,
} from "./utils/models";
import { BASE_URL } from "./utils/url";
import Replicate from "replicate";
import type { TextOptionProps, TTSOptions } from "./types/types";
import {
BgRemovalModelMap,
SSTInputMap,
SSTModelMap,
TSSInputMap,
TTSModelMap,
UpscaleModelMap,
} from "./utils/replicate";
/**
* Main provider for the Hackclub AI SDK.
*/
export class HackclubProvider {
private replicateProvider;
constructor(private apiKey: string) {
constructor(private apiKey: string = process.env.HACKCLUB_API_KEY ?? "") {
this.replicateProvider = new Replicate({
auth: this.apiKey,
baseUrl: "https://ai.hackclub.com/proxy/v1/replicate",

@@ -19,2 +37,17 @@ });

private createProvider() {
return createOpenRouter({
apiKey: this.apiKey,
baseURL: BASE_URL,
});
}
private async saveFile(url: string, filename: string): Promise<void> {
const { writeFileSync } = await import("fs");
const response = await fetch(url);
const buffer = Buffer.from(await response.arrayBuffer());
writeFileSync(filename, buffer);
console.log(`Saved to ${filename}`);
}
/**

@@ -24,2 +57,3 @@ * Generate text using a specified model.

* @param model - The model to use (auto-completions available)
* @param options - Generation options
* @returns Promise<string> - The generated text

@@ -30,10 +64,21 @@ */

model: HackclubModel = "google/gemini-2.5-flash",
options: TextOptionProps = {},
): Promise<string> {
const provider = createOpenRouter({
apiKey: this.apiKey,
baseURL: BASE_URL,
});
const { text } = await generateTextAISDK({
model: provider(model),
prompt,
model: this.createProvider()(model),
...(options.messages ? { messages: options.messages } : { prompt }),
tools: options.tools,
system: options.systemPrompt,
temperature: options.temperature,
maxOutputTokens: options.maxOutputTokens,
maxRetries: options.maxRetries,
headers: options.headers,
toolChoice: options.toolChoice,
onFinish: options.onFinish,
onStepFinish: options.onStepFinish,
output: options.output,
timeout: options.timeout,
experimental_onToolCallStart: options.experimental_onToolCallStart,
experimental_onToolCallFinish: options.experimental_onToolCallFinish,
...(options.maxSteps ? { stopWhen: stepCountIs(options.maxSteps) } : {}),
});

@@ -45,2 +90,41 @@

/**
* Stream text using a specified model.
* @param prompt - The text prompt to send to the model
* @param model - The model to use (auto-completions available)
* @param options - Generation options
* @returns Promise<AsyncIterable<string>> - A stream of text chunks
* @example
* const stream = await ai.streamText("Tell me a story");
* for await (const chunk of stream) {
* process.stdout.write(chunk);
* }
*/
async streamText(
prompt: string,
model: HackclubModel = "google/gemini-2.5-flash",
options: TextOptionProps = {},
) {
const result = await streamTextAISDK({
model: this.createProvider()(model),
...(options.messages ? { messages: options.messages } : { prompt }),
tools: options.tools,
system: options.systemPrompt,
temperature: options.temperature,
maxOutputTokens: options.maxOutputTokens,
maxRetries: options.maxRetries,
headers: options.headers,
toolChoice: options.toolChoice,
onFinish: options.onFinish,
onStepFinish: options.onStepFinish,
output: options.output,
timeout: options.timeout,
experimental_onToolCallStart: options.experimental_onToolCallStart,
experimental_onToolCallFinish: options.experimental_onToolCallFinish,
...(options.maxSteps ? { stopWhen: stepCountIs(options.maxSteps) } : {}),
});
return result.textStream;
}
/**
* Generate an image using a specified model.

@@ -52,3 +136,3 @@ * @param prompt - The text prompt to send to the model

* @param options.save - Whether to automatically save the image to disk
* @param options.filename - Custom filename for the saved image (defaults to "generated-{timestamp}.{ext}")
* @param options.filename - Custom filename for the saved image
* @returns Promise<{ url: string; mimeType: string }> - The generated image data URL and mime type

@@ -59,7 +143,3 @@ */

model: HackclubImageModel = "google/gemini-2.5-flash-image",
{
aspect_ratio = "16:9",
save = false,
filename,
}: { aspect_ratio?: string; save?: boolean; filename?: string } = {},
options: { aspect_ratio?: string; save?: boolean; filename?: string } = {},
) {

@@ -75,8 +155,6 @@ const response = await fetch(

body: JSON.stringify({
model: model,
model,
messages: [{ role: "user", content: prompt }],
modalities: ["image", "text"],
image_config: {
aspect_ratio,
},
image_config: { aspect_ratio: options.aspect_ratio ?? "16:9" },
}),

@@ -86,4 +164,3 @@ },

const data = (await response.json()) as unknown as any;
const data = (await response.json()) as any;
const imageData = data.choices[0].message.images[0].image_url;

@@ -93,6 +170,6 @@ const url = imageData.url;

if (save) {
if (options.save) {
const { writeFileSync } = await import("fs");
const ext = mimeType.split("/")[1];
const finalName = filename ?? `generated-${Date.now()}.${ext}`;
const finalName = options.filename ?? `generated-${Date.now()}.${ext}`;
const base64Data = url.replace(/^data:image\/\w+;base64,/, "");

@@ -105,2 +182,146 @@ writeFileSync(finalName, Buffer.from(base64Data, "base64"));

}
/**
* Transcribe audio to text using a specified model.
* @param audioFile - URL or base64 data URI of the audio file
* @param model - The STT model to use (auto-completions available)
* @returns Promise<string> - The transcribed text
* @example
* const text = await ai.transcribe("https://example.com/audio.mp3");
*/
async transcribe(
audioFile: string,
model: HackclubReplicateSTTModel = "vaibhavs10/incredibly-fast-whisper",
): Promise<string> {
const output = await this.replicateProvider.run(SSTModelMap[model], {
input: { [SSTInputMap[model]]: audioFile, batch_size: 64 },
});
return (output as any).text;
}
/**
* Convert text to speech using a specified model.
* @param text - The text to convert to speech
* @param model - The TTS model to use (auto-completions available)
* @param options - Generation options including voice and save settings
* @returns Promise<string> - URL to the generated audio file
* @example
* const url = await ai.speak("Hello from Hack Club!", "minimax/speech-02-turbo", {
* voice: "English_CalmWoman",
* save: true,
* });
*/
async speak<T extends HackclubReplicateTTSModel>(
text: string,
model: T = "minimax/speech-02-turbo" as T,
options: TTSOptions<T> = {},
): Promise<string> {
const output = await this.replicateProvider.run(TTSModelMap[model], {
input: {
[TSSInputMap[model]]: text,
...(options.voice ? { voice_id: options.voice } : {}),
},
});
const url = (output as any).url() as string;
if (options.save) {
await this.saveFile(url, options.filename ?? `tts-${Date.now()}.mp3`);
}
return url;
}
/**
* Remove the background from an image.
* @param imageUrl - URL of the image to process
* @param model - The background removal model to use
* @param options - Options for saving the output
* @returns Promise<string> - URL of the processed image
* @example
* const url = await ai.removeBg("https://example.com/photo.jpg");
*/
async removeBg(
imageUrl: string,
model: HackclubReplicateImageUtilModel = "lucataco/remove-bg",
options: { save?: boolean; filename?: string } = {},
): Promise<string> {
const output = await this.replicateProvider.run(BgRemovalModelMap[model]!, {
input: { image: imageUrl },
});
const url = (output as any).url() as string;
if (options.save) {
await this.saveFile(
url,
options.filename ?? `bg-removed-${Date.now()}.png`,
);
}
return url;
}
/**
* Upscale or refine an image using a specified model.
* @param imageUrl - URL of the image to upscale
* @param model - The upscale model to use
* @param options - Model-specific options including scale factor and prompt
* @returns Promise<string> - URL of the upscaled image
* @example
* const url = await ai.upscale("https://example.com/image.png", "google/upscaler", {
* upscale_factor: "x4"
* });
*/
async upscale(
imageUrl: string,
model: HackclubReplicateUpscaleModel = "google/upscaler",
options: {
upscale_factor?: "x2" | "x4";
prompt?: string;
save?: boolean;
filename?: string;
} = {},
): Promise<string> {
const input: Record<string, any> = { image: imageUrl };
if (model === "google/upscaler")
input.upscale_factor = options.upscale_factor ?? "x2";
if (model === "fermatresearch/magic-image-refiner" && options.prompt)
input.prompt = options.prompt;
const output = await this.replicateProvider.run(UpscaleModelMap[model]!, {
input,
});
const url = Array.isArray(output)
? ((output[0] as any).url() as string)
: ((output as any).url() as string);
if (options.save) {
await this.saveFile(
url,
options.filename ?? `upscaled-${Date.now()}.png`,
);
}
return url;
}
/**
* Generate music from a text prompt using Google Lyria 2.
* @param prompt - Description of the music to generate
* @param options - Generation options
* @returns Promise<string> - URL of the generated audio file
* @example
* const url = await ai.generateMusic("Chill lofi beats with piano and rain sounds");
*/
async generateMusic(
prompt: string,
options: { save?: boolean; filename?: string } = {},
): Promise<string> {
const output = await this.replicateProvider.run("google/lyria-2", {
input: { prompt },
});
const url = (output as any).url() as string;
if (options.save) {
await this.saveFile(url, options.filename ?? `music-${Date.now()}.wav`);
}
return url;
}
}
import { HackclubProvider } from "./provider";
const asd = new HackclubProvider(process.env.API_KEY ?? "");
const ai = new HackclubProvider(process.env.API_KEY ?? "");
await asd.generateImage("sunset", undefined, { save: true });
// → generated-1234567890.png
// custom name (ext auto-handled by the model's mime type)
await asd.generateImage("sunset", undefined, {
const url = await ai.speak("Hello from Hack Club!", "minimax/speech-02-turbo", {
save: true,
filename: "my-sunset.png",
voice: "English_AnimeCharacter",
});
console.log(url);

@@ -73,6 +73,73 @@ /**

export const hackclub_replicate_tts_models = [
"minimax/speech-02-turbo",
"resemble-ai/chatterbox-pro",
"zsxkib/dia",
"lucataco/xtts-v2",
"qwen/qwen3-tts",
] as const;
export const hackclub_replicate_stt_models = [
"vaibhavs10/incredibly-fast-whisper",
"nvidia/parakeet-rnnt-1.1b",
] as const;
export const hackclub_replicate_ocr_models = [
"cuuupid/glm-4v-9b",
"lucataco/deepseek-ocr",
"abiruyt/text-extract-ocr",
] as const;
export const hackclub_replicate_upscale_models = [
"fermatresearch/magic-image-refiner",
"recraft-ai/recraft-crisp-upscale",
"google/upscaler",
] as const;
export const hackclub_replicate_image_util_models = [
"lucataco/remove-bg",
"851-labs/background-remover",
"zsxkib/ic-light-background",
"arielreplicate/robust_video_matting",
"lucataco/rembg-video",
"falcons-ai/nsfw_image_detection",
] as const;
export const hackclub_replicate_music_models = [
"google/lyria-2",
"meta/musicgen",
"minimax/music-1.5",
] as const;
export const hackclub_replicate_specialized_models = [
"retro-diffusion/rd-plus",
"geopti/sam-audio-large",
] as const;
export type HackclubReplicateTTSModel =
(typeof hackclub_replicate_tts_models)[number];
export type HackclubReplicateSTTModel =
(typeof hackclub_replicate_stt_models)[number];
export type HackclubReplicateOCRModel =
(typeof hackclub_replicate_ocr_models)[number];
export type HackclubReplicateUpscaleModel =
(typeof hackclub_replicate_upscale_models)[number];
export type HackclubReplicateImageUtilModel =
(typeof hackclub_replicate_image_util_models)[number];
export type HackclubReplicateMusicModel =
(typeof hackclub_replicate_music_models)[number];
export type HackclubReplicateSpecializedModel =
(typeof hackclub_replicate_specialized_models)[number];
export type HackclubModel = (typeof hackclub_models)[number];
export type HackclubImageModel = (typeof hackclub_image_models)[number];
export type HackclubReplicateModel =
| HackclubReplicateTTSModel
| HackclubReplicateSTTModel
| HackclubReplicateOCRModel
| HackclubReplicateUpscaleModel
| HackclubReplicateImageUtilModel
| HackclubReplicateMusicModel
| HackclubReplicateSpecializedModel;
/**

@@ -83,3 +150,13 @@ * Get all available models as an array of strings.

export function getAvailableModels(): string[] {
return [...hackclub_models, ...hackclub_image_models];
return [
...hackclub_models,
...hackclub_image_models,
...hackclub_replicate_tts_models,
...hackclub_replicate_stt_models,
...hackclub_replicate_ocr_models,
...hackclub_replicate_upscale_models,
...hackclub_replicate_image_util_models,
...hackclub_replicate_music_models,
...hackclub_replicate_specialized_models,
];
}