浏览代码

feat: add bundle cli commands

Yeuoly 8 月之前
父节点
当前提交
c6d7fa1074

+ 43 - 2
cmd/commandline/bundle.go

@@ -1,15 +1,21 @@
 package main
 
 import (
+	"strconv"
+
+	"github.com/langgenius/dify-plugin-daemon/cmd/commandline/bundle"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/bundle_entities"
+	"github.com/langgenius/dify-plugin-daemon/internal/utils/log"
 	"github.com/spf13/cobra"
 )
 
 var (
 	bundleCreateCommand = &cobra.Command{
-		Use:   "create",
+		Use:   "init",
 		Short: "Create a bundle",
 		Long:  "Create a bundle",
 		Run: func(c *cobra.Command, args []string) {
+			bundle.InitBundle()
 		},
 	}
 
@@ -32,6 +38,14 @@ var (
 		Short: "Append a github dependency",
 		Long:  "Append a github dependency",
 		Run: func(c *cobra.Command, args []string) {
+			bundlePath := c.Flag("bundle_path").Value.String()
+			repoPattern := c.Flag("repo_pattern").Value.String()
+			githubPattern, err := bundle_entities.NewGithubRepoPattern(repoPattern)
+			if err != nil {
+				log.Error("Invalid github repo pattern: %v", err)
+				return
+			}
+			bundle.AddGithubDependency(bundlePath, githubPattern)
 		},
 	}
 
@@ -40,6 +54,14 @@ var (
 		Short: "Append a marketplace dependency",
 		Long:  "Append a marketplace dependency",
 		Run: func(c *cobra.Command, args []string) {
+			bundlePath := c.Flag("bundle_path").Value.String()
+			marketplacePatternString := c.Flag("marketplace_pattern").Value.String()
+			marketplacePattern, err := bundle_entities.NewMarketplacePattern(marketplacePatternString)
+			if err != nil {
+				log.Error("Invalid marketplace pattern: %v", err)
+				return
+			}
+			bundle.AddMarketplaceDependency(bundlePath, marketplacePattern)
 		},
 	}
 
@@ -48,6 +70,9 @@ var (
 		Short: "Append a local package dependency",
 		Long:  "Append a local package dependency",
 		Run: func(c *cobra.Command, args []string) {
+			bundlePath := c.Flag("bundle_path").Value.String()
+			packagePath := c.Flag("package_path").Value.String()
+			bundle.AddPackageDependency(bundlePath, packagePath)
 		},
 	}
 
@@ -64,6 +89,14 @@ var (
 		Short: "Remove a dependency",
 		Long:  "Remove a dependency",
 		Run: func(c *cobra.Command, args []string) {
+			bundlePath := c.Flag("bundle_path").Value.String()
+			index := c.Flag("index").Value.String()
+			indexInt, err := strconv.Atoi(index)
+			if err != nil {
+				log.Error("Invalid index: %v", err)
+				return
+			}
+			bundle.RemoveDependency(bundlePath, indexInt)
 		},
 	}
 
@@ -82,6 +115,14 @@ var (
 		Run: func(c *cobra.Command, args []string) {
 		},
 	}
+
+	bundlePackageCommand = &cobra.Command{
+		Use:   "package",
+		Short: "Package the bundle",
+		Long:  "Package the bundle",
+		Run: func(c *cobra.Command, args []string) {
+		},
+	}
 )
 
 func init() {
@@ -94,7 +135,7 @@ func init() {
 	bundleCommand.AddCommand(bundleRegenerateCommand)
 	bundleCommand.AddCommand(bundleBumpVersionCommand)
 	bundleCommand.AddCommand(bundleListDependenciesCommand)
-
+	bundleCommand.AddCommand(bundlePackageCommand)
 	bundleCommand.AddCommand(bundleAnalyzeCommand)
 
 	bundleAppendDependencyCommand.Flags().StringP("bundle_path", "i", "", "path to the bundle file")

+ 19 - 0
cmd/commandline/bundle/add_dep.go

@@ -0,0 +1,19 @@
+package bundle
+
+import "github.com/langgenius/dify-plugin-daemon/internal/types/entities/bundle_entities"
+
+func AddGithubDependency(bundlePath string, pattern bundle_entities.GithubRepoPattern) {
+
+}
+
+func AddMarketplaceDependency(bundlePath string, pattern bundle_entities.MarketplacePattern) {
+
+}
+
+func AddPackageDependency(bundlePath string, path string) {
+
+}
+
+func RemoveDependency(bundlePath string, index int) {
+
+}

+ 90 - 0
cmd/commandline/bundle/init.go

@@ -0,0 +1,90 @@
+package bundle
+
+import (
+	_ "embed"
+	"fmt"
+	"os"
+	"path"
+
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/bundle_entities"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/manifest_entities"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/plugin_entities"
+	"github.com/langgenius/dify-plugin-daemon/internal/utils/log"
+	"github.com/langgenius/dify-plugin-daemon/internal/utils/parser"
+
+	tea "github.com/charmbracelet/bubbletea"
+)
+
+//go:embed templates/icon.svg
+var BUNDLE_ICON []byte
+
+func InitBundle() {
+	m := newProfile()
+	p := tea.NewProgram(m)
+	if result, err := p.Run(); err != nil {
+		fmt.Println("Error running program:", err)
+	} else {
+		if _, ok := result.(profile); ok {
+			author := m.inputs[1].Value()
+			name := m.inputs[0].Value()
+			description := m.inputs[2].Value()
+
+			bundle := &bundle_entities.Bundle{
+				Name:         name,
+				Icon:         "icon.svg",
+				Labels:       plugin_entities.NewI18nObject(name),
+				Description:  plugin_entities.NewI18nObject(description),
+				Version:      "0.0.1",
+				Author:       author,
+				Type:         manifest_entities.BundleType,
+				Dependencies: []bundle_entities.Dependency{},
+			}
+
+			// create bundle directory
+			cwd, err := os.Getwd()
+			if err != nil {
+				log.Error("Error getting current directory: %v", err)
+				return
+			}
+
+			bundleDir := path.Join(cwd, bundle.Name)
+			if err := os.MkdirAll(bundleDir, 0755); err != nil {
+				log.Error("Error creating bundle directory: %v", err)
+				return
+			}
+
+			success := false
+			defer func() {
+				if !success {
+					os.RemoveAll(bundleDir)
+				}
+			}()
+
+			// save
+			bundleYaml := parser.MarshalYamlBytes(bundle)
+			if err := os.WriteFile(path.Join(bundleDir, "manifest.yaml"), bundleYaml, 0644); err != nil {
+				log.Error("Error saving manifest.yaml: %v", err)
+				return
+			}
+
+			// create _assets directory
+			if err := os.MkdirAll(path.Join(bundleDir, "_assets"), 0755); err != nil {
+				log.Error("Error creating _assets directory: %v", err)
+				return
+			}
+
+			// create _assets/icon.svg
+			if err := os.WriteFile(path.Join(bundleDir, "_assets", "icon.svg"), BUNDLE_ICON, 0644); err != nil {
+				log.Error("Error saving icon.svg: %v", err)
+				return
+			}
+
+			success = true
+
+			log.Info("Bundle created successfully: %s", bundleDir)
+		} else {
+			log.Error("Error running program: %v", err)
+			return
+		}
+	}
+}

+ 141 - 0
cmd/commandline/bundle/profile.go

@@ -0,0 +1,141 @@
+package bundle
+
+import (
+	"fmt"
+
+	ti "github.com/charmbracelet/bubbles/textinput"
+	tea "github.com/charmbracelet/bubbletea"
+	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/plugin_entities"
+)
+
+type profile struct {
+	cursor int
+	inputs []ti.Model
+
+	warning string
+}
+
+func newProfile() profile {
+	name := ti.New()
+	name.Placeholder = "Bundle name, a directory will be created with this name"
+	name.CharLimit = 128
+	name.Prompt = "Bundle name (press Enter to next step): "
+	name.Focus()
+
+	author := ti.New()
+	author.Placeholder = "Author name"
+	author.CharLimit = 128
+	author.Prompt = "Author (press Enter to next step): "
+
+	description := ti.New()
+	description.Placeholder = "Description"
+	description.CharLimit = 1024
+	description.Prompt = "Description (press Enter to next step): "
+
+	return profile{
+		inputs: []ti.Model{name, author, description},
+	}
+}
+
+func (p profile) Name() string {
+	return p.inputs[0].Value()
+}
+
+func (p profile) Author() string {
+	return p.inputs[1].Value()
+}
+
+func (p profile) Description() string {
+	return p.inputs[2].Value()
+}
+
+func (p profile) View() string {
+	s := fmt.Sprintf("Edit profile of the bundle\n%s\n%s\n%s\n", p.inputs[0].View(), p.inputs[1].View(), p.inputs[2].View())
+	if p.warning != "" {
+		s += fmt.Sprintf("\033[31m%s\033[0m\n", p.warning)
+	}
+	return s
+}
+
+func (p *profile) checkRule() bool {
+	if p.inputs[p.cursor].Value() == "" {
+		p.warning = "Name, author and description cannot be empty"
+		return false
+	} else if p.cursor == 0 && !plugin_entities.PluginNameRegex.MatchString(p.inputs[p.cursor].Value()) {
+		p.warning = "Bundle name must be 1-128 characters long, and can only contain letters, numbers, dashes and underscores"
+		return false
+	} else if p.cursor == 1 && !plugin_entities.AuthorRegex.MatchString(p.inputs[p.cursor].Value()) {
+		p.warning = "Author name must be 1-64 characters long, and can only contain letters, numbers, dashes and underscores"
+		return false
+	} else {
+		p.warning = ""
+	}
+	return true
+}
+
+func (p profile) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+	var cmds []tea.Cmd
+
+	switch msg := msg.(type) {
+	case tea.KeyMsg:
+		switch msg.String() {
+		case "ctrl+c":
+			return p, tea.Quit
+		case "down":
+			// check if empty
+			if !p.checkRule() {
+				return p, nil
+			}
+
+			// focus next
+			p.cursor++
+			if p.cursor >= len(p.inputs) {
+				p.cursor = 0
+			}
+		case "up":
+			if !p.checkRule() {
+				return p, nil
+			}
+
+			p.cursor--
+			if p.cursor < 0 {
+				p.cursor = len(p.inputs) - 1
+			}
+		case "enter":
+			if !p.checkRule() {
+				return p, nil
+			}
+
+			// submit
+			if p.cursor == len(p.inputs)-1 {
+				return p, tea.Quit
+			}
+			// move to next
+			p.cursor++
+		}
+	}
+
+	// update cursor
+	for i := 0; i < len(p.inputs); i++ {
+		if i == p.cursor {
+			p.inputs[i].Focus()
+		} else {
+			p.inputs[i].Blur()
+		}
+	}
+
+	// update view
+	for i := range p.inputs {
+		var cmd tea.Cmd
+		p.inputs[i], cmd = p.inputs[i].Update(msg)
+		if cmd != nil {
+			cmds = append(cmds, cmd)
+		}
+	}
+
+	return p, tea.Batch(cmds...)
+}
+
+func (p profile) Init() tea.Cmd {
+	return nil
+}

+ 6 - 0
cmd/commandline/bundle/templates/icon.svg

@@ -0,0 +1,6 @@
+<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
+  <path d="M20 20 V80 M20 20 H60 Q80 20 80 40 T60 60 H20" 
+        fill="none" 
+        stroke="black" 
+        stroke-width="5"/>
+</svg>

+ 8 - 12
cmd/commandline/plugin/init.go

@@ -118,22 +118,18 @@ func (m model) createPlugin() {
 
 	manifest := &plugin_entities.PluginDeclaration{
 		PluginDeclarationWithoutAdvancedFields: plugin_entities.PluginDeclarationWithoutAdvancedFields{
-			Version: manifest_entities.Version("0.0.1"),
-			Type:    manifest_entities.PluginType,
-			Icon:    "icon.svg",
-			Author:  m.subMenus[SUB_MENU_KEY_PROFILE].(profile).Author(),
-			Name:    m.subMenus[SUB_MENU_KEY_PROFILE].(profile).Name(),
-			Description: plugin_entities.I18nObject{
-				EnUS: m.subMenus[SUB_MENU_KEY_PROFILE].(profile).Description(),
-			},
-			CreatedAt: time.Now(),
+			Version:     manifest_entities.Version("0.0.1"),
+			Type:        manifest_entities.PluginType,
+			Icon:        "icon.svg",
+			Author:      m.subMenus[SUB_MENU_KEY_PROFILE].(profile).Author(),
+			Name:        m.subMenus[SUB_MENU_KEY_PROFILE].(profile).Name(),
+			Description: plugin_entities.NewI18nObject(m.subMenus[SUB_MENU_KEY_PROFILE].(profile).Description()),
+			CreatedAt:   time.Now(),
 			Resource: plugin_entities.PluginResourceRequirement{
 				Memory:     1024 * 1024 * 256, // 256MB
 				Permission: &permission,
 			},
-			Label: plugin_entities.I18nObject{
-				EnUS: m.subMenus[SUB_MENU_KEY_PROFILE].(profile).Name(),
-			},
+			Label: plugin_entities.NewI18nObject(m.subMenus[SUB_MENU_KEY_PROFILE].(profile).Name()),
 		},
 	}
 

+ 15 - 0
internal/types/entities/bundle_entities/dependency.go

@@ -23,8 +23,23 @@ type Dependency struct {
 }
 
 type GithubRepoPattern string
+
+func NewGithubRepoPattern(pattern string) (GithubRepoPattern, error) {
+	if !GITHUB_DEPENDENCY_PATTERN_REGEX_COMPILED.MatchString(pattern) {
+		return "", fmt.Errorf("invalid github repo pattern")
+	}
+	return GithubRepoPattern(pattern), nil
+}
+
 type MarketplacePattern string
 
+func NewMarketplacePattern(pattern string) (MarketplacePattern, error) {
+	if !MARKETPLACE_PATTERN_REGEX_COMPILED.MatchString(pattern) {
+		return "", fmt.Errorf("invalid marketplace pattern")
+	}
+	return MarketplacePattern(pattern), nil
+}
+
 var (
 	GITHUB_VERSION_PATTERN = fmt.Sprintf(
 		`([~^]?%s|%s(\.%s){2}|%s-%s)`,

+ 9 - 0
internal/types/entities/plugin_entities/basic_type.go

@@ -13,6 +13,15 @@ type I18nObject struct {
 	PtBr   string `json:"pt_BR,omitempty" yaml:"pt_BR,omitempty" validate:"lt=1024"`
 }
 
+func NewI18nObject(def string) I18nObject {
+	return I18nObject{
+		EnUS:   def,
+		ZhHans: def,
+		JaJp:   def,
+		PtBr:   def,
+	}
+}
+
 func isBasicType(fl validator.FieldLevel) bool {
 	// allowed int, string, bool, float64
 	switch fl.Field().Kind() {