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

nekoai-js

v1.3.0

Published

A lightweight JavaScript/TypeScript API for NovelAI image generation, director tools and text generation

Readme

NekoAI-JS

Overview

NekoAI-JS wraps NovelAI's image and text generation APIs behind a small, strongly typed interface. It runs in both Node.js and browsers, ships CJS and ESM builds with full TypeScript definitions, and is based on the NekoAI-API Python package.

Capabilities

  • Image generation with V3, V4, and V4.5 models, including multi-character prompts, img2img, and inpainting
  • Real-time streaming of V4/V4.5 generation steps
  • Vibe transfer and character reference (director reference) for V4.5
  • Dedicated 2x/4x upscaling and img2img-based enhancement
  • All Director tools: line art, sketch, background removal, declutter, colorize, emotion change
  • Text generation (chat and completions) through NovelAI's OpenAI-compatible endpoints, with streaming
  • Tag autocomplete suggestions
  • Metadata extraction from AI-generated images (PNG text chunks, EXIF, NovelAI stealth LSB)
  • Automatic retries with exponential backoff, structured API errors, flexible image inputs

Requirements

  • Node.js 18 or later, or a modern browser
  • A NovelAI account with an active subscription and a persistent API token

Installation

npm install nekoai-js
# or: yarn add nekoai-js / pnpm add nekoai-js / bun add nekoai-js

In Node.js, install the optional canvas package if you plan to pass images by file path or buffer (img2img, inpainting, director tools, metadata extraction):

npm install canvas

Browsers use the native Canvas API and do not need this dependency.

Quick Start

import { NovelAI, Model, Resolution } from "nekoai-js";

const client = new NovelAI({ token: process.env.NOVELAI_TOKEN });

const images = await client.generateImage({
  prompt: "1girl, cute, anime style, detailed",
  model: Model.V4_5,
  resPreset: Resolution.NORMAL_PORTRAIT,
});

await images[0].save("./output"); // Node.js
// or, in the browser:
// imgElement.src = images[0].toDataURL();

The package is dual-published: import { NovelAI } from "nekoai-js" (ESM) and const { NovelAI } = require("nekoai-js") (CJS) both work.

Client Configuration

const client = new NovelAI({
  token: "your_access_token", // required
  host: Host.WEB,             // image API host (default: https://image.novelai.net)
  textHost: Host.TEXT,        // text API host (default: https://text.novelai.net)
  timeout: 120000,            // ms until the API responds, including generation time
  retry: {
    enabled: true,            // default: true
    maxRetries: 3,            // default: 3
    baseDelay: 1000,          // exponential backoff base, ms
    maxDelay: 30000,          // backoff cap, ms
    retryStatusCodes: [429, 500, 502, 503, 504],
  },
  verbose: false,             // log payloads and estimated Anlas cost
});

Image Generation

generateImage(metadata) accepts a Metadata object. Every field is optional except prompt in practice; unset fields receive the same defaults the NovelAI web UI uses (model V4_5, 28 steps, scale 6.0, Euler Ancestral sampler, quality tags and negative-prompt preset applied).

const images = await client.generateImage({
  prompt: "1girl, cute, anime style",
  model: Model.V4_5,
  resPreset: Resolution.NORMAL_PORTRAIT,
  n_samples: 1,
  steps: 28,
  scale: 6.0,
  seed: 1234567890,     // omit for a random seed
  qualityToggle: true,  // append model-specific quality tags
  ucPreset: 0,          // negative-prompt preset strength (0-3, model dependent)
});

Models

| Enum | API id | | --------------------- | ----------------------------------- | | Model.V4_5 | nai-diffusion-4-5-full | | Model.V4_5_INP | nai-diffusion-4-5-full-inpainting | | Model.V4_5_CUR | nai-diffusion-4-5-curated | | Model.V4_5_CUR_INP | nai-diffusion-4-5-curated-inpainting | | Model.V4 | nai-diffusion-4-full | | Model.V4_INP | nai-diffusion-4-full-inpainting | | Model.V4_CUR | nai-diffusion-4-curated-preview | | Model.V4_CUR_INP | nai-diffusion-4-curated-inpainting | | Model.V3 | nai-diffusion-3 | | Model.V3_INP | nai-diffusion-3-inpainting | | Model.FURRY | nai-diffusion-furry-3 | | Model.FURRY_INP | nai-diffusion-furry-3-inpainting |

Resolution presets

resPreset sets width/height unless you specify them explicitly. Explicit dimensions are rounded up to multiples of 64 and validated against the API's pixel budget.

| Preset | Dimensions | | ------------------------------- | ----------- | | SMALL_PORTRAIT / SMALL_LANDSCAPE / SMALL_SQUARE | 512x768 / 768x512 / 640x640 | | NORMAL_PORTRAIT / NORMAL_LANDSCAPE / NORMAL_SQUARE | 832x1216 / 1216x832 / 1024x1024 | | LARGE_PORTRAIT / LARGE_LANDSCAPE / LARGE_SQUARE | 1024x1536 / 1536x1024 / 1472x1472 | | WALLPAPER_PORTRAIT / WALLPAPER_LANDSCAPE | 1088x1920 / 1920x1088 |

Streaming

V4/V4.5 generations can stream each denoising step. Pass true as the second argument; the return type narrows to an async generator of MsgpackEvent objects.

import { EventType } from "nekoai-js";

const stream = await client.generateImage(
  { prompt: "1girl, night sky", model: Model.V4_5 },
  true,
);

for await (const event of stream) {
  if (event.event_type === EventType.INTERMEDIATE) {
    console.log(`step ${event.step_ix}`); // event.image is a JPEG preview
  } else if (event.event_type === EventType.FINAL) {
    await event.image.save("./output/final.png");
  }
}

Streaming with a V3 model throws, since V3 only returns final images.

Image inputs

Every image-bearing field (image, mask, reference_image_multiple, director_reference_images, and all director tool / upscale / enhance arguments) accepts any of:

  • file path (Node.js)
  • HTTP(S) URL, data URL, or blob URL
  • raw base64 string
  • Blob, File, ArrayBuffer, Uint8Array
  • HTMLImageElement, HTMLCanvasElement (browser)
  • { data: Uint8Array } (for example, a generated Image object)

Conversion happens automatically; you no longer need to call parseImage yourself (it remains exported for cases where you need dimensions).

img2img

import { Action } from "nekoai-js";

const images = await client.generateImage({
  prompt: "1girl, fantasy outfit",
  action: Action.IMG2IMG,
  image: "./input/image.png",
  strength: 0.5, // lower = closer to the source
  noise: 0.1,
});

Inpainting

Use an inpainting model with Action.INPAINT. White mask areas are regenerated.

const images = await client.generateImage({
  prompt: "1girl, red eyes",
  model: Model.V4_5_INP,
  action: Action.INPAINT,
  image: "./input/image.png",
  mask: "./input/mask.png",
  add_original_image: true, // preserve unmasked areas exactly
});

Multi-character prompts (V4/V4.5)

const images = await client.generateImage({
  prompt: "two people standing together, park background",
  model: Model.V4_5,
  characterPrompts: [
    {
      prompt: "girl, red hair, red dress",
      uc: "bad hands, bad anatomy",
      center: { x: 0.3, y: 0.5 }, // optional; coordinates in 0-1
    },
    {
      prompt: "boy, blue hair, blue uniform",
      center: { x: 0.7, y: 0.5 },
    },
  ],
});

Character coordinates are only sent when at least one character has a non-default center. The V4 prompt structures (v4_prompt, v4_negative_prompt) are built automatically.

Vibe transfer (V4/V4.5)

Reference images are encoded into vibe tokens through /ai/encode-vibe (results are cached in-memory per client, keyed by image hash, extraction level, and model).

const images = await client.generateImage({
  prompt: "1girl, cute",
  model: Model.V4_5,
  reference_image_multiple: ["./input/reference.png"],
  reference_information_extracted_multiple: [0.7], // 0-1, default 1.0
  reference_strength_multiple: [0.6],              // 0-1, default 0.6
});

Character reference (V4.5)

Director reference conditions the generation on a reference character and/or style. Reference images should be 1024x1536, 1536x1024, or 1472x1472, padded with black to fit.

const images = await client.generateImage({
  prompt: "1girl, dancing in the rain",
  model: Model.V4_5,
  director_reference_images: ["./reference/character.png"],
  director_reference_descriptions: [
    { caption: { base_caption: "character&style", char_captions: [] } },
  ],
  director_reference_information_extracted: [1],
  director_reference_strength_values: [1],
  director_reference_secondary_strength_values: [1], // fidelity, 0-1
});

Use base_caption: "character" to transfer only the character, or "character&style" to transfer the art style as well.

Upscale and enhance

// Dedicated upscaler (api.novelai.net); pixels preserved, no re-generation
const upscaled = await client.upscale("./output/image.png", 2); // scale: 2 or 4
await upscaled.save("./output/upscaled.png");

// Enhance: img2img re-generation at a scaled-up resolution
const enhanced = await client.enhance("./output/image.png", {
  prompt: "1girl, cute, watercolor", // ideally the image's original prompt
  upscaleFactor: 1.5,                // clamped to the API's pixel budget
  strength: 0.4,                     // lower = closer to the source
});
await enhanced[0].save("./output/enhanced.png");

Director Tools

Each tool takes any supported image input and returns a single Image.

const lineArt = await client.lineArt("./input/image.png");
const sketch = await client.sketch("./input/image.png");
const noBackground = await client.backgroundRemoval("./input/image.png");
const decluttered = await client.declutter("./input/image.png");
const colorized = await client.colorize("./input/lineart.png", "blue hair", 0);

import { EmotionOptions, EmotionLevel } from "nekoai-js";
const happy = await client.changeEmotion(
  "./input/image.png",
  EmotionOptions.HAPPY,
  "",                  // additional prompt
  EmotionLevel.NORMAL, // strength of the change
);

Tag Suggestions

const suggestions = await client.suggestTags("blue hai");
// [{ tag: "blue hair", confidence: ..., count: ... }, ...]

An optional second argument selects the model (Model.V4_5 by default) and a third the query language ("en" or "jp").

Text Generation

Text generation uses NovelAI's OpenAI-compatible endpoints on text.novelai.net. Query the live model list with listTextModels(); the TextModel enum covers the currently available ids (glm-4-6, xialong-v1).

import { TextModel } from "nekoai-js";

const models = await client.listTextModels();

// Chat completion; a plain string becomes a single user message
const completion = await client.chat("Describe a cozy tavern in one sentence.", {
  model: TextModel.GLM_4_6, // default
  max_tokens: 100,
  temperature: 1.0,
});
console.log(completion.choices[0].message.content);

// Message arrays with roles
const reply = await client.chat(
  [
    { role: "system", content: "You are a concise storyteller." },
    { role: "user", content: "Continue: The dragon opened one eye and" },
  ],
  { max_tokens: 100 },
);

// Streaming
const stream = await client.chatStream("Tell me a short story.", { max_tokens: 200 });
for await (const chunk of stream) {
  const delta = chunk.choices[0]?.delta?.content;
  if (delta) process.stdout.write(delta);
}

// Raw text completion
const continuation = await client.completion("The old lighthouse keeper", {
  max_tokens: 100,
});
console.log(continuation.choices[0].text);

Options follow the OpenAI parameter names (max_tokens, temperature, top_p, top_k, min_p, frequency_penalty, presence_penalty, stop, seed, logit_bias, n); unknown keys are passed through to the API unchanged.

Working with Results

Generation methods return Image objects:

| Member | Description | | ------ | ----------- | | image.data | Raw bytes (Uint8Array) | | image.filename | Timestamped default filename | | image.size | Size in bytes | | await image.save(path) | Write to disk (Node.js). Directories are created as needed; a trailing / or extension-less path is treated as a directory | | image.toBase64() | Base64 string | | image.toDataURL() | data: URL for direct use in img.src | | image.toBlob() / image.toFile() | Browser Blob / File |

Metadata Extraction

Read generation parameters back out of AI-generated images. Supports NovelAI PNG text chunks, NovelAI stealth metadata (alpha-channel LSB), and Stable Diffusion WebUI EXIF/parameters formats.

import { extractImageMetadata, getImageSummary } from "nekoai-js";

const metadata = await extractImageMetadata("./image.png");
// { type: "NOVELAI" | "SD-WEBUI" | "NONE", entries: [{ keyword, text }, ...] }

const summary = await getImageSummary("./image.png");
// { dimensions, generationTool, positivePrompt, parameters, ... }

Error Handling

API failures throw NovelAIApiError, which carries the HTTP status and the error message returned by the API:

import { NovelAIApiError } from "nekoai-js";

try {
  await client.generateImage({ prompt: "1girl" });
} catch (err) {
  if (err instanceof NovelAIApiError) {
    console.error(err.status, err.message); // e.g. 402 "Not enough Anlas"
  }
}

Retryable failures (rate limits, 5xx responses, network errors, timeouts) are retried automatically with exponential backoff and jitter according to the client's retry configuration. Set retry: { enabled: false } to disable.

Method Reference

| Method | Description | | ------ | ----------- | | generateImage(metadata, stream?, isOpus?) | Generate images; stream: true returns an async generator of step events | | enhance(image, options?) | img2img re-generation at a scaled-up resolution | | upscale(image, scale?) | Dedicated 2x/4x upscaler | | lineArt(image) / sketch(image) / backgroundRemoval(image) / declutter(image) | Director tools | | colorize(image, prompt?, defry?) | Colorize sketch or line art | | changeEmotion(image, emotion?, prompt?, level?) | Change a character's emotion | | suggestTags(prompt, model?, lang?) | Tag autocomplete | | chat(messages, options?) | Chat completion (OpenAI format) | | chatStream(messages, options?) | Streaming chat completion | | completion(prompt, options?) | Raw text completion | | listTextModels() | Available text model ids | | useDirectorTool(request) | Low-level director tool access |

Browser Usage

The library works in browsers without extra dependencies; image parsing uses the DOM Canvas API. Note that calling the NovelAI API directly from a browser is subject to CORS policy, and embedding a user's token in client-side code should be handled with care. For user-facing applications, NovelAI recommends asking each user for their own persistent API token.

<script type="module">
  import { NovelAI, Model } from "./node_modules/nekoai-js/dist/index.mjs";

  const client = new NovelAI({ token });
  const images = await client.generateImage({ prompt: "1girl", model: Model.V4_5 });
  document.querySelector("img").src = images[0].toDataURL();
</script>

Development

npm install
npm run typecheck  # tsc --noEmit
npm test           # vitest (offline unit tests)
npm run lint       # eslint
npm run build      # tsup -> dist/ (CJS + ESM + d.ts)

Examples in examples/ run against the live API and expect a NOVELAI_TOKEN entry in .env.

License

Licensed under AGPL-3.0.

This project transitioned from MIT to AGPL-3.0 to align with its inspiration source, NekoAI-API, and to provide stronger copyleft protections for the community.

References