@natjswenson/devlog 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nate Swenson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,162 @@
1
+ # devlog
2
+
3
+ A Claude Code skill that turns your daily git commits into a published dev log — and a React example for displaying it on your site.
4
+
5
+ > **Build in public, automatically.** Make commits like you always do. Run `/devlog`. Today's work shows up on your site as a narrative entry, not raw commit messages.
6
+
7
+ ## Live example
8
+
9
+ The skill is in production at [natejswenson.io/devlog](https://natejswenson.io/devlog), publishing entries to [github.com/natejswenson/daily-dev-log](https://github.com/natejswenson/daily-dev-log).
10
+
11
+ ## How it works
12
+
13
+ 1. **You commit code** in your projects, like you already do.
14
+ 2. **Run `/devlog` in Claude Code.** The skill reads today's commits, writes a narrative markdown entry, and pushes it to your dev-log GitHub repo.
15
+ 3. **Your site fetches it.** Static `manifest.json` + per-day markdown files served from `raw.githubusercontent.com` — no backend needed.
16
+
17
+ ## Quick start
18
+
19
+ ```sh
20
+ npx @natjswenson/devlog init
21
+ ```
22
+
23
+ That command:
24
+ - Creates `<your-username>/daily-dev-log` on GitHub
25
+ - Installs the skill at `~/.claude/skills/devlog/`
26
+ - Writes `~/.claude/skills/devlog/config.json` with your answers
27
+
28
+ Then:
29
+ ```sh
30
+ npx @natjswenson/devlog preview
31
+ ```
32
+ to see your dev log rendered locally.
33
+
34
+ ## Prerequisites
35
+
36
+ - **Node 18+** — for the CLI and preview app
37
+ - **GitHub CLI** (`gh`), authenticated — used to create your dev-log repo and push entries
38
+ - **Claude Code** — to run the `/devlog` skill
39
+
40
+ ## What you end up with
41
+
42
+ ```
43
+ ~/.claude/skills/devlog/
44
+ ├── SKILL.md # The slash command
45
+ └── config.json # Your settings (target repo, projects, etc)
46
+
47
+ github.com/<you>/daily-dev-log/ # Created by `init`, populated by /devlog
48
+ ├── myproject/
49
+ │ ├── manifest.json
50
+ │ ├── 2026-05-01.md
51
+ │ └── ...
52
+ └── ...
53
+ ```
54
+
55
+ ## Manual setup (if you'd rather not use the CLI)
56
+
57
+ 1. **Create your dev-log repo:**
58
+ ```sh
59
+ gh repo create <you>/daily-dev-log --public --add-readme
60
+ ```
61
+ 2. **Install the skill:**
62
+ ```sh
63
+ mkdir -p ~/.claude/skills/devlog
64
+ curl -o ~/.claude/skills/devlog/SKILL.md \
65
+ https://raw.githubusercontent.com/natejswenson/devlog/main/SKILL.md
66
+ ```
67
+ 3. **Write your config** at `~/.claude/skills/devlog/config.json` — copy [`config.example.json`](./config.example.json) and fill in.
68
+
69
+ ## Add to your site
70
+
71
+ ### React (drop-in)
72
+
73
+ ```sh
74
+ cp -r examples/react/ your-site/src/devlog/
75
+ ```
76
+
77
+ Edit `your-site/src/devlog/devlog-config.js` to point at your repo, then mount:
78
+
79
+ ```jsx
80
+ import DevLogPage from './devlog/DevLogPage.jsx';
81
+
82
+ <DevLogPage project="myproject" />
83
+ ```
84
+
85
+ Full instructions: [`examples/react/README.md`](./examples/react/README.md).
86
+
87
+ ### No site yet?
88
+
89
+ The `preview/` directory is a complete deployable Vite app. Set `VITE_DEVLOG_OWNER` / `VITE_DEVLOG_REPO` / `VITE_DEVLOG_PROJECTS` env vars on Vercel, Netlify, or Cloudflare Pages, build with `vite build`, deploy `dist/`. Done — you have a public dev log at your own URL. See [`preview/README.md`](./preview/README.md).
90
+
91
+ ### Other stacks (Next, Astro, plain HTML, anything)
92
+
93
+ It's static JSON and Markdown on GitHub. Build whatever UI you want — see the **Data contract** below.
94
+
95
+ ## Data contract
96
+
97
+ The dev-log repo has this layout, all served as raw files from `https://raw.githubusercontent.com/<owner>/<repo>/main/`:
98
+
99
+ ```
100
+ <repo>/
101
+ └── <project-key>/
102
+ ├── manifest.json # Index of all entries (newest first)
103
+ ├── 2026-05-01.md # One entry per day
104
+ ├── 2026-04-30.md
105
+ └── ...
106
+ ```
107
+
108
+ **`manifest.json`:**
109
+ ```json
110
+ {
111
+ "entries": [
112
+ { "date": "2026-05-01", "file": "2026-05-01.md", "title": "...", "summary": "..." }
113
+ ]
114
+ }
115
+ ```
116
+
117
+ **Entry markdown:**
118
+ ```markdown
119
+ ---
120
+ title: "Concise day summary"
121
+ date: 2026-05-01
122
+ project: myproject
123
+ summary: "1-2 sentence summary"
124
+ ---
125
+
126
+ ## What I Built
127
+ Narrative paragraphs.
128
+
129
+ ## What's Next
130
+ Forward-looking note.
131
+
132
+ ## Public Commits
133
+ - [myproject] commit message ([abc1234](https://github.com/.../commit/abc1234567...))
134
+ ```
135
+
136
+ That's the entire contract.
137
+
138
+ ## Configuration reference
139
+
140
+ `~/.claude/skills/devlog/config.json`:
141
+
142
+ | Field | Type | Description |
143
+ |---|---|---|
144
+ | `targetRepo` | `"<owner>/<repo>"` | Repo where dev log entries are published. **You create this — `init` does it for you, or `gh repo create` manually.** |
145
+ | `gitAuthor` | string | Used as `git log --author=...` to find your commits. |
146
+ | `githubUser` | string | Your GitHub username (for constructing commit links). |
147
+ | `projects` | array | One entry per project you want dev logs for. |
148
+ | `projects[].key` | string | Subdirectory name in the dev-log repo + tab label on the UI. |
149
+ | `projects[].path` | string | Local filesystem path to the project. |
150
+ | `projects[].remote` | `"<owner>/<repo>"` | The project's GitHub remote, used to mark public commits and link them. |
151
+
152
+ See [`config.example.json`](./config.example.json) for a complete template.
153
+
154
+ ## Customization
155
+
156
+ - **Tweak the entry template:** edit `~/.claude/skills/devlog/SKILL.md` (Step 4 — generate the entry).
157
+ - **Tweak the UI:** override the `--devlog-*` CSS variables in `examples/react/DevLogPage.css` to match your theme. Or build your own UI against the data contract above.
158
+ - **Add more projects:** edit `~/.claude/skills/devlog/config.json` directly.
159
+
160
+ ## License
161
+
162
+ MIT
package/SKILL.md ADDED
@@ -0,0 +1,174 @@
1
+ ---
2
+ name: devlog
3
+ description: Generate a daily dev log entry from today's git commits and publish to GitHub
4
+ user_invocable: true
5
+ ---
6
+
7
+ # /devlog — Daily Dev Log Generator
8
+
9
+ You are generating a daily dev log entry from the user's git commits and publishing it to a GitHub repo configured in `~/.claude/skills/devlog/config.json`.
10
+
11
+ Usage: `/devlog` (all configured projects) or `/devlog <project-key>` (single project)
12
+
13
+ ## Configuration
14
+
15
+ This skill is configuration-driven. All user-specific values (target repo, git author, project list) live in `~/.claude/skills/devlog/config.json`.
16
+
17
+ Schema:
18
+
19
+ ```json
20
+ {
21
+ "targetRepo": "<owner>/<repo>",
22
+ "gitAuthor": "Your Name",
23
+ "githubUser": "<your-github-username>",
24
+ "projects": [
25
+ {
26
+ "key": "project-key",
27
+ "path": "/absolute/path/to/project",
28
+ "remote": "<owner>/<repo>"
29
+ }
30
+ ]
31
+ }
32
+ ```
33
+
34
+ ## Step 0: Load and validate config
35
+
36
+ ```bash
37
+ cat ~/.claude/skills/devlog/config.json
38
+ ```
39
+
40
+ If the file does not exist or cannot be parsed, stop and tell the user:
41
+
42
+ > No devlog config found at `~/.claude/skills/devlog/config.json`. Run `npx @natjswenson/devlog init` to set up, or copy `config.example.json` from the devlog repo and fill it in manually.
43
+
44
+ Validate that `targetRepo`, `gitAuthor`, `githubUser`, and `projects` (non-empty array) are all present. Each project must have `key`, `path`, and `remote`.
45
+
46
+ ## Step 1: Determine scope
47
+
48
+ - If the user passed a project argument (e.g. `/devlog myproject`), filter `projects` to that one. If the key is not in the registry, list available keys and stop.
49
+ - If no argument, run for **all projects** in `config.projects`. Generate a separate entry per project (only for projects that have commits today). Use a single clone of the target repo and a single commit/push for all entries.
50
+
51
+ ## Step 2: Gather today's commits
52
+
53
+ For each project in scope, run:
54
+
55
+ ```bash
56
+ cd <project.path> && git log --author="<config.gitAuthor>" --since="midnight" --format="%H|%s|%D" --all
57
+ ```
58
+
59
+ If no commits are found for a project, skip it. If no commits are found across all projects, inform the user and stop.
60
+
61
+ ## Step 3: Check for public commits
62
+
63
+ For each commit, check if it's on the `main` branch and if the remote is public:
64
+
65
+ ```bash
66
+ cd <project.path> && git remote get-url origin
67
+ git branch --contains <hash> -r 2>/dev/null | grep -q 'origin/main'
68
+ ```
69
+
70
+ - If the remote URL matches `<project.remote>` (i.e. `github.com/<project.remote>` or the SSH equivalent) and the commit is on `origin/main`, it's a public commit — include a link using `https://github.com/<project.remote>/commit/<hash>`.
71
+ - Otherwise, describe the feature without linking.
72
+
73
+ ## Step 4: Generate the entry
74
+
75
+ Based on the commit messages, generate a markdown entry with this structure:
76
+
77
+ ```markdown
78
+ ---
79
+ title: "<concise title summarizing the day's work>"
80
+ date: YYYY-MM-DD
81
+ project: <project.key>
82
+ summary: "<1-2 sentence summary>"
83
+ ---
84
+
85
+ ## What I Built
86
+
87
+ <Narrative paragraphs about features implemented. Focus on WHAT was built and WHY, not raw commit messages. Group related commits into coherent feature descriptions. Write in first person, casual but professional tone.>
88
+
89
+ ## What's Next
90
+
91
+ <Brief 1-2 sentence forward-looking note based on the trajectory of current work.>
92
+
93
+ ## Public Commits
94
+
95
+ - [<project.key>] commit message ([short-hash](https://github.com/<project.remote>/commit/full-hash))
96
+ ```
97
+
98
+ **Important rules for content generation:**
99
+ - The "What I Built" section is a NARRATIVE, not a commit list. Describe features, not individual commits.
100
+ - Only include "Public Commits" section if there are commits on `main` of a public repo.
101
+ - "What's Next" should be a reasonable inference from the work done today.
102
+ - Tone: first person, casual but professional, like a senior engineer's standup notes for a public audience.
103
+
104
+ ## Step 5: Check for existing entry (append mode)
105
+
106
+ For each project with commits, check if an entry for today already exists:
107
+
108
+ ```bash
109
+ gh api repos/<config.targetRepo>/contents/<project.key>/YYYY-MM-DD.md --jq '.content' 2>/dev/null | base64 -d
110
+ ```
111
+
112
+ **If the entry exists:**
113
+ 1. Fetch and read the existing content
114
+ 2. Keep the original frontmatter (title, date, project, summary) unchanged
115
+ 3. Append new content under an `## Update — HH:MM AM/PM` heading
116
+ 4. Merge any new public commits into the existing "Public Commits" section
117
+ 5. Update "What's Next" with the latest context
118
+
119
+ **If the entry does NOT exist:**
120
+ 1. Create a new file with the full structure above
121
+
122
+ ## Step 6: Push to GitHub
123
+
124
+ Clone the repo once, write all project entries, then push:
125
+
126
+ ```bash
127
+ # Clone to temp directory
128
+ TMPDIR=$(mktemp -d)
129
+ cd "$TMPDIR"
130
+ git clone https://github.com/<config.targetRepo>.git
131
+ cd $(basename <config.targetRepo>)
132
+
133
+ # For each project with commits:
134
+ # - Create directory if needed: mkdir -p <project.key>/
135
+ # - Write the entry file to <project.key>/YYYY-MM-DD.md
136
+ # - Update <project.key>/manifest.json
137
+ # - Read manifest, add/update entry in entries array (newest first)
138
+ # - Entry object: { "date": "YYYY-MM-DD", "file": "YYYY-MM-DD.md", "title": "...", "summary": "..." }
139
+ # - If appending to existing entry, update title/summary only if changed
140
+ # - If manifest doesn't exist, create it as { "entries": [...] }
141
+
142
+ # Stage all changed project directories
143
+ git add .
144
+
145
+ # Single commit covering all projects
146
+ git commit -m "devlog: add entries for YYYY-MM-DD"
147
+ git push origin main
148
+
149
+ # Cleanup
150
+ rm -rf "$TMPDIR"
151
+ ```
152
+
153
+ ## Step 7: Confirm
154
+
155
+ After pushing, output a summary for each project:
156
+
157
+ ```
158
+ Dev log entries published for <Month Day, Year>
159
+
160
+ Project: <project.key>
161
+ Commits summarized: <count>
162
+ Public commits linked: <count>
163
+ URL: https://github.com/<config.targetRepo>/blob/main/<project.key>/YYYY-MM-DD.md
164
+ ```
165
+
166
+ ## Edge Cases
167
+
168
+ - **No commits today:** Stop with a message. Do not create an empty entry.
169
+ - **Project path doesn't exist:** Error with "Repository not found at <project.path>" and skip that project.
170
+ - **Push fails:** Inform the user of the error. Do not retry automatically.
171
+ - **All WIP/fixup commits:** Still generate a narrative about the intent of the work.
172
+ - **Manifest doesn't exist:** Create it with the standard structure.
173
+ - **Unknown project argument:** List available project keys from `config.projects`.
174
+ - **Config missing or invalid:** Stop at Step 0 with the setup instructions above.
package/bin/devlog.js ADDED
@@ -0,0 +1,316 @@
1
+ #!/usr/bin/env node
2
+ import { spawn, execSync } from 'node:child_process';
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs';
4
+ import { homedir } from 'node:os';
5
+ import { dirname, join, resolve, basename } from 'node:path';
6
+ import { fileURLToPath, pathToFileURL } from 'node:url';
7
+ import { createRequire } from 'node:module';
8
+ import prompts from 'prompts';
9
+ import kleur from 'kleur';
10
+
11
+ const require = createRequire(import.meta.url);
12
+ const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
13
+ const SKILL_SRC = join(PACKAGE_ROOT, 'SKILL.md');
14
+ const CONFIG_DIR = join(homedir(), '.claude', 'skills', 'devlog');
15
+ const CONFIG_PATH = join(CONFIG_DIR, 'config.json');
16
+ const SKILL_DEST = join(CONFIG_DIR, 'SKILL.md');
17
+ const PREVIEW_DIR = join(PACKAGE_ROOT, 'preview');
18
+
19
+ const log = {
20
+ info: (msg) => console.log(msg),
21
+ ok: (msg) => console.log(kleur.green('✓ ') + msg),
22
+ warn: (msg) => console.log(kleur.yellow('! ') + msg),
23
+ err: (msg) => console.error(kleur.red('✗ ') + msg),
24
+ step: (msg) => console.log(kleur.cyan('→ ') + msg),
25
+ };
26
+
27
+ function readPackageVersion() {
28
+ const pkg = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8'));
29
+ return pkg.version;
30
+ }
31
+
32
+ function tryExec(cmd) {
33
+ try {
34
+ return execSync(cmd, { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8' }).trim();
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ function expandHome(p) {
41
+ if (!p) return p;
42
+ if (p === '~') return homedir();
43
+ if (p.startsWith('~/')) return join(homedir(), p.slice(2));
44
+ return p;
45
+ }
46
+
47
+ async function preflight() {
48
+ const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
49
+ if (nodeMajor < 18) {
50
+ log.err(`Node 18+ required (you have ${process.versions.node}).`);
51
+ process.exit(1);
52
+ }
53
+
54
+ const ghVersion = tryExec('gh --version');
55
+ if (!ghVersion) {
56
+ log.err('GitHub CLI (`gh`) is not installed.');
57
+ log.info('Install: https://cli.github.com/');
58
+ process.exit(1);
59
+ }
60
+
61
+ const ghAuth = tryExec('gh auth status');
62
+ if (!ghAuth) {
63
+ log.err('GitHub CLI is not authenticated.');
64
+ log.info('Run: gh auth login');
65
+ process.exit(1);
66
+ }
67
+ }
68
+
69
+ function detectGhUser() {
70
+ const out = tryExec('gh api user --jq .login');
71
+ return out || null;
72
+ }
73
+
74
+ function detectGitName() {
75
+ return tryExec('git config --global user.name');
76
+ }
77
+
78
+ function detectProjectRemote(path) {
79
+ const url = tryExec(`git -C "${path}" remote get-url origin`);
80
+ if (!url) return null;
81
+ const m = url.match(/[:/]([^/:]+\/[^/]+?)(?:\.git)?$/);
82
+ return m ? m[1] : null;
83
+ }
84
+
85
+ async function confirmOverwrite(label, path) {
86
+ if (!existsSync(path)) return true;
87
+ const { ok } = await prompts({
88
+ type: 'confirm',
89
+ name: 'ok',
90
+ message: `${label} already exists at ${path}. Overwrite?`,
91
+ initial: false,
92
+ });
93
+ return ok === true;
94
+ }
95
+
96
+ async function cmdInit() {
97
+ log.info(kleur.bold('\ndevlog setup\n'));
98
+ await preflight();
99
+
100
+ const defaults = {
101
+ gitAuthor: detectGitName() || '',
102
+ githubUser: detectGhUser() || '',
103
+ targetRepoName: 'daily-dev-log',
104
+ };
105
+
106
+ const answers = await prompts([
107
+ {
108
+ type: 'text',
109
+ name: 'gitAuthor',
110
+ message: 'Your name (used to filter `git log --author`):',
111
+ initial: defaults.gitAuthor,
112
+ validate: (v) => v.trim().length > 0 || 'Required',
113
+ },
114
+ {
115
+ type: 'text',
116
+ name: 'githubUser',
117
+ message: 'Your GitHub username:',
118
+ initial: defaults.githubUser,
119
+ validate: (v) => /^[a-z0-9-]+$/i.test(v.trim()) || 'Invalid username',
120
+ },
121
+ {
122
+ type: 'text',
123
+ name: 'targetRepoName',
124
+ message: 'Name of the repo where dev logs will be published:',
125
+ initial: defaults.targetRepoName,
126
+ validate: (v) => /^[a-z0-9._-]+$/i.test(v.trim()) || 'Invalid repo name',
127
+ },
128
+ {
129
+ type: 'confirm',
130
+ name: 'registerProject',
131
+ message: 'Register a project now? (you can add more later by editing config.json)',
132
+ initial: true,
133
+ },
134
+ ], { onCancel: () => process.exit(1) });
135
+
136
+ let projectAnswers = null;
137
+ if (answers.registerProject) {
138
+ const cwd = process.cwd();
139
+ const cwdRemote = detectProjectRemote(cwd);
140
+ projectAnswers = await prompts([
141
+ {
142
+ type: 'text',
143
+ name: 'path',
144
+ message: 'Project absolute path:',
145
+ initial: cwd,
146
+ validate: (v) => existsSync(expandHome(v)) || 'Path does not exist',
147
+ },
148
+ {
149
+ type: 'text',
150
+ name: 'key',
151
+ message: 'Project key (used as dev-log subdir name):',
152
+ initial: (prev) => basename(expandHome(prev || cwd)),
153
+ validate: (v) => /^[a-z0-9._-]+$/i.test(v.trim()) || 'Invalid key',
154
+ },
155
+ {
156
+ type: 'text',
157
+ name: 'remote',
158
+ message: 'Project GitHub remote (<owner>/<repo>):',
159
+ initial: (_prev, values) => detectProjectRemote(expandHome(values.path)) || cwdRemote || `${answers.githubUser}/${basename(expandHome(values.path))}`,
160
+ validate: (v) => /^[\w.-]+\/[\w.-]+$/.test(v.trim()) || 'Expected <owner>/<repo>',
161
+ },
162
+ ], { onCancel: () => process.exit(1) });
163
+ }
164
+
165
+ const targetRepo = `${answers.githubUser}/${answers.targetRepoName}`;
166
+ const config = {
167
+ targetRepo,
168
+ gitAuthor: answers.gitAuthor,
169
+ githubUser: answers.githubUser,
170
+ projects: projectAnswers ? [{
171
+ key: projectAnswers.key,
172
+ path: expandHome(projectAnswers.path),
173
+ remote: projectAnswers.remote,
174
+ }] : [],
175
+ };
176
+
177
+ log.info('\n' + kleur.bold('Summary:'));
178
+ log.info(` Target repo: ${kleur.cyan(`github.com/${targetRepo}`)}`);
179
+ log.info(` Git author: ${config.gitAuthor}`);
180
+ log.info(` GitHub user: ${config.githubUser}`);
181
+ log.info(` Projects: ${config.projects.length === 0 ? '(none — add later)' : config.projects.map(p => p.key).join(', ')}`);
182
+ log.info(` Skill location: ${CONFIG_DIR}`);
183
+
184
+ const { proceed } = await prompts({
185
+ type: 'confirm',
186
+ name: 'proceed',
187
+ message: 'Continue?',
188
+ initial: true,
189
+ }, { onCancel: () => process.exit(1) });
190
+ if (!proceed) process.exit(0);
191
+
192
+ log.info('');
193
+
194
+ const repoExists = tryExec(`gh repo view ${targetRepo} --json name`) !== null;
195
+ if (repoExists) {
196
+ log.warn(`Repo github.com/${targetRepo} already exists. Will use it as-is.`);
197
+ } else {
198
+ log.step(`Creating github.com/${targetRepo}...`);
199
+ try {
200
+ execSync(`gh repo create ${targetRepo} --public --description "Daily dev log" --add-readme`, {
201
+ stdio: 'inherit',
202
+ });
203
+ log.ok('Repo created');
204
+ } catch {
205
+ log.err('Failed to create repo. Check `gh` permissions.');
206
+ process.exit(1);
207
+ }
208
+ }
209
+
210
+ if (!existsSync(CONFIG_DIR)) {
211
+ mkdirSync(CONFIG_DIR, { recursive: true });
212
+ log.ok(`Created ${CONFIG_DIR}`);
213
+ }
214
+
215
+ if (await confirmOverwrite('SKILL.md', SKILL_DEST)) {
216
+ copyFileSync(SKILL_SRC, SKILL_DEST);
217
+ log.ok(`Installed SKILL.md → ${SKILL_DEST}`);
218
+ } else {
219
+ log.warn('Skipped SKILL.md');
220
+ }
221
+
222
+ if (await confirmOverwrite('config.json', CONFIG_PATH)) {
223
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
224
+ log.ok(`Wrote config → ${CONFIG_PATH}`);
225
+ } else {
226
+ log.warn('Skipped config.json');
227
+ }
228
+
229
+ log.info('\n' + kleur.bold().green('Done.') + '\n');
230
+ log.info('Next steps:');
231
+ log.info(` 1. ${config.projects.length === 0 ? 'Edit config.json to register projects' : '(Optional) edit config.json to register more projects'}`);
232
+ log.info(' 2. Make some commits in a registered project');
233
+ log.info(' 3. In Claude Code, run: /devlog');
234
+ log.info(' 4. Preview locally: npx @natjswenson/devlog preview');
235
+ log.info('');
236
+ }
237
+
238
+ async function cmdPreview() {
239
+ if (!existsSync(CONFIG_PATH)) {
240
+ log.err(`No config found at ${CONFIG_PATH}`);
241
+ log.info('Run `npx @natjswenson/devlog init` first.');
242
+ process.exit(1);
243
+ }
244
+
245
+ let config;
246
+ try {
247
+ config = JSON.parse(readFileSync(CONFIG_PATH, 'utf8'));
248
+ } catch (e) {
249
+ log.err(`Failed to parse config: ${e.message}`);
250
+ process.exit(1);
251
+ }
252
+
253
+ const [owner, repo] = (config.targetRepo || '').split('/');
254
+ if (!owner || !repo) {
255
+ log.err('config.targetRepo is not in <owner>/<repo> format');
256
+ process.exit(1);
257
+ }
258
+
259
+ const projects = (config.projects || []).map((p) => ({ key: p.key, label: p.key }));
260
+
261
+ log.step(`Launching preview against github.com/${config.targetRepo}...`);
262
+
263
+ const vitePkgPath = require.resolve('vite/package.json');
264
+ const vitePkg = JSON.parse(readFileSync(vitePkgPath, 'utf8'));
265
+ const viteBin = resolve(dirname(vitePkgPath), vitePkg.bin?.vite || 'bin/vite.js');
266
+
267
+ const proc = spawn(process.execPath, [viteBin], {
268
+ cwd: PREVIEW_DIR,
269
+ stdio: 'inherit',
270
+ env: {
271
+ ...process.env,
272
+ VITE_DEVLOG_OWNER: owner,
273
+ VITE_DEVLOG_REPO: repo,
274
+ VITE_DEVLOG_BRANCH: 'main',
275
+ VITE_DEVLOG_PROJECTS: JSON.stringify(projects),
276
+ },
277
+ });
278
+ proc.on('exit', (code) => process.exit(code ?? 0));
279
+ }
280
+
281
+ function printHelp() {
282
+ console.log(`
283
+ ${kleur.bold('@natjswenson/devlog')} — daily dev log generator
284
+
285
+ Usage:
286
+ npx @natjswenson/devlog init Set up the skill, create your dev-log repo, write config
287
+ npx @natjswenson/devlog preview Run a local preview of your published dev log
288
+ npx @natjswenson/devlog --help
289
+ npx @natjswenson/devlog --version
290
+
291
+ Docs: https://github.com/natejswenson/devlog
292
+ `);
293
+ }
294
+
295
+ const arg = process.argv[2];
296
+ switch (arg) {
297
+ case 'init':
298
+ cmdInit();
299
+ break;
300
+ case 'preview':
301
+ cmdPreview();
302
+ break;
303
+ case '-v':
304
+ case '--version':
305
+ console.log(readPackageVersion());
306
+ break;
307
+ case undefined:
308
+ case '-h':
309
+ case '--help':
310
+ printHelp();
311
+ break;
312
+ default:
313
+ log.err(`Unknown command: ${arg}`);
314
+ printHelp();
315
+ process.exit(1);
316
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "targetRepo": "yourusername/daily-dev-log",
3
+ "gitAuthor": "Your Name",
4
+ "githubUser": "yourusername",
5
+ "projects": [
6
+ {
7
+ "key": "midnight-side-quest",
8
+ "path": "/Users/yourusername/code/midnight-side-quest",
9
+ "remote": "yourusername/midnight-side-quest"
10
+ },
11
+ {
12
+ "key": "todays-existential-crisis",
13
+ "path": "/Users/yourusername/code/todays-existential-crisis",
14
+ "remote": "yourusername/todays-existential-crisis"
15
+ }
16
+ ]
17
+ }