Skip to main content
ClaudeWave
Skill91 repo starsupdated 1mo ago

cybrix-deploy

|

Install in Claude Code
Copy
git clone --depth 1 https://github.com/cybrixcc/cybrix-skills /tmp/cybrix-deploy && cp -r /tmp/cybrix-deploy/plugins/cybrix-deploy/skills/cybrix-deploy ~/.claude/skills/cybrix-deploy
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# cybrix-deploy

## Prerequisites

Before deploying, ensure the user has an API token. Check in this order:

1. `CYBRIX_DEPLOY_TOKEN` — set automatically by Claude Code from userConfig keychain (no action needed).
2. Environment variable `CYBRIX_TOKEN`.
3. File `~/.config/cybrix/token`.
4. File `.cybrix/token` in the project (gitignored).

If none exist, instruct the user exactly like this:

> No Cybrix token found. Get one free at **https://app.cybrix.cc/dashboard**
> (Step 3 — "Save your API token" → Generate).
>
> Once you have it, you can either:
> - **Paste it here** and I'll use it for this deploy (easiest)
> - Run `export CYBRIX_TOKEN=<token>` in your terminal to set it for this session
> - Run `echo <token> > ~/.config/cybrix/token` to save it permanently

Then wait for the user to provide the token. If they paste it directly in
chat, use it immediately — do not require them to re-run any command.
The project is ready to deploy once the token is available.

## Deployment workflow

### Step 1 — Detect project type (heuristic)

Do not rely on a framework whitelist. Instead, look for signals and
classify the project as **static**, **server**, or **unknown**.

**Static signals** (proceed with deploy):

- `package.json` has a `build` script AND output lands in `dist/`, `out/`,
  `build/`, `public/`, `_site/`, or `.output/public/`
- `next.config.{js,ts,mjs}` with `output: 'export'` or `output: 'static'`
- `astro.config.{js,ts,mjs}` present (default mode is static)
- `vite.config.{js,ts}` without SSR plugins
- `_config.yml` (Jekyll), `config.toml` or `hugo.toml` (Hugo),
  `.eleventy.js` / `eleventy.config.js` (Eleventy),
  `zola.toml` (Zola)
- Only HTML/CSS/JS files at root, no server entry point

**Server signals** (refuse — see below):

- `Dockerfile` or `docker-compose.yml` (unless it only copies a static
  `dist/`)
- `main.go`, `server.go`, or any `*.go` containing `net/http` or
  `ListenAndServe`
- `main.py`, `app.py`, `server.py` with `uvicorn`, `gunicorn`, `flask`,
  `fastapi` imports, or a `if __name__ == '__main__'` block calling
  `app.run` / `serve` / `asyncio.run`
- `main.rs` or `server.rs` with `actix`, `axum`, `rocket`, `warp`,
  or `tokio::main`
- `package.json` with a `start` script that runs `node`/`tsx`/`bun` on a
  server file (NOT `next start` in a static-export config)
- `Gemfile` with `puma`, `unicorn`, `rails`, or `sinatra`
- `pom.xml` or `build.gradle` with `spring-boot`
- `.csproj` with ASP.NET

**Database signals** (warn but allow if everything else is static):

- `*.sql` files, `migrations/` folder, `prisma/schema.prisma`,
  `drizzle.config.*`, `DATABASE_URL` referenced in source
- A static site hitting a hosted DB from the browser is unusual but valid.
  Warn the user, don't refuse.

**When refusing** (server signals detected):

> This looks like a project that needs a server runtime — I detected
> `<specific signal, e.g. "main.go with net/http" or "Dockerfile with EXPOSE">`.
>
> Cybrix currently supports static sites only. Your options:
> 1. Convert to a static export (e.g. Next.js `output: 'export'`, Astro,
>    Hugo).
> 2. Use a service that supports backends: Railway, Fly.io, Render.
> 3. Tell me to deploy anyway if you think the detection is wrong.

Always allow option 3 — heuristics are imperfect and the user knows
their project.

**Static output directories** to check, in order: `dist`, `out`, `public`,
`_site`, `build`, `.output/public`.

### Step 2 — Scan environment variables

After confirming the project is static but BEFORE running the build, scan
for environment variables the build will need.

**2a. Read .env files** — parse `KEY=value` format, skip comments (`#`)
and blank lines. Files to check: `.env`, `.env.local`, `.env.production`,
`.env.example`.

**2b. Grep source code** for build-time env var references:

- JS/TS: `process.env.X`, `import.meta.env.X`
- Look in `src/`, `app/`, `pages/`, `components/` — any
  `*.{js,jsx,ts,tsx,vue,svelte}`
- Pay extra attention to `NEXT_PUBLIC_*`, `VITE_*`, `PUBLIC_*`,
  `REACT_APP_*` — these are baked into the bundle at build time

**2c. Cross-reference** keys found in code against keys present in .env
files to find what the build needs.

**2d. Show the user:**

> I detected the following environment variables your build needs:
>
>   NEXT_PUBLIC_API_URL    (in .env.local, used in 3 files)
>   VITE_STRIPE_KEY        (in .env.local, used in src/checkout.ts)
>
> These need to be set before the build. How would you like to provide
> them?
>
>   1. Paste them here (sent encrypted with the deploy)
>   2. Set them later in the dashboard
>   3. Skip (build may fail or site may not work correctly)

If the user picks **option 1**, ask for each value one at a time. Include
them in the multipart POST to `/v1/deploys` as an `env_vars` field
(JSON map: `{"KEY": "value", ...}`).

**2e. Warn about missing variables** — if a var is referenced in code but
not in any .env file:

> ⚠ `AUTH_SECRET` is referenced in your code but not in any .env file.
> Provide it now or the build may fail.

**2f. Refuse to forward secrets in client-exposed vars** — if a key with
a client prefix (`NEXT_PUBLIC_*`, `VITE_*`, `REACT_APP_*`, `PUBLIC_*`)
looks like a secret (`*_SECRET`, `*_PRIVATE_KEY`, `DATABASE_URL`,
`JWT_SECRET`):

> ⚠ `NEXT_PUBLIC_JWT_SECRET` looks like a private secret but has a
> client-bundle prefix — it will be visible to anyone who opens your
> site's source. Are you sure you want to include it?

Do not send it without explicit confirmation.

### Step 3 — Choose project name and confirm

**3a. Infer a default name** from the current folder name, slugified
(lowercase, hyphens, max 32 chars). Example: `my-portfolio-site`.

**3b. Check availability** by calling:
```
GET https://api.cybrix.cc/v1/slugs/<name>/available
```
Response: `{"available": true, "slug": "my-portfolio-site"}`

- If `available: true` — use it as the default.
- If `available: false` — do NOT use it. Tell the user:
  > The name `<name>` is already taken. Wh