# AGENTS.md This file contains guidelines and commands for agentic coding agents working in this TypeScript news aggregation and Mastodon bot repository. ## Project Overview This is a TypeScript-based news aggregation system that scrapes 20+ Chilean news portals and posts articles to Mastodon (mastodon.cl). The system runs on AWS Lambda using the Serverless Framework and includes interactive bots for fortune telling, reminders, and anniversaries. ## Build and Development Commands ### Core Commands ```bash # Build TypeScript to JavaScript bun run build # or npx tsc # Run tests (currently placeholder) bun run test # Lint code bun run lint # or npx eslint src/ # Start local development server bun run offline # or serverless offline ``` ### Running Individual Tests Currently no testing framework is implemented. The test script is a placeholder (`"test": "exit 0"`). When adding tests, use Jest or Vitest and update this section. ### Deployment ```bash # Deploy to AWS Lambda serverless deploy # Deploy specific function serverless deploy function -f emol ``` ## Code Style Guidelines ### TypeScript Configuration - **Target**: ES6 - **Module System**: CommonJS - **Strict Mode**: Enabled (`strictNullChecks: true`) - **Output Directory**: `dist/` - **Source Directory**: `src/` ### Import Style ```typescript // External libraries first import "dotenv/config"; import { type Handler } from "aws-lambda"; import { createRestAPIClient, mastodon } from "masto"; // Internal modules with relative paths import ScraperArticles from "../utils/scraper-articles"; import RedisClient from "../libs/redis-client"; import LogLevels from "../enums/log-levels"; import config from "../config"; // Type imports import { type IScraperArticlesOptions } from "../interfaces/scaper-articles-options"; ``` ### Naming Conventions - **Classes**: PascalCase (`Portal`, `RedisClient`) - **Interfaces**: PascalCase with `I` prefix (`IArticle`, `IScraperOptions`) - **Enums**: PascalCase (`LogLevels`, `Emojis`) - **Variables**: camelCase (`_name`, `_mastodonClient`) - **Constants**: UPPER_SNAKE_CASE (`LOG_LEVEL`, `REDIS_CONN`) - **Files**: kebab-case (`scraper-articles.ts`, `log-levels.ts`) ### Class Structure ```typescript export default class Portal { private readonly _name: string; private readonly _mastodonClient: mastodon.rest.Client; constructor(name: string, accessToken: string, options: IOptions) { this._name = name; // Initialize properties } public async run(): Promise { try { // Main logic } catch (err: any) { // Error handling } } public getHandler(): Handler { return async (event, context) => { await this.run(event, context); } } } ``` ### Interface Definitions ```typescript export interface IArticle { title: string content: string link: string image: File | null author: string date: string } ``` ### Enum Definitions ```typescript enum LogLevels { DEBUG = "debug", INFO = "info" }; export default LogLevels; ``` ### Error Handling ```typescript try { // Code that might throw } catch (err: any) { console.log(`${this._name} | An error has occurred\n`); console.error(err.message); if (config.LOG_LEVEL === LogLevels.DEBUG) { console.debug("Additional context:", event, context); } } ``` ### Logging - Use `console.log()` for general logging - Use `console.error()` for errors - Use `console.debug()` for debug information (only when `LOG_LEVEL === "debug"`) - Include class name in logs: `${this._name} | Message` ### Environment Configuration - All configuration in `src/config.ts` - Use environment variables with fallbacks: `process.env.VAR ?? "default"` - Import config: `import config from "../config"` ### ESLint Rules - Quotes: Disabled (can use single or double) - Semicolons: Disabled (optional) - Base: `standard-with-typescript` ### File Organization ``` src/ ├── agents/ # Interactive Mastodon bots ├── enums/ # TypeScript enums ├── interfaces/ # TypeScript interfaces ├── libs/ # Client libraries (redis, postgres) ├── portales/ # News portal scrapers │ └── [portal]/ │ ├── handler.ts │ ├── definition.yml │ └── scraper.ts ├── utils/ # Utility functions ├── test/ # Test files └── config.ts # Central configuration ``` ### Portal Implementation Pattern Each news portal follows this structure: 1. `handler.ts` - Lambda handler function 2. `definition.yml` - Serverless function definition 3. `scraper.ts` - Portal-specific scraping logic 4. Extends base `Portal` class from `src/portales/portal.ts` ### Mastodon Integration - Use `masto` library for API calls - Create client: `createRestAPIClient({ url: config.MASTODON_URL, accessToken })` - Post statuses: `client.v1.statuses.create({ status: message, mediaIds })` - Upload media: `client.v2.media.create({ file: image, description })` ### Redis Caching - Use Redis client for duplicate prevention - Store article links with expiration - Key pattern: article link, Value: date string - Default expiration: 24 hours (`60 * 60 * 24`) ### Message Formatting - Include emojis from `Emojis` enum - Limit message length to 400 characters - Format: `${EMOJI} Title\n\nContent\n\n${HASHTAGS}\n\n${LINK}` - Handle special cases (subscriber-only content, publirreportajes) ## Development Notes ### Package Manager - Primary: Bun (uses `bun.lockb`) - Fallback: npm (has `package-lock.json`) ### Dependencies - **Scraping**: `axios`, `cheerio`, `chrono-node` - **Social**: `masto` (Mastodon API) - **Database**: `postgres`, `redis` - **Utilities**: `date-fns-tz`, `dotenv`, `cron` ### Testing - No testing framework currently implemented - When adding tests, consider Jest or Vitest - Test files should be excluded from TypeScript compilation - Place tests in `src/test/` directory ### Deployment - Uses Serverless Framework - AWS Lambda with Node.js 20.x runtime - Functions triggered hourly via CloudWatch Events - Timeout: 60 seconds ### Security - Never commit secrets or API keys - Use environment variables for sensitive data - Access tokens stored in environment variables - Redis connection string configurable