Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions cmd/anyclaw-registry/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package main

import (
"context"
"errors"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"

"github.com/1024XEngineer/anyclaw/pkg/marketregistry"
)

func main() {
if err := run(os.Args[1:]); err != nil {
log.Fatal(err)
}
}

func run(args []string) error {
if len(args) == 0 {
args = []string{"serve"}
}
switch args[0] {
case "serve":
return serve(args[1:])
case "-h", "--help", "help":
printUsage()
return nil
default:
return fmt.Errorf("unknown command %q", args[0])
}
}

func serve(args []string) error {
fs := flag.NewFlagSet("serve", flag.ContinueOnError)
addr := fs.String("addr", ":8791", "HTTP listen address")
dataDir := fs.String("data-dir", ".anyclaw-registry", "registry data directory")
dbDriver := fs.String("db-driver", "sqlite", "database/sql driver name")
dbDSN := fs.String("db-dsn", "", "database DSN; defaults to <data-dir>/registry.db for sqlite")
adminToken := fs.String("admin-token", os.Getenv("ANYCLAW_REGISTRY_ADMIN_TOKEN"), "admin bearer token; defaults to ANYCLAW_REGISTRY_ADMIN_TOKEN")
seed := fs.Bool("seed", true, "seed fixture artifacts when the registry is empty")
if err := fs.Parse(args); err != nil {
return err
}

ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

server, err := marketregistry.NewServer(ctx, marketregistry.ServerConfig{
Addr: *addr,
DataDir: *dataDir,
DBDriver: *dbDriver,
DBDSN: *dbDSN,
AdminToken: *adminToken,
Seed: *seed,
})
if err != nil {
return err
}
defer server.Close()

log.Printf("anyclaw registry listening on %s, data_dir=%s", *addr, *dataDir)
err = server.StartWithContext(ctx)
if errors.Is(err, http.ErrServerClosed) {
return nil
}
return err
}

func printUsage() {
fmt.Println("Usage: anyclaw-registry serve [--addr :8791] [--data-dir .anyclaw-registry] [--db-driver sqlite] [--db-dsn path-or-url] [--admin-token token] [--seed=true]")
}
53 changes: 53 additions & 0 deletions cmd/anyclaw-registry/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package main

import (
"bytes"
"io"
"os"
"strings"
"testing"
)

func TestRunDefaultsToServeAndRequiresAdminToken(t *testing.T) {
t.Setenv("ANYCLAW_REGISTRY_ADMIN_TOKEN", "")

err := run([]string{"serve", "--data-dir", t.TempDir(), "--seed=false"})
if err == nil || !strings.Contains(err.Error(), "admin token is required") {
t.Fatalf("expected missing admin token error, got %v", err)
}
}

func TestRunHelpAndUnknownCommand(t *testing.T) {
out := captureStdout(t, func() {
if err := run([]string{"help"}); err != nil {
t.Fatalf("help returned error: %v", err)
}
})
if !strings.Contains(out, "anyclaw-registry serve") {
t.Fatalf("help output = %q", out)
}
if err := run([]string{"nope"}); err == nil || !strings.Contains(err.Error(), "unknown command") {
t.Fatalf("expected unknown command error, got %v", err)
}
}

func captureStdout(t *testing.T, fn func()) string {
t.Helper()
original := os.Stdout
read, write, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
os.Stdout = write
defer func() { os.Stdout = original }()

fn()
if err := write.Close(); err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
if _, err := io.Copy(&buf, read); err != nil {
t.Fatal(err)
}
return buf.String()
}
108 changes: 108 additions & 0 deletions pkg/marketregistry/seed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package marketregistry

import (
"context"
"time"
)

func SeedIfEmpty(ctx context.Context, store *Store, storage *LocalStorage) error {
count, err := store.CountArtifacts(ctx)
if err != nil {
return err
}
if count > 0 {
return nil
}
return SeedFixtures(ctx, store, storage)
}

func SeedFixtures(ctx context.Context, store *Store, storage *LocalStorage) error {
now := time.Now().UTC().Format(time.RFC3339)
fixtures := []Artifact{
{
ID: "cloud.agent.code-reviewer",
Kind: ArtifactKindAgent,
Name: "Cloud Code Reviewer",
Summary: "Reviews local changes and highlights concrete risks before merge.",
DescriptionMD: "A marketplace fixture agent for review-oriented workflows.",
Version: "1.0.0",
LatestVersion: "1.0.0",
Source: defaultRegistrySourceID,
Publisher: "AnyClaw Labs",
RiskLevel: "medium",
TrustLevel: "verified",
Permissions: []string{"fs.read", "git.read"},
Compatibility: Compatibility{AnyClawMin: "0.1.0", OS: []string{"windows", "linux", "darwin"}, Arch: []string{"amd64", "arm64"}},
Tags: []string{"agent", "review", "quality"},
HitSignals: []string{"code review", "风险检查", "pull request"},
Score: 0.96,
UpdatedAt: now,
ManifestSummary: map[string]string{"entry": "agent/profile.json"},
},
{
ID: "cloud.skill.release-notes",
Kind: ArtifactKindSkill,
Name: "Release Notes Writer",
Summary: "Turns git history and issue notes into compact release notes.",
DescriptionMD: "A marketplace fixture skill for writing release notes from project context.",
Version: "1.0.0",
LatestVersion: "1.0.0",
Source: defaultRegistrySourceID,
Publisher: "AnyClaw Labs",
RiskLevel: "low",
TrustLevel: "verified",
Permissions: []string{"fs.read", "git.read"},
Compatibility: Compatibility{AnyClawMin: "0.1.0", OS: []string{"windows", "linux", "darwin"}, Arch: []string{"amd64", "arm64"}},
Tags: []string{"skill", "release", "writing"},
HitSignals: []string{"release notes", "changelog", "发布说明"},
Score: 0.94,
UpdatedAt: now,
ManifestSummary: map[string]string{"entry": "skill/SKILL.md"},
},
{
ID: "cloud.cli.repo-health",
Kind: ArtifactKindCLI,
Name: "Repo Health CLI",
Summary: "Runs a lightweight repository health check command.",
DescriptionMD: "A marketplace fixture CLI package for command binding tests.",
Version: "1.0.0",
LatestVersion: "1.0.0",
Source: defaultRegistrySourceID,
Publisher: "AnyClaw Labs",
RiskLevel: "medium",
TrustLevel: "verified",
Permissions: []string{"process.exec", "fs.read"},
Compatibility: Compatibility{AnyClawMin: "0.1.0", OS: []string{"windows", "linux", "darwin"}, Arch: []string{"amd64", "arm64"}},
Tags: []string{"cli", "health", "repository"},
HitSignals: []string{"repo health", "诊断", "cli"},
Score: 0.91,
UpdatedAt: now,
ManifestSummary: map[string]string{"command": "anyclaw-repo-health"},
},
}

for _, artifact := range fixtures {
version := ArtifactVersion{
ArtifactID: artifact.ID,
Version: artifact.LatestVersion,
ReleasedAt: now,
ChangelogMD: "Initial registry fixture.",
Compatibility: artifact.Compatibility,
Permissions: artifact.Permissions,
PermissionsDiff: artifact.Permissions,
}
info, err := storage.EnsurePackage(artifact, version)
if err != nil {
return err
}
version.SizeBytes = info.SizeBytes
version.ChecksumSHA256 = info.ChecksumSHA256
version.StorageKey = info.StorageKey
artifact.SizeBytes = info.SizeBytes
artifact.ChecksumSHA256 = info.ChecksumSHA256
if err := store.UpsertArtifact(ctx, artifact, []ArtifactVersion{version}); err != nil {
return err
}
}
return nil
}
Loading
Loading