---
name: favori
version: 0.1.0
description: >-
  Publish static artifacts (HTML sites, reports, dashboards, demos, folders of
  files) to favori.ai and get a shareable https://<slug>.preview.favori.ai URL.
  Use when the user asks to publish, deploy, share, host, or get a link for
  static files — or, in French, when they say "publie", "mets en ligne",
  "partage ce site/rapport", "héberge ces fichiers", "donne-moi une URL".
---

# favori

**Skill version: 0.1.0**

favori.ai is a gateway that publishes static artifacts and returns a shareable
URL at `https://<slug>.preview.favori.ai`. It is built to be driven by agents.

Use this skill when the user wants to put static files online: an HTML site, a
report, a dashboard export, a demo, or any folder of files.

To install or update: `curl -fsSL https://api.favori.ai/install.sh | bash`

## Quick start

```bash
URL=$(~/.claude/skills/favori/scripts/publish.sh ./site)
```

`publish.sh` prints **only the URL on stdout** (progress and warnings go to
stderr), so it captures cleanly. Give that URL to the user.

## Read the current docs first

Before answering questions about favori capabilities, limits, or error codes,
read the live machine summary — values there are authoritative and can change
per instance:

→ **https://api.favori.ai/llms.txt**

Full schema: **https://api.favori.ai/openapi.json**. Keep this file short; rely
on the server for live values (limits, error codes).

## Requirements

- `curl`, `tar`
- `jq` **or** `python3` (the script uses whichever is present to parse the JSON
  response — no hard dependency on jq)

## API key

Publishing requires an API key (`favori_…`).

- The user creates it themselves on the dashboard at **https://favori.ai**
  (Settings → API keys). Signup is closed in production — **do not try to create
  an account or sign the user up.**
- The script reads the key, first match wins:
  1. `$FAVORI_API_KEY` environment variable
  2. `~/.favori/credentials` file (the key on a single line)
- If no key is configured, **ask the user for their key**, then store it for
  them yourself — do not ask them to run the command manually:

  ```bash
  mkdir -p ~/.favori && printf '%s\n' "$KEY" > ~/.favori/credentials && chmod 600 ~/.favori/credentials
  ```

**Never print, log, echo back, or commit the API key.** Never read the
credentials file just to show its contents to the user.

## Publishing

```bash
./scripts/publish.sh [OPTIONS] <dir | file...>
```

- **One directory** → its contents become the site root. Put `index.html` at the
  root of that directory (it is auto-detected as the entry point), not inside a
  subfolder.
- **Multiple files** → each file is added under its own name.

| Option                  | Effect                                                       |
| ----------------------- | ------------------------------------------------------------ |
| `--name <text>`         | Display label for the publication                            |
| `--expires-in-days <n>` | Expiry in days (server caps at 30; default 7)                |
| `--never-expire`        | Publication never expires                                    |
| `--password <pw>`       | Protect with a password (visitors get an HTTP Basic challenge) |
| `--api-url <url>`       | API base URL (default `https://api.favori.ai`)               |
| `-h`, `--help`          | Usage                                                        |

## No update — publications are immutable

⚠️ favori has **no update endpoint**. Re-publishing always creates a **new URL**
(a new slug). There is no way to edit a live publication in place.

If the user wants to "update" a site:

1. Re-publish — this yields a new URL. Give them the new URL.
2. Offer to delete the old one:
   ```bash
   curl -fsS -X DELETE -H "Authorization: Bearer $FAVORI_API_KEY" \
     https://api.favori.ai/api/v1/publications/<id>
   ```
   Find `<id>` via `GET /api/v1/publications` (the listing returns `id` per
   publication).

## What to tell the user

- Always give them the **URL** and the **expiration date**. Default expiry is
  **7 days** (max 30); mention `--never-expire` if they want it permanent.
- Always relay the **`skipped`** files the script reports. Sensitive files
  (`.env`, private keys, SQL dumps, …) are **excluded automatically** — this is a
  security feature, not an error. Tell the user what was left out.
- If the publication is password-protected, remind them visitors will face an
  HTTP Basic Auth challenge with that password.
- The URL is public-by-obscurity (the slug is not guessable) unless protected by
  a password. Don't describe an unprotected publication as private.

## Error handling

Errors come back as a stable envelope — test `error.code` (stable,
machine-readable), never `message` (human, may change):

```json
{ "error": { "code": "unauthorized", "message": "…", "field": null } }
```

| `code`                                      | Meaning / what to do                                    |
| ------------------------------------------- | ------------------------------------------------------- |
| `unauthorized`                              | `Authorization` header missing → configure a key        |
| `invalid_credentials`                       | Key wrong or revoked → ask the user for a fresh key     |
| `validation_error`                          | A field is invalid (`field` says which)                 |
| `file_too_large` / `publication_too_large`  | Over the size limits                                    |
| `request_too_large`                         | Archive over the request limit (~26 MB)                 |
| `empty_publication`                         | Everything was excluded — nothing left to publish       |

Full list: **https://api.favori.ai/llms.txt**.

## Limits

- 25 MB per file
- 100 MB per publication
- 500 files per publication
- Uploaded archive ≤ ~26 MB (request Content-Length limit). Above that, the
  one-shot upload won't fit; split the artifact or trim large assets.

Live values for a given instance are reflected in `/llms.txt` and
`/openapi.json`.
