package markdown import ( "context" "fmt" "regexp" "strings" "git.yeij.top/AskaEth/Cyrene-Plugins/sdk" ) type MarkdownPlugin struct{ sdk.BasePlugin } func (p *MarkdownPlugin) Metadata() sdk.PluginMetadata { return sdk.PluginMetadata{ Name: "markdown", DisplayName: "Markdown Processor", Version: "1.0.0", Description: "Markdown processing: to HTML, extract text/links/code, generate TOC", Category: "format", Author: sdk.PluginAuthor{Name: "Cyrene Team"}, } } func (p *MarkdownPlugin) Tools() []sdk.Tool { return []sdk.Tool{&MarkdownTool{}} } type MarkdownTool struct{ sdk.BaseTool } func (t *MarkdownTool) Definition() sdk.ToolDefinition { return sdk.ToolDefinition{ ID: "markdown", Name: "markdown", DisplayName: "Markdown Processor", Description: "Markdown processing. Convert to HTML, extract plain text, extract links/code blocks, generate TOC.", Category: "format", Complexity: sdk.ComplexitySimple, Parameters: map[string]interface{}{ "type": "object", "properties": map[string]interface{}{ "action": map[string]interface{}{"type": "string", "enum": []string{"to_html", "to_text", "extract_links", "extract_code", "table_of_contents"}}, "markdown": map[string]interface{}{"type": "string"}, }, "required": []string{"action", "markdown"}, }, } } func (t *MarkdownTool) Validate(args map[string]interface{}) error { for _, k := range []string{"action", "markdown"} { if _, ok := args[k]; !ok { return fmt.Errorf("missing required parameter: %s", k) } } return nil } func (t *MarkdownTool) Execute(_ context.Context, args map[string]interface{}) (*sdk.ToolResult, error) { action, _ := args["action"].(string) md, _ := args["markdown"].(string) switch action { case "to_html": return &sdk.ToolResult{ToolName: "markdown", Success: true, Output: mdToHTML(md)}, nil case "to_text": text := md reCode := regexp.MustCompile("(?s)```.*?```") text = reCode.ReplaceAllString(text, "") text = regexp.MustCompile(`\*\*([^*]+)\*\*`).ReplaceAllString(text, "$1") text = regexp.MustCompile(`\*([^*]+)\*`).ReplaceAllString(text, "$1") text = regexp.MustCompile(`~~([^~]+)~~`).ReplaceAllString(text, "$1") text = regexp.MustCompile(`^#{1,6}\s+`).ReplaceAllString(text, "") text = regexp.MustCompile(`^[*-]\s+`).ReplaceAllString(text, "- ") text = regexp.MustCompile(`^>\s+`).ReplaceAllString(text, "") text = regexp.MustCompile(`\n{3,}`).ReplaceAllString(text, "\n\n") return &sdk.ToolResult{ToolName: "markdown", Success: true, Output: strings.TrimSpace(text)}, nil case "extract_links": re := regexp.MustCompile(`\[([^\]]+)\]\(([^)]+)\)`) matches := re.FindAllStringSubmatch(md, -1) if len(matches) == 0 { return &sdk.ToolResult{ToolName: "markdown", Success: true, Output: "No links found"}, nil } var out strings.Builder for i, m := range matches { out.WriteString(fmt.Sprintf("%d. %s -> %s\n", i+1, m[1], m[2])) } return &sdk.ToolResult{ToolName: "markdown", Success: true, Output: out.String()}, nil case "extract_code": re := regexp.MustCompile("(?s)```(\\w*)\n?(.*?)```") matches := re.FindAllStringSubmatch(md, -1) if len(matches) == 0 { return &sdk.ToolResult{ToolName: "markdown", Success: true, Output: "No code blocks found"}, nil } var out strings.Builder for i, m := range matches { lang := m[1] if lang == "" { lang = "text" } code := m[2] if len([]rune(code)) > 500 { code = string([]rune(code)[:500]) + "..." } out.WriteString(fmt.Sprintf("--- Block %d (%s) ---\n%s\n\n", i+1, lang, code)) } return &sdk.ToolResult{ToolName: "markdown", Success: true, Output: out.String()}, nil case "table_of_contents": re := regexp.MustCompile(`(?m)^(#{1,6})\s+(.+)$`) matches := re.FindAllStringSubmatch(md, -1) if len(matches) == 0 { return &sdk.ToolResult{ToolName: "markdown", Success: true, Output: "No headings found"}, nil } var out strings.Builder for _, m := range matches { depth := len(m[1]) indent := strings.Repeat(" ", depth-1) out.WriteString(fmt.Sprintf("%s- %s\n", indent, m[2])) } return &sdk.ToolResult{ToolName: "markdown", Success: true, Output: out.String()}, nil } return &sdk.ToolResult{ToolName: "markdown", Success: false, Error: "unknown action: " + action}, nil } func mdToHTML(md string) string { // Save code blocks type placeholder struct { orig string content string language string } blocks := []*placeholder{} reCode := regexp.MustCompile("(?s)```(\\w*)\n?(.*?)```") md = reCode.ReplaceAllStringFunc(md, func(s string) string { m := reCode.FindStringSubmatch(s) b := &placeholder{orig: fmt.Sprintf("\x00CODE%d\x00", len(blocks)), language: m[1], content: escapeHTML(m[2])} blocks = append(blocks, b) return b.orig }) // Inline elements md = regexp.MustCompile("`([^`]+)`").ReplaceAllString(md, "$1") md = regexp.MustCompile(`!\[([^\]]*)\]\(([^)]+)\)`).ReplaceAllString(md, `$1`) md = regexp.MustCompile(`\[([^\]]+)\]\(([^)]+)\)`).ReplaceAllString(md, `$1`) md = regexp.MustCompile(`\*\*([^*]+)\*\*`).ReplaceAllString(md, `$1`) md = regexp.MustCompile(`\*([^*]+)\*`).ReplaceAllString(md, `$1`) md = regexp.MustCompile(`~~([^~]+)~~`).ReplaceAllString(md, `$1`) md = regexp.MustCompile(`(?m)^#{6}\s+(.+)$`).ReplaceAllString(md, `
$1
`) md = regexp.MustCompile(`(?m)^#{5}\s+(.+)$`).ReplaceAllString(md, `
$1
`) md = regexp.MustCompile(`(?m)^#{4}\s+(.+)$`).ReplaceAllString(md, `

$1

`) md = regexp.MustCompile(`(?m)^#{3}\s+(.+)$`).ReplaceAllString(md, `

$1

`) md = regexp.MustCompile(`(?m)^#{2}\s+(.+)$`).ReplaceAllString(md, `

$1

`) md = regexp.MustCompile(`(?m)^#{1}\s+(.+)$`).ReplaceAllString(md, `

$1

`) md = regexp.MustCompile(`(?m)^---\s*$`).ReplaceAllString(md, `
`) md = regexp.MustCompile(`(?m)^>\s+(.+)$`).ReplaceAllString(md, `
$1
`) // Restore code blocks for _, b := range blocks { langAttr := "" if b.language != "" { langAttr = " class=\"language-" + b.language + "\"" } md = strings.Replace(md, b.orig, "
"+b.content+"
", 1) } // Paragraphs lines := strings.Split(md, "\n") var out strings.Builder for _, line := range lines { trimmed := strings.TrimSpace(line) if trimmed == "" { continue } if strings.HasPrefix(trimmed, "<") { out.WriteString(trimmed + "\n") } else { out.WriteString("

" + trimmed + "

\n") } } return out.String() } func escapeHTML(s string) string { s = strings.ReplaceAll(s, "&", "&") s = strings.ReplaceAll(s, "<", "<") s = strings.ReplaceAll(s, ">", ">") return s }