backend-manager 5.8.3 → 5.9.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/CHANGELOG.md +31 -0
- package/CLAUDE.md +1 -1
- package/docs/admin-post-route.md +1 -1
- package/docs/ghostii.md +85 -60
- package/docs/marketing-campaigns.md +2 -2
- package/package.json +1 -1
- package/src/manager/events/cron/daily/blog-auto-publisher.js +680 -0
- package/src/manager/libraries/content/feed-parser.js +1 -1
- package/src/manager/libraries/content/ghostii.js +3 -3
- package/src/manager/libraries/email/data/disposable-domains.json +28 -0
- package/src/manager/libraries/email/generators/lib/structure.js +5 -1
- package/src/manager/libraries/email/generators/newsletter.js +48 -46
- package/src/manager/routes/brand/get.js +1 -1
- package/templates/backend-manager-config.json +66 -70
- package/test/helpers/content/blog-auto-publisher.js +484 -0
- package/test/helpers/content/ghostii-feed-integration.js +22 -10
- package/test/helpers/content/ghostii-write-article.js +1 -1
- package/test/routes/test/usage.js +1 -1
- package/src/manager/events/cron/daily/ghostii-auto-publisher.js +0 -513
- package/test/helpers/content/ghostii-auto-publisher.js +0 -349
|
@@ -260,6 +260,7 @@
|
|
|
260
260
|
"675hosting.com",
|
|
261
261
|
"675hosting.net",
|
|
262
262
|
"675hosting.org",
|
|
263
|
+
"67899vip.com",
|
|
263
264
|
"681mail.com",
|
|
264
265
|
"689345.xyz",
|
|
265
266
|
"6974.click",
|
|
@@ -879,6 +880,8 @@
|
|
|
879
880
|
"badopsec.lol",
|
|
880
881
|
"badpotato.tk",
|
|
881
882
|
"bahlill.cfd",
|
|
883
|
+
"baicai1145.online",
|
|
884
|
+
"baicai1145.shop",
|
|
882
885
|
"baileybridge.org",
|
|
883
886
|
"baiyucraft.sbs",
|
|
884
887
|
"baizoto.shop",
|
|
@@ -1200,6 +1203,7 @@
|
|
|
1200
1203
|
"cantozil.com",
|
|
1201
1204
|
"canvect.com",
|
|
1202
1205
|
"caowo.online",
|
|
1206
|
+
"capct.id",
|
|
1203
1207
|
"capcud.shop",
|
|
1204
1208
|
"capcut.digital",
|
|
1205
1209
|
"capcutku.io",
|
|
@@ -1667,6 +1671,7 @@
|
|
|
1667
1671
|
"devsonline.studio",
|
|
1668
1672
|
"devthien.io.vn",
|
|
1669
1673
|
"deyom.com",
|
|
1674
|
+
"dfghjertyh.dynv6.net",
|
|
1670
1675
|
"dfyuih.eu.cc",
|
|
1671
1676
|
"dghdfjfd.eu.cc",
|
|
1672
1677
|
"dh372.top",
|
|
@@ -2656,6 +2661,7 @@
|
|
|
2656
2661
|
"gghjnvgfyjhk.top",
|
|
2657
2662
|
"ggmal.ml",
|
|
2658
2663
|
"ggrainn.com",
|
|
2664
|
+
"ggvendas.store",
|
|
2659
2665
|
"ggvk.ru",
|
|
2660
2666
|
"ggvk.store",
|
|
2661
2667
|
"ggxhunter.com",
|
|
@@ -3150,6 +3156,7 @@
|
|
|
3150
3156
|
"http.nyc.mn",
|
|
3151
3157
|
"httpsgreenwichmeantime.in",
|
|
3152
3158
|
"huage.me",
|
|
3159
|
+
"huangao.xyz",
|
|
3153
3160
|
"huangniu8.com",
|
|
3154
3161
|
"hubglee.com",
|
|
3155
3162
|
"hubhost.store",
|
|
@@ -3512,6 +3519,7 @@
|
|
|
3512
3519
|
"joseihorumon.info",
|
|
3513
3520
|
"josse.ltd",
|
|
3514
3521
|
"jourrapide.com",
|
|
3522
|
+
"joywavepoint.com",
|
|
3515
3523
|
"jp-ml.com",
|
|
3516
3524
|
"jpco.org",
|
|
3517
3525
|
"jrhmail.com",
|
|
@@ -3605,6 +3613,7 @@
|
|
|
3605
3613
|
"kenfern.com",
|
|
3606
3614
|
"kenhbanme.com",
|
|
3607
3615
|
"kenhphim.net",
|
|
3616
|
+
"kenvanharen.com",
|
|
3608
3617
|
"kerotu.com",
|
|
3609
3618
|
"ket-qua.org",
|
|
3610
3619
|
"ketua.id",
|
|
@@ -4111,6 +4120,7 @@
|
|
|
4111
4120
|
"mailjunk.gq",
|
|
4112
4121
|
"mailjunk.ml",
|
|
4113
4122
|
"mailjunk.tk",
|
|
4123
|
+
"mailku.co",
|
|
4114
4124
|
"mailllshop.website",
|
|
4115
4125
|
"maillog.uk",
|
|
4116
4126
|
"mailmagnet.co",
|
|
@@ -4192,6 +4202,7 @@
|
|
|
4192
4202
|
"mailtv.net",
|
|
4193
4203
|
"mailtv.tv",
|
|
4194
4204
|
"mailuniverse.co.uk",
|
|
4205
|
+
"mailvip.net",
|
|
4195
4206
|
"mailw.site",
|
|
4196
4207
|
"mailyan.my.id",
|
|
4197
4208
|
"mailzi.ru",
|
|
@@ -4570,6 +4581,7 @@
|
|
|
4570
4581
|
"muathegame.com",
|
|
4571
4582
|
"muchomail.com",
|
|
4572
4583
|
"mucincanon.com",
|
|
4584
|
+
"mudahmaxwin.com",
|
|
4573
4585
|
"muehlacker.tk",
|
|
4574
4586
|
"muell.icu",
|
|
4575
4587
|
"muell.io",
|
|
@@ -4686,6 +4698,7 @@
|
|
|
4686
4698
|
"narsub.shop",
|
|
4687
4699
|
"nashapoultryshop.com",
|
|
4688
4700
|
"naslazhdai.ru",
|
|
4701
|
+
"natachai.me",
|
|
4689
4702
|
"natange.org.uk",
|
|
4690
4703
|
"nationalgardeningclub.com",
|
|
4691
4704
|
"natuanal.com",
|
|
@@ -4860,6 +4873,7 @@
|
|
|
4860
4873
|
"notrnailinator.com",
|
|
4861
4874
|
"notsharingmy.info",
|
|
4862
4875
|
"novaemail.com",
|
|
4876
|
+
"novaprime.vip",
|
|
4863
4877
|
"novasub.click",
|
|
4864
4878
|
"novyrix.cfd",
|
|
4865
4879
|
"now.im",
|
|
@@ -4896,6 +4910,7 @@
|
|
|
4896
4910
|
"nwrlr.org",
|
|
4897
4911
|
"nwytg.com",
|
|
4898
4912
|
"nwytg.net",
|
|
4913
|
+
"nxdbxd.top",
|
|
4899
4914
|
"ny7.me",
|
|
4900
4915
|
"nyasan.com",
|
|
4901
4916
|
"nyfhk.com",
|
|
@@ -5599,6 +5614,7 @@
|
|
|
5599
5614
|
"rvneous.com",
|
|
5600
5615
|
"rwstatus.com",
|
|
5601
5616
|
"rwxsxnomo.top",
|
|
5617
|
+
"rxinai.com",
|
|
5602
5618
|
"rxs9.cn",
|
|
5603
5619
|
"rygel.infos.st",
|
|
5604
5620
|
"ryteto.me",
|
|
@@ -5865,6 +5881,7 @@
|
|
|
5865
5881
|
"smarttalent.pw",
|
|
5866
5882
|
"smarttickethub.com",
|
|
5867
5883
|
"smashmail.de",
|
|
5884
|
+
"smell.sbs",
|
|
5868
5885
|
"smellfear.com",
|
|
5869
5886
|
"smellrear.com",
|
|
5870
5887
|
"smellypotato.tk",
|
|
@@ -7351,6 +7368,17 @@
|
|
|
7351
7368
|
"yours.tools",
|
|
7352
7369
|
"yourspamgoesto.space",
|
|
7353
7370
|
"yourtube.ml",
|
|
7371
|
+
"youtube-com-watch-jtpdc8khnpi.accesscam.org",
|
|
7372
|
+
"youtube-com-watch-jtpdc8khnpi.bond",
|
|
7373
|
+
"youtube-com-watch-jtpdc8khnpi.cyou",
|
|
7374
|
+
"youtube-com-watch-jtpdc8khnpi.dedyn.io",
|
|
7375
|
+
"youtube-com-watch-jtpdc8khnpi.freeddns.org",
|
|
7376
|
+
"youtube-com-watch-jtpdc8khnpi.gleeze.com",
|
|
7377
|
+
"youtube-com-watch-jtpdc8khnpi.loseyourip.com",
|
|
7378
|
+
"youtube-com-watch-jtpdc8khnpi.mywire.org",
|
|
7379
|
+
"youtube-com-watch-jtpdc8khnpi.ooguy.com",
|
|
7380
|
+
"youtube-com-watch-jtpdc8khnpi.theworkpc.com",
|
|
7381
|
+
"youtube-com-watch-jtpdc8khnpi.webredirect.org",
|
|
7354
7382
|
"youxiang.dev",
|
|
7355
7383
|
"yroid.com",
|
|
7356
7384
|
"yrppwsekv.top",
|
|
@@ -211,6 +211,8 @@ async function generateStructure({ sources, brand, newsletterConfig, ai, assista
|
|
|
211
211
|
function buildClassicSystemPrompt(brand, config) {
|
|
212
212
|
const tone = config?.tone || 'professional';
|
|
213
213
|
const instructions = config?.instructions || '';
|
|
214
|
+
const links = config?.links || [];
|
|
215
|
+
const keywords = config?.keywords || [];
|
|
214
216
|
const taglineLine = brand?.tagline ? `\nTagline: ${brand.tagline}` : '';
|
|
215
217
|
const descriptionLine = brand?.description ? `\nDescription: ${brand.description}` : '';
|
|
216
218
|
|
|
@@ -218,6 +220,8 @@ function buildClassicSystemPrompt(brand, config) {
|
|
|
218
220
|
`You are a newsletter writer for ${brand?.name || 'a tech company'}.${taglineLine}${descriptionLine}`,
|
|
219
221
|
instructions ? `\nBrand instructions:\n${instructions}` : '',
|
|
220
222
|
`\nTone: ${tone}`,
|
|
223
|
+
keywords.length ? `\nKeywords to weave in naturally: ${keywords.join(', ')}` : '',
|
|
224
|
+
links.length ? `\nBrand links to incorporate where relevant: ${links.join(', ')}` : '',
|
|
221
225
|
'',
|
|
222
226
|
'You will be given a set of "source articles" — these are background research, NOT publications you are writing for or about.',
|
|
223
227
|
'Treat them as raw information. Synthesize the IDEAS into original content written as if you are the original author.',
|
|
@@ -246,7 +250,7 @@ function buildClassicSystemPrompt(brand, config) {
|
|
|
246
250
|
'- 3-5 sections — each is ONE topic, rewritten in your voice as original content',
|
|
247
251
|
'- Each section: title (compelling, scannable), body (80-150 words, markdown OK)',
|
|
248
252
|
'- Each section: image_prompt — one-sentence visual description for an illustrator. Be specific about subject/style.',
|
|
249
|
-
'- Do NOT
|
|
253
|
+
'- Do NOT invent URLs or "read more" links. The newsletter is a self-contained read. The ONLY URLs allowed in section bodies are the brand links listed above (if any) — weave them in naturally where relevant, do not force them.',
|
|
250
254
|
`- Signoff: a SHORT human sign-off, formatted as two lines with \\n between them. First line is a closing phrase like "Best,", "Cheers,", "Until next week,", or "Stay sharp,". Second line is the team name like "The ${brand?.name || 'Team'} Team". Example: "Best,\\nThe ${brand?.name || 'Team'} Team". Do NOT write a summary, tagline, motto, or thematic conclusion sentence — this is the literal way you sign off the email, like the end of a letter.`,
|
|
251
255
|
'- citations: array of { note, source } for any hard data referenced. Empty array if none.',
|
|
252
256
|
'',
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Pipeline:
|
|
9
9
|
* 1. Read newsletter categories from Manager.config.marketing.newsletter.content.categories
|
|
10
|
-
* 2. Fetch ready sources from parent server (
|
|
10
|
+
* 2. Fetch ready sources from parent server (no claiming — child tracks locally)
|
|
11
11
|
* 3. structure.js → AI authors subject, preheader, intro, sections, signoff
|
|
12
12
|
* 4. image-illustrator.js → AI generates one flat-vector PNG per section via
|
|
13
13
|
* gpt-image-2 (default). Set content.method.image = 'svg' to use the legacy
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* the markdown pipeline (content) if needed.
|
|
22
22
|
*/
|
|
23
23
|
const fetch = require('wonderful-fetch');
|
|
24
|
+
const powertools = require('node-powertools');
|
|
24
25
|
|
|
25
26
|
const { filterSources } = require('./lib/filter.js');
|
|
26
27
|
const { generateStructure } = require('./lib/structure.js');
|
|
@@ -41,6 +42,7 @@ const { renderMarkdown } = require('./lib/markdown-renderer.js');
|
|
|
41
42
|
const { uploadAssets, RAW_BASE } = require('./lib/image-host.js');
|
|
42
43
|
const { buildPublicConfig } = require('../../../routes/brand/get.js');
|
|
43
44
|
const { writeArticle, publishArticle } = require('../../../libraries/content/ghostii.js');
|
|
45
|
+
const { trackContentSource, contentSourceHash } = require('../../../events/cron/daily/blog-auto-publisher.js');
|
|
44
46
|
|
|
45
47
|
/**
|
|
46
48
|
* Generate newsletter content from parent server sources.
|
|
@@ -69,38 +71,52 @@ const { writeArticle, publishArticle } = require('../../../libraries/content/gho
|
|
|
69
71
|
*/
|
|
70
72
|
async function generate(Manager, assistant, settings, opts = {}) {
|
|
71
73
|
const newsletterRoleConfig = Manager.config?.marketing?.newsletter;
|
|
72
|
-
const
|
|
74
|
+
const rawContent = newsletterRoleConfig?.content;
|
|
73
75
|
|
|
74
76
|
if (!newsletterRoleConfig?.enabled) {
|
|
75
77
|
assistant.log('Newsletter generator: newsletter disabled in config');
|
|
76
78
|
return null;
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
if (!
|
|
81
|
+
if (!rawContent) {
|
|
80
82
|
assistant.log('Newsletter generator: no marketing.newsletter.content config block');
|
|
81
83
|
return null;
|
|
82
84
|
}
|
|
83
85
|
|
|
84
|
-
//
|
|
86
|
+
// Content can be an array or single object (matches blog config shape)
|
|
87
|
+
const contentArray = powertools.arrayify(rawContent);
|
|
88
|
+
const config = contentArray[0];
|
|
89
|
+
|
|
90
|
+
if (!config) {
|
|
91
|
+
assistant.log('Newsletter generator: empty content array');
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Default sources to ['$parent'] if not specified
|
|
96
|
+
const contentSources = config.sources || ['$parent'];
|
|
97
|
+
|
|
98
|
+
// Either use pre-fetched sources (iteration test) or resolve from config
|
|
85
99
|
let sources = opts.sources;
|
|
86
100
|
|
|
87
101
|
if (!sources) {
|
|
88
|
-
|
|
102
|
+
// For now, newsletter resolves $parent sources via the parent server
|
|
103
|
+
if (contentSources.includes('$parent')) {
|
|
104
|
+
const categories = config.categories || [];
|
|
89
105
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
106
|
+
if (!categories.length) {
|
|
107
|
+
assistant.log('Newsletter generator: no categories configured');
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
94
110
|
|
|
95
|
-
|
|
111
|
+
const parentUrl = Manager.getParentApiUrl();
|
|
96
112
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
113
|
+
if (!parentUrl) {
|
|
114
|
+
assistant.log('Newsletter generator: no parent URL configured');
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
101
117
|
|
|
102
|
-
|
|
103
|
-
|
|
118
|
+
sources = await fetchSources(parentUrl, categories, assistant);
|
|
119
|
+
}
|
|
104
120
|
}
|
|
105
121
|
|
|
106
122
|
if (!sources?.length) {
|
|
@@ -382,12 +398,20 @@ async function generate(Manager, assistant, settings, opts = {}) {
|
|
|
382
398
|
});
|
|
383
399
|
}
|
|
384
400
|
|
|
385
|
-
// 4.
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
401
|
+
// 4. Track all sources in the unified content-sources collection
|
|
402
|
+
const admin = Manager.libraries?.admin;
|
|
403
|
+
if (admin) {
|
|
404
|
+
for (const source of sources) {
|
|
405
|
+
await trackContentSource(admin, {
|
|
406
|
+
url: source.url || source.id,
|
|
407
|
+
origin: '$parent',
|
|
408
|
+
itemId: source.id,
|
|
409
|
+
itemTitle: source.title || '',
|
|
410
|
+
usedBy: 'newsletter',
|
|
411
|
+
brandId: brand?.id || '',
|
|
412
|
+
}).catch((e) => {
|
|
413
|
+
assistant.error(`Newsletter generator: Error tracking content source (non-fatal): ${e.message}`);
|
|
414
|
+
});
|
|
391
415
|
}
|
|
392
416
|
}
|
|
393
417
|
|
|
@@ -675,7 +699,7 @@ function aggregateTotals(filterMeta, structureMeta, images) {
|
|
|
675
699
|
/**
|
|
676
700
|
* Fetch ready newsletter sources from the parent server.
|
|
677
701
|
*/
|
|
678
|
-
async function fetchSources(parentUrl, categories,
|
|
702
|
+
async function fetchSources(parentUrl, categories, assistant) {
|
|
679
703
|
const allSources = [];
|
|
680
704
|
|
|
681
705
|
for (const category of categories) {
|
|
@@ -687,7 +711,6 @@ async function fetchSources(parentUrl, categories, brandId, assistant) {
|
|
|
687
711
|
query: {
|
|
688
712
|
category,
|
|
689
713
|
limit: 3,
|
|
690
|
-
claimFor: brandId,
|
|
691
714
|
backendManagerKey: process.env.BACKEND_MANAGER_KEY,
|
|
692
715
|
},
|
|
693
716
|
});
|
|
@@ -703,27 +726,6 @@ async function fetchSources(parentUrl, categories, brandId, assistant) {
|
|
|
703
726
|
return allSources;
|
|
704
727
|
}
|
|
705
728
|
|
|
706
|
-
/**
|
|
707
|
-
* Mark sources as used on the parent server.
|
|
708
|
-
*/
|
|
709
|
-
async function claimSources(parentUrl, sources, brandId, assistant) {
|
|
710
|
-
for (const source of sources) {
|
|
711
|
-
try {
|
|
712
|
-
await fetch(`${parentUrl}/newsletter-sources`, {
|
|
713
|
-
method: 'put',
|
|
714
|
-
response: 'json',
|
|
715
|
-
timeout: 60000,
|
|
716
|
-
body: {
|
|
717
|
-
id: source.id,
|
|
718
|
-
usedBy: brandId || 'unknown',
|
|
719
|
-
backendManagerKey: process.env.BACKEND_MANAGER_KEY,
|
|
720
|
-
},
|
|
721
|
-
});
|
|
722
|
-
} catch (e) {
|
|
723
|
-
assistant.error(`Newsletter generator: Failed to claim source ${source.id}: ${e.message}`);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
729
|
|
|
728
730
|
/**
|
|
729
731
|
* Generate a 20-character Firebase push ID (RTDB-style).
|
|
@@ -776,4 +778,4 @@ function generatePushId() {
|
|
|
776
778
|
return id;
|
|
777
779
|
}
|
|
778
780
|
|
|
779
|
-
module.exports = { generate, fetchSources,
|
|
781
|
+
module.exports = { generate, fetchSources, generatePushId };
|
|
@@ -10,7 +10,7 @@ module.exports = async ({ assistant, Manager }) => {
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Build a public-safe config object from Manager.config
|
|
13
|
-
* Excludes sensitive fields: sentry, analytics,
|
|
13
|
+
* Excludes sensitive fields: sentry, analytics, blog, etc.
|
|
14
14
|
*/
|
|
15
15
|
function buildPublicConfig(config) {
|
|
16
16
|
return {
|
|
@@ -146,41 +146,29 @@
|
|
|
146
146
|
enabled: false,
|
|
147
147
|
platform: 'beehiiv',
|
|
148
148
|
publicationId: '', // Beehiiv publication ID (e.g., 'pub_xxxxx'). Populated by OMEGA's beehiiv/ensure/publication.js. Required for marketing sync to Beehiiv.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
sponsorships: [
|
|
173
|
-
// {
|
|
174
|
-
// label: 'Sponsored', // optional eyebrow label (defaults to "Sponsored")
|
|
175
|
-
// headline: 'Grow your audience faster', // bold headline
|
|
176
|
-
// body: 'Short pitch text…', // 1-2 sentence body
|
|
177
|
-
// url: 'https://somiibo.com/promo', // click destination
|
|
178
|
-
// image: 'https://...png', // optional image URL
|
|
179
|
-
// ctaLabel: 'Try Somiibo', // button text (default: "Learn more")
|
|
180
|
-
// position: 'middle', // 'top' | 'middle' | 'end' (default: 'middle')
|
|
181
|
-
// },
|
|
182
|
-
],
|
|
183
|
-
},
|
|
149
|
+
// Content: single object or array (matches blog config shape). Generator uses the first entry.
|
|
150
|
+
content: [
|
|
151
|
+
{
|
|
152
|
+
sources: ['$parent'], // '$parent' | '$feed:https://...' | '$brand'
|
|
153
|
+
categories: [], // e.g., ['social-media', 'marketing'] — topic constraints + AI categorization
|
|
154
|
+
links: [], // links to inject into generated content
|
|
155
|
+
instructions: '', // free-form text passed to the AI
|
|
156
|
+
tone: 'professional', // 'professional' | 'casual' | 'actionable' | 'witty' | etc.
|
|
157
|
+
keywords: [], // SEO keywords to weave in
|
|
158
|
+
template: 'clean', // 'clean' | 'editorial' | 'field-report' — layout template
|
|
159
|
+
article: {
|
|
160
|
+
enabled: false, // expand lead section into a full blog post via Ghostii
|
|
161
|
+
author: null,
|
|
162
|
+
},
|
|
163
|
+
theme: {
|
|
164
|
+
primaryColor: '#5B5BFF',
|
|
165
|
+
secondaryColor: '#1E1E2A',
|
|
166
|
+
accentColor: '#F6F7FB',
|
|
167
|
+
font: 'Inter, system-ui, sans-serif',
|
|
168
|
+
},
|
|
169
|
+
sponsorships: [],
|
|
170
|
+
},
|
|
171
|
+
],
|
|
184
172
|
},
|
|
185
173
|
prune: {
|
|
186
174
|
enabled: true,
|
|
@@ -195,48 +183,56 @@
|
|
|
195
183
|
appId: '1:123:web:456',
|
|
196
184
|
measurementId: 'G-0123456789',
|
|
197
185
|
},
|
|
198
|
-
// Standalone
|
|
199
|
-
//
|
|
200
|
-
//
|
|
186
|
+
// Standalone blog auto-publisher (daily cron). OPT-IN: disabled by default.
|
|
187
|
+
// Set enabled: true and quantity >= 1 to auto-publish independent blog posts.
|
|
188
|
+
// For newsletter-linked articles, use marketing.newsletter.content.article.enabled instead.
|
|
189
|
+
//
|
|
190
|
+
// `platform` selects the article-generation provider (only 'ghostii' for now).
|
|
201
191
|
//
|
|
202
192
|
// Source types:
|
|
203
|
-
// '$
|
|
193
|
+
// '$brand' — generic brand-topic generation
|
|
204
194
|
// '$feed:https://example.com/feed' — RSS/Atom/JSON feed: pick one unprocessed article per run
|
|
195
|
+
// '$parent' — fetch sources from parent server's source pool (without claiming)
|
|
205
196
|
// 'https://example.com/page' — fetch URL content as prompt seed
|
|
206
197
|
// 'Write about topic X' — text: use directly as prompt seed
|
|
207
198
|
//
|
|
208
|
-
//
|
|
209
|
-
// is never reprocessed. When a feed is unreachable or exhausted, falls back to $
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// '$feed:https
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
199
|
+
// All feed/parent sources are tracked in Firestore (`content-sources`) so the same
|
|
200
|
+
// article is never reprocessed. When a feed is unreachable or exhausted, falls back to $brand.
|
|
201
|
+
blog: {
|
|
202
|
+
enabled: false,
|
|
203
|
+
platform: 'ghostii',
|
|
204
|
+
content: [
|
|
205
|
+
{
|
|
206
|
+
sources: [ // '$brand' | '$feed:https://...' | '$parent'
|
|
207
|
+
'$brand',
|
|
208
|
+
// '$feed:https://techcrunch.com/feed/',
|
|
209
|
+
// '$parent',
|
|
210
|
+
],
|
|
211
|
+
categories: [], // topic constraints + output tags
|
|
212
|
+
links: [], // links to inject into generated content
|
|
213
|
+
instructions: '', // free-form text passed to the AI
|
|
214
|
+
tone: 'professional', // 'professional' | 'casual' | 'actionable' | 'witty' | etc.
|
|
215
|
+
keywords: [], // SEO keywords to weave in
|
|
216
|
+
quantity: 0, // number of articles to generate per run
|
|
217
|
+
chance: 1.0,
|
|
218
|
+
author: null,
|
|
219
|
+
postPath: 'ghostii', // defaults to platform name; sub-folder under _posts/{year}/
|
|
220
|
+
// Per-entry Ghostii API overrides (all optional, fall back to framework defaults)
|
|
221
|
+
overrides: {
|
|
222
|
+
// keywords: ['AI', 'technology'],
|
|
223
|
+
// length: 'long', // short | medium | long | comprehensive
|
|
224
|
+
// research: true, // web search for real external links
|
|
225
|
+
// insertImages: true, // section images from Unsplash
|
|
226
|
+
// headerImageUrl: 'unsplash', // disabled | unsplash | generate
|
|
227
|
+
// maxLinks: 6,
|
|
228
|
+
// sectionQuantity: 5,
|
|
229
|
+
// feedUrl: '', // brand blog feed for title dedup
|
|
230
|
+
},
|
|
231
|
+
// brand: 'other-app-id', // Optional: target a different brand
|
|
232
|
+
// brandUrl: 'https://api.otherapp.com', // Required if brand is set
|
|
235
233
|
},
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
],
|
|
234
|
+
],
|
|
235
|
+
},
|
|
240
236
|
reviews: {
|
|
241
237
|
enabled: true,
|
|
242
238
|
sites: [
|