140 lines
3.4 KiB
Go
140 lines
3.4 KiB
Go
package toolsrv
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"ollie/paths"
|
|
)
|
|
|
|
// ToolsPath returns the tools directory: CfgDir()/tools.
|
|
func ToolsPath() string {
|
|
return paths.ToolsDir()
|
|
}
|
|
|
|
|
|
// ExtractPrompt parses the ollie:prompt ... ollie:end block from a tool
|
|
// script's header comments. Returns the prompt text with comment prefixes
|
|
// stripped, or "" if no prompt block is found.
|
|
func ExtractPrompt(script string) string {
|
|
var lines []string
|
|
var inPrompt bool
|
|
for _, line := range strings.Split(script, "\n") {
|
|
trimmed := strings.TrimSpace(line)
|
|
// Strip comment prefix (# or //)
|
|
bare := trimmed
|
|
if strings.HasPrefix(bare, "# ") {
|
|
bare = bare[2:]
|
|
} else if strings.HasPrefix(bare, "#") {
|
|
bare = bare[1:]
|
|
} else if strings.HasPrefix(bare, "// ") {
|
|
bare = bare[3:]
|
|
} else if strings.HasPrefix(bare, "//") {
|
|
bare = bare[2:]
|
|
} else if inPrompt {
|
|
// Non-comment line while in prompt block — end of header
|
|
break
|
|
} else {
|
|
// Non-comment line before prompt block found
|
|
if trimmed != "" && !strings.HasPrefix(trimmed, "#!") {
|
|
break
|
|
}
|
|
continue
|
|
}
|
|
|
|
if strings.Contains(bare, "ollie:prompt") {
|
|
inPrompt = true
|
|
continue
|
|
}
|
|
if inPrompt && strings.Contains(bare, "ollie:end") {
|
|
break
|
|
}
|
|
if inPrompt {
|
|
lines = append(lines, bare)
|
|
}
|
|
}
|
|
if len(lines) == 0 {
|
|
return ""
|
|
}
|
|
return strings.Join(lines, "\n")
|
|
}
|
|
|
|
// DiscoverTools scans the tools directory and returns metadata for all scripts,
|
|
// including their ollie:prompt blocks and short descriptions.
|
|
func DiscoverTools() []ToolInfo {
|
|
dir := ToolsPath()
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var infos []ToolInfo
|
|
for _, e := range entries {
|
|
if e.IsDir() || e.Name() == "idx" || strings.HasPrefix(e.Name(), ".") {
|
|
continue
|
|
}
|
|
data, err := os.ReadFile(filepath.Join(dir, e.Name()))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
script := string(data)
|
|
prompt := ExtractPrompt(script)
|
|
desc := extractShortDescription(prompt)
|
|
infos = append(infos, ToolInfo{
|
|
Name: e.Name(),
|
|
Description: desc,
|
|
Prompt: prompt,
|
|
})
|
|
}
|
|
return infos
|
|
}
|
|
|
|
// extractShortDescription gets the first non-heading, non-empty line from a prompt.
|
|
func extractShortDescription(prompt string) string {
|
|
for _, line := range strings.Split(prompt, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
return line
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// ExtractShortDescription is the exported version of extractShortDescription.
|
|
func ExtractShortDescription(prompt string) string {
|
|
return extractShortDescription(prompt)
|
|
}
|
|
|
|
// ToolPrompt returns the full ollie:prompt content for a named tool.
|
|
// Returns "" if the tool has no prompt or doesn't exist.
|
|
func ToolPrompt(name string) string {
|
|
if strings.Contains(name, "/") || strings.Contains(name, "..") {
|
|
return ""
|
|
}
|
|
path := filepath.Join(ToolsPath(), name)
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return ExtractPrompt(string(data))
|
|
}
|
|
|
|
// ReadTool reads a named tool script from the tools directory.
|
|
func ReadTool(name string) (string, error) {
|
|
if strings.Contains(name, "/") || strings.Contains(name, "..") {
|
|
return "", fmt.Errorf("invalid tool name")
|
|
}
|
|
|
|
path := filepath.Join(ToolsPath(), name)
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return "", fmt.Errorf("tool not found: %s", name)
|
|
}
|
|
return "", fmt.Errorf("read tool %s: %w", name, err)
|
|
}
|
|
return string(data), nil
|
|
}
|