@ibalzam/codejitsu-core 0.10.0 → 0.11.0
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/bin/codejitsu.mjs +3 -0
- package/modules/blog-writer/BLOG_BATCH.md +142 -0
- package/modules/blog-writer/BLOG_IMAGES.md +127 -0
- package/modules/blog-writer/BLOG_WRITING.md +198 -0
- package/modules/blog-writer/CLAUDE.md +125 -0
- package/modules/blog-writer/templates/.claude/commands/blog-batch.md +10 -0
- package/modules/blog-writer/templates/.claude/commands/blog-images.md +12 -0
- package/modules/blog-writer/templates/.claude/commands/blog.md +10 -0
- package/modules/cli/src/blog-init.mjs +59 -0
- package/modules/config/src/types.d.ts +49 -0
- package/modules/config/src/types.ts +41 -0
- package/package.json +1 -1
package/bin/codejitsu.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { parseArgs } from 'node:util';
|
|
|
3
3
|
import { runBlog } from '../modules/cli/src/blog.mjs';
|
|
4
4
|
import { runDeploySetup, runDeployTrigger } from '../modules/cli/src/deploy.mjs';
|
|
5
5
|
import { runDoctor } from '../modules/cli/src/doctor.mjs';
|
|
6
|
+
import { runBlogInit } from '../modules/cli/src/blog-init.mjs';
|
|
6
7
|
import { runAudit } from '../modules/audit/src/run.mjs';
|
|
7
8
|
|
|
8
9
|
const subcommand = process.argv[2];
|
|
@@ -11,6 +12,7 @@ const rest = process.argv.slice(3);
|
|
|
11
12
|
const COMMANDS = {
|
|
12
13
|
'blog:list': () => runBlog('blog:list'),
|
|
13
14
|
'blog:drafts': () => runBlog('blog:drafts'),
|
|
15
|
+
'blog:init': () => runBlogInit(),
|
|
14
16
|
'deploy:setup': () => runDeploySetup(),
|
|
15
17
|
'deploy:run': () => runDeployTrigger(),
|
|
16
18
|
doctor: () => runDoctor(),
|
|
@@ -56,6 +58,7 @@ function printHelp() {
|
|
|
56
58
|
console.log(`Subcommands:`);
|
|
57
59
|
console.log(` blog:list List every non-draft post with URL + image check`);
|
|
58
60
|
console.log(` blog:drafts List future-dated (pending) posts only`);
|
|
61
|
+
console.log(` blog:init Install /blog, /blog-batch, /blog-images slash commands`);
|
|
59
62
|
console.log(``);
|
|
60
63
|
console.log(` deploy:setup Wire up daily Cloudflare deploy (prompts for hook URL)`);
|
|
61
64
|
console.log(` deploy:run Trigger the Daily Deploy workflow once now`);
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Blog batch generation — Claude playbook
|
|
2
|
+
|
|
3
|
+
> Triggered when the user runs `/blog-batch <N>` in a Codejitsu site
|
|
4
|
+
> (default N = 20).
|
|
5
|
+
>
|
|
6
|
+
> Generates a **schedule + outline** for N future-dated posts. It does NOT
|
|
7
|
+
> write the full body of every post — that's `/blog` per row. The goal here
|
|
8
|
+
> is to produce a publishable roadmap that the user reviews + edits before
|
|
9
|
+
> any prose is committed.
|
|
10
|
+
|
|
11
|
+
## Step 0 — Read site config
|
|
12
|
+
|
|
13
|
+
Same as `BLOG_WRITING.md` Step 0: load `codejitsu.config.ts.blogWriter`.
|
|
14
|
+
|
|
15
|
+
Also read:
|
|
16
|
+
- `src/content/blog/` — existing posts (find the latest `pubDate`)
|
|
17
|
+
- `src/content.config.ts` — confirm the schema shape
|
|
18
|
+
|
|
19
|
+
## Step 1 — Decide cadence
|
|
20
|
+
|
|
21
|
+
Default cadence is **4 days between posts** (matches workzen / pearl /
|
|
22
|
+
veteran). If existing posts have a different rhythm, infer from the last
|
|
23
|
+
10 and match it.
|
|
24
|
+
|
|
25
|
+
Starting date = max(`latest existing pubDate`, today) + cadence.
|
|
26
|
+
|
|
27
|
+
## Step 2 — Generate N topics
|
|
28
|
+
|
|
29
|
+
Brainstorm `N` topics, balancing across:
|
|
30
|
+
|
|
31
|
+
- **Service × location** combinations — cycle through `services` and
|
|
32
|
+
`locations` from config so coverage is even
|
|
33
|
+
- **Topic format** mix — roughly:
|
|
34
|
+
- 30% comparison / "X vs Y" posts
|
|
35
|
+
- 30% how-to / guides
|
|
36
|
+
- 20% troubleshooting
|
|
37
|
+
- 20% seasonal / news
|
|
38
|
+
- **Season awareness** — apply `seasonalRules` from config. A post landing
|
|
39
|
+
in July shouldn't be about "winterize"; one in December shouldn't be
|
|
40
|
+
about "pre-summer".
|
|
41
|
+
- **Unique titles** — no two posts in the batch should target the same slug
|
|
42
|
+
or near-duplicate keyword. Read all existing post titles before
|
|
43
|
+
brainstorming to avoid collisions.
|
|
44
|
+
- **Approved tag balance** — distribute tags so no single tag is over 40% of
|
|
45
|
+
the batch.
|
|
46
|
+
|
|
47
|
+
For each topic, derive:
|
|
48
|
+
- `slug` — kebab-case, includes the city when applicable
|
|
49
|
+
- `title` — natural, clickable, ≤70 chars
|
|
50
|
+
- `pubDate` — assigned by cadence walk
|
|
51
|
+
- `tags` — 2-3 from `approvedTags`, primary first
|
|
52
|
+
- `service` + `city` — for internal-link planning
|
|
53
|
+
|
|
54
|
+
## Step 3 — Produce the schedule table
|
|
55
|
+
|
|
56
|
+
Write to `Blog/BLOG_SCHEDULE.md` (create the `Blog/` directory if needed).
|
|
57
|
+
Append to it if it already exists — never overwrite without asking.
|
|
58
|
+
|
|
59
|
+
Format:
|
|
60
|
+
|
|
61
|
+
```markdown
|
|
62
|
+
# <Site Name> — Blog Schedule (<start month> - <end month> <year>)
|
|
63
|
+
|
|
64
|
+
Offline publishing calendar. N posts on a <cadence>-day cadence,
|
|
65
|
+
<start date> through <end date>.
|
|
66
|
+
|
|
67
|
+
## How to use
|
|
68
|
+
|
|
69
|
+
1. Pick a row. Slug = filename → `src/content/blog/<slug>.md`.
|
|
70
|
+
2. `pubDate: <date>` in frontmatter controls visibility.
|
|
71
|
+
3. Run `/blog "<title>"` to flesh out the post body.
|
|
72
|
+
4. Run `/blog-images` once a batch of posts is drafted.
|
|
73
|
+
|
|
74
|
+
## Schedule
|
|
75
|
+
|
|
76
|
+
| # | Date | Category | City | Slug | Title |
|
|
77
|
+
|---|------------|----------------|-----------|------|-------|
|
|
78
|
+
| 1 | YYYY-MM-DD | <primary tag> | <city> | `<slug>` | <Title> |
|
|
79
|
+
| 2 | ... | ... | ... | ... | ... |
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Step 4 — Produce per-post outlines
|
|
83
|
+
|
|
84
|
+
In the same file or a sibling `Blog/BLOG_OUTLINES.md`, expand each row into
|
|
85
|
+
an outline:
|
|
86
|
+
|
|
87
|
+
```markdown
|
|
88
|
+
## <Slug>
|
|
89
|
+
|
|
90
|
+
**pubDate**: YYYY-MM-DD
|
|
91
|
+
**target words**: <from config.wordCount.default>
|
|
92
|
+
**tags**: [primary, secondary]
|
|
93
|
+
**service**: <slug>
|
|
94
|
+
**city**: <slug or 'general'>
|
|
95
|
+
|
|
96
|
+
**Angle / hook**: <one-sentence framing of the reader's pain>
|
|
97
|
+
|
|
98
|
+
**Outline**:
|
|
99
|
+
- Intro — 1-2 paragraphs hooking <hook>
|
|
100
|
+
- ## <H2 #1>
|
|
101
|
+
- 2-3 paragraphs covering <points>
|
|
102
|
+
- ## <H2 #2>
|
|
103
|
+
- Comparison table of <X vs Y>
|
|
104
|
+
- ## <H2 #3>
|
|
105
|
+
- ...
|
|
106
|
+
- Closing CTA + 5-8 FAQs
|
|
107
|
+
|
|
108
|
+
**FAQ seeds**: list of 5-8 question stems
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Step 5 — Show + approve
|
|
112
|
+
|
|
113
|
+
Present the full schedule + outline file to the user. Ask:
|
|
114
|
+
|
|
115
|
+
> "Here's the schedule. Anything to adjust before we lock it in?
|
|
116
|
+
> Want me to flesh out the first N posts now, or stop here for review?"
|
|
117
|
+
|
|
118
|
+
If the user says go: run `/blog` for each row (one post at a time). If they
|
|
119
|
+
say stop: leave the file and exit. They can run `/blog` row by row whenever.
|
|
120
|
+
|
|
121
|
+
## Hard rules
|
|
122
|
+
|
|
123
|
+
- **Don't write the full prose for all N in one shot.** Too long, too
|
|
124
|
+
expensive, hard to review. Schedule + outlines first; prose per row on
|
|
125
|
+
demand.
|
|
126
|
+
- **Don't push or commit.** The user reviews + commits.
|
|
127
|
+
- **Respect existing posts.** If `Blog/BLOG_SCHEDULE.md` already has rows,
|
|
128
|
+
append the new batch and don't renumber.
|
|
129
|
+
- **Don't invent services or locations.** Use only what's in
|
|
130
|
+
`blogWriter.services` and `blogWriter.locations`.
|
|
131
|
+
- **Don't invent tags.** Only from `approvedTags`.
|
|
132
|
+
- **Don't bunch up topic types.** Reject your own first draft if it has 5
|
|
133
|
+
comparison posts in a row.
|
|
134
|
+
|
|
135
|
+
## Verify
|
|
136
|
+
|
|
137
|
+
- [ ] N rows, all unique slugs
|
|
138
|
+
- [ ] Dates respect cadence; no collisions with existing posts
|
|
139
|
+
- [ ] Tags balanced across `approvedTags`
|
|
140
|
+
- [ ] Service + city distribution covers most of the config lists
|
|
141
|
+
- [ ] Season fits each `pubDate`'s month
|
|
142
|
+
- [ ] No duplicate or near-duplicate topics
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Blog image prompts — Claude playbook
|
|
2
|
+
|
|
3
|
+
> Triggered when the user runs `/blog-images` (or `/blog-images <N>`) in a
|
|
4
|
+
> Codejitsu site.
|
|
5
|
+
>
|
|
6
|
+
> Generates AI-image-generation prompts for blog posts that don't have their
|
|
7
|
+
> images yet. Output goes to `Blog/IMAGE_PROMPTS.md` in the **5-dash separator
|
|
8
|
+
> format**.
|
|
9
|
+
|
|
10
|
+
## Step 0 — Read site config
|
|
11
|
+
|
|
12
|
+
Open `codejitsu.config.ts` and locate `blogWriter.imageStyle`. Use:
|
|
13
|
+
|
|
14
|
+
- `imageStyle.description` — the full prompt-style description of the visual
|
|
15
|
+
style (photorealistic / cartoon / illustration / etc., palette, framing)
|
|
16
|
+
- `imageStyle.branding` — branding rule (e.g. "logo small in bottom-right")
|
|
17
|
+
- `imageStyle.outputDir` — where the final .webp will live
|
|
18
|
+
- `imageStyle.maxWords` — cap on prompt length (typical ≤60)
|
|
19
|
+
- `imageStyle.realism` — `'photorealistic' | 'illustration' | 'cartoon' | 'mixed'`
|
|
20
|
+
|
|
21
|
+
## Step 1 — Find the posts to prompt
|
|
22
|
+
|
|
23
|
+
Default `N = 10` if not specified. Scan `src/content/blog/` and pick posts
|
|
24
|
+
that:
|
|
25
|
+
|
|
26
|
+
1. Have `pubDate > today` (pending / scheduled), OR
|
|
27
|
+
2. Are missing an image file at `<imageStyle.outputDir>/<slug>.webp`
|
|
28
|
+
|
|
29
|
+
Sort by `pubDate` ascending. Take up to N.
|
|
30
|
+
|
|
31
|
+
If nothing matches → tell the user "no pending posts need images" and stop.
|
|
32
|
+
|
|
33
|
+
## Step 2 — Generate prompts
|
|
34
|
+
|
|
35
|
+
For each post, write a prompt ≤`maxWords` words that:
|
|
36
|
+
|
|
37
|
+
- Captures the **specific topic** of the post (e.g. a comparison post should
|
|
38
|
+
show both materials, a city-anchored post should hint at the city's
|
|
39
|
+
setting)
|
|
40
|
+
- Follows the style + branding from `imageStyle`
|
|
41
|
+
- References any specific materials, fixtures, or settings mentioned in the
|
|
42
|
+
post title
|
|
43
|
+
- **Doesn't** include text, captions, watermarks, or competitor brand names
|
|
44
|
+
- **Doesn't** specify image dimensions (the image generator decides)
|
|
45
|
+
|
|
46
|
+
If `imageStyle.realism === 'photorealistic'`, prompts emphasize architecture,
|
|
47
|
+
lighting, real materials, magazine-quality framing.
|
|
48
|
+
|
|
49
|
+
If `imageStyle.realism === 'cartoon'`, prompts emphasize character design,
|
|
50
|
+
flat colors, friendly tone.
|
|
51
|
+
|
|
52
|
+
## Step 3 — Output format
|
|
53
|
+
|
|
54
|
+
Write to `Blog/IMAGE_PROMPTS.md` (create the `Blog/` directory if absent).
|
|
55
|
+
Append if the file exists — never overwrite existing prompts.
|
|
56
|
+
|
|
57
|
+
The format is **EXACTLY**:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
**<Post Title>**
|
|
61
|
+
<prompt text>
|
|
62
|
+
|
|
63
|
+
-----
|
|
64
|
+
|
|
65
|
+
**<Post Title>**
|
|
66
|
+
<prompt text>
|
|
67
|
+
|
|
68
|
+
-----
|
|
69
|
+
|
|
70
|
+
**<Post Title>**
|
|
71
|
+
<prompt text>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Format rules — strict
|
|
75
|
+
|
|
76
|
+
- Separator is **exactly 5 dashes** on their own line: `-----`. Not 3, not 7.
|
|
77
|
+
Markdown's `---` is wrong here; use `-----` literally.
|
|
78
|
+
- Separator appears **between entries**, never after the last one.
|
|
79
|
+
- Title goes on its own line in **bold** (with `**...**`).
|
|
80
|
+
- Prompt goes directly below the title line, no blank line between them.
|
|
81
|
+
- Blank line before each separator, blank line after.
|
|
82
|
+
- No numbering.
|
|
83
|
+
- No dimensions in the prompt text.
|
|
84
|
+
- The output file goes to `imageStyle.outputDir/<slug>.webp` (note this
|
|
85
|
+
convention in a one-line header at the top of the file, not in each
|
|
86
|
+
prompt).
|
|
87
|
+
|
|
88
|
+
## Step 4 — Header for the file
|
|
89
|
+
|
|
90
|
+
Top of `Blog/IMAGE_PROMPTS.md`:
|
|
91
|
+
|
|
92
|
+
```markdown
|
|
93
|
+
# <Site Name> — Blog Image Prompts (Posts <range>)
|
|
94
|
+
|
|
95
|
+
Generated against `modules/blog-writer/BLOG_IMAGES.md` from `<Site Name>`'s
|
|
96
|
+
`codejitsu.config.ts.blogWriter.imageStyle`.
|
|
97
|
+
|
|
98
|
+
Style: <one-line summary derived from imageStyle.description>.
|
|
99
|
+
Output file convention: each image saves to `<outputDir>/<slug>.webp`.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
(That `---` IS markdown HR — it's the only 3-dash separator in the file,
|
|
105
|
+
separating the header from the prompts. Everything between prompt entries is
|
|
106
|
+
`-----`.)
|
|
107
|
+
|
|
108
|
+
## Hard rules
|
|
109
|
+
|
|
110
|
+
1. **Separator is `-----` (5 dashes).** Not 3.
|
|
111
|
+
2. **No numbering** of entries.
|
|
112
|
+
3. **No dimensions** in the prompt text.
|
|
113
|
+
4. **No competitor brand names** in any prompt.
|
|
114
|
+
5. **No text / captions / watermarks inside the image** unless the style
|
|
115
|
+
description explicitly allows it.
|
|
116
|
+
6. **Append, don't overwrite.** If the user has a prior prompts file, add new
|
|
117
|
+
entries below; never delete or rewrite existing prompts.
|
|
118
|
+
7. **One prompt per post.** Don't generate alternatives unless asked.
|
|
119
|
+
|
|
120
|
+
## Verify
|
|
121
|
+
|
|
122
|
+
- [ ] N prompts produced (or fewer if not enough pending posts)
|
|
123
|
+
- [ ] Every separator is exactly 5 dashes
|
|
124
|
+
- [ ] Last entry has NO trailing separator
|
|
125
|
+
- [ ] No prompt exceeds `maxWords`
|
|
126
|
+
- [ ] Each prompt references the post's specific topic + city if applicable
|
|
127
|
+
- [ ] Output file path is `Blog/IMAGE_PROMPTS.md`
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Blog writing — Claude playbook
|
|
2
|
+
|
|
3
|
+
> Triggered when the user runs `/blog` (or `/blog <topic>`) in a Codejitsu site
|
|
4
|
+
> that has the kit's commands installed (`npx codejitsu blog:init`).
|
|
5
|
+
>
|
|
6
|
+
> The slash command file is a thin reference; this file is the actual playbook.
|
|
7
|
+
|
|
8
|
+
## Step 0 — Read site config
|
|
9
|
+
|
|
10
|
+
Open `codejitsu.config.ts` at the site root and locate the `blogWriter` block.
|
|
11
|
+
Everything in there is **site-specific input** for what you're about to write:
|
|
12
|
+
|
|
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; never invent new ones
|
|
19
|
+
- `wordCount` — `{ min, max, default }`
|
|
20
|
+
- `faqs` — `{ min, max }` (kit default 5-8)
|
|
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` — what to put in frontmatter `author`
|
|
26
|
+
|
|
27
|
+
Also detect the blog's frontmatter shape by reading `src/content.config.ts` and
|
|
28
|
+
ONE existing post in `src/content/blog/`. Use that exact shape for the new post.
|
|
29
|
+
|
|
30
|
+
## Step 1 — Gather inputs (interactive)
|
|
31
|
+
|
|
32
|
+
Use `AskUserQuestion` to gather these in order. If the user passed a topic as
|
|
33
|
+
`$ARGUMENTS`, skip Question 1.
|
|
34
|
+
|
|
35
|
+
### Question 1 — Topic & focus
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
header: "Topic"
|
|
39
|
+
question: "What is this blog post about?"
|
|
40
|
+
options:
|
|
41
|
+
- "Industry guide" — Educational content the audience would search for
|
|
42
|
+
- "How-to / tutorial" — Step-by-step instructions
|
|
43
|
+
- "Comparison / vs" — Compare 2+ options on multiple attributes
|
|
44
|
+
- "Troubleshooting" — Diagnose-and-fix walkthrough
|
|
45
|
+
- "Seasonal / news" — Time-sensitive (rebate change, season prep, etc.)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Question 2 — Length
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
header: "Length"
|
|
52
|
+
question: "How long?"
|
|
53
|
+
options:
|
|
54
|
+
- "Short (400-600 words)" — Announcement, quick explainer
|
|
55
|
+
- "Medium (800-1200 words)" — Single-question explainer
|
|
56
|
+
- "Long (1500-2500 words)" — Default for SEO posts
|
|
57
|
+
- "XL (2500+ words)" — Definitive guide
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Default = the `wordCount.default` from config.
|
|
61
|
+
|
|
62
|
+
### Question 3 — Primary service + city focus
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
header: "Service + city"
|
|
66
|
+
question: "Which service and city is this anchored to?"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Two follow-ups (separate questions):
|
|
70
|
+
- Service options: derive from `blogWriter.services`
|
|
71
|
+
- City options: derive from `blogWriter.locations` (plus an "any/general" option)
|
|
72
|
+
|
|
73
|
+
These drive the internal-link choices later.
|
|
74
|
+
|
|
75
|
+
### Question 4 — Publish date
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
header: "Publish date"
|
|
79
|
+
question: "When should this go live?"
|
|
80
|
+
options:
|
|
81
|
+
- "Today" — immediate publish
|
|
82
|
+
- "Next available slot" — read recent posts in src/content/blog/, find the
|
|
83
|
+
next open date in the existing cadence (e.g. 4-day gap), use that
|
|
84
|
+
- "Custom date" — user types YYYY-MM-DD
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Step 2 — Outline + approval
|
|
88
|
+
|
|
89
|
+
Before writing prose, produce an outline:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
TITLE: <working title>
|
|
93
|
+
SLUG: <kebab-case>
|
|
94
|
+
DATE: <YYYY-MM-DD>
|
|
95
|
+
TAGS: <2-3 from approvedTags, primary first>
|
|
96
|
+
WORDS: <target word count>
|
|
97
|
+
|
|
98
|
+
OUTLINE:
|
|
99
|
+
Intro (1-2 paragraphs)
|
|
100
|
+
## <H2 #1>
|
|
101
|
+
2-3 paragraphs of [angle]
|
|
102
|
+
## <H2 #2>
|
|
103
|
+
Includes a comparison table / bullet list of [items]
|
|
104
|
+
## <H2 #3>
|
|
105
|
+
...
|
|
106
|
+
Closing CTA (modal + phone)
|
|
107
|
+
FAQs (5-8): planned questions
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Show the outline and **ask for approval before writing prose**. Adjust on
|
|
111
|
+
feedback. Don't proceed without approval.
|
|
112
|
+
|
|
113
|
+
## Step 3 — Write the post
|
|
114
|
+
|
|
115
|
+
Write the full post to `src/content/blog/<slug>.md`. Match the existing
|
|
116
|
+
frontmatter shape exactly (detected in Step 0).
|
|
117
|
+
|
|
118
|
+
### HARD RULES — apply to every post
|
|
119
|
+
|
|
120
|
+
1. **No em dashes anywhere.** Use ` - ` (regular dash with spaces around it).
|
|
121
|
+
2. **No H1 in markdown body.** Frontmatter `title` becomes the H1. Body starts
|
|
122
|
+
with intro paragraph(s), then `## H2`.
|
|
123
|
+
3. **No "one-line H2 sections."** Every `## H2` must have at least
|
|
124
|
+
**2-3 paragraphs**, not just a sentence + bullet list.
|
|
125
|
+
4. **At least one list per post.** Either a bullet list (3+ parallel items) or
|
|
126
|
+
a numbered list (if order matters) or a markdown table (for comparisons).
|
|
127
|
+
No exceptions — a wall-of-prose 2,000-word post is wrong. But also no
|
|
128
|
+
slide-deck (every section ending in bullets).
|
|
129
|
+
5. **FAQ block** in frontmatter — `faqs.min` to `faqs.max` entries from config
|
|
130
|
+
(default 5-8). Each FAQ = real question a reader would search; answer is
|
|
131
|
+
2-4 sentences, concise and complete.
|
|
132
|
+
6. **Internal links** — `internalLinks.min` to `internalLinks.max` from config
|
|
133
|
+
(default 3-6). Link to `/services/<slug>/`, `/service-areas/<slug>/`, and
|
|
134
|
+
`/services/<slug>/<city>/` patterns. Don't link the same URL twice.
|
|
135
|
+
7. **Banned phrases** — if `bannedPhrases` is set in config, refuse them.
|
|
136
|
+
Common offenders: "In today's fast-paced world…", "When it comes to…",
|
|
137
|
+
"Look no further…", "In conclusion…".
|
|
138
|
+
8. **Pricing** — if `pricing: 'brackets-only'`, every price reference is a
|
|
139
|
+
range with context. Never a single dollar figure. If `pricing: 'never-mention'`,
|
|
140
|
+
omit pricing entirely.
|
|
141
|
+
9. **CTA** — closing paragraph includes a call-to-action. Two patterns:
|
|
142
|
+
- Modal: `[text](#contact)` or trigger-class link
|
|
143
|
+
- Phone: `[text](tel:+1...)` — use the actual number from `site.business.telephone`
|
|
144
|
+
Best is one paragraph with both.
|
|
145
|
+
10. **Approved tags only.** Pick 2-3 from `blogWriter.approvedTags`. Primary
|
|
146
|
+
tag first. Don't invent new tags. If nothing fits, ask the user.
|
|
147
|
+
11. **Image placeholder.** Frontmatter `image` field points to where the image
|
|
148
|
+
WILL live: `<imageStyle.outputDir>/<slug>.webp`. The file doesn't exist
|
|
149
|
+
yet; that's fine. Run `/blog-images` to generate prompts later.
|
|
150
|
+
12. **Seasonal awareness.** If `seasonalRules` is set, the topic + angle must
|
|
151
|
+
fit the post's publish month. No "winterize" posts in July.
|
|
152
|
+
|
|
153
|
+
### Structure
|
|
154
|
+
|
|
155
|
+
- **Intro** — 1-2 paragraphs that hook on a real audience pain. Tie to a
|
|
156
|
+
specific local concern when possible (the climate, regulation, market).
|
|
157
|
+
- **3-6 H2 sections** — each 2-3 paragraphs minimum. Mix prose, lists, and
|
|
158
|
+
tables. Roughly:
|
|
159
|
+
- Prose carries narrative + explanation in most sections
|
|
160
|
+
- 1-3 bullet lists where content is genuinely parallel items
|
|
161
|
+
- A markdown table for 2+ option comparisons (when it's a vs/comparison post)
|
|
162
|
+
- Numbered list only when order matters
|
|
163
|
+
- **H3 sub-sections** within H2s where useful.
|
|
164
|
+
- **Closing paragraph** with CTA.
|
|
165
|
+
- **FAQ block** in frontmatter.
|
|
166
|
+
|
|
167
|
+
### Tone
|
|
168
|
+
|
|
169
|
+
Read `blogWriter.tone` and `blogWriter.audience` from config. Match
|
|
170
|
+
exactly. Examples:
|
|
171
|
+
|
|
172
|
+
- "professional but friendly, confident not boastful" → don't oversell, don't joke
|
|
173
|
+
- "BC HVAC pro, plain-spoken, technical when needed" → mix terms-of-art with plain explanations
|
|
174
|
+
- "Zen calm, helpful, occasional humour" → workzen's signature
|
|
175
|
+
|
|
176
|
+
## Step 4 — Verify after writing
|
|
177
|
+
|
|
178
|
+
- [ ] Word count is within target range
|
|
179
|
+
- [ ] FAQ count is within `faqs.min`-`faqs.max`
|
|
180
|
+
- [ ] Internal links count is within `internalLinks.min`-`internalLinks.max`
|
|
181
|
+
- [ ] Tags are from `approvedTags` only
|
|
182
|
+
- [ ] No em dashes (`—` or `–`) anywhere
|
|
183
|
+
- [ ] No H1 in body
|
|
184
|
+
- [ ] No `bannedPhrases` present
|
|
185
|
+
- [ ] Pricing follows `pricing` policy
|
|
186
|
+
- [ ] Frontmatter shape matches existing posts
|
|
187
|
+
- [ ] At least one list (bullet, numbered, or table)
|
|
188
|
+
|
|
189
|
+
## Step 5 — Report back
|
|
190
|
+
|
|
191
|
+
Tell the user:
|
|
192
|
+
- Path to the new file
|
|
193
|
+
- Word count
|
|
194
|
+
- Tag selection + why
|
|
195
|
+
- Whether image needs generating (point at `/blog-images`)
|
|
196
|
+
- Whether the post is published immediately or scheduled (based on date)
|
|
197
|
+
|
|
198
|
+
Don't push or commit unless explicitly asked.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Blog writer module — instructions for Claude
|
|
2
|
+
|
|
3
|
+
When the user asks to **set up blog writing** or **add the codejitsu blog
|
|
4
|
+
writer** to a site, do the following.
|
|
5
|
+
|
|
6
|
+
## What this module provides
|
|
7
|
+
|
|
8
|
+
A robust blog-writing workflow driven by per-site config + three slash
|
|
9
|
+
commands in the user's `.claude/commands/`. The slash commands themselves
|
|
10
|
+
are **thin references** to playbooks that live in the package — so updates
|
|
11
|
+
flow through `npm update` without re-copying anything.
|
|
12
|
+
|
|
13
|
+
**Files in this module:**
|
|
14
|
+
|
|
15
|
+
- `BLOG_WRITING.md` — single-post playbook (driven by `/blog`)
|
|
16
|
+
- `BLOG_BATCH.md` — schedule + outline N posts (driven by `/blog-batch <N>`)
|
|
17
|
+
- `BLOG_IMAGES.md` — generate image prompts for pending posts (driven by `/blog-images`)
|
|
18
|
+
- `templates/.claude/commands/*.md` — thin slash command files that the
|
|
19
|
+
site keeps in its repo (copied once via `codejitsu blog:init`)
|
|
20
|
+
|
|
21
|
+
**Config block in `codejitsu.config.ts`:**
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
blogWriter: {
|
|
25
|
+
tone: 'professional, plain-spoken, confident not boastful',
|
|
26
|
+
about: 'Veteran Heating and Cooling is a Chilliwack HVAC contractor...',
|
|
27
|
+
audience: 'BC Lower Mainland homeowners planning HVAC upgrades',
|
|
28
|
+
services: ['Heat Pump Installation', 'Furnace Installation', ...],
|
|
29
|
+
locations: ['Vancouver', 'Burnaby', 'Surrey', ...],
|
|
30
|
+
approvedTags: ['rebates-savings', 'buying-guides', 'troubleshooting', 'seasonal-care', 'home-comfort'],
|
|
31
|
+
wordCount: { min: 1200, max: 2500, default: 1800 },
|
|
32
|
+
faqs: { min: 5, max: 8 },
|
|
33
|
+
internalLinks: { min: 3, max: 6 },
|
|
34
|
+
pricing: 'brackets-only',
|
|
35
|
+
seasonalRules: 'July-Sep: heat-wave + AC topics. Oct-Nov: pre-winter prep...',
|
|
36
|
+
bannedPhrases: ["In today's fast-paced world", "When it comes to", "Look no further"],
|
|
37
|
+
authorDefault: 'Veteran HVAC',
|
|
38
|
+
imageStyle: {
|
|
39
|
+
description: 'Photorealistic real-estate / architectural photography of HVAC equipment + BC home interiors. Bright natural daylight, modern Lower Mainland aesthetic, no people unless they wear branded uniform.',
|
|
40
|
+
branding: 'Logo small in bottom-right corner. No other brand marks.',
|
|
41
|
+
outputDir: 'public/assets/blog',
|
|
42
|
+
maxWords: 60,
|
|
43
|
+
realism: 'photorealistic',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Wiring it into a site
|
|
49
|
+
|
|
50
|
+
### 1. Configure `blogWriter` in `codejitsu.config.ts`
|
|
51
|
+
|
|
52
|
+
Fill in the block above. Take care with:
|
|
53
|
+
- `approvedTags` — exhaustive list. The commands will refuse to invent new tags.
|
|
54
|
+
- `imageStyle.description` — be specific. This is the seed for every image prompt.
|
|
55
|
+
- `seasonalRules` — free text. The batch generator reads this to balance topics by month.
|
|
56
|
+
- `bannedPhrases` — common AI tells.
|
|
57
|
+
|
|
58
|
+
### 2. Run `codejitsu blog:init`
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx codejitsu blog:init
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Copies the three slash command files into `.claude/commands/`:
|
|
65
|
+
- `blog.md`
|
|
66
|
+
- `blog-batch.md`
|
|
67
|
+
- `blog-images.md`
|
|
68
|
+
|
|
69
|
+
Each is a one-line reference pointing at the matching playbook in
|
|
70
|
+
`node_modules/@ibalzam/codejitsu-core/modules/blog-writer/`. **Sites should
|
|
71
|
+
not edit these files.** Updates to the playbooks ship via `npm update`.
|
|
72
|
+
|
|
73
|
+
### 3. Use in Claude Code
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
/blog # single post, interactive
|
|
77
|
+
/blog "tankless water heater" # single post with topic seeded
|
|
78
|
+
/blog-batch 20 # schedule + outline 20 future posts
|
|
79
|
+
/blog-images # image prompts for next 10 pending posts
|
|
80
|
+
/blog-images 30 # for next 30
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## How the slash commands work
|
|
84
|
+
|
|
85
|
+
Each `.claude/commands/blog.md` looks like:
|
|
86
|
+
|
|
87
|
+
```md
|
|
88
|
+
---
|
|
89
|
+
description: Codejitsu blog writer
|
|
90
|
+
argument-hint: [optional topic]
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
Open `node_modules/@ibalzam/codejitsu-core/modules/blog-writer/BLOG_WRITING.md`
|
|
94
|
+
and follow it top-down. The playbook reads site-specific tone, services,
|
|
95
|
+
locations, and rules from `codejitsu.config.ts`.
|
|
96
|
+
|
|
97
|
+
Topic provided: $ARGUMENTS
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
When the user runs `/blog`, Claude Code feeds this file's contents as the
|
|
101
|
+
prompt. Claude reads BLOG_WRITING.md from the installed package and follows
|
|
102
|
+
that playbook. **The actual writing rules live in the package**, not in the
|
|
103
|
+
site's repo — that's the robust part.
|
|
104
|
+
|
|
105
|
+
## What must NOT be done
|
|
106
|
+
|
|
107
|
+
- **Don't copy the playbook contents into the site.** The slash command
|
|
108
|
+
should be a thin reference. If a site has hand-edited BLOG_WRITING.md
|
|
109
|
+
copy, it drifts from the package over time. Reference only.
|
|
110
|
+
- **Don't write posts that invent tags.** Only `approvedTags` are valid.
|
|
111
|
+
- **Don't break the 5-dash separator** for image prompts. Markdown's `---`
|
|
112
|
+
is the wrong separator — use `-----` literally.
|
|
113
|
+
- **Don't run `/blog-batch` with N > 50.** Too many posts in one batch
|
|
114
|
+
bunches topics and degrades quality. Three batches of 20 is better.
|
|
115
|
+
- **Don't generate prose for all N in `/blog-batch`.** Schedule + outlines
|
|
116
|
+
only. Prose per row via `/blog`.
|
|
117
|
+
- **Don't push or commit blog posts without user permission.**
|
|
118
|
+
|
|
119
|
+
## Verify after setup
|
|
120
|
+
|
|
121
|
+
- [ ] `codejitsu.config.ts` has the full `blogWriter` block
|
|
122
|
+
- [ ] `.claude/commands/blog.md`, `blog-batch.md`, `blog-images.md` exist
|
|
123
|
+
- [ ] Each command is a one-line reference (don't edit)
|
|
124
|
+
- [ ] `node_modules/@ibalzam/codejitsu-core/modules/blog-writer/BLOG_WRITING.md` exists
|
|
125
|
+
- [ ] Running `/blog` in Claude Code opens the writer (test with a throwaway topic)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Codejitsu blog batch — schedule + outline N future-dated posts
|
|
3
|
+
argument-hint: [N — number of posts, default 20]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Open `node_modules/@ibalzam/codejitsu-core/modules/blog-writer/BLOG_BATCH.md` and follow it top-down.
|
|
7
|
+
|
|
8
|
+
The playbook generates a schedule + outline (NOT full prose) for the next N posts, reading services, locations, tags, cadence rules, and seasonal awareness from `codejitsu.config.ts`. Prose per row is filled in via `/blog`.
|
|
9
|
+
|
|
10
|
+
N provided: $ARGUMENTS
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Codejitsu blog image prompts — generate AI-image prompts for pending posts
|
|
3
|
+
argument-hint: [N — number of prompts, default 10]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Open `node_modules/@ibalzam/codejitsu-core/modules/blog-writer/BLOG_IMAGES.md` and follow it top-down.
|
|
7
|
+
|
|
8
|
+
The playbook reads `blogWriter.imageStyle` from `codejitsu.config.ts` (style description, branding, output dir, max words, realism) and generates prompts in the **5-dash separator format** (not the markdown `---` HR — use `-----` literally).
|
|
9
|
+
|
|
10
|
+
Output goes to `Blog/IMAGE_PROMPTS.md`. Appends to existing file — never overwrites.
|
|
11
|
+
|
|
12
|
+
N provided: $ARGUMENTS
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Codejitsu blog writer — interactive single-post creation
|
|
3
|
+
argument-hint: [optional topic]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Open `node_modules/@ibalzam/codejitsu-core/modules/blog-writer/BLOG_WRITING.md` and follow it top-down.
|
|
7
|
+
|
|
8
|
+
The playbook reads site-specific tone, services, locations, audience, approved tags, and writing rules from `codejitsu.config.ts` (the `blogWriter` block). Don't improvise from training-data memory — the config is the source of truth.
|
|
9
|
+
|
|
10
|
+
Topic provided: $ARGUMENTS
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { c } from './format.mjs';
|
|
4
|
+
|
|
5
|
+
const PACKAGE_ROOT = path.resolve(
|
|
6
|
+
path.dirname(new URL(import.meta.url).pathname),
|
|
7
|
+
'..', '..', '..'
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `codejitsu blog:init` — copies the blog-writer slash command templates
|
|
12
|
+
* into the site's `.claude/commands/` directory. The templates are thin
|
|
13
|
+
* references to playbooks that live in the package, so site updates flow
|
|
14
|
+
* via `npm update` without re-running this command.
|
|
15
|
+
*/
|
|
16
|
+
export async function runBlogInit() {
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
const dest = path.join(cwd, '.claude/commands');
|
|
19
|
+
const src = path.join(
|
|
20
|
+
PACKAGE_ROOT,
|
|
21
|
+
'modules/blog-writer/templates/.claude/commands'
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
if (!fs.existsSync(src)) {
|
|
25
|
+
console.error(c.red(`✗ Source dir missing: ${src}`));
|
|
26
|
+
console.error(' Reinstall @ibalzam/codejitsu-core.');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
31
|
+
|
|
32
|
+
const files = fs.readdirSync(src).filter((n) => n.endsWith('.md'));
|
|
33
|
+
console.log(c.bold('\nCodejitsu blog:init\n'));
|
|
34
|
+
|
|
35
|
+
let written = 0;
|
|
36
|
+
let skipped = 0;
|
|
37
|
+
for (const name of files) {
|
|
38
|
+
const destPath = path.join(dest, name);
|
|
39
|
+
if (fs.existsSync(destPath)) {
|
|
40
|
+
console.log(c.gray('= ') + `${name} (already exists, skipping)`);
|
|
41
|
+
skipped++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
fs.copyFileSync(path.join(src, name), destPath);
|
|
45
|
+
console.log(c.green('+ ') + `.claude/commands/${name}`);
|
|
46
|
+
written++;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(`${written} created, ${skipped} skipped.`);
|
|
51
|
+
console.log('');
|
|
52
|
+
console.log('Next: configure the `blogWriter` block in codejitsu.config.ts.');
|
|
53
|
+
console.log('See `node_modules/@ibalzam/codejitsu-core/modules/blog-writer/CLAUDE.md`.');
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log('Then in Claude Code:');
|
|
56
|
+
console.log(' /blog single post, interactive');
|
|
57
|
+
console.log(' /blog-batch 20 schedule + outline 20 future posts');
|
|
58
|
+
console.log(' /blog-images image prompts for pending posts');
|
|
59
|
+
}
|
|
@@ -16,6 +16,55 @@ export interface CodejitsuConfig {
|
|
|
16
16
|
deploy?: DeployConfig | false;
|
|
17
17
|
contact?: ContactConfig | false;
|
|
18
18
|
audit?: AuditConfig;
|
|
19
|
+
blogWriter?: BlogWriterConfig | false;
|
|
20
|
+
}
|
|
21
|
+
export interface BlogWriterConfig {
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
/** Voice + register, free text. e.g. "professional but friendly, confident not boastful". */
|
|
24
|
+
tone: string;
|
|
25
|
+
/** What the company does + who it serves. Helps the writer ground posts in context. */
|
|
26
|
+
about: string;
|
|
27
|
+
/** Primary reader. e.g. "BC Lower Mainland homeowners planning HVAC upgrades". */
|
|
28
|
+
audience: string;
|
|
29
|
+
/** Service names that map to /services/<slug>/. Used for internal-link planning. */
|
|
30
|
+
services: string[];
|
|
31
|
+
/** Location names that map to /service-areas/<slug>/. */
|
|
32
|
+
locations: string[];
|
|
33
|
+
/** Exhaustive tag list. The writer refuses to invent new tags. */
|
|
34
|
+
approvedTags: string[];
|
|
35
|
+
wordCount: {
|
|
36
|
+
min: number;
|
|
37
|
+
max: number;
|
|
38
|
+
default: number;
|
|
39
|
+
};
|
|
40
|
+
faqs?: {
|
|
41
|
+
min: number;
|
|
42
|
+
max: number;
|
|
43
|
+
};
|
|
44
|
+
internalLinks?: {
|
|
45
|
+
min: number;
|
|
46
|
+
max: number;
|
|
47
|
+
};
|
|
48
|
+
/** Pricing policy. 'brackets-only' = always show as range with context. */
|
|
49
|
+
pricing?: 'brackets-only' | 'allowed' | 'never-mention';
|
|
50
|
+
/** Free-text seasonal rules. e.g. "May-Sep: outdoor + AC; Oct-Nov: pre-winter prep". */
|
|
51
|
+
seasonalRules?: string;
|
|
52
|
+
/** Phrases the writer must NOT produce. e.g. ["In today's fast-paced world", "Look no further"]. */
|
|
53
|
+
bannedPhrases?: string[];
|
|
54
|
+
/** Frontmatter `author` default if a post doesn't specify one. */
|
|
55
|
+
authorDefault?: string;
|
|
56
|
+
imageStyle: BlogImageStyle;
|
|
57
|
+
}
|
|
58
|
+
export interface BlogImageStyle {
|
|
59
|
+
/** Full prompt-style description of the visual style: palette, framing, materials, mood. */
|
|
60
|
+
description: string;
|
|
61
|
+
/** Branding rule. e.g. "logo small in bottom-right corner, no other brand marks". */
|
|
62
|
+
branding: string;
|
|
63
|
+
/** Where final .webp files live. e.g. "public/assets/images/blog". */
|
|
64
|
+
outputDir: string;
|
|
65
|
+
/** Max words per generated prompt. Typical ≤ 60. */
|
|
66
|
+
maxWords: number;
|
|
67
|
+
realism: 'photorealistic' | 'illustration' | 'cartoon' | 'mixed';
|
|
19
68
|
}
|
|
20
69
|
export interface ContactConfig {
|
|
21
70
|
enabled?: boolean;
|
|
@@ -17,6 +17,47 @@ export interface CodejitsuConfig {
|
|
|
17
17
|
deploy?: DeployConfig | false;
|
|
18
18
|
contact?: ContactConfig | false;
|
|
19
19
|
audit?: AuditConfig;
|
|
20
|
+
blogWriter?: BlogWriterConfig | false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface BlogWriterConfig {
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
/** Voice + register, free text. e.g. "professional but friendly, confident not boastful". */
|
|
26
|
+
tone: string;
|
|
27
|
+
/** What the company does + who it serves. Helps the writer ground posts in context. */
|
|
28
|
+
about: string;
|
|
29
|
+
/** Primary reader. e.g. "BC Lower Mainland homeowners planning HVAC upgrades". */
|
|
30
|
+
audience: string;
|
|
31
|
+
/** Service names that map to /services/<slug>/. Used for internal-link planning. */
|
|
32
|
+
services: string[];
|
|
33
|
+
/** Location names that map to /service-areas/<slug>/. */
|
|
34
|
+
locations: string[];
|
|
35
|
+
/** Exhaustive tag list. The writer refuses to invent new tags. */
|
|
36
|
+
approvedTags: string[];
|
|
37
|
+
wordCount: { min: number; max: number; default: number };
|
|
38
|
+
faqs?: { min: number; max: number };
|
|
39
|
+
internalLinks?: { min: number; max: number };
|
|
40
|
+
/** Pricing policy. 'brackets-only' = always show as range with context. */
|
|
41
|
+
pricing?: 'brackets-only' | 'allowed' | 'never-mention';
|
|
42
|
+
/** Free-text seasonal rules. e.g. "May-Sep: outdoor + AC; Oct-Nov: pre-winter prep". */
|
|
43
|
+
seasonalRules?: string;
|
|
44
|
+
/** Phrases the writer must NOT produce. e.g. ["In today's fast-paced world", "Look no further"]. */
|
|
45
|
+
bannedPhrases?: string[];
|
|
46
|
+
/** Frontmatter `author` default if a post doesn't specify one. */
|
|
47
|
+
authorDefault?: string;
|
|
48
|
+
imageStyle: BlogImageStyle;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface BlogImageStyle {
|
|
52
|
+
/** Full prompt-style description of the visual style: palette, framing, materials, mood. */
|
|
53
|
+
description: string;
|
|
54
|
+
/** Branding rule. e.g. "logo small in bottom-right corner, no other brand marks". */
|
|
55
|
+
branding: string;
|
|
56
|
+
/** Where final .webp files live. e.g. "public/assets/images/blog". */
|
|
57
|
+
outputDir: string;
|
|
58
|
+
/** Max words per generated prompt. Typical ≤ 60. */
|
|
59
|
+
maxWords: number;
|
|
60
|
+
realism: 'photorealistic' | 'illustration' | 'cartoon' | 'mixed';
|
|
20
61
|
}
|
|
21
62
|
|
|
22
63
|
export interface ContactConfig {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ibalzam/codejitsu-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
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": [
|