A zero-dependency AT Protocol Personal Data Server written in JavaScript
atproto pds

Federation Support Implementation Plan#

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Enable the Cloudflare PDS to federate with Bluesky by adding handle resolution, DID:PLC registration, and relay notification.

Architecture: Add /.well-known/atproto-did endpoint to resolve handles to DIDs. Create a zero-dependency Node.js setup script that generates P-256 keys, registers a did:plc with plc.directory, initializes the PDS, and notifies the relay.

Tech Stack: Cloudflare Workers (existing), Node.js crypto (setup script), plc.directory API, bsky.network relay


Task 1: Add Handle Storage to PDS#

Files:

  • Modify: src/pds.js

Step 1: Update /init endpoint to accept handle

In src/pds.js, modify the /init endpoint to also store the handle:

if (url.pathname === '/init') {
  const body = await request.json()
  if (!body.did || !body.privateKey) {
    return Response.json({ error: 'missing did or privateKey' }, { status: 400 })
  }
  await this.initIdentity(body.did, body.privateKey, body.handle || null)
  return Response.json({ ok: true, did: body.did, handle: body.handle || null })
}

Step 2: Update initIdentity method

Modify the initIdentity method to store handle:

async initIdentity(did, privateKeyHex, handle = null) {
  await this.state.storage.put('did', did)
  await this.state.storage.put('privateKey', privateKeyHex)
  if (handle) {
    await this.state.storage.put('handle', handle)
  }
}

Step 3: Add getHandle method

Add after getDid():

async getHandle() {
  return this.state.storage.get('handle')
}

Step 4: Test locally

Run: npx wrangler dev --port 8788

curl -X POST "http://localhost:8788/init?did=did:plc:test" \
  -H "Content-Type: application/json" \
  -d '{"did":"did:plc:test","privateKey":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef","handle":"alice.example.com"}'

Expected: {"ok":true,"did":"did:plc:test","handle":"alice.example.com"}

Step 5: Commit

git add src/pds.js
git commit -m "feat: add handle storage to identity"

Task 2: Add Handle Resolution Endpoint#

Files:

  • Modify: src/pds.js

Step 1: Add /.well-known/atproto-did endpoint

Add this at the START of the fetch() method, BEFORE the if (url.pathname === '/init') check. This endpoint should not require the ?did= query parameter:

async fetch(request) {
  const url = new URL(request.url)

  // Handle resolution - doesn't require ?did= param
  if (url.pathname === '/.well-known/atproto-did') {
    const did = await this.getDid()
    if (!did) {
      return new Response('User not found', { status: 404 })
    }
    return new Response(did, {
      headers: { 'Content-Type': 'text/plain' }
    })
  }

  // ... rest of existing fetch code

Step 2: Update the default export to handle /.well-known without ?did=

The main router needs to route /.well-known/atproto-did requests differently. Modify the default export:

export default {
  async fetch(request, env) {
    const url = new URL(request.url)

    // For /.well-known/atproto-did, extract DID from subdomain
    // e.g., alice.atproto-pds.chad-53c.workers.dev -> look up "alice"
    if (url.pathname === '/.well-known/atproto-did') {
      const host = request.headers.get('Host') || ''
      // For now, use the first Durable Object (single-user PDS)
      // Extract handle from subdomain if present
      const did = url.searchParams.get('did') || 'default'
      const id = env.PDS.idFromName(did)
      const pds = env.PDS.get(id)
      return pds.fetch(request)
    }

    const did = url.searchParams.get('did')
    if (!did) {
      return new Response('missing did param', { status: 400 })
    }

    const id = env.PDS.idFromName(did)
    const pds = env.PDS.get(id)
    return pds.fetch(request)
  }
}

Step 3: Test locally

Run: npx wrangler dev --port 8788

First init:

curl -X POST "http://localhost:8788/init?did=did:plc:testhandle" \
  -H "Content-Type: application/json" \
  -d '{"did":"did:plc:testhandle","privateKey":"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef","handle":"alice"}'

Then test resolution:

curl "http://localhost:8788/.well-known/atproto-did?did=did:plc:testhandle"

Expected: did:plc:testhandle

Step 4: Commit

git add src/pds.js
git commit -m "feat: add handle resolution endpoint"

Task 3: Create Setup Script Skeleton#

Files:

  • Create: scripts/setup.js
  • Modify: package.json

Step 1: Create scripts directory and setup.js

Create scripts/setup.js:

#!/usr/bin/env node

/**
 * PDS Setup Script
 *
 * Registers a did:plc, initializes the PDS, and notifies the relay.
 * Zero dependencies - uses Node.js built-ins only.
 *
 * Usage: node scripts/setup.js --handle alice --pds https://your-pds.workers.dev
 */

import { webcrypto } from 'crypto'

// === ARGUMENT PARSING ===

function parseArgs() {
  const args = process.argv.slice(2)
  const opts = {
    handle: null,
    pds: null,
    plcUrl: 'https://plc.directory',
    relayUrl: 'https://bsky.network'
  }

  for (let i = 0; i < args.length; i++) {
    if (args[i] === '--handle' && args[i + 1]) {
      opts.handle = args[++i]
    } else if (args[i] === '--pds' && args[i + 1]) {
      opts.pds = args[++i]
    } else if (args[i] === '--plc-url' && args[i + 1]) {
      opts.plcUrl = args[++i]
    } else if (args[i] === '--relay-url' && args[i + 1]) {
      opts.relayUrl = args[++i]
    }
  }

  if (!opts.handle || !opts.pds) {
    console.error('Usage: node scripts/setup.js --handle <handle> --pds <pds-url>')
    console.error('')
    console.error('Options:')
    console.error('  --handle      Handle name (e.g., "alice")')
    console.error('  --pds         PDS URL (e.g., "https://atproto-pds.chad-53c.workers.dev")')
    console.error('  --plc-url     PLC directory URL (default: https://plc.directory)')
    console.error('  --relay-url   Relay URL (default: https://bsky.network)')
    process.exit(1)
  }

  return opts
}

// === MAIN ===

async function main() {
  const opts = parseArgs()

  console.log('PDS Federation Setup')
  console.log('====================')
  console.log(`Handle: ${opts.handle}`)
  console.log(`PDS: ${opts.pds}`)
  console.log(`PLC: ${opts.plcUrl}`)
  console.log(`Relay: ${opts.relayUrl}`)
  console.log('')

  // TODO: Implement in subsequent tasks
  console.log('TODO: Generate keypair')
  console.log('TODO: Register DID:PLC')
  console.log('TODO: Initialize PDS')
  console.log('TODO: Notify relay')
}

main().catch(err => {
  console.error('Error:', err.message)
  process.exit(1)
})

Step 2: Add npm script

Modify package.json:

{
  "name": "cloudflare-pds",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "wrangler dev",
    "deploy": "wrangler deploy",
    "test": "node --test test/*.test.js",
    "setup": "node scripts/setup.js"
  }
}

Step 3: Test the skeleton

Run: node scripts/setup.js --handle alice --pds https://example.com

Expected:

PDS Federation Setup
====================
Handle: alice
PDS: https://example.com
...

Step 4: Commit

git add scripts/setup.js package.json
git commit -m "feat: add setup script skeleton"

Task 4: Add P-256 Key Generation#

Files:

  • Modify: scripts/setup.js

Step 1: Add key generation utilities

Add after the argument parsing section:

// === KEY GENERATION ===

async function generateP256Keypair() {
  const keyPair = await webcrypto.subtle.generateKey(
    { name: 'ECDSA', namedCurve: 'P-256' },
    true,
    ['sign', 'verify']
  )

  // Export private key as raw 32 bytes
  const privateJwk = await webcrypto.subtle.exportKey('jwk', keyPair.privateKey)
  const privateBytes = base64UrlDecode(privateJwk.d)

  // Export public key as uncompressed point (65 bytes)
  const publicRaw = await webcrypto.subtle.exportKey('raw', keyPair.publicKey)
  const publicBytes = new Uint8Array(publicRaw)

  // Compress public key to 33 bytes
  const compressedPublic = compressPublicKey(publicBytes)

  return {
    privateKey: privateBytes,
    publicKey: compressedPublic,
    cryptoKey: keyPair.privateKey
  }
}

function compressPublicKey(uncompressed) {
  // uncompressed is 65 bytes: 0x04 + x(32) + y(32)
  const x = uncompressed.slice(1, 33)
  const y = uncompressed.slice(33, 65)
  const prefix = (y[31] & 1) === 0 ? 0x02 : 0x03
  const compressed = new Uint8Array(33)
  compressed[0] = prefix
  compressed.set(x, 1)
  return compressed
}

function base64UrlDecode(str) {
  const base64 = str.replace(/-/g, '+').replace(/_/g, '/')
  const binary = atob(base64)
  const bytes = new Uint8Array(binary.length)
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i)
  }
  return bytes
}

function bytesToHex(bytes) {
  return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')
}

Step 2: Add did:key encoding for P-256

// === DID:KEY ENCODING ===

// Multicodec prefix for P-256 public key (0x1200)
const P256_MULTICODEC = new Uint8Array([0x80, 0x24])

function publicKeyToDidKey(compressedPublicKey) {
  // did:key format: "did:key:" + multibase(base58btc) of multicodec + key
  const keyWithCodec = new Uint8Array(P256_MULTICODEC.length + compressedPublicKey.length)
  keyWithCodec.set(P256_MULTICODEC)
  keyWithCodec.set(compressedPublicKey, P256_MULTICODEC.length)

  return 'did:key:z' + base58btcEncode(keyWithCodec)
}

function base58btcEncode(bytes) {
  const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'

  // Count leading zeros
  let zeros = 0
  for (const b of bytes) {
    if (b === 0) zeros++
    else break
  }

  // Convert to base58
  const digits = [0]
  for (const byte of bytes) {
    let carry = byte
    for (let i = 0; i < digits.length; i++) {
      carry += digits[i] << 8
      digits[i] = carry % 58
      carry = (carry / 58) | 0
    }
    while (carry > 0) {
      digits.push(carry % 58)
      carry = (carry / 58) | 0
    }
  }

  // Convert to string
  let result = '1'.repeat(zeros)
  for (let i = digits.length - 1; i >= 0; i--) {
    result += ALPHABET[digits[i]]
  }

  return result
}

Step 3: Update main() to generate and display keys

async function main() {
  const opts = parseArgs()

  console.log('PDS Federation Setup')
  console.log('====================')
  console.log(`Handle: ${opts.handle}`)
  console.log(`PDS: ${opts.pds}`)
  console.log('')

  // Step 1: Generate keypair
  console.log('Generating P-256 keypair...')
  const keyPair = await generateP256Keypair()
  const didKey = publicKeyToDidKey(keyPair.publicKey)
  console.log(`  did:key: ${didKey}`)
  console.log(`  Private key: ${bytesToHex(keyPair.privateKey)}`)
  console.log('')

  // TODO: Register DID:PLC
  // TODO: Initialize PDS
  // TODO: Notify relay
}

Step 4: Test key generation

Run: node scripts/setup.js --handle alice --pds https://example.com

Expected:

Generating P-256 keypair...
  did:key: zDnae...
  Private key: abcd1234...

Step 5: Commit

git add scripts/setup.js
git commit -m "feat: add P-256 key generation to setup script"

Task 5: Add DID:PLC Operation Signing#

Files:

  • Modify: scripts/setup.js

Step 1: Add CBOR encoding (minimal, for PLC operations)

// === CBOR ENCODING (minimal for PLC operations) ===

function cborEncode(value) {
  const parts = []

  function encode(val) {
    if (val === null) {
      parts.push(0xf6)
    } else if (typeof val === 'string') {
      const bytes = new TextEncoder().encode(val)
      encodeHead(3, bytes.length)
      parts.push(...bytes)
    } else if (typeof val === 'number') {
      if (Number.isInteger(val) && val >= 0) {
        encodeHead(0, val)
      }
    } else if (val instanceof Uint8Array) {
      encodeHead(2, val.length)
      parts.push(...val)
    } else if (Array.isArray(val)) {
      encodeHead(4, val.length)
      for (const item of val) encode(item)
    } else if (typeof val === 'object') {
      const keys = Object.keys(val).sort()
      encodeHead(5, keys.length)
      for (const key of keys) {
        encode(key)
        encode(val[key])
      }
    }
  }

  function encodeHead(majorType, length) {
    const mt = majorType << 5
    if (length < 24) {
      parts.push(mt | length)
    } else if (length < 256) {
      parts.push(mt | 24, length)
    } else if (length < 65536) {
      parts.push(mt | 25, length >> 8, length & 0xff)
    }
  }

  encode(value)
  return new Uint8Array(parts)
}

Step 2: Add SHA-256 hashing

// === HASHING ===

async function sha256(data) {
  const hash = await webcrypto.subtle.digest('SHA-256', data)
  return new Uint8Array(hash)
}

Step 3: Add PLC operation signing

// === PLC OPERATIONS ===

async function signPlcOperation(operation, privateKey) {
  // Encode operation without sig field
  const { sig, ...opWithoutSig } = operation
  const encoded = cborEncode(opWithoutSig)

  // Sign with P-256
  const signature = await webcrypto.subtle.sign(
    { name: 'ECDSA', hash: 'SHA-256' },
    privateKey,
    encoded
  )

  // Convert to low-S form and base64url encode
  const sigBytes = ensureLowS(new Uint8Array(signature))
  return base64UrlEncode(sigBytes)
}

function ensureLowS(sig) {
  // P-256 order N
  const N = BigInt('0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551')
  const halfN = N / 2n

  const r = sig.slice(0, 32)
  const s = sig.slice(32, 64)

  // Convert s to BigInt
  let sInt = BigInt('0x' + bytesToHex(s))

  // If s > N/2, replace with N - s
  if (sInt > halfN) {
    sInt = N - sInt
    const newS = hexToBytes(sInt.toString(16).padStart(64, '0'))
    const result = new Uint8Array(64)
    result.set(r)
    result.set(newS, 32)
    return result
  }

  return sig
}

function hexToBytes(hex) {
  const bytes = new Uint8Array(hex.length / 2)
  for (let i = 0; i < hex.length; i += 2) {
    bytes[i / 2] = parseInt(hex.substr(i, 2), 16)
  }
  return bytes
}

function base64UrlEncode(bytes) {
  const binary = String.fromCharCode(...bytes)
  return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}

async function createGenesisOperation(opts) {
  const { didKey, handle, pdsUrl, cryptoKey } = opts

  // Build the full handle
  const pdsHost = new URL(pdsUrl).host
  const fullHandle = `${handle}.${pdsHost}`

  const operation = {
    type: 'plc_operation',
    rotationKeys: [didKey],
    verificationMethods: {
      atproto: didKey
    },
    alsoKnownAs: [`at://${fullHandle}`],
    services: {
      atproto_pds: {
        type: 'AtprotoPersonalDataServer',
        endpoint: pdsUrl
      }
    },
    prev: null
  }

  // Sign the operation
  operation.sig = await signPlcOperation(operation, cryptoKey)

  return { operation, fullHandle }
}

async function deriveDidFromOperation(operation) {
  const { sig, ...opWithoutSig } = operation
  const encoded = cborEncode(opWithoutSig)
  const hash = await sha256(encoded)
  // DID is base32 of first 24 bytes of hash
  return 'did:plc:' + base32Encode(hash.slice(0, 24))
}

function base32Encode(bytes) {
  const alphabet = 'abcdefghijklmnopqrstuvwxyz234567'
  let result = ''
  let bits = 0
  let value = 0

  for (const byte of bytes) {
    value = (value << 8) | byte
    bits += 8
    while (bits >= 5) {
      bits -= 5
      result += alphabet[(value >> bits) & 31]
    }
  }

  if (bits > 0) {
    result += alphabet[(value << (5 - bits)) & 31]
  }

  return result
}

Step 4: Update main() to create operation

async function main() {
  const opts = parseArgs()

  console.log('PDS Federation Setup')
  console.log('====================')
  console.log(`Handle: ${opts.handle}`)
  console.log(`PDS: ${opts.pds}`)
  console.log('')

  // Step 1: Generate keypair
  console.log('Generating P-256 keypair...')
  const keyPair = await generateP256Keypair()
  const didKey = publicKeyToDidKey(keyPair.publicKey)
  console.log(`  did:key: ${didKey}`)
  console.log('')

  // Step 2: Create genesis operation
  console.log('Creating PLC genesis operation...')
  const { operation, fullHandle } = await createGenesisOperation({
    didKey,
    handle: opts.handle,
    pdsUrl: opts.pds,
    cryptoKey: keyPair.cryptoKey
  })
  const did = await deriveDidFromOperation(operation)
  console.log(`  DID: ${did}`)
  console.log(`  Handle: ${fullHandle}`)
  console.log('')

  // TODO: Register with plc.directory
  // TODO: Initialize PDS
  // TODO: Notify relay
}

Step 5: Test operation creation

Run: node scripts/setup.js --handle alice --pds https://example.com

Expected:

Creating PLC genesis operation...
  DID: did:plc:...
  Handle: alice.example.com

Step 6: Commit

git add scripts/setup.js
git commit -m "feat: add PLC operation signing"

Task 6: Add PLC Directory Registration#

Files:

  • Modify: scripts/setup.js

Step 1: Add PLC registration function

// === PLC DIRECTORY REGISTRATION ===

async function registerWithPlc(plcUrl, did, operation) {
  const url = `${plcUrl}/${encodeURIComponent(did)}`

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(operation)
  })

  if (!response.ok) {
    const text = await response.text()
    throw new Error(`PLC registration failed: ${response.status} ${text}`)
  }

  return true
}

Step 2: Update main() to register

Add after operation creation:

  // Step 3: Register with PLC directory
  console.log(`Registering with ${opts.plcUrl}...`)
  await registerWithPlc(opts.plcUrl, did, operation)
  console.log('  Registered successfully!')
  console.log('')

Step 3: Commit

git add scripts/setup.js
git commit -m "feat: add PLC directory registration"

Task 7: Add PDS Initialization#

Files:

  • Modify: scripts/setup.js

Step 1: Add PDS initialization function

// === PDS INITIALIZATION ===

async function initializePds(pdsUrl, did, privateKeyHex, handle) {
  const url = `${pdsUrl}/init?did=${encodeURIComponent(did)}`

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      did,
      privateKey: privateKeyHex,
      handle
    })
  })

  if (!response.ok) {
    const text = await response.text()
    throw new Error(`PDS initialization failed: ${response.status} ${text}`)
  }

  return response.json()
}

Step 2: Update main() to initialize PDS

Add after PLC registration:

  // Step 4: Initialize PDS
  console.log(`Initializing PDS at ${opts.pds}...`)
  const privateKeyHex = bytesToHex(keyPair.privateKey)
  await initializePds(opts.pds, did, privateKeyHex, fullHandle)
  console.log('  PDS initialized!')
  console.log('')

Step 3: Commit

git add scripts/setup.js
git commit -m "feat: add PDS initialization to setup script"

Task 8: Add Relay Notification#

Files:

  • Modify: scripts/setup.js

Step 1: Add relay notification function

// === RELAY NOTIFICATION ===

async function notifyRelay(relayUrl, pdsHostname) {
  const url = `${relayUrl}/xrpc/com.atproto.sync.requestCrawl`

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      hostname: pdsHostname
    })
  })

  // Relay might return 200 or 202, both are OK
  if (!response.ok && response.status !== 202) {
    const text = await response.text()
    console.warn(`  Warning: Relay notification returned ${response.status}: ${text}`)
    return false
  }

  return true
}

Step 2: Update main() to notify relay

Add after PDS initialization:

  // Step 5: Notify relay
  const pdsHostname = new URL(opts.pds).host
  console.log(`Notifying relay at ${opts.relayUrl}...`)
  const relayOk = await notifyRelay(opts.relayUrl, pdsHostname)
  if (relayOk) {
    console.log('  Relay notified!')
  }
  console.log('')

Step 3: Commit

git add scripts/setup.js
git commit -m "feat: add relay notification to setup script"

Task 9: Add Credentials File Output#

Files:

  • Modify: scripts/setup.js

Step 1: Add fs import and credentials saving

At the top of the file, add:

import { writeFileSync } from 'fs'

Step 2: Add credentials saving function

// === CREDENTIALS OUTPUT ===

function saveCredentials(filename, credentials) {
  writeFileSync(filename, JSON.stringify(credentials, null, 2))
}

Step 3: Update main() with final output

Replace the end of main() with:

  // Step 6: Save credentials
  const credentials = {
    handle: fullHandle,
    did,
    privateKeyHex: bytesToHex(keyPair.privateKey),
    didKey,
    pdsUrl: opts.pds,
    createdAt: new Date().toISOString()
  }

  const credentialsFile = `./credentials-${opts.handle}.json`
  saveCredentials(credentialsFile, credentials)

  // Final output
  console.log('Setup Complete!')
  console.log('===============')
  console.log(`Handle: ${fullHandle}`)
  console.log(`DID: ${did}`)
  console.log(`PDS: ${opts.pds}`)
  console.log('')
  console.log(`Credentials saved to: ${credentialsFile}`)
  console.log('Keep this file safe - it contains your private key!')
}

Step 4: Add .gitignore entry

Add to .gitignore (create if doesn't exist):

credentials-*.json

Step 5: Commit

git add scripts/setup.js .gitignore
git commit -m "feat: add credentials file output"

Task 10: Deploy and Test End-to-End#

Files:

  • None (testing only)

Step 1: Deploy updated PDS

source .env && npx wrangler deploy

Step 2: Run full setup

node scripts/setup.js --handle testuser --pds https://atproto-pds.chad-53c.workers.dev

Expected output:

PDS Federation Setup
====================
Handle: testuser
PDS: https://atproto-pds.chad-53c.workers.dev

Generating P-256 keypair...
  did:key: zDnae...

Creating PLC genesis operation...
  DID: did:plc:...
  Handle: testuser.atproto-pds.chad-53c.workers.dev

Registering with https://plc.directory...
  Registered successfully!

Initializing PDS at https://atproto-pds.chad-53c.workers.dev...
  PDS initialized!

Notifying relay at https://bsky.network...
  Relay notified!

Setup Complete!
===============
Handle: testuser.atproto-pds.chad-53c.workers.dev
DID: did:plc:...
PDS: https://atproto-pds.chad-53c.workers.dev

Credentials saved to: ./credentials-testuser.json

Step 3: Verify handle resolution

curl "https://atproto-pds.chad-53c.workers.dev/.well-known/atproto-did?did=<your-did>"

Expected: Returns the DID as plain text

Step 4: Verify on plc.directory

curl "https://plc.directory/<your-did>"

Expected: Returns DID document with your PDS as the service endpoint

Step 5: Create a test post

curl -X POST "https://atproto-pds.chad-53c.workers.dev/xrpc/com.atproto.repo.createRecord?did=<your-did>" \
  -H "Content-Type: application/json" \
  -d '{"collection":"app.bsky.feed.post","record":{"text":"Hello from my Cloudflare PDS!","createdAt":"2026-01-05T12:00:00.000Z"}}'

Step 6: Commit final state

git add -A
git commit -m "chore: federation support complete"

Summary#

Files created/modified:

  • src/pds.js - Added handle storage and /.well-known/atproto-did endpoint
  • scripts/setup.js - Complete setup script (~300 lines, zero dependencies)
  • package.json - Added setup script
  • .gitignore - Added credentials file pattern

What the setup script does:

  1. Generates P-256 keypair
  2. Creates did:key from public key
  3. Builds and signs PLC genesis operation
  4. Derives DID from operation
  5. Registers with plc.directory
  6. Initializes PDS with identity
  7. Notifies relay
  8. Saves credentials to file

Usage:

npm run setup -- --handle yourname --pds https://your-pds.workers.dev