---
name: copilot-deploy
description: >
  Deploy a static HTML file or folder to a public per-app subdomain (such as
  myapp.docs.copilotartifacts.com) on the CoPilot hosting server. Use when the user wants to
  publish, host, deploy, or put online a static site, HTML file, folder, or report to copilot,
  to docs.copilotartifacts.com, or to a docs.copilotartifacts.com subdomain. Requires the user
  to have registered their SSH key at https://docs.copilotartifacts.com first.
---

# CoPilot Deploy

Publish static HTML to a unique `https://<name>.docs.copilotartifacts.com` subdomain using
the user's own SSH access. Every command runs on the user's machine, because deployment
relies on their personal SSH key — there is no shared service account.

## Prerequisite
The user must have registered their SSH key at https://docs.copilotartifacts.com. Registration
gives them a no-sudo account in the `webapps` group. If the connection check in Step 1 fails,
send them there before doing anything else.

## Constants
- Server host: `docs.copilotartifacts.com` (used verbatim in every command below)
- Identity config file: `~/.config/copilot-deploy/config` — a single line, `USERNAME=...`

The only thing that varies per user is their username, so that's all the config stores. The
host is fixed, so it lives here as a constant rather than in the config file.

## Step 1 — Identity (ask once, then remember)
The goal is to learn the user's username once and reuse it, so they aren't asked every time.

1. If `~/.config/copilot-deploy/config` exists, read `USERNAME` from it and skip to Step 2.
2. Otherwise ask: "What username did you register at docs.copilotartifacts.com?"
3. Verify the key works (`BatchMode=yes` ensures it fails fast instead of hanging on a
   password prompt if the key isn't accepted):
   ```
   ssh -o BatchMode=yes -o ConnectTimeout=10 <USERNAME>@docs.copilotartifacts.com true
   ```
   - **Succeeds** → create the directory and save the username, then continue:
     ```
     mkdir -p ~/.config/copilot-deploy
     printf 'USERNAME=%s\n' '<username>' > ~/.config/copilot-deploy/config
     ```
   - **Fails** → tell the user: "I couldn't connect. Register your SSH key at
     https://docs.copilotartifacts.com first, then ask me again." Then stop — don't retry in
     a loop, since the fix is on the website and retrying won't change the outcome.

## Step 2 — Confirm local tools
Deployment uploads files from the user's machine, so the relevant CLI tools must exist locally.
`ssh` and `scp` ship with virtually every system. `rsync` is used for folders but is often
missing on Windows — check for it with `command -v rsync` and, if it's absent, fall back to
`scp -r` in Step 6 (noted there).

## Step 3 — What to deploy
Ask which file or folder to publish (or infer it from the conversation):
- a single `.html` file, or
- a folder — it should contain an `index.html` at its root; if it doesn't, warn the user that
  the site root (`/`) will return a 404 until one exists, then continue if they confirm.

## Step 4 — Choose and CONFIRM the subdomain name
The subdomain becomes part of a public URL, so it has to be a valid DNS label. Sanitize a
proposed name like this:

1. Start from the file or folder's base name (drop any leading directory path).
2. Drop a trailing `.html` extension if present.
3. Lowercase it.
4. Replace every run of characters that aren't `a–z` or `0–9` with a single `-`.
5. Strip any leading or trailing `-`.
6. Truncate to 40 characters, then strip a trailing `-` again in case the cut left one.

The result must match `^[a-z0-9][a-z0-9-]{1,40}$` (2–41 chars, starts alphanumeric) — this is
the exact rule the server enforces, so a name that fails it will be rejected in Step 6. If
sanitizing leaves an empty string or something too short to be meaningful (for example a
one-letter name from `a.html`), don't guess — ask the user to type a name and sanitize that
the same way.

Then confirm before doing anything on the server:
"Publish to **https://<name>.docs.copilotartifacts.com** — use this name?"
- **Yes** → continue.
- **No** → ask for an alternative, re-sanitize it the same way, and confirm again.

## Step 5 — Check the name is available
Subdomain names are global and first-come, so check before creating to avoid colliding with
someone else's app:
```
ssh <USERNAME>@docs.copilotartifacts.com 'test -e /var/www/apps/<name> && echo TAKEN || echo FREE'
```
- **FREE** → go to Step 6 (create it).
- **TAKEN** → the name is in use. Either pick a different name (return to Step 4), or — only if
  the user confirms they own that app and want to update it — treat this as an overwrite:
  **skip Step 6 entirely** (the subdomain already exists) and go straight to Step 7 to upload.

## Step 6 — Create the subdomain (new apps only)
Run this only when Step 5 reported FREE:
```
ssh <USERNAME>@docs.copilotartifacts.com sudo deploy-app.sh <name>
```
- Exits with "Invalid app name" / "reserved" → show the message and return to Step 4 for a new name.
- Fails issuing a certificate (e.g. a Let's Encrypt rate limit) → show the message and suggest
  trying again later; the name is reserved either way.

## Step 7 — Upload the files (adds and updates; never deletes by default)
- **Single file:**
  ```
  scp <file> <USERNAME>@docs.copilotartifacts.com:/var/www/apps/<name>/index.html
  ```
- **Folder (rsync available):**
  ```
  rsync -az <dir>/ <USERNAME>@docs.copilotartifacts.com:/var/www/apps/<name>/
  ```
- **Folder (rsync NOT available, e.g. Windows):**
  ```
  scp -r <dir>/. <USERNAME>@docs.copilotartifacts.com:/var/www/apps/<name>/
  ```
- **Exact mirror (deletes server files not present locally):** only if the user explicitly wants
  the live site to mirror their folder exactly, and only after confirming with them, since this
  is destructive:
  ```
  rsync -az --delete <dir>/ <USERNAME>@docs.copilotartifacts.com:/var/www/apps/<name>/
  ```

The trailing slash on `<dir>/` matters — it uploads the folder's *contents* into the app
directory rather than nesting the folder inside it.

## Step 8 — Give them the link
Tell the user: "✅ Live at **https://<name>.docs.copilotartifacts.com**"

## Step 9 — Password protection (only if the user asks for it)
1. Ask the user for the password they want and let them type it.
2. Apply it. The password is piped over stdin rather than passed as a command-line argument, so
   it doesn't appear in the server's process list or shell history:
   ```
   printf %s '<password>' | ssh <USERNAME>@docs.copilotartifacts.com sudo deploy-app.sh <name> --password
   ```
   Then tell them the site login is username `viewer` with the password they chose.
3. Remove protection later:
   ```
   ssh <USERNAME>@docs.copilotartifacts.com sudo deploy-app.sh <name> --public
   ```

## Notes
- The only thing stored locally is `USERNAME` in the config file; all access uses the user's
  own SSH key, so nothing sensitive is persisted.
- The Step 5 availability check is what prevents accidentally overwriting someone else's app —
  never skip it.
