Language-agnostic plugin contract for the Surface scanner. Extensions are out-of-process binaries that speak a small gRPC service over a unix socket; the scanner spawns them, dials in, and treats each one as a peer of its built-in discovery modules.
- Module:
github.com/superalignhq/scanner-ext - Contract:
scannerpb/scanner.proto - Wire format: gRPC over a unix socket (
SURFACE_EXT_SOCKETenv var) - License: Apache-2.0
package main
import (
"context"
"log"
pb "github.com/superalignhq/scanner-ext/scannerpb"
"github.com/superalignhq/scanner-ext/sdk"
)
type my struct{ pb.UnimplementedScannerServer }
func (*my) Metadata(_ context.Context, _ *pb.MetadataRequest) (*pb.MetadataResponse, error) {
return &pb.MetadataResponse{Name: "My Scanner", Category: "my_category"}, nil
}
func (*my) Scan(_ *pb.ScanRequest, s pb.Scanner_ScanServer) error {
return s.Send(&pb.AIAsset{
Name: "An Asset",
StableId: "ext:my:asset-1",
Category: "my_category",
})
}
func main() {
if err := sdk.Serve(&my{}); err != nil {
log.Fatal(err)
}
}Build it, then point a Surface scanner at the resulting config.json:
go build -o my-scanner .
sudo surface extension load /path/to/config.jsonSee examples/hello/ for a complete, runnable example.
Run protoc (or your language's buf generator) against
scannerpb/scanner.proto, implement the
Scanner service, and listen on the unix socket named in the
SURFACE_EXT_SOCKET env variable. Exit cleanly on SIGTERM. The Surface
host writes a PID lockfile at the path in SURFACE_EXT_PID_PATH; remove
it on graceful shutdown for clean liveness reporting.
Every emitted asset must carry a stable_id prefixed uniquely to your
extension — e.g. ext:<slug>:<thing>. The host deduplicates assets across
all scanners (built-in and extensions) by stable_id, so collisions
silently merge.
This module follows semver. Breaking changes to the proto bump the major
version. Run buf breaking against the previous tag before tagging a new
release.
scannerpb/ # the gRPC contract — source of truth
sdk/ # Go SDK (sdk.Serve hides the boilerplate)
examples/hello/ # smoke-test extension that emits one asset
buf.yaml # buf module config
buf.gen.yaml # protoc plugin pipeline
Makefile # gen / lint / build / test
Open an issue or PR. The proto under scannerpb/ is the contract; any
change there must keep buf breaking clean across releases.