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.
- package/dist/bin/optimal.d.ts +2 -0
- package/dist/bin/optimal.js +1590 -0
- package/dist/lib/assets/index.d.ts +79 -0
- package/dist/lib/assets/index.js +153 -0
- package/dist/lib/assets.d.ts +20 -0
- package/dist/lib/assets.js +112 -0
- package/dist/lib/auth/index.d.ts +83 -0
- package/dist/lib/auth/index.js +146 -0
- package/dist/lib/board/index.d.ts +39 -0
- package/dist/lib/board/index.js +285 -0
- package/dist/lib/board/types.d.ts +111 -0
- package/dist/lib/board/types.js +1 -0
- package/dist/lib/bot/claim.d.ts +3 -0
- package/dist/lib/bot/claim.js +20 -0
- package/dist/lib/bot/coordinator.d.ts +27 -0
- package/dist/lib/bot/coordinator.js +178 -0
- package/dist/lib/bot/heartbeat.d.ts +6 -0
- package/dist/lib/bot/heartbeat.js +30 -0
- package/dist/lib/bot/index.d.ts +9 -0
- package/dist/lib/bot/index.js +6 -0
- package/dist/lib/bot/protocol.d.ts +12 -0
- package/dist/lib/bot/protocol.js +74 -0
- package/dist/lib/bot/reporter.d.ts +3 -0
- package/dist/lib/bot/reporter.js +27 -0
- package/dist/lib/bot/skills.d.ts +26 -0
- package/dist/lib/bot/skills.js +69 -0
- package/dist/lib/budget/projections.d.ts +115 -0
- package/dist/lib/budget/projections.js +384 -0
- package/dist/lib/budget/scenarios.d.ts +93 -0
- package/dist/lib/budget/scenarios.js +214 -0
- package/dist/lib/cms/publish-blog.d.ts +62 -0
- package/dist/lib/cms/publish-blog.js +74 -0
- package/dist/lib/cms/strapi-client.d.ts +123 -0
- package/dist/lib/cms/strapi-client.js +213 -0
- package/dist/lib/config/registry.d.ts +17 -0
- package/dist/lib/config/registry.js +182 -0
- package/dist/lib/config/schema.d.ts +31 -0
- package/dist/lib/config/schema.js +25 -0
- package/dist/lib/config.d.ts +55 -0
- package/dist/lib/config.js +206 -0
- package/dist/lib/errors.d.ts +25 -0
- package/dist/lib/errors.js +91 -0
- package/dist/lib/format.d.ts +28 -0
- package/dist/lib/format.js +98 -0
- package/dist/lib/infra/deploy.d.ts +29 -0
- package/dist/lib/infra/deploy.js +58 -0
- package/dist/lib/infra/migrate.d.ts +34 -0
- package/dist/lib/infra/migrate.js +103 -0
- package/dist/lib/newsletter/distribute.d.ts +52 -0
- package/dist/lib/newsletter/distribute.js +193 -0
- package/{lib/newsletter/generate-insurance.ts → dist/lib/newsletter/generate-insurance.d.ts} +7 -24
- package/dist/lib/newsletter/generate-insurance.js +36 -0
- package/dist/lib/newsletter/generate.d.ts +104 -0
- package/dist/lib/newsletter/generate.js +571 -0
- package/dist/lib/returnpro/anomalies.d.ts +64 -0
- package/dist/lib/returnpro/anomalies.js +166 -0
- package/dist/lib/returnpro/audit.d.ts +32 -0
- package/dist/lib/returnpro/audit.js +147 -0
- package/dist/lib/returnpro/diagnose.d.ts +52 -0
- package/dist/lib/returnpro/diagnose.js +281 -0
- package/dist/lib/returnpro/kpis.d.ts +32 -0
- package/dist/lib/returnpro/kpis.js +192 -0
- package/dist/lib/returnpro/templates.d.ts +48 -0
- package/dist/lib/returnpro/templates.js +229 -0
- package/dist/lib/returnpro/upload-income.d.ts +25 -0
- package/dist/lib/returnpro/upload-income.js +235 -0
- package/dist/lib/returnpro/upload-netsuite.d.ts +37 -0
- package/dist/lib/returnpro/upload-netsuite.js +566 -0
- package/dist/lib/returnpro/upload-r1.d.ts +48 -0
- package/dist/lib/returnpro/upload-r1.js +398 -0
- package/dist/lib/returnpro/validate.d.ts +37 -0
- package/dist/lib/returnpro/validate.js +124 -0
- package/dist/lib/social/meta.d.ts +90 -0
- package/dist/lib/social/meta.js +160 -0
- package/dist/lib/social/post-generator.d.ts +83 -0
- package/dist/lib/social/post-generator.js +333 -0
- package/dist/lib/social/publish.d.ts +66 -0
- package/dist/lib/social/publish.js +226 -0
- package/dist/lib/social/scraper.d.ts +67 -0
- package/dist/lib/social/scraper.js +361 -0
- package/dist/lib/supabase.d.ts +4 -0
- package/dist/lib/supabase.js +20 -0
- package/dist/lib/transactions/delete-batch.d.ts +60 -0
- package/dist/lib/transactions/delete-batch.js +203 -0
- package/dist/lib/transactions/ingest.d.ts +43 -0
- package/dist/lib/transactions/ingest.js +555 -0
- package/dist/lib/transactions/stamp.d.ts +51 -0
- package/dist/lib/transactions/stamp.js +524 -0
- package/package.json +3 -4
- package/bin/optimal.ts +0 -1731
- package/lib/assets/index.ts +0 -225
- package/lib/assets.ts +0 -124
- package/lib/auth/index.ts +0 -189
- package/lib/board/index.ts +0 -309
- package/lib/board/types.ts +0 -124
- package/lib/bot/claim.ts +0 -43
- package/lib/bot/coordinator.ts +0 -254
- package/lib/bot/heartbeat.ts +0 -37
- package/lib/bot/index.ts +0 -9
- package/lib/bot/protocol.ts +0 -99
- package/lib/bot/reporter.ts +0 -42
- package/lib/bot/skills.ts +0 -81
- package/lib/budget/projections.ts +0 -561
- package/lib/budget/scenarios.ts +0 -312
- package/lib/cms/publish-blog.ts +0 -129
- package/lib/cms/strapi-client.ts +0 -302
- package/lib/config/registry.ts +0 -228
- package/lib/config/schema.ts +0 -58
- package/lib/config.ts +0 -247
- package/lib/errors.ts +0 -129
- package/lib/format.ts +0 -120
- package/lib/infra/.gitkeep +0 -0
- package/lib/infra/deploy.ts +0 -70
- package/lib/infra/migrate.ts +0 -141
- package/lib/newsletter/.gitkeep +0 -0
- package/lib/newsletter/distribute.ts +0 -256
- package/lib/newsletter/generate.ts +0 -735
- package/lib/returnpro/.gitkeep +0 -0
- package/lib/returnpro/anomalies.ts +0 -258
- package/lib/returnpro/audit.ts +0 -194
- package/lib/returnpro/diagnose.ts +0 -400
- package/lib/returnpro/kpis.ts +0 -255
- package/lib/returnpro/templates.ts +0 -323
- package/lib/returnpro/upload-income.ts +0 -311
- package/lib/returnpro/upload-netsuite.ts +0 -696
- package/lib/returnpro/upload-r1.ts +0 -563
- package/lib/returnpro/validate.ts +0 -154
- package/lib/social/meta.ts +0 -228
- package/lib/social/post-generator.ts +0 -468
- package/lib/social/publish.ts +0 -301
- package/lib/social/scraper.ts +0 -503
- package/lib/supabase.ts +0 -25
- package/lib/transactions/delete-batch.ts +0 -258
- package/lib/transactions/ingest.ts +0 -659
- package/lib/transactions/stamp.ts +0 -654
package/lib/social/publish.ts
DELETED
|
@@ -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
|
-
}
|