К основному содержанию
скилл CX Codex

extension-object-storage — скилл для Claude Code: файлы и объекты

Скилл добавляет в проект хранилище файлов: содержимое лежит вне сети, а бэкенд держит только ссылки на него. Пригодится для галерей изображений, видео и документов.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add caffeinelabs/skills --skill extension-object-storage -a claude-code
Открыть на GitHub

Что делает extension-object-storage

Скилл подключает off-chain хранилище объектов с on-chain ссылками. Миксин MixinObjectStorage даёт инфраструктуру для работы с файлами, а сами загруженные файлы вы отслеживаете в своих структурах через Storage.ExternalBlob. Крупные файлы проходят мимо лимита IC, доступ идёт по HTTP-ссылке, которую кеширует браузер.

Настройка состоит из четырёх шагов, и все они обязательны. В mops.toml добавляется зависимость caffeineai-object-storage, в main.mo вызывается include MixinObjectStorage(), каждое поле с файлом получает тип Storage.ExternalBlob вместо Text, а на фронтенде ставится npm-пакет @caffeineai/object-storage и вызывается ExternalBlob.fromBytes(bytes, file.type, file.name). Пропустите любой шаг — и загрузка упадёт с 403 Forbidden: Invalid payload.

Отдельно про фронтенд: npm-пакет не работает без бэкендового mops-пакета. Ставить только npm и забыть про mops — верный путь к тихим сбоям загрузки, потому что шлюз хранилища отвечает 403. Импорт миксина всегда идёт по пути "mo:caffeineai-object-storage/Mixin", относительные пути вроде "mixins/object-storage-api" не годятся. Свои реализации методов _immutableObjectStorage* писать нельзя: у них платформенная сигнатура, и ручной вариант ломает валидацию шлюза.

Какие задачи решает

  • хранить изображения и видео вне сети, а в бэкенде держать только ссылки
  • собирать галерею файлов с загрузкой и прогрессом
  • отдавать крупные файлы в обход лимита IC
  • показывать картинки и видео по прямой ссылке в интерфейсе
  • скачивать файл с исходным именем через getBytes()

Примеры запросов агенту

  • Добавь в проект хранилище объектов: подключи caffeineai-object-storage в mops.toml и вызови include MixinObjectStorage() в main.mo.
  • Сделай загрузку файла на фронтенде через ExternalBlob.fromBytes(bytes, file.type, file.name) и передай имя файла в бэкенд.
  • Покажи изображения из записей через getDirectURL() и определи тип файла по полю filename, а не по ссылке.

Как установить extension-object-storage

1Через Skills CLI (Vercel)

npx skills add caffeinelabs/skills --skill extension-object-storage -a claude-code

2Вручную: положить файлы

~/.claude/skills/extension-object-storage/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/extension-object-storage/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Из того же набора25

extension-email-raw Скилл добавляет в проект отправку одного письма сразу нескольким получателям через to, cc и bcc. Пригодится, когда нужно разослать уведомление участникам встречи. CX Codex +1 28 100установок extension-invite-links Скилл добавляет в проект систему приглашений и сбор ответов RSVP. Гости отправляют подтверждения по кодам без авторизации, а администратор видит сводку через вход в систему. CX Codex +1 28 100установок extension-http-outcalls Скилл для HTTP-запросов из бэкенд-канистры к внешним API. Следит, чтобы каждый запрос был ограничен на стороне провайдера. CX Codex +1 28 100установок extension-email Скилл учит агента отправлять сервисные и транзакционные письма из канистр Motoko. Пригодится для разовых системных оповещений и чеков. CX Codex +1 28 100установок extension-email-verification Добавляет в приложение на Caffeine AI подтверждение email по ссылке: получатель кликает кнопку и адрес помечается проверенным. CX Codex +1 28 100установок extension-oql Открывает данные канистры Motoko для запросов агента Caffeine Data Intelligence через эндпоинты schema() и execute(). Пригодится для поиска по картам и массивам на естественном языке. CX Codex +1 27 700установок connector-googlemail Инструкция по отправке писем из канистры от имени личного аккаунта Gmail пользователя через Motoko и Mops. Защищает от сырых HTTP-запросов к Google API. CX Codex +1 25 000установок extension-querying-oql Шпаргалка по составлению OQL-запросов к канистре backend через консольную утилиту icp. Помогает агенту считывать схему, фильтровать записи и разбирать Candid-ответы. CX Codex +1 24 700установок

Похожие

neon-object-storage neondatabase Настраивает S3-совместимое хранилище Neon, которое ветвится вместе с базой данных. Пригодится для загрузок, изображений и документов приложения. CX Codex +1 28 200установок object-storage devops-skills Скилл для работы с объектным хранилищем: команды AWS CLI, бакет-политики, lifecycle-правила, версионирование и self-hosted MinIO. CX Codex +1 350установок catalyst-stratus catalystbyzoho Скилл для работы с объектным хранилищем Catalyst Stratus: загрузка файлов, подписанные ссылки, составная загрузка. Пригодится, если проект хранит файлы в Stratus. CX Codex +1 994установок tigris-object-operations tigrisdata Скилл для загрузки, скачивания, удаления и листинга объектов в Tigris Storage прямо из агента, плюс presigned URL. CX Codex +1 3 200установок inertia-rails-forms inertia-rails Скилл для работы с формами Inertia.js + Rails: создание, редактирование, удаление, визарды и загрузка файлов. Пригодится, когда нужно связать форму с контроллером Rails. CX Codex +1 486установок sql-object-impact-analysis официальный github Находит зависимости таблиц, колонок и процедур в SQL-коде и бэкенде перед их удалением или изменением. Помогает оценить зону поражения в легаси-проектах. 26установок

Рядом в теме «Бэкенд и API»

Всё по теме «Бэкенд и API» →

Вопросы о extension-object-storage

Как установить extension-object-storage в Claude?

Выполните npx skills add caffeinelabs/skills --skill extension-object-storage -a claude-code. Скилл окажется в ~/.claude/skills/extension-object-storage/SKILL.md.

Что делает extension-object-storage?

Добавляет хранилище файлов и объектов: содержимое хранится вне сети, а бэкенд ведёт ссылки на него через Storage.ExternalBlob. Подходит для галерей изображений, видео и документов.

Что нужно, чтобы загрузка файлов не падала с 403?

Все четыре шага настройки: зависимость caffeineai-object-storage в mops.toml, вызов include MixinObjectStorage() в main.mo, тип Storage.ExternalBlob у полей с файлами и npm-пакет @caffeineai/object-storage на фронтенде. Пропуск любого шага даёт 403 Forbidden: Invalid payload.

Исходный текст

SKILL.md · Apache-2.0
---
name: extension-object-storage
description: General file/object storage, such as for images, videos, files, documents and other bulk data. Perfect fit for image galleries, video galleries, and other file or object management. Supports large files beyond IC limit, with browser-cached HTTP URL access.
version: 1.1.1
compatibility:
  mops:
    caffeineai-object-storage: "~1.1.0"
caffeineai-subscription: [none]
---

# Object Storage
Object storage extension for [Caffeine AI](https://caffeine.ai?utm_source=caffeine-skill&utm_medium=referral).

## Overview

This skill adds off-chain file/object storage with on-chain references. The `MixinObjectStorage` mixin provides infrastructure for file operations; you track uploaded files in your own data structures using `Storage.ExternalBlob`.

## Required Setup Checklist

All four steps are mandatory. Skipping any one causes `403 Forbidden: Invalid payload` at upload time.

1. **mops dependency** — add `caffeineai-object-storage` to `mops.toml` under `[dependencies]`.
2. **Mixin invocation** — `include MixinObjectStorage()` in `main.mo` (imported from `"mo:caffeineai-object-storage/Mixin"`).
3. **Storage.ExternalBlob types** — every data field that represents a file MUST use `Storage.ExternalBlob`, never `Text`.
4. **Frontend npm package** — `@caffeineai/object-storage` installed and `ExternalBlob.fromBytes(bytes, file.type, file.name)` used at the call site.

CRITICAL: The frontend package (`@caffeineai/object-storage`) does NOT work without the backend mops package (`caffeineai-object-storage`). Installing only the npm package and not the mops package causes silent upload failures (403 from the storage gateway). You MUST install both together.

# Backend

File content is stored off-chain. The backend manages references to external files using the `Storage.ExternalBlob` type from `mo:caffeineai-object-storage/Storage`. The frontend handles the actual upload/download; the backend only stores the reference.

CRITICAL: ANY data field that represents a file, image, photo, document, or media MUST use `Storage.ExternalBlob` as its type -- NEVER `Text`. Using `Text` breaks the upload/download proxy. Method parameters that accept file uploads MUST also use `Storage.ExternalBlob`, not `Text`.

Correct:
```
blob : Storage.ExternalBlob
```

Wrong:
```
blobId : Text
imageUrl : Text
fileRef : Text
```

## Module API

The only type you use from `mo:caffeineai-object-storage/Storage` is `ExternalBlob` (which is `Blob`). All other functions in `Storage.mo` are internal infrastructure used by `MixinObjectStorage` -- do not call them directly.

## Setup in main.mo

`include MixinObjectStorage()` MUST be placed in `main.mo`, not in a custom mixin file. Your own file-tracking logic goes in a separate mixin.

```motoko filepath=src/backend/main.mo
import MixinObjectStorage "mo:caffeineai-object-storage/Mixin";
import Storage "mo:caffeineai-object-storage/Storage";

actor {
  include MixinObjectStorage();

  // Track file references
  type Data = {
    id : Text;
    blob : Storage.ExternalBlob;
    name : Text;
    // other metadata
  };
};
```

## Wrong: Do NOT Implement Storage Methods Yourself

NEVER create your own implementation of `_immutableObjectStorageCreateCertificate` or any other `_immutableObjectStorage*` method. These are platform-reserved method names provided exclusively by the `MixinObjectStorage` mixin from the mops package. Hand-written implementations produce wrong return types and cause `403 Forbidden: Invalid payload` at upload time.

Wrong — inline stub in main.mo:
<!-- motoko-check:skip -->
```motoko filepath=wrong.mo
// WRONG: Do not write this yourself
public shared func _immutableObjectStorageCreateCertificate(fileHash : Text) : async Blob {
  CertifiedData.set(Blob.fromArray(hashBytes));
  Blob.fromArray([])
};
```

Wrong — custom mixin file mimicking the platform shape:
```motoko filepath=wrong-mixin.mo
// WRONG: Do not create src/backend/mixins/object-storage-api.mo
import ObjectStorageMixin "mixins/object-storage-api";
include ObjectStorageMixin();
```

The correct import path is ALWAYS `"mo:caffeineai-object-storage/Mixin"` — a mops package, never a relative path. Any relative import like `"mixins/object-storage-api"` or `"./ObjectStorage"` is wrong.

The correct signature produced by the platform mixin is:
```
_immutableObjectStorageCreateCertificate : (blobHash : Text) -> async record { method : Text; blob_hash : Text }
```

Any other return type (`Blob`, `()`, `Text`, etc.) will fail gateway validation.

# Frontend

Backend `Blob` fields are represented as `ExternalBlob` on the frontend.


```typescript
import { ExternalBlob } from "@caffeineai/object-storage";
import type { FileRecord } from "@caffeineai/object-storage";
```

## ExternalBlob API

```typescript
class ExternalBlob {
  getBytes(): Promise<Uint8Array<ArrayBuffer>>;
  getDirectURL(): string;
  static fromURL(url: string): ExternalBlob;
  static fromBytes(
    blob: Uint8Array<ArrayBuffer>,
    contentType?: string,
    filename?: string,
  ): ExternalBlob;
  withUploadProgress(onProgress: (percentage: number) => void): ExternalBlob;
}
```

## Uploading Files

Pass the browser `File` type and name into `fromBytes` so the gateway blob tree stores `Content-Type` and `Content-Disposition` (original filename). Also pass `file.name` to the backend so app records keep the filename for lists and UI.

```typescript
const handleUpload = async (file: File) => {
  const bytes = new Uint8Array(await file.arrayBuffer());
  const blob = ExternalBlob.fromBytes(bytes, file.type, file.name).withUploadProgress((pct) => {
    setProgress(pct);
  });

  await actor.uploadFile(file.name, blob);
};
```

Gateway GET/HEAD responses echo the stored filename via `Content-Disposition`. Keep the backend `filename` field for queries and display without hitting the gateway.

## Displaying Files

Use `getDirectURL()` for inline display (images, videos). This returns an opaque proxy URL -- it has no file extension, so never inspect the URL to determine file type.

```typescript
<img src={record.blob.getDirectURL()} alt={record.filename} />
```

## File Type Detection

CRITICAL: Never detect file types by inspecting the URL from `getDirectURL()`. These are opaque proxy URLs with no extension. Instead use the `filename` field from the backend record:

```typescript
const isImage = (filename: string) =>
  /\.(jpg|jpeg|png|gif|webp|svg|bmp|ico)$/i.test(filename);

// Conditional rendering
{isImage(record.filename) ? (
  <img src={record.blob.getDirectURL()} alt={record.filename} />
) : (
  <div>{record.filename}</div>
)}
```

If the backend also returns a `mimeType` field, prefer that:

```typescript
const isImage = (mimeType?: string) => mimeType?.startsWith("image/");
```

## Downloading Files

For downloads with the original filename, use `getBytes()` to create a downloadable link:

```typescript
const handleDownload = async (record: FileRecord) => {
  const bytes = await record.blob.getBytes();
  const blob = new Blob([bytes]);
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = record.filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
};
```

Use `getDirectURL()` for inline display, `getBytes()` for save-as downloads.

## Summary

| Use case | Method | Notes |
|---|---|---|
| Display image/video | `blob.getDirectURL()` | Streaming, cached |
| Download with filename | `blob.getBytes()` | Wrap in Blob + anchor |
| Upload from browser | `ExternalBlob.fromBytes(bytes, file.type, file.name)` | MIME + filename in gateway headers |
| Detect file type | `filename` or `mimeType` field | NEVER inspect the URL |

# Verifying the Setup

Confirm the backend has the mops dependency installed. Check `src/backend/mops.toml`:

```toml
[dependencies]
caffeineai-object-storage = "0.1.2"
```

If `caffeineai-object-storage` is missing from `[dependencies]`, object storage will not work regardless of what the frontend does. Add it, run `mops install`, and rebuild.

# Troubleshooting

| Error | Cause | Fix |
|---|---|---|
| `403 Forbidden: Invalid payload` on `PUT /v1/blob-tree/` | Backend canister missing `_immutableObjectStorageCreateCertificate` or returning wrong type | Install `caffeineai-object-storage` in mops.toml, add `include MixinObjectStorage()` in main.mo, redeploy |
| `403 Forbidden: Invalid payload` (all files) | `@caffeineai/object-storage` npm installed but `caffeineai-object-storage` mops NOT installed | Add the mops dependency and rebuild backend |
| Method exists but still 403 | Hand-written stub returns wrong type (e.g. `Blob` or `()` instead of `record { method; blob_hash }`) | Remove the custom implementation, use the platform mixin instead |
| `Forbidden: Owner does not have an account with the cashier` | Cashier registration issue (unrelated to this skill) | Redeploy the backend canister to trigger self-healing registration |

© caffeinelabs · Apache-2.0 · caffeinelabs/skills

Смотрите также

Спонсоры проекта

Компании, которые помогают держать аналитику открытой