@vinnyum/newsletter-sync 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -27,7 +27,7 @@ npx newsletter-sync [options]
27
27
  ### Options
28
28
 
29
29
  | Flag | Description |
30
- |---|---|
30
+ | --- | --- |
31
31
  | `-c, --config <file>` | Path to a JSON configuration file |
32
32
  | `--apiKey <key>` | Composio API Key (overrides env `COMPOSIO_API_KEY`) |
33
33
  | `--zohoAccountId <id>` | Zoho Mail Account ID |
@@ -38,20 +38,22 @@ npx newsletter-sync [options]
38
38
  | `--fromEmail <email>` | From email address for Zoho Mail alert emails |
39
39
  | `--processed <path>` | Path to save the processed IDs tracking list (default: `./processed_emails.json`) |
40
40
  | `--log <path>` | Path to save the execution logs (default: `./cron.log`) |
41
+ | `--supabaseUrl <url>` | Supabase Project URL (overrides env `SUPABASE_URL`) |
42
+ | `--supabaseKey <key>` | Supabase Key (overrides env `SUPABASE_KEY`) |
41
43
 
42
- ## Setup Example
44
+ ## Setup Example (Local Execution)
43
45
 
44
46
  ### 1. Create a `config.json` file
45
47
 
46
48
  ```json
47
49
  {
48
50
  "composioApiKey": "your-composio-api-key",
49
- "zohoAccountId": "1988858000000008002",
50
- "zohoFolderId": "1988858000000009011",
51
- "senderFilter": "newsletter@jamiebrindle.io",
52
- "notebookId": "17c7393b-19eb-4dcc-b497-38d05976448d",
53
- "alertEmail": "vinny@vinnyum.tech",
54
- "fromEmail": "vinny@vinnyum.tech",
51
+ "zohoAccountId": "your-zoho-account-id",
52
+ "zohoFolderId": "your-zoho-folder-id",
53
+ "senderFilter": "newsletter@example.com",
54
+ "notebookId": "your-notebook-id-uuid",
55
+ "alertEmail": "alerts@yourdomain.com",
56
+ "fromEmail": "sender@yourdomain.com",
55
57
  "processedPath": "./processed_emails.json",
56
58
  "logPath": "./cron.log"
57
59
  }
@@ -63,16 +65,49 @@ npx newsletter-sync [options]
63
65
  npx newsletter-sync --config ./config.json
64
66
  ```
65
67
 
66
- ## Scheduling on Windows
68
+ ---
67
69
 
68
- To run this tool every week automatically:
70
+ ## Setup for Cloud Execution (GitHub Actions + Supabase)
69
71
 
70
- 1. Create a batch script `run-sync.bat`:
71
- ```bat
72
- @echo off
73
- cd /d "%~dp0"
74
- npx newsletter-sync --config ./config.json
75
- ```
76
- 2. Open **Windows Task Scheduler** (`taskschd.msc`).
77
- 3. Select **Create Basic Task** and set it to **Weekly** (e.g., Mondays at 9:00 AM).
78
- 4. For action, choose **Start a program**, select `run-sync.bat`, and set the **Start in (optional)** field to the folder containing your `config.json` and bat script.
72
+ To run this tool in the cloud weekly, we use a combination of GitHub Actions and a Supabase database to track processed email IDs.
73
+
74
+ ### 1. Setup Supabase Table
75
+
76
+ In your Supabase SQL Editor, run the following DDL query to create the tracking table:
77
+
78
+ ```sql
79
+ create table processed_emails (
80
+ message_id text primary key,
81
+ created_at timestamp with time zone default timezone('utc'::text, now()) not null
82
+ );
83
+ ```
84
+
85
+ ### 2. Export NotebookLM Cookies
86
+
87
+ The `nlm` CLI needs your Google Account session. To get the cookies in JSON format:
88
+
89
+ 1. Log in to [notebooklm.google.com](https://notebooklm.google.com) in Chrome.
90
+ 2. Use a Chrome extension like **EditThisCookie** or **Get cookies.txt as JSON** to copy all active cookies as a JSON array.
91
+
92
+ ### 3. Add Secrets in GitHub Repository
93
+
94
+ Go to your GitHub repository Settings ➜ Secrets and Variables ➜ Actions, and add the following repository secrets:
95
+
96
+ - `COMPOSIO_API_KEY`: Your Composio API Key.
97
+ - `SUPABASE_URL`: Your Supabase Project URL.
98
+ - `SUPABASE_KEY`: Your Supabase Project Anon/Service Key.
99
+ - `NLM_COOKIES_JSON`: The JSON array of cookies copied in step 2.
100
+ - `SYNC_CONFIG_JSON`: A JSON config representing your Zoho and NotebookLM IDs:
101
+
102
+ ```json
103
+ {
104
+ "zohoAccountId": "your-zoho-account-id",
105
+ "zohoFolderId": "your-zoho-folder-id",
106
+ "senderFilter": "newsletter@example.com",
107
+ "notebookId": "your-notebook-id-uuid",
108
+ "alertEmail": "alerts@yourdomain.com",
109
+ "fromEmail": "sender@yourdomain.com"
110
+ }
111
+ ```
112
+
113
+ Once configured, the GitHub Action workflow will run automatically every Monday at 9:00 AM UTC. You can also trigger it manually from the Actions tab in your repository.
package/bin/cli.js CHANGED
@@ -16,6 +16,8 @@ const options = {
16
16
  fromEmail: { type: 'string' },
17
17
  processed: { type: 'string' },
18
18
  log: { type: 'string' },
19
+ supabaseUrl: { type: 'string' },
20
+ supabaseKey: { type: 'string' },
19
21
  help: { type: 'boolean', short: 'h' }
20
22
  };
21
23
 
@@ -34,6 +36,8 @@ Options:
34
36
  --fromEmail <email> From email for error alert emails (must be verified in Zoho)
35
37
  --processed <path> Path to processed_emails.json (default: ./processed_emails.json)
36
38
  --log <path> Path to execution log file (default: ./cron.log)
39
+ --supabaseUrl <url> Supabase Project URL (can be set as SUPABASE_URL env var)
40
+ --supabaseKey <key> Supabase Anon/Service Key (can be set as SUPABASE_KEY env var)
37
41
  -h, --help Show help
38
42
  `);
39
43
  }
@@ -74,7 +78,9 @@ async function main() {
74
78
  alertEmail: values.alertEmail || configData.alertEmail,
75
79
  fromEmail: values.fromEmail || configData.fromEmail,
76
80
  processedPath: values.processed || configData.processedPath || './processed_emails.json',
77
- logPath: values.log || configData.logPath || './cron.log'
81
+ logPath: values.log || configData.logPath || './cron.log',
82
+ supabaseUrl: values.supabaseUrl || configData.supabaseUrl || process.env.SUPABASE_URL,
83
+ supabaseKey: values.supabaseKey || configData.supabaseKey || process.env.SUPABASE_KEY
78
84
  };
79
85
 
80
86
  // 3. Run Sincronizador
package/index.js CHANGED
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { execSync } from 'node:child_process';
4
4
  import puppeteer from 'puppeteer';
5
+ import { createClient } from '@supabase/supabase-js';
5
6
  import ComposioModule from '@composio/client';
6
7
 
7
8
  const Composio = ComposioModule.default || ComposioModule.Composio || ComposioModule;
@@ -20,7 +21,9 @@ export async function runSync(options = {}) {
20
21
  alertEmail,
21
22
  fromEmail,
22
23
  processedPath = './processed_emails.json',
23
- logPath = './cron.log'
24
+ logPath = './cron.log',
25
+ supabaseUrl = process.env.SUPABASE_URL,
26
+ supabaseKey = process.env.SUPABASE_KEY
24
27
  } = options;
25
28
 
26
29
  // Helper for logging
@@ -40,13 +43,35 @@ export async function runSync(options = {}) {
40
43
  if (!senderFilter) throw new Error("Missing Sender Filter. Pass --senderFilter.");
41
44
  if (!notebookId) throw new Error("Missing Notebook ID. Pass --notebookId.");
42
45
 
43
- const processedFile = path.resolve(process.cwd(), processedPath);
46
+ // Initialize State Tracker (Supabase or Local JSON)
47
+ const useSupabase = !!(supabaseUrl && supabaseKey);
48
+ let supabaseClient = null;
44
49
  let processed = [];
45
- if (fs.existsSync(processedFile)) {
50
+
51
+ if (useSupabase) {
52
+ log("Using Supabase database for tracking processed emails.");
53
+ supabaseClient = createClient(supabaseUrl, supabaseKey);
46
54
  try {
47
- processed = JSON.parse(fs.readFileSync(processedFile, 'utf8'));
55
+ const { data, error } = await supabaseClient
56
+ .from('processed_emails')
57
+ .select('message_id');
58
+
59
+ if (error) throw error;
60
+ processed = (data || []).map(row => row.message_id);
61
+ log(`Loaded ${processed.length} processed IDs from Supabase.`);
48
62
  } catch (e) {
49
- log(`Warning: Failed to parse processed list, starting fresh.`);
63
+ throw new Error(`Failed to load processed emails from Supabase: ${e.message}`);
64
+ }
65
+ } else {
66
+ const processedFile = path.resolve(process.cwd(), processedPath);
67
+ log(`Using local file for tracking processed emails: ${processedFile}`);
68
+ if (fs.existsSync(processedFile)) {
69
+ try {
70
+ processed = JSON.parse(fs.readFileSync(processedFile, 'utf8'));
71
+ log(`Loaded ${processed.length} processed IDs from local file.`);
72
+ } catch (e) {
73
+ log(`Warning: Failed to parse processed list, starting fresh.`);
74
+ }
50
75
  }
51
76
  }
52
77
 
@@ -145,13 +170,25 @@ export async function runSync(options = {}) {
145
170
  }
146
171
 
147
172
  // Track as processed
148
- processed.push(email.messageId);
149
- fs.writeFileSync(processedFile, JSON.stringify(processed, null, 2));
150
- log(`Logged ${email.messageId} as processed.`);
173
+ if (useSupabase) {
174
+ const { error } = await supabaseClient
175
+ .from('processed_emails')
176
+ .insert({ message_id: email.messageId });
177
+
178
+ if (error) {
179
+ log(`Warning: Failed to save ${email.messageId} to Supabase: ${error.message}`);
180
+ } else {
181
+ log(`Logged ${email.messageId} to Supabase.`);
182
+ }
183
+ } else {
184
+ const processedFile = path.resolve(process.cwd(), processedPath);
185
+ processed.push(email.messageId);
186
+ fs.writeFileSync(processedFile, JSON.stringify(processed, null, 2));
187
+ log(`Logged ${email.messageId} to local file.`);
188
+ }
151
189
 
152
190
  } catch (itemError) {
153
191
  log(`Error processing email ${email.messageId}: ${itemError.message}`);
154
- // Continue with other items
155
192
  }
156
193
  }
157
194
 
package/package.json CHANGED
@@ -1,17 +1,18 @@
1
1
  {
2
2
  "name": "@vinnyum/newsletter-sync",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Weekly automated newsletter to PDF converter and Google NotebookLM source synchronizer",
5
5
  "type": "module",
6
6
  "main": "./index.js",
7
7
  "bin": {
8
- "newsletter-sync": "./bin/cli.js"
8
+ "newsletter-sync": "bin/cli.js"
9
9
  },
10
10
  "engines": {
11
11
  "node": ">=18.0.0"
12
12
  },
13
13
  "dependencies": {
14
14
  "@composio/client": "^0.1.0-alpha.75",
15
+ "@supabase/supabase-js": "^2.48.0",
15
16
  "puppeteer": "^25.3.0"
16
17
  },
17
18
  "author": {