devlyn-cli 0.0.5 → 0.0.6

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/README.md CHANGED
@@ -49,7 +49,7 @@ npx devlyn-cli list
49
49
  your-project/
50
50
  ├── .claude/
51
51
  │ ├── commands/ # 9 slash commands
52
- │ ├── skills/ # 3 reusable AI skills
52
+ │ ├── skills/ # 2 core skills + optional addons
53
53
  │ ├── templates/ # Document templates
54
54
  │ └── commit-conventions.md # Commit message standards
55
55
  └── CLAUDE.md # Project-level instructions
@@ -78,7 +78,6 @@ Skills are triggered automatically based on conversation context.
78
78
  | Skill | Description |
79
79
  |---|---|
80
80
  | `investigate` | Parallel code exploration with structured progress checkpoints |
81
- | `prompt-engineering` | Claude prompt optimization based on Anthropic best practices |
82
81
  | `feature-gap-analysis` | Identify missing features by comparing against baselines or competitors |
83
82
 
84
83
  ## Templates
@@ -89,17 +88,17 @@ Skills are triggered automatically based on conversation context.
89
88
  | `template-feature.spec.md` | Feature specification template |
90
89
  | `prompt-templates.md` | Reusable prompt snippets for common tasks |
91
90
 
92
- ## Optional Skill Packs
91
+ ## Optional Skills & Packs
93
92
 
94
- During installation, you can choose to add third-party skill packs:
93
+ During installation, you can choose to add optional skills and third-party skill packs:
95
94
 
96
- ```bash
97
- # Vercel — React, Next.js, and React Native best practices
98
- npx skills add vercel-labs/agent-skills
99
-
100
- # Supabase Supabase integration patterns
101
- npx skills add supabase/agent-skills
102
- ```
95
+ | Addon | Type | Description |
96
+ |---|---|---|
97
+ | `cloudflare-nextjs-setup` | skill | Cloudflare Workers + Next.js deployment with OpenNext |
98
+ | `prompt-engineering` | skill | Claude 4 prompt optimization using Anthropic best practices |
99
+ | `vercel-labs/agent-skills` | pack | React, Next.js, React Native best practices |
100
+ | `supabase/agent-skills` | pack | Supabase integration patterns |
101
+ | `coreyhaines31/marketingskills` | pack | Marketing automation and content skills |
103
102
 
104
103
  ## How It Works
105
104
 
package/bin/devlyn.js CHANGED
@@ -6,6 +6,7 @@ const readline = require('readline');
6
6
  const { execSync } = require('child_process');
7
7
 
8
8
  const CONFIG_SOURCE = path.join(__dirname, '..', 'config');
9
+ const OPTIONAL_SKILLS_SOURCE = path.join(__dirname, '..', 'optional-skills');
9
10
  const PKG = require('../package.json');
10
11
 
11
12
  function getTargetDir() {
@@ -56,10 +57,14 @@ ${g} v${PKG.version} ${COLORS.dim}· ${k}🍩 by Donut Studio${r}
56
57
  console.log(logo);
57
58
  }
58
59
 
59
- const SKILL_PACKS = [
60
- { name: 'vercel-labs/agent-skills', desc: 'React, Next.js, React Native best practices' },
61
- { name: 'supabase/agent-skills', desc: 'Supabase integration patterns' },
62
- { name: 'coreyhaines31/marketingskills', desc: 'Marketing automation and content skills' },
60
+ const OPTIONAL_ADDONS = [
61
+ // Local optional skills (copied to .claude/skills/)
62
+ { name: 'cloudflare-nextjs-setup', desc: 'Cloudflare Workers + Next.js deployment with OpenNext', type: 'local' },
63
+ { name: 'prompt-engineering', desc: 'Claude 4 prompt optimization using Anthropic best practices', type: 'local' },
64
+ // External skill packs (installed via npx skills add)
65
+ { name: 'vercel-labs/agent-skills', desc: 'React, Next.js, React Native best practices', type: 'external' },
66
+ { name: 'supabase/agent-skills', desc: 'Supabase integration patterns', type: 'external' },
67
+ { name: 'coreyhaines31/marketingskills', desc: 'Marketing automation and content skills', type: 'external' },
63
68
  ];
64
69
 
65
70
  function log(msg, color = 'reset') {
@@ -153,6 +158,14 @@ function listContents() {
153
158
  }
154
159
  }
155
160
 
161
+ // List optional addons
162
+ log('\n📦 Optional Addons:', 'blue');
163
+ OPTIONAL_ADDONS.forEach((addon) => {
164
+ const tag = addon.type === 'local' ? `${COLORS.magenta}skill${COLORS.reset}` : `${COLORS.cyan}pack${COLORS.reset}`;
165
+ log(` ${COLORS.green}${addon.name}${COLORS.reset} ${COLORS.dim}[${tag}${COLORS.dim}]${COLORS.reset}`);
166
+ log(` ${COLORS.dim}${addon.desc}${COLORS.reset}`);
167
+ });
168
+
156
169
  log('');
157
170
  }
158
171
 
@@ -197,7 +210,8 @@ function multiSelect(items) {
197
210
  const checkbox = selected.has(i) ? `${COLORS.green}◉${COLORS.reset}` : `${COLORS.dim}○${COLORS.reset}`;
198
211
  const pointer = i === cursor ? `${COLORS.cyan}❯${COLORS.reset}` : ' ';
199
212
  const name = i === cursor ? `${COLORS.cyan}${item.name}${COLORS.reset}` : item.name;
200
- console.log(`${pointer} ${checkbox} ${name}`);
213
+ const tag = item.type === 'local' ? `${COLORS.magenta}skill${COLORS.reset}` : `${COLORS.cyan}pack${COLORS.reset}`;
214
+ console.log(`${pointer} ${checkbox} ${name} ${COLORS.dim}[${tag}${COLORS.dim}]${COLORS.reset}`);
201
215
  console.log(` ${COLORS.dim}${item.desc}${COLORS.reset}`);
202
216
  });
203
217
  };
@@ -267,6 +281,21 @@ function multiSelect(items) {
267
281
  });
268
282
  }
269
283
 
284
+ function installLocalSkill(skillName) {
285
+ const src = path.join(OPTIONAL_SKILLS_SOURCE, skillName);
286
+ const targetDir = getTargetDir();
287
+ const dest = path.join(targetDir, 'skills', skillName);
288
+
289
+ if (!fs.existsSync(src)) {
290
+ log(` ⚠️ Skill "${skillName}" not found`, 'yellow');
291
+ return false;
292
+ }
293
+
294
+ log(`\n🛠️ Installing ${skillName}...`, 'cyan');
295
+ copyRecursive(src, dest, targetDir);
296
+ return true;
297
+ }
298
+
270
299
  function installSkillPack(packName) {
271
300
  try {
272
301
  log(`\n📦 Installing ${packName}...`, 'cyan');
@@ -278,6 +307,13 @@ function installSkillPack(packName) {
278
307
  }
279
308
  }
280
309
 
310
+ function installAddon(addon) {
311
+ if (addon.type === 'local') {
312
+ return installLocalSkill(addon.name);
313
+ }
314
+ return installSkillPack(addon.name);
315
+ }
316
+
281
317
  async function init(skipPrompts = false) {
282
318
  showLogo();
283
319
  log('─'.repeat(44), 'dim');
@@ -304,26 +340,30 @@ async function init(skipPrompts = false) {
304
340
 
305
341
  // Skip prompts if -y flag or non-interactive
306
342
  if (skipPrompts || !process.stdin.isTTY) {
307
- log('\n💡 Add skill packs later with:', 'dim');
308
- SKILL_PACKS.forEach((pack) => {
309
- log(` npx skills add ${pack.name}`, 'dim');
343
+ log('\n💡 Add optional skills & packs later:', 'dim');
344
+ OPTIONAL_ADDONS.forEach((addon) => {
345
+ if (addon.type === 'local') {
346
+ log(` npx devlyn-cli (select "${addon.name}" during install)`, 'dim');
347
+ } else {
348
+ log(` npx skills add ${addon.name}`, 'dim');
349
+ }
310
350
  });
311
351
  log('');
312
352
  return;
313
353
  }
314
354
 
315
- // Ask about skill packs
316
- log('\n📚 Optional skill packs:\n', 'blue');
355
+ // Ask about optional addons (local skills + external packs)
356
+ log('\n📚 Optional skills & packs:\n', 'blue');
317
357
 
318
- const selectedPacks = await multiSelect(SKILL_PACKS);
358
+ const selectedAddons = await multiSelect(OPTIONAL_ADDONS);
319
359
 
320
- if (selectedPacks.length > 0) {
321
- for (const pack of selectedPacks) {
322
- installSkillPack(pack.name);
360
+ if (selectedAddons.length > 0) {
361
+ for (const addon of selectedAddons) {
362
+ installAddon(addon);
323
363
  }
324
364
  } else {
325
- log('💡 No skill packs selected', 'dim');
326
- log(' Add later with: npx skills add <pack-name>\n', 'dim');
365
+ log('💡 No optional addons selected', 'dim');
366
+ log(' Run again to add them later\n', 'dim');
327
367
  }
328
368
 
329
369
  log('\n✨ All done!', 'green');
@@ -337,8 +377,12 @@ function showHelp() {
337
377
  log(' npx devlyn-cli list List available commands & templates');
338
378
  log(' npx devlyn-cli -y Install without prompts');
339
379
  log(' npx devlyn-cli --help Show this help\n');
340
- log('Skill packs:', 'green');
341
- SKILL_PACKS.forEach((pack) => {
380
+ log('Optional skills (select during install):', 'green');
381
+ OPTIONAL_ADDONS.filter((a) => a.type === 'local').forEach((skill) => {
382
+ log(` ${skill.name} ${COLORS.dim}${skill.desc}${COLORS.reset}`);
383
+ });
384
+ log('\nExternal skill packs:', 'green');
385
+ OPTIONAL_ADDONS.filter((a) => a.type === 'external').forEach((pack) => {
342
386
  log(` npx skills add ${pack.name}`);
343
387
  });
344
388
  log('');
@@ -0,0 +1,286 @@
1
+ ---
2
+ name: cloudflare-nextjs-setup
3
+ description: Set up Cloudflare Workers deployment for an existing Next.js project using OpenNext. Triggers on "deploy to Cloudflare", "set up Cloudflare Workers", "Cloudflare deployment", "add Cloudflare to this project".
4
+ argument-hint: "[project-name]"
5
+ disable-model-invocation: true
6
+ ---
7
+
8
+ # Cloudflare Workers + Next.js Setup
9
+
10
+ Set up Cloudflare Workers deployment for an existing Next.js project using OpenNext.
11
+
12
+ ## Procedure
13
+
14
+ Follow these 8 steps in order. Read existing files before modifying them.
15
+
16
+ ### Step 1: Detect Environment
17
+
18
+ Before making any changes, gather project context:
19
+
20
+ 1. **Package manager** — Check for `pnpm-lock.yaml`, `yarn.lock`, `bun.lockb`, or `package-lock.json` (in that priority order)
21
+ 2. **Next.js config format** — Check for `next.config.ts`, `next.config.mjs`, or `next.config.js`
22
+ 3. **Existing Cloudflare config** — Check for `wrangler.jsonc`, `wrangler.toml`, `wrangler.json`, or `open-next.config.ts`. If found, warn the user and ask before overwriting
23
+ 4. **Monorepo detection** — Check if `package.json` has `workspaces` field or if a root `pnpm-workspace.yaml` exists. If monorepo, determine which directory contains the Next.js app (look for `next.config.*`)
24
+ 5. **Project name** — Use `$ARGUMENTS` if provided, otherwise extract `name` from the app's `package.json`
25
+
26
+ Store results for use in subsequent steps. All config files go in the **Next.js app directory** (not the monorepo root).
27
+
28
+ ### Step 2: Install Dependencies
29
+
30
+ Install using the detected package manager. Both packages go in the Next.js app directory:
31
+
32
+ - `@opennextjs/cloudflare` — as a **dependency** (required at runtime)
33
+ - `wrangler` — as a **devDependency** (CLI tooling only)
34
+
35
+ Examples:
36
+ ```bash
37
+ # pnpm (with workspace filter if monorepo)
38
+ pnpm add @opennextjs/cloudflare
39
+ pnpm add -D wrangler
40
+
41
+ # npm
42
+ npm install @opennextjs/cloudflare
43
+ npm install -D wrangler
44
+
45
+ # yarn
46
+ yarn add @opennextjs/cloudflare
47
+ yarn add -D wrangler
48
+
49
+ # bun
50
+ bun add @opennextjs/cloudflare
51
+ bun add -D wrangler
52
+ ```
53
+
54
+ ### Step 3: Create open-next.config.ts
55
+
56
+ Create `open-next.config.ts` in the Next.js app directory with minimal config:
57
+
58
+ ```ts
59
+ import { defineCloudflareConfig } from "@opennextjs/cloudflare";
60
+
61
+ export default defineCloudflareConfig({});
62
+ ```
63
+
64
+ This is the minimal working config. Advanced options (R2 cache, custom bindings) can be added later.
65
+
66
+ ### Step 4: Create wrangler.jsonc
67
+
68
+ Create `wrangler.jsonc` in the Next.js app directory. Use today's date for `compatibility_date` and the project name from Step 1:
69
+
70
+ ```jsonc
71
+ {
72
+ "$schema": "node_modules/wrangler/config-schema.json",
73
+ "main": ".open-next/worker.js",
74
+ "name": "<PROJECT_NAME>",
75
+ "compatibility_date": "<TODAY_YYYY-MM-DD>",
76
+ "compatibility_flags": ["nodejs_compat"],
77
+ "assets": {
78
+ "directory": ".open-next/assets",
79
+ "binding": "ASSETS"
80
+ }
81
+ // Non-secret env vars can be added here:
82
+ // "vars": {
83
+ // "EXAMPLE_VAR": "value"
84
+ // }
85
+ // Secrets must be set via: npx wrangler secret put <KEY_NAME>
86
+ }
87
+ ```
88
+
89
+ **CRITICAL**: The `"nodejs_compat"` compatibility flag is **required**. Without it, Node.js APIs (crypto, buffer, etc.) will fail at runtime with cryptic errors.
90
+
91
+ ### Step 5: Modify next.config
92
+
93
+ Add `initOpenNextCloudflareForDev()` to the **top** of the Next.js config file. This call MUST be:
94
+ - At **module level** (not inside a function, not conditional)
95
+ - **Before** any other config logic
96
+ - An **import + call** pattern, not dynamic import
97
+
98
+ Read the existing config file first, then add the import and call at the very top:
99
+
100
+ **For `.mjs` or `.js` (ESM):**
101
+ ```js
102
+ import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";
103
+
104
+ initOpenNextCloudflareForDev();
105
+
106
+ // ... rest of existing config unchanged ...
107
+ ```
108
+
109
+ **For `.ts`:**
110
+ ```ts
111
+ import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";
112
+
113
+ initOpenNextCloudflareForDev();
114
+
115
+ // ... rest of existing config unchanged ...
116
+ ```
117
+
118
+ **For `.js` (CommonJS — rare with modern Next.js):**
119
+ ```js
120
+ const { initOpenNextCloudflareForDev } = require("@opennextjs/cloudflare");
121
+
122
+ initOpenNextCloudflareForDev();
123
+
124
+ // ... rest of existing config unchanged ...
125
+ ```
126
+
127
+ **GOTCHA**: `initOpenNextCloudflareForDev()` must execute at module evaluation time. Do NOT wrap it in `if (process.env.NODE_ENV === 'development')` or any other conditional — it handles environment detection internally.
128
+
129
+ ### Step 6: Add Package.json Scripts
130
+
131
+ Add these scripts to the Next.js app's `package.json`. Read the file first and merge with existing scripts — do not overwrite:
132
+
133
+ ```json
134
+ {
135
+ "scripts": {
136
+ "build:worker": "opennextjs-cloudflare build",
137
+ "preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
138
+ "deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy"
139
+ }
140
+ }
141
+ ```
142
+
143
+ If `build:worker`, `preview`, or `deploy` scripts already exist, warn the user and ask before overwriting.
144
+
145
+ ### Step 7: Update .gitignore
146
+
147
+ Add Cloudflare-specific entries to the project's `.gitignore` (either the Next.js app's or the repo root, whichever exists). Only add entries that are not already present:
148
+
149
+ ```gitignore
150
+ # cloudflare
151
+ .open-next/
152
+ .dev.vars
153
+ ```
154
+
155
+ - `.open-next/` — build output directory (large, regenerated on every build)
156
+ - `.dev.vars` — local secrets file (equivalent of `.env` for Wrangler)
157
+
158
+ ### Step 8: Guide Secrets Setup
159
+
160
+ After all file changes are complete, check for existing `.env` / `.env.local` files and inform the user about secrets management:
161
+
162
+ 1. **For local development** — Check if the project already has `.env` or `.env.local` files:
163
+ - **If `.env` / `.env.local` already exists**: The existing file works as-is for `next dev` (via `initOpenNextCloudflareForDev()`). Do NOT ask the user to duplicate values into `.dev.vars`. Only mention that `.dev.vars` exists as an alternative if they ever run `wrangler dev` directly (outside of Next.js).
164
+ - **If no `.env` file exists**: Create a `.dev.vars` file in the Next.js app directory with placeholder keys:
165
+ ```
166
+ SECRET_KEY=your-local-value
167
+ ANOTHER_SECRET=another-value
168
+ ```
169
+ This file is gitignored and read by both `wrangler dev` and `next dev` (via the OpenNext dev hook).
170
+
171
+ 2. **For production**, secrets must be set via the Wrangler CLI:
172
+ ```bash
173
+ npx wrangler secret put SECRET_KEY
174
+ npx wrangler secret put ANOTHER_SECRET
175
+ ```
176
+ Each command prompts for the value interactively. Secrets are encrypted and stored by Cloudflare.
177
+
178
+ 3. **Non-secret env vars** (public URLs, feature flags) go in `wrangler.jsonc` under `"vars"`:
179
+ ```jsonc
180
+ "vars": {
181
+ "PUBLIC_API_URL": "https://api.example.com"
182
+ }
183
+ ```
184
+
185
+ 4. **First deploy**: Run `npm run deploy` (or equivalent). The user will be prompted to log in to Cloudflare if not already authenticated.
186
+
187
+ 5. **Cloudflare Dashboard setup** — If deploying via the Cloudflare dashboard (Workers & Pages > Create > Connect to Git), inform the user to configure these fields correctly:
188
+ - **Project name**: Must match the `"name"` field in `wrangler.jsonc` (e.g., `pyx-interface-v1`). A mismatch creates a separate worker and causes deployment conflicts.
189
+ - **Build command**: Must be the **OpenNext build**, not the default Next.js build. Use the package manager detected in Step 1:
190
+ - pnpm: `pnpm run build:worker`
191
+ - npm: `npm run build:worker`
192
+ - yarn: `yarn build:worker`
193
+ - bun: `bun run build:worker`
194
+ - Or directly: `npx opennextjs-cloudflare build`
195
+ - **Deploy command**: `npx wrangler deploy` (the dashboard default is correct)
196
+
197
+ **CRITICAL**: The default build command pre-filled by Cloudflare (e.g., `pnpm run build`) runs `next build` only — it does **not** run the OpenNext build step that produces the `.open-next/` output. The deploy will fail or serve a broken app if the build command is not changed to `build:worker`.
198
+
199
+ 6. **Production secrets** — After the first deploy, set all secret environment variables via the Wrangler CLI. Scan the project for env vars (check `.env`, `.env.local`, `.dev.vars`, and any `process.env.*` references in the codebase) and instruct the user to run `npx wrangler secret put <KEY>` for each one:
200
+ ```bash
201
+ npx wrangler secret put SECRET_KEY
202
+ npx wrangler secret put ANOTHER_SECRET
203
+ # Repeat for each secret env var the project uses
204
+ ```
205
+ Each command prompts for the value interactively. Secrets are encrypted and stored by Cloudflare, never committed to source.
206
+
207
+ Alternatively, secrets can be set in the Cloudflare dashboard: **Workers & Pages > your project > Settings > Variables and Secrets > Add**.
208
+
209
+ **IMPORTANT**: The worker will fail at runtime if required env vars are not set. Always list the specific env var names the project needs so the user knows exactly what to configure.
210
+
211
+ ## Gotchas & Troubleshooting
212
+
213
+ ### "X is not a function" or Node.js API errors at runtime
214
+ **Cause**: Missing `nodejs_compat` compatibility flag.
215
+ **Fix**: Ensure `wrangler.jsonc` includes `"compatibility_flags": ["nodejs_compat"]`.
216
+
217
+ ### Config files in wrong directory (monorepo)
218
+ **Cause**: `wrangler.jsonc` and `open-next.config.ts` placed in the monorepo root instead of the Next.js app directory.
219
+ **Fix**: Move all Cloudflare config files to the directory containing `next.config.*`.
220
+
221
+ ### Environment variables are undefined in production
222
+ **Cause**: Secrets not set via `wrangler secret put`.
223
+ **Fix**: Run `npx wrangler secret put <KEY_NAME>` for each secret. Non-secret vars go in `wrangler.jsonc` `"vars"`.
224
+
225
+ ### `initOpenNextCloudflareForDev()` not working
226
+ **Cause**: Wrapped in a conditional or placed inside a function.
227
+ **Fix**: Must be called at module top-level, unconditionally, before any config exports.
228
+
229
+ ### Build succeeds but deploy fails with "no routes"
230
+ **Cause**: `"main"` or `"assets.directory"` paths in `wrangler.jsonc` don't match the OpenNext output.
231
+ **Fix**: Ensure `"main": ".open-next/worker.js"` and `"assets": { "directory": ".open-next/assets" }`.
232
+
233
+ ### `wrangler dev` doesn't pick up `.dev.vars`
234
+ **Cause**: File is in the wrong directory or has wrong filename.
235
+ **Fix**: `.dev.vars` must be in the same directory as `wrangler.jsonc` (the Next.js app directory).
236
+
237
+ ## Advanced Configuration
238
+
239
+ ### R2 Cache (ISR/Incremental Static Regeneration)
240
+
241
+ To enable ISR with R2 storage, update `open-next.config.ts`:
242
+
243
+ ```ts
244
+ import { defineCloudflareConfig } from "@opennextjs/cloudflare";
245
+ import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache";
246
+
247
+ export default defineCloudflareConfig({
248
+ incrementalCache: r2IncrementalCache,
249
+ });
250
+ ```
251
+
252
+ And add the R2 binding to `wrangler.jsonc`:
253
+
254
+ ```jsonc
255
+ {
256
+ "r2_buckets": [
257
+ {
258
+ "binding": "NEXT_INC_CACHE_R2_BUCKET",
259
+ "bucket_name": "<your-bucket-name>"
260
+ }
261
+ ]
262
+ }
263
+ ```
264
+
265
+ ### Cloudflare Bindings (KV, D1, etc.)
266
+
267
+ Add bindings directly to `wrangler.jsonc`. They are accessible in Next.js via `getCloudflareContext()`:
268
+
269
+ ```ts
270
+ import { getCloudflareContext } from "@opennextjs/cloudflare";
271
+
272
+ export async function GET() {
273
+ const { env } = await getCloudflareContext();
274
+ const value = await env.MY_KV.get("key");
275
+ return Response.json({ value });
276
+ }
277
+ ```
278
+
279
+ ```jsonc
280
+ // wrangler.jsonc
281
+ {
282
+ "kv_namespaces": [
283
+ { "binding": "MY_KV", "id": "<namespace-id>" }
284
+ ]
285
+ }
286
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devlyn-cli",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Claude Code configuration toolkit for teams",
5
5
  "bin": {
6
6
  "devlyn": "bin/devlyn.js"
@@ -8,6 +8,7 @@
8
8
  "files": [
9
9
  "bin",
10
10
  "config",
11
+ "optional-skills",
11
12
  "CLAUDE.md"
12
13
  ],
13
14
  "keywords": [