Add marketplace display and MCP actions
This commit is contained in:
parent
2b59d5892a
commit
710bd4f7b5
4 changed files with 284 additions and 0 deletions
|
|
@ -151,6 +151,7 @@ func (s *Service) OnStartup(_ context.Context) core.Result {
|
|||
return core.Result{Value: s.networkState(), OK: true}
|
||||
})
|
||||
s.registerBackgroundActions()
|
||||
s.registerMarketplaceActions()
|
||||
s.registerSidecarActions()
|
||||
s.registerDefaultSchemes()
|
||||
|
||||
|
|
@ -529,6 +530,14 @@ func (s *Service) handleWSMessage(msg WSMessage) core.Result {
|
|||
return c.Action("gui.chat.thinking.append").Run(ctx, wsOptions(msg.Data))
|
||||
case "chat:thinking:end":
|
||||
return c.Action("gui.chat.thinking.end").Run(ctx, wsOptions(msg.Data))
|
||||
case "marketplace:list":
|
||||
return c.Action("display.marketplace.list").Run(ctx, wsOptions(msg.Data))
|
||||
case "marketplace:fetch":
|
||||
return c.Action("display.marketplace.fetch").Run(ctx, wsOptions(msg.Data))
|
||||
case "marketplace:verify":
|
||||
return c.Action("display.marketplace.verify").Run(ctx, wsOptions(msg.Data))
|
||||
case "marketplace:install":
|
||||
return c.Action("display.marketplace.install").Run(ctx, wsOptions(msg.Data))
|
||||
case "keybinding:add":
|
||||
accelerator, _ := msg.Data["accelerator"].(string)
|
||||
description, _ := msg.Data["description"].(string)
|
||||
|
|
|
|||
123
pkg/display/marketplace.go
Normal file
123
pkg/display/marketplace.go
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
package display
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"forge.lthn.ai/core/gui/pkg/marketplace"
|
||||
)
|
||||
|
||||
type marketplaceListInput struct {
|
||||
RegistryURL string `json:"url"`
|
||||
}
|
||||
|
||||
type marketplaceFetchInput struct {
|
||||
ManifestURL string `json:"url"`
|
||||
}
|
||||
|
||||
type marketplaceInstallInput struct {
|
||||
ManifestURL string `json:"url"`
|
||||
InstallDir string `json:"install_dir,omitempty"`
|
||||
GitBinary string `json:"git_binary,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Service) registerMarketplaceActions() {
|
||||
s.Core().Action("display.marketplace.list", func(ctx context.Context, opts core.Options) core.Result {
|
||||
input := marketplaceListInput{RegistryURL: marketplaceRegistryURL(opts)}
|
||||
if strings.TrimSpace(input.RegistryURL) == "" {
|
||||
return core.Result{Value: coreerr.E("display.marketplace.list", "registry url is required", nil), OK: false}
|
||||
}
|
||||
installer := marketplace.Installer{HTTPClient: http.DefaultClient}
|
||||
manifests, err := installer.List(ctx, input.RegistryURL)
|
||||
if err != nil {
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
return core.Result{Value: map[string]any{
|
||||
"registry_url": input.RegistryURL,
|
||||
"manifests": manifests,
|
||||
}, OK: true}
|
||||
})
|
||||
|
||||
s.Core().Action("display.marketplace.fetch", func(ctx context.Context, opts core.Options) core.Result {
|
||||
input := marketplaceFetchInput{ManifestURL: strings.TrimSpace(opts.String("url"))}
|
||||
if input.ManifestURL == "" {
|
||||
return core.Result{Value: coreerr.E("display.marketplace.fetch", "manifest url is required", nil), OK: false}
|
||||
}
|
||||
installer := marketplace.Installer{HTTPClient: http.DefaultClient}
|
||||
manifest, err := installer.FetchManifest(ctx, input.ManifestURL)
|
||||
if err != nil {
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
return core.Result{Value: manifest, OK: true}
|
||||
})
|
||||
|
||||
s.Core().Action("display.marketplace.verify", func(ctx context.Context, opts core.Options) core.Result {
|
||||
input := marketplaceFetchInput{ManifestURL: strings.TrimSpace(opts.String("url"))}
|
||||
if input.ManifestURL == "" {
|
||||
return core.Result{Value: coreerr.E("display.marketplace.verify", "manifest url is required", nil), OK: false}
|
||||
}
|
||||
installer := marketplace.Installer{HTTPClient: http.DefaultClient}
|
||||
manifest, err := installer.Verify(ctx, input.ManifestURL)
|
||||
if err != nil {
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
return core.Result{Value: map[string]any{
|
||||
"manifest": manifest,
|
||||
"digest": marketplace.DigestManifest(manifest),
|
||||
}, OK: true}
|
||||
})
|
||||
|
||||
s.Core().Action("display.marketplace.install", func(ctx context.Context, opts core.Options) core.Result {
|
||||
input := marketplaceInstallInput{
|
||||
ManifestURL: strings.TrimSpace(opts.String("url")),
|
||||
InstallDir: strings.TrimSpace(opts.String("install_dir")),
|
||||
GitBinary: strings.TrimSpace(opts.String("git_binary")),
|
||||
}
|
||||
if input.ManifestURL == "" {
|
||||
return core.Result{Value: coreerr.E("display.marketplace.install", "manifest url is required", nil), OK: false}
|
||||
}
|
||||
|
||||
installer := marketplace.Installer{
|
||||
HTTPClient: http.DefaultClient,
|
||||
GitBinary: input.GitBinary,
|
||||
InstallDir: marketplaceInstallRoot(input.InstallDir),
|
||||
}
|
||||
manifest, err := installer.Verify(ctx, input.ManifestURL)
|
||||
if err != nil {
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
targetDir, err := installer.Install(ctx, manifest)
|
||||
if err != nil {
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
return core.Result{Value: map[string]any{
|
||||
"manifest": manifest,
|
||||
"digest": marketplace.DigestManifest(manifest),
|
||||
"target_dir": targetDir,
|
||||
"install_dir": installer.InstallDir,
|
||||
}, OK: true}
|
||||
})
|
||||
}
|
||||
|
||||
func marketplaceRegistryURL(opts core.Options) string {
|
||||
if url := strings.TrimSpace(opts.String("url")); url != "" {
|
||||
return url
|
||||
}
|
||||
return strings.TrimSpace(core.Env("CORE_MARKETPLACE_REGISTRY_URL"))
|
||||
}
|
||||
|
||||
func marketplaceInstallRoot(raw string) string {
|
||||
if trimmed := strings.TrimSpace(raw); trimmed != "" {
|
||||
return trimmed
|
||||
}
|
||||
home := strings.TrimSpace(core.Env("DIR_HOME"))
|
||||
if home == "" {
|
||||
return filepath.Join(os.TempDir(), "core", "apps")
|
||||
}
|
||||
return filepath.Join(home, ".core", "apps")
|
||||
}
|
||||
|
|
@ -59,6 +59,7 @@ func (s *Subsystem) RegisterTools(server *mcp.Server) {
|
|||
s.registerKeybindingTools(server)
|
||||
s.registerDockTools(server)
|
||||
s.registerLifecycleTools(server)
|
||||
s.registerMarketplaceTools(server)
|
||||
s.registerEventsTools(server)
|
||||
s.registerMenuTools(server)
|
||||
}
|
||||
|
|
|
|||
151
pkg/mcp/tools_marketplace.go
Normal file
151
pkg/mcp/tools_marketplace.go
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
// pkg/mcp/tools_marketplace.go
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"forge.lthn.ai/core/gui/pkg/marketplace"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
type MarketplaceListInput struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type MarketplaceListOutput struct {
|
||||
RegistryURL string `json:"registry_url"`
|
||||
Manifests []marketplace.Manifest `json:"manifests"`
|
||||
}
|
||||
|
||||
func (s *Subsystem) marketplaceList(_ context.Context, _ *mcp.CallToolRequest, input MarketplaceListInput) (*mcp.CallToolResult, MarketplaceListOutput, error) {
|
||||
r := s.core.Action("display.marketplace.list").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "url", Value: input.URL},
|
||||
))
|
||||
if !r.OK {
|
||||
if e, ok := r.Value.(error); ok {
|
||||
return nil, MarketplaceListOutput{}, e
|
||||
}
|
||||
return nil, MarketplaceListOutput{}, coreerr.E("mcp.marketplaceList", "display.marketplace.list failed", nil)
|
||||
}
|
||||
payload, ok := r.Value.(map[string]any)
|
||||
if !ok {
|
||||
return nil, MarketplaceListOutput{}, coreerr.E("mcp.marketplaceList", "unexpected result type", nil)
|
||||
}
|
||||
output := MarketplaceListOutput{RegistryURL: stringValue(payload, "registry_url")}
|
||||
if manifests, ok := payload["manifests"].([]marketplace.Manifest); ok {
|
||||
output.Manifests = manifests
|
||||
}
|
||||
return nil, output, nil
|
||||
}
|
||||
|
||||
type MarketplaceFetchInput struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
func (s *Subsystem) marketplaceFetch(_ context.Context, _ *mcp.CallToolRequest, input MarketplaceFetchInput) (*mcp.CallToolResult, marketplace.Manifest, error) {
|
||||
r := s.core.Action("display.marketplace.fetch").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "url", Value: input.URL},
|
||||
))
|
||||
if !r.OK {
|
||||
if e, ok := r.Value.(error); ok {
|
||||
return nil, marketplace.Manifest{}, e
|
||||
}
|
||||
return nil, marketplace.Manifest{}, coreerr.E("mcp.marketplaceFetch", "display.marketplace.fetch failed", nil)
|
||||
}
|
||||
manifest, ok := r.Value.(marketplace.Manifest)
|
||||
if !ok {
|
||||
return nil, marketplace.Manifest{}, coreerr.E("mcp.marketplaceFetch", "unexpected result type", nil)
|
||||
}
|
||||
return nil, manifest, nil
|
||||
}
|
||||
|
||||
type MarketplaceVerifyInput struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type MarketplaceVerifyOutput struct {
|
||||
Manifest marketplace.Manifest `json:"manifest"`
|
||||
Digest string `json:"digest"`
|
||||
}
|
||||
|
||||
func (s *Subsystem) marketplaceVerify(_ context.Context, _ *mcp.CallToolRequest, input MarketplaceVerifyInput) (*mcp.CallToolResult, MarketplaceVerifyOutput, error) {
|
||||
r := s.core.Action("display.marketplace.verify").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "url", Value: input.URL},
|
||||
))
|
||||
if !r.OK {
|
||||
if e, ok := r.Value.(error); ok {
|
||||
return nil, MarketplaceVerifyOutput{}, e
|
||||
}
|
||||
return nil, MarketplaceVerifyOutput{}, coreerr.E("mcp.marketplaceVerify", "display.marketplace.verify failed", nil)
|
||||
}
|
||||
payload, ok := r.Value.(map[string]any)
|
||||
if !ok {
|
||||
return nil, MarketplaceVerifyOutput{}, coreerr.E("mcp.marketplaceVerify", "unexpected result type", nil)
|
||||
}
|
||||
output := MarketplaceVerifyOutput{Digest: stringValue(payload, "digest")}
|
||||
if manifest, ok := payload["manifest"].(marketplace.Manifest); ok {
|
||||
output.Manifest = manifest
|
||||
}
|
||||
return nil, output, nil
|
||||
}
|
||||
|
||||
type MarketplaceInstallInput struct {
|
||||
URL string `json:"url"`
|
||||
InstallDir string `json:"install_dir,omitempty"`
|
||||
GitBinary string `json:"git_binary,omitempty"`
|
||||
}
|
||||
|
||||
type MarketplaceInstallOutput struct {
|
||||
Manifest marketplace.Manifest `json:"manifest"`
|
||||
Digest string `json:"digest"`
|
||||
TargetDir string `json:"target_dir"`
|
||||
InstallDir string `json:"install_dir"`
|
||||
}
|
||||
|
||||
func (s *Subsystem) marketplaceInstall(_ context.Context, _ *mcp.CallToolRequest, input MarketplaceInstallInput) (*mcp.CallToolResult, MarketplaceInstallOutput, error) {
|
||||
r := s.core.Action("display.marketplace.install").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "url", Value: input.URL},
|
||||
core.Option{Key: "install_dir", Value: input.InstallDir},
|
||||
core.Option{Key: "git_binary", Value: input.GitBinary},
|
||||
))
|
||||
if !r.OK {
|
||||
if e, ok := r.Value.(error); ok {
|
||||
return nil, MarketplaceInstallOutput{}, e
|
||||
}
|
||||
return nil, MarketplaceInstallOutput{}, coreerr.E("mcp.marketplaceInstall", "display.marketplace.install failed", nil)
|
||||
}
|
||||
payload, ok := r.Value.(map[string]any)
|
||||
if !ok {
|
||||
return nil, MarketplaceInstallOutput{}, coreerr.E("mcp.marketplaceInstall", "unexpected result type", nil)
|
||||
}
|
||||
output := MarketplaceInstallOutput{
|
||||
Digest: stringValue(payload, "digest"),
|
||||
TargetDir: stringValue(payload, "target_dir"),
|
||||
InstallDir: stringValue(payload, "install_dir"),
|
||||
}
|
||||
if manifest, ok := payload["manifest"].(marketplace.Manifest); ok {
|
||||
output.Manifest = manifest
|
||||
}
|
||||
return nil, output, nil
|
||||
}
|
||||
|
||||
func (s *Subsystem) registerMarketplaceTools(server *mcp.Server) {
|
||||
addTool(s, server, &mcp.Tool{
|
||||
Name: "marketplace_list",
|
||||
Description: `List marketplace manifests from a registry. Example: {"url":"https://example.com/marketplace.yaml"}`,
|
||||
}, s.marketplaceList)
|
||||
addTool(s, server, &mcp.Tool{
|
||||
Name: "marketplace_fetch",
|
||||
Description: `Fetch a marketplace manifest without installing it. Example: {"url":"https://example.com/core-ui.yaml"}`,
|
||||
}, s.marketplaceFetch)
|
||||
addTool(s, server, &mcp.Tool{
|
||||
Name: "marketplace_verify",
|
||||
Description: `Fetch and verify a signed marketplace manifest. Example: {"url":"https://example.com/core-ui.yaml"}`,
|
||||
}, s.marketplaceVerify)
|
||||
addTool(s, server, &mcp.Tool{
|
||||
Name: "marketplace_install",
|
||||
Description: `Fetch, verify, and install a marketplace manifest. Example: {"url":"https://example.com/core-ui.yaml","install_dir":"/Users/me/.core/apps"}`,
|
||||
}, s.marketplaceInstall)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue