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

movie-agent

v1.1.4

Published

Movie Agent - A TypeScript-based movie recommendation agent

Readme

Movie Agent

An intelligent movie recommendation system that helps users discover movies based on their preferences and shows where they're available to stream in Canada.

Features

  • 🎬 Personalized movie recommendations based on mood, genre, and preferences
  • 🤖 AI-powered output formatting with LangChain and Google Gemini
  • 🌊 Streaming and non-streaming output modes
  • 📺 Real-time streaming availability for Canadian platforms
  • ⚡ Fast responses using TMDb API
  • 🎯 Smart filtering by runtime, release year, and streaming platforms
  • 📦 Easy integration into web applications and APIs
  • 🔒 Multi-tenant cache isolation for secure deployments (see Cache Isolation Guide)

Prerequisites

Installation

npm install movie-agent

Quick Start

Basic Usage - Get Structured Data

import { MovieAgent } from 'movie-agent';

// Create agent
const agent = new MovieAgent();

// Get structured recommendations
const response = await agent.getRecommendations({
  mood: 'excited',
  platforms: ['Netflix', 'Prime Video'],
});

console.log(response.recommendations);

AI-Formatted Output - Invoke Mode (Waits for complete response)

import { MovieAgent } from 'movie-agent';

const agent = new MovieAgent();

// Get AI-formatted markdown output
const output = await agent.invoke({
  mood: 'happy',
  platforms: ['Netflix'],
});

console.log(output); // Formatted markdown string

AI-Formatted Output - Stream Mode (Real-time streaming)

import { MovieAgent } from 'movie-agent';

const agent = new MovieAgent();

// Stream AI-formatted output in real-time
await agent.stream({
  mood: 'excited',
  platforms: ['Netflix'],
}, (chunk) => {
  process.stdout.write(chunk); // or update your UI
});

Using with MovieAgentFactory

import { MovieAgentFactory } from 'movie-agent';

// Create agent with explicit configuration (Gemini)
const agent = MovieAgentFactory.create({
  tmdbAccessToken: process.env.TMDB_ACCESS_TOKEN!,
  tmdbRegion: 'CA',
  llmProvider: 'gemini',
  geminiApiKey: process.env.GEMINI_API_KEY,
  debug: true,
});

// Or use Azure OpenAI
const agentWithAzure = MovieAgentFactory.create({
  tmdbAccessToken: process.env.TMDB_ACCESS_TOKEN!,
  tmdbRegion: 'CA',
  llmProvider: 'azure',
  azureOpenAiApiKey: process.env.AZURE_OPENAI_API_KEY,
  azureOpenAiEndpoint: process.env.AZURE_OPENAI_ENDPOINT,
  azureOpenAiDeployment: process.env.AZURE_OPENAI_DEPLOYMENT,
  debug: true,
});

// All three methods available: getRecommendations, invoke, stream
const structured = await agent.getRecommendations({ mood: 'happy' });
const formatted = await agent.invoke({ mood: 'happy' });
await agent.stream({ mood: 'happy' }, (chunk) => console.log(chunk));

API Reference

MovieAgent Methods

getRecommendations(input: UserInput): Promise<AgentResponse | ErrorResponse>

Returns structured movie recommendations with metadata.

const response = await agent.getRecommendations({
  mood: 'happy',
  platforms: ['Netflix']
});
// Returns: { recommendations: [...], metadata: {...} }

invoke(input: UserInput): Promise<string | ErrorResponse>

Returns AI-formatted markdown output (non-streaming). Waits for complete response before returning.

const output = await agent.invoke({
  mood: 'excited',
  platforms: ['Netflix']
});
// Returns: Formatted markdown string

stream(input: UserInput, onChunk: (chunk: string) => void): Promise<void | ErrorResponse>

Streams AI-formatted output in real-time. Best for interactive UIs.

await agent.stream({
  mood: 'relaxed',
  platforms: ['Netflix']
}, (chunk) => {
  process.stdout.write(chunk);
});

Input Parameters

interface UserInput {
  mood?: string;                    // e.g., 'excited', 'relaxed', 'thoughtful', 'happy', 'scared'
  genre?: string | string[];        // e.g., 'Action' or ['Action', 'Thriller']
  platforms?: string[];             // e.g., ['Netflix', 'Prime Video', 'Disney+']
  runtime?: {
    min?: number;                   // Minimum runtime in minutes
    max?: number;                   // Maximum runtime in minutes
  };
  releaseYear?: number | {          // Single year or range
    from?: number;
    to?: number;
  };
}

Output Comparison

getRecommendations() - Structured Data

{
  "recommendations": [
    {
      "tmdbId": 123,
      "title": "Movie Title",
      "releaseYear": "2024",
      "runtime": 120,
      "genres": ["Action", "Adventure"],
      "description": "...",
      "streamingPlatforms": [...],
      "matchReason": "...",
      "posterUrl": "https://image.tmdb.org/t/p/w500/abc123.jpg"
    }
  ],
  "metadata": {...}
}

invoke() / stream() - AI-Formatted Markdown

Hey there! 👋 Looking for some action-packed thrills? Here are your recommendations:

### **Movie Title (2024)** - 120 minutes
*Genres: Action, Adventure*
A compelling description...
📺 Available on: Netflix
✨ Why: Perfect for your excited mood with thrilling action!

API Examples

// Simple mood-based search
await agent.getRecommendations({ mood: 'happy' });

// AI-formatted output
await agent.invoke({ mood: 'happy' });

// Streaming output
await agent.stream({ mood: 'happy' }, (chunk) => console.log(chunk));

// Genre-specific with platform filter
await agent.getRecommendations({
  genre: 'Action',
  platforms: ['Disney+']
});

// Complex filtering
await agent.getRecommendations({
  mood: 'adventurous',
  platforms: ['Netflix', 'Prime Video'],
  runtime: { min: 90, max: 150 },
  releaseYear: { from: 2020, to: 2024 }
});

// Multiple genres
await agent.getRecommendations({
  genre: ['Comedy', 'Romance'],
  platforms: ['Netflix'],
  runtime: { max: 120 }
});

Integration Examples

Next.js API Route (Structured Data)

// pages/api/recommendations.ts
import { MovieAgent } from 'movie-agent';
import type { NextApiRequest, NextApiResponse } from 'next';

const agent = new MovieAgent();

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  try {
    const { mood, genre, platforms } = req.body;
    const recommendations = await agent.getRecommendations({ mood, genre, platforms });
    res.status(200).json(recommendations);
  } catch (error) {
    console.error('Error getting recommendations:', error);
    res.status(500).json({ error: 'Failed to get recommendations' });
  }
}

Next.js API Route (AI-Formatted Streaming)

// pages/api/recommendations-stream.ts
import { MovieAgent } from 'movie-agent';
import type { NextApiRequest, NextApiResponse } from 'next';

const agent = new MovieAgent();

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  try {
    const { mood, genre, platforms } = req.body;
    
    res.setHeader('Content-Type', 'text/plain; charset=utf-8');
    res.setHeader('Transfer-Encoding', 'chunked');
    
    await agent.stream({ mood, genre, platforms }, (chunk) => {
      res.write(chunk);
    });
    
    res.end();
  } catch (error) {
    console.error('Error streaming recommendations:', error);
    res.status(500).json({ error: 'Failed to stream recommendations' });
  }
}

Express Server (Streaming)

import express from 'express';
import { MovieAgent } from 'movie-agent';

const app = express();
app.use(express.json());

const agent = new MovieAgent();

// Structured data endpoint
app.post('/api/recommendations', async (req, res) => {
  try {
    const recommendations = await agent.getRecommendations(req.body);
    res.json(recommendations);
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: 'Failed to get recommendations' });
  }
});

// Streaming endpoint
app.post('/api/recommendations/stream', async (req, res) => {
  try {
    res.setHeader('Content-Type', 'text/plain; charset=utf-8');
    res.setHeader('Transfer-Encoding', 'chunked');
    
    await agent.stream(req.body, (chunk) => {
      res.write(chunk);
    });
    
    res.end();
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: 'Failed to stream recommendations' });
  }
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

React Component with Streaming

import { useState } from 'react';

function MovieRecommendations() {
  const [output, setOutput] = useState('');
  const [loading, setLoading] = useState(false);

  const getRecommendations = async () => {
    setLoading(true);
    setOutput('');

    const response = await fetch('/api/recommendations/stream', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ mood: 'happy', platforms: ['Netflix'] }),
    });

    const reader = response.body?.getReader();
    const decoder = new TextDecoder();

    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      
      const chunk = decoder.decode(value);
      setOutput(prev => prev + chunk);
    }

    setLoading(false);
  };

  return (
    <div>
      <button onClick={getRecommendations} disabled={loading}>
        Get Recommendations
      </button>
      <div style={{ whiteSpace: 'pre-wrap' }}>{output}</div>
    </div>
  );
}

Configuration

Environment Variables

# Required
TMDB_ACCESS_TOKEN=your_tmdb_api_key_here

# LLM Provider Selection (optional)
LLM_PROVIDER=gemini  # Options: gemini, azure

# Option 1: Google Gemini (for AI-formatted output)
GEMINI_API_KEY=your_gemini_api_key_here

# Option 2: Azure OpenAI (for AI-formatted output)
AZURE_OPENAI_API_KEY=your_azure_openai_api_key_here
AZURE_OPENAI_ENDPOINT=https://your-resource-name.openai.azure.com/
AZURE_OPENAI_DEPLOYMENT=your_deployment_name

# Optional
TMDB_REGION=CA
CACHE_TTL=86400
MAX_RECOMMENDATIONS=5
MIN_RECOMMENDATIONS=3

Note: If no LLM API key is provided, the invoke() and stream() methods will use a fallback formatter.

LLM Provider Configuration

The package supports multiple LLM providers for AI-formatted output:

Google Gemini (Default)

const agent = MovieAgentFactory.create({
  tmdbAccessToken: 'your-tmdb-key',
  llmProvider: 'gemini',
  geminiApiKey: 'your-gemini-key',
});

Azure OpenAI

const agent = MovieAgentFactory.create({
  tmdbAccessToken: 'your-tmdb-key',
  llmProvider: 'azure',
  azureOpenAiApiKey: 'your-azure-key',
  azureOpenAiEndpoint: 'https://your-resource.openai.azure.com/',
  azureOpenAiDeployment: 'your-deployment-name',
});

Using Environment Variables

# Set LLM_PROVIDER in .env
LLM_PROVIDER=azure
AZURE_OPENAI_API_KEY=your_key
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
AZURE_OPENAI_DEPLOYMENT=gpt-4
// Automatically uses Azure OpenAI from environment
const agent = MovieAgentFactory.fromEnv();

Input Parameters

interface UserInput {
  mood?: string;                    // e.g., 'excited', 'relaxed', 'thoughtful'
  genre?: string | string[];        // e.g., 'Action' or ['Action', 'Thriller']
  platforms?: string[];             // e.g., ['Netflix', 'Prime Video']
  runtime?: {
    min?: number;                   // Minimum runtime in minutes
    max?: number;                   // Maximum runtime in minutes
  };
  releaseYear?: number | {          // Single year or range
    from: number;
    to: number;
  };
}

Error Handling

const result = await agent.getRecommendations(input);

if ('error' in result) {
  console.error(`Error: ${result.errorType} - ${result.message}`);
  
  switch (result.errorType) {
    case 'INVALID_API_KEY':
      // Handle invalid API key
      break;
    case 'RATE_LIMIT_EXCEEDED':
      // Handle rate limit
      break;
    case 'NO_RESULTS':
      // No movies found matching criteria
      break;
  }
} else {
  // Success! Use recommendations
  console.log(result.recommendations);
}

Supported Streaming Platforms (Canada)

  • Netflix
  • Prime Video
  • Crave
  • Disney+
  • Apple TV+
  • Paramount+
  • And many more regional platforms

TypeScript Support

The package is fully typed. Import types as needed:

import {
  MovieAgentFactory,
  UserInput,
  AgentResponse,
  MovieRecommendation,
  ErrorResponse,
  buildPosterUrl,        // Utility to build poster URLs with different sizes
  TMDB_IMAGE_BASE_URL,   // Base URL for TMDb images
} from 'movie-agent';

// Build poster URL with custom size (default is w500)
const smallPoster = buildPosterUrl('/abc123.jpg', 'w185');
// => "https://image.tmdb.org/t/p/w185/abc123.jpg"

Response Format

Success Response

interface AgentResponse {
  recommendations: MovieRecommendation[];
  metadata?: {
    timestamp?: string;
    inputParameters?: UserInput;
  };
}

interface MovieRecommendation {
  tmdbId: number;
  title: string;
  releaseYear: string;
  runtime: number;
  genres: string[];
  overview: string;
  streamingPlatforms: StreamingPlatform[];
  matchReason: string;
  posterUrl: string | null;  // Full URL to movie poster image (w500 size)
}

Error Response

interface ErrorResponse {
  error: true;
  errorType: 'VALIDATION_ERROR' | 'INVALID_API_KEY' | 'RATE_LIMIT_EXCEEDED' | 'NO_RESULTS' | 'UNKNOWN_ERROR';
  message: string;
  timestamp: string;
}

Development

For Contributors

If you want to develop or modify this package:

# Clone the repository
git clone https://github.com/imWayneWY/movie-agent.git
cd movie-agent

# Install dependencies
npm install

# Copy environment variables template
cp .env.example .env

# Add your API keys to .env

Building

# Type checking
npm run type-check

# Lint code
npm run lint

# Fix linting issues automatically
npm run lint:fix

# Format code
npm run format

# Check formatting without modifying files
npm run format:check

# Run all validations (type-check + lint + coverage)
npm run validate

Building

# Clean build artifacts
npm run clean

# Build TypeScript to JavaScript
npm run build

# Clean and build (runs validation first)
npm run prebuild && npm run build

Testing

The project follows Test-Driven Development (TDD) practices with comprehensive test coverage.

Running Tests

# Run all tests (unit + E2E)
npm test

# Run only unit tests (excludes E2E and live integration tests)
npm run test:unit

# Run E2E tests
npm run test:e2e

# Run live integration tests (requires API keys)
npm run test:integration

# Run tests with coverage report
npm run test:coverage

# Run tests in watch mode (for development)
npm run test:watch

# Run tests in CI mode (with coverage)
npm run test:ci

Test Structure

  • Unit Tests (src/__tests__/*.test.ts) - Test individual modules and functions
  • E2E Tests (src/__tests__/e2e.test.ts) - Test complete recommendation pipeline
  • Integration Tests (src/__tests__/*.live.test.ts) - Test with real APIs (requires credentials)

Coverage Requirements

The project enforces minimum 90% code coverage across:

  • Branches: 90%
  • Functions: 90%
  • Lines: 90%
  • Statements: 90%

View coverage report after running tests:

npm run test:coverage
open coverage/lcov-report/index.html

CI/CD Workflow

GitHub Actions Example

name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm ci
      - run: npm run type-check
      - run: npm run lint
      - run: npm run test:ci
      - uses: codecov/codecov-action@v3
        with:
          files: ./coverage/lcov.info

Pre-commit Workflow

Recommended pre-commit hook (.git/hooks/pre-commit):

#!/bin/sh
npm run type-check && npm run lint && npm run test:unit

Development Workflow

  1. Create a new branch

    git checkout -b feature/your-feature
  2. Make changes with TDD

    # Write tests first
    npm run test:watch
    # Implement feature
    # Ensure tests pass
  3. Validate before commit

    npm run validate
  4. Commit and push

    git add .
    git commit -m "feat: your feature description"
    git push origin feature/your-feature
  5. Create Pull Request

    • Ensure CI passes
    • Request code review
    • Merge when approved

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT

Acknowledgments