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

quest-forge

v0.1.0

Published

Schema-first game content generator powered by Vercel AI SDK

Downloads

29

Readme

quest-forge

Schema-first game content generator powered by Vercel AI SDK, Zod, and balance constraints.

quest-forge helps game developers generate structured, perfectly balanced game content (quests, items, NPCs, loot tables). Instead of relying on raw LLM prompts that easily break structural rules or game balance, quest-forge combines Zod schemas with custom balance rules and an automatic iterative repair loop.


Key Features

  • 🛡️ Zod Schema First: Type-safe generation derived directly from your Zod schemas.
  • ⚖️ Dynamic Balance Constraints: Enforce complex game math & range rules (e.g. gold rewards scaled by level).
  • 🔄 Self-Healing Repair Loop: Automatic validation and targeted re-prompting when LLMs generate invalid fields.
  • 🚀 Batch Generation & Deduplication: Generate hundreds of items with built-in title/name duplicate prevention.
  • 🖥️ Developer CLI: Inspect items as colorized terminal cards, pipe raw JSON to jq, or export to files.

Requirements

  • Node.js: >= 20.0.0
  • API Key: OPENAI_API_KEY or ANTHROPIC_API_KEY (for CLI & LLM execution)

Installation

npm install quest-forge zod ai

60-Second Quick Start

Generate 10 valid quest objects using the CLI and an example schema:

# Set your API key
export OPENAI_API_KEY="your-openai-api-key"

# Generate 10 quests with level and theme parameters
npx quest-forge generate \
  --schema ./examples/quest.schema.ts \
  --count 10 \
  --param level=5 \
  --param theme=dark_forest \
  --model openai:gpt-4o-mini \
  --out quests.json

CLI Reference

quest-forge includes a CLI tool for generating content directly from schema files.

Security Note: --schema executes the specified file as code via jiti. Only load schema files from trusted sources.

Options

| Flag | Short | Type | Default | Description | | :--- | :--- | :--- | :--- | :--- | | --schema | -s | string | Required | Path to .ts/.js Zod schema file | | --count | -c | number | 1 | Number of items to generate | | --param | -p | string | {} | Generation parameters (key=value, repeatable) | | --model | -m | string | — | Provider and model spec (openai:gpt-4o-mini, anthropic:claude-3-5-sonnet-20241022) | | --out | -o | string | — | Output JSON file path | | --json | — | boolean | false | Output raw JSON array to stdout | | --temperature | -t | number | Model default | Sampling temperature (0 to 2) | | --concurrency | — | number | 3 | Number of concurrent generation workers |

CLI Schema File Format

To use a schema file with the CLI, export a default object containing schema, optional constraints, and optional examples:

// quest.schema.ts
import { z } from "zod";
import { range, oneOf } from "quest-forge";

export const questSchema = z.object({
  title: z.string().describe("Quest title"),
  requiredLevel: z.number(),
  difficulty: z.enum(["easy", "medium", "hard"]),
});

export default {
  schema: questSchema,
  constraints: [
    range("requiredLevel", (_obj, params) => {
      const level = (params?.level as number) || 1;
      return [Math.max(1, level - 2), level + 2];
    }),
    oneOf("difficulty", ["easy", "medium", "hard"]),
  ],
  examples: [
    { title: "The Goblin Raid", requiredLevel: 3, difficulty: "easy" },
  ],
};

Output Modes

Note: Without --out or --json, generated items are only printed to stdout and NOT saved to any file.

Progress updates (Generating X/Y...) and failure summaries are written to stderr, keeping stdout clean for UNIX redirection and piping.

1. Default Mode (Human-Readable Terminal Cards)

Outputs formatted, colorized cards to stdout. Perfect for quick terminal inspection:

npx quest-forge generate \
  --schema ./examples/quest.schema.ts \
  --count 2 \
  --param level=5 \
  --model openai:gpt-4o-mini

Example output:

────────────────────────────────────────
#1: The Lost Amulet of Eloria
  description: Retrieve the ancient silver amulet stolen by goblin raiders.
  requiredLevel: 5
  difficulty: medium
  reward.gold: 150
  reward.experience: 450
  reward.itemRarity: rare
────────────────────────────────────────
#2: Whisper in the Dark Forest
  description: Investigate mysterious disappearances near the ancient grove.
  requiredLevel: 5
  difficulty: hard
  reward.gold: 210
  reward.experience: 600
  reward.itemRarity: epic

2. Raw JSON Stream (--json)

Outputs an unformatted JSON array to stdout for shell redirection (>) or piping into CLI tools (jq):

# Redirect to file:
npx quest-forge generate \
  --schema ./examples/quest.schema.ts \
  --count 5 \
  --json \
  --model openai:gpt-4o-mini > quests.json

# Pipe into jq:
npx quest-forge generate \
  --schema ./examples/quest.schema.ts \
  --count 5 \
  --json \
  --model openai:gpt-4o-mini | jq '.[] | .title'

3. File Output (--out)

Saves a formatted JSON array to the specified file. Terminal (stderr) displays only progress and summary:

npx quest-forge generate \
  --schema ./examples/quest.schema.ts \
  --count 10 \
  --out quests.json \
  --model openai:gpt-4o-mini

Programmatic API

1. Define a Schema & Generator

import { z } from "zod";
import { defineGenerator, range, custom } from "quest-forge";
import { openai } from "@ai-sdk/openai";

const questSchema = z.object({
  title: z.string().describe("Descriptive title of the fantasy quest"),
  requiredLevel: z.number().describe("Minimum player level required"),
  reward: z.object({
    gold: z.number().describe("Gold awarded upon completion"),
    experience: z.number().describe("EXP awarded"),
  }),
});

const generator = defineGenerator({
  schema: questSchema,
  constraints: [
    // Dynamic gold range based on level parameter
    range("reward.gold", (_obj, params) => {
      const level = (params?.level as number) || 1;
      return [level * 10, level * 50];
    }),
    // Custom balance check: experience should not exceed 10x gold reward
    custom((obj) => {
      if (obj.reward.experience > obj.reward.gold * 10) {
        return "Experience reward is disproportionately high compared to gold reward.";
      }
      return null;
    }),
  ],
  model: openai("gpt-4o-mini"),
  temperature: 0.7,
  maxRepairs: 3,
});

2. Generate a Single Object (generate)

const quest = await generator.generate({ level: 5 });
console.log(quest);

3. Generate a Batch (generateBatch)

const { items, failures } = await generator.generateBatch({
  count: 20,
  concurrency: 3,
  temperature: 0.8,
  params: { level: 5 },
  onProgress: ({ completed, total }) => {
    console.log(`Generated ${completed}/${total}...`);
  },
});

console.log(`Successfully generated ${items.length} items.`);
if (failures.length > 0) {
  console.warn(`${failures.length} items failed validation after max repairs.`);
}

Balance Constraints Reference

quest-forge provides three constraint helpers to enforce game balance rules beyond static Zod structural types:

| Helper | Signature | Description | | :--- | :--- | :--- | | range | range(path, bounds) | Enforces numeric value bounds. bounds can be [min, max] or a function (obj, params) => [min, max]. | | oneOf | oneOf(path, values) | Enforces element inclusion in an allowed list. values can be array or (obj, params) => array. | | custom | custom(fn) | Custom balance rule. fn(obj, params) returns string error message or null if valid. |

Code Examples

// 1. Static or dynamic numeric bounds
range("reward.gold", (_obj, params) => [
  ((params?.level as number) ?? 1) * 10,
  ((params?.level as number) ?? 1) * 50,
]);

// 2. Dynamic enum or option whitelist
oneOf("difficulty", (_obj, params) => {
  if (params?.theme === "dark_forest") return ["medium", "hard", "deadly"];
  return ["easy", "medium", "hard"];
});

// 3. Custom cross-field validation rule
custom((obj) => {
  if (obj.requiredLevel < 5 && obj.reward.itemRarity === "legendary") {
    return "Low level quests cannot award legendary items.";
  }
  return null;
});

Automatic Validation & Repair Loop

When the LLM generates an object, quest-forge runs both Zod structural validation and balance constraints. If any issue is found, it automatically builds a targeted repair prompt and re-invokes the LLM up to maxRepairs (default: 3).

graph TD
    A[Schema + Constraints] --> B[Build Prompt]
    B --> C[generateObject Draft]
    C --> D{Validate: Zod + Constraints}
    D -- Valid --> E[Return Object]
    D -- Invalid --> F{Repairs < maxRepairs?}
    F -- Yes --> G[Build Specific Repair Prompt]
    G --> C
    F -- No --> H[Throw GenerationError / Record Failure]

FAQ

1. How much does a batch of 100 items cost?

Token consumption depends on schema complexity and model choice. Using gpt-4o-mini with a typical 200-token prompt and 150-token output per object, a batch of 100 items costs approximately $0.01 – $0.03 total.

2. Why do unit tests use MockLanguageModelV1?

To guarantee fast, deterministic, offline CI builds without requiring real API keys or incurring LLM API costs.

3. What happens if an item reaches maxRepairs?

If an item fails validation after maxRepairs (e.g. 3 repairs = 4 total attempts), it is added to the failures array in generateBatch without aborting the rest of the batch. In single generate(), a GenerationError is thrown.

4. How does duplicate prevention work and what concurrency should I set?

quest-forge tracks accepted name or title values across a batch and supplies them to the model's avoidNames prompt list.

When running with --concurrency > 1, worker tasks start in parallel and cannot see names generated by active peers. If two workers happen to produce the same name, output deduplication catches the collision and retries once with an explicit warning prompt.

Best Practices for Unique Names:

  • Lower concurrency: Set --concurrency 1 (or 1-3) so accepted names update avoidNames immediately after each item finishes.
  • Increase temperature: Set --temperature 0.9 to encourage structural and naming variety.

License

MIT License © 2026 Koval09