一键导入
add-plugin
Scaffold a new storage plugin for the telemetry collector. Use when the user wants to handle a new AGS event type or add a new storage backend.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Scaffold a new storage plugin for the telemetry collector. Use when the user wants to handle a new AGS event type or add a new storage backend.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | add-plugin |
| description | Scaffold a new storage plugin for the telemetry collector. Use when the user wants to handle a new AGS event type or add a new storage backend. |
| disable-model-invocation | true |
| argument-hint | ["type <name> | backend <name>"] |
| allowed-tools | Read, Write, Edit, Glob, Grep, Bash |
Scaffolds plugin code for this telemetry collector project.
/add-plugin type <name> # New AGS event type across all existing backends
/add-plugin backend <name> # New storage backend for all existing event types
Arguments received: $ARGUMENTS
Parse the arguments:
type or backend) — determines the modestat_item_deleted, big_query)If arguments are missing or ambiguous, ask for clarification before proceeding.
This is an Extend Event Handler app. Event schemas come from AccelByte Gaming Services. Developers must NOT define their own proto messages. All proto files come from the AGS event catalogue (source: https://github.com/AccelByte/accelbyte-api-proto/tree/main/asyncapi).
Inject the current project state to avoid guessing:
Existing event types:
!`ls pkg/events/*.go | xargs -I{} basename {} .go | grep -v _test`
Existing backends:
!`ls pkg/storage/plugins/`
Current bootstrap plugin cases:
!`grep -n 'case "' internal/bootstrap/plugins.go`
Read CONVENTIONS.md before writing any code. It has the naming rules and file patterns this project enforces.
When: first argument is
typeExample:
/add-plugin type stat_item_deleted— adds stat_item_deleted event handling across all backends
Check whether the proto for this event already exists under pkg/proto/accelbyte-asyncapi/:
!`find pkg/proto/accelbyte-asyncapi -name "*.proto" | head -20`
If the proto is not present, tell the user to copy it from
https://github.com/AccelByte/accelbyte-api-proto/tree/main/asyncapi
into the matching path under pkg/proto/accelbyte-asyncapi/, then run ./proto.sh to
regenerate Go code. Do not proceed until the generated *_grpc.pb.go exists.
If the proto already exists, read the relevant *_grpc.pb.go to identify:
StatisticStatItemDeletedServiceServer)OnMessage method signature and its request message type (e.g., *StatItemDeleted)Unimplemented* embed struct nameRead these files in full before writing anything:
pkg/events/stat_item_updated.go
pkg/service/stat_item_updated.go
pkg/storage/plugins/postgres/stat_item_updated.go
pkg/storage/plugins/mongodb/stat_item_updated.go
pkg/storage/plugins/s3/stat_item_updated.go
pkg/storage/plugins/kafka/stat_item_updated.go
These are your source of truth. Every file you create must follow the exact same structure, substituting only the type name and proto message fields.
Also read the bootstrap files to understand the current wiring:
internal/bootstrap/plugins.go
internal/bootstrap/processor.go
internal/bootstrap/deduplicator.go
internal/app/app.go
File: pkg/events/<name>.go
Copy the structure of pkg/events/stat_item_updated.go. The event wrapper must:
ID, Version, Namespace, Name,
UserID, SessionID, Timestamp, ServerTimestamp) and a typed Payload field.StatItem), reuse
the existing struct rather than defining a duplicate.DeduplicationKey() string — use fields that make the event unique in your domain.ToDocument() map[string]interface{} — flat map for JSON/BSON serialization.Key substitutions:
| Original | Replace with |
|---|---|
StatItemUpdatedEvent | <Name>Event (PascalCase) |
"stat_item_updated:" prefix | "<name>:" |
"stat_item_updated" in ToDocument()["kind"] | "<name>" |
File: pkg/service/<name>.go
Copy the structure of pkg/service/stat_item_updated.go. Change:
| Original | Replace with |
|---|---|
StatItemUpdatedService | <Name>Service |
UnimplementedStatisticStatItemUpdatedServiceServer | Unimplemented struct from the generated grpc file |
*statistic.StatItemUpdated | The correct proto message type for this event |
RegisterStatisticStatItemUpdatedServiceServer | The correct Register function for this service |
All field mappings in OnMessage | Fields from the actual proto message |
The handler must:
req != nilreq.Namespace (fall back to s.namespace if empty)UserID from req.UserIdServerTimestamp to time.Now().UnixMilli()s.proc.Submit(event) and return gRPC status errors on failureFor each existing backend, create one plugin file:
| Create | Based on |
|---|---|
pkg/storage/plugins/postgres/<name>.go | pkg/storage/plugins/postgres/stat_item_updated.go |
pkg/storage/plugins/mongodb/<name>.go | pkg/storage/plugins/mongodb/stat_item_updated.go |
pkg/storage/plugins/s3/<name>.go | pkg/storage/plugins/s3/stat_item_updated.go |
pkg/storage/plugins/kafka/<name>.go | pkg/storage/plugins/kafka/stat_item_updated.go |
Substitutions for every file:
| Original | Replace with |
|---|---|
StatItemUpdatedPlugin | <Name>Plugin |
StatItemUpdatedPluginConfig | <Name>PluginConfig |
NewStatItemUpdatedPlugin | New<Name>Plugin |
"postgres:stat_item_updated" / etc. | "<backend>:<name>" |
"stat_item_updated_events" | "<name>_events" |
*events.StatItemUpdatedEvent | *events.<Name>Event |
kind: "stat_item_updated" in Kafka headers | kind: "<name>" |
stat_item_updated/<namespace>/... S3 key path | <name>/<namespace>/... |
For Kafka: compressionCodec is defined in stat_item_updated.go in the kafka package.
Do not add it again — just call it directly.
Make surgical edits — do not rewrite any file.
A. internal/bootstrap/plugins.go
Add <Name> []storage.StoragePlugin[*events.<Name>Event] to StoragePlugins.
Add <Name>: []storage.StoragePlugin[*events.<Name>Event]{} to the struct literal.
Add var <name>Plugin storage.StoragePlugin[*events.<Name>Event] inside the loop's var block.
Add plugin construction inside each backend case, mirroring the siuPlugin pattern.
Add initialize, log, and append after the switch:
if err := <name>Plugin.Initialize(ctx); err != nil {
return nil, err
}
logger.Info("plugin initialized", "plugin", <name>Plugin.Name())
plugins.<Name> = append(plugins.<Name>, <name>Plugin)
Add noop fallback inside if len(plugins.StatItemUpdated) == 0.
Add "<name>", len(plugins.<Name>) to the final logger.Info call.
Add close loop in CloseStoragePlugins.
B. internal/bootstrap/processor.go
Add <Name> *processor.Processor[*events.<Name>Event] to Processors.
Add <Name>: processor.NewProcessor(processorCfg, plugins.<Name>, dedups.<Name>, logger) to the struct literal.
Add procs.<Name>.Start().
Add shutdown call in ShutdownProcessors.
C. internal/bootstrap/deduplicator.go
Add <Name> dedup.Deduplicator[*events.<Name>Event] to Deduplicators.
Add <Name>: buildDeduplicator[*events.<Name>Event](appCfg, logger) to the struct literal.
Add close call in CloseDeduplicators.
D. internal/app/app.go
Register the service after the existing service registrations:
<name>Svc := service.New<Name>Service(
bootstrap.GetNamespace(),
app.processors.<Name>,
app.logger,
)
statistic.Register<ServiceName>Server(grpcServer, <name>Svc)
go build ./...
Report any errors and fix them before finishing.
When: first argument is
backendExample:
/add-plugin backend bigquery— adds BigQuery support for all event types
Read these files in full:
pkg/storage/plugins/postgres/stat_item_updated.go
internal/bootstrap/plugins.go
pkg/config/config.go
Also discover all current event types:
!`ls pkg/events/*.go | xargs -I{} basename {} .go | grep -v _test`
Before writing code, ask the user (or infer from the arguments if already stated):
cloud.google.com/go/bigquery)go.mod? If not, should you run go get?If the user's initial message contains enough context, skip asking and state your assumptions.
go get <library-import-path>
Create pkg/storage/plugins/<name>/ with one file per existing event type.
For each event type file, implement all six interface methods following the same structure as
the Postgres plugins. Refer to CONVENTIONS.md for the required DEVELOPER
NOTE comment blocks on Filter and transform.
The Name() method must return "<backend>:<type>" (e.g., "bigquery:stat_item_updated").
Each plugin file is self-contained with its own config struct, its own client, and its own
Initialize/Close lifecycle.
In pkg/config/config.go, add a new config struct and field to StorageConfig:
type <Name>Config struct {
// One field per connection parameter, with env tags following the existing pattern
}
type StorageConfig struct {
// ... existing fields ...
<Name> <Name>Config `envPrefix:"<NAME>_"`
}
In internal/bootstrap/plugins.go:
A. Add the import for the new package.
B. Add a new case "<name>": block in the plugin switch. For each existing event type,
create and assign its plugin variable (e.g., siuPlugin, sidPlugin, etc.).
go build ./...
Report any errors and fix them before finishing.
After all files are created and the build passes, summarize what was created: