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,301 +0,0 @@
1
- /**
2
- * Social Post Publisher
3
- *
4
- * Handles publishing social posts from Strapi to platforms via n8n webhooks,
5
- * with delivery status tracking written back to Strapi.
6
- *
7
- * Functions:
8
- * publishSocialPosts() — Main orchestrator: fetch pending posts, publish to Strapi,
9
- * trigger n8n webhook, update delivery_status
10
- * getPublishQueue() — List posts ready to publish (pending + has scheduled_date)
11
- * retryFailed() — Re-attempt posts with delivery_status = 'failed'
12
- */
13
-
14
- import 'dotenv/config'
15
- import {
16
- strapiGet,
17
- strapiPut,
18
- publish,
19
- type StrapiPage,
20
- type StrapiItem,
21
- } from '../cms/strapi-client.js'
22
-
23
- // ── Types ─────────────────────────────────────────────────────────────
24
-
25
- export interface PublishOptions {
26
- brand: string
27
- /** Max posts to publish (default: all pending) */
28
- limit?: number
29
- /** Preview without actually publishing */
30
- dryRun?: boolean
31
- }
32
-
33
- export interface QueuedPost {
34
- documentId: string
35
- headline: string
36
- platform: string
37
- brand: string
38
- scheduled_date: string
39
- }
40
-
41
- export interface PublishResult {
42
- published: number
43
- failed: number
44
- skipped: number
45
- details: Array<{
46
- documentId: string
47
- headline: string
48
- status: 'published' | 'failed' | 'skipped'
49
- error?: string
50
- }>
51
- }
52
-
53
- // ── Config ────────────────────────────────────────────────────────────
54
-
55
- function getN8nWebhookUrl(): string {
56
- const url = process.env.N8N_WEBHOOK_URL
57
- if (!url) {
58
- throw new Error(
59
- 'Missing env var: N8N_WEBHOOK_URL\n' +
60
- 'Set it in your .env file, e.g.:\n' +
61
- ' N8N_WEBHOOK_URL=https://n8n.op-hub.com',
62
- )
63
- }
64
- return url.replace(/\/+$/, '')
65
- }
66
-
67
- // ── Internal helpers ──────────────────────────────────────────────────
68
-
69
- /** Trigger n8n webhook for a single social post */
70
- async function triggerN8nWebhook(
71
- documentId: string,
72
- platform: string,
73
- brand: string,
74
- ): Promise<void> {
75
- const baseUrl = getN8nWebhookUrl()
76
- const webhookUrl = `${baseUrl}/webhook/social-post-publish`
77
-
78
- const res = await fetch(webhookUrl, {
79
- method: 'POST',
80
- headers: { 'Content-Type': 'application/json' },
81
- body: JSON.stringify({ documentId, platform, brand }),
82
- })
83
-
84
- if (!res.ok) {
85
- let detail = `HTTP ${res.status}: ${res.statusText}`
86
- try {
87
- const body = await res.text()
88
- if (body) detail += ` — ${body.slice(0, 200)}`
89
- } catch {
90
- // non-text body, ignore
91
- }
92
- throw new Error(`n8n webhook failed: ${detail}`)
93
- }
94
- }
95
-
96
- /** Fetch social posts by brand + delivery_status from Strapi */
97
- async function fetchPostsByStatus(
98
- brand: string,
99
- deliveryStatus: 'pending' | 'failed',
100
- ): Promise<StrapiItem[]> {
101
- const result = await strapiGet<StrapiPage>('/api/social-posts', {
102
- 'filters[brand][$eq]': brand,
103
- 'filters[delivery_status][$eq]': deliveryStatus,
104
- 'sort': 'scheduled_date:asc',
105
- 'pagination[pageSize]': '250',
106
- })
107
- return result.data
108
- }
109
-
110
- /** Process a single post: publish in Strapi, trigger n8n, update status */
111
- async function processPost(
112
- post: StrapiItem,
113
- dryRun: boolean,
114
- ): Promise<{ status: 'published' | 'failed' | 'skipped'; error?: string }> {
115
- const documentId = post.documentId
116
- const headline = (post.headline as string | undefined) ?? '(no headline)'
117
- const platform = (post.platform as string | undefined) ?? 'unknown'
118
- const brand = (post.brand as string | undefined) ?? 'unknown'
119
-
120
- if (dryRun) {
121
- return { status: 'skipped' }
122
- }
123
-
124
- try {
125
- // Step 1: Publish in Strapi (set publishedAt)
126
- await publish('social-posts', documentId)
127
-
128
- // Step 2: Trigger n8n webhook
129
- try {
130
- await triggerN8nWebhook(documentId, platform, brand)
131
- } catch (webhookErr) {
132
- // Webhook failure: mark failed, but don't rethrow — continue to next post
133
- const errMsg = webhookErr instanceof Error ? webhookErr.message : String(webhookErr)
134
- await strapiPut('/api/social-posts', documentId, {
135
- delivery_status: 'failed',
136
- delivery_errors: [{ timestamp: new Date().toISOString(), error: errMsg }],
137
- })
138
- return { status: 'failed', error: errMsg }
139
- }
140
-
141
- // Step 3: Update delivery_status to 'scheduled' on success
142
- await strapiPut('/api/social-posts', documentId, {
143
- delivery_status: 'scheduled',
144
- })
145
-
146
- return { status: 'published' }
147
- } catch (err) {
148
- const errMsg = err instanceof Error ? err.message : String(err)
149
- // Best-effort status update on unexpected errors
150
- try {
151
- await strapiPut('/api/social-posts', documentId, {
152
- delivery_status: 'failed',
153
- delivery_errors: [{ timestamp: new Date().toISOString(), error: errMsg }],
154
- })
155
- } catch {
156
- // Ignore secondary failure — original error is more important
157
- }
158
- return { status: 'failed', error: errMsg }
159
- }
160
- }
161
-
162
- // ── Core orchestrator ─────────────────────────────────────────────────
163
-
164
- /**
165
- * Fetch pending social posts for a brand and publish them:
166
- * 1. Publish in Strapi (set publishedAt)
167
- * 2. Trigger n8n webhook
168
- * 3. Update delivery_status to 'scheduled' (or 'failed' on error)
169
- *
170
- * @example
171
- * const result = await publishSocialPosts({ brand: 'LIFEINSUR', limit: 3 })
172
- * console.log(`Published: ${result.published}, Failed: ${result.failed}`)
173
- */
174
- export async function publishSocialPosts(
175
- opts: PublishOptions,
176
- ): Promise<PublishResult> {
177
- const { brand, limit, dryRun = false } = opts
178
-
179
- // Validate n8n URL up front (unless dry run)
180
- if (!dryRun) {
181
- getN8nWebhookUrl()
182
- }
183
-
184
- const posts = await fetchPostsByStatus(brand, 'pending')
185
- const postsToProcess = limit !== undefined ? posts.slice(0, limit) : posts
186
-
187
- const result: PublishResult = {
188
- published: 0,
189
- failed: 0,
190
- skipped: 0,
191
- details: [],
192
- }
193
-
194
- for (const post of postsToProcess) {
195
- const documentId = post.documentId
196
- const headline = (post.headline as string | undefined) ?? '(no headline)'
197
-
198
- const outcome = await processPost(post, dryRun)
199
-
200
- if (outcome.status === 'published') result.published++
201
- else if (outcome.status === 'failed') result.failed++
202
- else result.skipped++
203
-
204
- result.details.push({
205
- documentId,
206
- headline,
207
- status: outcome.status,
208
- ...(outcome.error !== undefined && { error: outcome.error }),
209
- })
210
- }
211
-
212
- return result
213
- }
214
-
215
- // ── Publish queue ─────────────────────────────────────────────────────
216
-
217
- /**
218
- * List posts ready to publish: delivery_status = 'pending' AND has a scheduled_date.
219
- *
220
- * @example
221
- * const queue = await getPublishQueue('LIFEINSUR')
222
- * queue.forEach(p => console.log(p.scheduled_date, p.headline))
223
- */
224
- export async function getPublishQueue(brand: string): Promise<QueuedPost[]> {
225
- const posts = await fetchPostsByStatus(brand, 'pending')
226
-
227
- return posts
228
- .filter((post) => {
229
- const scheduledDate = post.scheduled_date as string | null | undefined
230
- return scheduledDate != null && scheduledDate !== ''
231
- })
232
- .map((post) => ({
233
- documentId: post.documentId,
234
- headline: (post.headline as string | undefined) ?? '(no headline)',
235
- platform: (post.platform as string | undefined) ?? 'unknown',
236
- brand: (post.brand as string | undefined) ?? brand,
237
- scheduled_date: post.scheduled_date as string,
238
- }))
239
- }
240
-
241
- // ── Retry failed ──────────────────────────────────────────────────────
242
-
243
- /**
244
- * Re-attempt publishing posts with delivery_status = 'failed'.
245
- * Resets delivery_status to 'pending' on each post before re-processing.
246
- *
247
- * @example
248
- * const result = await retryFailed('LIFEINSUR')
249
- * console.log(`Re-published: ${result.published}, Still failing: ${result.failed}`)
250
- */
251
- export async function retryFailed(brand: string): Promise<PublishResult> {
252
- // Validate n8n URL up front
253
- getN8nWebhookUrl()
254
-
255
- const posts = await fetchPostsByStatus(brand, 'failed')
256
-
257
- const result: PublishResult = {
258
- published: 0,
259
- failed: 0,
260
- skipped: 0,
261
- details: [],
262
- }
263
-
264
- for (const post of posts) {
265
- const documentId = post.documentId
266
- const headline = (post.headline as string | undefined) ?? '(no headline)'
267
-
268
- // Reset to pending so processPost can re-publish cleanly
269
- try {
270
- await strapiPut('/api/social-posts', documentId, {
271
- delivery_status: 'pending',
272
- delivery_errors: null,
273
- })
274
- } catch (err) {
275
- const errMsg = err instanceof Error ? err.message : String(err)
276
- result.failed++
277
- result.details.push({
278
- documentId,
279
- headline,
280
- status: 'failed',
281
- error: `Could not reset delivery_status: ${errMsg}`,
282
- })
283
- continue
284
- }
285
-
286
- const outcome = await processPost(post, false)
287
-
288
- if (outcome.status === 'published') result.published++
289
- else if (outcome.status === 'failed') result.failed++
290
- else result.skipped++
291
-
292
- result.details.push({
293
- documentId,
294
- headline,
295
- status: outcome.status,
296
- ...(outcome.error !== undefined && { error: outcome.error }),
297
- })
298
- }
299
-
300
- return result
301
- }