Tina is live!
this morning we bought Tina online. from now on the’ll be handling the publsihing of blog posts.
💬 Tina: publishing
also, i think i have a bit of a man crush on Gemini. I still love ChatGPT, but for coding Gemini is a freaking Boss! See the documentaiton he created for Tina setup below.
💬 Tina: documentation
# Tina - Publishing Assistant Lambda Documentation
## 1. Introduction
Tina is an AWS Lambda function designed to act as an automated publishing assistant for a blog. It runs on a schedule, retrieves draft blog posts from an S3 bucket, processes them using AI (OpenAI API) for grammar checks and description generation, formats them for an Astro blog, commits the final post to a GitHub repository, and archives the original and processed files in S3.
## 2. Core Functionality
1. **Find Oldest Draft:** On execution (triggered by a schedule), Tina lists objects in the specified S3 draft prefix (`blog-drafts/` by default) and selects the oldest file based on modification time.
2. **Initial Checks:**
* **Empty File:** Deletes the file if it's empty or contains only whitespace.
* **Front Matter:** Parses the YAML front matter. Moves the file to the `other-files/` prefix if valid front matter isn't found.
* **Mandatory Tags:** Checks for the presence of `blog` in the `tags` list and a non-empty `title` within the front matter. Moves the file to `other-files/` if these are missing.
3. **Content Processing:**
* **AI Tags:** Processes any content within ```ai ... ``` blocks, adding a `### AI Generated:` heading and wrapping the content in a standard code block.
* **Grammar Check:** Sends the text content (excluding content within any ```...``` code blocks) to the configured OpenAI model to get grammar and spelling suggestions.
* **Comment Insertion:** Inserts comments (`💬 Tina: {suggestion}`) into the text near sentences identified with errors.
4. **Description Generation:**
* Checks if a `description` field exists in the original front matter.
* If yes, uses the existing description.
* If no, calls the OpenAI model to generate a short description based on the post content.
5. **Final Formatting:**
* Uses the mandatory `title` from the original front matter.
* Constructs the final Astro-compatible front matter, including the title, description (original or generated), `pubDatetime` (using original date if present, otherwise current time), original `tags`, and sets `draft: false`.
6. **Publishing:** Commits the fully processed file (new front matter + body with comments) to the specified GitHub repository and branch, prefixing the filename with a timestamp.
7. **Archiving:**
* If archiving is enabled (default), saves *both* the original draft content and the final processed content to the specified S3 archive prefix (`processed-archive/` by default), renaming them slightly (e.g., `draft_original.md`, `processed_original.md`).
* Deletes the original file from the draft prefix.
* If archiving is disabled, simply deletes the original file from the draft prefix.
8. **Looping:** If a file is skipped during the initial checks (deleted or moved to `other-files/`), Tina immediately checks for the next oldest file and continues processing until one file is successfully published or no suitable drafts remain in the folder for that invocation.
## 3. Environment Variables
These need to be configured in the Lambda function's settings (**Configuration -> Environment variables**).
**Required:**
* `S3_BUCKET_NAME`: The name of the S3 bucket containing the draft, archive, and other-files prefixes.
* `OPENAI_API_KEY`: Your secret API key from OpenAI. **(Sensitive - handle with care)**.
* `GITHUB_PAT`: Your GitHub Personal Access Token with `repo` scope permissions. **(Sensitive - handle with care)**.
* `GITHUB_REPO_NAME`: The name of your GitHub repository (e.g., `your-username/your-repo`).
* `GITHUB_BRANCH`: The branch in your GitHub repository to commit the published posts to (e.g., `main`, `master`).
* `GITHUB_FILE_PATH_PREFIX`: The folder path within your GitHub repository where the final `.md` files should be saved (e.g., `src/content/blog/`). Must end with a `/`.
**Optional:**
* `S3_DRAFT_PREFIX` (Default: `blog-drafts/`): The prefix (folder) in the S3 bucket where draft posts are located. Must end with a `/`.
* `S3_ARCHIVE_PREFIX` (Default: `processed-archive/`): The prefix where original and processed files are archived upon success. Must end with a `/`.
* `S3_OTHER_PREFIX` (Default: `other-files/`): The prefix where files failing initial checks (no front matter, missing required tags/title) are moved. Must end with a `/`.
* `OPENAI_API_ENDPOINT` (Default: `https://api.openai.com/v1/chat/completions`): The OpenAI API endpoint URL.
* `OPENAI_MODEL` (Default: `gpt-3.5-turbo`): The specific OpenAI model to use (e.g., `gpt-4o`, `gpt-4-turbo`).
* `LOG_LEVEL` (Default: `INFO`): Controls the logging verbosity. Set to `DEBUG` to see detailed logs, including OpenAI prompts and responses. Other valid values include `WARNING`, `ERROR`.
* `ENABLE_ARCHIVING` (Default: `true`): Controls whether the original draft and final processed file are saved to the `S3_ARCHIVE_PREFIX`. Set to `false` to disable archiving (files will just be deleted from drafts upon success).
## 4. Dependencies
The script requires the following external Python libraries:
* `boto3`: The AWS SDK for Python (usually included in the standard Python Lambda runtime).
* `requests`: For making HTTP calls to the OpenAI API.
* `PyYAML`: For parsing the YAML front matter.
* `PyGithub`: For interacting with the GitHub API.
You will need to package `requests`, `PyYAML`, and `PyGithub` with your Lambda function code.
## 5. Build Steps (Creating the Deployment Package)
You need to create a `.zip` file containing the `lambda_function.py` script and the required libraries.
1. **Create a Project Directory:**
```bash
mkdir tina-lambda-project
cd tina-lambda-project
```
2. **Save the Code:** Save the final Python script as `lambda_function.py` inside this directory.
3. **Install Dependencies Locally:** Install the required libraries into a `package` subdirectory.
```bash
pip install requests PyYAML PyGithub -t ./package
```
*(Note: If your local machine architecture differs significantly from the Lambda runtime (e.g., M1/M2 Mac vs. Amazon Linux x86_64), you might need to build dependencies using Docker or specify compatible versions/platforms if `pip` supports it for these packages).*
4. **Create the ZIP File:**
* Navigate into the `package` directory:
```bash
cd package
```
* Zip the contents of the `package` directory (the libraries):
```bash
zip -r9 ../deployment-package.zip .
```
* Navigate back to the project root:
```bash
cd ..
```
* Add the `lambda_function.py` script to the *root* of the zip file:
```bash
zip -g deployment-package.zip lambda_function.py
```
You should now have a `deployment-package.zip` file ready for upload.
## 6. Deployment to AWS Lambda
1. **Create Lambda Function:**
* Go to the AWS Lambda console.
* Click "Create function".
* Select "Author from scratch".
* **Function name:** Enter a name (e.g., `Tina-Publishing-Assistant`).
* **Runtime:** Select a supported Python version (e.g., Python 3.10, 3.11, 3.12).
* **Architecture:** Choose `x86_64` or `arm64` (ensure your dependencies are compatible if choosing arm64).
* **Execution role:** Choose "Create a new role with basic Lambda permissions" or select an existing role that has the necessary permissions (see Section 7).
* Click "Create function".
2. **Upload Code:**
* In the function's "Code" tab, click the "Upload from" button.
* Select ".zip file".
* Click "Upload" and choose the `deployment-package.zip` file you created.
* Click "Save".
3. **Configure Handler:**
* In the "Runtime settings" section (under the Code tab or in Configuration -> General configuration), click "Edit".
* Ensure the **Handler** is set to `lambda_function.lambda_handler`.
* Click "Save".
4. **Configure Environment Variables:**
* Go to the "Configuration" tab -> "Environment variables".
* Click "Edit".
* Add all the **Required** environment variables listed in Section 3, providing the appropriate values. **Remember to handle the sensitive `OPENAI_API_KEY` and `GITHUB_PAT` securely.**
* Add any **Optional** environment variables you wish to override from their defaults.
* Click "Save".
5. **Configure Timeout and Memory:**
* Go to the "Configuration" tab -> "General configuration".
* Click "Edit".
* Set the **Memory**. Start with at least `512` MB, potentially increase to `1024` MB or more depending on file size and OpenAI performance.
* Set the **Timeout**. Start with `3` to `5` minutes (e.g., `3 min 0 sec`). OpenAI calls can sometimes be slow.
* Click "Save".
6. **Assign Permissions:** Ensure the Lambda function's execution role (found under Configuration -> Permissions) has the necessary IAM policies attached (see Section 7).
## 7. IAM Permissions
The Lambda function's execution role needs the following IAM permissions:
* **S3 Access:** Permissions to interact with your S3 bucket. Create an inline policy or attach a managed policy granting actions like:
* `s3:ListBucket` (for the specified bucket)
* `s3:GetObject` (for `blog-drafts/*`)
* `s3:PutObject` (for `processed-archive/*`)
* `s3:CopyObject` (if moving files via copy)
* `s3:DeleteObject` (for `blog-drafts/*`)
* *(Restrict permissions to the specific bucket and prefixes where possible)*
* **CloudWatch Logs:** Basic Lambda execution permissions usually include:
* `logs:CreateLogGroup`
* `logs:CreateLogStream`
* `logs:PutLogEvents`
* *(These allow Tina to write logs for monitoring and debugging)*
*Note:* Access to GitHub and OpenAI is handled via the PAT and API Key provided as environment variables, not via AWS IAM roles.
## 8. Trigger Setup
1. Go to your Lambda function in the AWS console.
2. Click "+ Add trigger".
3. Select "EventBridge (CloudWatch Events)" as the trigger source.
4. Choose "Create a new rule" or select an existing one.
5. **Rule name:** Give it a name (e.g., `Tina-Hourly-Trigger`).
6. **Rule type:** Select "Schedule expression".
7. **Schedule expression:** Define how often Tina should run.
* Example (run once per hour): `rate(1 hour)`
* Example (run daily at 2 AM UTC): `cron(0 2 * * ? *)`
* *(Use AWS Cron expression syntax)*
8. Ensure "Enable trigger" is checked.
9. Click "Add".
## 9. Usage
1. Ensure the Lambda function is deployed and the trigger is enabled.
2. Create draft blog posts as Markdown (`.md`) files.
3. Ensure each draft file includes YAML front matter with at least a non-empty `title` field and a `tags` list containing `blog`. Optionally include `date` and `description`.
4. Use ```ai ... ``` blocks to denote AI-generated content within the Markdown if desired.
5. Upload these draft files to the S3 bucket under the specified draft prefix (e.g., `s3://your-bucket-name/blog-drafts/`).
6. Tina will automatically pick up the oldest draft file on her next scheduled run, process it, publish it to GitHub, and archive/delete the draft according to the configuration.
7. Monitor CloudWatch Logs for execution details and errors.