Skill: hosainnet/deno-subhosting-deploy-skill (version 0.1.0)

--- name: deno-deploy description: Deploy simple web pages and HTML apps live to the internet using the Deno Deploy REST API. Use this skill whenever the user wants to make something "live", "hosted", "shareable via URL", "deployed", or "accessible online" — even if they don't mention Deno explicitly. Also trigger when the user asks to build a web page, interactive app, or HTML project that would benefit from a live URL. Does not require the Deno MCP tool — this skill is fully standalone and uses the Deno API directly. license: Apache-2.0 metadata: author: hosainnet version: "0.0.1" --- # Deno Deploy Skill (Standalone) Deploy simple web pages and HTML apps to Deno Deploy using a bundled Python script that calls the Deno REST API directly. No MCP tool required. --- ## Credentials Setup (First Time) Before deploying, the user must create a **Deno Subhosting organization** and retrieve their credentials: 1. Go to [dash.deno.com/subhosting/new_auto](https://dash.deno.com/subhosting/new_auto) and create a new subhosting org 2. From the org dashboard, copy the **org ID** and **access token** Then save them as config files under `~/.config/deno-deploy/`: ```bash mkdir -p ~/.config/deno-deploy echo "your_token_here" > ~/.config/deno-deploy/access_token echo "your_org_id_here" > ~/.config/deno-deploy/org_id ``` If these files don't exist, the deploy script will print a clear error with setup instructions. Direct the user to [dash.deno.com/subhosting/new_auto](https://dash.deno.com/subhosting/new_auto) to get started. --- ## Step 1: Plan the App Before writing code, think about: - What HTML/CSS/JS is needed? - Does it need external libraries? (Use CDN links — no npm installs) - Is it purely static, or does it need a simple backend (e.g., an API route)? For simple pages: serve everything from a single `main.ts` file with inline HTML. --- ## Step 2: Write Good Deno-Compatible Code ### Standard Pattern All Deno Deploy apps must export a `fetch` handler: ```typescript export default { async fetch(req: Request): Promise<Response> { const html = `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My App</title> </head> <body> <!-- content here --> </body> </html>`; return new Response(html, { headers: { "Content-Type": "text/html; charset=utf-8" }, }); }, }; ``` ### Key Rules - **No Node.js APIs** — no `require()`, no `fs`, no `path` - **No npm packages** — use CDN links (e.g. `https://cdn.tailwindcss.com`) - **Single file** — inline all HTML, CSS, JS as template literals in `main.ts` - **Always set Content-Type** — include `charset=utf-8` for HTML responses - **Routing** — use `new URL(req.url).pathname` for multi-route apps ### Useful CDN Libraries | Purpose | URL | |---|---| | Tailwind CSS | `https://cdn.tailwindcss.com` | | Alpine.js | `https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js` | | Chart.js | `https://cdn.jsdelivr.net/npm/chart.js` | | Marked (markdown) | `https://cdn.jsdelivr.net/npm/marked/marked.min.js` | --- ## Step 3: Save the Code to a File Write the TypeScript code to a temporary file, e.g. `/tmp/main.ts`: ```bash cat > /tmp/main.ts << 'EOF' export default { async fetch(req: Request): Promise<Response> { ... }, }; EOF ``` --- ## Step 4: Deploy Using the Script Run the bundled deploy script: ```bash python scripts/deploy.py \ --name <project-name> \ --code /tmp/main.ts ``` **Project naming tips:** - Use the topic/purpose: `birthday-card`, `sales-dashboard`, `quiz-game` - Lowercase, hyphens only, max ~30 chars - Avoid generic names like `app` or `test` The script will: 1. Create a new Deno Deploy project 2. Upload the code 3. Print the live URL --- ## Step 5: Verify the Deployment After the deploy script runs, you MUST verify the deployment was successful: 1. **Check the script output** — look at the deployment response JSON printed by the script: - `"status"` should NOT be `"failed"`. If it is, the code has errors — fix and redeploy. - If the status is `"pending"`, wait a few seconds and proceed to the next check. 2. **Curl the live URL** to confirm it's serving correctly: ```bash curl -s -o /dev/null -w "%{http_code}" https://<project-name>.deno.dev ``` - **200** = success, the page is live - **404** or **500** = something is wrong — check the deployment logs URL printed by the script - **Any other error** = the deployment may still be propagating, wait 5 seconds and retry once 3. **If the deployment failed**, check for these common causes: - Syntax errors in the TypeScript code (missing braces, unclosed template literals) - Missing `export default { fetch }` handler - Use of Node.js APIs (`require`, `fs`, etc.) - Fix the issue in the code file and redeploy Do NOT tell the user the deployment succeeded until you have confirmed it with curl. --- ## Step 6: Share the Result After a **verified** successful deployment, always: 1. **Share the live URL** prominently as a clickable link 2. **Briefly explain** what the user will see when they open it Example: > Your page is live at **https://your-project.deno.dev** > > It shows [brief description]. Let me know if you'd like to change anything! --- ## Common Pitfalls - Don't forget `<!DOCTYPE html>` — browsers may render in quirks mode without it - Don't use backticks inside template literals without escaping them - Don't forget `export default { fetch }` — the app won't start without it - If the project name is already taken, try a more specific name or add a suffix

path: scripts/deploy.py
content: #!/usr/bin/env python3
"""
Standalone Deno Deploy script.
Reads credentials from ~/.config/deno-deploy/{access_token,org_id}.

Usage:
  python deploy.py --name <project-name> --code <path-to-code-file>
  python deploy.py --name <project-name> --code-string '<inline code>'
"""

import argparse
import json
import os
import sys
import urllib.request
import urllib.error

API = "https://api.deno.com/v1"
CONFIG_DIR = os.path.expanduser("~/.config/deno-deploy")


def get_credentials():
    token_path = os.path.join(CONFIG_DIR, "access_token")
    org_id_path = os.path.join(CONFIG_DIR, "org_id")
    if not os.path.exists(token_path) or not os.path.exists(org_id_path):
        print("ERROR: Deno Deploy credentials not found.")
        print()
        print("1. Create a Deno Subhosting org at: https://dash.deno.com/subhosting/new_auto")
        print("2. Copy the org ID and access token from the org dashboard")
        print("3. Save them:")
        print(f"   mkdir -p {CONFIG_DIR}")
        print(f'   echo "your_token_here" > {token_path}')
        print(f'   echo "your_org_id_here" > {org_id_path}')
        sys.exit(1)
    [REDACTED_SECRET]).read().strip()
    org_id = open(org_id_path).read().strip()
    return token, org_id


def api_request(method, path, token, body=None):
    url = f"{API}{path}"
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json",
    }
    data = json.dumps(body).encode() if body else None
    req = urllib.request.Request(url, data=data, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req) as resp:
            return json.loads(resp.read())
    except urllib.error.HTTPError as e:
        error_body = e.read().decode()
        print(f"ERROR: HTTP {e.code} from Deno API")
        print(error_body)
        sys.exit(1)


def deploy(project_name, code, entry_point="main.ts", env_vars=None, description=None):
    token, org_id = get_credentials()

    # Step 1: Create project
    print(f"Creating project '{project_name}'...")
    body = {"name": project_name}
    if description:
        body["description"] = description
    project = api_request("POST", f"/organizations/{org_id}/projects", token, body)
    project_id = project["id"]
    project_name_actual = project["name"]
    print(f"  Project created: {project_name_actual} (id: {project_id})")

    # Step 2: Create deployment
    print("Deploying code...")
    deployment_body = {
        "entryPointUrl": entry_point,
        "assets": {
            entry_point: {
                "kind": "file",
                "content": code,
                "encoding": "utf-8",
            }
        },
        "envVars": env_vars or {},
    }
    if description:
        deployment_body["description"] = description
    deployment = api_request("POST", f"/projects/{project_id}/deployments", token, deployment_body)

    deployment_id = deployment.get("id", "")
    status = deployment.get("status", "unknown")
    domains = deployment.get("domains", [])
    url = f"https://{project_name_actual}.deno.dev"

    print(f"\n--- Deployment Response ---")
    print(json.dumps(deployment, indent=2))
    print(f"---")

    if status == "failed":
        print(f"\n❌ Deployment failed!")
        print(f"   Status: {status}")
        print(f"   Check the response above for error details.")
        sys.exit(1)

    print(f"\n✅ Deployed successfully!")
    print(f"   URL: {url}")
    print(f"   Deployment ID: {deployment_id}")
    print(f"   Status: {status}")
    if domains:
        print(f"   Domains: {', '.join(domains)}")
    print(f"\n⚠️  IMPORTANT: Verify the deployment by visiting the URL above.")
    print(f"   If the page shows an error, check the deployment logs at:")
    print(f"   https://dash.deno.com/projects/{project_name_actual}/deployments/{deployment_id}")
    return {"url": url, "deployment_id": deployment_id, "status": status, "project_id": project_id, "project_name": project_name_actual}


def main():
    parser = argparse.ArgumentParser(description="Deploy code to Deno Deploy")
    parser.add_argument("--name", required=True, help="Project name (kebab-case)")
    parser.add_argument("--code", help="Path to TypeScript/JS file to deploy")
    parser.add_argument("--code-string", help="Inline code string to deploy")
    parser.add_argument("--entry-point", default="main.ts", help="Entry point filename (default: main.ts)")
    parser.add_argument("--description", help="Project description")
    args = parser.parse_args()

    if args.code:
        with open(args.code) as f:
            code = f.read()
    elif args.code_string:
        code = args.code_string
    else:
        print("ERROR: Provide either --code <file> or --code-string '<code>'")
        sys.exit(1)

    deploy(args.name, code, args.entry_point, description=args.description)


if __name__ == "__main__":
    main()

Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (HIGH)