optimal-cli 1.0.0 → 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 (85) 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 +278 -591
  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/config/registry.ts +5 -4
  17. package/lib/kanban-obsidian.ts +232 -0
  18. package/lib/kanban-sync.ts +258 -0
  19. package/lib/kanban.ts +239 -0
  20. package/lib/obsidian-tasks.ts +231 -0
  21. package/package.json +5 -19
  22. package/pnpm-workspace.yaml +3 -0
  23. package/scripts/check-table.ts +24 -0
  24. package/scripts/create-tables.ts +94 -0
  25. package/scripts/migrate-kanban.sh +28 -0
  26. package/scripts/migrate-v2.ts +78 -0
  27. package/scripts/migrate.ts +79 -0
  28. package/scripts/run-migration.ts +59 -0
  29. package/scripts/seed-board.ts +203 -0
  30. package/scripts/test-kanban.ts +21 -0
  31. package/skills/audit-financials/SKILL.md +33 -0
  32. package/skills/board-create/SKILL.md +28 -0
  33. package/skills/board-update/SKILL.md +27 -0
  34. package/skills/board-view/SKILL.md +27 -0
  35. package/skills/delete-batch/SKILL.md +77 -0
  36. package/skills/deploy/SKILL.md +40 -0
  37. package/skills/diagnose-months/SKILL.md +68 -0
  38. package/skills/distribute-newsletter/SKILL.md +58 -0
  39. package/skills/export-budget/SKILL.md +44 -0
  40. package/skills/export-kpis/SKILL.md +52 -0
  41. package/skills/generate-netsuite-template/SKILL.md +51 -0
  42. package/skills/generate-newsletter/SKILL.md +53 -0
  43. package/skills/generate-newsletter-insurance/SKILL.md +59 -0
  44. package/skills/generate-social-posts/SKILL.md +67 -0
  45. package/skills/health-check/SKILL.md +42 -0
  46. package/skills/ingest-transactions/SKILL.md +51 -0
  47. package/skills/manage-cms/SKILL.md +50 -0
  48. package/skills/manage-scenarios/SKILL.md +83 -0
  49. package/skills/migrate-db/SKILL.md +79 -0
  50. package/skills/preview-newsletter/SKILL.md +50 -0
  51. package/skills/project-budget/SKILL.md +60 -0
  52. package/skills/publish-blog/SKILL.md +70 -0
  53. package/skills/publish-social-posts/SKILL.md +70 -0
  54. package/skills/rate-anomalies/SKILL.md +62 -0
  55. package/skills/scrape-ads/SKILL.md +49 -0
  56. package/skills/stamp-transactions/SKILL.md +62 -0
  57. package/skills/upload-income-statements/SKILL.md +54 -0
  58. package/skills/upload-netsuite/SKILL.md +56 -0
  59. package/skills/upload-r1/SKILL.md +45 -0
  60. package/supabase/.temp/cli-latest +1 -0
  61. package/supabase/migrations/.gitkeep +0 -0
  62. package/supabase/migrations/20250305000001_create_agent_configs.sql +36 -0
  63. package/supabase/migrations/20260305111300_create_cli_config_registry.sql +22 -0
  64. package/supabase/migrations/20260306195000_create_kanban_tables.sql +97 -0
  65. package/tests/config-command-smoke.test.ts +395 -0
  66. package/tests/config-registry.test.ts +173 -0
  67. package/tsconfig.json +19 -0
  68. package/agents/profiles.json +0 -5
  69. package/docs/CLI-REFERENCE.md +0 -361
  70. package/lib/assets/index.ts +0 -225
  71. package/lib/assets.ts +0 -124
  72. package/lib/auth/index.ts +0 -189
  73. package/lib/board/index.ts +0 -309
  74. package/lib/board/types.ts +0 -124
  75. package/lib/bot/claim.ts +0 -43
  76. package/lib/bot/coordinator.ts +0 -254
  77. package/lib/bot/heartbeat.ts +0 -37
  78. package/lib/bot/index.ts +0 -9
  79. package/lib/bot/protocol.ts +0 -99
  80. package/lib/bot/reporter.ts +0 -42
  81. package/lib/bot/skills.ts +0 -81
  82. package/lib/errors.ts +0 -129
  83. package/lib/format.ts +0 -120
  84. package/lib/returnpro/validate.ts +0 -154
  85. package/lib/social/meta.ts +0 -228
@@ -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.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: ingest-transactions
3
+ description: Parse & deduplicate bank CSV files into the OptimalOS transactions table
4
+ ---
5
+
6
+ ## Purpose
7
+ Reads a bank-exported CSV from disk, auto-detects the bank format (Chase Checking, Chase Credit, Discover, or Generic), parses rows into normalized transactions, deduplicates against existing data using SHA-256 hashes, and batch-inserts new records into the `transactions` table on the OptimalOS Supabase instance.
8
+
9
+ ## Inputs
10
+ - **file** (required): Absolute path to the CSV file on disk
11
+ - **user-id** (required): Supabase user UUID to own the imported transactions
12
+
13
+ ## Supported Formats
14
+ | Format | Detection | Sign Convention |
15
+ |--------|-----------|-----------------|
16
+ | Chase Checking | `Details, Posting Date, Description, Amount, Type, Balance` | Negative = expense |
17
+ | Chase Credit | `Transaction Date, Post Date, Description, Category, Type, Amount` | Negative = expense |
18
+ | Discover | `Trans. Date, Post Date, Description, Amount, Category` | Positive = charge (flipped) |
19
+ | Generic CSV | Any CSV with `date`, `description`, `amount` columns | As-is |
20
+
21
+ Amex XLSX is not yet supported in the CLI (use the OptimalOS web UI).
22
+
23
+ ## Steps
24
+ 1. Read the CSV file from `--file` path
25
+ 2. Auto-detect bank format from header row
26
+ 3. Parse rows using format-specific normalizer (handles quoted fields, date formats, amounts with $, parentheses, commas)
27
+ 4. Generate SHA-256 dedup hash for each row: `sha256(date|amount|normalizedDescription)[0:32]`
28
+ 5. Query `transactions.dedup_hash` to find existing duplicates
29
+ 6. Create an `upload_batches` record for provenance tracking
30
+ 7. Resolve category names to `categories.id` (create if missing)
31
+ 8. Batch-insert new rows (50 per batch)
32
+
33
+ ## Output
34
+ ```
35
+ Format detected: chase_credit (confidence: 1.0)
36
+ Parsed 247 transactions
37
+ Inserted: 231 | Skipped (duplicates): 16 | Failed: 0
38
+ ```
39
+
40
+ ## CLI Usage
41
+ ```bash
42
+ tsx bin/optimal.ts ingest-transactions --file ~/Downloads/chase-statement.csv --user-id <uuid>
43
+ ```
44
+
45
+ ## Environment
46
+ Requires: `OPTIMAL_SUPABASE_URL`, `OPTIMAL_SUPABASE_SERVICE_KEY`
47
+
48
+ ## Tables Touched
49
+ - `transactions` — insert new rows
50
+ - `upload_batches` — provenance record
51
+ - `categories` — resolve or create category mappings
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: manage-cms
3
+ description: Create, update, list, publish, and delete content in Strapi CMS across all brands
4
+ ---
5
+
6
+ ## Purpose
7
+ Full content lifecycle management for Strapi v5 CMS. Handles newsletters, social posts, and blog posts across multiple brands (CRE-11TRUST, LIFEINSUR). Used by the newsletter pipeline, social post pipeline, and portfolio blog publishing.
8
+
9
+ ## Inputs
10
+ - **action** (required): One of `list`, `get`, `create`, `update`, `delete`, `publish`, `unpublish`, `upload`
11
+ - **contentType** (required): Strapi plural name — `newsletters`, `social-posts`, or `blog-posts`
12
+ - **brand** (optional): Brand filter — `CRE-11TRUST` or `LIFEINSUR`
13
+ - **documentId** (required for update/delete/publish/unpublish): Strapi v5 documentId (UUID string, NOT numeric id)
14
+ - **slug** (optional for get): Look up item by slug instead of documentId
15
+ - **data** (required for create/update): Field values as key-value pairs
16
+ - **status** (optional for list): `draft` or `published` — filters by Strapi draftAndPublish status
17
+ - **filePath** (required for upload): Absolute path to file for upload
18
+ - **refData** (optional for upload): `{ ref, refId, field }` to link upload to an entry
19
+
20
+ ## Steps
21
+ 1. Determine action and validate required inputs
22
+ 2. Call the appropriate function from `lib/cms/strapi-client.ts`:
23
+ - **list**: `listByBrand(contentType, brand, status?)` or `strapiGet('/api/{contentType}', params)`
24
+ - **get**: `findBySlug(contentType, slug)` or `strapiGet('/api/{contentType}/{documentId}')`
25
+ - **create**: `strapiPost('/api/{contentType}', data)` — include `brand` in data
26
+ - **update**: `strapiPut('/api/{contentType}', documentId, data)` — uses documentId, NOT numeric id
27
+ - **delete**: `strapiDelete('/api/{contentType}', documentId)`
28
+ - **publish**: `publish(contentType, documentId)` — sets publishedAt
29
+ - **unpublish**: `unpublish(contentType, documentId)` — clears publishedAt
30
+ - **upload**: `strapiUploadFile(filePath, refData?)`
31
+ 3. Return the result with documentId, title/headline, and status
32
+
33
+ ## Output
34
+ - **list**: Count and table of items with documentId, title, brand, status, updatedAt
35
+ - **get**: Full item fields
36
+ - **create**: `Created {contentType}: {documentId} — "{title}"`
37
+ - **update**: `Updated {contentType}: {documentId}`
38
+ - **delete**: `Deleted {contentType}: {documentId}`
39
+ - **publish/unpublish**: `Published/Unpublished {contentType}: {documentId}`
40
+ - **upload**: Uploaded file URL(s)
41
+
42
+ ## Environment
43
+ Requires: `STRAPI_URL`, `STRAPI_API_TOKEN`
44
+
45
+ ## Gotchas
46
+ - **documentId, not id**: Strapi v5 PUT/DELETE use documentId (UUID string). The numeric `id` field exists but should not be used for mutations.
47
+ - **Reserved fields**: Never use `status`, `published_at`, `locale`, or `meta` as custom field names — Strapi v5 reserves them.
48
+ - **Slug uniqueness**: Include a timestamp in slugs for same-day reruns to avoid conflicts.
49
+ - **draftAndPublish**: Drafts are created by default. Explicitly call `publish` to make content live.
50
+ - **Rate limits**: Strapi admin login rate-limits aggressively (5 attempts then 429). The API token does not have this issue.