@ibalzam/codejitsu-core 0.11.0 → 0.11.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/modules/blog-writer/BLOG_BATCH.md +15 -6
- package/modules/blog-writer/BLOG_WRITING.md +57 -24
- package/modules/blog-writer/CLAUDE.md +21 -0
- package/modules/cli/src/blog-init.mjs +37 -5
- package/modules/config/src/types.d.ts +57 -13
- package/modules/config/src/types.ts +57 -13
- package/package.json +1 -1
|
@@ -10,19 +10,28 @@
|
|
|
10
10
|
|
|
11
11
|
## Step 0 — Read site config
|
|
12
12
|
|
|
13
|
-
Same as `BLOG_WRITING.md` Step 0: load `codejitsu.config.ts.blogWriter`.
|
|
13
|
+
Same as `BLOG_WRITING.md` Step 0: load `codejitsu.config.ts.blogWriter`. If
|
|
14
|
+
missing, STOP with the same error message.
|
|
14
15
|
|
|
15
16
|
Also read:
|
|
16
|
-
- `src/content/blog/` — existing posts (find
|
|
17
|
+
- `src/content/blog/` — existing posts (find latest `pubDate`)
|
|
17
18
|
- `src/content.config.ts` — confirm the schema shape
|
|
18
19
|
|
|
19
20
|
## Step 1 — Decide cadence
|
|
20
21
|
|
|
21
|
-
Default cadence is
|
|
22
|
-
veteran). If existing posts have a different rhythm, infer from the last
|
|
23
|
-
10 and match it.
|
|
22
|
+
Default cadence is **`blogWriter.cadenceDays` from config** (kit default: 4).
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
Cadence inference rules (in order):
|
|
25
|
+
1. If `blogWriter.cadenceDays` is set in config → use it (don't infer).
|
|
26
|
+
2. Else if `≥ 5` existing posts with future `pubDate` exist → measure gaps
|
|
27
|
+
between their consecutive dates. If the standard deviation of gaps is
|
|
28
|
+
`≤ 30%` of the median gap → use the median.
|
|
29
|
+
3. Else (irregular spacing, e.g. WordPress import dates) → fall back to **4 days**.
|
|
30
|
+
|
|
31
|
+
Starting date = `max(latest existing pubDate, today) + cadence`.
|
|
32
|
+
|
|
33
|
+
If you fall back to default because of irregular existing dates, **mention it
|
|
34
|
+
to the user** before continuing — they may want to override.
|
|
26
35
|
|
|
27
36
|
## Step 2 — Generate N topics
|
|
28
37
|
|
|
@@ -8,21 +8,43 @@
|
|
|
8
8
|
## Step 0 — Read site config
|
|
9
9
|
|
|
10
10
|
Open `codejitsu.config.ts` at the site root and locate the `blogWriter` block.
|
|
11
|
+
|
|
12
|
+
**If `blogWriter` is missing or empty**, STOP and tell the user:
|
|
13
|
+
|
|
14
|
+
> "The blogWriter config block isn't set up yet. Add this to your codejitsu.config.ts:
|
|
15
|
+
>
|
|
16
|
+
> ```ts
|
|
17
|
+
> blogWriter: {
|
|
18
|
+
> tone: '<one-line voice description>',
|
|
19
|
+
> about: '<what the company does + who it serves>',
|
|
20
|
+
> audience: '<primary reader, e.g. BC homeowners planning HVAC>',
|
|
21
|
+
> services: ['Service A', 'Service B'],
|
|
22
|
+
> locations: ['City A', 'City B'],
|
|
23
|
+
> },
|
|
24
|
+
> ```
|
|
25
|
+
>
|
|
26
|
+
> The other fields (approvedTags, wordCount, imageStyle, pricing, seasonalRules,
|
|
27
|
+
> bannedPhrases) have sensible kit defaults. See `modules/blog-writer/CLAUDE.md`
|
|
28
|
+
> for the full shape."
|
|
29
|
+
|
|
30
|
+
Do not proceed without the block. Don't invent values.
|
|
31
|
+
|
|
11
32
|
Everything in there is **site-specific input** for what you're about to write:
|
|
12
33
|
|
|
13
|
-
- `tone` — voice + register
|
|
14
|
-
- `about` — what the company does, who it serves
|
|
15
|
-
- `audience` — primary reader
|
|
16
|
-
- `services` — names that map to `/services/<slug>/`
|
|
17
|
-
- `locations` — names that map to `/service-areas/<slug>/`
|
|
18
|
-
- `approvedTags` — exhaustive
|
|
19
|
-
- `wordCount` — `{ min, max, default }`
|
|
20
|
-
- `faqs` — `{ min, max }
|
|
21
|
-
- `internalLinks` — `{ min, max }` per post
|
|
22
|
-
- `pricing` — `'brackets-only' | 'allowed' | 'never-mention'`
|
|
23
|
-
- `seasonalRules` — free text; current date should respect this
|
|
24
|
-
- `bannedPhrases` — refuse to write these
|
|
25
|
-
- `authorDefault` —
|
|
34
|
+
- `tone` — voice + register (REQUIRED)
|
|
35
|
+
- `about` — what the company does, who it serves (REQUIRED)
|
|
36
|
+
- `audience` — primary reader (REQUIRED)
|
|
37
|
+
- `services` — names that map to `/services/<slug>/` (REQUIRED)
|
|
38
|
+
- `locations` — names that map to `/service-areas/<slug>/` (REQUIRED)
|
|
39
|
+
- `approvedTags` — exhaustive. If unset, derive from `blog.categories` in config OR ask the user once
|
|
40
|
+
- `wordCount` — `{ min, max, default }`. Kit default `{ 1200, 2500, 1800 }`
|
|
41
|
+
- `faqs` — `{ min, max }`. Kit default `{ 5, 8 }`
|
|
42
|
+
- `internalLinks` — `{ min, max }` per post. Kit default `{ 3, 6 }`
|
|
43
|
+
- `pricing` — `'brackets-only' | 'allowed' | 'never-mention'`. Kit default `'brackets-only'` for service businesses
|
|
44
|
+
- `seasonalRules` — free text; current date should respect this. Default: none
|
|
45
|
+
- `bannedPhrases` — refuse to write these. Kit default includes "In today's fast-paced world", "When it comes to", "Look no further", "In conclusion"
|
|
46
|
+
- `authorDefault` — frontmatter `author`. Default: `site.defaultAuthor` or `site.name`
|
|
47
|
+
- `cadenceDays` — for /blog-batch. Kit default `4`
|
|
26
48
|
|
|
27
49
|
Also detect the blog's frontmatter shape by reading `src/content.config.ts` and
|
|
28
50
|
ONE existing post in `src/content/blog/`. Use that exact shape for the new post.
|
|
@@ -143,7 +165,12 @@ frontmatter shape exactly (detected in Step 0).
|
|
|
143
165
|
- Phone: `[text](tel:+1...)` — use the actual number from `site.business.telephone`
|
|
144
166
|
Best is one paragraph with both.
|
|
145
167
|
10. **Approved tags only.** Pick 2-3 from `blogWriter.approvedTags`. Primary
|
|
146
|
-
tag first.
|
|
168
|
+
tag first. **Never invent.** If nothing in `approvedTags` fits the topic,
|
|
169
|
+
STOP and ask the user:
|
|
170
|
+
> "None of your approved tags fit '<topic>'. Options: pick the closest fit
|
|
171
|
+
> from <list>, OR add a new tag to `blogWriter.approvedTags` in
|
|
172
|
+
> codejitsu.config.ts. Which?"
|
|
173
|
+
Don't auto-add tags to the config.
|
|
147
174
|
11. **Image placeholder.** Frontmatter `image` field points to where the image
|
|
148
175
|
WILL live: `<imageStyle.outputDir>/<slug>.webp`. The file doesn't exist
|
|
149
176
|
yet; that's fine. Run `/blog-images` to generate prompts later.
|
|
@@ -175,16 +202,22 @@ exactly. Examples:
|
|
|
175
202
|
|
|
176
203
|
## Step 4 — Verify after writing
|
|
177
204
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
-
|
|
182
|
-
-
|
|
183
|
-
-
|
|
184
|
-
-
|
|
185
|
-
-
|
|
186
|
-
-
|
|
187
|
-
-
|
|
205
|
+
After writing the file, **actively run these checks** by reading the file back
|
|
206
|
+
and counting. Don't skip. If any fail, fix in place before reporting Step 5.
|
|
207
|
+
|
|
208
|
+
- Word count is within `wordCount` target (count after stripping frontmatter)
|
|
209
|
+
- FAQ count in frontmatter is within `faqs.min`-`faqs.max`
|
|
210
|
+
- Internal links count: grep for `](/services/` and `](/service-areas/` in body
|
|
211
|
+
- Tags are all from `approvedTags`
|
|
212
|
+
- **No em dashes**: grep body for `—` and `–` — neither should appear
|
|
213
|
+
- **No H1 in body**: first non-frontmatter line is NOT `# ...`
|
|
214
|
+
- **No `bannedPhrases`**: grep body for each banned phrase
|
|
215
|
+
- **Pricing policy**: if `'brackets-only'`, grep for standalone `$<digits>` not in a range
|
|
216
|
+
- **Frontmatter shape**: matches existing posts in `src/content/blog/`
|
|
217
|
+
- **At least one list**: body contains `- `, `1. `, OR `|` (markdown table)
|
|
218
|
+
|
|
219
|
+
If any check fails, **fix the file then re-verify**. Surface unfixable issues
|
|
220
|
+
to the user before reporting Step 5.
|
|
188
221
|
|
|
189
222
|
## Step 5 — Report back
|
|
190
223
|
|
|
@@ -102,6 +102,27 @@ prompt. Claude reads BLOG_WRITING.md from the installed package and follows
|
|
|
102
102
|
that playbook. **The actual writing rules live in the package**, not in the
|
|
103
103
|
site's repo — that's the robust part.
|
|
104
104
|
|
|
105
|
+
## Conflict with existing site blog instructions
|
|
106
|
+
|
|
107
|
+
Some Codejitsu sites already have `.claude/BLOG_INSTRUCTIONS.md` (pearl,
|
|
108
|
+
workzen) — older bespoke instructions written before the kit existed.
|
|
109
|
+
After running `blog:init`, the site has BOTH:
|
|
110
|
+
- The kit's `/blog` command → reads BLOG_WRITING.md in the package
|
|
111
|
+
- The site's `.claude/BLOG_INSTRUCTIONS.md` → may be referenced by Claude
|
|
112
|
+
ambiently when the site is open
|
|
113
|
+
|
|
114
|
+
**Resolution when adopting the kit on such a site:**
|
|
115
|
+
1. Read the existing `.claude/BLOG_INSTRUCTIONS.md`. Identify what's
|
|
116
|
+
genuinely site-specific (specific tone phrasings, special pricing
|
|
117
|
+
rules, internal link patterns).
|
|
118
|
+
2. Move those site-specific bits into `codejitsu.config.ts.blogWriter`
|
|
119
|
+
(or `seasonalRules` / `bannedPhrases` fields).
|
|
120
|
+
3. Delete or rename the old `.claude/BLOG_INSTRUCTIONS.md` to
|
|
121
|
+
`.claude/BLOG_INSTRUCTIONS.archived.md` so it doesn't get auto-loaded.
|
|
122
|
+
4. Keep only the kit's slash command files in `.claude/commands/`.
|
|
123
|
+
|
|
124
|
+
This avoids the "two contradicting instruction sources" problem.
|
|
125
|
+
|
|
105
126
|
## What must NOT be done
|
|
106
127
|
|
|
107
128
|
- **Don't copy the playbook contents into the site.** The slash command
|
|
@@ -9,9 +9,9 @@ const PACKAGE_ROOT = path.resolve(
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* `codejitsu blog:init` — copies the blog-writer slash command templates
|
|
12
|
-
* into the site's `.claude/commands
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* into the site's `.claude/commands/`. The templates are thin references
|
|
13
|
+
* to playbooks that live in the package; updates flow via `npm update`
|
|
14
|
+
* without re-running this command.
|
|
15
15
|
*/
|
|
16
16
|
export async function runBlogInit() {
|
|
17
17
|
const cwd = process.cwd();
|
|
@@ -49,11 +49,43 @@ export async function runBlogInit() {
|
|
|
49
49
|
console.log('');
|
|
50
50
|
console.log(`${written} created, ${skipped} skipped.`);
|
|
51
51
|
console.log('');
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
|
|
53
|
+
// Detect whether blogWriter is already in the config.
|
|
54
|
+
const configCandidates = ['codejitsu.config.ts', 'codejitsu.config.mjs', 'codejitsu.config.json'];
|
|
55
|
+
let hasBlogWriter = false;
|
|
56
|
+
for (const name of configCandidates) {
|
|
57
|
+
const p = path.join(cwd, name);
|
|
58
|
+
if (fs.existsSync(p) && /blogWriter\s*:/.test(fs.readFileSync(p, 'utf8'))) {
|
|
59
|
+
hasBlogWriter = true;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (hasBlogWriter) {
|
|
65
|
+
console.log(c.green('✓') + ' `blogWriter` block detected in codejitsu.config — ready to use.');
|
|
66
|
+
} else {
|
|
67
|
+
console.log(c.yellow('!') + ' No `blogWriter` block in codejitsu.config yet.');
|
|
68
|
+
console.log('');
|
|
69
|
+
console.log(c.gray('Add this minimal block to your codejitsu.config.ts:'));
|
|
70
|
+
console.log('');
|
|
71
|
+
console.log(c.gray(' blogWriter: {'));
|
|
72
|
+
console.log(c.gray(" tone: 'professional, plain-spoken, confident not boastful',"));
|
|
73
|
+
console.log(c.gray(" about: '<what the company does + who it serves>',"));
|
|
74
|
+
console.log(c.gray(" audience: '<primary reader, e.g. BC homeowners ...>',"));
|
|
75
|
+
console.log(c.gray(" services: ['Service A', 'Service B'],"));
|
|
76
|
+
console.log(c.gray(" locations: ['City A', 'City B'],"));
|
|
77
|
+
console.log(c.gray(' // Optional with kit defaults: approvedTags, wordCount, faqs,'));
|
|
78
|
+
console.log(c.gray(' // internalLinks, pricing, seasonalRules, bannedPhrases,'));
|
|
79
|
+
console.log(c.gray(' // authorDefault, cadenceDays, imageStyle'));
|
|
80
|
+
console.log(c.gray(' },'));
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log(c.gray('See node_modules/@ibalzam/codejitsu-core/modules/blog-writer/CLAUDE.md for the full shape.'));
|
|
83
|
+
}
|
|
84
|
+
|
|
54
85
|
console.log('');
|
|
55
86
|
console.log('Then in Claude Code:');
|
|
56
87
|
console.log(' /blog single post, interactive');
|
|
57
88
|
console.log(' /blog-batch 20 schedule + outline 20 future posts');
|
|
58
89
|
console.log(' /blog-images image prompts for pending posts');
|
|
90
|
+
console.log('');
|
|
59
91
|
}
|
|
@@ -18,43 +18,87 @@ export interface CodejitsuConfig {
|
|
|
18
18
|
audit?: AuditConfig;
|
|
19
19
|
blogWriter?: BlogWriterConfig | false;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* The minimum to enable blog writing: tone, about, audience, services,
|
|
23
|
+
* locations. Everything else has kit defaults that suit most sites.
|
|
24
|
+
*
|
|
25
|
+
* @example minimal
|
|
26
|
+
* ```ts
|
|
27
|
+
* blogWriter: {
|
|
28
|
+
* tone: 'professional, plain-spoken, confident not boastful',
|
|
29
|
+
* about: 'Veteran is a BC HVAC contractor serving the Lower Mainland',
|
|
30
|
+
* audience: 'BC homeowners planning HVAC upgrades',
|
|
31
|
+
* services: ['Heat Pump Installation', 'Furnace Installation'],
|
|
32
|
+
* locations: ['Vancouver', 'Burnaby', 'Surrey'],
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
21
36
|
export interface BlogWriterConfig {
|
|
22
37
|
enabled?: boolean;
|
|
23
|
-
/** Voice + register, free text. e.g. "professional but friendly, confident not boastful". */
|
|
38
|
+
/** REQUIRED. Voice + register, free text. e.g. "professional but friendly, confident not boastful". */
|
|
24
39
|
tone: string;
|
|
25
|
-
/** What the company does + who it serves.
|
|
40
|
+
/** REQUIRED. What the company does + who it serves. Grounds the writer in context. */
|
|
26
41
|
about: string;
|
|
27
|
-
/** Primary reader. e.g. "BC Lower Mainland homeowners planning HVAC upgrades". */
|
|
42
|
+
/** REQUIRED. Primary reader. e.g. "BC Lower Mainland homeowners planning HVAC upgrades". */
|
|
28
43
|
audience: string;
|
|
29
|
-
/** Service names
|
|
44
|
+
/** REQUIRED. Service names mapping to /services/<slug>/. Used for internal-link planning. */
|
|
30
45
|
services: string[];
|
|
31
|
-
/** Location names
|
|
46
|
+
/** REQUIRED. Location names mapping to /service-areas/<slug>/. */
|
|
32
47
|
locations: string[];
|
|
33
|
-
/** Exhaustive tag list. The writer refuses to invent new tags. */
|
|
34
|
-
approvedTags
|
|
35
|
-
|
|
48
|
+
/** Exhaustive tag list. The writer refuses to invent new tags; if nothing fits it asks the user. Default: derives from `blog.categories` in config, or asks at first /blog use. */
|
|
49
|
+
approvedTags?: string[];
|
|
50
|
+
/** Default: { min: 1200, max: 2500, default: 1800 } (the "Long" tier). */
|
|
51
|
+
wordCount?: {
|
|
36
52
|
min: number;
|
|
37
53
|
max: number;
|
|
38
54
|
default: number;
|
|
39
55
|
};
|
|
56
|
+
/** Default: { min: 5, max: 8 }. */
|
|
40
57
|
faqs?: {
|
|
41
58
|
min: number;
|
|
42
59
|
max: number;
|
|
43
60
|
};
|
|
61
|
+
/** Default: { min: 3, max: 6 }. */
|
|
44
62
|
internalLinks?: {
|
|
45
63
|
min: number;
|
|
46
64
|
max: number;
|
|
47
65
|
};
|
|
48
|
-
/**
|
|
66
|
+
/** Default: 'brackets-only' for service businesses; 'allowed' otherwise. */
|
|
49
67
|
pricing?: 'brackets-only' | 'allowed' | 'never-mention';
|
|
50
|
-
/** Free-text seasonal rules. e.g. "May-Sep: outdoor + AC; Oct-Nov: pre-winter prep". */
|
|
68
|
+
/** Free-text seasonal rules. e.g. "May-Sep: outdoor + AC; Oct-Nov: pre-winter prep". Default: none. */
|
|
51
69
|
seasonalRules?: string;
|
|
52
|
-
/** Phrases the writer must NOT produce.
|
|
70
|
+
/** Phrases the writer must NOT produce. Default kit list includes "In today's fast-paced world", "When it comes to", "Look no further", "In conclusion". */
|
|
53
71
|
bannedPhrases?: string[];
|
|
54
|
-
/** Frontmatter `author` default
|
|
72
|
+
/** Frontmatter `author` default. Default: `site.defaultAuthor` or `site.name`. */
|
|
55
73
|
authorDefault?: string;
|
|
56
|
-
|
|
74
|
+
/** Cadence for /blog-batch, in days. Default: 4. */
|
|
75
|
+
cadenceDays?: number;
|
|
76
|
+
/** Image generation config. Required only if you'll use /blog-images. */
|
|
77
|
+
imageStyle?: BlogImageStyle;
|
|
57
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* @example photorealistic-architecture
|
|
81
|
+
* ```ts
|
|
82
|
+
* imageStyle: {
|
|
83
|
+
* description: 'Photorealistic real-estate / architectural photography of the actual space the post is about. Wide landscape framing, eye-level. Bright natural daylight, modern desert-contemporary palette. Clean composition, lightly staged, no clutter, no text overlays.',
|
|
84
|
+
* branding: 'Logo small in bottom-right corner. No other brand marks.',
|
|
85
|
+
* outputDir: 'public/assets/images/blog',
|
|
86
|
+
* maxWords: 60,
|
|
87
|
+
* realism: 'photorealistic',
|
|
88
|
+
* }
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @example sloth-mascot-cartoon
|
|
92
|
+
* ```ts
|
|
93
|
+
* imageStyle: {
|
|
94
|
+
* description: 'Cartoon sloth-mascot character relevant to the post topic, flat friendly colors, with a white rounded text box at the bottom containing a SHORT version of the title.',
|
|
95
|
+
* branding: 'No watermarks, just the in-image character',
|
|
96
|
+
* outputDir: 'public/images/blog/posts',
|
|
97
|
+
* maxWords: 50,
|
|
98
|
+
* realism: 'cartoon',
|
|
99
|
+
* }
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
58
102
|
export interface BlogImageStyle {
|
|
59
103
|
/** Full prompt-style description of the visual style: palette, framing, materials, mood. */
|
|
60
104
|
description: string;
|
|
@@ -20,34 +20,78 @@ export interface CodejitsuConfig {
|
|
|
20
20
|
blogWriter?: BlogWriterConfig | false;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* The minimum to enable blog writing: tone, about, audience, services,
|
|
25
|
+
* locations. Everything else has kit defaults that suit most sites.
|
|
26
|
+
*
|
|
27
|
+
* @example minimal
|
|
28
|
+
* ```ts
|
|
29
|
+
* blogWriter: {
|
|
30
|
+
* tone: 'professional, plain-spoken, confident not boastful',
|
|
31
|
+
* about: 'Veteran is a BC HVAC contractor serving the Lower Mainland',
|
|
32
|
+
* audience: 'BC homeowners planning HVAC upgrades',
|
|
33
|
+
* services: ['Heat Pump Installation', 'Furnace Installation'],
|
|
34
|
+
* locations: ['Vancouver', 'Burnaby', 'Surrey'],
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
23
38
|
export interface BlogWriterConfig {
|
|
24
39
|
enabled?: boolean;
|
|
25
|
-
/** Voice + register, free text. e.g. "professional but friendly, confident not boastful". */
|
|
40
|
+
/** REQUIRED. Voice + register, free text. e.g. "professional but friendly, confident not boastful". */
|
|
26
41
|
tone: string;
|
|
27
|
-
/** What the company does + who it serves.
|
|
42
|
+
/** REQUIRED. What the company does + who it serves. Grounds the writer in context. */
|
|
28
43
|
about: string;
|
|
29
|
-
/** Primary reader. e.g. "BC Lower Mainland homeowners planning HVAC upgrades". */
|
|
44
|
+
/** REQUIRED. Primary reader. e.g. "BC Lower Mainland homeowners planning HVAC upgrades". */
|
|
30
45
|
audience: string;
|
|
31
|
-
/** Service names
|
|
46
|
+
/** REQUIRED. Service names mapping to /services/<slug>/. Used for internal-link planning. */
|
|
32
47
|
services: string[];
|
|
33
|
-
/** Location names
|
|
48
|
+
/** REQUIRED. Location names mapping to /service-areas/<slug>/. */
|
|
34
49
|
locations: string[];
|
|
35
|
-
/** Exhaustive tag list. The writer refuses to invent new tags. */
|
|
36
|
-
approvedTags
|
|
37
|
-
|
|
50
|
+
/** Exhaustive tag list. The writer refuses to invent new tags; if nothing fits it asks the user. Default: derives from `blog.categories` in config, or asks at first /blog use. */
|
|
51
|
+
approvedTags?: string[];
|
|
52
|
+
/** Default: { min: 1200, max: 2500, default: 1800 } (the "Long" tier). */
|
|
53
|
+
wordCount?: { min: number; max: number; default: number };
|
|
54
|
+
/** Default: { min: 5, max: 8 }. */
|
|
38
55
|
faqs?: { min: number; max: number };
|
|
56
|
+
/** Default: { min: 3, max: 6 }. */
|
|
39
57
|
internalLinks?: { min: number; max: number };
|
|
40
|
-
/**
|
|
58
|
+
/** Default: 'brackets-only' for service businesses; 'allowed' otherwise. */
|
|
41
59
|
pricing?: 'brackets-only' | 'allowed' | 'never-mention';
|
|
42
|
-
/** Free-text seasonal rules. e.g. "May-Sep: outdoor + AC; Oct-Nov: pre-winter prep". */
|
|
60
|
+
/** Free-text seasonal rules. e.g. "May-Sep: outdoor + AC; Oct-Nov: pre-winter prep". Default: none. */
|
|
43
61
|
seasonalRules?: string;
|
|
44
|
-
/** Phrases the writer must NOT produce.
|
|
62
|
+
/** Phrases the writer must NOT produce. Default kit list includes "In today's fast-paced world", "When it comes to", "Look no further", "In conclusion". */
|
|
45
63
|
bannedPhrases?: string[];
|
|
46
|
-
/** Frontmatter `author` default
|
|
64
|
+
/** Frontmatter `author` default. Default: `site.defaultAuthor` or `site.name`. */
|
|
47
65
|
authorDefault?: string;
|
|
48
|
-
|
|
66
|
+
/** Cadence for /blog-batch, in days. Default: 4. */
|
|
67
|
+
cadenceDays?: number;
|
|
68
|
+
/** Image generation config. Required only if you'll use /blog-images. */
|
|
69
|
+
imageStyle?: BlogImageStyle;
|
|
49
70
|
}
|
|
50
71
|
|
|
72
|
+
/**
|
|
73
|
+
* @example photorealistic-architecture
|
|
74
|
+
* ```ts
|
|
75
|
+
* imageStyle: {
|
|
76
|
+
* description: 'Photorealistic real-estate / architectural photography of the actual space the post is about. Wide landscape framing, eye-level. Bright natural daylight, modern desert-contemporary palette. Clean composition, lightly staged, no clutter, no text overlays.',
|
|
77
|
+
* branding: 'Logo small in bottom-right corner. No other brand marks.',
|
|
78
|
+
* outputDir: 'public/assets/images/blog',
|
|
79
|
+
* maxWords: 60,
|
|
80
|
+
* realism: 'photorealistic',
|
|
81
|
+
* }
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @example sloth-mascot-cartoon
|
|
85
|
+
* ```ts
|
|
86
|
+
* imageStyle: {
|
|
87
|
+
* description: 'Cartoon sloth-mascot character relevant to the post topic, flat friendly colors, with a white rounded text box at the bottom containing a SHORT version of the title.',
|
|
88
|
+
* branding: 'No watermarks, just the in-image character',
|
|
89
|
+
* outputDir: 'public/images/blog/posts',
|
|
90
|
+
* maxWords: 50,
|
|
91
|
+
* realism: 'cartoon',
|
|
92
|
+
* }
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
51
95
|
export interface BlogImageStyle {
|
|
52
96
|
/** Full prompt-style description of the visual style: palette, framing, materials, mood. */
|
|
53
97
|
description: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ibalzam/codejitsu-core",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shared core for Codejitsu Astro sites — reusable code and Claude-facing instructions for blog, SEO, images, deploy, and llms.txt.",
|
|
6
6
|
"keywords": [
|