optimal-cli 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/dist/bin/optimal.d.ts +2 -0
  2. package/dist/bin/optimal.js +1590 -0
  3. package/dist/lib/assets/index.d.ts +79 -0
  4. package/dist/lib/assets/index.js +153 -0
  5. package/dist/lib/assets.d.ts +20 -0
  6. package/dist/lib/assets.js +112 -0
  7. package/dist/lib/auth/index.d.ts +83 -0
  8. package/dist/lib/auth/index.js +146 -0
  9. package/dist/lib/board/index.d.ts +39 -0
  10. package/dist/lib/board/index.js +285 -0
  11. package/dist/lib/board/types.d.ts +111 -0
  12. package/dist/lib/board/types.js +1 -0
  13. package/dist/lib/bot/claim.d.ts +3 -0
  14. package/dist/lib/bot/claim.js +20 -0
  15. package/dist/lib/bot/coordinator.d.ts +27 -0
  16. package/dist/lib/bot/coordinator.js +178 -0
  17. package/dist/lib/bot/heartbeat.d.ts +6 -0
  18. package/dist/lib/bot/heartbeat.js +30 -0
  19. package/dist/lib/bot/index.d.ts +9 -0
  20. package/dist/lib/bot/index.js +6 -0
  21. package/dist/lib/bot/protocol.d.ts +12 -0
  22. package/dist/lib/bot/protocol.js +74 -0
  23. package/dist/lib/bot/reporter.d.ts +3 -0
  24. package/dist/lib/bot/reporter.js +27 -0
  25. package/dist/lib/bot/skills.d.ts +26 -0
  26. package/dist/lib/bot/skills.js +69 -0
  27. package/dist/lib/budget/projections.d.ts +115 -0
  28. package/dist/lib/budget/projections.js +384 -0
  29. package/dist/lib/budget/scenarios.d.ts +93 -0
  30. package/dist/lib/budget/scenarios.js +214 -0
  31. package/dist/lib/cms/publish-blog.d.ts +62 -0
  32. package/dist/lib/cms/publish-blog.js +74 -0
  33. package/dist/lib/cms/strapi-client.d.ts +123 -0
  34. package/dist/lib/cms/strapi-client.js +213 -0
  35. package/dist/lib/config/registry.d.ts +17 -0
  36. package/dist/lib/config/registry.js +182 -0
  37. package/dist/lib/config/schema.d.ts +31 -0
  38. package/dist/lib/config/schema.js +25 -0
  39. package/dist/lib/config.d.ts +55 -0
  40. package/dist/lib/config.js +206 -0
  41. package/dist/lib/errors.d.ts +25 -0
  42. package/dist/lib/errors.js +91 -0
  43. package/dist/lib/format.d.ts +28 -0
  44. package/dist/lib/format.js +98 -0
  45. package/dist/lib/infra/deploy.d.ts +29 -0
  46. package/dist/lib/infra/deploy.js +58 -0
  47. package/dist/lib/infra/migrate.d.ts +34 -0
  48. package/dist/lib/infra/migrate.js +103 -0
  49. package/dist/lib/newsletter/distribute.d.ts +52 -0
  50. package/dist/lib/newsletter/distribute.js +193 -0
  51. package/{lib/newsletter/generate-insurance.ts → dist/lib/newsletter/generate-insurance.d.ts} +7 -24
  52. package/dist/lib/newsletter/generate-insurance.js +36 -0
  53. package/dist/lib/newsletter/generate.d.ts +104 -0
  54. package/dist/lib/newsletter/generate.js +571 -0
  55. package/dist/lib/returnpro/anomalies.d.ts +64 -0
  56. package/dist/lib/returnpro/anomalies.js +166 -0
  57. package/dist/lib/returnpro/audit.d.ts +32 -0
  58. package/dist/lib/returnpro/audit.js +147 -0
  59. package/dist/lib/returnpro/diagnose.d.ts +52 -0
  60. package/dist/lib/returnpro/diagnose.js +281 -0
  61. package/dist/lib/returnpro/kpis.d.ts +32 -0
  62. package/dist/lib/returnpro/kpis.js +192 -0
  63. package/dist/lib/returnpro/templates.d.ts +48 -0
  64. package/dist/lib/returnpro/templates.js +229 -0
  65. package/dist/lib/returnpro/upload-income.d.ts +25 -0
  66. package/dist/lib/returnpro/upload-income.js +235 -0
  67. package/dist/lib/returnpro/upload-netsuite.d.ts +37 -0
  68. package/dist/lib/returnpro/upload-netsuite.js +566 -0
  69. package/dist/lib/returnpro/upload-r1.d.ts +48 -0
  70. package/dist/lib/returnpro/upload-r1.js +398 -0
  71. package/dist/lib/returnpro/validate.d.ts +37 -0
  72. package/dist/lib/returnpro/validate.js +124 -0
  73. package/dist/lib/social/meta.d.ts +90 -0
  74. package/dist/lib/social/meta.js +160 -0
  75. package/dist/lib/social/post-generator.d.ts +83 -0
  76. package/dist/lib/social/post-generator.js +333 -0
  77. package/dist/lib/social/publish.d.ts +66 -0
  78. package/dist/lib/social/publish.js +226 -0
  79. package/dist/lib/social/scraper.d.ts +67 -0
  80. package/dist/lib/social/scraper.js +361 -0
  81. package/dist/lib/supabase.d.ts +4 -0
  82. package/dist/lib/supabase.js +20 -0
  83. package/dist/lib/transactions/delete-batch.d.ts +60 -0
  84. package/dist/lib/transactions/delete-batch.js +203 -0
  85. package/dist/lib/transactions/ingest.d.ts +43 -0
  86. package/dist/lib/transactions/ingest.js +555 -0
  87. package/dist/lib/transactions/stamp.d.ts +51 -0
  88. package/dist/lib/transactions/stamp.js +524 -0
  89. package/package.json +3 -4
  90. package/bin/optimal.ts +0 -1731
  91. package/lib/assets/index.ts +0 -225
  92. package/lib/assets.ts +0 -124
  93. package/lib/auth/index.ts +0 -189
  94. package/lib/board/index.ts +0 -309
  95. package/lib/board/types.ts +0 -124
  96. package/lib/bot/claim.ts +0 -43
  97. package/lib/bot/coordinator.ts +0 -254
  98. package/lib/bot/heartbeat.ts +0 -37
  99. package/lib/bot/index.ts +0 -9
  100. package/lib/bot/protocol.ts +0 -99
  101. package/lib/bot/reporter.ts +0 -42
  102. package/lib/bot/skills.ts +0 -81
  103. package/lib/budget/projections.ts +0 -561
  104. package/lib/budget/scenarios.ts +0 -312
  105. package/lib/cms/publish-blog.ts +0 -129
  106. package/lib/cms/strapi-client.ts +0 -302
  107. package/lib/config/registry.ts +0 -228
  108. package/lib/config/schema.ts +0 -58
  109. package/lib/config.ts +0 -247
  110. package/lib/errors.ts +0 -129
  111. package/lib/format.ts +0 -120
  112. package/lib/infra/.gitkeep +0 -0
  113. package/lib/infra/deploy.ts +0 -70
  114. package/lib/infra/migrate.ts +0 -141
  115. package/lib/newsletter/.gitkeep +0 -0
  116. package/lib/newsletter/distribute.ts +0 -256
  117. package/lib/newsletter/generate.ts +0 -735
  118. package/lib/returnpro/.gitkeep +0 -0
  119. package/lib/returnpro/anomalies.ts +0 -258
  120. package/lib/returnpro/audit.ts +0 -194
  121. package/lib/returnpro/diagnose.ts +0 -400
  122. package/lib/returnpro/kpis.ts +0 -255
  123. package/lib/returnpro/templates.ts +0 -323
  124. package/lib/returnpro/upload-income.ts +0 -311
  125. package/lib/returnpro/upload-netsuite.ts +0 -696
  126. package/lib/returnpro/upload-r1.ts +0 -563
  127. package/lib/returnpro/validate.ts +0 -154
  128. package/lib/social/meta.ts +0 -228
  129. package/lib/social/post-generator.ts +0 -468
  130. package/lib/social/publish.ts +0 -301
  131. package/lib/social/scraper.ts +0 -503
  132. package/lib/supabase.ts +0 -25
  133. package/lib/transactions/delete-batch.ts +0 -258
  134. package/lib/transactions/ingest.ts +0 -659
  135. package/lib/transactions/stamp.ts +0 -654
@@ -1,141 +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
-
7
- const run = promisify(execFile)
8
-
9
- // ---------------------------------------------------------------------------
10
- // Types
11
- // ---------------------------------------------------------------------------
12
-
13
- export interface MigrateOptions {
14
- target: 'returnpro' | 'optimalos'
15
- dryRun?: boolean // if true, just show what would be pushed
16
- }
17
-
18
- export interface MigrateResult {
19
- success: boolean
20
- target: string
21
- output: string
22
- errors: string
23
- }
24
-
25
- // ---------------------------------------------------------------------------
26
- // Constants
27
- // ---------------------------------------------------------------------------
28
-
29
- /** Hardcoded project directories — these live on Carlos's machine. */
30
- const PROJECT_DIRS: Record<'returnpro' | 'optimalos', string> = {
31
- returnpro: '/home/optimal/dashboard-returnpro',
32
- optimalos: '/home/optimal/optimalos',
33
- }
34
-
35
- // ---------------------------------------------------------------------------
36
- // Helpers
37
- // ---------------------------------------------------------------------------
38
-
39
- function getProjectDir(target: 'returnpro' | 'optimalos'): string {
40
- return PROJECT_DIRS[target]
41
- }
42
-
43
- function migrationsDir(target: 'returnpro' | 'optimalos'): string {
44
- return join(getProjectDir(target), 'supabase', 'migrations')
45
- }
46
-
47
- /**
48
- * Generate a timestamp string in YYYYMMDDHHMMSS format (UTC).
49
- */
50
- function timestamp(): string {
51
- const now = new Date()
52
- const pad = (n: number, len = 2) => String(n).padStart(len, '0')
53
- return [
54
- now.getUTCFullYear(),
55
- pad(now.getUTCMonth() + 1),
56
- pad(now.getUTCDate()),
57
- pad(now.getUTCHours()),
58
- pad(now.getUTCMinutes()),
59
- pad(now.getUTCSeconds()),
60
- ].join('')
61
- }
62
-
63
- // ---------------------------------------------------------------------------
64
- // Public API
65
- // ---------------------------------------------------------------------------
66
-
67
- /**
68
- * Run `supabase db push --linked` (or `--dry-run` if requested) against the
69
- * given target project directory.
70
- *
71
- * Uses `execFile` (not `exec`) to avoid shell injection.
72
- * The `cwd` option switches the Supabase CLI into the correct project.
73
- */
74
- export async function migrateDb(opts: MigrateOptions): Promise<MigrateResult> {
75
- const { target, dryRun = false } = opts
76
- const projectDir = getProjectDir(target)
77
-
78
- const args: string[] = ['db', 'push', '--linked']
79
- if (dryRun) args.push('--dry-run')
80
-
81
- try {
82
- const { stdout, stderr } = await run('supabase', args, {
83
- cwd: projectDir,
84
- timeout: 120_000,
85
- })
86
- return {
87
- success: true,
88
- target,
89
- output: stdout.trim(),
90
- errors: stderr.trim(),
91
- }
92
- } catch (err: unknown) {
93
- const e = err as { stdout?: string; stderr?: string; message?: string }
94
- return {
95
- success: false,
96
- target,
97
- output: (e.stdout ?? '').trim(),
98
- errors: (e.stderr ?? e.message ?? String(err)).trim(),
99
- }
100
- }
101
- }
102
-
103
- /**
104
- * List migration `.sql` files in the target's `supabase/migrations/` directory,
105
- * sorted chronologically (by filename, which starts with a YYYYMMDDHHMMSS prefix).
106
- *
107
- * Returns only filenames, not full paths.
108
- */
109
- export async function listPendingMigrations(
110
- target: 'returnpro' | 'optimalos'
111
- ): Promise<string[]> {
112
- const dir = migrationsDir(target)
113
- const entries = await readdir(dir)
114
- return entries
115
- .filter((f) => f.endsWith('.sql'))
116
- .sort() // lexicographic == chronological given the YYYYMMDDHHMMSS prefix
117
- }
118
-
119
- /**
120
- * Create a new empty migration file in the target's `supabase/migrations/`
121
- * directory.
122
- *
123
- * The filename format is `{YYYYMMDDHHMMSS}_{name}.sql` (UTC timestamp).
124
- * Returns the full absolute path of the created file.
125
- */
126
- export async function createMigration(
127
- target: 'returnpro' | 'optimalos',
128
- name: string
129
- ): Promise<string> {
130
- const sanitized = name.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_]/g, '')
131
- const filename = `${timestamp()}_${sanitized}.sql`
132
- const fullPath = join(migrationsDir(target), filename)
133
-
134
- await writeFile(
135
- fullPath,
136
- `-- Migration: ${sanitized}\n-- Target: ${target}\n-- Created: ${new Date().toISOString()}\n\n`,
137
- { encoding: 'utf8' }
138
- )
139
-
140
- return fullPath
141
- }
File without changes
@@ -1,256 +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
-
12
- import 'dotenv/config'
13
- import { strapiGet, strapiPut } from '../cms/strapi-client.js'
14
-
15
- // ── Types ─────────────────────────────────────────────────────────────
16
-
17
- export interface DistributionResult {
18
- success: boolean
19
- documentId: string
20
- channel: string
21
- webhookResponse?: unknown
22
- error?: string
23
- }
24
-
25
- export interface DeliveryStatus {
26
- documentId: string
27
- delivery_status: string
28
- delivered_at: string | null
29
- recipients_count: number | null
30
- ghl_campaign_id: string | null
31
- delivery_errors: unknown[] | null
32
- }
33
-
34
- // Shape of the Strapi newsletter item's data fields we care about
35
- interface NewsletterData {
36
- documentId: string
37
- publishedAt?: string | null
38
- delivery_status?: string | null
39
- delivered_at?: string | null
40
- recipients_count?: number | null
41
- ghl_campaign_id?: string | null
42
- delivery_errors?: unknown[] | null
43
- brand?: string
44
- [key: string]: unknown
45
- }
46
-
47
- // ── Environment helpers ───────────────────────────────────────────────
48
-
49
- function getWebhookUrl(): string {
50
- const url = process.env.N8N_WEBHOOK_URL
51
- if (!url) {
52
- throw new Error(
53
- 'Missing env var: N8N_WEBHOOK_URL\n' +
54
- 'Set it to your n8n base URL, e.g. https://n8n.op-hub.com\n' +
55
- 'The distribute module will POST to: $N8N_WEBHOOK_URL/webhook/newsletter-distribute',
56
- )
57
- }
58
- return url.replace(/\/+$/, '')
59
- }
60
-
61
- // ── 1. Distribute Newsletter ──────────────────────────────────────────
62
-
63
- /**
64
- * Main orchestrator: fetch newsletter from Strapi, validate state,
65
- * update delivery_status to 'sending', trigger the n8n webhook,
66
- * and update tracking fields based on the result.
67
- *
68
- * @param documentId - Strapi documentId (UUID string) of the newsletter
69
- * @param opts.channel - Distribution channel. Defaults to 'all'.
70
- *
71
- * @example
72
- * const result = await distributeNewsletter('abc123-def456')
73
- * const emailOnly = await distributeNewsletter('abc123-def456', { channel: 'email' })
74
- */
75
- export async function distributeNewsletter(
76
- documentId: string,
77
- opts: { channel?: 'email' | 'all' } = {},
78
- ): Promise<DistributionResult> {
79
- const channel = opts.channel ?? 'all'
80
-
81
- // Step 1: Fetch the newsletter from Strapi
82
- let newsletter: NewsletterData
83
- try {
84
- const response = await strapiGet<{ data: NewsletterData }>(
85
- `/api/newsletters/${documentId}`,
86
- )
87
- newsletter = response.data
88
- } catch (err) {
89
- const msg = err instanceof Error ? err.message : String(err)
90
- return {
91
- success: false,
92
- documentId,
93
- channel,
94
- error: `Failed to fetch newsletter from Strapi: ${msg}`,
95
- }
96
- }
97
-
98
- // Step 2: Validate published state
99
- if (!newsletter.publishedAt) {
100
- return {
101
- success: false,
102
- documentId,
103
- channel,
104
- error:
105
- 'Newsletter is not published. Publish it in Strapi before distributing.',
106
- }
107
- }
108
-
109
- // Step 3: Validate delivery_status — only distribute if pending or unset
110
- const currentStatus = newsletter.delivery_status ?? 'pending'
111
- if (currentStatus !== 'pending' && currentStatus !== undefined) {
112
- if (
113
- currentStatus === 'sending' ||
114
- currentStatus === 'delivered' ||
115
- currentStatus === 'partial'
116
- ) {
117
- return {
118
- success: false,
119
- documentId,
120
- channel,
121
- error: `Newsletter already has delivery_status="${currentStatus}". Cannot re-distribute.`,
122
- }
123
- }
124
- }
125
-
126
- // Step 4: Mark as 'sending' in Strapi
127
- try {
128
- await strapiPut('/api/newsletters', documentId, {
129
- delivery_status: 'sending',
130
- })
131
- } catch (err) {
132
- const msg = err instanceof Error ? err.message : String(err)
133
- return {
134
- success: false,
135
- documentId,
136
- channel,
137
- error: `Failed to update delivery_status to 'sending': ${msg}`,
138
- }
139
- }
140
-
141
- // Step 5: Trigger n8n webhook
142
- const webhookUrl = getWebhookUrl()
143
- const webhookEndpoint = `${webhookUrl}/webhook/newsletter-distribute`
144
- const brand = typeof newsletter.brand === 'string' ? newsletter.brand : ''
145
-
146
- let webhookResponse: unknown
147
- try {
148
- const res = await fetch(webhookEndpoint, {
149
- method: 'POST',
150
- headers: { 'Content-Type': 'application/json' },
151
- body: JSON.stringify({ documentId, brand, channel }),
152
- })
153
-
154
- if (!res.ok) {
155
- let body: unknown
156
- try {
157
- body = await res.json()
158
- } catch {
159
- body = await res.text().catch(() => res.statusText)
160
- }
161
-
162
- // Webhook failed — update Strapi to 'failed' with error details
163
- const errorDetails = [
164
- { step: 'webhook', status: res.status, body },
165
- ]
166
- await strapiPut('/api/newsletters', documentId, {
167
- delivery_status: 'failed',
168
- delivery_errors: errorDetails,
169
- }).catch(() => {
170
- // Best-effort update — don't mask the original error
171
- })
172
-
173
- return {
174
- success: false,
175
- documentId,
176
- channel,
177
- error: `n8n webhook returned ${res.status}: ${JSON.stringify(body)}`,
178
- }
179
- }
180
-
181
- try {
182
- webhookResponse = await res.json()
183
- } catch {
184
- webhookResponse = { status: res.status }
185
- }
186
- } catch (err) {
187
- // Network/connection error
188
- const msg = err instanceof Error ? err.message : String(err)
189
- const errorDetails = [{ step: 'webhook', error: msg }]
190
-
191
- await strapiPut('/api/newsletters', documentId, {
192
- delivery_status: 'failed',
193
- delivery_errors: errorDetails,
194
- }).catch(() => {
195
- // Best-effort
196
- })
197
-
198
- return {
199
- success: false,
200
- documentId,
201
- channel,
202
- error: `n8n webhook request failed: ${msg}`,
203
- }
204
- }
205
-
206
- // Step 6: Webhook succeeded — return success
207
- // Note: n8n will update delivery_status to 'delivered' (or 'partial') via its
208
- // own Strapi PUT once it finishes sending. We don't update it here.
209
- return {
210
- success: true,
211
- documentId,
212
- channel,
213
- webhookResponse,
214
- }
215
- }
216
-
217
- // ── 2. Check Distribution Status ─────────────────────────────────────
218
-
219
- /**
220
- * Fetch the current delivery tracking fields for a newsletter from Strapi.
221
- *
222
- * @param documentId - Strapi documentId (UUID string) of the newsletter
223
- *
224
- * @example
225
- * const status = await checkDistributionStatus('abc123-def456')
226
- * console.log(status.delivery_status) // 'delivered'
227
- * console.log(status.recipients_count) // 847
228
- */
229
- export async function checkDistributionStatus(
230
- documentId: string,
231
- ): Promise<DeliveryStatus> {
232
- const response = await strapiGet<{ data: NewsletterData }>(
233
- `/api/newsletters/${documentId}`,
234
- )
235
-
236
- const data = response.data
237
-
238
- return {
239
- documentId,
240
- delivery_status: typeof data.delivery_status === 'string'
241
- ? data.delivery_status
242
- : 'pending',
243
- delivered_at: typeof data.delivered_at === 'string'
244
- ? data.delivered_at
245
- : null,
246
- recipients_count: typeof data.recipients_count === 'number'
247
- ? data.recipients_count
248
- : null,
249
- ghl_campaign_id: typeof data.ghl_campaign_id === 'string'
250
- ? data.ghl_campaign_id
251
- : null,
252
- delivery_errors: Array.isArray(data.delivery_errors)
253
- ? data.delivery_errors
254
- : null,
255
- }
256
- }