optimal-cli 1.0.1 → 1.1.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.
Files changed (185) hide show
  1. package/.claude-plugin/marketplace.json +18 -0
  2. package/.claude-plugin/plugin.json +10 -0
  3. package/.env.example +17 -0
  4. package/CLAUDE.md +67 -0
  5. package/COMMANDS.md +264 -0
  6. package/PUBLISH.md +70 -0
  7. package/agents/content-ops.md +2 -2
  8. package/agents/financial-ops.md +2 -2
  9. package/agents/infra-ops.md +2 -2
  10. package/apps/.gitkeep +0 -0
  11. package/bin/optimal.ts +1418 -0
  12. package/docs/MIGRATION_NEEDED.md +37 -0
  13. package/docs/plans/.gitkeep +0 -0
  14. package/docs/plans/optimal-cli-config-registry-v1.md +71 -0
  15. package/hooks/.gitkeep +0 -0
  16. package/lib/budget/projections.ts +561 -0
  17. package/lib/budget/scenarios.ts +312 -0
  18. package/lib/cms/publish-blog.ts +129 -0
  19. package/lib/cms/strapi-client.ts +302 -0
  20. package/lib/config/registry.ts +229 -0
  21. package/lib/config/schema.ts +58 -0
  22. package/lib/config.ts +247 -0
  23. package/lib/infra/.gitkeep +0 -0
  24. package/lib/infra/deploy.ts +70 -0
  25. package/lib/infra/migrate.ts +141 -0
  26. package/lib/kanban-obsidian.ts +232 -0
  27. package/lib/kanban-sync.ts +258 -0
  28. package/lib/kanban.ts +239 -0
  29. package/lib/newsletter/.gitkeep +0 -0
  30. package/lib/newsletter/distribute.ts +256 -0
  31. package/{dist/lib/newsletter/generate-insurance.d.ts → lib/newsletter/generate-insurance.ts} +24 -7
  32. package/lib/newsletter/generate.ts +735 -0
  33. package/lib/obsidian-tasks.ts +231 -0
  34. package/lib/returnpro/.gitkeep +0 -0
  35. package/lib/returnpro/anomalies.ts +258 -0
  36. package/lib/returnpro/audit.ts +194 -0
  37. package/lib/returnpro/diagnose.ts +400 -0
  38. package/lib/returnpro/kpis.ts +255 -0
  39. package/lib/returnpro/templates.ts +323 -0
  40. package/lib/returnpro/upload-income.ts +311 -0
  41. package/lib/returnpro/upload-netsuite.ts +696 -0
  42. package/lib/returnpro/upload-r1.ts +563 -0
  43. package/lib/social/post-generator.ts +468 -0
  44. package/lib/social/publish.ts +301 -0
  45. package/lib/social/scraper.ts +503 -0
  46. package/lib/supabase.ts +25 -0
  47. package/lib/transactions/delete-batch.ts +258 -0
  48. package/lib/transactions/ingest.ts +659 -0
  49. package/lib/transactions/stamp.ts +654 -0
  50. package/package.json +5 -18
  51. package/pnpm-workspace.yaml +3 -0
  52. package/scripts/check-table.ts +24 -0
  53. package/scripts/create-tables.ts +94 -0
  54. package/scripts/migrate-kanban.sh +28 -0
  55. package/scripts/migrate-v2.ts +78 -0
  56. package/scripts/migrate.ts +79 -0
  57. package/scripts/run-migration.ts +59 -0
  58. package/scripts/seed-board.ts +203 -0
  59. package/scripts/test-kanban.ts +21 -0
  60. package/skills/audit-financials/SKILL.md +33 -0
  61. package/skills/board-create/SKILL.md +28 -0
  62. package/skills/board-update/SKILL.md +27 -0
  63. package/skills/board-view/SKILL.md +27 -0
  64. package/skills/delete-batch/SKILL.md +77 -0
  65. package/skills/deploy/SKILL.md +40 -0
  66. package/skills/diagnose-months/SKILL.md +68 -0
  67. package/skills/distribute-newsletter/SKILL.md +58 -0
  68. package/skills/export-budget/SKILL.md +44 -0
  69. package/skills/export-kpis/SKILL.md +52 -0
  70. package/skills/generate-netsuite-template/SKILL.md +51 -0
  71. package/skills/generate-newsletter/SKILL.md +53 -0
  72. package/skills/generate-newsletter-insurance/SKILL.md +59 -0
  73. package/skills/generate-social-posts/SKILL.md +67 -0
  74. package/skills/health-check/SKILL.md +42 -0
  75. package/skills/ingest-transactions/SKILL.md +51 -0
  76. package/skills/manage-cms/SKILL.md +50 -0
  77. package/skills/manage-scenarios/SKILL.md +83 -0
  78. package/skills/migrate-db/SKILL.md +79 -0
  79. package/skills/preview-newsletter/SKILL.md +50 -0
  80. package/skills/project-budget/SKILL.md +60 -0
  81. package/skills/publish-blog/SKILL.md +70 -0
  82. package/skills/publish-social-posts/SKILL.md +70 -0
  83. package/skills/rate-anomalies/SKILL.md +62 -0
  84. package/skills/scrape-ads/SKILL.md +49 -0
  85. package/skills/stamp-transactions/SKILL.md +62 -0
  86. package/skills/upload-income-statements/SKILL.md +54 -0
  87. package/skills/upload-netsuite/SKILL.md +56 -0
  88. package/skills/upload-r1/SKILL.md +45 -0
  89. package/supabase/.temp/cli-latest +1 -0
  90. package/supabase/migrations/.gitkeep +0 -0
  91. package/supabase/migrations/20250305000001_create_agent_configs.sql +36 -0
  92. package/supabase/migrations/20260305111300_create_cli_config_registry.sql +22 -0
  93. package/supabase/migrations/20260306195000_create_kanban_tables.sql +97 -0
  94. package/tests/config-command-smoke.test.ts +395 -0
  95. package/tests/config-registry.test.ts +173 -0
  96. package/tsconfig.json +19 -0
  97. package/agents/profiles.json +0 -5
  98. package/dist/bin/optimal.d.ts +0 -2
  99. package/dist/bin/optimal.js +0 -1590
  100. package/dist/lib/assets/index.d.ts +0 -79
  101. package/dist/lib/assets/index.js +0 -153
  102. package/dist/lib/assets.d.ts +0 -20
  103. package/dist/lib/assets.js +0 -112
  104. package/dist/lib/auth/index.d.ts +0 -83
  105. package/dist/lib/auth/index.js +0 -146
  106. package/dist/lib/board/index.d.ts +0 -39
  107. package/dist/lib/board/index.js +0 -285
  108. package/dist/lib/board/types.d.ts +0 -111
  109. package/dist/lib/board/types.js +0 -1
  110. package/dist/lib/bot/claim.d.ts +0 -3
  111. package/dist/lib/bot/claim.js +0 -20
  112. package/dist/lib/bot/coordinator.d.ts +0 -27
  113. package/dist/lib/bot/coordinator.js +0 -178
  114. package/dist/lib/bot/heartbeat.d.ts +0 -6
  115. package/dist/lib/bot/heartbeat.js +0 -30
  116. package/dist/lib/bot/index.d.ts +0 -9
  117. package/dist/lib/bot/index.js +0 -6
  118. package/dist/lib/bot/protocol.d.ts +0 -12
  119. package/dist/lib/bot/protocol.js +0 -74
  120. package/dist/lib/bot/reporter.d.ts +0 -3
  121. package/dist/lib/bot/reporter.js +0 -27
  122. package/dist/lib/bot/skills.d.ts +0 -26
  123. package/dist/lib/bot/skills.js +0 -69
  124. package/dist/lib/budget/projections.d.ts +0 -115
  125. package/dist/lib/budget/projections.js +0 -384
  126. package/dist/lib/budget/scenarios.d.ts +0 -93
  127. package/dist/lib/budget/scenarios.js +0 -214
  128. package/dist/lib/cms/publish-blog.d.ts +0 -62
  129. package/dist/lib/cms/publish-blog.js +0 -74
  130. package/dist/lib/cms/strapi-client.d.ts +0 -123
  131. package/dist/lib/cms/strapi-client.js +0 -213
  132. package/dist/lib/config/registry.d.ts +0 -17
  133. package/dist/lib/config/registry.js +0 -182
  134. package/dist/lib/config/schema.d.ts +0 -31
  135. package/dist/lib/config/schema.js +0 -25
  136. package/dist/lib/config.d.ts +0 -55
  137. package/dist/lib/config.js +0 -206
  138. package/dist/lib/errors.d.ts +0 -25
  139. package/dist/lib/errors.js +0 -91
  140. package/dist/lib/format.d.ts +0 -28
  141. package/dist/lib/format.js +0 -98
  142. package/dist/lib/infra/deploy.d.ts +0 -29
  143. package/dist/lib/infra/deploy.js +0 -58
  144. package/dist/lib/infra/migrate.d.ts +0 -34
  145. package/dist/lib/infra/migrate.js +0 -103
  146. package/dist/lib/newsletter/distribute.d.ts +0 -52
  147. package/dist/lib/newsletter/distribute.js +0 -193
  148. package/dist/lib/newsletter/generate-insurance.js +0 -36
  149. package/dist/lib/newsletter/generate.d.ts +0 -104
  150. package/dist/lib/newsletter/generate.js +0 -571
  151. package/dist/lib/returnpro/anomalies.d.ts +0 -64
  152. package/dist/lib/returnpro/anomalies.js +0 -166
  153. package/dist/lib/returnpro/audit.d.ts +0 -32
  154. package/dist/lib/returnpro/audit.js +0 -147
  155. package/dist/lib/returnpro/diagnose.d.ts +0 -52
  156. package/dist/lib/returnpro/diagnose.js +0 -281
  157. package/dist/lib/returnpro/kpis.d.ts +0 -32
  158. package/dist/lib/returnpro/kpis.js +0 -192
  159. package/dist/lib/returnpro/templates.d.ts +0 -48
  160. package/dist/lib/returnpro/templates.js +0 -229
  161. package/dist/lib/returnpro/upload-income.d.ts +0 -25
  162. package/dist/lib/returnpro/upload-income.js +0 -235
  163. package/dist/lib/returnpro/upload-netsuite.d.ts +0 -37
  164. package/dist/lib/returnpro/upload-netsuite.js +0 -566
  165. package/dist/lib/returnpro/upload-r1.d.ts +0 -48
  166. package/dist/lib/returnpro/upload-r1.js +0 -398
  167. package/dist/lib/returnpro/validate.d.ts +0 -37
  168. package/dist/lib/returnpro/validate.js +0 -124
  169. package/dist/lib/social/meta.d.ts +0 -90
  170. package/dist/lib/social/meta.js +0 -160
  171. package/dist/lib/social/post-generator.d.ts +0 -83
  172. package/dist/lib/social/post-generator.js +0 -333
  173. package/dist/lib/social/publish.d.ts +0 -66
  174. package/dist/lib/social/publish.js +0 -226
  175. package/dist/lib/social/scraper.d.ts +0 -67
  176. package/dist/lib/social/scraper.js +0 -361
  177. package/dist/lib/supabase.d.ts +0 -4
  178. package/dist/lib/supabase.js +0 -20
  179. package/dist/lib/transactions/delete-batch.d.ts +0 -60
  180. package/dist/lib/transactions/delete-batch.js +0 -203
  181. package/dist/lib/transactions/ingest.d.ts +0 -43
  182. package/dist/lib/transactions/ingest.js +0 -555
  183. package/dist/lib/transactions/stamp.d.ts +0 -51
  184. package/dist/lib/transactions/stamp.js +0 -524
  185. package/docs/CLI-REFERENCE.md +0 -361
@@ -1,103 +0,0 @@
1
- import 'dotenv/config';
2
- import { execFile } from 'node:child_process';
3
- import { promisify } from 'node:util';
4
- import { readdir, writeFile } from 'node:fs/promises';
5
- import { join } from 'node:path';
6
- const run = promisify(execFile);
7
- // ---------------------------------------------------------------------------
8
- // Constants
9
- // ---------------------------------------------------------------------------
10
- /** Hardcoded project directories — these live on Carlos's machine. */
11
- const PROJECT_DIRS = {
12
- returnpro: '/home/optimal/dashboard-returnpro',
13
- optimalos: '/home/optimal/optimalos',
14
- };
15
- // ---------------------------------------------------------------------------
16
- // Helpers
17
- // ---------------------------------------------------------------------------
18
- function getProjectDir(target) {
19
- return PROJECT_DIRS[target];
20
- }
21
- function migrationsDir(target) {
22
- return join(getProjectDir(target), 'supabase', 'migrations');
23
- }
24
- /**
25
- * Generate a timestamp string in YYYYMMDDHHMMSS format (UTC).
26
- */
27
- function timestamp() {
28
- const now = new Date();
29
- const pad = (n, len = 2) => String(n).padStart(len, '0');
30
- return [
31
- now.getUTCFullYear(),
32
- pad(now.getUTCMonth() + 1),
33
- pad(now.getUTCDate()),
34
- pad(now.getUTCHours()),
35
- pad(now.getUTCMinutes()),
36
- pad(now.getUTCSeconds()),
37
- ].join('');
38
- }
39
- // ---------------------------------------------------------------------------
40
- // Public API
41
- // ---------------------------------------------------------------------------
42
- /**
43
- * Run `supabase db push --linked` (or `--dry-run` if requested) against the
44
- * given target project directory.
45
- *
46
- * Uses `execFile` (not `exec`) to avoid shell injection.
47
- * The `cwd` option switches the Supabase CLI into the correct project.
48
- */
49
- export async function migrateDb(opts) {
50
- const { target, dryRun = false } = opts;
51
- const projectDir = getProjectDir(target);
52
- const args = ['db', 'push', '--linked'];
53
- if (dryRun)
54
- args.push('--dry-run');
55
- try {
56
- const { stdout, stderr } = await run('supabase', args, {
57
- cwd: projectDir,
58
- timeout: 120_000,
59
- });
60
- return {
61
- success: true,
62
- target,
63
- output: stdout.trim(),
64
- errors: stderr.trim(),
65
- };
66
- }
67
- catch (err) {
68
- const e = err;
69
- return {
70
- success: false,
71
- target,
72
- output: (e.stdout ?? '').trim(),
73
- errors: (e.stderr ?? e.message ?? String(err)).trim(),
74
- };
75
- }
76
- }
77
- /**
78
- * List migration `.sql` files in the target's `supabase/migrations/` directory,
79
- * sorted chronologically (by filename, which starts with a YYYYMMDDHHMMSS prefix).
80
- *
81
- * Returns only filenames, not full paths.
82
- */
83
- export async function listPendingMigrations(target) {
84
- const dir = migrationsDir(target);
85
- const entries = await readdir(dir);
86
- return entries
87
- .filter((f) => f.endsWith('.sql'))
88
- .sort(); // lexicographic == chronological given the YYYYMMDDHHMMSS prefix
89
- }
90
- /**
91
- * Create a new empty migration file in the target's `supabase/migrations/`
92
- * directory.
93
- *
94
- * The filename format is `{YYYYMMDDHHMMSS}_{name}.sql` (UTC timestamp).
95
- * Returns the full absolute path of the created file.
96
- */
97
- export async function createMigration(target, name) {
98
- const sanitized = name.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_]/g, '');
99
- const filename = `${timestamp()}_${sanitized}.sql`;
100
- const fullPath = join(migrationsDir(target), filename);
101
- await writeFile(fullPath, `-- Migration: ${sanitized}\n-- Target: ${target}\n-- Created: ${new Date().toISOString()}\n\n`, { encoding: 'utf8' });
102
- return fullPath;
103
- }
@@ -1,52 +0,0 @@
1
- /**
2
- * Newsletter Distribution Module
3
- *
4
- * Triggers newsletter distribution via the n8n webhook and updates
5
- * Strapi delivery tracking fields accordingly.
6
- *
7
- * Functions:
8
- * distributeNewsletter(documentId, opts?) — orchestrate distribution
9
- * checkDistributionStatus(documentId) — read current delivery status
10
- */
11
- import 'dotenv/config';
12
- export interface DistributionResult {
13
- success: boolean;
14
- documentId: string;
15
- channel: string;
16
- webhookResponse?: unknown;
17
- error?: string;
18
- }
19
- export interface DeliveryStatus {
20
- documentId: string;
21
- delivery_status: string;
22
- delivered_at: string | null;
23
- recipients_count: number | null;
24
- ghl_campaign_id: string | null;
25
- delivery_errors: unknown[] | null;
26
- }
27
- /**
28
- * Main orchestrator: fetch newsletter from Strapi, validate state,
29
- * update delivery_status to 'sending', trigger the n8n webhook,
30
- * and update tracking fields based on the result.
31
- *
32
- * @param documentId - Strapi documentId (UUID string) of the newsletter
33
- * @param opts.channel - Distribution channel. Defaults to 'all'.
34
- *
35
- * @example
36
- * const result = await distributeNewsletter('abc123-def456')
37
- * const emailOnly = await distributeNewsletter('abc123-def456', { channel: 'email' })
38
- */
39
- export declare function distributeNewsletter(documentId: string, opts?: {
40
- channel?: 'email' | 'all';
41
- }): Promise<DistributionResult>;
42
- /**
43
- * Fetch the current delivery tracking fields for a newsletter from Strapi.
44
- *
45
- * @param documentId - Strapi documentId (UUID string) of the newsletter
46
- *
47
- * @example
48
- * const status = await checkDistributionStatus('abc123-def456')
49
- * console.log(status.delivery_status) // 'delivered'
50
- * console.log(status.recipients_count) // 847
51
- */
52
- export declare function checkDistributionStatus(documentId: string): Promise<DeliveryStatus>;
@@ -1,193 +0,0 @@
1
- /**
2
- * Newsletter Distribution Module
3
- *
4
- * Triggers newsletter distribution via the n8n webhook and updates
5
- * Strapi delivery tracking fields accordingly.
6
- *
7
- * Functions:
8
- * distributeNewsletter(documentId, opts?) — orchestrate distribution
9
- * checkDistributionStatus(documentId) — read current delivery status
10
- */
11
- import 'dotenv/config';
12
- import { strapiGet, strapiPut } from '../cms/strapi-client.js';
13
- // ── Environment helpers ───────────────────────────────────────────────
14
- function getWebhookUrl() {
15
- const url = process.env.N8N_WEBHOOK_URL;
16
- if (!url) {
17
- throw new Error('Missing env var: N8N_WEBHOOK_URL\n' +
18
- 'Set it to your n8n base URL, e.g. https://n8n.op-hub.com\n' +
19
- 'The distribute module will POST to: $N8N_WEBHOOK_URL/webhook/newsletter-distribute');
20
- }
21
- return url.replace(/\/+$/, '');
22
- }
23
- // ── 1. Distribute Newsletter ──────────────────────────────────────────
24
- /**
25
- * Main orchestrator: fetch newsletter from Strapi, validate state,
26
- * update delivery_status to 'sending', trigger the n8n webhook,
27
- * and update tracking fields based on the result.
28
- *
29
- * @param documentId - Strapi documentId (UUID string) of the newsletter
30
- * @param opts.channel - Distribution channel. Defaults to 'all'.
31
- *
32
- * @example
33
- * const result = await distributeNewsletter('abc123-def456')
34
- * const emailOnly = await distributeNewsletter('abc123-def456', { channel: 'email' })
35
- */
36
- export async function distributeNewsletter(documentId, opts = {}) {
37
- const channel = opts.channel ?? 'all';
38
- // Step 1: Fetch the newsletter from Strapi
39
- let newsletter;
40
- try {
41
- const response = await strapiGet(`/api/newsletters/${documentId}`);
42
- newsletter = response.data;
43
- }
44
- catch (err) {
45
- const msg = err instanceof Error ? err.message : String(err);
46
- return {
47
- success: false,
48
- documentId,
49
- channel,
50
- error: `Failed to fetch newsletter from Strapi: ${msg}`,
51
- };
52
- }
53
- // Step 2: Validate published state
54
- if (!newsletter.publishedAt) {
55
- return {
56
- success: false,
57
- documentId,
58
- channel,
59
- error: 'Newsletter is not published. Publish it in Strapi before distributing.',
60
- };
61
- }
62
- // Step 3: Validate delivery_status — only distribute if pending or unset
63
- const currentStatus = newsletter.delivery_status ?? 'pending';
64
- if (currentStatus !== 'pending' && currentStatus !== undefined) {
65
- if (currentStatus === 'sending' ||
66
- currentStatus === 'delivered' ||
67
- currentStatus === 'partial') {
68
- return {
69
- success: false,
70
- documentId,
71
- channel,
72
- error: `Newsletter already has delivery_status="${currentStatus}". Cannot re-distribute.`,
73
- };
74
- }
75
- }
76
- // Step 4: Mark as 'sending' in Strapi
77
- try {
78
- await strapiPut('/api/newsletters', documentId, {
79
- delivery_status: 'sending',
80
- });
81
- }
82
- catch (err) {
83
- const msg = err instanceof Error ? err.message : String(err);
84
- return {
85
- success: false,
86
- documentId,
87
- channel,
88
- error: `Failed to update delivery_status to 'sending': ${msg}`,
89
- };
90
- }
91
- // Step 5: Trigger n8n webhook
92
- const webhookUrl = getWebhookUrl();
93
- const webhookEndpoint = `${webhookUrl}/webhook/newsletter-distribute`;
94
- const brand = typeof newsletter.brand === 'string' ? newsletter.brand : '';
95
- let webhookResponse;
96
- try {
97
- const res = await fetch(webhookEndpoint, {
98
- method: 'POST',
99
- headers: { 'Content-Type': 'application/json' },
100
- body: JSON.stringify({ documentId, brand, channel }),
101
- });
102
- if (!res.ok) {
103
- let body;
104
- try {
105
- body = await res.json();
106
- }
107
- catch {
108
- body = await res.text().catch(() => res.statusText);
109
- }
110
- // Webhook failed — update Strapi to 'failed' with error details
111
- const errorDetails = [
112
- { step: 'webhook', status: res.status, body },
113
- ];
114
- await strapiPut('/api/newsletters', documentId, {
115
- delivery_status: 'failed',
116
- delivery_errors: errorDetails,
117
- }).catch(() => {
118
- // Best-effort update — don't mask the original error
119
- });
120
- return {
121
- success: false,
122
- documentId,
123
- channel,
124
- error: `n8n webhook returned ${res.status}: ${JSON.stringify(body)}`,
125
- };
126
- }
127
- try {
128
- webhookResponse = await res.json();
129
- }
130
- catch {
131
- webhookResponse = { status: res.status };
132
- }
133
- }
134
- catch (err) {
135
- // Network/connection error
136
- const msg = err instanceof Error ? err.message : String(err);
137
- const errorDetails = [{ step: 'webhook', error: msg }];
138
- await strapiPut('/api/newsletters', documentId, {
139
- delivery_status: 'failed',
140
- delivery_errors: errorDetails,
141
- }).catch(() => {
142
- // Best-effort
143
- });
144
- return {
145
- success: false,
146
- documentId,
147
- channel,
148
- error: `n8n webhook request failed: ${msg}`,
149
- };
150
- }
151
- // Step 6: Webhook succeeded — return success
152
- // Note: n8n will update delivery_status to 'delivered' (or 'partial') via its
153
- // own Strapi PUT once it finishes sending. We don't update it here.
154
- return {
155
- success: true,
156
- documentId,
157
- channel,
158
- webhookResponse,
159
- };
160
- }
161
- // ── 2. Check Distribution Status ─────────────────────────────────────
162
- /**
163
- * Fetch the current delivery tracking fields for a newsletter from Strapi.
164
- *
165
- * @param documentId - Strapi documentId (UUID string) of the newsletter
166
- *
167
- * @example
168
- * const status = await checkDistributionStatus('abc123-def456')
169
- * console.log(status.delivery_status) // 'delivered'
170
- * console.log(status.recipients_count) // 847
171
- */
172
- export async function checkDistributionStatus(documentId) {
173
- const response = await strapiGet(`/api/newsletters/${documentId}`);
174
- const data = response.data;
175
- return {
176
- documentId,
177
- delivery_status: typeof data.delivery_status === 'string'
178
- ? data.delivery_status
179
- : 'pending',
180
- delivered_at: typeof data.delivered_at === 'string'
181
- ? data.delivered_at
182
- : null,
183
- recipients_count: typeof data.recipients_count === 'number'
184
- ? data.recipients_count
185
- : null,
186
- ghl_campaign_id: typeof data.ghl_campaign_id === 'string'
187
- ? data.ghl_campaign_id
188
- : null,
189
- delivery_errors: Array.isArray(data.delivery_errors)
190
- ? data.delivery_errors
191
- : null,
192
- };
193
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * Insurance Newsletter Generation — LIFEINSUR (Anchor Point Insurance Co.)
3
- *
4
- * Thin wrapper around the shared generateNewsletter() orchestrator in generate.ts.
5
- * All pipeline logic (NewsAPI fetch, Groq AI, HTML build, Strapi push) lives there;
6
- * BRAND_CONFIGS['LIFEINSUR'] already wires up the correct news query, sender email,
7
- * brand colors (charcoal #44403E / terracotta #AD7C59 / beige #FCF9F6), and
8
- * hasProperties=false so no Excel step runs.
9
- *
10
- * Ported from: ~/projects/newsletter-automation/generate-newsletter-lifeinsur.py
11
- */
12
- import { generateNewsletter } from './generate.js';
13
- // ── Orchestrator ──────────────────────────────────────────────────────
14
- /**
15
- * Generate a LIFEINSUR newsletter for Anchor Point Insurance Co.
16
- *
17
- * Pipeline steps (delegated to generateNewsletter):
18
- * 1. Skip property extraction (LIFEINSUR has no listings)
19
- * 2. Fetch life insurance news via NewsAPI
20
- * (query: "life insurance coverage policy florida texas alabama")
21
- * 3. Generate market overview + news summaries via Groq (Llama 3.3 70B)
22
- * 4. Build branded HTML email with LIFEINSUR color scheme
23
- * 5. Push draft to Strapi as brand=LIFEINSUR (unless dryRun=true)
24
- *
25
- * @example
26
- * const result = await generateInsuranceNewsletter({ dryRun: false })
27
- * console.log(result.strapiDocumentId)
28
- */
29
- export async function generateInsuranceNewsletter(options = {}) {
30
- return generateNewsletter({
31
- brand: 'LIFEINSUR',
32
- date: options.date,
33
- dryRun: options.dryRun,
34
- // No excelPath — LIFEINSUR has no property listings
35
- });
36
- }
@@ -1,104 +0,0 @@
1
- /**
2
- * Newsletter Generation Pipeline
3
- *
4
- * Ported from Python: ~/projects/newsletter-automation/generate-newsletter-v2.py
5
- *
6
- * Pipeline: Excel properties -> NewsAPI -> Groq AI -> HTML build -> Strapi push
7
- *
8
- * Functions:
9
- * fetchNews() — fetch articles from NewsAPI
10
- * generateAiContent() — call Groq (OpenAI-compatible) for AI summaries
11
- * readExcelProperties() — parse columnar Excel (col A = labels, B-N = properties)
12
- * buildPropertyCardHtml() — render a single property card
13
- * buildNewsItemHtml() — render a single news item
14
- * buildHtml() — assemble full newsletter HTML
15
- * buildStrapiPayload() — build the structured Strapi newsletter payload
16
- * generateNewsletter() — orchestrator that runs the full pipeline
17
- */
18
- export interface Property {
19
- name?: string;
20
- address?: string;
21
- price?: string | number;
22
- propertyType?: string;
23
- size?: string | number;
24
- region?: string;
25
- highlights?: string[] | string;
26
- imageUrl?: string;
27
- listingUrl?: string;
28
- contact?: string;
29
- notes?: string;
30
- analysis?: string;
31
- [key: string]: unknown;
32
- }
33
- export interface NewsArticle {
34
- title: string;
35
- source: string;
36
- date: string;
37
- description: string;
38
- url: string;
39
- }
40
- export interface AiContent {
41
- market_overview: string;
42
- property_analyses: Array<{
43
- name: string;
44
- analysis: string;
45
- }>;
46
- news_summaries: Array<{
47
- title: string;
48
- analysis: string;
49
- }>;
50
- }
51
- export interface NewsletterPayload {
52
- data: {
53
- title: string;
54
- slug: string;
55
- brand: string;
56
- edition_date: string;
57
- subject_line: string;
58
- market_overview: string;
59
- featured_properties: Property[];
60
- news_items: Array<NewsArticle & {
61
- analysis?: string;
62
- }>;
63
- html_body: string;
64
- sender_email: string;
65
- };
66
- }
67
- export interface BrandConfig {
68
- brand: string;
69
- displayName: string;
70
- newsQuery: string;
71
- senderEmail: string;
72
- contactEmail: string;
73
- titlePrefix: string;
74
- subjectPrefix: string;
75
- /** Whether this brand uses Excel property listings */
76
- hasProperties: boolean;
77
- }
78
- export interface GenerateOptions {
79
- brand: string;
80
- date?: string;
81
- excelPath?: string;
82
- dryRun?: boolean;
83
- }
84
- export interface GenerateResult {
85
- properties: Property[];
86
- newsArticles: NewsArticle[];
87
- aiContent: AiContent | null;
88
- html: string;
89
- payload: NewsletterPayload;
90
- strapiDocumentId: string | null;
91
- }
92
- export declare function getBrandConfig(brand: string): BrandConfig;
93
- export declare function fetchNews(query?: string): Promise<NewsArticle[]>;
94
- export declare function generateAiContent(properties: Property[], newsArticles: NewsArticle[]): Promise<AiContent | null>;
95
- /**
96
- * Read multiple properties from columnar Excel format.
97
- * Column A = field labels, Columns B-N = one property per column.
98
- *
99
- * Uses exceljs (dynamically imported to keep the dependency optional).
100
- */
101
- export declare function readExcelProperties(filePath: string): Promise<Property[]>;
102
- export declare function buildHtml(properties: Property[], newsArticles: NewsArticle[], aiContent: AiContent | null, brandConfig: BrandConfig): string;
103
- export declare function buildStrapiPayload(properties: Property[], newsArticles: NewsArticle[], aiContent: AiContent | null, html: string, brandConfig: BrandConfig, editionDate?: string): NewsletterPayload;
104
- export declare function generateNewsletter(opts: GenerateOptions): Promise<GenerateResult>;