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
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: board-view
3
+ description: Display the current kanban board as a markdown table
4
+ ---
5
+
6
+ ## Purpose
7
+ Shows all tasks for a project grouped by status column. Use this to check what work is queued, in progress, or completed.
8
+
9
+ ## Inputs
10
+ - **project** (optional): Project slug. Default: `optimal-cli-refactor`
11
+ - **status** (optional): Filter to a single status column (backlog, ready, in_progress, blocked, review, done)
12
+
13
+ ## Steps
14
+ 1. Call `lib/kanban.ts::getBoard(projectSlug)` to fetch all tasks
15
+ 2. Group tasks by status
16
+ 3. Format as a markdown table with columns: Status | Priority | Title | Agent | Skill
17
+
18
+ ## Output
19
+ Markdown table grouped by status:
20
+
21
+ | Status | P | Title | Agent | Skill |
22
+ |--------|---|-------|-------|-------|
23
+ | in_progress | 1 | Upload R1 data | claude-code | /upload-r1 |
24
+ | backlog | 2 | Extract KPI skills | — | — |
25
+
26
+ ## Environment
27
+ Requires: `OPTIMAL_SUPABASE_URL`, `OPTIMAL_SUPABASE_SERVICE_KEY`
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: delete-batch
3
+ description: Bulk delete transactions by filter from the OptimalOS transactions table
4
+ ---
5
+
6
+ ## Purpose
7
+ Performs bulk deletion of transactions from OptimalOS's `transactions` table based on filter criteria. Used to clean up bad imports, remove duplicate batches, or clear test data. Requires explicit confirmation due to the destructive nature of the operation. Always creates an audit log entry before deleting.
8
+
9
+ ## Inputs
10
+ - **batch-id** (optional): Delete all transactions belonging to a specific `upload_batches.id`. This is the safest and most common deletion method.
11
+ - **user-id** (optional): Filter by owner UUID. Required if not using `--batch-id`.
12
+ - **date-range** (optional): Delete transactions within a date range as `YYYY-MM-DD:YYYY-MM-DD` (inclusive).
13
+ - **bank** (optional): Filter by bank/source format (`chase_checking`, `chase_credit`, `discover`, `generic`).
14
+ - **confirm** (optional): Skip the interactive confirmation prompt. Default: false (requires confirmation).
15
+ - **dry-run** (optional): Show what would be deleted without actually deleting. Default: false.
16
+
17
+ ## Steps
18
+ 1. Call `lib/transactions/ingest.ts::deleteBatch(options)` to orchestrate the deletion
19
+ 2. **Build filter** — construct WHERE clause from provided filters (batch-id, user-id, date-range, bank)
20
+ 3. **Count affected rows** — `SELECT COUNT(*) FROM transactions WHERE <filters>` to show impact
21
+ 4. **Dry-run check** — if `--dry-run`, display count and sample rows, then exit
22
+ 5. **Confirm** — unless `--confirm` is set, display count and ask for explicit confirmation
23
+ 6. **Audit log** — insert a record into `cli_task_logs` with deletion details (filter, count, timestamp)
24
+ 7. **Delete** — `DELETE FROM transactions WHERE <filters>` in batches of 100
25
+ 8. **Clean up batches** — if `--batch-id` used, update `upload_batches.status` to `deleted`
26
+ 9. Log execution via `lib/kanban.ts::logSkillExecution()`
27
+
28
+ ## Output
29
+ ```
30
+ Filter: batch_id = 42
31
+ Affected rows: 231 transactions
32
+ Date range: 2025-11-01 to 2025-11-30
33
+ Bank: chase_credit
34
+
35
+ Deleted: 231 transactions
36
+ Batch 42 marked as deleted.
37
+ Audit log entry: cli_task_logs.id = 789
38
+ ```
39
+
40
+ Dry-run mode:
41
+ ```
42
+ [DRY RUN] Would delete 231 transactions matching:
43
+ batch_id = 42, bank = chase_credit
44
+ Sample: 2025-11-01 "AMAZON.COM" -$47.99, 2025-11-02 "WHOLE FOODS" -$82.31, ...
45
+ ```
46
+
47
+ ## CLI Usage
48
+ ```bash
49
+ # Delete by batch ID (safest)
50
+ optimal delete-batch --batch-id 42
51
+
52
+ # Delete by user and date range
53
+ optimal delete-batch --user-id <uuid> --date-range 2025-11-01:2025-11-30
54
+
55
+ # Dry run to preview
56
+ optimal delete-batch --batch-id 42 --dry-run
57
+
58
+ # Skip confirmation prompt
59
+ optimal delete-batch --batch-id 42 --confirm
60
+ ```
61
+
62
+ ## Environment
63
+ Requires: `OPTIMAL_SUPABASE_URL`, `OPTIMAL_SUPABASE_SERVICE_KEY`
64
+
65
+ ## Tables Touched
66
+ - `transactions` — DELETE matching rows
67
+ - `upload_batches` — update status to `deleted` (if batch-id used)
68
+ - `cli_task_logs` — audit trail entry
69
+
70
+ ## Gotchas
71
+ - **Destructive operation**: Always runs a count + dry-run preview before actual deletion unless `--confirm` is explicitly passed.
72
+ - **No undo**: Deleted transactions cannot be recovered. Re-import the original CSV if needed.
73
+ - **Batch deletion is preferred**: Using `--batch-id` is the safest method because it deletes exactly what was imported in one operation, with clean provenance tracking.
74
+ - **Categories are preserved**: Deleting transactions does not cascade to `categories`.
75
+
76
+ ## Status
77
+ Implementation status: Not yet implemented. Spec only. Lib function to be added to `lib/transactions/ingest.ts` alongside existing ingestion logic.
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: deploy
3
+ description: Deploy an app to Vercel (preview or production)
4
+ ---
5
+
6
+ ## Purpose
7
+ Deploy one of the Optimal project apps to Vercel. Supports both preview deployments (default) and production deployments (with `--prod`).
8
+
9
+ ## Inputs
10
+ - **app** (required): App name to deploy. One of: `dashboard-returnpro`, `optimalos`, `portfolio`, `newsletter-preview`, `wes`.
11
+ - **prod** (optional): Pass `--prod` flag to deploy to production instead of preview.
12
+
13
+ ## Steps
14
+ 1. Resolve the app name to its absolute filesystem path via `lib/infra/deploy.ts::getAppPath()`
15
+ 2. Call `vercel --cwd <path>` (or `vercel --prod --cwd <path>` for production)
16
+ 3. Wait up to 2 minutes for the deployment to complete
17
+ 4. Return the deployment URL
18
+
19
+ ## Output
20
+ The Vercel deployment URL (e.g., `https://portfolio-2026-abc123.vercel.app` for preview, or the production URL for `--prod`).
21
+
22
+ ## Available Apps
23
+
24
+ | Name | Path |
25
+ |------|------|
26
+ | dashboard-returnpro | /home/optimal/dashboard-returnpro |
27
+ | optimalos | /home/optimal/optimalos |
28
+ | portfolio | /home/optimal/portfolio-2026 |
29
+ | newsletter-preview | /home/optimal/projects/newsletter-preview |
30
+ | wes | /home/optimal/wes-dashboard |
31
+
32
+ ## Usage
33
+ ```bash
34
+ optimal deploy portfolio # preview deployment
35
+ optimal deploy portfolio --prod # production deployment
36
+ optimal deploy dashboard-returnpro --prod
37
+ ```
38
+
39
+ ## Environment
40
+ Requires: `vercel` CLI installed globally and authenticated.
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: diagnose-months
3
+ description: Find FK resolution failures and data gaps in staged financial months
4
+ ---
5
+
6
+ ## Purpose
7
+ Diagnoses data quality issues in `stg_financials_raw` by scanning for foreign key resolution failures (orphaned account codes, unresolved program IDs, missing client mappings) and data gaps (months with unexpectedly low row counts). This is the go-to debugging tool when audit-financials reports accuracy below 100% — it pinpoints exactly which rows failed to resolve against dimension tables.
8
+
9
+ ## Inputs
10
+ - **months** (optional): Comma-separated YYYY-MM strings to diagnose. Default: all months with data.
11
+ - **verbose** (optional): Show individual failing rows (not just summary counts). Default: false.
12
+
13
+ ## Steps
14
+ 1. Call `lib/returnpro/diagnose.ts::diagnoseMonths(months?, options?)` to run diagnostics
15
+ 2. For each target month, query `stg_financials_raw` and attempt FK resolution:
16
+ - Join `account_code` against `dim_account.account_code` — flag unresolved
17
+ - Join `master_program_id` against `dim_master_program.id` — flag unresolved
18
+ - Join `client_id` against `dim_client.id` — flag unresolved (if present)
19
+ 3. Count rows per month and compare against expected baseline (~88-93 accounts/month for staging)
20
+ 4. Identify months with zero staging data (data gaps)
21
+ 5. Summarize failures by type and month
22
+ 6. Log execution via `lib/kanban.ts::logSkillExecution()`
23
+
24
+ ## Output
25
+ Summary table:
26
+
27
+ | Month | Rows | Unresolved Accounts | Unresolved Programs | Unresolved Clients | Status |
28
+ |-------|------|---------------------|---------------------|--------------------|--------|
29
+ | 2026-01 | 91 | 0 | 2 | 0 | 2 issues |
30
+ | 2025-12 | 89 | 1 | 0 | 0 | 1 issue |
31
+ | 2025-11 | 0 | - | - | - | NO DATA |
32
+
33
+ With `--verbose`, individual failing rows are listed below the summary:
34
+
35
+ ```
36
+ Unresolved programs in 2026-01:
37
+ Row 45: account_code=4100, master_program_id=999 (no match in dim_master_program)
38
+ Row 72: account_code=5200, master_program_id=1001 (no match in dim_master_program)
39
+ ```
40
+
41
+ ## CLI Usage
42
+ ```bash
43
+ # Diagnose all months
44
+ optimal diagnose-months
45
+
46
+ # Specific months
47
+ optimal diagnose-months --months 2026-01,2025-12
48
+
49
+ # Verbose output with individual rows
50
+ optimal diagnose-months --months 2026-01 --verbose
51
+ ```
52
+
53
+ ## Environment
54
+ Requires: `RETURNPRO_SUPABASE_URL`, `RETURNPRO_SUPABASE_SERVICE_KEY`
55
+
56
+ ## Tables Touched
57
+ - `stg_financials_raw` — scan for FK issues
58
+ - `dim_account` — validate account codes
59
+ - `dim_master_program` — validate program IDs
60
+ - `dim_client` — validate client IDs
61
+ - `dim_program_id` — validate program IDs (secondary lookup)
62
+
63
+ ## Gotchas
64
+ - **Coverage gap is expected**: Staging has ~88-93 accounts/month vs confirmed's ~185-193. This is because not all GL accounts are in Solution7. Diagnose-months focuses on FK resolution failures within the staged data, not the coverage gap itself.
65
+ - **amount is TEXT**: Remember to CAST when doing any numeric analysis on flagged rows.
66
+
67
+ ## Status
68
+ Implementation status: Not yet implemented. Spec only. Lib function `lib/returnpro/diagnose.ts` to be extracted from dashboard-returnpro's `/api/admin/diagnose-months` route.
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: distribute-newsletter
3
+ description: Push a published newsletter to GoHighLevel for email distribution via n8n webhook
4
+ ---
5
+
6
+ ## Purpose
7
+ Triggers distribution of a published Strapi newsletter to subscribers via GoHighLevel email. Uses n8n as the orchestration layer — the skill fires a webhook that kicks off the n8n distribution workflow, which handles email blast creation, recipient targeting, and delivery tracking with 3x retry logic.
8
+
9
+ ## Inputs
10
+ - **documentId** (required): Strapi newsletter documentId to distribute.
11
+ - **brand** (optional): Brand filter for recipient targeting (`CRE-11TRUST` or `LIFEINSUR`). Auto-detected from the newsletter if not provided.
12
+ - **test** (optional): Send to a test recipient list instead of the full subscriber list. Useful for previewing the email in an inbox before full blast.
13
+
14
+ ## Steps
15
+ 1. Call `lib/newsletter/distribute.ts::distributeNewsletter(documentId, options?)` to orchestrate
16
+ 2. **Fetch newsletter from Strapi** — `strapiGet('/api/newsletters/{documentId}')` — verify it is published (not draft)
17
+ 3. **Validate HTML body** — ensure `html_body` is present and non-empty
18
+ 4. **Determine brand** — from newsletter's `brand` field or `--brand` override
19
+ 5. **Fire n8n webhook** — POST to the n8n distribution webhook URL with payload: `{ documentId, brand, html_body, subject_line, sender_email, test }`
20
+ 6. **n8n workflow handles**: GHL campaign creation, recipient targeting by brand, email send, 3x retry on failures
21
+ 7. **Poll for delivery status** — check newsletter's `delivery_status` field (pending → sending → delivered/partial/failed) with 10s intervals, timeout after 5 minutes
22
+ 8. **Update Strapi** — `strapiPut('/api/newsletters', documentId, { delivery_status, delivered_at, recipients_count })` (done by n8n, but verify here)
23
+ 9. Log execution via `lib/kanban.ts::logSkillExecution()`
24
+
25
+ ## Output
26
+ ```
27
+ Newsletter: "South Florida CRE Market Update — March 2026"
28
+ Brand: CRE-11TRUST
29
+ Delivery status: delivered
30
+ Recipients: 342
31
+ Delivered at: 2026-03-01T09:15:00Z
32
+ GHL Campaign ID: camp_abc123
33
+ ```
34
+
35
+ ## CLI Usage
36
+ ```bash
37
+ # Distribute a published newsletter
38
+ optimal distribute-newsletter --documentId abc123-def456
39
+
40
+ # Test send first
41
+ optimal distribute-newsletter --documentId abc123-def456 --test
42
+
43
+ # Explicit brand override
44
+ optimal distribute-newsletter --documentId abc123-def456 --brand LIFEINSUR
45
+ ```
46
+
47
+ ## Environment
48
+ Requires: `STRAPI_URL`, `STRAPI_API_TOKEN`, `N8N_WEBHOOK_URL` (distribution trigger endpoint)
49
+ GoHighLevel credentials are stored in n8n, not in the CLI.
50
+
51
+ ## Gotchas
52
+ - **Must be published**: The newsletter must be in "published" state in Strapi. Draft newsletters cannot be distributed.
53
+ - **n8n must be running**: The distribution workflow depends on n8n being active (`npx n8n` or running as service on port 5678).
54
+ - **3x retry logic**: n8n handles retries. If all 3 attempts fail, `delivery_status` is set to `failed` and `delivery_errors` JSON contains the failure details.
55
+ - **Scheduling**: Newsletters with a future `scheduled_date` are queued and distributed by a 15-minute n8n cron, not immediately.
56
+
57
+ ## Status
58
+ Implementation status: Not yet implemented. Spec only. Lib function `lib/newsletter/distribute.ts` to be built as a webhook trigger client.
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: export-budget
3
+ description: Export FY26 budget projections as CSV with unit, retail, and inventory value columns
4
+ ---
5
+
6
+ ## Purpose
7
+ Exports FY26 budget projections as a CSV file suitable for import into spreadsheets, Vena, or other planning tools. Includes full unit projection, average retail projection, and computed inventory value (units x retail) columns.
8
+
9
+ This is a convenience wrapper around `project-budget --format csv` that writes directly to stdout for piping to a file.
10
+
11
+ ## Inputs
12
+ - **adjustment-type** (optional): `percent` or `flat`. Default: `percent`.
13
+ - **adjustment-value** (optional): Numeric value for the adjustment. Default: `0` (no change).
14
+ - **fiscal-year** (optional): Base fiscal year for actuals. Default: `2025`.
15
+ - **user-id** (optional): Supabase user UUID to filter imports by.
16
+ - **file** (optional): Path to a JSON file containing `CheckedInUnitsSummary[]` array.
17
+
18
+ ## Steps
19
+ 1. Load FY25 data (same as `project-budget`)
20
+ 2. Initialize projections
21
+ 3. Apply adjustment if specified
22
+ 4. Call `exportToCSV(projections)` and write to stdout
23
+
24
+ ## Output
25
+ CSV with these columns:
26
+ - Program Code, Master Program, Client
27
+ - 2025 Actual Units, Unit Adj Type, Unit Adj Value, 2026 Projected Units, Unit Change, Unit Change %
28
+ - 2025 Avg Retail, Retail Adj Type, Retail Adj Value, 2026 Projected Retail, Retail Change, Retail Change %
29
+ - 2025 Inventory Value, 2026 Projected Inv. Value, Inv. Value Change, Inv. Value Change %
30
+
31
+ ## CLI Usage
32
+ ```bash
33
+ # Export with 4% growth to file
34
+ npx tsx bin/optimal.ts export-budget --adjustment-type percent --adjustment-value 4 > fy26-projections.csv
35
+
36
+ # Export from JSON data source
37
+ npx tsx bin/optimal.ts export-budget --file ./fy25-actuals.json > fy26-projections.csv
38
+
39
+ # No adjustment (baseline copy)
40
+ npx tsx bin/optimal.ts export-budget > fy26-baseline.csv
41
+ ```
42
+
43
+ ## Environment
44
+ Requires (when using Supabase): `RETURNPRO_SUPABASE_URL`, `RETURNPRO_SUPABASE_SERVICE_KEY`
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: export-kpis
3
+ description: Export KPI totals by program and client from ReturnPro financial data
4
+ ---
5
+
6
+ ## Purpose
7
+ Exports KPI data aggregated by program, client, and month from ReturnPro's `stg_financials_raw` table via the `get_kpi_totals_by_program_client` RPC function. Useful for ad-hoc financial analysis, stakeholder reporting, and data validation.
8
+
9
+ ## Inputs
10
+ - **months** (optional): Comma-separated YYYY-MM strings (e.g., `2026-01,2025-12`). Defaults to the 3 most recent months with data.
11
+ - **programs** (optional): Comma-separated program name substrings for case-insensitive filtering (e.g., `BRTON,FORTX`).
12
+ - **format** (optional): Output format — `table` (markdown, default) or `csv`.
13
+
14
+ ## Steps
15
+ 1. Call `lib/returnpro/kpis.ts::exportKpis(options?)` to fetch KPI data
16
+ 2. Resolve months — use provided list or default to 3 most recent months in `stg_financials_raw`
17
+ 3. If programs filter given, resolve names to `master_program_id` via `dim_master_program` (partial match)
18
+ 4. For each month (x program), call `get_kpi_totals_by_program_client` RPC
19
+ 5. Map results to flat `KpiRow[]` (month, kpiName, kpiBucket, programName, clientName, totalAmount)
20
+ 6. Format as markdown table or CSV
21
+
22
+ ## Output
23
+ Table format (default):
24
+
25
+ | Month | KPI | Bucket | Client | Program | Amount |
26
+ |---------|-----|--------|--------|---------|--------|
27
+ | 2026-01 | Revenue | Actual | Walmart | BRTON-WM | $1.2M |
28
+
29
+ CSV format: standard comma-separated with header row.
30
+
31
+ Amounts use compact notation ($1.2M, $890K) in table mode, full precision in CSV mode.
32
+
33
+ ## CLI Usage
34
+ ```bash
35
+ # Latest 3 months, table format
36
+ npx tsx bin/optimal.ts export-kpis
37
+
38
+ # Specific month
39
+ npx tsx bin/optimal.ts export-kpis --months 2026-01
40
+
41
+ # Multiple months, CSV output
42
+ npx tsx bin/optimal.ts export-kpis --months 2025-10,2025-11,2025-12 --format csv
43
+
44
+ # Filter to BRTON programs only
45
+ npx tsx bin/optimal.ts export-kpis --months 2026-01 --programs BRTON
46
+
47
+ # Pipe CSV to file
48
+ npx tsx bin/optimal.ts export-kpis --format csv > kpis-export.csv
49
+ ```
50
+
51
+ ## Environment
52
+ Requires: `RETURNPRO_SUPABASE_URL`, `RETURNPRO_SUPABASE_SERVICE_KEY`
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: generate-netsuite-template
3
+ description: Generate a blank NetSuite upload template pre-filled with account codes and program mappings
4
+ ---
5
+
6
+ ## Purpose
7
+ Creates a blank upload template (XLSX or CSV) pre-populated with valid account codes, account names, and program mappings from ReturnPro's dimension tables. This saves time when preparing NetSuite data for staging upload — instead of manually looking up codes, Carlos gets a ready-to-fill template with all valid FK references.
8
+
9
+ ## Inputs
10
+ - **month** (required): Target month as YYYY-MM for the template header/period column.
11
+ - **format** (optional): Output format — `xlsx` (default) or `csv`.
12
+ - **output** (optional): File path to save the template. Default: `~/Downloads/returnpro-data/netsuite-template-{YYYY-MM}.{ext}`
13
+ - **programs** (optional): Comma-separated program name substrings to include. Default: all active programs.
14
+
15
+ ## Steps
16
+ 1. Call `lib/returnpro/templates.ts::generateNetsuiteTemplate(month, options?)` to build the template
17
+ 2. Fetch all active accounts from `dim_account` (account_code, account_name)
18
+ 3. Fetch all active programs from `dim_master_program` (program_id, program_name)
19
+ 4. Build template structure: one row per account_code, columns for period, account_code, account_name, amount (blank), master_program_id, program_name
20
+ 5. If `--programs` filter given, only include matching programs
21
+ 6. Write to disk as XLSX (with header formatting) or CSV
22
+ 7. Log execution via `lib/kanban.ts::logSkillExecution()`
23
+
24
+ ## Output
25
+ ```
26
+ Generated NetSuite template for 2026-01
27
+ Accounts: 193 | Programs: 97
28
+ Saved to: ~/Downloads/returnpro-data/netsuite-template-2026-01.xlsx
29
+ ```
30
+
31
+ ## CLI Usage
32
+ ```bash
33
+ # Default XLSX template
34
+ optimal generate-netsuite-template --month 2026-01
35
+
36
+ # CSV format, custom output path
37
+ optimal generate-netsuite-template --month 2026-01 --format csv --output ./template.csv
38
+
39
+ # Only BRTON programs
40
+ optimal generate-netsuite-template --month 2026-01 --programs BRTON
41
+ ```
42
+
43
+ ## Environment
44
+ Requires: `RETURNPRO_SUPABASE_URL`, `RETURNPRO_SUPABASE_SERVICE_KEY`
45
+
46
+ ## Tables Touched
47
+ - `dim_account` — read account codes and names
48
+ - `dim_master_program` — read program IDs and names
49
+
50
+ ## Status
51
+ Implementation status: Not yet implemented. Spec only. Lib function `lib/returnpro/templates.ts` to be extracted from dashboard-returnpro's `/api/admin/netsuite-template` route.
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: generate-newsletter
3
+ description: Generate a branded newsletter with AI-powered content, news, and optional property listings, then push to Strapi CMS
4
+ ---
5
+
6
+ ## Purpose
7
+ End-to-end newsletter generation pipeline. Fetches news from NewsAPI, generates AI summaries via Groq (Llama 3.3 70B), reads property listings from Excel (CRE brand only), builds branded HTML, and pushes a draft to Strapi CMS. Supports multiple brands: CRE-11TRUST (ElevenTrust) and LIFEINSUR (Anchor Point Insurance).
8
+
9
+ ## Inputs
10
+ - **brand** (required): `CRE-11TRUST` or `LIFEINSUR`
11
+ - **date** (optional): Edition date as YYYY-MM-DD (default: today)
12
+ - **excel** (optional): Path to Excel file with property listings (columnar format: col A = labels, B-N = properties). Only used for CRE-11TRUST brand.
13
+ - **dry-run** (optional): If set, generates content but does NOT push to Strapi. Useful for previewing output.
14
+
15
+ ## Steps
16
+ 1. **Load brand config** — determines news query, sender email, display name, template styling
17
+ 2. **Read Excel properties** (CRE-11TRUST only) — parse columnar Excel via `readExcelProperties()`
18
+ 3. **Fetch news** — `fetchNews(query)` hits NewsAPI for 5 latest articles matching the brand's query
19
+ 4. **Generate AI content** — `generateAiContent(properties, news)` sends properties + news to Groq and gets back market overview, property analyses, and news summaries as structured JSON
20
+ 5. **Build HTML** — `buildHtml()` assembles a responsive email-safe HTML newsletter with brand-specific colors and sections
21
+ 6. **Build Strapi payload** — `buildStrapiPayload()` creates the structured payload with slug (includes timestamp for uniqueness)
22
+ 7. **Push to Strapi** — `strapiPost('/api/newsletters', data)` creates a draft newsletter in Strapi CMS (skipped in dry-run mode)
23
+
24
+ ## Output
25
+ - Newsletter HTML (logged length)
26
+ - Strapi draft documentId (or "DRY RUN" indicator)
27
+ - Console summary of all generated content
28
+
29
+ ## CLI Usage
30
+ ```bash
31
+ # CRE newsletter with properties from Excel
32
+ optimal generate-newsletter --brand CRE-11TRUST --excel ~/projects/newsletter-automation/input/properties.xlsx
33
+
34
+ # LIFEINSUR newsletter (no properties)
35
+ optimal generate-newsletter --brand LIFEINSUR
36
+
37
+ # Preview without pushing to Strapi
38
+ optimal generate-newsletter --brand CRE-11TRUST --dry-run
39
+
40
+ # Specific edition date
41
+ optimal generate-newsletter --brand CRE-11TRUST --date 2026-03-01 --excel ./input/latest.xlsx
42
+ ```
43
+
44
+ ## Environment
45
+ Requires: `GROQ_API_KEY`, `NEWSAPI_KEY`, `STRAPI_URL`, `STRAPI_API_TOKEN`
46
+ Optional: `GROQ_MODEL` (default: llama-3.3-70b-versatile), `NEWSAPI_QUERY`, `LIFEINSUR_NEWSAPI_QUERY`
47
+
48
+ ## Gotchas
49
+ - **Image extraction skipped**: The Python pipeline extracts embedded EMF/WMF images from Excel. This is deferred in the TypeScript port — use the Python pipeline for image-heavy newsletters.
50
+ - **Slug uniqueness**: Slugs include a timestamp (YYYYMMDDTHHMMSS) to avoid conflicts on same-day reruns.
51
+ - **Excel column order matters**: "contact info" matcher runs before "name" to avoid disambiguation issues.
52
+ - **Strapi rate limits**: API token does not rate-limit, but admin login does (5 attempts then 429 for ~2min).
53
+ - **ExcelJS dependency**: Only loaded dynamically when `--excel` is provided, so the dep is optional for non-CRE newsletters.
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: generate-newsletter-insurance
3
+ description: Generate an insurance-specific newsletter for Anchor Point Insurance Co. (brand=LIFEINSUR)
4
+ ---
5
+
6
+ ## Purpose
7
+ Generates a branded newsletter for Anchor Point Insurance Co. (brand=LIFEINSUR). This is a specialization of the generate-newsletter skill, pre-configured for the insurance vertical — it fetches life insurance and financial planning news, uses the Anchor Point brand palette (warm charcoal #44403E, terracotta #AD7C59, warm beige #FCF9F6), and omits property listings (which are CRE-11TRUST only).
8
+
9
+ ## Inputs
10
+ - **date** (optional): Edition date as YYYY-MM-DD. Default: today.
11
+ - **dry-run** (optional): Generate content but do NOT push to Strapi. Useful for previewing.
12
+ - **news-query** (optional): Override the default NewsAPI query. Default: `LIFEINSUR_NEWSAPI_QUERY` env var or `"life insurance financial planning south florida"`.
13
+
14
+ ## Steps
15
+ 1. Call `lib/newsletter/generate-insurance.ts::generateInsuranceNewsletter(options?)` to orchestrate
16
+ 2. **Load LIFEINSUR brand config** — palette, sender email (from `brand-config` in Strapi or hardcoded defaults), display name "Anchor Point Insurance Co."
17
+ 3. **Fetch news** — `fetchNews(query)` hits NewsAPI for 5 latest articles matching insurance/financial planning topics
18
+ 4. **Generate AI content** — `generateAiContent(null, news)` sends news to Groq (no properties for LIFEINSUR). Returns market overview and news summaries.
19
+ 5. **Build HTML** — `buildHtml()` assembles responsive email-safe HTML with Anchor Point brand colors and insurance-specific sections
20
+ 6. **Build Strapi payload** — `buildStrapiPayload()` with brand=`LIFEINSUR`, slug includes timestamp for uniqueness
21
+ 7. **Push to Strapi** — `strapiPost('/api/newsletters', data)` creates a draft newsletter (skipped in dry-run mode)
22
+ 8. Log execution via `lib/kanban.ts::logSkillExecution()`
23
+
24
+ ## Output
25
+ ```
26
+ Brand: LIFEINSUR (Anchor Point Insurance Co.)
27
+ News articles fetched: 5
28
+ AI content generated: market_overview (342 words), 5 news summaries
29
+ HTML length: 12,847 chars
30
+ Strapi draft created: documentId=abc123-def456
31
+ ```
32
+
33
+ ## CLI Usage
34
+ ```bash
35
+ # Generate insurance newsletter for today
36
+ optimal generate-newsletter-insurance
37
+
38
+ # Specific date
39
+ optimal generate-newsletter-insurance --date 2026-03-01
40
+
41
+ # Preview without pushing to Strapi
42
+ optimal generate-newsletter-insurance --dry-run
43
+
44
+ # Custom news query
45
+ optimal generate-newsletter-insurance --news-query "florida insurance market rates"
46
+ ```
47
+
48
+ ## Environment
49
+ Requires: `GROQ_API_KEY`, `NEWSAPI_KEY`, `STRAPI_URL`, `STRAPI_API_TOKEN`
50
+ Optional: `GROQ_MODEL` (default: llama-3.3-70b-versatile), `LIFEINSUR_NEWSAPI_QUERY`
51
+
52
+ ## Gotchas
53
+ - **No properties**: Unlike CRE-11TRUST, LIFEINSUR newsletters do not include property listings. The `--excel` parameter is not available.
54
+ - **Slug uniqueness**: Slugs include a timestamp (YYYYMMDDTHHMMSS) to avoid conflicts on same-day reruns.
55
+ - **Brand palette**: Primary #44403E (warm charcoal), Accent #AD7C59 (terracotta), BG #FCF9F6 (warm beige).
56
+ - **Preview site**: Published newsletters render at https://newsletter.op-hub.com/lifeinsur
57
+
58
+ ## Status
59
+ Implementation status: Not yet implemented. Spec only. Lib function `lib/newsletter/generate-insurance.ts` to be ported from `generate-newsletter-lifeinsur.py` in the newsletter-automation repo.
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: generate-social-posts
3
+ description: Analyze competitor ads, generate 9 branded social posts with AI copy and Unsplash photos
4
+ ---
5
+
6
+ ## Purpose
7
+ End-to-end social media post generation pipeline. Scrapes competitor ads from Meta Ad Library for pattern analysis, generates 9 themed social posts with AI-written copy, sources stock photography from Unsplash, and pushes drafts to Strapi CMS. This is a weekly workflow — typically run as "Generate 9 new social posts for [BRAND] for the week of [DATE]".
8
+
9
+ ## Inputs
10
+ - **brand** (required): Brand key — `CRE-11TRUST` or `LIFEINSUR`
11
+ - **week** (required): Week start date as YYYY-MM-DD (e.g., `2026-03-03` for the week of March 3rd)
12
+ - **competitors** (optional): Comma-separated competitor names or path to file. Default: uses the brand's standard competitor list.
13
+ - **count** (optional): Number of posts to generate. Default: `9`.
14
+ - **dry-run** (optional): Generate content but do NOT push to Strapi.
15
+
16
+ ## Steps
17
+ 1. Call `lib/social/post-generator.ts::generateSocialPosts(brand, week, options?)` to orchestrate the full pipeline
18
+ 2. **Scrape competitor ads** — run `lib/social/scraper.ts::scrapeAds(competitors)` against the brand's competitor list (3 parallel batches of 6 companies)
19
+ 3. **Analyze ad patterns** — extract common themes, CTAs, platforms, and copy structures from scraped data
20
+ 4. **Generate post copy** — send analysis to Groq AI to generate 9 posts with: headline, body, cta_text, cta_url, platform targeting, overlay_style, and scheduling (spread across the week)
21
+ 5. **Source photos** — for each post theme, search Unsplash via `unsplash.com/napi/search/photos?query=X&per_page=3` and select the best match
22
+ 6. **Build Strapi payloads** — create `social-post` entries with all fields: brand, headline, body, cta_text, cta_url, image_url, overlay_style, template, scheduled_date, competitor_ref, platform, delivery_status=pending
23
+ 7. **Push to Strapi** — `strapiPost('/api/social-posts', data)` for each post (skipped in dry-run mode)
24
+ 8. Log execution via `lib/kanban.ts::logSkillExecution()`
25
+
26
+ ## Output
27
+ ```
28
+ Brand: LIFEINSUR (Anchor Point Insurance Co.)
29
+ Competitors scraped: 18 companies, 1,382 ads analyzed
30
+ Posts generated: 9
31
+
32
+ | # | Platform | Headline | Scheduled | Overlay |
33
+ |---|----------|----------|-----------|---------|
34
+ | 1 | instagram | "Protect What Matters Most" | Mon 3/3 | dark-bottom |
35
+ | 2 | facebook | "Your Family's Future..." | Mon 3/3 | brand-bottom |
36
+ | 3 | instagram | "Life Insurance Myths..." | Tue 3/4 | brand-full |
37
+ | ... | ... | ... | ... | ... |
38
+
39
+ Pushed 9 drafts to Strapi.
40
+ ```
41
+
42
+ ## CLI Usage
43
+ ```bash
44
+ # Generate 9 posts for Anchor Point Insurance
45
+ optimal generate-social-posts --brand LIFEINSUR --week 2026-03-03
46
+
47
+ # CRE brand with custom competitors
48
+ optimal generate-social-posts --brand CRE-11TRUST --week 2026-03-03 --competitors "CBRE,JLL,Cushman"
49
+
50
+ # Dry run, custom count
51
+ optimal generate-social-posts --brand LIFEINSUR --week 2026-03-03 --count 6 --dry-run
52
+ ```
53
+
54
+ ## Environment
55
+ Requires: `GROQ_API_KEY`, `STRAPI_URL`, `STRAPI_API_TOKEN`, `playwright` (for ad scraping)
56
+ Optional: `GROQ_MODEL` (default: llama-3.3-70b-versatile)
57
+
58
+ ## Gotchas
59
+ - **Unsplash API**: Use `unsplash.com/napi/search/photos` (public search is bot-blocked by Anubis challenge page).
60
+ - **Scraper batches**: Run in 3 parallel batches of 6 companies each for optimal throughput.
61
+ - **Overlay styles**: `dark-bottom`, `brand-bottom`, `brand-full`, `dark-full` — choose based on image content.
62
+ - **Platform targeting**: Posts should be spread across instagram, facebook, linkedin based on brand's platform mix.
63
+ - **Scheduled dates**: Spread posts across the week (e.g., 2 Mon, 2 Tue, 2 Wed, 2 Thu, 1 Fri).
64
+ - **Playwright browser**: Requires one-time `npx playwright install chromium`.
65
+
66
+ ## Status
67
+ Implementation status: Not yet implemented. Spec only. Lib function `lib/social/post-generator.ts` to be built as a multi-step orchestrator calling existing scraper and CMS functions.
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: health-check
3
+ description: Run the health check script across all Optimal services
4
+ ---
5
+
6
+ ## Purpose
7
+ Run the workstation health check script to verify the status of all Optimal services, Docker containers, and Git repositories.
8
+
9
+ ## Inputs
10
+ None.
11
+
12
+ ## Steps
13
+ 1. Execute `/home/optimal/scripts/health-check.sh` via `lib/infra/deploy.ts::healthCheck()`
14
+ 2. Script checks each service (timeout: 30 seconds total):
15
+ - **n8n**: Process running check (`pgrep`)
16
+ - **Affine**: Docker container status + HTTP check against `https://affine.op-hub.com`
17
+ - **Strapi CMS**: systemd user service status + HTTP health endpoint on `127.0.0.1:1337/_health`
18
+ - **Git Repositories**: Fetch latest, report uncommitted changes / unpushed commits / behind remote
19
+ - **Docker**: systemd service status + active container count
20
+ - **OptimalOS**: HTTP check on `localhost:3001`
21
+ 3. Return the full formatted output
22
+
23
+ ## Output
24
+ Formatted status report with per-service check/warn/fail indicators:
25
+
26
+ ```
27
+ Health Check - 2026-03-01 10:00:00
28
+ ▸ n8n -> running on port 5678
29
+ ▸ Affine -> running at https://affine.op-hub.com
30
+ ▸ Strapi CMS -> running at https://strapi.op-hub.com
31
+ ▸ Git Repos -> per-repo sync status
32
+ ▸ Docker -> N containers active
33
+ ▸ OptimalOS -> dev server on port 3001 (optional)
34
+ ```
35
+
36
+ ## Usage
37
+ ```bash
38
+ optimal health-check
39
+ ```
40
+
41
+ ## Environment
42
+ Requires: bash, curl, git, docker, systemctl. The script at `/home/optimal/scripts/health-check.sh` must exist.