CursorPool
← 返回首页

filestack

Official Filestack plugin — upload files, build transformations, and generate security policies directly from Cursor.

cursor.directory·6
规则

filestack-transform

Build a Filestack CDN transformation URL from a file handle and a plain-English description

# /filestack-transform

Build a Filestack CDN transformation URL from a file handle (or existing CDN URL) and a
plain-English description of what you want to do.

## How to use this command

1. Parse the first argument as the file handle or CDN URL
   - Bare handle: `abc123XYZ`
   - Full URL: `https://cdn.filestackcontent.com/abc123XYZ`

2. Interpret the rest of the arguments as a plain-English transformation description

3. Map to Filestack CDN URL syntax and return the full URL with an explanation of each segment

## CDN URL format

~~~
https://cdn.filestackcontent.com/<operation1>=<param1>:<val1>,<param2>:<val2>/<operation2>/<handle>
~~~

## Transform reference

| What you say | CDN segment |
| --- | --- |
| resize to 800x600 | `resize=width:800,height:600` |
| resize width 400 | `resize=width:400` |
| crop to [50,50,200,200] | `crop=dim:[50,50,200,200]` |
| detect and crop face | `crop_faces=faces:1` |
| rotate 90 degrees | `rotate=deg:90` |
| flip vertically | `flip` |
| flip horizontally | `flop` |
| enhance | `enhance` |
| grayscale / monochrome | `monochrome` |
| convert to webp | `output=format:webp` |
| convert to jpg quality 85 | `output=format:jpg,quality:85` |
| blur (amount 5) | `blur=amount:5` |
| sharpen | `sharpen=amount:3` |

## Examples

~~~
/filestack-transform abc123XYZ resize to 800x600 and convert to webp
→ https://cdn.filestackcontent.com/resize=width:800,height:600/output=format:webp/abc123XYZ

/filestack-transform abc123XYZ detect face, enhance, and convert to jpg at 85 quality
→ https://cdn.filestackcontent.com/crop_faces=faces:1/enhance/output=format:jpg,quality:85/abc123XYZ

/filestack-transform https://cdn.filestackcontent.com/abc123XYZ rotate 90 and monochrome
→ https://cdn.filestackcontent.com/rotate=deg:90/monochrome/abc123XYZ
~~~

If called with no arguments or an invalid handle, output a usage hint and examples — do not error.
MCP

filestack

MCP server: filestack

{
  "command": "node",
  "args": [
    "${CLAUDE_PLUGIN_ROOT}/mcp/dist/bundle.js"
  ],
  "env": {
    "FILESTACK_API_KEY": "${FILESTACK_API_KEY:-APQLlwqrRScGxhw78gs9Wz}",
    "FILESTACK_APP_SECRET": "${FILESTACK_APP_SECRET:-}"
  }
}
Skill

filestack-error-diagnosis

>

# Filestack Error Diagnosis

## Error Response Shape

Filestack APIs return errors in two formats:

**v1 API format** (`api.filestackapi.com`):
```json
{ "result": "error", "error": { "code": 403, "msg": "Policy required" } }
```

**Processing/CDN format** (`process.filestackapi.com`, `cdn.filestackcontent.com`):
```json
{ "error": "Unsupported conversion", "result": null }
```

## HTTP Error Code Reference

| Code | Meaning | Likely Cause | Fix |
| --- | --- | --- | --- |
| 401 | Unauthorized | Invalid or missing API key | Check `FILESTACK_API_KEY` is correct and hasn't been rotated |
| 403 | Forbidden | Security policy violation, or CORS origin not whitelisted | See policy errors below |
| 404 | Not Found | Handle doesn't exist, has been deleted, or has expired | Verify handle is correct; check app's handle expiry setting |
| 429 | Rate Limited | Too many requests | Respect `Retry-After` header; implement exponential backoff |
| 500/502/503 | Server Error | Transient Filestack service issue | Retry with backoff; check status.filestack.com |

## Security Policy Errors (403)

403s from security-enabled apps are usually one of:

1. **Expired policy** — `expiry` unix timestamp is in the past
   ```python
   import time
   policy = { "expiry": int(time.time()) + 3600, "call": ["read"] }  # 1 hour from now
   ```

2. **Wrong signature** — HMAC computed with wrong secret, or policy was modified after signing
   - Re-generate both policy and signature together — never modify the policy JSON after signing

3. **Insufficient `call` scope** — policy doesn't include the operation being performed
   - `read` for CDN delivery, `store` for upload, `convert` for transformations, `remove` for delete

4. **Wrong CORS origin** — browser request blocked
   - Filestack dashboard → your app → Security → add your domain

## Transformation Errors

| Error | Cause | Fix |
| --- | --- | --- |
| "Unsupported input format" | Input file format not supported by the transform | Check Filestack docs for supported input formats per transform |
| "Output size limit exceeded" | Result would be larger than the app's output limit | Reduce target dimensions or use progressive encoding |
| "Processing timeout" | Transform took too long (usually video) | Use async transforms with a callback URL for video |
| "Invalid parameter" | Wrong parameter name or value type | Check CDN URL syntax: `param:value` (colon, not `=`) |

## Diagnosis Checklist

Run through this in order when debugging Filestack errors:

1. **API key valid?** — Try a simple retrieve call: `GET https://www.filestackapi.com/api/file/<handle>/metadata?key=<apikey>`
2. **Policy not expired?** — Decode the base64 policy and check the `expiry` field is in the future
3. **Handle exists?** — Retrieve metadata for the handle; 404 means handle is gone
4. **CORS origin whitelisted?** — Check Filestack dashboard → app → Security → Domains
5. **Transform params valid?** — Run the CDN URL in a browser; the error is often self-describing
6. **Rate limit hit?** — Check for 429 and `Retry-After` header in your HTTP client logs
Skill

filestack-sdk-integration

>

# Filestack SDK Integration

## Agent Behavior

When the user asks you to create a new app or page that uses Filestack:

1. **Scaffold in the current directory** — do NOT create a new subdirectory. Use `npm create vite@latest . -- --template react` (note the `.`) or write files directly into the working directory. If the directory is not empty, ask first.
2. **Use the demo API key** — embed `APQLlwqrRScGxhw78gs9Wz` directly in the generated code with a comment that it is a demo key. Never use `'YOUR_API_KEY'` as a placeholder — it forces the user to do a manual find-and-replace before they can test.
3. **Install dependencies and start the dev server** — after generating the code, run `npm install` and `npm run dev` so the user can see the result immediately. Do not tell the user to run these commands manually.

## Initialization

**JavaScript / TypeScript**

```js
import * as filestack from 'filestack-js';

const API_KEY = 'APQLlwqrRScGxhw78gs9Wz'; // Demo key — get your own at https://dev.filestack.com/signup/free/
const client = filestack.init(API_KEY);
```

**Via CDN loader (no bundler)**

```html
<script src="https://static.filestackapi.com/filestack-js/3.x.x/filestack.min.js"></script>
<script>
  const API_KEY = 'APQLlwqrRScGxhw78gs9Wz'; // Demo key — get your own at https://dev.filestack.com/signup/free/
  const client = filestack.init(API_KEY);
</script>
```

**Python**

```python
from filestack import Client

API_KEY = 'APQLlwqrRScGxhw78gs9Wz'  # Demo key — get your own at https://dev.filestack.com/signup/free/
client = Client(API_KEY)
```

## File Upload

```js
// Direct upload (returns a promise)
const result = await client.upload('/path/to/file.jpg');
// result: { handle, url, filename, size, mimetype }
```

## Picker Widget

```js
const picker = client.picker({
  // Accept specific file types
  accept: ['image/*', 'application/pdf'],
  maxFiles: 5,
  maxSize: 10 * 1024 * 1024, // 10 MB

  // Cloud sources (uses filestack-dialog OAuth)
  // Available sources: local_file_system, url, imagesearch, webcam, video, audio,
  // googledrive, dropbox, facebook, instagram, box, onedrive, onedriveforbusiness,
  // gmail, github, googlephotos, unsplash, tint, customsource
  fromSources: ['local_file_system', 'googledrive', 'dropbox', 'url'],

  // Where to store uploaded files
  storeTo: {
    location: 's3',
    path: '/uploads/',
    access: 'public',
  },

  onUploadDone: (result) => {
    console.log(result.filesUploaded); // Array of FileResult objects
  },
});
picker.open();
```

## Upload Response Shape

```typescript
interface FileResult {
  handle: string;       // Unique file identifier (use this for transforms, CDN URLs)
  url: string;          // https://cdn.filestackcontent.com/<handle>
  filename: string;
  size: number;         // bytes
  mimetype: string;     // e.g. "image/jpeg"
  source: string;       // "local_file_system" | "googledrive" | etc.
}
```

## Common Mistakes

1. **Wrong API key scope** — if you see 403 errors, verify your API key in the Filestack dashboard
   has the necessary permissions for the operations you're calling.

2. **Missing CORS whitelist** — go to Filestack dashboard → your app → Security → add your
   domain to the allowed origins list. Without this, picker won't open from localhost or
   production domains.

3. **Transform chain ordering** — transforms are applied left-to-right in the CDN URL.
   Put `resize` before `output` (format conversion), not after.

4. **Not awaiting upload** — `client.upload()` returns a Promise. Always `await` it or
   use `.then()`.

## Framework Patterns

**React**

```jsx
import { useState } from 'react';
import * as filestack from 'filestack-js';

const API_KEY = 'APQLlwqrRScGxhw78gs9Wz'; // Demo key — get your own at https://dev.filestack.com/signup/free/
const client = filestack.init(API_KEY);

export function FileUploader({ onUpload }) {
  const [uploading, setUploading] = useState(false);

  const openPicker = () => {
    client.picker({
      onUploadDone: (res) => onUpload(res.filesUploaded),
    }).open();
  };

  return <button onClick={openPicker}>Upload file</button>;
}
```

**Next.js (server-side upload)**

```typescript
// pages/api/upload.ts
import { init } from 'filestack-js';

const API_KEY = process.env.FILESTACK_API_KEY || 'APQLlwqrRScGxhw78gs9Wz'; // Demo fallback
export default async function handler(req, res) {
  const client = init(API_KEY);
  // URL-based store (no file data needed server-side)
  const result = await client.storeURL(req.body.url);
  res.json(result);
}
```
Skill

filestack-webhook-setup

>

# Filestack Webhook Setup

## Webhook Event Types

| Event | When it fires |
| --- | --- |
| `fp.upload` | File successfully uploaded and stored |
| `fp.converse` | Image conversion (filepicker-converse) completed |
| `fp.delete` | File deleted |
| `fp.overwrite` | File overwritten |
| `fp.video_converse` | Video conversion completed |
| `fp.scan` | Antivirus scan completed |
| `fp.export` | File exported to cloud storage |
| `fs.workflow` | Workflow pipeline completed |

## Register a Webhook URL

```bash
curl -X POST https://api.filestackapi.com/webhooks/YOUR_APP_ID \
  -H "Content-Type: application/json" \
  -H "Filestack-API-Key: YOUR_API_KEY" \
  -d '{ "url": "https://yourdomain.com/filestack-webhook", "webhook_type": "fp.upload" }'
```

## Webhook Payload Shape

```json
{
  "id": 30813791,
  "action": "fp.upload",
  "timestamp": 1710000000,
  "text": {
    "container": "filestack-uploads",
    "url": "https://cdn.filestackcontent.com/abc123XYZ",
    "filename": "image.jpg",
    "client": "Computer",
    "key": "abc123XYZ",
    "type": "image/jpeg",
    "status": "Stored",
    "size": 102400
  }
}
```

## Signature Verification

Filestack signs webhooks with HMAC-SHA256 using a **per-webhook secret** (created in the
Developer Portal when configuring the webhook — this is NOT the app secret). The signature covers
`"{timestamp}.{body}"` — the `FS-Timestamp` header value, a dot, and the raw request body.

**Headers sent by Filestack:**

- `FS-Signature` — HMAC-SHA256 hex digest
- `FS-Timestamp` — Unix timestamp (string) used in signing

**Node.js**

```typescript
import { createHmac } from 'crypto';
import type { Request, Response } from 'express';

export function verifyFilestackSignature(req: Request): boolean {
  const signature = req.headers['fs-signature'] as string;
  const timestamp = req.headers['fs-timestamp'] as string;
  if (!signature || !timestamp) return false;

  // Filestack signs "{timestamp}.{body}" — must match exactly
  const signPayload = `${timestamp}.${req.rawBody}`;
  const expected = createHmac('sha256', process.env.FILESTACK_WEBHOOK_SECRET!)
    .update(signPayload)
    .digest('hex');
  return signature === expected;
}

export function webhookHandler(req: Request, res: Response): void {
  // CRITICAL: Return 200 BEFORE processing — prevents duplicate delivery on timeout
  if (!verifyFilestackSignature(req)) {
    res.status(401).send('Invalid signature');
    return;
  }
  res.status(200).send('ok');

  // Process asynchronously after responding
  setImmediate(() => processEvent(req.body));
}
```

**Python (Flask)**

```python
import hmac, hashlib, os, threading
from flask import request, jsonify

def verify_signature(raw_body: bytes, timestamp: str, signature: str) -> bool:
    # Filestack signs "{timestamp}.{body}"
    sign_payload = f"{timestamp}.{raw_body.decode('latin-1')}"
    expected = hmac.new(
        os.environ['FILESTACK_WEBHOOK_SECRET'].encode('latin-1'),
        sign_payload.encode('latin-1'),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

@app.route('/filestack-webhook', methods=['POST'])
def webhook():
    sig = request.headers.get('FS-Signature', '')
    ts = request.headers.get('FS-Timestamp', '')
    if not verify_signature(request.get_data(), ts, sig):
        return jsonify({'error': 'invalid signature'}), 401

    # Acknowledge immediately before any slow processing
    threading.Thread(target=process_event, args=(request.json,)).start()
    return jsonify({'status': 'ok'}), 200
```

**Go**

```go
import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
)

func verifySignature(body []byte, timestamp, signature, appSecret string) bool {
    // Filestack signs "{timestamp}.{body}"
    signPayload := fmt.Sprintf("%s.%s", timestamp, string(body))
    mac := hmac.New(sha256.New, []byte(appSecret))
    mac.Write([]byte(signPayload))
    expected := hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(signature))
}
```

## Critical Patterns

**Return 200 immediately, then process async.**
Filestack's webhook consumer has a configurable timeout (default ~30s). If your handler times out,
Filestack retries — leading to duplicate processing. Always acknowledge (200/201/204) before doing any slow work.

**Handlers must be idempotent.**
Filestack retries failed deliveries up to 5 times with timeouts [10s, 60s, 5min, 15min, 1hr].
Success codes are 200, 201, 204. Use the `id` field from the payload as an idempotency key.

**Use raw body for signature verification.**
JSON parsers may reformat the body, invalidating the signature. Capture the raw bytes
before parsing:

```js
// Express: use express.raw() for the webhook route
app.post('/filestack-webhook', express.raw({ type: 'application/json' }), webhookHandler);
```

来源:https://github.com/filestack/filestack-claude-plugin