---
name: slidesfly
description: |
  Publish or share an existing local HTML deck (HTML PPT / slide file) to Slidesfly
  to get a shareable short URL. Use ONLY when (1) a .html file already exists on
  disk AND (2) user mentions "publish / share / 分享 / 发布 / 传到网上 / 给我链接 /
  put online / 上线 / make this shareable", OR wants to manage existing Slidesfly
  decks (list / delete / change visibility / claim / login).

  DO NOT use this skill to generate the HTML deck. If the user wants to create a
  new deck, use a slide-generation skill (e.g. anthropic/slides, codex/slides,
  marp, reveal.js) or write HTML inline FIRST, then invoke this skill to publish
  the resulting file.
version: 0.1.0
license: MIT
---

# Slidesfly Skill

Publish a local HTML deck to [Slidesfly](https://slidesfly.com) and return a shareable link on `slidesfly.xyz`.

## Trigger conditions

**Do trigger** when all of the following are true:

1. A local `.html` or `.htm` file already exists (or was just generated in this session).
2. The user wants to publish, share, or manage an existing Slidesfly deck.

Examples that **should** trigger:

- "Publish this deck and give me a link."
- "把这个 HTML 演示稿发到网上。"
- "Share the slides with my team."
- "List my Slidesfly decks."
- "Delete the deck we published yesterday."

Examples that **should not** trigger:

- "Make me a 10-slide deck about RAG." (generate HTML first, then publish)
- "Improve the typography on slide 3." (edit HTML first)
- "What is Slidesfly?" (answer directly, no CLI)
- "Convert this PDF to HTML." (different task)
- "Deploy this Next.js app." (not a single HTML deck file)

## Prerequisites

1. **HTML file on disk** — single self-contained `.html` is best for v0.
2. **Node.js + npx** — preferred entrypoint (no global install required):

```bash
npx -y @slidesfly/cli@latest publish ./deck.html --title "My Deck" --json
```

3. **Install this skill locally** (optional, for persistent agent guidance):

```bash
npx -y @slidesfly/cli@latest install --target auto
# or: slidesfly install --target cursor --scope project
```

Alias for repeated use in one shell session:

```bash
alias slidesfly='npx -y @slidesfly/cli@latest'
```

## Reader navigation (optional)

Decks can include this script so swipe and keyboard navigation from the Slidesfly reader shell (`slidesfly.xyz/d/...`) reaches in-iframe slide decks via `postMessage`:

```html
<script src="https://slidesfly.xyz/reader-host.js"></script>
```

The shell sends `{ source: 'slidesfly', action: 'next' | 'prev' | ... }`; the host script dispatches keyboard events and Reveal.js API calls when present.

## Anonymous-first workflow (default)

**No login is required for the first publish.** The CLI posts to the anonymous API, stores `claim_token` locally, and returns a URL on the content domain:

```bash
npx -y @slidesfly/cli@latest publish ./deck.html --title "Q4 Plan" --json
```

Example response:

```json
{
  "ok": true,
  "data": {
    "deck_id": "v0c8Kf3sQ1MnEa7bYj9wHt",
    "url": "https://slidesfly.xyz/d/v0c8Kf3sQ1MnEa7bYj9wHt",
    "title": "Q4 Plan",
    "visibility": "unlisted",
    "size_bytes": 184320,
    "anonymous": true
  },
  "warnings": [{
    "code": "ANONYMOUS_LOCAL_ONLY",
    "message": "This deck is managed only on this machine via ~/.slidesfly/config.json. Run slidesfly login to claim it to your account."
  }]
}
```

Agent obligations after publish:

1. Show the full `https://slidesfly.xyz/d/...` URL to the user.
2. Explain visibility (`unlisted` = link-only access by default).
3. Repeat the `ANONYMOUS_LOCAL_ONLY` warning when present.
4. Never paste `claim_token` or raw `~/.slidesfly/config.json` into chat.

## Authenticated publish (when logged in)

After `slidesfly login`, the CLI stores an API key in `~/.slidesfly/config.json`. Subsequent `publish` calls use the owned-deck API (no local `claim_token`, no `anon_decks` entry):

```bash
slidesfly login --json
slidesfly publish ./deck.html --title "Q4 Plan" --visibility public --json
slidesfly publish ./deck.html --id v0c8Kf3sQ1MnEa7bYj9wHt --json   # update owned deck
slidesfly delete v0c8Kf3sQ1MnEa7bYj9wHt --json
slidesfly visibility v0c8Kf3sQ1MnEa7bYj9wHt public --json
```

Owned publish supports `--visibility public|unlisted|private` (plan limits apply). Use `--id <deck_id>` to upload a new version of an existing owned deck. `delete` and `visibility` work on owned decks by deck ID when logged in, even if the deck is not in local `anon_decks`.

## Command reference

| Command | Purpose |
|---|---|
| `slidesfly publish <file.html> [--title T] [--visibility V] [--id DECK_ID] [--json]` | Publish (anonymous if logged out; owned if logged in). `--id` updates an owned deck |
| `slidesfly list [--json]` | List decks stored in local config (no server call) |
| `slidesfly open <deck_id> [--json]` | Open deck URL in browser |
| `slidesfly delete <deck_id> [--json]` | Delete deck (anon: local claim_token; owned: API key) |
| `slidesfly visibility <deck_id> <public\|unlisted\|private> [--json]` | Change visibility (anon: fails with ANONYMOUS_LIMITED; owned: API key) |
| `slidesfly claim [deck_id] [--json]` | Claim anonymous deck(s) to logged-in account |
| `slidesfly login [--no-claim] [--api-key KEY] [--code] [--json]` | Browser PKCE login (default); `--code` for headless/SSH; `--api-key` is fallback |
| `slidesfly logout [--json]` | Clear stored API key (keeps anon_decks) |
| `slidesfly status [--json]` | Show local config summary |
| `slidesfly install [--target auto\|claude-code\|cursor\|all] [--scope user\|project] [--force] [--from-url URL] [--json]` | Install this skill for Claude Code / Cursor |
| `slidesfly uninstall [--json]` | Remove installed skill files (does not delete config) |

Global flag: `--api-key <key>` on any command.

Always pass `--json` when parsing stdout programmatically. Non-TTY stdout auto-emits JSON.

## Common workflows

### Publish a new deck

```bash
npx -y @slidesfly/cli@latest publish ./deck.html --title "RAG 101" --json
```

### Update title or republish

When logged in, update an owned deck in place:

```bash
slidesfly publish ./deck.html --id v0c8Kf3sQ1MnEa7bYj9wHt --json
```

When logged out, republish creates a new anonymous deck. After `login` + `claim`, use authenticated publish/update instead.

### Manage local anonymous decks

```bash
slidesfly list --json
slidesfly delete v0c8Kf3sQ1MnEa7bYj9wHt --json
```

### Upgrade to account (claim)

When the user needs public visibility, analytics, or cross-machine management:

```bash
slidesfly login --json          # opens browser; auto-claims local anon decks
slidesfly login --no-claim --json   # login only, skip auto-claim
slidesfly claim --json            # claim manually if needed
```

Fallback when browser/loopback login is blocked (SSH, remote devbox, no local browser):

```bash
slidesfly login --code --json
# CLI prints /cli/code URL; user opens it in any browser, copies 8-char code, pastes in terminal
slidesfly claim --json
```

Fallback when no browser access at all:

```bash
slidesfly login --api-key "$KEY" --json
slidesfly claim --json
```

## Error handling matrix

Parse `error.code` from JSON output. Follow retry rules strictly.

| Code | HTTP | Agent action | User message template |
|---|---|---|---|
| `INVALID_HTML` | 422 | Do not retry | "HTML 格式有问题：{details}。需要我重写吗？" |
| `MALICIOUS_CONTENT` | 451 | Do not retry | "Slidesfly 拒绝了该 deck（{details}）。误判可去 /report 申诉" |
| `QUOTA_EXCEEDED` | 402 | Do not retry; suggest login | "匿名配额已用完。运行 `slidesfly login` 注册后可发更多" |
| `RATE_LIMITED` | 429 | Wait 60s, retry once | 终失败："Slidesfly 限频中，请稍后再试" |
| `AUTH_REQUIRED` | 401 | Do not retry; prompt login | "需要登录。运行 `slidesfly login` 后我再试" |
| `AUTH_INVALID` | 401 | Do not retry; re-login | "Token 失效，运行 `slidesfly login` 重新授权" |
| `FORBIDDEN` | 403 | Do not retry | "无权操作该 deck。匿名 deck 可先 `slidesfly login` 再 claim" |
| `DECK_NOT_FOUND` | 404 | Do not retry | "Deck `{id}` 不存在或已删除" |
| `EXPIRED` | 410 | Do not retry | "Deck 已过期" |
| `PASSWORD_REQUIRED` | 401 | Ask user for password | "该 deck 有密码保护，请告诉我密码" |
| `ANONYMOUS_LIMITED` | 403 | Suggest login + claim | "匿名 deck 只能 unlisted。要登录认领后再改吗？" |
| `INTERNAL_ERROR` | 500 | Retry once | "Slidesfly 后端错误，稍后重试" |
| `SERVICE_UNAVAILABLE` | 503 | Wait 30s, retry once | 终失败："Slidesfly 上游不可用" |

**Network errors:** wait 5s, retry once, then stop.

**Global rules:**

- Never auto-rewrite HTML after `INVALID_HTML` or `MALICIOUS_CONTENT`.
- Do not retry other 4xx errors (except one 429 retry).
- Retry 5xx at most once.

## Output contract

Success:

```json
{ "ok": true, "data": { ... }, "warnings": [ ... ] }
```

Failure:

```json
{ "ok": false, "error": { "code": "...", "message": "...", "hint": "..." } }
```

Human TTY mode prints minimal text (usually the URL). Agents should always use `--json`.

## Sandbox notes

If `npx` is unavailable:

1. Check for a preinstalled `slidesfly` binary.
2. Ask the user to run `npx @slidesfly/cli publish ...` locally and paste the URL.
3. Worst case: `curl -F file=@deck.html` against `https://slidesfly.com/api/decks/anonymous` (requires manual multipart assembly).

If loopback login is blocked, use `slidesfly login --code` (headless device flow) or `slidesfly login --api-key` with a key from the dashboard.

## Example dialogues

**Success:** User asks to publish an existing `deck.html` → run publish with `--json` → return `https://slidesfly.xyz/d/...` + anonymous warning.

**Quota exceeded:** Surface `QUOTA_EXCEEDED`, suggest `slidesfly login`, do not spam retries.

**Malicious content:** Surface `MALICIOUS_CONTENT` verbatim, do not modify HTML and retry.

**Cross-machine:** `slidesfly list` empty → explain local-only anon decks; offer `slidesfly login` + claim on original machine.

**Go public:** When logged out, `visibility public` on anon deck → `ANONYMOUS_LIMITED` → suggest `slidesfly login` + claim, or publish with `--visibility public` after login.

## Privacy & security

- Do not expose `~/.slidesfly/config.json`, API keys, or `claim_token`.
- Default visibility is `unlisted` unless the user explicitly asks for `public`.
- Share URLs live on `slidesfly.xyz`; SaaS dashboard lives on `slidesfly.com`.
