Bootstrap the plugin-facing SDK module so the fleet migration (P2) becomes a mechanical block/core/X -> block/pluginsdk/X import rewrite. - abi/proto/v1: the single source-of-truth ABI proto tree, copied from the cms authoring source (cms/backend/abi/proto/v1) with go_package retargeted to git.dev.alexdunmow.com/block/pluginsdk/abi/v1;abiv1. Kills the hand-kept cms/core proto duplication (audit gap 4). - abi/v1: generated Go bindings (buf generate); byte-identical to core's abi/v1 save the embedded go_package path. - plugin/ (registration + DI surface), plugin/wasmguest/** (transport shim, caps stubs, bnwasm db driver, testdata fixtures + golden .pb), and the guest-facing type packages: blocks (+builtin/shared/tags), templates (+pongo/bn), auth, settings, content, gating, crypto, rbac, video, ai, subscriptions, menus, datasources. Internal imports rewritten core -> pluginsdk; zero block/core references remain. - README/AGENTS(+CLAUDE symlink)/Makefile: proto is the contract, Go is one binding; no replace directives; templates/bn is a synced copy authored in cms. Spec: cms docs/superpowers/specs/2026-07-07-proto-first-plugin-sdk-design.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
148 lines
5.1 KiB
Go
148 lines
5.1 KiB
Go
package plugin
|
|
|
|
import (
|
|
"context"
|
|
|
|
"connectrpc.com/connect"
|
|
"git.dev.alexdunmow.com/block/pluginsdk/ai"
|
|
"git.dev.alexdunmow.com/block/pluginsdk/auth"
|
|
"git.dev.alexdunmow.com/block/pluginsdk/content"
|
|
"git.dev.alexdunmow.com/block/pluginsdk/crypto"
|
|
"git.dev.alexdunmow.com/block/pluginsdk/datasources"
|
|
"git.dev.alexdunmow.com/block/pluginsdk/gating"
|
|
"git.dev.alexdunmow.com/block/pluginsdk/menus"
|
|
"git.dev.alexdunmow.com/block/pluginsdk/settings"
|
|
"git.dev.alexdunmow.com/block/pluginsdk/subscriptions"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// CoreServices provides CMS capabilities to plugins.
|
|
type CoreServices struct {
|
|
// Capability interfaces — typed access to CMS functionality
|
|
Content content.Content
|
|
ContentAuthor content.Author
|
|
Settings settings.Settings
|
|
Gating gating.Gating
|
|
Crypto crypto.Crypto
|
|
Menus menus.Menus
|
|
Datasources datasources.Datasources
|
|
PublicUsers auth.PublicUsers
|
|
Subscriptions subscriptions.Subscriptions
|
|
|
|
// Database — for plugin's own sqlc queries
|
|
Pool Pool
|
|
|
|
// RPC interceptors — core-provided Connect handler options
|
|
Interceptors connect.Option
|
|
|
|
// Site configuration
|
|
MediaPath string
|
|
AppURL string
|
|
|
|
// Media library — deposit images through the full upload pipeline
|
|
Media Media
|
|
|
|
// AI
|
|
ToolRegistry ai.ToolRegistry
|
|
AITextCall func(ctx context.Context, taskKey, systemPrompt, userMessage string) (string, error)
|
|
|
|
// Email
|
|
EmailSender EmailSender
|
|
|
|
// Plugin interop
|
|
Bridge PluginBridge
|
|
|
|
// Core RPC services — pre-built bindings for CMS-provided services
|
|
CoreServiceBindings CoreServiceBindings
|
|
ReviewSubmitter ReviewSubmitter
|
|
BadgeRefresher BadgeRefresher
|
|
SettingsUpdater settings.Updater
|
|
|
|
// Extension points — typed as narrow interfaces where possible
|
|
JobRunner JobRunner
|
|
EmbeddingService EmbeddingService
|
|
RAGService RAGService
|
|
|
|
// Provisioner — idempotent ensure/seed operations. In the wasm world this
|
|
// is a LOAD-TIME capability: call it from the Load hook, not from
|
|
// Register/RegisterWithProvisioner (DESCRIBE stubs every host function to
|
|
// fail, so register-time provisioning cannot cross the ABI).
|
|
Provisioner Provisioner
|
|
}
|
|
|
|
// MediaDeposit describes an image a plugin deposits into the CMS media library.
|
|
// The bytes flow through the same pipeline as an admin upload (optimize, WebP,
|
|
// thumbnails, LQIP, dimensions); responsive variants are generated on demand.
|
|
type MediaDeposit struct {
|
|
// ID is the media row's primary key. REQUIRED for Provisioner.EnsureMedia —
|
|
// it is the deterministic, template-referable key a seeded {% img %} tag
|
|
// resolves by. Optional for Media.Deposit, where a zero value is generated.
|
|
ID uuid.UUID
|
|
// Filename is the original filename, e.g. "hero.jpg".
|
|
Filename string
|
|
// Data is the raw image bytes, typically from go:embed.
|
|
Data []byte
|
|
// AltText is optional accessibility text.
|
|
AltText string
|
|
// Folder optionally groups the media under a named library folder,
|
|
// created if absent.
|
|
Folder string
|
|
// Source is an optional provenance label, e.g. the plugin name.
|
|
Source string
|
|
}
|
|
|
|
// MediaResult reports the outcome of a deposit.
|
|
type MediaResult struct {
|
|
// ID is the media row's primary key (the supplied ID, or a generated one).
|
|
ID uuid.UUID
|
|
// Ref is a ready-to-use reference ("media:<uuid>") for a block src or a
|
|
// {% img %} tag.
|
|
Ref string
|
|
// Created is false when an existing row with the supplied ID was reused.
|
|
Created bool
|
|
}
|
|
|
|
// Media lets a plugin deposit images into the CMS media library at runtime.
|
|
// Seed-time deposits use Provisioner.EnsureMedia instead — it is idempotent
|
|
// (skip if the ID exists; on changed bytes it warns rather than overwriting).
|
|
type Media interface {
|
|
Deposit(ctx context.Context, deposit MediaDeposit) (MediaResult, error)
|
|
}
|
|
|
|
// JobRunner submits background jobs for async processing.
|
|
type JobRunner interface {
|
|
Submit(ctx context.Context, jobType string, config []byte) error
|
|
}
|
|
|
|
// EmbeddingService generates and manages text embeddings.
|
|
type EmbeddingService interface {
|
|
GenerateEmbedding(ctx context.Context, text string) ([]float32, error)
|
|
EmbedContent(ctx context.Context, sourceType string, sourceID uuid.UUID, text string) (bool, error)
|
|
IsAvailable() bool
|
|
}
|
|
|
|
// ContentFetcher retrieves the title and full text for a content item so the
|
|
// RAG service can re-index it after changes. Plugins register one per content type.
|
|
type ContentFetcher func(ctx context.Context, contentID uuid.UUID) (title string, text string, err error)
|
|
|
|
// RAGService provides retrieval-augmented generation for AI agents.
|
|
type RAGService interface {
|
|
Query(ctx context.Context, query string, limit int) ([]RAGResult, error)
|
|
RegisterContentFetcher(contentType string, fetcher ContentFetcher)
|
|
OnContentChanged(ctx context.Context, contentType string, contentID uuid.UUID)
|
|
}
|
|
|
|
// RAGResult is a single result from a RAG query.
|
|
type RAGResult struct {
|
|
Content string
|
|
Score float64
|
|
Metadata map[string]string
|
|
}
|
|
|
|
// BadgeRefresher recomputes badges for a data table row.
|
|
// The CMS handles loading the table schema, aggregating ratings,
|
|
// evaluating badge rules, and persisting the updated badge list.
|
|
type BadgeRefresher interface {
|
|
RefreshBadges(ctx context.Context, tableID, rowID uuid.UUID) error
|
|
}
|