Skill: lmanchu/gamma-presentation (version 1.0.0)

--- name: gamma description: Generate professional presentations with Gamma AI. Just describe what you want — topic, outline, or full content — and get a polished deck. No Gamma account needed. metadata: {"clawdbot":{"emoji":"🎯","os":["darwin","linux","windows"],"requires":{"bins":["bun"],"env":["GAMMA_API_KEY"]}}} --- # Gamma Presentation Generator Generate polished presentations from a topic or content brief. Uses Gamma AI to create professionally designed slides. The user does not need a Gamma account. ## When to Use Trigger when the user wants to: - Create a presentation, deck, or slides - "Make me a pitch deck about X" - "Generate slides for my talk on Y" - "Create a presentation with 10 slides about Z" Do NOT trigger for: - PPTX file editing (use document-skills:pptx instead) - HTML/web presentations (use frontend-slides instead) - Presenton-specific requests (use presenton instead) ## How to Use ### Step 1: Gather Requirements Ask the user (if not already provided): - **Topic or content**: What the presentation is about - **Audience**: Who is this for? (investors, team, conference) - **Length**: How many slides? (default: ~8-10) - **Format**: PDF or PPTX? (default: PDF) ### Step 2: Prepare the Input For best results, compose a structured prompt: ``` Create a [N]-slide presentation about [TOPIC]. Audience: [WHO] Tone: [professional/casual/technical] Key points to cover: 1. [Point 1] 2. [Point 2] 3. [Point 3] Include: [data, charts, examples, quotes, etc.] ``` ### Step 3: Generate ```bash GAMMA_API_KEY="$GAMMA_API_KEY" bun run ~/.claude/skills/gamma/generate.ts \ --content "$(cat /tmp/gamma-prompt.txt)" \ --format pdf \ --output /tmp/presentation.pdf ``` Or with just a topic: ```bash GAMMA_API_KEY="$GAMMA_API_KEY" bun run ~/.claude/skills/gamma/generate.ts \ --topic "IrisGo Series A Pitch" \ --pages 12 \ --format pdf \ --output /tmp/pitch-deck.pdf ``` ### Step 4: Deliver - If output file was saved, tell the user the file path - If download failed, provide the Gamma URL for manual export - Offer to open the file or share the link ## Configuration The API key is stored in the environment. Do not expose it to the user. ```bash # Set in your shell profile or .env export GAMMA_API_KEY="sk-gamma-..." ``` ## Limitations - Gamma AI designs the slides — layout and visual style are Gamma's choice - Export to PDF/PPTX may require the Gamma URL (direct download not always available) - Generation takes 30-90 seconds depending on complexity - Content is limited by Gamma's input length (~4000 chars recommended) ## Cost Each generation costs Gamma credits (~$0.01/credit, typical deck = 50-100 credits). The skill owner covers this cost — users generate for free.

path: README.md
content: # Gamma Presentation Generator Skill

Generate professional presentations with Gamma AI from your agent. Describe what you want and get a polished deck — no Gamma account needed.

## Two Modes

**BYOK** — Bring your own Gamma API key:
```bash
export GAMMA_API_KEY=sk-gamma-...
```

**Managed** — Use a shared key (for friends/teammates):
```json
// ~/.gamma/config.json
{"api_key": "sk-gamma-..."}
```

## Usage

```bash
# Generate from a topic
bun run generate.ts --topic "Q2 Product Roadmap" --format pdf --output ./roadmap.pdf

# Generate from detailed content
bun run generate.ts --content "Create a 10-slide pitch deck about..." --output ./pitch.pdf
```

## Install

### Claude Code
```bash
# Symlink into skills
ln -s /path/to/gamma-skill ~/.claude/skills/gamma
```

### OpenClaw / PCClaw
```bash
cp SKILL.md ~/.openclaw/skills/gamma/SKILL.md
```

### ClawhHub
```bash
npx clawhub install gamma-presentation
```

## How It Works

1. Skill receives topic or content from the user
2. Calls Gamma API to generate presentation
3. Polls until generation completes (~30-90s)
4. Downloads the result as PDF or PPTX
5. Returns the file to the user

The API key is never exposed to the user. Skill owner pays for generations.

## Cost

~$0.01/credit. Typical 8-slide deck = 50-100 credits ($0.50-$1.00).

## Requirements

- [Bun](https://bun.sh) runtime
- Gamma API key (get one at [gamma.app](https://gamma.app))

## License

MIT


path: generate.ts
content: #!/usr/bin/env bun
/**
 * Gamma Presentation Generator
 * Calls Gamma API, polls for completion, downloads the result.
 *
 * Usage:
 *   bun run generate.ts --topic "AI Agent Economy" --output ./slides.pdf
 *   bun run generate.ts --topic "Q2 Planning" --format pptx --pages 10
 *   bun run generate.ts --content "$(cat brief.md)" --output ./deck.pdf
 *
 * Env:
 *   GAMMA_API_KEY  — Gamma API key (required)
 */

const GAMMA_API = 'https://public-api.gamma.app/v1.0'
const CONFIG_PATH = `${process.env.HOME}/.gamma/config.json`

async function readManagedKey(): Promise<string | null> {
  try {
    const config = JSON.parse(await Bun.file(CONFIG_PATH).text())
    return config.api_key || null
  } catch {
    return null
  }
}

interface GenerateOptions {
  topic?: string
  content?: string
  format?: 'pdf' | 'pptx'
  pages?: number
  output?: string
}

function parseArgs(): GenerateOptions {
  const args = process.argv.slice(2)
  const opts: GenerateOptions = { format: 'pdf' }
  for (let i = 0; i < args.length; i++) {
    if (args[i] === '--topic' && args[i + 1]) opts.topic = args[++i]
    else if (args[i] === '--content' && args[i + 1]) opts.content = args[++i]
    else if (args[i] === '--format' && args[i + 1]) opts.format = args[++i] as 'pdf' | 'pptx'
    else if (args[i] === '--pages' && args[i + 1]) opts.pages = parseInt(args[++i])
    else if (args[i] === '--output' && args[i + 1]) opts.output = args[++i]
  }
  return opts
}

async function createGeneration([REDACTED_SECRET], inputText: string): Promise<string> {
  const res = await fetch(`${GAMMA_API}/generations`, {
    method: 'POST',
    headers: {
      'X-API-KEY': apiKey,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      inputText,
      textMode: 'AUTO',
    }),
  })

  if (!res.ok) {
    const err = await res.text()
    throw new Error(`Gamma API error ${res.status}: ${err}`)
  }

  const data = await res.json() as { generationId: string }
  return data.generationId
}

async function pollGeneration([REDACTED_SECRET], generationId: string, maxWait = 120000): Promise<{
  gammaUrl: string
  title: string
}> {
  const start = Date.now()
  while (Date.now() - start < maxWait) {
    const res = await fetch(`${GAMMA_API}/generations/${generationId}`, {
      headers: { 'X-API-KEY': apiKey },
    })

    if (!res.ok) {
      const err = await res.text()
      throw new Error(`Poll error ${res.status}: ${err}`)
    }

    const data = await res.json() as any
    if (data.status === 'completed') {
      return {
        gammaUrl: data.gammaUrl || data.url,
        title: data.title || 'presentation',
      }
    }
    if (data.status === 'failed') {
      throw new Error(`Generation failed: ${data.error || 'unknown'}`)
    }

    // Wait 3 seconds before next poll
    await new Promise(r => setTimeout(r, 3000))
  }
  throw new Error('Generation timed out after ' + (maxWait / 1000) + 's')
}

async function downloadPresentation(gammaUrl: string, format: string, outputPath: string): Promise<string> {
  // Gamma export endpoint
  const exportUrl = `${gammaUrl}/export/${format}`
  console.log(`Downloading ${format} from: ${exportUrl}`)

  const res = await fetch(exportUrl, { redirect: 'follow' })
  if (!res.ok) {
    // Try alternate download approach — Gamma web export
    console.log(`Direct export failed (${res.status}), trying web export...`)
    // For now, return the gammaUrl for manual download
    return gammaUrl
  }

  const buffer = await res.arrayBuffer()
  await Bun.write(outputPath, buffer)
  return outputPath
}

async function main() {
  // API key resolution: BYOK > managed config > env
  const [REDACTED_SECRET]
    || await readManagedKey()
  if (!apiKey) {
    console.error('Error: No Gamma API key found.')
    console.error('  BYOK: export GAMMA_API_KEY=sk-gamma-...')
    console.error('  Managed: add key to ~/.gamma/config.json')
    process.exit(1)
  }

  const opts = parseArgs()
  if (!opts.topic && !opts.content) {
    console.log(`Usage:
  bun run generate.ts --topic "Topic" [--format pdf|pptx] [--pages N] [--output path]
  bun run generate.ts --content "Full content" [--format pdf|pptx] [--output path]`)
    process.exit(1)
  }

  const inputText = opts.content || `Create a presentation about: ${opts.topic}${opts.pages ? `. Target ${opts.pages} slides.` : ''}`

  console.log('Creating presentation...')
  const generationId = await createGeneration(apiKey, inputText)
  console.log(`Generation ID: ${generationId}`)

  console.log('Waiting for completion...')
  const result = await pollGeneration(apiKey, generationId)
  console.log(`Done! Title: ${result.title}`)
  console.log(`Gamma URL: ${result.gammaUrl}`)

  if (opts.output) {
    const format = opts.format || 'pdf'
    const downloaded = await downloadPresentation(result.gammaUrl, format, opts.output)
    if (downloaded === result.gammaUrl) {
      console.log(`Download: open ${result.gammaUrl} and export manually`)
    } else {
      console.log(`Saved to: ${downloaded}`)
    }
  }

  // Output JSON for skill consumption
  console.log(JSON.stringify({
    generationId,
    gammaUrl: result.gammaUrl,
    title: result.title,
    output: opts.output || null,
  }))
}

main().catch(e => {
  console.error('Error:', e.message)
  process.exit(1)
})


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