movie-agent 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +464 -0
- package/bin/movie-agent +209 -0
- package/dist/agent.d.ts +89 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +365 -0
- package/dist/agent.js.map +1 -0
- package/dist/cache.d.ts +75 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +133 -0
- package/dist/cache.js.map +1 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +31 -0
- package/dist/config.js.map +1 -0
- package/dist/discover.d.ts +38 -0
- package/dist/discover.d.ts.map +1 -0
- package/dist/discover.js +121 -0
- package/dist/discover.js.map +1 -0
- package/dist/factory.d.ts +87 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +118 -0
- package/dist/factory.js.map +1 -0
- package/dist/filters.d.ts +61 -0
- package/dist/filters.d.ts.map +1 -0
- package/dist/filters.js +97 -0
- package/dist/filters.js.map +1 -0
- package/dist/format.d.ts +33 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +85 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/mood.d.ts +7 -0
- package/dist/mood.d.ts.map +1 -0
- package/dist/mood.js +21 -0
- package/dist/mood.js.map +1 -0
- package/dist/providers.d.ts +10 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/providers.js +70 -0
- package/dist/providers.js.map +1 -0
- package/dist/ranking.d.ts +57 -0
- package/dist/ranking.d.ts.map +1 -0
- package/dist/ranking.js +198 -0
- package/dist/ranking.js.map +1 -0
- package/dist/tmdbApi.d.ts +79 -0
- package/dist/tmdbApi.d.ts.map +1 -0
- package/dist/tmdbApi.js +88 -0
- package/dist/tmdbApi.js.map +1 -0
- package/dist/types.d.ts +99 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +13 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +47 -0
- package/dist/validate.js.map +1 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Wei Yan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
# Movie Agent
|
|
2
|
+
|
|
3
|
+
An intelligent movie recommendation system that helps users discover movies based on their preferences and shows where they're available to stream in Canada.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- š¬ Personalized movie recommendations based on mood, genre, and preferences
|
|
8
|
+
- šŗ Real-time streaming availability for Canadian platforms
|
|
9
|
+
- ā” Fast responses using TMDb API
|
|
10
|
+
- šÆ Smart filtering by runtime, release year, and streaming platforms
|
|
11
|
+
- š¦ Easy integration into web applications and APIs
|
|
12
|
+
|
|
13
|
+
## Prerequisites
|
|
14
|
+
|
|
15
|
+
- Node.js (v18 or higher)
|
|
16
|
+
- TMDb API key ([Get one here](https://www.themoviedb.org/settings/api))
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install movie-agent
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### Option 1: Using MovieAgentFactory.create() with explicit config (Recommended)
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { MovieAgentFactory } from 'movie-agent';
|
|
30
|
+
|
|
31
|
+
// Create agent with explicit configuration
|
|
32
|
+
const agent = MovieAgentFactory.create({
|
|
33
|
+
tmdbApiKey: process.env.TMDB_API_KEY!,
|
|
34
|
+
tmdbRegion: 'CA',
|
|
35
|
+
debug: true, // Enable debug logging
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Get recommendations
|
|
39
|
+
const recommendations = await agent.getRecommendations({
|
|
40
|
+
mood: 'excited',
|
|
41
|
+
genre: 'Action',
|
|
42
|
+
platforms: ['Netflix', 'Prime Video'],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
console.log(recommendations);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Option 2: Using MovieAgentFactory.fromEnv() convenience method
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { MovieAgentFactory } from 'movie-agent';
|
|
52
|
+
import dotenv from 'dotenv';
|
|
53
|
+
|
|
54
|
+
// Load your .env file BEFORE creating the agent
|
|
55
|
+
dotenv.config();
|
|
56
|
+
|
|
57
|
+
// Create agent from environment variables
|
|
58
|
+
const agent = MovieAgentFactory.fromEnv(true); // true = enable debug logging
|
|
59
|
+
|
|
60
|
+
// Get recommendations
|
|
61
|
+
const recommendations = await agent.getRecommendations({
|
|
62
|
+
mood: 'relaxed',
|
|
63
|
+
genre: ['Comedy', 'Romance'],
|
|
64
|
+
runtime: { max: 120 },
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## API Examples
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// Simple mood-based search
|
|
72
|
+
await agent.getRecommendations({
|
|
73
|
+
mood: 'happy'
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Genre-specific with platform filter
|
|
77
|
+
await agent.getRecommendations({
|
|
78
|
+
genre: 'Action',
|
|
79
|
+
platforms: ['Disney+']
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Complex filtering
|
|
83
|
+
await agent.getRecommendations({
|
|
84
|
+
mood: 'adventurous',
|
|
85
|
+
platforms: ['Netflix', 'Prime Video'],
|
|
86
|
+
runtime: { min: 90, max: 150 },
|
|
87
|
+
releaseYear: { from: 2020, to: 2024 }
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Multiple genres
|
|
91
|
+
await agent.getRecommendations({
|
|
92
|
+
genre: ['Comedy', 'Romance'],
|
|
93
|
+
platforms: ['Netflix'],
|
|
94
|
+
runtime: { max: 120 }
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Integration Examples
|
|
99
|
+
|
|
100
|
+
### Next.js API Route
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// pages/api/recommendations.ts
|
|
104
|
+
import { MovieAgentFactory } from 'movie-agent';
|
|
105
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
106
|
+
|
|
107
|
+
const agent = MovieAgentFactory.create({
|
|
108
|
+
tmdbApiKey: process.env.TMDB_API_KEY!,
|
|
109
|
+
tmdbRegion: 'CA',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
113
|
+
if (req.method !== 'POST') {
|
|
114
|
+
return res.status(405).json({ error: 'Method not allowed' });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const { mood, genre, platforms } = req.body;
|
|
119
|
+
const recommendations = await agent.getRecommendations({ mood, genre, platforms });
|
|
120
|
+
res.status(200).json(recommendations);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error('Error getting recommendations:', error);
|
|
123
|
+
res.status(500).json({ error: 'Failed to get recommendations' });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Express Server
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import express from 'express';
|
|
132
|
+
import { MovieAgentFactory } from 'movie-agent';
|
|
133
|
+
import dotenv from 'dotenv';
|
|
134
|
+
|
|
135
|
+
dotenv.config();
|
|
136
|
+
|
|
137
|
+
const app = express();
|
|
138
|
+
app.use(express.json());
|
|
139
|
+
|
|
140
|
+
const agent = MovieAgentFactory.create({
|
|
141
|
+
tmdbApiKey: process.env.TMDB_API_KEY!,
|
|
142
|
+
tmdbRegion: 'CA',
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
app.post('/api/recommendations', async (req, res) => {
|
|
146
|
+
try {
|
|
147
|
+
const recommendations = await agent.getRecommendations(req.body);
|
|
148
|
+
res.json(recommendations);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error(error);
|
|
151
|
+
res.status(500).json({ error: 'Failed to get recommendations' });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
app.listen(3000, () => {
|
|
156
|
+
console.log('Server running on http://localhost:3000');
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Configuration
|
|
161
|
+
|
|
162
|
+
### Environment Variables
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# Required
|
|
166
|
+
TMDB_API_KEY=your_tmdb_api_key_here
|
|
167
|
+
|
|
168
|
+
# Optional
|
|
169
|
+
TMDB_REGION=CA
|
|
170
|
+
CACHE_TTL=86400
|
|
171
|
+
MAX_RECOMMENDATIONS=5
|
|
172
|
+
MIN_RECOMMENDATIONS=3
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Input Parameters
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
interface UserInput {
|
|
179
|
+
mood?: string; // e.g., 'excited', 'relaxed', 'thoughtful'
|
|
180
|
+
genre?: string | string[]; // e.g., 'Action' or ['Action', 'Thriller']
|
|
181
|
+
platforms?: string[]; // e.g., ['Netflix', 'Prime Video']
|
|
182
|
+
runtime?: {
|
|
183
|
+
min?: number; // Minimum runtime in minutes
|
|
184
|
+
max?: number; // Maximum runtime in minutes
|
|
185
|
+
};
|
|
186
|
+
releaseYear?: number | { // Single year or range
|
|
187
|
+
from: number;
|
|
188
|
+
to: number;
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Error Handling
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
const result = await agent.getRecommendations(input);
|
|
197
|
+
|
|
198
|
+
if ('error' in result) {
|
|
199
|
+
console.error(`Error: ${result.errorType} - ${result.message}`);
|
|
200
|
+
|
|
201
|
+
switch (result.errorType) {
|
|
202
|
+
case 'INVALID_API_KEY':
|
|
203
|
+
// Handle invalid API key
|
|
204
|
+
break;
|
|
205
|
+
case 'RATE_LIMIT_EXCEEDED':
|
|
206
|
+
// Handle rate limit
|
|
207
|
+
break;
|
|
208
|
+
case 'NO_RESULTS':
|
|
209
|
+
// No movies found matching criteria
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
// Success! Use recommendations
|
|
214
|
+
console.log(result.recommendations);
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Supported Streaming Platforms (Canada)
|
|
219
|
+
|
|
220
|
+
- Netflix
|
|
221
|
+
- Prime Video
|
|
222
|
+
- Crave
|
|
223
|
+
- Disney+
|
|
224
|
+
- Apple TV+
|
|
225
|
+
- Paramount+
|
|
226
|
+
- And many more regional platforms
|
|
227
|
+
|
|
228
|
+
## TypeScript Support
|
|
229
|
+
|
|
230
|
+
The package is fully typed. Import types as needed:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import {
|
|
234
|
+
MovieAgentFactory,
|
|
235
|
+
UserInput,
|
|
236
|
+
AgentResponse,
|
|
237
|
+
MovieRecommendation,
|
|
238
|
+
ErrorResponse,
|
|
239
|
+
} from 'movie-agent';
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Response Format
|
|
243
|
+
|
|
244
|
+
### Success Response
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
interface AgentResponse {
|
|
248
|
+
recommendations: MovieRecommendation[];
|
|
249
|
+
metadata?: {
|
|
250
|
+
timestamp?: string;
|
|
251
|
+
inputParameters?: UserInput;
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
interface MovieRecommendation {
|
|
256
|
+
tmdbId: number;
|
|
257
|
+
title: string;
|
|
258
|
+
releaseYear: string;
|
|
259
|
+
runtime: number;
|
|
260
|
+
genres: string[];
|
|
261
|
+
overview: string;
|
|
262
|
+
streamingPlatforms: StreamingPlatform[];
|
|
263
|
+
matchReason: string;
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Error Response
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
interface ErrorResponse {
|
|
271
|
+
error: true;
|
|
272
|
+
errorType: 'VALIDATION_ERROR' | 'INVALID_API_KEY' | 'RATE_LIMIT_EXCEEDED' | 'NO_RESULTS' | 'UNKNOWN_ERROR';
|
|
273
|
+
message: string;
|
|
274
|
+
timestamp: string;
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Development
|
|
279
|
+
|
|
280
|
+
### For Contributors
|
|
281
|
+
|
|
282
|
+
If you want to develop or modify this package:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
# Clone the repository
|
|
286
|
+
git clone https://github.com/imWayneWY/movie-agent.git
|
|
287
|
+
cd movie-agent
|
|
288
|
+
|
|
289
|
+
# Install dependencies
|
|
290
|
+
npm install
|
|
291
|
+
|
|
292
|
+
# Copy environment variables template
|
|
293
|
+
cp .env.example .env
|
|
294
|
+
|
|
295
|
+
# Add your API keys to .env
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Building
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
# Type checking
|
|
302
|
+
npm run type-check
|
|
303
|
+
|
|
304
|
+
# Lint code
|
|
305
|
+
npm run lint
|
|
306
|
+
|
|
307
|
+
# Fix linting issues automatically
|
|
308
|
+
npm run lint:fix
|
|
309
|
+
|
|
310
|
+
# Format code
|
|
311
|
+
npm run format
|
|
312
|
+
|
|
313
|
+
# Check formatting without modifying files
|
|
314
|
+
npm run format:check
|
|
315
|
+
|
|
316
|
+
# Run all validations (type-check + lint + coverage)
|
|
317
|
+
npm run validate
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Building
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
# Clean build artifacts
|
|
324
|
+
npm run clean
|
|
325
|
+
|
|
326
|
+
# Build TypeScript to JavaScript
|
|
327
|
+
npm run build
|
|
328
|
+
|
|
329
|
+
# Clean and build (runs validation first)
|
|
330
|
+
npm run prebuild && npm run build
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Testing
|
|
334
|
+
|
|
335
|
+
The project follows Test-Driven Development (TDD) practices with comprehensive test coverage.
|
|
336
|
+
|
|
337
|
+
### Running Tests
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
# Run all tests (unit + E2E)
|
|
341
|
+
npm test
|
|
342
|
+
|
|
343
|
+
# Run only unit tests (excludes E2E and live integration tests)
|
|
344
|
+
npm run test:unit
|
|
345
|
+
|
|
346
|
+
# Run E2E tests
|
|
347
|
+
npm run test:e2e
|
|
348
|
+
|
|
349
|
+
# Run live integration tests (requires API keys)
|
|
350
|
+
npm run test:integration
|
|
351
|
+
|
|
352
|
+
# Run tests with coverage report
|
|
353
|
+
npm run test:coverage
|
|
354
|
+
|
|
355
|
+
# Run tests in watch mode (for development)
|
|
356
|
+
npm run test:watch
|
|
357
|
+
|
|
358
|
+
# Run tests in CI mode (with coverage)
|
|
359
|
+
npm run test:ci
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Test Structure
|
|
363
|
+
|
|
364
|
+
- **Unit Tests** (`src/__tests__/*.test.ts`) - Test individual modules and functions
|
|
365
|
+
- **E2E Tests** (`src/__tests__/e2e.test.ts`) - Test complete recommendation pipeline
|
|
366
|
+
- **Integration Tests** (`src/__tests__/*.live.test.ts`) - Test with real APIs (requires credentials)
|
|
367
|
+
|
|
368
|
+
### Coverage Requirements
|
|
369
|
+
|
|
370
|
+
The project enforces minimum 90% code coverage across:
|
|
371
|
+
- Branches: 90%
|
|
372
|
+
- Functions: 90%
|
|
373
|
+
- Lines: 90%
|
|
374
|
+
- Statements: 90%
|
|
375
|
+
|
|
376
|
+
View coverage report after running tests:
|
|
377
|
+
```bash
|
|
378
|
+
npm run test:coverage
|
|
379
|
+
open coverage/lcov-report/index.html
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## CI/CD Workflow
|
|
383
|
+
|
|
384
|
+
### GitHub Actions Example
|
|
385
|
+
|
|
386
|
+
```yaml
|
|
387
|
+
name: CI
|
|
388
|
+
|
|
389
|
+
on: [push, pull_request]
|
|
390
|
+
|
|
391
|
+
jobs:
|
|
392
|
+
test:
|
|
393
|
+
runs-on: ubuntu-latest
|
|
394
|
+
steps:
|
|
395
|
+
- uses: actions/checkout@v3
|
|
396
|
+
- uses: actions/setup-node@v3
|
|
397
|
+
with:
|
|
398
|
+
node-version: '18'
|
|
399
|
+
- run: npm ci
|
|
400
|
+
- run: npm run type-check
|
|
401
|
+
- run: npm run lint
|
|
402
|
+
- run: npm run test:ci
|
|
403
|
+
- uses: codecov/codecov-action@v3
|
|
404
|
+
with:
|
|
405
|
+
files: ./coverage/lcov.info
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Pre-commit Workflow
|
|
409
|
+
|
|
410
|
+
Recommended pre-commit hook (`.git/hooks/pre-commit`):
|
|
411
|
+
```bash
|
|
412
|
+
#!/bin/sh
|
|
413
|
+
npm run type-check && npm run lint && npm run test:unit
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Development Workflow
|
|
417
|
+
|
|
418
|
+
1. **Create a new branch**
|
|
419
|
+
```bash
|
|
420
|
+
git checkout -b feature/your-feature
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
2. **Make changes with TDD**
|
|
424
|
+
```bash
|
|
425
|
+
# Write tests first
|
|
426
|
+
npm run test:watch
|
|
427
|
+
# Implement feature
|
|
428
|
+
# Ensure tests pass
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
3. **Validate before commit**
|
|
432
|
+
```bash
|
|
433
|
+
npm run validate
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
4. **Commit and push**
|
|
437
|
+
```bash
|
|
438
|
+
git add .
|
|
439
|
+
git commit -m "feat: your feature description"
|
|
440
|
+
git push origin feature/your-feature
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
5. **Create Pull Request**
|
|
444
|
+
- Ensure CI passes
|
|
445
|
+
- Request code review
|
|
446
|
+
- Merge when approved
|
|
447
|
+
|
|
448
|
+
## Contributing
|
|
449
|
+
|
|
450
|
+
1. Fork the repository
|
|
451
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
452
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
453
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
454
|
+
5. Open a Pull Request
|
|
455
|
+
|
|
456
|
+
## License
|
|
457
|
+
|
|
458
|
+
MIT
|
|
459
|
+
|
|
460
|
+
## Acknowledgments
|
|
461
|
+
|
|
462
|
+
- [The Movie Database (TMDb)](https://www.themoviedb.org/) for movie data and streaming availability
|
|
463
|
+
- [LangChain.js](https://js.langchain.com/) for agent framework
|
|
464
|
+
- [OpenAI](https://openai.com/) for LLM capabilities
|
package/bin/movie-agent
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// bin/movie-agent
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Parse command-line arguments
|
|
9
|
+
*/
|
|
10
|
+
function parseArgs() {
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
const input = {};
|
|
13
|
+
|
|
14
|
+
for (let i = 0; i < args.length; i++) {
|
|
15
|
+
const arg = args[i];
|
|
16
|
+
const nextArg = args[i + 1];
|
|
17
|
+
|
|
18
|
+
if (arg.startsWith('--') && nextArg && !nextArg.startsWith('--')) {
|
|
19
|
+
const key = arg.substring(2);
|
|
20
|
+
|
|
21
|
+
switch (key) {
|
|
22
|
+
case 'mood':
|
|
23
|
+
input.mood = nextArg;
|
|
24
|
+
i++;
|
|
25
|
+
break;
|
|
26
|
+
|
|
27
|
+
case 'platforms':
|
|
28
|
+
// Support comma-separated platforms
|
|
29
|
+
input.platforms = nextArg.split(',').map(p => p.trim());
|
|
30
|
+
i++;
|
|
31
|
+
break;
|
|
32
|
+
|
|
33
|
+
case 'genre':
|
|
34
|
+
// Support comma-separated genres
|
|
35
|
+
const genres = nextArg.split(',').map(g => g.trim());
|
|
36
|
+
input.genre = genres.length === 1 ? genres[0] : genres;
|
|
37
|
+
i++;
|
|
38
|
+
break;
|
|
39
|
+
|
|
40
|
+
case 'runtimeMax':
|
|
41
|
+
if (!input.runtime) input.runtime = {};
|
|
42
|
+
input.runtime.max = parseInt(nextArg, 10);
|
|
43
|
+
i++;
|
|
44
|
+
break;
|
|
45
|
+
|
|
46
|
+
case 'runtimeMin':
|
|
47
|
+
if (!input.runtime) input.runtime = {};
|
|
48
|
+
input.runtime.min = parseInt(nextArg, 10);
|
|
49
|
+
i++;
|
|
50
|
+
break;
|
|
51
|
+
|
|
52
|
+
case 'year':
|
|
53
|
+
input.releaseYear = parseInt(nextArg, 10);
|
|
54
|
+
i++;
|
|
55
|
+
break;
|
|
56
|
+
|
|
57
|
+
case 'yearFrom':
|
|
58
|
+
if (typeof input.releaseYear !== 'object') {
|
|
59
|
+
input.releaseYear = {};
|
|
60
|
+
}
|
|
61
|
+
input.releaseYear.from = parseInt(nextArg, 10);
|
|
62
|
+
i++;
|
|
63
|
+
break;
|
|
64
|
+
|
|
65
|
+
case 'yearTo':
|
|
66
|
+
if (typeof input.releaseYear !== 'object') {
|
|
67
|
+
input.releaseYear = {};
|
|
68
|
+
}
|
|
69
|
+
input.releaseYear.to = parseInt(nextArg, 10);
|
|
70
|
+
i++;
|
|
71
|
+
break;
|
|
72
|
+
|
|
73
|
+
default:
|
|
74
|
+
console.error(`Unknown flag: ${arg}`);
|
|
75
|
+
printUsage();
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
79
|
+
printUsage();
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return input;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Print usage information
|
|
89
|
+
*/
|
|
90
|
+
function printUsage() {
|
|
91
|
+
console.log(`
|
|
92
|
+
Usage: movie-agent [options]
|
|
93
|
+
|
|
94
|
+
Options:
|
|
95
|
+
--mood <string> User's mood (e.g., "happy", "excited", "thoughtful")
|
|
96
|
+
--platforms <list> Comma-separated list of platforms (e.g., "Netflix,Prime Video")
|
|
97
|
+
--genre <list> Genre or comma-separated genres (e.g., "Action" or "Action,Comedy")
|
|
98
|
+
--runtimeMax <number> Maximum runtime in minutes
|
|
99
|
+
--runtimeMin <number> Minimum runtime in minutes
|
|
100
|
+
--year <number> Specific release year
|
|
101
|
+
--yearFrom <number> Release year range start
|
|
102
|
+
--yearTo <number> Release year range end
|
|
103
|
+
--help, -h Show this help message
|
|
104
|
+
|
|
105
|
+
Examples:
|
|
106
|
+
movie-agent --mood excited --platforms Netflix
|
|
107
|
+
movie-agent --genre Action,Thriller --runtimeMax 120
|
|
108
|
+
movie-agent --mood relaxed --yearFrom 2020 --yearTo 2023
|
|
109
|
+
`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Format recommendations for human-readable display
|
|
114
|
+
*/
|
|
115
|
+
function formatOutput(response) {
|
|
116
|
+
let output = '\nš¬ Movie Recommendations for You\n\n';
|
|
117
|
+
|
|
118
|
+
response.recommendations.forEach((movie, index) => {
|
|
119
|
+
output += `${index + 1}. **${movie.title}** (${movie.releaseYear}) ⢠${movie.runtime} min\n`;
|
|
120
|
+
output += ` Genres: ${movie.genres.join(', ')}\n\n`;
|
|
121
|
+
output += ` ${movie.description}\n\n`;
|
|
122
|
+
|
|
123
|
+
// Format streaming platforms
|
|
124
|
+
const availablePlatforms = movie.streamingPlatforms
|
|
125
|
+
.filter(p => p.available)
|
|
126
|
+
.map(p => p.name);
|
|
127
|
+
|
|
128
|
+
if (availablePlatforms.length > 0) {
|
|
129
|
+
output += ` šŗ Available on: ${availablePlatforms.join(', ')}\n`;
|
|
130
|
+
} else {
|
|
131
|
+
output += ` šŗ No streaming availability found\n`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
output += ` ⨠Why: ${movie.matchReason}\n`;
|
|
135
|
+
|
|
136
|
+
if (index < response.recommendations.length - 1) {
|
|
137
|
+
output += '\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n\n';
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return output;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Main execution
|
|
146
|
+
*/
|
|
147
|
+
async function main() {
|
|
148
|
+
try {
|
|
149
|
+
// Parse arguments first (for --help)
|
|
150
|
+
const args = process.argv.slice(2);
|
|
151
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
152
|
+
printUsage();
|
|
153
|
+
process.exit(0);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Check if .env file exists and load it
|
|
157
|
+
const envPath = path.resolve(__dirname, '../.env.development');
|
|
158
|
+
if (fs.existsSync(envPath)) {
|
|
159
|
+
require('dotenv').config({ path: envPath });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Check for TMDb API key
|
|
163
|
+
if (!process.env.TMDB_API_KEY) {
|
|
164
|
+
console.error(`
|
|
165
|
+
ā Error: TMDb API key not found
|
|
166
|
+
|
|
167
|
+
Please set the TMDB_API_KEY environment variable:
|
|
168
|
+
export TMDB_API_KEY=your_api_key_here
|
|
169
|
+
|
|
170
|
+
Or create a .env.development file with:
|
|
171
|
+
TMDB_API_KEY=your_api_key_here
|
|
172
|
+
`);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Now it's safe to load the agent module
|
|
177
|
+
const { MovieAgent } = require('../dist/agent');
|
|
178
|
+
|
|
179
|
+
const input = parseArgs();
|
|
180
|
+
|
|
181
|
+
// Show help if no arguments provided
|
|
182
|
+
if (Object.keys(input).length === 0) {
|
|
183
|
+
printUsage();
|
|
184
|
+
process.exit(0);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.log('\nš Finding movie recommendations...\n');
|
|
188
|
+
|
|
189
|
+
const agent = new MovieAgent();
|
|
190
|
+
const response = await agent.getRecommendations(input);
|
|
191
|
+
|
|
192
|
+
const output = formatOutput(response);
|
|
193
|
+
console.log(output);
|
|
194
|
+
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error('\nā Error:', error.message);
|
|
197
|
+
|
|
198
|
+
if (error.message.includes('API') || error.message.includes('network')) {
|
|
199
|
+
console.error('\nPlease check your internet connection and TMDb API key.');
|
|
200
|
+
} else if (error.message.includes('Invalid') || error.message.includes('validation')) {
|
|
201
|
+
console.error('\nPlease check your input parameters and try again.');
|
|
202
|
+
console.error('Use --help for usage information.');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
main();
|