devlyn-cli 0.0.5 → 0.0.7
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/ #
|
|
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,18 @@ 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
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
+
| `pyx-scan` | skill | Check whether an AI agent skill is safe before installing |
|
|
100
|
+
| `vercel-labs/agent-skills` | pack | React, Next.js, React Native best practices |
|
|
101
|
+
| `supabase/agent-skills` | pack | Supabase integration patterns |
|
|
102
|
+
| `coreyhaines31/marketingskills` | pack | Marketing automation and content skills |
|
|
103
103
|
|
|
104
104
|
## How It Works
|
|
105
105
|
|
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,15 @@ ${g} v${PKG.version} ${COLORS.dim}· ${k}🍩 by Donut Studio${r}
|
|
|
56
57
|
console.log(logo);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
{ name: '
|
|
62
|
-
{ name: '
|
|
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
|
+
{ name: 'pyx-scan', desc: 'Check whether an AI agent skill is safe before installing', type: 'local' },
|
|
65
|
+
// External skill packs (installed via npx skills add)
|
|
66
|
+
{ name: 'vercel-labs/agent-skills', desc: 'React, Next.js, React Native best practices', type: 'external' },
|
|
67
|
+
{ name: 'supabase/agent-skills', desc: 'Supabase integration patterns', type: 'external' },
|
|
68
|
+
{ name: 'coreyhaines31/marketingskills', desc: 'Marketing automation and content skills', type: 'external' },
|
|
63
69
|
];
|
|
64
70
|
|
|
65
71
|
function log(msg, color = 'reset') {
|
|
@@ -153,6 +159,14 @@ function listContents() {
|
|
|
153
159
|
}
|
|
154
160
|
}
|
|
155
161
|
|
|
162
|
+
// List optional addons
|
|
163
|
+
log('\n📦 Optional Addons:', 'blue');
|
|
164
|
+
OPTIONAL_ADDONS.forEach((addon) => {
|
|
165
|
+
const tag = addon.type === 'local' ? `${COLORS.magenta}skill${COLORS.reset}` : `${COLORS.cyan}pack${COLORS.reset}`;
|
|
166
|
+
log(` ${COLORS.green}${addon.name}${COLORS.reset} ${COLORS.dim}[${tag}${COLORS.dim}]${COLORS.reset}`);
|
|
167
|
+
log(` ${COLORS.dim}${addon.desc}${COLORS.reset}`);
|
|
168
|
+
});
|
|
169
|
+
|
|
156
170
|
log('');
|
|
157
171
|
}
|
|
158
172
|
|
|
@@ -197,7 +211,8 @@ function multiSelect(items) {
|
|
|
197
211
|
const checkbox = selected.has(i) ? `${COLORS.green}◉${COLORS.reset}` : `${COLORS.dim}○${COLORS.reset}`;
|
|
198
212
|
const pointer = i === cursor ? `${COLORS.cyan}❯${COLORS.reset}` : ' ';
|
|
199
213
|
const name = i === cursor ? `${COLORS.cyan}${item.name}${COLORS.reset}` : item.name;
|
|
200
|
-
|
|
214
|
+
const tag = item.type === 'local' ? `${COLORS.magenta}skill${COLORS.reset}` : `${COLORS.cyan}pack${COLORS.reset}`;
|
|
215
|
+
console.log(`${pointer} ${checkbox} ${name} ${COLORS.dim}[${tag}${COLORS.dim}]${COLORS.reset}`);
|
|
201
216
|
console.log(` ${COLORS.dim}${item.desc}${COLORS.reset}`);
|
|
202
217
|
});
|
|
203
218
|
};
|
|
@@ -267,6 +282,21 @@ function multiSelect(items) {
|
|
|
267
282
|
});
|
|
268
283
|
}
|
|
269
284
|
|
|
285
|
+
function installLocalSkill(skillName) {
|
|
286
|
+
const src = path.join(OPTIONAL_SKILLS_SOURCE, skillName);
|
|
287
|
+
const targetDir = getTargetDir();
|
|
288
|
+
const dest = path.join(targetDir, 'skills', skillName);
|
|
289
|
+
|
|
290
|
+
if (!fs.existsSync(src)) {
|
|
291
|
+
log(` ⚠️ Skill "${skillName}" not found`, 'yellow');
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
log(`\n🛠️ Installing ${skillName}...`, 'cyan');
|
|
296
|
+
copyRecursive(src, dest, targetDir);
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
|
|
270
300
|
function installSkillPack(packName) {
|
|
271
301
|
try {
|
|
272
302
|
log(`\n📦 Installing ${packName}...`, 'cyan');
|
|
@@ -278,6 +308,13 @@ function installSkillPack(packName) {
|
|
|
278
308
|
}
|
|
279
309
|
}
|
|
280
310
|
|
|
311
|
+
function installAddon(addon) {
|
|
312
|
+
if (addon.type === 'local') {
|
|
313
|
+
return installLocalSkill(addon.name);
|
|
314
|
+
}
|
|
315
|
+
return installSkillPack(addon.name);
|
|
316
|
+
}
|
|
317
|
+
|
|
281
318
|
async function init(skipPrompts = false) {
|
|
282
319
|
showLogo();
|
|
283
320
|
log('─'.repeat(44), 'dim');
|
|
@@ -304,26 +341,30 @@ async function init(skipPrompts = false) {
|
|
|
304
341
|
|
|
305
342
|
// Skip prompts if -y flag or non-interactive
|
|
306
343
|
if (skipPrompts || !process.stdin.isTTY) {
|
|
307
|
-
log('\n💡 Add
|
|
308
|
-
|
|
309
|
-
|
|
344
|
+
log('\n💡 Add optional skills & packs later:', 'dim');
|
|
345
|
+
OPTIONAL_ADDONS.forEach((addon) => {
|
|
346
|
+
if (addon.type === 'local') {
|
|
347
|
+
log(` npx devlyn-cli (select "${addon.name}" during install)`, 'dim');
|
|
348
|
+
} else {
|
|
349
|
+
log(` npx skills add ${addon.name}`, 'dim');
|
|
350
|
+
}
|
|
310
351
|
});
|
|
311
352
|
log('');
|
|
312
353
|
return;
|
|
313
354
|
}
|
|
314
355
|
|
|
315
|
-
// Ask about
|
|
316
|
-
log('\n📚 Optional
|
|
356
|
+
// Ask about optional addons (local skills + external packs)
|
|
357
|
+
log('\n📚 Optional skills & packs:\n', 'blue');
|
|
317
358
|
|
|
318
|
-
const
|
|
359
|
+
const selectedAddons = await multiSelect(OPTIONAL_ADDONS);
|
|
319
360
|
|
|
320
|
-
if (
|
|
321
|
-
for (const
|
|
322
|
-
|
|
361
|
+
if (selectedAddons.length > 0) {
|
|
362
|
+
for (const addon of selectedAddons) {
|
|
363
|
+
installAddon(addon);
|
|
323
364
|
}
|
|
324
365
|
} else {
|
|
325
|
-
log('💡 No
|
|
326
|
-
log('
|
|
366
|
+
log('💡 No optional addons selected', 'dim');
|
|
367
|
+
log(' Run again to add them later\n', 'dim');
|
|
327
368
|
}
|
|
328
369
|
|
|
329
370
|
log('\n✨ All done!', 'green');
|
|
@@ -337,8 +378,12 @@ function showHelp() {
|
|
|
337
378
|
log(' npx devlyn-cli list List available commands & templates');
|
|
338
379
|
log(' npx devlyn-cli -y Install without prompts');
|
|
339
380
|
log(' npx devlyn-cli --help Show this help\n');
|
|
340
|
-
log('
|
|
341
|
-
|
|
381
|
+
log('Optional skills (select during install):', 'green');
|
|
382
|
+
OPTIONAL_ADDONS.filter((a) => a.type === 'local').forEach((skill) => {
|
|
383
|
+
log(` ${skill.name} ${COLORS.dim}${skill.desc}${COLORS.reset}`);
|
|
384
|
+
});
|
|
385
|
+
log('\nExternal skill packs:', 'green');
|
|
386
|
+
OPTIONAL_ADDONS.filter((a) => a.type === 'external').forEach((pack) => {
|
|
342
387
|
log(` npx skills add ${pack.name}`);
|
|
343
388
|
});
|
|
344
389
|
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
|
+
```
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pyx-scan
|
|
3
|
+
description: >
|
|
4
|
+
Check whether an AI agent skill is safe before installing or using it.
|
|
5
|
+
Calls the PYX Scanner API to retrieve trust status, risk score, and safety
|
|
6
|
+
recommendation. Use when agent needs to verify skill safety, or user says
|
|
7
|
+
"is this safe", "check skill", "scan skill", "verify tool", "pyx scan".
|
|
8
|
+
allowed-tools: WebFetch, Bash(curl *)
|
|
9
|
+
argument-hint: "[owner/name]"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# PYX Scan — Agent Skill Safety Check
|
|
13
|
+
|
|
14
|
+
Verify whether an AI agent skill is safe before installing or using it by querying the PYX Scanner API.
|
|
15
|
+
|
|
16
|
+
## Workflow
|
|
17
|
+
|
|
18
|
+
### Step 1: Parse Input
|
|
19
|
+
|
|
20
|
+
Extract `owner` and `name` from `$ARGUMENTS`.
|
|
21
|
+
|
|
22
|
+
- Expected format: `owner/name` (e.g., `anthropic/web-search`)
|
|
23
|
+
- If `$ARGUMENTS` is empty or missing the `/` separator, ask the user:
|
|
24
|
+
*"Which skill do you want to check? Provide it as `owner/name` (e.g., `anthropic/web-search`)."*
|
|
25
|
+
- Trim whitespace. Reject if either part is empty after trimming.
|
|
26
|
+
|
|
27
|
+
### Step 2: Call the PYX Scanner API
|
|
28
|
+
|
|
29
|
+
Fetch the safety data:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
WebFetch URL: https://scanner.pyxmate.com/api/v1/check/{owner}/{name}
|
|
33
|
+
Prompt: "Return the full JSON response body exactly as-is. Do not summarize."
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
If `WebFetch` fails (tool unavailable, network error), fall back to:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
curl -s "https://scanner.pyxmate.com/api/v1/check/{owner}/{name}"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Step 3: Handle Errors
|
|
43
|
+
|
|
44
|
+
| HTTP Status | Meaning | Action |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| **200** | Skill found | Proceed to Step 4 |
|
|
47
|
+
| **404** | Skill not in database | Verdict = **UNSCANNED** |
|
|
48
|
+
| **429** | Rate limited | Verdict = **ERROR** — "Rate limited. Try again shortly." |
|
|
49
|
+
| **5xx** | Server error | Verdict = **ERROR** — "PYX Scanner is temporarily unavailable." |
|
|
50
|
+
| Network failure | Cannot reach API | Verdict = **ERROR** — "Could not connect to PYX Scanner." |
|
|
51
|
+
|
|
52
|
+
### Step 4: Determine Verdict
|
|
53
|
+
|
|
54
|
+
Use the JSON response fields to determine the verdict:
|
|
55
|
+
|
|
56
|
+
| Condition | Verdict |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `recommendation == "safe"` AND `is_outdated == false` | **SAFE** |
|
|
59
|
+
| `recommendation == "safe"` AND `is_outdated == true` | **OUTDATED** |
|
|
60
|
+
| `recommendation == "caution"` | **CAUTION** |
|
|
61
|
+
| `recommendation == "danger"` | **FAILED** |
|
|
62
|
+
| `recommendation == "unknown"` | **UNSCANNED** |
|
|
63
|
+
|
|
64
|
+
### Step 5: Output Report
|
|
65
|
+
|
|
66
|
+
Format the report as structured markdown. Omit any section where the data is null or empty.
|
|
67
|
+
|
|
68
|
+
**For SAFE verdict:**
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
## PYX Scan: {owner}/{name}
|
|
72
|
+
|
|
73
|
+
**Verdict: SAFE** — This skill has been scanned and verified safe.
|
|
74
|
+
|
|
75
|
+
**Trust Score:** {trust_score}/10 | **Risk Score:** {risk_score}/10 | **Confidence:** {confidence}%
|
|
76
|
+
**Intent:** {intent} | **Status:** {status}
|
|
77
|
+
|
|
78
|
+
### Summary
|
|
79
|
+
{summary}
|
|
80
|
+
|
|
81
|
+
### About
|
|
82
|
+
**Purpose:** {about.purpose}
|
|
83
|
+
**Capabilities:** {about.capabilities as bullet list}
|
|
84
|
+
**Permissions Required:** {about.permissions_required as bullet list}
|
|
85
|
+
|
|
86
|
+
[View full report]({detail_url}) | [Badge]({badge_url})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**For OUTDATED verdict:**
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
## PYX Scan: {owner}/{name}
|
|
93
|
+
|
|
94
|
+
**Verdict: OUTDATED** — Last scan was safe, but the skill has been updated since.
|
|
95
|
+
|
|
96
|
+
The scanned commit (`{scanned_commit}`) no longer matches the latest (`{latest_commit}`).
|
|
97
|
+
The new version has NOT been reviewed. Proceed with caution.
|
|
98
|
+
|
|
99
|
+
**Trust Score:** {trust_score}/10 | **Risk Score:** {risk_score}/10
|
|
100
|
+
**Last Safe Commit:** {last_safe_commit}
|
|
101
|
+
|
|
102
|
+
### Summary
|
|
103
|
+
{summary}
|
|
104
|
+
|
|
105
|
+
[View full report]({detail_url})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**For CAUTION verdict:**
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
## PYX Scan: {owner}/{name}
|
|
112
|
+
|
|
113
|
+
**Verdict: CAUTION** — This skill has potential risks that need your attention.
|
|
114
|
+
|
|
115
|
+
**Trust Score:** {trust_score}/10 | **Risk Score:** {risk_score}/10 | **Confidence:** {confidence}%
|
|
116
|
+
**Intent:** {intent} | **Status:** {status}
|
|
117
|
+
|
|
118
|
+
### Summary
|
|
119
|
+
{summary}
|
|
120
|
+
|
|
121
|
+
### About
|
|
122
|
+
**Purpose:** {about.purpose}
|
|
123
|
+
**Permissions Required:** {about.permissions_required as bullet list}
|
|
124
|
+
**Security Notes:** {about.security_notes}
|
|
125
|
+
|
|
126
|
+
**Do you want to proceed despite the caution rating?** Please confirm before installing or using this skill.
|
|
127
|
+
|
|
128
|
+
[View full report]({detail_url})
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**For FAILED verdict:**
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
## PYX Scan: {owner}/{name}
|
|
135
|
+
|
|
136
|
+
**Verdict: FAILED** — This skill has been flagged as dangerous. Do NOT install or use it.
|
|
137
|
+
|
|
138
|
+
**Trust Score:** {trust_score}/10 | **Risk Score:** {risk_score}/10 | **Confidence:** {confidence}%
|
|
139
|
+
**Intent:** {intent} | **Status:** {status}
|
|
140
|
+
|
|
141
|
+
### Summary
|
|
142
|
+
{summary}
|
|
143
|
+
|
|
144
|
+
### About
|
|
145
|
+
**Security Notes:** {about.security_notes}
|
|
146
|
+
|
|
147
|
+
[View full report]({detail_url})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**For UNSCANNED verdict:**
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
## PYX Scan: {owner}/{name}
|
|
154
|
+
|
|
155
|
+
**Verdict: UNSCANNED** — This skill has not been scanned by PYX Scanner.
|
|
156
|
+
|
|
157
|
+
No safety data is available. You should:
|
|
158
|
+
1. Review the skill's source code manually before use
|
|
159
|
+
2. Check the skill's repository for known issues
|
|
160
|
+
3. Request a scan at https://scanner.pyxmate.com
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**For ERROR verdict:**
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
## PYX Scan: {owner}/{name}
|
|
167
|
+
|
|
168
|
+
**Verdict: ERROR** — {error_message}
|
|
169
|
+
|
|
170
|
+
Safety could not be verified. Treat this skill as unverified until you can confirm its safety.
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Behavioral Rules
|
|
174
|
+
|
|
175
|
+
1. **Always call the API** — never skip the check or return a cached/assumed result.
|
|
176
|
+
2. **Never soften a FAILED verdict** — if the scan says danger, report danger. Do not add qualifiers like "but it might be fine."
|
|
177
|
+
3. **Always ask user confirmation on CAUTION** — the user must explicitly agree before proceeding.
|
|
178
|
+
4. **Keep reports concise** — omit null/empty sections rather than showing "N/A."
|
|
179
|
+
5. **No raw JSON** — always format the response as the structured markdown report above.
|
|
180
|
+
|
|
181
|
+
## Self-Scan Awareness
|
|
182
|
+
|
|
183
|
+
When `$ARGUMENTS` is `pyxmate/pyx-scan`, `pyxmate/pyx-scanner`, or refers to this skill itself, still call the API honestly and report whatever comes back. If the result is UNSCANNED, append:
|
|
184
|
+
|
|
185
|
+
> *"Yes, even the security scanner's own skill hasn't been scanned yet. We practice what we preach — treat unscanned skills with caution."*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devlyn-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
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": [
|
|
File without changes
|