@olegkoval/agent-skills 1.5.0 → 1.6.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "olko-agent-skills",
3
3
  "description": "Agent-agnostic skill catalog for Codex, Claude, Cursor, and other skill-aware tools.",
4
- "version": "1.4.0",
4
+ "version": "1.5.1",
5
5
  "author": {
6
6
  "name": "Oleg Koval"
7
7
  },
@@ -23,6 +23,7 @@
23
23
  "./packages/software-development/starter-rules",
24
24
  "./packages/software-development/open-source-publisher",
25
25
  "./packages/marketing/viral-launch",
26
- "./packages/marketing/search-console-indexing-audit"
26
+ "./packages/marketing/search-console-indexing-audit",
27
+ "./packages/software-development/cloudflare-block-countries"
27
28
  ]
28
29
  }
@@ -75,6 +75,11 @@
75
75
  "name": "olko:search-console-indexing-audit",
76
76
  "source": "./packages/marketing/search-console-indexing-audit/adapters/cursor",
77
77
  "description": "Analyze Google Search Console Coverage CSV exports and correlate them with sitemap, robots, canonical, redirect, and noindex signals."
78
+ },
79
+ {
80
+ "name": "olko:cloudflare-block-countries",
81
+ "source": "./packages/software-development/cloudflare-block-countries/adapters/cursor",
82
+ "description": "Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Handles creating new rulesets and updating existing ones across single or multiple zones."
78
83
  }
79
84
  ]
80
85
  }
package/README.md CHANGED
@@ -53,14 +53,7 @@ Use the olko:semantic-release-beta skill to add prereleases on a beta branch.
53
53
 
54
54
  The repository includes a generated Claude marketplace manifest at `.claude-plugin/marketplace.json`.
55
55
 
56
- For local development, clone the repo and point Claude Code at the plugin directory:
57
-
58
- ```bash
59
- git clone https://github.com/oleg-koval/agent-skills.git
60
- claude --plugin-dir /path/to/agent-skills
61
- ```
62
-
63
- If your Claude Code environment supports GitHub marketplace installs for this repo, add the marketplace source first:
56
+ **For marketplace installs:**
64
57
 
65
58
  ```text
66
59
  /plugin marketplace add oleg-koval/agent-skills
@@ -72,7 +65,16 @@ Then install the catalog plugin:
72
65
  /plugin install olko-agent-skills@olko-agent-skills
73
66
  ```
74
67
 
75
- Live Claude marketplace install is not yet validated for this repo.
68
+ **For local development:**
69
+
70
+ Clone the repo and point Claude Code at the plugin directory:
71
+
72
+ ```bash
73
+ git clone https://github.com/oleg-koval/agent-skills.git
74
+ claude --plugin-dir /path/to/agent-skills
75
+ ```
76
+
77
+ See [Publishing to Marketplaces](#publishing-to-marketplaces) below for full submission details.
76
78
 
77
79
  </details>
78
80
 
@@ -81,7 +83,15 @@ Live Claude marketplace install is not yet validated for this repo.
81
83
 
82
84
  The repository includes a generated Cursor plugin index at `.cursor-plugin/index.json` and per-package Cursor adapters under `packages/*/*/adapters/cursor/`.
83
85
 
84
- For a single project, copy the relevant `SKILL.md` or adapter content into `.cursor/rules/`, or reference the full package directory from your Cursor rules.
86
+ **For plugin marketplace:**
87
+
88
+ Install from Cursor's extension marketplace using the published repository.
89
+
90
+ **For a single project:**
91
+
92
+ Copy the relevant `SKILL.md` or adapter content into `.cursor/rules/`, or reference the full package directory from your Cursor rules.
93
+
94
+ See [Publishing to Marketplaces](#publishing-to-marketplaces) for registry submission.
85
95
 
86
96
  </details>
87
97
 
@@ -95,7 +105,15 @@ Use the generated repository instructions and prompt files:
95
105
  .github/prompts/*.prompt.md
96
106
  ```
97
107
 
98
- These files are generated from `catalog/skills.json` and can be copied into a repository that should use the same skill guidance.
108
+ **To use in your GitHub workspace:**
109
+
110
+ Copy `.github/copilot-instructions.md` to your repository and enable Copilot. It will automatically apply skill guidance.
111
+
112
+ **To customize per-skill:**
113
+
114
+ Use files from `.github/prompts/*.prompt.md` in your Copilot configuration.
115
+
116
+ See [Publishing to Marketplaces](#publishing-to-marketplaces) for integration details.
99
117
 
100
118
  </details>
101
119
 
@@ -269,38 +287,119 @@ Use the olko:docs-index-keeper skill to set up docs index automation in this rep
269
287
  Use the olko:semantic-release-beta skill to add prereleases on a beta branch.
270
288
  ```
271
289
 
272
- ### Claude
290
+ Status: **Stable** — ready for daily use
273
291
 
274
- The repository includes a generated root manifest at `.claude-plugin/marketplace.json` and per-package Claude adapter stubs under `packages/*/*/adapters/claude/`.
292
+ ### Claude Code
275
293
 
276
- Current status:
294
+ The repository includes a generated marketplace manifest at `.claude-plugin/marketplace.json` and per-package Claude adapter stubs under `packages/*/*/adapters/claude/`.
277
295
 
278
- - suitable for local packaging and iteration
279
- - not yet validated against a live Claude marketplace install flow
296
+ **Local development:**
297
+
298
+ ```bash
299
+ git clone https://github.com/oleg-koval/agent-skills.git
300
+ claude --plugin-dir /path/to/agent-skills
301
+ ```
302
+
303
+ **Marketplace installation:**
304
+
305
+ See [Publishing to Marketplaces → Claude Marketplace](#claude-marketplace) for submission and user installation instructions.
306
+
307
+ Status: **Ready for marketplace** — fully generated manifests, ready to publish
280
308
 
281
309
  ### Cursor
282
310
 
283
- The repository includes a generated root plugin index at `.cursor-plugin/index.json` and per-package Cursor adapter stubs under `packages/*/*/adapters/cursor/`.
311
+ The repository includes a generated plugin index at `.cursor-plugin/index.json` and per-package Cursor adapter stubs under `packages/*/*/adapters/cursor/`.
284
312
 
285
- Current status:
313
+ See [Publishing to Marketplaces → Cursor Plugin Registry](#cursor-plugin-registry) for submission instructions.
286
314
 
287
- - suitable for local packaging and iteration
288
- - not yet validated against a live Cursor plugin install flow
315
+ Status: **Ready for marketplace** plugin index generated and ready to submit
289
316
 
290
317
  ### GitHub Copilot
291
318
 
292
- The repository includes generated Copilot repository instructions at `.github/copilot-instructions.md` and one reusable prompt file per skill under `.github/prompts/`.
319
+ The repository includes generated repository instructions at `.github/copilot-instructions.md` and per-skill prompt files under `.github/prompts/`.
320
+
321
+ To use in your GitHub workspace, copy `.github/copilot-instructions.md` to your repository.
293
322
 
294
- Current status:
323
+ See [Publishing to Marketplaces → GitHub Copilot](#github-copilot) for integration details.
295
324
 
296
- - suitable for repository-level Copilot customization
297
- - generated from the canonical package catalog
298
- - not yet validated against GitHub Copilot in a live GitHub workspace
325
+ Status: **Ready to use** copy instructions to any GitHub repository
299
326
 
300
327
  ### Source sync
301
328
 
302
329
  `./scripts/sync-from-sources.sh` syncs package content only for catalog entries that explicitly define `sourcePath`. If no package has `sourcePath`, the script exits successfully and reports that there is nothing to sync.
303
330
 
331
+ ## Publishing to Marketplaces
332
+
333
+ ### Claude Marketplace
334
+
335
+ The repository includes a generated marketplace manifest at `.claude-plugin/marketplace.json` that is compatible with Claude Code's plugin system.
336
+
337
+ **To submit to Claude marketplace:**
338
+
339
+ 1. Ensure the repository is public and contains this structure:
340
+ ```
341
+ ├── .claude-plugin/
342
+ │ └── marketplace.json # Generated from catalog/skills.json
343
+ └── packages/
344
+ └── {category}/{skill}/
345
+ ├── SKILL.md # Required canonical definition
346
+ └── adapters/
347
+ └── claude/ # Optional Claude-specific wrapper
348
+ ```
349
+
350
+ 2. Push to GitHub with a public repository:
351
+ ```bash
352
+ git push origin main
353
+ ```
354
+
355
+ 3. Submit the repository URL to Claude's plugin marketplace:
356
+ - In Claude Code: `/plugin marketplace add oleg-koval/agent-skills`
357
+ - Or visit the Claude plugin marketplace and add `https://github.com/oleg-koval/agent-skills`
358
+
359
+ 4. The marketplace will:
360
+ - Read `.claude-plugin/marketplace.json` for plugin metadata
361
+ - Index all skills from `catalog/skills.json`
362
+ - Auto-register skills by their `name` field
363
+ - Use `adapters: ["claude"]` entries to provide Claude-specific hints
364
+
365
+ **After publishing:**
366
+ - Users install with: `/plugin install olko-agent-skills@olko-agent-skills`
367
+ - All 12 skills become available as `olko:*` lookup names in Claude Code
368
+
369
+ ### Cursor Plugin Registry
370
+
371
+ Cursor plugin index is generated at `.cursor-plugin/index.json`.
372
+
373
+ **To submit to Cursor:**
374
+
375
+ 1. Ensure Cursor plugin metadata is present:
376
+ - `.cursor-plugin/index.json` (generated)
377
+ - `packages/*/*/adapters/cursor/` (optional Cursor-specific guidance)
378
+
379
+ 2. Submit the repository to Cursor's plugin marketplace with the `.cursor-plugin/` manifest
380
+
381
+ 3. Users install from Cursor's extension marketplace and reference skills by lookup name
382
+
383
+ ### GitHub Copilot
384
+
385
+ Generated Copilot instructions are at `.github/copilot-instructions.md` and per-skill prompts under `.github/prompts/`.
386
+
387
+ **To use in a GitHub workspace:**
388
+
389
+ 1. Copy `.github/copilot-instructions.md` to your repository
390
+ 2. Copilot reads this file automatically when enabled
391
+ 3. Per-skill prompts can be referenced or imported into custom Copilot configurations
392
+
393
+ ### Codex
394
+
395
+ Codex installation uses symlinks:
396
+
397
+ ```bash
398
+ ./scripts/install-codex-symlinks.sh
399
+ ```
400
+
401
+ Codex looks up skills by `olko:*` lookup names from the installed symlink directories.
402
+
304
403
  ## Local validation
305
404
 
306
405
  Rebuild generated manifests:
@@ -315,6 +414,12 @@ Validate the neutral catalog and generated root manifests:
315
414
  ./scripts/validate-catalog.sh
316
415
  ```
317
416
 
417
+ Run both before pushing marketplace updates:
418
+
419
+ ```bash
420
+ ./scripts/build-adapters.sh && ./scripts/validate-catalog.sh
421
+ ```
422
+
318
423
  ## Smoke-tested workflows
319
424
 
320
425
  The following package workflows have been smoke-tested locally before first push:
@@ -306,6 +306,26 @@
306
306
  "cursor",
307
307
  "copilot"
308
308
  ]
309
+ },
310
+ {
311
+ "name": "cloudflare-block-countries",
312
+ "lookupName": "olko:cloudflare-block-countries",
313
+ "category": "software-development",
314
+ "path": "packages/software-development/cloudflare-block-countries",
315
+ "description": "Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Handles creating new rulesets and updating existing ones across single or multiple zones.",
316
+ "tags": [
317
+ "cloudflare",
318
+ "waf",
319
+ "security",
320
+ "geo-blocking",
321
+ "firewall"
322
+ ],
323
+ "adapters": [
324
+ "codex",
325
+ "claude",
326
+ "cursor",
327
+ "copilot"
328
+ ]
309
329
  }
310
330
  ]
311
331
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olegkoval/agent-skills",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -0,0 +1,175 @@
1
+ ---
2
+ name: cloudflare-block-countries
3
+ description: 'Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Use when user wants to geo-block traffic, block countries in Cloudflare, set up WAF country rules, or mentions blocking regions. Handles both creating new rulesets and updating existing ones.'
4
+ license: MIT
5
+ allowed-tools: Bash
6
+ compatibility: Codex, Claude Code, Cursor, and other Agent Skills compatible tools. Requires curl and jq.
7
+ metadata:
8
+ author: Oleg Koval
9
+ tags:
10
+ - cloudflare
11
+ - waf
12
+ - security
13
+ - geo-blocking
14
+ - firewall
15
+ ---
16
+
17
+ # Cloudflare Country Block via WAF Custom Rules
18
+
19
+ Block traffic from specific countries using Cloudflare WAF Custom Rules and the Cloudflare API.
20
+
21
+ ## Prerequisites
22
+
23
+ - Cloudflare API Token with `Zone:Rulesets:Edit` permission
24
+ - Zone ID for the target domain
25
+ - `curl` and `jq` installed
26
+
27
+ ## Workflow
28
+
29
+ ### 1. Get Zone IDs
30
+
31
+ ```bash
32
+ curl -s -X GET \
33
+ "https://api.cloudflare.com/client/v4/zones?account.id=YOUR_ACCOUNT_ID&status=active" \
34
+ -H "Authorization: Bearer YOUR_API_TOKEN" \
35
+ -H "Content-Type: application/json" | jq '.result[] | {name: .name, id: .id}'
36
+ ```
37
+
38
+ ### 2. Check Existing Custom Firewall Rulesets
39
+
40
+ ```bash
41
+ curl -s -X GET \
42
+ "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
43
+ -H "Authorization: Bearer YOUR_API_TOKEN" \
44
+ -H "Content-Type: application/json" | \
45
+ jq '.result[] | select(.phase == "http_request_firewall_custom")'
46
+ ```
47
+
48
+ If the output is empty — no existing ruleset. Go to **3a**. If a ruleset exists, note its `id` and go to **3b**.
49
+
50
+ ### 3a. Create NEW Ruleset with Block Rule
51
+
52
+ ```bash
53
+ curl -s -X POST \
54
+ "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
55
+ -H "Authorization: Bearer YOUR_API_TOKEN" \
56
+ -H "Content-Type: application/json" \
57
+ --data '{
58
+ "name": "default",
59
+ "description": "WAF Custom Rules",
60
+ "kind": "zone",
61
+ "phase": "http_request_firewall_custom",
62
+ "rules": [
63
+ {
64
+ "action": "block",
65
+ "description": "Block traffic from sanctioned countries",
66
+ "enabled": true,
67
+ "expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
68
+ }
69
+ ]
70
+ }' | jq '.success'
71
+ ```
72
+
73
+ ### 3b. Update EXISTING Ruleset
74
+
75
+ ```bash
76
+ curl -s -X PUT \
77
+ "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
78
+ -H "Authorization: Bearer YOUR_API_TOKEN" \
79
+ -H "Content-Type: application/json" \
80
+ --data '{
81
+ "description": "WAF Custom Rules",
82
+ "rules": [
83
+ {
84
+ "action": "block",
85
+ "description": "Block traffic from sanctioned countries",
86
+ "enabled": true,
87
+ "expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
88
+ }
89
+ ]
90
+ }' | jq '.success'
91
+ ```
92
+
93
+ ### 4. Verify the Rule is Active
94
+
95
+ ```bash
96
+ curl -s -X GET \
97
+ "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
98
+ -H "Authorization: Bearer YOUR_API_TOKEN" \
99
+ -H "Content-Type: application/json" | jq '.result.rules'
100
+ ```
101
+
102
+ ## Country Codes Reference
103
+
104
+ | Code | Country |
105
+ |------|---------|
106
+ | `RU` | Russia |
107
+ | `BY` | Belarus |
108
+ | `IR` | Iran |
109
+ | `KP` | North Korea |
110
+ | `CN` | China |
111
+ | `CU` | Cuba |
112
+ | `SY` | Syria |
113
+ | `VE` | Venezuela |
114
+
115
+ ## WAF Expression Examples
116
+
117
+ ```
118
+ # Block multiple countries
119
+ (ip.src.country in {"RU" "BY" "IR" "KP"})
120
+
121
+ # Block country + specific ASN
122
+ (ip.src.country eq "RU") or (ip.geoip.asnum eq 12345)
123
+
124
+ # Block all except allowlisted IPs
125
+ (ip.src.country in {"RU" "BY"}) and not (ip.src in {1.2.3.4/32})
126
+ ```
127
+
128
+ ## Multi-Zone Script
129
+
130
+ To apply the same block rule across multiple zones:
131
+
132
+ ```bash
133
+ #!/usr/bin/env bash
134
+ API_TOKEN="YOUR_API_TOKEN"
135
+ ZONES=("ZONE_ID_1" "ZONE_ID_2" "ZONE_ID_3")
136
+ EXPRESSION='(ip.src.country in {"RU" "BY" "IR" "KP"})'
137
+
138
+ for ZONE_ID in "${ZONES[@]}"; do
139
+ EXISTING=$(curl -s -X GET \
140
+ "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets" \
141
+ -H "Authorization: Bearer $API_TOKEN" \
142
+ -H "Content-Type: application/json" | \
143
+ jq -r '.result[] | select(.phase == "http_request_firewall_custom") | .id')
144
+
145
+ if [ -z "$EXISTING" ]; then
146
+ METHOD="POST"
147
+ URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets"
148
+ DATA=$(jq -n --arg expr "$EXPRESSION" '{
149
+ name: "default", description: "WAF Custom Rules",
150
+ kind: "zone", phase: "http_request_firewall_custom",
151
+ rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
152
+ }')
153
+ else
154
+ METHOD="PUT"
155
+ URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/$EXISTING"
156
+ DATA=$(jq -n --arg expr "$EXPRESSION" '{
157
+ description: "WAF Custom Rules",
158
+ rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
159
+ }')
160
+ fi
161
+
162
+ RESULT=$(curl -s -X "$METHOD" "$URL" \
163
+ -H "Authorization: Bearer $API_TOKEN" \
164
+ -H "Content-Type: application/json" \
165
+ --data "$DATA" | jq '.success')
166
+
167
+ echo "Zone $ZONE_ID: $RESULT"
168
+ done
169
+ ```
170
+
171
+ ## Notes
172
+
173
+ - `PUT` on an existing ruleset **replaces all rules** in it. Include all rules in the payload if the ruleset already has other rules.
174
+ - WAF Custom Rules require a Cloudflare plan that supports rulesets (Free includes basic WAF; Pro/Business/Enterprise for full custom rules).
175
+ - Changes are applied globally within seconds; no cache purge needed.
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "olko-cloudflare-block-countries",
3
+ "description": "Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Handles creating new rulesets and updating existing ones across single or multiple zones.",
4
+ "skills": "./skills"
5
+ }
@@ -0,0 +1,177 @@
1
+ <!-- Generated by scripts/build-adapters.sh. Do not edit directly. -->
2
+
3
+ ---
4
+ name: cloudflare-block-countries
5
+ description: 'Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Use when user wants to geo-block traffic, block countries in Cloudflare, set up WAF country rules, or mentions blocking regions. Handles both creating new rulesets and updating existing ones.'
6
+ license: MIT
7
+ allowed-tools: Bash
8
+ compatibility: Codex, Claude Code, Cursor, and other Agent Skills compatible tools. Requires curl and jq.
9
+ metadata:
10
+ author: Oleg Koval
11
+ tags:
12
+ - cloudflare
13
+ - waf
14
+ - security
15
+ - geo-blocking
16
+ - firewall
17
+ ---
18
+
19
+ # Cloudflare Country Block via WAF Custom Rules
20
+
21
+ Block traffic from specific countries using Cloudflare WAF Custom Rules and the Cloudflare API.
22
+
23
+ ## Prerequisites
24
+
25
+ - Cloudflare API Token with `Zone:Rulesets:Edit` permission
26
+ - Zone ID for the target domain
27
+ - `curl` and `jq` installed
28
+
29
+ ## Workflow
30
+
31
+ ### 1. Get Zone IDs
32
+
33
+ ```bash
34
+ curl -s -X GET \
35
+ "https://api.cloudflare.com/client/v4/zones?account.id=YOUR_ACCOUNT_ID&status=active" \
36
+ -H "Authorization: Bearer YOUR_API_TOKEN" \
37
+ -H "Content-Type: application/json" | jq '.result[] | {name: .name, id: .id}'
38
+ ```
39
+
40
+ ### 2. Check Existing Custom Firewall Rulesets
41
+
42
+ ```bash
43
+ curl -s -X GET \
44
+ "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
45
+ -H "Authorization: Bearer YOUR_API_TOKEN" \
46
+ -H "Content-Type: application/json" | \
47
+ jq '.result[] | select(.phase == "http_request_firewall_custom")'
48
+ ```
49
+
50
+ If the output is empty — no existing ruleset. Go to **3a**. If a ruleset exists, note its `id` and go to **3b**.
51
+
52
+ ### 3a. Create NEW Ruleset with Block Rule
53
+
54
+ ```bash
55
+ curl -s -X POST \
56
+ "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
57
+ -H "Authorization: Bearer YOUR_API_TOKEN" \
58
+ -H "Content-Type: application/json" \
59
+ --data '{
60
+ "name": "default",
61
+ "description": "WAF Custom Rules",
62
+ "kind": "zone",
63
+ "phase": "http_request_firewall_custom",
64
+ "rules": [
65
+ {
66
+ "action": "block",
67
+ "description": "Block traffic from sanctioned countries",
68
+ "enabled": true,
69
+ "expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
70
+ }
71
+ ]
72
+ }' | jq '.success'
73
+ ```
74
+
75
+ ### 3b. Update EXISTING Ruleset
76
+
77
+ ```bash
78
+ curl -s -X PUT \
79
+ "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
80
+ -H "Authorization: Bearer YOUR_API_TOKEN" \
81
+ -H "Content-Type: application/json" \
82
+ --data '{
83
+ "description": "WAF Custom Rules",
84
+ "rules": [
85
+ {
86
+ "action": "block",
87
+ "description": "Block traffic from sanctioned countries",
88
+ "enabled": true,
89
+ "expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
90
+ }
91
+ ]
92
+ }' | jq '.success'
93
+ ```
94
+
95
+ ### 4. Verify the Rule is Active
96
+
97
+ ```bash
98
+ curl -s -X GET \
99
+ "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
100
+ -H "Authorization: Bearer YOUR_API_TOKEN" \
101
+ -H "Content-Type: application/json" | jq '.result.rules'
102
+ ```
103
+
104
+ ## Country Codes Reference
105
+
106
+ | Code | Country |
107
+ |------|---------|
108
+ | `RU` | Russia |
109
+ | `BY` | Belarus |
110
+ | `IR` | Iran |
111
+ | `KP` | North Korea |
112
+ | `CN` | China |
113
+ | `CU` | Cuba |
114
+ | `SY` | Syria |
115
+ | `VE` | Venezuela |
116
+
117
+ ## WAF Expression Examples
118
+
119
+ ```
120
+ # Block multiple countries
121
+ (ip.src.country in {"RU" "BY" "IR" "KP"})
122
+
123
+ # Block country + specific ASN
124
+ (ip.src.country eq "RU") or (ip.geoip.asnum eq 12345)
125
+
126
+ # Block all except allowlisted IPs
127
+ (ip.src.country in {"RU" "BY"}) and not (ip.src in {1.2.3.4/32})
128
+ ```
129
+
130
+ ## Multi-Zone Script
131
+
132
+ To apply the same block rule across multiple zones:
133
+
134
+ ```bash
135
+ #!/usr/bin/env bash
136
+ API_TOKEN="YOUR_API_TOKEN"
137
+ ZONES=("ZONE_ID_1" "ZONE_ID_2" "ZONE_ID_3")
138
+ EXPRESSION='(ip.src.country in {"RU" "BY" "IR" "KP"})'
139
+
140
+ for ZONE_ID in "${ZONES[@]}"; do
141
+ EXISTING=$(curl -s -X GET \
142
+ "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets" \
143
+ -H "Authorization: Bearer $API_TOKEN" \
144
+ -H "Content-Type: application/json" | \
145
+ jq -r '.result[] | select(.phase == "http_request_firewall_custom") | .id')
146
+
147
+ if [ -z "$EXISTING" ]; then
148
+ METHOD="POST"
149
+ URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets"
150
+ DATA=$(jq -n --arg expr "$EXPRESSION" '{
151
+ name: "default", description: "WAF Custom Rules",
152
+ kind: "zone", phase: "http_request_firewall_custom",
153
+ rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
154
+ }')
155
+ else
156
+ METHOD="PUT"
157
+ URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/$EXISTING"
158
+ DATA=$(jq -n --arg expr "$EXPRESSION" '{
159
+ description: "WAF Custom Rules",
160
+ rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
161
+ }')
162
+ fi
163
+
164
+ RESULT=$(curl -s -X "$METHOD" "$URL" \
165
+ -H "Authorization: Bearer $API_TOKEN" \
166
+ -H "Content-Type: application/json" \
167
+ --data "$DATA" | jq '.success')
168
+
169
+ echo "Zone $ZONE_ID: $RESULT"
170
+ done
171
+ ```
172
+
173
+ ## Notes
174
+
175
+ - `PUT` on an existing ruleset **replaces all rules** in it. Include all rules in the payload if the ruleset already has other rules.
176
+ - WAF Custom Rules require a Cloudflare plan that supports rulesets (Free includes basic WAF; Pro/Business/Enterprise for full custom rules).
177
+ - Changes are applied globally within seconds; no cache purge needed.
@@ -0,0 +1,3 @@
1
+ # Codex adapter
2
+
3
+ Use the canonical skill directly from `packages/software-development/cloudflare-block-countries/SKILL.md`.
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "olko:cloudflare-block-countries",
3
+ "version": "0.1.0",
4
+ "description": "Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Handles creating new rulesets and updating existing ones across single or multiple zones.",
5
+ "skills": "skills/"
6
+ }
@@ -0,0 +1,177 @@
1
+ <!-- Generated by scripts/build-adapters.sh. Do not edit directly. -->
2
+
3
+ ---
4
+ name: cloudflare-block-countries
5
+ description: 'Block specific countries via Cloudflare WAF Custom Rules using the Cloudflare API. Use when user wants to geo-block traffic, block countries in Cloudflare, set up WAF country rules, or mentions blocking regions. Handles both creating new rulesets and updating existing ones.'
6
+ license: MIT
7
+ allowed-tools: Bash
8
+ compatibility: Codex, Claude Code, Cursor, and other Agent Skills compatible tools. Requires curl and jq.
9
+ metadata:
10
+ author: Oleg Koval
11
+ tags:
12
+ - cloudflare
13
+ - waf
14
+ - security
15
+ - geo-blocking
16
+ - firewall
17
+ ---
18
+
19
+ # Cloudflare Country Block via WAF Custom Rules
20
+
21
+ Block traffic from specific countries using Cloudflare WAF Custom Rules and the Cloudflare API.
22
+
23
+ ## Prerequisites
24
+
25
+ - Cloudflare API Token with `Zone:Rulesets:Edit` permission
26
+ - Zone ID for the target domain
27
+ - `curl` and `jq` installed
28
+
29
+ ## Workflow
30
+
31
+ ### 1. Get Zone IDs
32
+
33
+ ```bash
34
+ curl -s -X GET \
35
+ "https://api.cloudflare.com/client/v4/zones?account.id=YOUR_ACCOUNT_ID&status=active" \
36
+ -H "Authorization: Bearer YOUR_API_TOKEN" \
37
+ -H "Content-Type: application/json" | jq '.result[] | {name: .name, id: .id}'
38
+ ```
39
+
40
+ ### 2. Check Existing Custom Firewall Rulesets
41
+
42
+ ```bash
43
+ curl -s -X GET \
44
+ "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
45
+ -H "Authorization: Bearer YOUR_API_TOKEN" \
46
+ -H "Content-Type: application/json" | \
47
+ jq '.result[] | select(.phase == "http_request_firewall_custom")'
48
+ ```
49
+
50
+ If the output is empty — no existing ruleset. Go to **3a**. If a ruleset exists, note its `id` and go to **3b**.
51
+
52
+ ### 3a. Create NEW Ruleset with Block Rule
53
+
54
+ ```bash
55
+ curl -s -X POST \
56
+ "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets" \
57
+ -H "Authorization: Bearer YOUR_API_TOKEN" \
58
+ -H "Content-Type: application/json" \
59
+ --data '{
60
+ "name": "default",
61
+ "description": "WAF Custom Rules",
62
+ "kind": "zone",
63
+ "phase": "http_request_firewall_custom",
64
+ "rules": [
65
+ {
66
+ "action": "block",
67
+ "description": "Block traffic from sanctioned countries",
68
+ "enabled": true,
69
+ "expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
70
+ }
71
+ ]
72
+ }' | jq '.success'
73
+ ```
74
+
75
+ ### 3b. Update EXISTING Ruleset
76
+
77
+ ```bash
78
+ curl -s -X PUT \
79
+ "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
80
+ -H "Authorization: Bearer YOUR_API_TOKEN" \
81
+ -H "Content-Type: application/json" \
82
+ --data '{
83
+ "description": "WAF Custom Rules",
84
+ "rules": [
85
+ {
86
+ "action": "block",
87
+ "description": "Block traffic from sanctioned countries",
88
+ "enabled": true,
89
+ "expression": "(ip.src.country in {\"RU\" \"BY\" \"IR\" \"KP\"})"
90
+ }
91
+ ]
92
+ }' | jq '.success'
93
+ ```
94
+
95
+ ### 4. Verify the Rule is Active
96
+
97
+ ```bash
98
+ curl -s -X GET \
99
+ "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/rulesets/RULESET_ID" \
100
+ -H "Authorization: Bearer YOUR_API_TOKEN" \
101
+ -H "Content-Type: application/json" | jq '.result.rules'
102
+ ```
103
+
104
+ ## Country Codes Reference
105
+
106
+ | Code | Country |
107
+ |------|---------|
108
+ | `RU` | Russia |
109
+ | `BY` | Belarus |
110
+ | `IR` | Iran |
111
+ | `KP` | North Korea |
112
+ | `CN` | China |
113
+ | `CU` | Cuba |
114
+ | `SY` | Syria |
115
+ | `VE` | Venezuela |
116
+
117
+ ## WAF Expression Examples
118
+
119
+ ```
120
+ # Block multiple countries
121
+ (ip.src.country in {"RU" "BY" "IR" "KP"})
122
+
123
+ # Block country + specific ASN
124
+ (ip.src.country eq "RU") or (ip.geoip.asnum eq 12345)
125
+
126
+ # Block all except allowlisted IPs
127
+ (ip.src.country in {"RU" "BY"}) and not (ip.src in {1.2.3.4/32})
128
+ ```
129
+
130
+ ## Multi-Zone Script
131
+
132
+ To apply the same block rule across multiple zones:
133
+
134
+ ```bash
135
+ #!/usr/bin/env bash
136
+ API_TOKEN="YOUR_API_TOKEN"
137
+ ZONES=("ZONE_ID_1" "ZONE_ID_2" "ZONE_ID_3")
138
+ EXPRESSION='(ip.src.country in {"RU" "BY" "IR" "KP"})'
139
+
140
+ for ZONE_ID in "${ZONES[@]}"; do
141
+ EXISTING=$(curl -s -X GET \
142
+ "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets" \
143
+ -H "Authorization: Bearer $API_TOKEN" \
144
+ -H "Content-Type: application/json" | \
145
+ jq -r '.result[] | select(.phase == "http_request_firewall_custom") | .id')
146
+
147
+ if [ -z "$EXISTING" ]; then
148
+ METHOD="POST"
149
+ URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets"
150
+ DATA=$(jq -n --arg expr "$EXPRESSION" '{
151
+ name: "default", description: "WAF Custom Rules",
152
+ kind: "zone", phase: "http_request_firewall_custom",
153
+ rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
154
+ }')
155
+ else
156
+ METHOD="PUT"
157
+ URL="https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/$EXISTING"
158
+ DATA=$(jq -n --arg expr "$EXPRESSION" '{
159
+ description: "WAF Custom Rules",
160
+ rules: [{action: "block", description: "Block sanctioned countries", enabled: true, expression: $expr}]
161
+ }')
162
+ fi
163
+
164
+ RESULT=$(curl -s -X "$METHOD" "$URL" \
165
+ -H "Authorization: Bearer $API_TOKEN" \
166
+ -H "Content-Type: application/json" \
167
+ --data "$DATA" | jq '.success')
168
+
169
+ echo "Zone $ZONE_ID: $RESULT"
170
+ done
171
+ ```
172
+
173
+ ## Notes
174
+
175
+ - `PUT` on an existing ruleset **replaces all rules** in it. Include all rules in the payload if the ruleset already has other rules.
176
+ - WAF Custom Rules require a Cloudflare plan that supports rulesets (Free includes basic WAF; Pro/Business/Enterprise for full custom rules).
177
+ - Changes are applied globally within seconds; no cache purge needed.