@natjswenson/devlog 0.14.0 → 0.14.2
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 +18 -0
- package/README.md +19 -7
- package/SKILL.md +159 -40
- package/bin/devlog.js +94 -50
- package/image-style/style-guide.example.md +19 -16
- package/package.json +1 -1
- package/skill-invariants.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@natjswenson/devlog` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.14.2 (2026-09-17)
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Completed issueflow integration and made initialization host-aware.
|
|
10
|
+
|
|
11
|
+
## Unreleased
|
|
12
|
+
|
|
13
|
+
- Make init host-aware: Claude retains standalone skill installation while
|
|
14
|
+
Codex writes shared personal assets without shadowing the marketplace plugin.
|
|
15
|
+
|
|
16
|
+
## 0.14.1 (2026-09-10) — native branded cover generation
|
|
17
|
+
|
|
18
|
+
- Require Codex covers to use native generated raster artwork in the established
|
|
19
|
+
monochrome technical-engraving PRESS style; reject wireframes and SVG substitutes.
|
|
20
|
+
- Require copyable agent handoffs and validated covers before publication.
|
|
21
|
+
- Fix cover backfills when the target repository stores entries below `targetDir`.
|
|
22
|
+
|
|
5
23
|
## 0.14.0 (2026-09-09) — verified concept guides and AI cover composition
|
|
6
24
|
|
|
7
25
|
- Add an explicit concept-guide draft workflow: select one transferable reader
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# devlog
|
|
2
2
|
|
|
3
|
-
<!-- >>> press:masthead v0.
|
|
4
|
-
**NS** · NATE SWENSON · CLAUDE CODE + CODEX SKILL · PRESS v0.
|
|
3
|
+
<!-- >>> press:masthead v0.11.0 sha256:582134ecd7a5 GENERATED by @natjswenson/press, do not edit -->
|
|
4
|
+
**NS** · NATE SWENSON · CLAUDE CODE + CODEX SKILL · PRESS v0.11.0 · linkedin.com/in/natejswenson
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
<!-- <<< press:masthead -->
|
|
@@ -65,13 +65,16 @@ $devlog
|
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
```sh
|
|
68
|
-
npx @natjswenson/devlog init # create the repo, install the skill, write config
|
|
68
|
+
npx @natjswenson/devlog init # create the repo, install the Claude skill, write config
|
|
69
69
|
npx @natjswenson/devlog preview # see it rendered at http://localhost:5173
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
`init` creates `<your-username>/daily-dev-log` on GitHub (or uses an existing
|
|
73
|
-
repo),
|
|
74
|
-
lets you register one or more projects in a single run.
|
|
73
|
+
repo), writes shared config and personal assets to `~/.claude/skills/devlog/`,
|
|
74
|
+
and lets you register one or more projects in a single run. It defaults to the
|
|
75
|
+
Claude standalone installation; use `init --host codex` when configuring the
|
|
76
|
+
Codex plugin. Codex keeps the marketplace-installed $devlog plugin
|
|
77
|
+
authoritative and does not create or update a standalone skill copy.
|
|
75
78
|
|
|
76
79
|
> **Run from any directory *outside* a clone of this repo.** Running inside it
|
|
77
80
|
> causes a `package.json` name collision and `npx` fails with
|
|
@@ -91,7 +94,9 @@ lets you register one or more projects in a single run.
|
|
|
91
94
|
## Requirements
|
|
92
95
|
|
|
93
96
|
- **Claude Code:** Authenticate `gh` in the shell and allow web research for release guides.
|
|
94
|
-
- **Codex:** Use the same authenticated `gh` CLI and available web tools.
|
|
97
|
+
- **Codex:** Use the same authenticated `gh` CLI and available web tools. Run
|
|
98
|
+
`init --host codex` to write shared config and personal assets; install or
|
|
99
|
+
update the marketplace plugin separately using the Codex commands above.
|
|
95
100
|
- **Personal data:** Both hosts retain config, voice fallback and image-style assets in `~/.claude/skills/devlog/`; an existing `~/.claude/ghostwriter/voice` can also supply the voice.
|
|
96
101
|
|
|
97
102
|
See [Codex migration notes](../../docs/codex-migration.md) for host tools and retained data paths.
|
|
@@ -125,7 +130,7 @@ not apply to a dev log.
|
|
|
125
130
|
|
|
126
131
|
| Command | What it does |
|
|
127
132
|
|---|---|
|
|
128
|
-
| `devlog init` | One-time setup
|
|
133
|
+
| `devlog init [--host claude or codex]` | One-time setup (Claude by default); write config and host-appropriate assets |
|
|
129
134
|
| `devlog add-project` | Register a project (interactive; `--yes --path <p>` for agent use) |
|
|
130
135
|
| `devlog remove-project <key> --yes` | Unregister a project (published entries stay) |
|
|
131
136
|
| `devlog set <field> <value>` | Update one config field |
|
|
@@ -211,6 +216,13 @@ Netlify or Cloudflare Pages, build with `vite build`, deploy `dist/`.
|
|
|
211
216
|
**Other stacks (Next, Astro, plain HTML):** it is static JSON and Markdown on
|
|
212
217
|
GitHub. Build whatever UI you want against the data contract below.
|
|
213
218
|
|
|
219
|
+
## Producer and website onboarding
|
|
220
|
+
|
|
221
|
+
Follow the [complete producer and website onboarding guide](https://github.com/natejswenson/claude-skills/blob/main/skills/devlog/skills/devlog/SKILL.md#producer-and-website-onboarding).
|
|
222
|
+
The guide ships inside SKILL.md for plugin, standalone Claude and npm installations.
|
|
223
|
+
It covers inspecting and preserving registration, privacy, monorepo release scoping,
|
|
224
|
+
and the website registry, manifest and verification required before setup is complete.
|
|
225
|
+
|
|
214
226
|
## Data contract
|
|
215
227
|
|
|
216
228
|
The dev-log repo has this layout, all served as raw files from
|
package/SKILL.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
name: devlog
|
|
3
3
|
description: Turn releases into researched how-to posts, or explicitly draft one complete concept guide readers can implement with their own coding agent. Supports Codex AI cover drafts and conversational devlog configuration.
|
|
4
4
|
user_invocable: true
|
|
5
|
+
version: 0.14.2
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
## Codex runtime
|
|
@@ -10,6 +11,11 @@ When running in Codex, invoke this skill as `$devlog`. Resolve scripts, assets,
|
|
|
10
11
|
and references from the directory containing this SKILL.md, regardless of the
|
|
11
12
|
current working directory. Existing `~/.claude/` personal-data paths remain valid
|
|
12
13
|
and are still used by the bundled scripts; they do not require Claude to run.
|
|
14
|
+
The Codex marketplace plugin is the authoritative $devlog entrypoint. When
|
|
15
|
+
running init from Codex, use --host codex so the CLI writes shared config,
|
|
16
|
+
voice, and image-style assets without creating or updating a standalone skill
|
|
17
|
+
copy in the retained legacy directory. Claude init remains the standalone
|
|
18
|
+
skill-copy installation path.
|
|
13
19
|
Map `Read`/`Write`/`Edit`/`Bash` to the available file and shell tools, and
|
|
14
20
|
`WebSearch`/`WebFetch` to available web tools. For `AskUserQuestion`, use an
|
|
15
21
|
available question tool or a concise chat question; wait for answers that gate
|
|
@@ -19,6 +25,10 @@ apps by capability rather than assuming Claude MCP tool names exist.
|
|
|
19
25
|
|
|
20
26
|
# /devlog — Release How-To Generator
|
|
21
27
|
|
|
28
|
+
<!-- press:runtime -->
|
|
29
|
+
In Claude Code, load `/press`; in Codex, load `$press`; then follow the shared PRESS terminal/UI contract from `brand/agent-ui.md`. Do not copy or override that contract here.
|
|
30
|
+
<!-- press:runtime -->
|
|
31
|
+
|
|
22
32
|
In the default Generate mode, you turn each **new version release** (a semver git tag) in the user's projects into a
|
|
23
33
|
published blog post, written in **the user's own voice**, and pushed to the GitHub repo
|
|
24
34
|
configured in `~/.claude/skills/devlog/config.json`.
|
|
@@ -104,13 +114,24 @@ from a working page, not from the repo. Without it a run can push but cannot con
|
|
|
104
114
|
anything went live. Set with `set siteUrl 'https://example.com'`, clear with
|
|
105
115
|
`set siteUrl ''`.
|
|
106
116
|
|
|
107
|
-
For **add-project**:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
For **add-project**: inspect validated config first with `config --json`, then
|
|
118
|
+
resolve the user's absolute repository path and compare key, label, path, remote,
|
|
119
|
+
pathFilter, tagPrefix and the effective private value (omitted means false). A private
|
|
120
|
+
repository requires private: true; report a privacy mismatch for correction before
|
|
121
|
+
considering setup complete. Preserve a matching existing row unchanged and skip the
|
|
122
|
+
add. Run add-project only when the key is absent. Report a mismatch instead of
|
|
123
|
+
removing/recreating the existing row automatically; duplicate-key rejection remains
|
|
124
|
+
intentional. Detect the default key from the directory basename and remote with
|
|
125
|
+
`git -C '<path>' remote get-url origin`. In a monorepo, use the project's subdirectory
|
|
126
|
+
as `--path-filter` and its release namespace as `--tag-prefix`. Confirm unresolved
|
|
127
|
+
values with the user before adding; values already supplied or authorized need no
|
|
128
|
+
second confirmation. For **remove-project**, confirm once before running; tell the
|
|
129
|
+
user published entries are not deleted.
|
|
130
|
+
|
|
131
|
+
Complete the [producer and website onboarding guide](#producer-and-website-onboarding)
|
|
132
|
+
before declaring setup complete. It covers the exact Issue Flow monorepo fields,
|
|
133
|
+
retained shared configuration, and the website registry-plus-manifest requirement.
|
|
134
|
+
Registration does not publish an article; preserve any existing website content.
|
|
114
135
|
|
|
115
136
|
**Private repos.** If the user says the repo is private (or a source repo happens to be
|
|
116
137
|
private on GitHub even though it's configured normally), pass `--private`. A private
|
|
@@ -126,6 +147,106 @@ If any command prints `{"error": "config-missing", ...}`, tell the user to run
|
|
|
126
147
|
`npx @natjswenson/devlog init` first. On `config-invalid`, show the message and offer to
|
|
127
148
|
fix the named field via `set`.
|
|
128
149
|
|
|
150
|
+
## Producer and website onboarding
|
|
151
|
+
|
|
152
|
+
Registering a source project makes releases discoverable; it does not publish an
|
|
153
|
+
article or finish website onboarding. Use this checklist before the first publication
|
|
154
|
+
and when checking an existing integration. Invoke `/devlog` in Claude Code or
|
|
155
|
+
`$devlog` in Codex; both retain personal configuration at
|
|
156
|
+
`~/.claude/skills/devlog/config.json`. Keep that file outside the plugin.
|
|
157
|
+
|
|
158
|
+
### Inspect and preserve the producer registration
|
|
159
|
+
|
|
160
|
+
Run the host-independent CLI to inspect validated configuration first:
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
npx -y @natjswenson/devlog@0.14.1 config --json
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
For Issue Flow in the monorepo, compare the existing row with this example.
|
|
167
|
+
Replace the example path with the user's absolute monorepo checkout path:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"key": "issueflow",
|
|
172
|
+
"label": "Issue Flow",
|
|
173
|
+
"path": "/absolute/path/to/claude-skills",
|
|
174
|
+
"remote": "natejswenson/claude-skills",
|
|
175
|
+
"pathFilter": "skills/issueflow",
|
|
176
|
+
"tagPrefix": "issueflow-v"
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Compare all six fields above and the effective private value (omitted means false).
|
|
181
|
+
This public Issue Flow example expects private: false; a private repository requires
|
|
182
|
+
private: true and --private when adding it. If exactly one row matches, preserve it
|
|
183
|
+
unchanged and skip add-project. Report any mismatch, including privacy, for correction
|
|
184
|
+
before considering setup complete;
|
|
185
|
+
do not remove and recreate it automatically. Direct duplicate adds are rejected.
|
|
186
|
+
Only when the key is absent, run:
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
npx -y @natjswenson/devlog@0.14.1 add-project --yes \
|
|
190
|
+
--path '/absolute/path/to/claude-skills' --key issueflow --label 'Issue Flow' \
|
|
191
|
+
--remote natejswenson/claude-skills --path-filter skills/issueflow \
|
|
192
|
+
--tag-prefix issueflow-v
|
|
193
|
+
npx -y @natjswenson/devlog@0.14.1 config --json
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Then inspect release discovery with
|
|
197
|
+
`npx -y @natjswenson/devlog@0.14.1 scan --json --project issueflow`.
|
|
198
|
+
Expect only `issueflow-v` release tags and `skills/issueflow` commit/diff history;
|
|
199
|
+
other skills' releases and changes must remain outside this project. An empty
|
|
200
|
+
new-release list may mean entries already exist; inspect scan status and skipped
|
|
201
|
+
reasons before concluding that registration failed.
|
|
202
|
+
|
|
203
|
+
### Register the website and its manifest together
|
|
204
|
+
|
|
205
|
+
In `natejswenson/natejswenson.io`, inspect the current remote branch before editing:
|
|
206
|
+
a stale local checkout can omit an integration that already exists upstream.
|
|
207
|
+
Check `PROJECTS` in `src/data/site.js` for exactly one `issueflow` row labeled
|
|
208
|
+
`Issue Flow`, and check `content/devlog/issueflow/manifest.json`. Preserve an
|
|
209
|
+
existing registry row and populated manifest; never replace published entries
|
|
210
|
+
with the empty example below.
|
|
211
|
+
|
|
212
|
+
For a new project, add its PROJECTS row and a valid manifest in the same change.
|
|
213
|
+
Match neighboring registry fields. Before any article exists, the manifest is:
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{"entries": []}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The loader visits registered projects only and requires every manifest. A directory
|
|
220
|
+
alone is insufficient; a missing manifest fails the build. An empty entries array
|
|
221
|
+
allows the project route to render its empty state. Source, config and documentation
|
|
222
|
+
changes follow the website's own PR flow, even when content publishing has a separate
|
|
223
|
+
flow. Keep existing content intact and link the companion website PR from the
|
|
224
|
+
producer setup PR. Consult the website README and contributor instructions for its
|
|
225
|
+
current branch policy.
|
|
226
|
+
|
|
227
|
+
For this target, verify `targetRepo`, `targetDir: content/devlog`, and the publishing
|
|
228
|
+
branch in shared config. Set `siteUrl` from a working public page: the repository
|
|
229
|
+
name is not necessarily the served domain. Do not change these settings just to
|
|
230
|
+
register another source project.
|
|
231
|
+
|
|
232
|
+
### Verify both halves
|
|
233
|
+
|
|
234
|
+
Run the website's `npm run test:ci`, `npm run build`, and relevant
|
|
235
|
+
`npm run test:e2e` checks. In a disposable fixture or preview, verify an empty
|
|
236
|
+
manifest builds with an Issue Flow project route and other projects still available.
|
|
237
|
+
Use an existing article or a temporary representative fixture to check the Issue Flow
|
|
238
|
+
listing/filter and tag search, hash restoration, project/article links, canonical/OG
|
|
239
|
+
metadata and a successful cover response (including the established fallback).
|
|
240
|
+
Inspect generated sitemap routes and RSS; the RSS feed includes only the latest 30
|
|
241
|
+
entries, so use a latest-dated fixture when specifically testing inclusion.
|
|
242
|
+
Never commit or publish a fake article for setup, or empty a populated production
|
|
243
|
+
manifest to test the pre-publication state.
|
|
244
|
+
|
|
245
|
+
Record the checked commits and command outcomes in both PRs. A local producer config
|
|
246
|
+
row alone is not evidence that the website integration is complete. Publishing new
|
|
247
|
+
articles, backfilling old releases and merging/deploying website changes are separate
|
|
248
|
+
actions from this onboarding check.
|
|
249
|
+
|
|
129
250
|
## Status mode
|
|
130
251
|
|
|
131
252
|
Run `npx -y @natjswenson/devlog@latest scan --json --summary` and render a compact table:
|
|
@@ -392,9 +513,12 @@ The `--clone` flag always points at the CONTENT ROOT: `<abs-tmp>/<repo-name>` wh
|
|
|
392
513
|
`targetDir` is empty, `<abs-tmp>/<repo-name>/<targetDir>` when it's set. Git commands
|
|
393
514
|
always run against the clone root `<abs-tmp>/<repo-name>` regardless.
|
|
394
515
|
|
|
395
|
-
In this
|
|
396
|
-
release's own `publish-entry` call
|
|
397
|
-
|
|
516
|
+
In this Generate path, each release also gets a cover image, created inline in this same loop right before that
|
|
517
|
+
release's own `publish-entry` call. In Codex, the dominant artwork MUST come from the native
|
|
518
|
+
image-generation tool as a persisted raster source; wireframes, hand-authored SVG, CSS drawings,
|
|
519
|
+
and title-only placeholders are not covers. The deterministic compositor may add PRESS typography
|
|
520
|
+
around that generated artwork, but it must never substitute for native image generation.
|
|
521
|
+
The cover must carry a custom illustration or other meaningful visual material; a cover that just re-renders the title in large text is a failure.
|
|
398
522
|
|
|
399
523
|
```bash
|
|
400
524
|
mktemp -d # → record the absolute path, e.g. /var/folders/.../tmp.abc
|
|
@@ -415,24 +539,17 @@ Per release:
|
|
|
415
539
|
returns the style guide, icon catalog, and up to 3 reference cover paths. **Read
|
|
416
540
|
only the single most recent reference image** (image reads are the expensive part;
|
|
417
541
|
open another only if you're genuinely unsure the new cover is distinct). On
|
|
418
|
-
`{"error": "style-guide-missing"}`:
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
(render-cover mechanically enforces this), one of two slots (single centered hero,
|
|
430
|
-
or two-node before/after), interior points snapped to a 25px grid; catalog icons
|
|
431
|
-
never go inside `#hero-zone` (optional small accent glyph near the kicker only,
|
|
432
|
-
bottom edge above y:400). Write the document with the Write tool to
|
|
433
|
-
`'<abs-scratch>/<key>/<version>.html'` — full `<!DOCTYPE html>` document, sized
|
|
434
|
-
`html, body { margin:0; width:1600px; height:900px; }`, font referenced only as
|
|
435
|
-
`font-family: 'DevlogCoverFont', sans-serif`.
|
|
542
|
+
`{"error": "style-guide-missing"}`: stop this release and report that publication is
|
|
543
|
+
blocked. A release entry is not publishable without a cover and agent handoff.
|
|
544
|
+
2. **Generate and compose.** Use ONLY this release's title/tags/summary/`## Shipped` text
|
|
545
|
+
(never the raw draft file, never `## Changelog`) plus the style guide and icon catalog to
|
|
546
|
+
brief the native image-generation tool. Request artwork only: one concrete mechanism,
|
|
547
|
+
no lettering, labels, numbers, logos, fake code, UI, wireframe, gradients, or watermark.
|
|
548
|
+
Persist and inspect the exact returned raster, then use `compose-art-cover` to add local
|
|
549
|
+
PRESS typography. Do not draw the dominant art as inline SVG or CSS. A cover that just
|
|
550
|
+
re-renders the title in large text is a failure. Match the established backfill look:
|
|
551
|
+
flat cream paper, monochrome technical engraving, black/gray ink, restrained crosshatching,
|
|
552
|
+
a quiet left field for the headline, and at most one small orange accent.
|
|
436
553
|
3. **Rasterize.**
|
|
437
554
|
`npx -y @natjswenson/devlog@latest render-cover '<abs-scratch>/<key>/<version>.html' --project '<key>' --slug '<version>' --out '<abs-scratch>'`
|
|
438
555
|
The HTML is the source of truth and **survives the render**: to fix a visual
|
|
@@ -478,18 +595,20 @@ content on disk that the registry doesn't list is built right past, and the entr
|
|
|
478
595
|
while every command in this skill reports success. Publishing a project's first entry is
|
|
479
596
|
therefore a two-part job, and the CLI can only do the first part.
|
|
480
597
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
2.
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
598
|
+
Follow the [producer and website onboarding guide](#producer-and-website-onboarding)
|
|
599
|
+
even before the first article. When `firstEntryForProject` is true, **before the push**:
|
|
600
|
+
|
|
601
|
+
1. Inspect the current website branch and find its registry in the clone. Search the
|
|
602
|
+
site's `src/` for existing project keys with `rg`. An Astro/Next-style site typically
|
|
603
|
+
maps a `PROJECTS` array. Preserve a matching registration instead of adding a duplicate.
|
|
604
|
+
2. For a new project, add the registry row and valid manifest in the same change,
|
|
605
|
+
matching neighboring rows. Never replace a populated manifest with an empty example.
|
|
606
|
+
3. Build the site and confirm the project/article routes exist in its output. Run the
|
|
607
|
+
site's tests and check navigation, tag search, metadata, cover, feed and sitemap
|
|
608
|
+
behavior as described in the paired guide.
|
|
609
|
+
4. Follow the site's own PR flow for registry/source/config/docs changes and link any
|
|
610
|
+
companion PR. Content publication alone does not authorize bypassing that flow;
|
|
611
|
+
do not report the route complete until the required website checks are observed.
|
|
493
612
|
|
|
494
613
|
If you cannot find a registry, say so plainly rather than assuming there isn't one; the
|
|
495
614
|
verification in Step 6 is what actually settles it.
|
package/bin/devlog.js
CHANGED
|
@@ -90,6 +90,10 @@ const log = {
|
|
|
90
90
|
hint: (msg) => console.log(kleur.dim(' ' + msg)),
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
+
async function initPrompt(questions, options) {
|
|
94
|
+
return prompts(questions, options);
|
|
95
|
+
}
|
|
96
|
+
|
|
93
97
|
function readPackageVersion() {
|
|
94
98
|
const pkg = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8'));
|
|
95
99
|
return pkg.version;
|
|
@@ -177,9 +181,10 @@ function detectProjectRemote(path) {
|
|
|
177
181
|
return m ? m[1] : null;
|
|
178
182
|
}
|
|
179
183
|
|
|
180
|
-
async function confirmOverwrite(label, path) {
|
|
184
|
+
async function confirmOverwrite(label, path, { autoConfirm = false } = {}) {
|
|
181
185
|
if (!existsSync(path)) return true;
|
|
182
|
-
|
|
186
|
+
if (autoConfirm) return true;
|
|
187
|
+
const { ok } = await initPrompt({
|
|
183
188
|
type: 'confirm',
|
|
184
189
|
name: 'ok',
|
|
185
190
|
message: `${label} already exists at ${path}. Overwrite?`,
|
|
@@ -296,7 +301,21 @@ async function promptForProject(defaults = {}) {
|
|
|
296
301
|
}
|
|
297
302
|
|
|
298
303
|
// ─── init ────────────────────────────────────────────────────────────────────
|
|
299
|
-
async function cmdInit() {
|
|
304
|
+
async function cmdInit(rest = []) {
|
|
305
|
+
const { values } = safeParseArgs({
|
|
306
|
+
args: rest,
|
|
307
|
+
options: {
|
|
308
|
+
host: { type: 'string', default: 'claude' },
|
|
309
|
+
yes: { type: 'boolean', default: false },
|
|
310
|
+
},
|
|
311
|
+
allowPositionals: false,
|
|
312
|
+
});
|
|
313
|
+
const host = values.host;
|
|
314
|
+
if (host !== 'claude' && host !== 'codex') {
|
|
315
|
+
log.err(`Unknown host "${host}". Use "claude" or "codex".`);
|
|
316
|
+
process.exit(2);
|
|
317
|
+
}
|
|
318
|
+
|
|
300
319
|
log.info(kleur.bold('\ndevlog setup\n'));
|
|
301
320
|
await preflight();
|
|
302
321
|
|
|
@@ -309,7 +328,12 @@ async function cmdInit() {
|
|
|
309
328
|
voicePath: existsSync(GHOSTWRITER_VOICE_DIR) ? GHOSTWRITER_VOICE_DIR : '',
|
|
310
329
|
};
|
|
311
330
|
|
|
312
|
-
const answers =
|
|
331
|
+
const answers = values.yes ? {
|
|
332
|
+
gitAuthor: defaults.gitAuthor,
|
|
333
|
+
githubUser: defaults.githubUser,
|
|
334
|
+
targetRepoName: defaults.targetRepoName,
|
|
335
|
+
voicePath: '',
|
|
336
|
+
} : await initPrompt([
|
|
313
337
|
{ type: 'text', name: 'gitAuthor', message: 'Your name (retained for backward compatibility; not currently rendered on entries):', initial: defaults.gitAuthor, validate: VALIDATORS.gitAuthor },
|
|
314
338
|
{ type: 'text', name: 'githubUser', message: 'Your GitHub username:', initial: defaults.githubUser, validate: VALIDATORS.githubUser },
|
|
315
339
|
{ type: 'text', name: 'targetRepoName', message: 'Name of the repo where dev logs will be published:', initial: defaults.targetRepoName, validate: VALIDATORS.targetRepoName },
|
|
@@ -318,24 +342,26 @@ async function cmdInit() {
|
|
|
318
342
|
|
|
319
343
|
// Optionally register projects in a loop. First time defaults to "yes".
|
|
320
344
|
const projects = [];
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
345
|
+
if (!values.yes) {
|
|
346
|
+
let firstPrompt = true;
|
|
347
|
+
for (;;) {
|
|
348
|
+
const { add } = await initPrompt({
|
|
349
|
+
type: 'confirm',
|
|
350
|
+
name: 'add',
|
|
351
|
+
message: firstPrompt ? 'Register a project now?' : 'Register another project?',
|
|
352
|
+
initial: firstPrompt,
|
|
353
|
+
}, { onCancel: () => process.exit(1) });
|
|
354
|
+
firstPrompt = false;
|
|
355
|
+
if (!add) break;
|
|
356
|
+
const p = await promptForProject();
|
|
357
|
+
if (projects.find((x) => x.key === p.key)) {
|
|
358
|
+
log.warn(`Skipped (duplicate key): ${p.key}`);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
if (p.tagPrefix === 'v') delete p.tagPrefix;
|
|
362
|
+
projects.push(p);
|
|
363
|
+
log.ok(`Registered: ${p.key}`);
|
|
335
364
|
}
|
|
336
|
-
if (p.tagPrefix === 'v') delete p.tagPrefix;
|
|
337
|
-
projects.push(p);
|
|
338
|
-
log.ok(`Registered: ${p.key}`);
|
|
339
365
|
}
|
|
340
366
|
|
|
341
367
|
const targetRepo = `${answers.githubUser}/${answers.targetRepoName}`;
|
|
@@ -367,9 +393,16 @@ async function cmdInit() {
|
|
|
367
393
|
log.info(` Branch: ${config.branch}`);
|
|
368
394
|
log.info(` Voice profile: ${config.voicePath || '(ghostwriter if present, else bundled default)'}`);
|
|
369
395
|
log.info(` Projects: ${config.projects.length === 0 ? '(none — add later with `devlog add-project`)' : config.projects.map((p) => p.key).join(', ')}`);
|
|
370
|
-
|
|
396
|
+
if (host === 'claude') {
|
|
397
|
+
log.info(` Skill location: ${CONFIG_DIR}`);
|
|
398
|
+
} else {
|
|
399
|
+
log.info(` Personal data: ${CONFIG_DIR}`);
|
|
400
|
+
log.info(' Host: Codex (marketplace plugin remains authoritative)');
|
|
401
|
+
}
|
|
371
402
|
|
|
372
|
-
const { proceed } =
|
|
403
|
+
const { proceed } = values.yes
|
|
404
|
+
? { proceed: true }
|
|
405
|
+
: await initPrompt({ type: 'confirm', name: 'proceed', message: 'Continue?', initial: true }, { onCancel: () => process.exit(1) });
|
|
373
406
|
if (!proceed) process.exit(0);
|
|
374
407
|
log.info('');
|
|
375
408
|
|
|
@@ -392,24 +425,26 @@ async function cmdInit() {
|
|
|
392
425
|
log.ok(`Created ${CONFIG_DIR}`);
|
|
393
426
|
}
|
|
394
427
|
|
|
395
|
-
if (
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
428
|
+
if (host === 'claude') {
|
|
429
|
+
if (await confirmOverwrite('SKILL.md', SKILL_DEST, { autoConfirm: values.yes })) {
|
|
430
|
+
// These are versioned instructions, not personal config/voice/style files.
|
|
431
|
+
// Install references with the entrypoint so standalone hosts can resolve them.
|
|
432
|
+
const references = join(PACKAGE_ROOT, 'references');
|
|
433
|
+
const referenceDest = join(CONFIG_DIR, 'references');
|
|
434
|
+
mkdirSync(referenceDest, { recursive: true, mode: 0o700 });
|
|
435
|
+
for (const name of readdirSync(references)) {
|
|
436
|
+
if (name.endsWith('.md') && statSync(join(references, name)).isFile()) {
|
|
437
|
+
copyFileSync(join(references, name), join(referenceDest, name));
|
|
438
|
+
}
|
|
404
439
|
}
|
|
440
|
+
copyFileSync(SKILL_SRC, SKILL_DEST);
|
|
441
|
+
log.ok(`Installed SKILL.md → ${SKILL_DEST}`);
|
|
442
|
+
} else {
|
|
443
|
+
log.warn('Skipped SKILL.md');
|
|
405
444
|
}
|
|
406
|
-
copyFileSync(SKILL_SRC, SKILL_DEST);
|
|
407
|
-
log.ok(`Installed SKILL.md → ${SKILL_DEST}`);
|
|
408
|
-
} else {
|
|
409
|
-
log.warn('Skipped SKILL.md');
|
|
410
445
|
}
|
|
411
446
|
|
|
412
|
-
if (await confirmOverwrite('config.json', CONFIG_PATH)) {
|
|
447
|
+
if (await confirmOverwrite('config.json', CONFIG_PATH, { autoConfirm: values.yes })) {
|
|
413
448
|
atomicWriteJSON(CONFIG_PATH, config);
|
|
414
449
|
log.ok(`Wrote config → ${CONFIG_PATH}`);
|
|
415
450
|
}
|
|
@@ -423,7 +458,7 @@ async function cmdInit() {
|
|
|
423
458
|
for (const [src, dest] of [['voice-profile.example.md', 'voice-profile.md'], ['voice-notes.example.md', 'voice-notes.md']]) {
|
|
424
459
|
const s = join(VOICE_SRC_DIR, src);
|
|
425
460
|
const d = join(VOICE_DEST_DIR, dest);
|
|
426
|
-
if (existsSync(s) && (await confirmOverwrite(`voice/${dest}`, d))) {
|
|
461
|
+
if (existsSync(s) && (await confirmOverwrite(`voice/${dest}`, d, { autoConfirm: values.yes }))) {
|
|
427
462
|
copyFileSync(s, d);
|
|
428
463
|
log.ok(`Installed voice/${dest} → ${d}`);
|
|
429
464
|
}
|
|
@@ -436,19 +471,19 @@ async function cmdInit() {
|
|
|
436
471
|
}
|
|
437
472
|
const styleGuideSrc = join(IMAGE_STYLE_SRC_DIR, 'style-guide.example.md');
|
|
438
473
|
const styleGuideDest = join(IMAGE_STYLE_DEST_DIR, 'style-guide.md');
|
|
439
|
-
if (existsSync(styleGuideSrc) && (await confirmOverwrite('image-style/style-guide.md', styleGuideDest))) {
|
|
474
|
+
if (existsSync(styleGuideSrc) && (await confirmOverwrite('image-style/style-guide.md', styleGuideDest, { autoConfirm: values.yes }))) {
|
|
440
475
|
copyFileSync(styleGuideSrc, styleGuideDest);
|
|
441
476
|
log.ok(`Installed image-style/style-guide.md → ${styleGuideDest}`);
|
|
442
477
|
}
|
|
443
478
|
const fontSrc = join(IMAGE_STYLE_SRC_DIR, 'font.ttf');
|
|
444
479
|
const fontDest = join(IMAGE_STYLE_DEST_DIR, 'font.ttf');
|
|
445
|
-
if (existsSync(fontSrc) && (await confirmOverwrite('image-style/font.ttf', fontDest))) {
|
|
480
|
+
if (existsSync(fontSrc) && (await confirmOverwrite('image-style/font.ttf', fontDest, { autoConfirm: values.yes }))) {
|
|
446
481
|
copyFileSync(fontSrc, fontDest);
|
|
447
482
|
log.ok(`Installed image-style/font.ttf → ${fontDest}`);
|
|
448
483
|
}
|
|
449
484
|
const iconsSrc = join(IMAGE_STYLE_SRC_DIR, 'icons.md');
|
|
450
485
|
const iconsDest = join(IMAGE_STYLE_DEST_DIR, 'icons.md');
|
|
451
|
-
if (existsSync(iconsSrc) && (await confirmOverwrite('image-style/icons.md', iconsDest))) {
|
|
486
|
+
if (existsSync(iconsSrc) && (await confirmOverwrite('image-style/icons.md', iconsDest, { autoConfirm: values.yes }))) {
|
|
452
487
|
copyFileSync(iconsSrc, iconsDest);
|
|
453
488
|
log.ok(`Installed image-style/icons.md → ${iconsDest}`);
|
|
454
489
|
}
|
|
@@ -470,7 +505,13 @@ async function cmdInit() {
|
|
|
470
505
|
|
|
471
506
|
log.info('\n' + kleur.bold().green('Setup complete.') + '\n');
|
|
472
507
|
log.info('Next steps:');
|
|
473
|
-
if (
|
|
508
|
+
if (host === 'codex') {
|
|
509
|
+
log.info(' 1. The marketplace-installed Codex plugin remains the authoritative $devlog entrypoint.');
|
|
510
|
+
log.info(' 2. To install the plugin in Codex:');
|
|
511
|
+
log.info(` ${kleur.cyan('codex plugin marketplace add <marketplace>')}`);
|
|
512
|
+
log.info(` ${kleur.cyan('codex plugin add devlog@claude-skills')}`);
|
|
513
|
+
log.info(` 3. Preview locally: ${kleur.cyan('npx @natjswenson/devlog preview')}`);
|
|
514
|
+
} else if (config.projects.length === 0) {
|
|
474
515
|
log.info(` 1. Add a project: ${kleur.cyan('npx @natjswenson/devlog add-project')}`);
|
|
475
516
|
log.info(' 2. Tag a release in the project (e.g. `git tag v0.1.0`)');
|
|
476
517
|
log.info(` 3. In Claude Code, run: ${kleur.cyan('/devlog')}`);
|
|
@@ -1126,15 +1167,18 @@ async function cmdCommitCovers(rest) {
|
|
|
1126
1167
|
// routinely runs well after the backfill/review session that produced the staging dir,
|
|
1127
1168
|
// and reusing an hours-or-days-old clone would risk mutating a manifest that's since
|
|
1128
1169
|
// moved on.
|
|
1129
|
-
const
|
|
1170
|
+
const repoDir = mkdtempSync(join(tmpdir(), 'devlog-commit-covers-'));
|
|
1130
1171
|
const branch = config.branch || 'main';
|
|
1131
1172
|
const cloneUrl = `https://github.com/${config.targetRepo}.git`;
|
|
1132
|
-
const cloneResult = spawnSync('git', ['clone', '--depth=1', '--branch', branch, cloneUrl,
|
|
1173
|
+
const cloneResult = spawnSync('git', ['clone', '--depth=1', '--branch', branch, cloneUrl, repoDir], { encoding: 'utf8' });
|
|
1133
1174
|
if (cloneResult.status !== 0) {
|
|
1134
|
-
rmSync(
|
|
1175
|
+
rmSync(repoDir, { recursive: true, force: true });
|
|
1135
1176
|
emitJSON({ error: 'clone-failed', message: cloneResult.stderr || 'git clone failed' }, 1);
|
|
1136
1177
|
return;
|
|
1137
1178
|
}
|
|
1179
|
+
// Manifests live under the configured content root; git operations still run
|
|
1180
|
+
// from the repository root.
|
|
1181
|
+
const cloneDir = join(repoDir, config.targetDir || '');
|
|
1138
1182
|
|
|
1139
1183
|
const summary = { written: [], skipped: [], failed: [], missingManifest: [] };
|
|
1140
1184
|
let bulkForceOverwriteCount = 0;
|
|
@@ -1199,20 +1243,20 @@ async function cmdCommitCovers(rest) {
|
|
|
1199
1243
|
['commit', '-m', `chore(devlog): add ${summary.written.length} cover image(s)`],
|
|
1200
1244
|
];
|
|
1201
1245
|
for (const args of steps) {
|
|
1202
|
-
const r = spawnSync('git', ['-C',
|
|
1246
|
+
const r = spawnSync('git', ['-C', repoDir, ...args], { encoding: 'utf8' });
|
|
1203
1247
|
if (r.status !== 0) {
|
|
1204
1248
|
emitJSON({ ok: false, ...summary, bulkForceOverwriteCount, error: 'git-commit-failed', message: r.stderr }, 1);
|
|
1205
1249
|
return;
|
|
1206
1250
|
}
|
|
1207
1251
|
}
|
|
1208
|
-
const push = spawnSync('git', ['-C',
|
|
1252
|
+
const push = spawnSync('git', ['-C', repoDir, 'push', '--no-tags', 'origin', branch], { encoding: 'utf8' });
|
|
1209
1253
|
if (push.status !== 0) {
|
|
1210
1254
|
emitJSON({ ok: false, ...summary, bulkForceOverwriteCount, error: 'git-push-failed', message: push.stderr }, 1);
|
|
1211
1255
|
return;
|
|
1212
1256
|
}
|
|
1213
1257
|
}
|
|
1214
1258
|
|
|
1215
|
-
rmSync(
|
|
1259
|
+
rmSync(repoDir, { recursive: true, force: true });
|
|
1216
1260
|
emitJSON({ ok: summary.failed.length === 0, ...summary, bulkForceOverwriteCount });
|
|
1217
1261
|
}
|
|
1218
1262
|
|
|
@@ -1330,7 +1374,7 @@ function printHelp() {
|
|
|
1330
1374
|
${kleur.bold('@natjswenson/devlog')} v${readPackageVersion()} — release dev log generator
|
|
1331
1375
|
|
|
1332
1376
|
Setup & config:
|
|
1333
|
-
${kleur.cyan('npx @natjswenson/devlog init')}
|
|
1377
|
+
${kleur.cyan('npx @natjswenson/devlog init [--host claude|codex] [--yes]')} One-time setup (defaults to Claude); Codex plugin is installed separately; --yes is non-interactive
|
|
1334
1378
|
${kleur.cyan('npx @natjswenson/devlog add-project')} Register a project (interactive; add --yes --path <p> for non-interactive)
|
|
1335
1379
|
${kleur.cyan('npx @natjswenson/devlog remove-project <key> --yes')} Unregister a project (entries stay published)
|
|
1336
1380
|
${kleur.cyan('npx @natjswenson/devlog set <field> <value>')} Update one config field (${SETTABLE_FIELDS.join(', ')})
|
|
@@ -1385,7 +1429,7 @@ if (isMain) {
|
|
|
1385
1429
|
const rest = process.argv.slice(3);
|
|
1386
1430
|
switch (arg) {
|
|
1387
1431
|
case 'init':
|
|
1388
|
-
cmdInit();
|
|
1432
|
+
cmdInit(rest);
|
|
1389
1433
|
break;
|
|
1390
1434
|
case 'add-project':
|
|
1391
1435
|
cmdAddProject(rest);
|
|
@@ -11,8 +11,9 @@ are how a from-scratch visual language stays consistent from post to post.
|
|
|
11
11
|
|
|
12
12
|
## What you're composing
|
|
13
13
|
|
|
14
|
-
A single
|
|
15
|
-
|
|
14
|
+
A single PRESS editorial cover that renders, at exactly 1600×900px, as a cover image for
|
|
15
|
+
one dev log entry. In Codex, the dominant image is a native generated raster illustration;
|
|
16
|
+
the local compositor adds the typography and layout. Compose from these
|
|
16
17
|
fields only — never open the post's raw markdown file, never reference any section other
|
|
17
18
|
than `## Shipped` (no `## Changelog`, no gotchas, no sources):
|
|
18
19
|
- `title`
|
|
@@ -34,11 +35,11 @@ Before writing any HTML, do this thinking step explicitly:
|
|
|
34
35
|
key that doesn't fit, a git tag being distrusted like any other user input, one broken
|
|
35
36
|
link in a chain, a filter separating signal from noise, a clock catching a stale
|
|
36
37
|
timestamp, two paths diverging and one being cut off.
|
|
37
|
-
3. Design ONE illustration
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
3. Design ONE native raster illustration that depicts that concept. Not a photo, stock
|
|
39
|
+
icon, screenshot, UI, wireframe, or geometric placeholder: a detailed original
|
|
40
|
+
**monochrome technical engraving in black and warm gray ink on flat cream paper**, in
|
|
41
|
+
the spirit of a historical engineering plate or newspaper field illustration, with at
|
|
42
|
+
most one small vermilion/orange focal accent. The art must look printed, not painterly.
|
|
42
43
|
4. That illustration is the dominant visual element of the cover — roughly half the
|
|
43
44
|
canvas, not a thumbnail in the corner. Title, kicker, and summary text support it;
|
|
44
45
|
they do not replace it.
|
|
@@ -151,10 +152,10 @@ For the two-node slot specifically: the accent icon's presence must not be read
|
|
|
151
152
|
`DevlogCoverFont` finishes loading. That's resolving a name the browser already has, not
|
|
152
153
|
embedding or fetching a file — no different in kind from the fallback this rule already
|
|
153
154
|
requires.
|
|
154
|
-
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
- The final cover must be self-contained and offline-renderable. Native raster artwork is
|
|
156
|
+
persisted locally and embedded by `compose-art-cover`; no remote images or web fonts.
|
|
157
|
+
Typography is rendered locally and the generated artwork is never replaced by inline
|
|
158
|
+
SVG, CSS shapes, a wireframe, or a title-only placeholder.
|
|
158
159
|
|
|
159
160
|
## Visual direction
|
|
160
161
|
|
|
@@ -165,7 +166,7 @@ site, not a marketing graphic and not a repeated template.
|
|
|
165
166
|
|
|
166
167
|
### Palette
|
|
167
168
|
|
|
168
|
-
<!-- >>> press:palette v0.
|
|
169
|
+
<!-- >>> press:palette v0.11.0 sha256:5904c52d4168 GENERATED by @natjswenson/press, do not edit -->
|
|
169
170
|
- **Paper** `#F5F0E6` — Warm cream. Flat — never gradiented, never textured.
|
|
170
171
|
- **Ink** `#181510` — Near-black. Text, headlines, and every structural rule.
|
|
171
172
|
- **Dim** `#6E675C` — Muted secondary text; the serif commentary voice's color.
|
|
@@ -176,9 +177,10 @@ site, not a marketing graphic and not a repeated template.
|
|
|
176
177
|
survives, and only inside a terminal element. Never on paper.
|
|
177
178
|
<!-- <<< press:palette -->
|
|
178
179
|
|
|
179
|
-
Prefer flat, limited color and solid/line fills over gradients or smooth shading
|
|
180
|
-
|
|
181
|
-
|
|
180
|
+
Prefer flat, limited color and solid/line fills over gradients or smooth shading. Native
|
|
181
|
+
art should be predominantly cream paper plus black/gray engraving lines, with no broad
|
|
182
|
+
color wash, no saturated palette, and no painterly digital lighting. The only bright color
|
|
183
|
+
is the single small orange accent permitted by the accent law below.
|
|
182
184
|
|
|
183
185
|
### The accent law, carried to covers
|
|
184
186
|
|
|
@@ -304,7 +306,8 @@ summary as on-image text.
|
|
|
304
306
|
cursor, no bare `_` suffix, no stray `$` prompt as decoration. The dark palette now
|
|
305
307
|
belongs to exactly one place, the `.term` panel, and only when it's real code.
|
|
306
308
|
- Don't embed a photograph, stock image, or anything requiring an external fetch — the
|
|
307
|
-
illustration is
|
|
309
|
+
illustration is native-generated raster artwork persisted locally and embedded by the
|
|
310
|
+
compositor, never a hand-authored SVG substitute.
|
|
308
311
|
- Don't reference any font file other than the bundled `'DevlogCoverFont'` — the serif
|
|
309
312
|
and display voices lean on this rendering host's own system fonts, never a file you
|
|
310
313
|
fetch or embed yourself.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@natjswenson/devlog",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.2",
|
|
4
4
|
"description": "Release dev log generator — Claude Code skill + preview app for publishing version-release dev logs, written in your voice, to your site",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nate Swenson",
|
package/skill-invariants.json
CHANGED
|
@@ -72,9 +72,9 @@
|
|
|
72
72
|
"rationale": "Cover composition must draw only from title/tags/summary/## Shipped \u2014 never the raw draft or Changelog \u2014 losing this line reopens off-scope content leaking into an auto-composed image."
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
|
-
"id": "cover-
|
|
76
|
-
"pattern": "
|
|
77
|
-
"rationale": "A missing/uninstalled style guide must
|
|
75
|
+
"id": "cover-required-before-publish",
|
|
76
|
+
"pattern": "publication is[\\s\\S]{0,5}blocked",
|
|
77
|
+
"rationale": "A missing/uninstalled style guide must stop the release so Generate mode cannot publish an entry without its required cover."
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
"id": "cover-review-gate",
|