@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 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.9.0 sha256:4b48a8031589 GENERATED by @natjswenson/press, do not edit -->
4
- **NS** · NATE SWENSON · CLAUDE CODE + CODEX SKILL · PRESS v0.9.0 · linkedin.com/in/natejswenson
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), installs the skill at `~/.claude/skills/devlog/`, writes its config, and
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. `init` still writes its legacy Claude skill copy; the Codex plugin is installed separately above.
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: create dev-log repo, install skill, write config |
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**: resolve the path first (the repo the user named, or the cwd), then
108
- detect what the CLI will use key = directory basename, remote = `git -C '<path>' remote
109
- get-url origin`. In a monorepo, suggest a `--path-filter` (the project's subdir) and a
110
- `--tag-prefix` (e.g. `myproj-v`). Confirm the resolved values with the user in ONE
111
- `AskUserQuestion` (options: "looks right" / sensible alternatives), then run with `--yes`
112
- and show the resulting project list. For **remove-project**, confirm once before running;
113
- tell the user published entries are not deleted.
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 legacy Generate path, each release also gets a cover image, composed inline in this same loop right before that
396
- release's own `publish-entry` call a self-contained HTML/CSS (or inline SVG) document,
397
- rasterized locally, never sent to any external service:
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"}`: skip cover composition for this release entirely
419
- and proceed straight to publish-entry with no `--cover` flag.
420
- Never block publish on a missing style guide.
421
- 2. **Compose** using ONLY this release's title/tags/summary/`## Shipped` text
422
- (never the raw draft file, never `## Changelog`) plus the style guide and icon catalog. A
423
- cover that just re-renders the title in large text is a failure — find the one
424
- concrete mechanism this release is actually about (not the project name, not "a bug
425
- fix") and draw ONE custom inline-SVG illustration of it as the dominant visual
426
- element; title/kicker stay secondary; two releases should never produce visually
427
- similar covers. Follow the style guide's hero-zone grid contract: the illustration
428
- lives in a `#hero-zone` container at exactly `x:150 y:425 width:1300 height:400`
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
- When `firstEntryForProject` is true, **before the push**:
482
-
483
- 1. Find the registry in the clone. It is a source file, not content — grep the site's
484
- `src/` for the existing project keys (`grep -rn '<a-known-project-key>' <clone>/src`).
485
- In an Astro/Next-style site it is typically a `PROJECTS` array the entry loader maps
486
- over.
487
- 2. Add the new project, matching the shape of the neighbouring rows exactly.
488
- 3. **Build the site in the clone and confirm the route exists** (`npm ci && npx astro
489
- build`, then check the output directory contains the new `<project>/<version>` path).
490
- A registry edit that doesn't produce a route is not done.
491
- 4. Run the site's own test suite if it has one, and commit the registry change together
492
- with the content in the same push.
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
- const { ok } = await prompts({
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 = await prompts([
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
- let firstPrompt = true;
322
- for (;;) {
323
- const { add } = await prompts({
324
- type: 'confirm',
325
- name: 'add',
326
- message: firstPrompt ? 'Register a project now?' : 'Register another project?',
327
- initial: firstPrompt,
328
- }, { onCancel: () => process.exit(1) });
329
- firstPrompt = false;
330
- if (!add) break;
331
- const p = await promptForProject();
332
- if (projects.find((x) => x.key === p.key)) {
333
- log.warn(`Skipped (duplicate key): ${p.key}`);
334
- continue;
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
- log.info(` Skill location: ${CONFIG_DIR}`);
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 } = await prompts({ type: 'confirm', name: 'proceed', message: 'Continue?', initial: true }, { onCancel: () => process.exit(1) });
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 (await confirmOverwrite('SKILL.md', SKILL_DEST)) {
396
- // These are versioned instructions, not personal config/voice/style files.
397
- // Install references with the entrypoint so standalone hosts can resolve them.
398
- const references = join(PACKAGE_ROOT, 'references');
399
- const referenceDest = join(CONFIG_DIR, 'references');
400
- mkdirSync(referenceDest, { recursive: true, mode: 0o700 });
401
- for (const name of readdirSync(references)) {
402
- if (name.endsWith('.md') && statSync(join(references, name)).isFile()) {
403
- copyFileSync(join(references, name), join(referenceDest, name));
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 (config.projects.length === 0) {
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 cloneDir = mkdtempSync(join(tmpdir(), 'devlog-commit-covers-'));
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, cloneDir], { encoding: 'utf8' });
1173
+ const cloneResult = spawnSync('git', ['clone', '--depth=1', '--branch', branch, cloneUrl, repoDir], { encoding: 'utf8' });
1133
1174
  if (cloneResult.status !== 0) {
1134
- rmSync(cloneDir, { recursive: true, force: true });
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', cloneDir, ...args], { encoding: 'utf8' });
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', cloneDir, 'push', '--no-tags', 'origin', branch], { encoding: 'utf8' });
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(cloneDir, { recursive: true, force: true });
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')} One-time setup: create your dev-log repo, install the skill, write config
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 self-contained HTML document (inline `<style>`, inline SVG for any artwork) that
15
- renders, at exactly 1600×900px, as a cover image for one dev log entry. Compose from these
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 built from inline SVG shapes (lines, arcs, polygons,
38
- simple geometric forms) that depicts that concept. Not a photo, not a stock icon,
39
- not a screenshot: a small original line-art scene, **editorial line art in ink with
40
- sparing orange**, in the spirit of a newspaper diagram or a technical schematic, using
41
- only the palette below.
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
- - No external resources of any kind no `<link>`, no `@import`, no remote `<img src>`,
155
- no web fonts, no raster images. All artwork is inline SVG built from basic shapes
156
- (`<path>`, `<circle>`, `<rect>`, `<line>`, `<polygon>`, `<polyline>`) everything must
157
- be inline HTML/CSS/SVG, hand-composed, not fetched or embedded from anywhere.
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.9.0 sha256:5904c52d4168 GENERATED by @natjswenson/press, do not edit -->
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 — the
180
- render is compressed with lossy PNG palette quantization afterward, and gradients band
181
- visibly under that compression while flat fills don't.
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 drawn from inline SVG primitives, not sourced from anywhere.
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.0",
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",
@@ -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-never-blocks-publish",
76
- "pattern": "[Nn]ever block[\\s\\S]{0,10}publish on a missing style guide",
77
- "rationale": "A missing/uninstalled style guide must degrade gracefully (no cover this run), never abort the whole publish."
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",