
Cloudflare Workers Publish
- 87 installs
- 62 repo stars
- Updated August 3, 2026
- terrylica/cc-skills
Helps with ai & agent building tasks.
About
cloudflare-workers-publish is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- cloudflare-workers-publish
- AI & Agent Building
- AI-coding skill
Cloudflare Workers Publish by the numbers
- 87 all-time installs (skills.sh)
- Ranked #4,982 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/terrylica/cc-skills --skill cloudflare-workers-publishAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 87 |
|---|---|
| repo stars | ★ 62 |
| Last updated | August 3, 2026 |
| Repository | terrylica/cc-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Cloudflare Workers Publish
Deploy static HTML files (Bokeh charts, dashboards, reports) to Cloudflare Workers with Static Assets, using 1Password for credential management.
Scope: Static-only deployments on workers.dev. No dynamic Workers, no R2 object storage.
Prerequisite: 1Password CLI (brew install 1password-cli) + Node.js (npx wrangler)
---
Self-Evolving Skill: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues.
When to Use This Skill
- Publishing HTML files to a public URL (too large for GitHub)
- Setting up a new Cloudflare Workers static site
- Troubleshooting a failed Cloudflare deploy
- Rotating Cloudflare API tokens in 1Password
Do NOT use for: Dynamic Workers (JavaScript/TypeScript logic), Cloudflare Pages (deprecated April 2025 - CFW-01), R2 object storage, or custom domains (advanced setup not covered).
---
Architecture
Local project/
├── results/published/ # Deploy root (contains wrangler.toml)
│ ├── wrangler.toml # Workers config (name + assets)
│ ├── index.html # Auto-generated directory listing
│ └── gen800/ # Subdirectories with HTML files
│ └── XRPUSDT_750/
│ └── equity_plot.html # 13MB Bokeh chart
└── scripts/
└── publish_findings.sh # Deploy script (3 phases)
Credential flow:
1Password (Claude Automation vault)
├── account_id (TEXT) → CLOUDFLARE_ACCOUNT_ID env var
└── credential (CONCEALED) → CLOUDFLARE_API_TOKEN env var
↓
npx wrangler deploy
↓
https://{name}.{slug}.workers.dev/---
TodoWrite Task Templates
Template A - New Static Site (First-Time Setup)
1. [Preflight] Verify Node.js and 1Password CLI installed
2. [Preflight] Create Cloudflare API token (Workers Scripts Edit permission)
3. [Execute] Pre-provision 1Password item in Claude Automation vault (biometric)
4. [Execute] Store token + account ID in 1Password item fields
5. [Execute] Create publish directory with wrangler.toml (3 fields only)
6. [Execute] Create deploy script from skill template (parameterize 4 vars)
7. [Execute] Create mise task wrapper in .mise/tasks/publish.toml
8. [Execute] Add .wrangler/ to .gitignore
9. [Execute] Add LFS tracking for large HTML files in .gitattributes
10. [Verify] Enable workers.dev subdomain in Cloudflare dashboard
11. [Verify] Run first deploy and verify URL in browser (NOT curl)
12. [Verify] Document the workers.dev URL in project docsTemplate B - Add Files to Existing Published Site
1. [Preflight] Verify files are real content, not LFS pointers (head -1)
2. [Execute] Copy HTML files to published/{generation}/{symbol_threshold}/
3. [Execute] Run deploy script (index.html auto-regenerates)
4. [Verify] Verify new files appear at workers.dev URL in browserTemplate C - New Worker (New Subdomain/Project)
1. [Preflight] Choose worker name ({name}.{slug}.workers.dev)
2. [Execute] Create 1Password item OR reuse existing Cloudflare credentials
3. [Execute] Create wrangler.toml with chosen name and today's date
4. [Execute] Create parameterized deploy script from skill template
5. [Execute] Create mise task wrapper
6. [Verify] Deploy and discover actual workers.dev URL via wrangler outputTemplate D - Rotate Cloudflare API Token
1. [Execute] Create new API token in Cloudflare dashboard (Workers Scripts Edit)
2. [Execute] Update 1Password item credential field (biometric required)
3. [Verify] Run deploy script to verify new token works
4. [Execute] Revoke old token in Cloudflare dashboardTemplate E - Troubleshoot Failed Deploy
1. Is wrangler.toml in current directory? (CFW-10)
2. Are credentials populated? Print first 8 chars of account ID
3. Is --reveal present for CONCEALED fields? (CFW-03)
4. Is workers.dev subdomain registered in CF dashboard? (CFW-07)
5. Does token have Workers Scripts Edit permission? (CFW-11)
6. Are HTML files real content or LFS pointers? head -1 file (CFW-12)
7. SSL handshake error? Verify in browser, not curl (CFW-08)
8. Is npx wrangler installed? npx wrangler --version---
Workflow: First-Time Setup
Phase 1: Create Cloudflare API Token
1. Go to <https://dash.cloudflare.com/profile/api-tokens> 2. Click Create Token > Custom token 3. Set permissions: Account > Workers Scripts > Edit (CFW-11) 4. Account Resources: Include > your account 5. Copy the token (shown only once)
Phase 2: Provision 1Password Credentials
CRITICAL (CFW-02): 1Password service accounts can only READ items. They CANNOT CREATE new items. Create the item manually first.
See 1Password setup guide for step-by-step instructions.
After provisioning, the item should have:
| Field | Type | --reveal | Content |
|---|---|---|---|
account_id | TEXT | No | Cloudflare acct ID |
credential | CONCEALED | YES | API token |
Phase 3: Create wrangler.toml
See wrangler setup guide.
# Minimal Workers Static Assets config (CFW-09)
name = "my-project-name"
compatibility_date = "2026-02-18"
[assets]
directory = "."Phase 4: Create Deploy Script
Copy the bundled template and edit the 4 config variables:
cp "$(skill-path)/scripts/publish_static.sh" scripts/publish_myproject.sh
# Edit: PUBLISH_DIR, OP_ITEM_ID, SITE_TITLE, PROJECT_URLOr reference the working implementation: rangebar-patterns/scripts/publish_findings.sh
Phase 5: Create mise Task
# .mise/tasks/publish.toml (CFW-13: bash in .sh file, not inline TOML)
["publish:site"]
description = "Deploy published files to Cloudflare Workers (static)"
run = "bash scripts/publish_myproject.sh"Add to .mise.toml [task_config] includes:
[task_config]
includes = [
".mise/tasks/publish.toml",
]Phase 6: Git Hygiene
.gitignore:
# Wrangler temp files (Cloudflare Workers deploy)
.wrangler/
results/published/.wrangler/.gitattributes (for large HTML files):
results/published/**/*.html filter=lfs diff=lfs merge=lfs -textPhase 7: Enable workers.dev Subdomain (CFW-07)
First-time Cloudflare accounts must enable the workers.dev route:
1. Go to <https://dash.cloudflare.com> > Workers & Pages 2. Enable workers.dev subdomain
The subdomain is NOT predictable (CFW-06). Discover yours after deploy:
npx wrangler whoamiPhase 8: Deploy and Verify
mise run publish:siteVerify in BROWSER, not curl (CFW-08). macOS LibreSSL can fail TLS handshake with Cloudflare but browsers handle it fine.
---
Anti-Patterns Summary
Full details with code examples: references/anti-patterns.md
| ID | Severity | Gotcha | Fix |
|---|---|---|---|
| CFW-01 | HIGH | Cloudflare Pages deprecated (April 2025) | Use Workers with Static Assets |
| CFW-02 | HIGH | 1P service account creating items | Pre-provision via biometric/web UI |
| CFW-03 | HIGH | Missing --reveal for CONCEALED fields | Always pass --reveal for API tokens |
| CFW-04 | MEDIUM | SC2155 export VAR=$(cmd) | Split: VAR=$(cmd) then export VAR |
| CFW-05 | LOW | Bash 4+ ${var^^} on macOS | Use tr '[:lower:]' '[:upper:]' |
| CFW-06 | MEDIUM | Assuming workers.dev URL format | Run npx wrangler whoami to discover slug |
| CFW-07 | HIGH | workers.dev subdomain not registered | Enable in Cloudflare dashboard first |
| CFW-08 | LOW | curl SSL/TLS handshake failure on macOS | Verify in browser instead |
| CFW-09 | MEDIUM | Overcomplicating wrangler.toml | Only name, compatibility_date, [assets] |
| CFW-10 | HIGH | Running wrangler from wrong directory | Always cd to directory with wrangler.toml |
| CFW-11 | MEDIUM | Excessive token permissions | Workers Scripts Edit (Account) only |
| CFW-12 | HIGH | Deploying LFS pointers instead of files | Run git lfs pull before deploy |
| CFW-13 | MEDIUM | Tera template conflict in mise TOML | Complex bash in standalone .sh files |
| CFW-14 | MEDIUM | Pipe subshell data loss in while-read | Use < <(find ...) process substitution |
| CFW-15 | LOW | No directory listing page | Auto-generate index.html before each deploy |
---
Reference Implementation
The working production deployment lives in rangebar-patterns:
| File | Purpose |
|---|---|
results/published/wrangler.toml | Minimal Workers config |
scripts/publish_findings.sh | 3-phase deploy script |
.mise/tasks/publish.toml | mise task wrapper |
.gitignore (.wrangler/) | Ignore wrangler temp files |
.gitattributes | LFS tracking for HTML |
Live URL: https://rangebar-findings.terry-301.workers.dev/
---
Post-Change Checklist
After modifying this skill:
1. [ ] Anti-patterns table matches references/anti-patterns.md 2. [ ] All bash examples use set -euo pipefail 3. [ ] No hardcoded 1Password item IDs (parameterized) 4. [ ] No hardcoded workers.dev slugs (discovered at runtime) 5. [ ] Template script passes bash -n syntax check 6. [ ] All internal links use relative paths (./references/...) 7. [ ] Link validator passes 8. [ ] Skill validator passes 9. [ ] Append changes to references/evolution-log.md
---
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
op item get returns masked | Missing --reveal flag (CFW-03) | Add --reveal for CONCEALED fields |
op item create fails | Service account can't create (CFW-02) | Use biometric op or web UI to create item first |
| wrangler: config not found | Not in correct directory (CFW-10) | cd to directory containing wrangler.toml before deploy |
| SSL handshake failure | macOS LibreSSL (CFW-08) | Verify in browser; ignore curl errors |
| 403 on workers.dev URL | Subdomain not enabled (CFW-07) | Enable in Cloudflare dashboard > Workers & Pages |
| Deploy succeeds, files missing | LFS pointers deployed (CFW-12) | Run git lfs pull before deploy |
${var^^} syntax error | Bash 3 on macOS (CFW-05) | Use tr '[:lower:]' '[:upper:]' |
| mise TOML parse error | Tera template conflict (CFW-13) | Move complex bash to standalone .sh file |
| Empty index.html | No gen*/*.html files found | Check file paths match find . -path './gen*/*.html' pattern |
| Token permission denied | Wrong token scope (CFW-11) | Recreate with Account > Workers Scripts > Edit permission |
Post-Execution Reflection
After this skill completes, reflect before closing the task:
0. Locate yourself. — Find this SKILL.md's canonical path (Glob for this skill's name) before editing. All corrections target THIS file and its sibling references/ — never other documentation. 1. What failed? — Fix the instruction that caused it. If it could recur, add it as an anti-pattern. 2. What worked better than expected? — Promote it to recommended practice. Document why. 3. What drifted? — Any script, reference, or external dependency that no longer matches reality gets fixed now. 4. Log it. — Every change gets an evolution-log entry with trigger, fix, and evidence.
Do NOT defer. The next invocation inherits whatever you leave behind
Cloudflare Workers Publish Anti-Patterns
Gotchas discovered during rangebar-patterns deployment (2026-02-18). Severity ratings indicate impact of hitting each issue without prior knowledge.
---
CFW-01: Using Cloudflare Pages (DEPRECATED) [HIGH]
Symptom: wrangler pages deploy produces deprecation warnings or fails.
Root cause: Cloudflare deprecated Pages in April 2025 in favor of Workers with Static Assets.
Fix: Use Workers with [assets] section in wrangler.toml. No Worker script needed.
# WRONG — Pages (deprecated)
# wrangler pages deploy ./dist
# RIGHT — Workers Static Assets
name = "my-site"
compatibility_date = "2026-02-18"
[assets]
directory = "."---
CFW-02: 1Password Service Account Cannot Create Items [HIGH]
Symptom: op item create fails with permission error when using OP_SERVICE_ACCOUNT_TOKEN.
Root cause: 1Password service accounts have READ + UPDATE access only. They cannot CREATE new items.
Fix: Create the 1Password item manually first (biometric op CLI or web UI), then scripts read from it via the service account.
# Step 1: Create item (interactive, requires biometric)
op item create \
--category "API Credential" \
--title "Cloudflare Workers - my-project" \
--vault "Claude Automation"
# Step 2: Script reads via service account (headless, no biometric)
OP_SERVICE_ACCOUNT_TOKEN="$(cat ~/.claude/.secrets/op-service-account-token)" \
op item get "{item-id}" --vault "Claude Automation" --fields "credential" --revealSee 1Password setup guide for full provisioning steps.
---
CFW-03: Missing --reveal for CONCEALED Fields [HIGH]
Symptom: op item get returns a masked placeholder string instead of the actual token value.
Root cause: 1Password CONCEALED fields require the --reveal flag to return the actual value.
Fix: Always use --reveal when fetching API tokens or passwords.
# WRONG — returns masked placeholder
op item get "$ITEM_ID" --vault "Claude Automation" --fields "credential"
# RIGHT — returns actual token value
op item get "$ITEM_ID" --vault "Claude Automation" --fields "credential" --revealTEXT fields (like account_id) do NOT need --reveal.
---
CFW-04: SC2155 — export VAR=$(cmd) Masks Return Values [MEDIUM]
Symptom: Script continues past a failed credential fetch because export masks the non-zero exit code.
Root cause: ShellCheck SC2155. export VAR=$(cmd) always returns 0 (the export succeeds) even if $(cmd) fails.
Fix: Split into two statements.
# WRONG — exit code of op is masked
export CLOUDFLARE_API_TOKEN=$(op item get "$ITEM_ID" --fields "credential" --reveal)
# RIGHT — preserves exit code
CLOUDFLARE_API_TOKEN=$(op item get "$ITEM_ID" --fields "credential" --reveal)
export CLOUDFLARE_API_TOKEN---
CFW-05: Bash 4+ Syntax on macOS [LOW]
Symptom: ${var^^} (uppercase conversion) fails with "bad substitution" on macOS.
Root cause: macOS ships with bash 3.2. ${var^^} is a bash 4+ feature.
Fix: Use tr for portable case conversion.
# WRONG — bash 4+ only
gen_upper="${gen_dir^^}"
# RIGHT — works on bash 3 and zsh
gen_upper=$(echo "$gen_dir" | tr '[:lower:]' '[:upper:]')---
CFW-06: workers.dev Subdomain Is Not Predictable [MEDIUM]
Symptom: Guessing the URL format leads to 404.
Root cause: The workers.dev URL is {worker-name}.{account-slug}.workers.dev. The account-slug is auto-generated by Cloudflare (e.g., terry-301), not derivable from account fields.
Fix: Discover your account slug after first deploy:
npx wrangler whoami
# Or check: Cloudflare dashboard > Workers & Pages > OverviewHardcode the full URL in your deploy script's success message and project docs.
---
CFW-07: workers.dev Subdomain Not Registered [HIGH]
Symptom: Deploy succeeds but URL returns 403 Forbidden or "workers.dev is not registered."
Root cause: First-time Cloudflare accounts must explicitly enable the workers.dev route.
Fix:
1. Go to <https://dash.cloudflare.com> > Workers & Pages 2. Look for workers.dev subdomain section 3. Click to enable/register your subdomain 4. Re-deploy
---
CFW-08: SSL/TLS Handshake Failure from macOS curl [LOW]
Symptom: curl https://{name}.{slug}.workers.dev/ fails with SSL handshake error. URL works fine in browsers.
Root cause: macOS bundles LibreSSL 3.3.6 which may fail TLS negotiation with Cloudflare's edge servers.
Fix: Verify in a browser. Do NOT use curl -k (disables certificate verification entirely).
# If you must use CLI:
curl --tlsv1.2 "https://{name}.{slug}.workers.dev/"
# Or just open in browser — this is a non-issue for actual users---
CFW-09: Overcomplicating wrangler.toml [MEDIUM]
Symptom: Copying wrangler.toml examples from docs that include main, routes, zone_id, KV bindings.
Root cause: Most examples are for dynamic Workers. Static-only sites need almost nothing.
Fix: Minimal config:
name = "my-project"
compatibility_date = "2026-02-18"
[assets]
directory = "."Three fields total. See wrangler setup guide.
---
CFW-10: Running wrangler from Wrong Directory [HIGH]
Symptom: wrangler deploy says "no wrangler.toml found" or deploys wrong files.
Root cause: wrangler looks for wrangler.toml in the current working directory.
Fix: Always cd to the directory containing wrangler.toml before deploy.
# WRONG — runs from project root
npx wrangler deploy
# RIGHT — cd to publish directory first
cd "$PUBLISH_DIR"
npx wrangler deploy 2>&1---
CFW-11: Excessive Token Permissions [MEDIUM]
Symptom: Token created with too many permissions (Zone edit, DNS, etc.).
Root cause: Following docs that suggest broader permissions for complex setups.
Fix: For static assets on workers.dev, the minimum is:
- Permission: Account > Workers Scripts > Edit
- Account resources: Include > (your specific account)
- No Zone, DNS, or custom domain permissions needed
---
CFW-12: Deploying Git LFS Pointers Instead of Files [HIGH]
Symptom: Deploy succeeds, but HTML files show as small text files with LFS pointer data.
Root cause: Git LFS stores pointers in the repo. git lfs pull must be run first.
Fix:
# Check if files are pointers
head -1 results/published/gen800/XRPUSDT_750/equity_plot.html
# If it starts with "version https://git-lfs.github.com" — it's a pointer
# Pull actual files
git lfs pull
# Then deploy
bash scripts/publish_findings.sh---
CFW-13: Tera Template Conflict in mise TOML [MEDIUM]
Symptom: mise run publish:site fails with "Variable not found in context."
Root cause: mise uses the Tera templating engine which interprets {{}}, {%}, and #} in TOML run strings.
Fix: Keep TOML task definitions simple. Move bash logic to standalone .sh files.
# WRONG — Tera will choke on bash syntax
["publish:site"]
run = '''
for f in "${FILES[@]}"; do
echo "processing #${f}"
done
'''
# RIGHT — mise just invokes the script
["publish:site"]
run = "bash scripts/publish_static.sh"---
CFW-14: Pipe Subshell Data Loss in while-read [MEDIUM]
Symptom: Variables set inside a while read loop are empty after the loop ends.
Root cause: find ... | while read runs the while in a subshell. Subshell variable changes do not propagate.
Fix: Use process substitution.
# WRONG — subshell, FOUND always 0 after loop
find . -name "*.html" | while IFS= read -r f; do
FOUND=$((FOUND + 1))
done
echo "$FOUND" # Always 0
# RIGHT — process substitution, runs in current shell
while IFS= read -r f; do
FOUND=$((FOUND + 1))
done < <(find . -name "*.html")
echo "$FOUND" # Correct count---
CFW-15: No Directory Listing Page [LOW]
Symptom: Workers static assets serves files by exact path but root URL returns 404 or blank page.
Root cause: Workers static assets does not auto-generate directory listings.
Fix: Auto-generate an index.html before each deploy that lists all published files. See the bundled deploy script template for the index generation logic.
cloudflare-workers-publish Evolution Log
Reverse-chronological log of skill improvements.
---
2026-02-18: Initial skill creation
Source: Empirical discovery during rangebar-patterns static hosting setup for Bokeh equity charts (10-15MB HTML files too large for GitHub).
Reference implementation: rangebar-patterns repository
results/published/wrangler.toml— minimal Workers Static Assets configscripts/publish_findings.sh— 3-phase deploy (1Password creds, index gen, wrangler deploy).mise/tasks/publish.toml— mise task wrapper
Gotchas documented: 15 anti-patterns (CFW-01 through CFW-15) covering:
- Cloudflare Pages deprecation (April 2025)
- 1Password service account limitations (read-only, no create)
- CONCEALED field
--revealrequirement - macOS bash 3 portability
- workers.dev subdomain discovery
- SSL/TLS handshake failure with macOS curl
- Git LFS pointer vs actual file content
- Tera template conflicts in mise TOML
Validation: Deployed 13MB Bokeh HTML chart to https://rangebar-findings.terry-301.workers.dev/ successfully.
1Password Credential Setup for Cloudflare Workers
Step-by-step guide for provisioning Cloudflare API credentials in 1Password's Claude Automation vault.
Architecture
1Password — Claude Automation vault
Item: "Cloudflare Workers - {project}"
├── account_id [TEXT] = Cloudflare Account ID
└── credential [CONCEALED] = Cloudflare API Token
Access:
├── Biometric (interactive op CLI): CREATE, READ, UPDATE
└── Service Account (headless): READ, UPDATE onlyCritical Constraint (CFW-02)
1Password service accounts CANNOT create new items. They can only READ and UPDATE existing items. Create the item first via one of:
1. Interactive op CLI (biometric prompt) 2. 1Password web UI (<https://my.1password.com>) 3. 1Password desktop app
Step-by-Step Provisioning
Step 1: Get Cloudflare Account ID
1. Go to <https://dash.cloudflare.com> 2. Click on any domain (or Workers and Pages) 3. Account ID is shown in the right sidebar (32-char hex string)
Step 2: Create API Token
1. Go to <https://dash.cloudflare.com/profile/api-tokens> 2. Create Token > Custom token 3. Permissions: Account > Workers Scripts > Edit (CFW-11) 4. Account Resources: Include > your account 5. Continue to summary > Create Token 6. Copy the token immediately (shown only once)
Step 3: Create 1Password Item (Interactive)
Using biometric op CLI:
# Create the item (requires biometric authentication)
op item create \
--category "API Credential" \
--title "Cloudflare Workers - {project-name}" \
--vault "Claude Automation" \
--tags "cloudflare,workers,static-hosting"
# Note the item ID from the output (e.g., "ewtid322w2bozkzqfg4my2kd5m")Or via 1Password web UI: Navigate to Claude Automation vault > + > API Credential.
Step 4: Set Fields
# Set account_id (TEXT field)
op item edit "{item-id}" \
--vault "Claude Automation" \
"account_id[text]={your-cloudflare-account-id}"
# Set credential (CONCEALED field)
op item edit "{item-id}" \
--vault "Claude Automation" \
"credential[concealed]={your-api-token}"Or via web UI: edit the item, add a TEXT field named account_id and a CONCEALED field named credential.
Step 5: Verify Service Account Access
# Test that the headless service account can read the item
OP_SERVICE_ACCOUNT_TOKEN="$(cat ~/.claude/.secrets/op-service-account-token)" \
op item get "{item-id}" --vault "Claude Automation" --fields "account_id"
# CRITICAL (CFW-03): Use --reveal for the CONCEALED field
OP_SERVICE_ACCOUNT_TOKEN="$(cat ~/.claude/.secrets/op-service-account-token)" \
op item get "{item-id}" --vault "Claude Automation" --fields "credential" --reveal
# Both should return actual values, not masked placeholdersStep 6: Record Item ID
Store the 1Password item ID in your deploy script as a constant:
OP_ITEM_ID="ewtid322w2bozkzqfg4my2kd5m"This ID is NOT secret (opaque reference, not a credential). Safe to commit to source control.
Field Type Reference
| Field Name | 1Password Type | --reveal Needed | Purpose |
|---|---|---|---|
account_id | TEXT | No | Cloudflare Account ID (32-char hex) |
credential | CONCEALED | YES (CFW-03) | API token (sensitive) |
Service Account Token Location
~/.claude/.secrets/op-service-account-token (chmod 600)Access scope: Read + Write to the Claude Automation vault only.
Reusing Credentials Across Projects
If multiple projects deploy to the same Cloudflare account, they can share the same 1Password item. The token's Workers Scripts Edit permission applies to all workers in the account.
Differentiate projects via separate worker names in wrangler.toml. One API token deploys to all of them.
Token Rotation
1. Create new API token in Cloudflare dashboard 2. Update the existing 1Password item (biometric or web UI required):
op item edit "{item-id}" \
--vault "Claude Automation" \
"credential[concealed]={new-api-token}"3. Verify deploy works with new token 4. Revoke old token in Cloudflare dashboard
The deploy script needs no changes since it reads from the same 1Password item.
Wrangler Setup Guide
Step-by-step guide for creating a minimal wrangler.toml for Cloudflare Workers Static Assets.
Prerequisites
- Node.js installed (for
npx wrangler) - Cloudflare account with API token (see 1Password setup)
Minimal wrangler.toml
# Cloudflare Workers Static Assets
# Deploy: mise run publish:{task} (or bash scripts/publish_{project}.sh)
name = "{worker-name}"
compatibility_date = "{YYYY-MM-DD}"
[assets]
directory = "."Three fields. That is all. No main, no route, no zone_id.
Field Reference
| Field | Required | Value | Notes |
|---|---|---|---|
name | YES | Worker name (kebab-case) | Becomes {name}.{slug}.workers.dev |
compatibility_date | YES | ISO date (YYYY-MM-DD) | Use today's date; controls API behavior versioning |
[assets] directory | YES | "." | Relative to wrangler.toml location |
main | NO | Omit entirely | Not needed for static-only |
route | NO | Omit entirely | workers.dev route is automatic |
zone_id | NO | Omit entirely | Not needed without custom domain |
account_id | NO | Omit (use env var instead) | Set via CLOUDFLARE_ACCOUNT_ID env var |
Worker Name Conventions
Choose a descriptive, unique name:
rangebar-findings— research project findingstrading-dashboards— dashboard collection{project}-static— generic pattern
The name must be globally unique within your Cloudflare account.
Directory Structure
Place wrangler.toml at the root of the directory to serve:
results/published/ # <-- wrangler.toml goes here
├── wrangler.toml
├── index.html # Root page (auto-generated by deploy script)
└── gen800/ # Subdirectory hierarchy
└── XRPUSDT_750/
└── equity_plot.html # Served at /gen800/XRPUSDT_750/equity_plot.htmlAll files under the directory path are deployed. Workers serves them by exact path match.
Compatibility Date
Set to the date you first create the worker. You do not need to update it unless you want to opt into new runtime behavior.
Verification
Validate config without deploying:
cd results/published/
npx wrangler deploy --dry-run 2>&1Custom Domains (Optional, Advanced)
To use a custom domain instead of workers.dev:
1. Add domain to Cloudflare (requires DNS management) 2. Add routes to wrangler.toml:
routes = [
{ pattern = "static.example.com/*", zone_name = "example.com" }
]This is outside the scope of the basic static hosting workflow.
#!/usr/bin/env bash
set -euo pipefail
# =============================================================================
# Generic static site deploy to Cloudflare Workers via 1Password credentials.
#
# USAGE:
# bash publish_static.sh
#
# CONFIGURATION (edit the 4 variables below, or set as environment variables):
# PUBLISH_DIR — directory containing wrangler.toml and files to deploy
# OP_ITEM_ID — 1Password item ID in Claude Automation vault
# SITE_TITLE — human-readable title for the generated index.html
# PROJECT_URL — GitHub/project URL for the footer link
#
# PHASES:
# 1. Fetch Cloudflare credentials from 1Password
# 2. Auto-generate index.html directory listing
# 3. Deploy via npx wrangler deploy
#
# ANTI-PATTERNS ADDRESSED:
# CFW-02: Service account read-only (creds pre-provisioned)
# CFW-03: --reveal for CONCEALED fields
# CFW-04: SC2155 split export
# CFW-05: tr for uppercase (bash 3 compat)
# CFW-10: cd to wrangler.toml directory
# CFW-12: LFS pointer detection
# CFW-14: Process substitution for while-read
# =============================================================================
# ---- CONFIGURATION (EDIT THESE OR SET AS ENV VARS) ----
PUBLISH_DIR="${PUBLISH_DIR:-CHANGE_ME}"
OP_ITEM_ID="${OP_ITEM_ID:-CHANGE_ME}"
SITE_TITLE="${SITE_TITLE:-Published Findings}"
PROJECT_URL="${PROJECT_URL:-https://github.com/terrylica/CHANGE_ME}"
# ---- END CONFIGURATION ----
echo "=== Deploy Static Site to Cloudflare Workers ==="
# Pre-flight: verify wrangler.toml exists (CFW-10)
if [ ! -f "$PUBLISH_DIR/wrangler.toml" ]; then
echo "ERROR: wrangler.toml not found in $PUBLISH_DIR"
echo "Create it first. See: references/wrangler-setup.md"
exit 1
fi
# =============================================================================
# Phase 1: Fetch credentials from 1Password (CFW-02, CFW-03, CFW-04)
# =============================================================================
echo "Phase 1: Fetching Cloudflare credentials from 1Password..."
# CFW-04: Split declaration and export to preserve exit codes (SC2155)
CLOUDFLARE_ACCOUNT_ID=$(OP_SERVICE_ACCOUNT_TOKEN="$(cat ~/.claude/.secrets/op-service-account-token)" \
op item get "$OP_ITEM_ID" --vault "Claude Automation" --fields "account_id")
export CLOUDFLARE_ACCOUNT_ID
# CFW-03: --reveal is REQUIRED for CONCEALED fields
CLOUDFLARE_API_TOKEN=$(OP_SERVICE_ACCOUNT_TOKEN="$(cat ~/.claude/.secrets/op-service-account-token)" \
op item get "$OP_ITEM_ID" --vault "Claude Automation" --fields "credential" --reveal)
export CLOUDFLARE_API_TOKEN
echo " Account ID: ${CLOUDFLARE_ACCOUNT_ID:0:8}..."
echo " Token loaded: yes"
# =============================================================================
# Phase 2: Auto-generate index.html (CFW-05, CFW-14, CFW-15)
# =============================================================================
echo "Phase 2: Generating index.html..."
INDEX="$PUBLISH_DIR/index.html"
cat > "$INDEX" << HEADER
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>${SITE_TITLE}</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; color: #333; }
h1 { border-bottom: 2px solid #0066cc; padding-bottom: 8px; }
h2 { color: #555; margin-top: 32px; }
a { color: #0066cc; text-decoration: none; }
a:hover { text-decoration: underline; }
.file-list { list-style: none; padding: 0; }
.file-list li { padding: 8px 0; border-bottom: 1px solid #eee; }
.file-list li:last-child { border-bottom: none; }
.meta { color: #888; font-size: 0.85em; margin-left: 12px; }
footer { margin-top: 40px; color: #999; font-size: 0.8em; border-top: 1px solid #eee; padding-top: 12px; }
</style>
</head>
<body>
<h1>${SITE_TITLE}</h1>
<p>Published static files.</p>
HEADER
# CFW-10: cd to publish directory (wrangler.toml location)
cd "$PUBLISH_DIR"
FOUND=0
CURRENT_GEN=""
# CFW-14: Process substitution to avoid subshell data loss
while IFS= read -r html_file; do
[ -z "$html_file" ] && continue
FOUND=$((FOUND + 1))
# Extract generation directory and sub-path
gen_dir=$(echo "$html_file" | cut -d'/' -f1)
sub_path=$(echo "$html_file" | cut -d'/' -f2)
# Start new section for each generation
if [ "$gen_dir" != "$CURRENT_GEN" ]; then
# Close previous list if open (fixes unclosed <ul> bug for multi-gen)
if [ -n "$CURRENT_GEN" ]; then
echo " </ul>" >> "$INDEX"
fi
CURRENT_GEN="$gen_dir"
# CFW-05: tr for uppercase (bash 3 compatible, not ${var^^})
gen_upper=$(echo "$gen_dir" | tr '[:lower:]' '[:upper:]')
echo " <h2>$gen_upper</h2>" >> "$INDEX"
echo " <ul class=\"file-list\">" >> "$INDEX"
fi
# File size for display
fsize=$(du -h "$html_file" | cut -f1 | tr -d ' ')
fname=$(basename "$html_file" .html)
echo " <li><a href=\"$html_file\">$sub_path / $fname</a> <span class=\"meta\">($fsize)</span></li>" >> "$INDEX"
done < <(find . -path './gen*/*.html' -type f | sed 's|^\./||' | sort)
# Close the last list if any files found
if [ "$FOUND" -gt 0 ] && [ -n "$CURRENT_GEN" ]; then
echo " </ul>" >> "$INDEX"
fi
if [ "$FOUND" -eq 0 ]; then
echo " <p><em>No published files yet.</em></p>" >> "$INDEX"
fi
# Footer with timestamp
TIMESTAMP=$(date -u '+%Y-%m-%d %H:%M UTC')
cat >> "$INDEX" << FOOTER
<footer>
Published: $TIMESTAMP | $FOUND files |
<a href="${PROJECT_URL}">GitHub</a>
</footer>
</body>
</html>
FOOTER
echo " Generated index.html ($FOUND files)"
# =============================================================================
# Phase 3: Pre-flight checks + Deploy (CFW-12, CFW-10)
# =============================================================================
# CFW-12: Check for LFS pointers before deploying
LFS_POINTER_FOUND=0
while IFS= read -r html_file; do
[ -z "$html_file" ] && continue
first_line=$(head -1 "$html_file")
if echo "$first_line" | grep -q "^version https://git-lfs.github.com"; then
echo "WARNING: LFS pointer detected: $html_file"
LFS_POINTER_FOUND=1
fi
done < <(find . -path './gen*/*.html' -type f)
if [ "$LFS_POINTER_FOUND" -eq 1 ]; then
echo ""
echo "ERROR: LFS pointers found. Run 'git lfs pull' before deploying."
echo "Aborting deploy."
exit 1
fi
echo "Phase 3: Deploying to Cloudflare Workers..."
# CFW-10: Already in PUBLISH_DIR from Phase 2
npx wrangler deploy 2>&1
echo ""
echo "=== Deploy complete ==="
echo "Verify in browser (not curl — see CFW-08 re: macOS SSL)."