135 lines
3.4 KiB
Go
135 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.eidonix.com/eidonix/cletus-plugins/sdk/manifest"
|
|
)
|
|
|
|
type listFlag []string
|
|
|
|
func (l *listFlag) String() string { return strings.Join(*l, ",") }
|
|
func (l *listFlag) Set(v string) error { *l = append(*l, v); return nil }
|
|
|
|
func runRun(args []string) error {
|
|
fs := flag.NewFlagSet("run", flag.ContinueOnError)
|
|
files := fs.String("files", "testdata", "the folder the user opened")
|
|
workspace := fs.String("workspace", "", "a directory to serve as one shared folder")
|
|
edit := fs.Bool("edit", false, "the user allows edits")
|
|
timeout := fs.Duration("timeout", 2*time.Minute, "how long to wait for the answer")
|
|
var caps listFlag
|
|
fs.Var(&caps, "capability", "a Cletus feature the user has (repeatable)")
|
|
if err := fs.Parse(args); err != nil {
|
|
return err
|
|
}
|
|
rest := fs.Args()
|
|
if len(rest) == 0 {
|
|
return errors.New("run needs a command: tools, invoke, ui or service")
|
|
}
|
|
plugin, err := readPlugin(".")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
wd, _ := os.Getwd()
|
|
filesDir, err := filepath.Abs(*files)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := os.MkdirAll(filesDir, 0o755); err != nil {
|
|
return err
|
|
}
|
|
wsDir := ""
|
|
if *workspace != "" {
|
|
if wsDir, err = filepath.Abs(*workspace); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
bin, cleanup, err := buildWorker(wd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cleanup()
|
|
host := &testHost{plugin: plugin, filesDir: filesDir, workspaceDir: wsDir, edit: *edit,
|
|
dataDir: filepath.Join(wd, ".cletus-dev", "data"), capabilities: map[string]bool{}, log: os.Stderr}
|
|
for _, c := range caps {
|
|
host.capabilities[c] = true
|
|
}
|
|
if err := host.start(bin); err != nil {
|
|
return err
|
|
}
|
|
defer host.stop()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
|
|
defer cancel()
|
|
arg := func(i int, fallback string) string {
|
|
if len(rest) > i {
|
|
return rest[i]
|
|
}
|
|
return fallback
|
|
}
|
|
var out json.RawMessage
|
|
switch rest[0] {
|
|
case "tools":
|
|
err = host.call(ctx, "tools.list", nil, &out)
|
|
case "invoke":
|
|
if len(rest) < 2 {
|
|
return errors.New("invoke needs a tool name")
|
|
}
|
|
err = host.call(ctx, "tools.invoke", map[string]any{"name": rest[1], "args": arg(2, "{}"), "working_dir": filesDir, "edit_mode": *edit}, &out)
|
|
case "ui":
|
|
if len(rest) < 2 {
|
|
return errors.New("ui needs a method name")
|
|
}
|
|
err = host.call(ctx, "ui."+strings.TrimPrefix(rest[1], "ui."), json.RawMessage(arg(2, "[]")), &out)
|
|
case "service":
|
|
if len(rest) < 2 {
|
|
return errors.New("service needs <service>.<method>")
|
|
}
|
|
err = host.call(ctx, "svc."+rest[1], map[string]any{"caller": "cletus-plugin", "mutating": false, "args": json.RawMessage(arg(2, "{}"))}, &out)
|
|
default:
|
|
return fmt.Errorf("unknown run command %q", rest[0])
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var pretty bytes.Buffer
|
|
if json.Indent(&pretty, out, "", " ") != nil {
|
|
pretty.Write(out)
|
|
}
|
|
fmt.Println(pretty.String())
|
|
return nil
|
|
}
|
|
|
|
func readPlugin(dir string) (*manifest.Plugin, error) {
|
|
path := filepath.Join(dir, "plugin.json")
|
|
if _, err := os.Stat(path); err != nil {
|
|
return nil, manifest.ErrNoManifest
|
|
}
|
|
return manifest.Read(path)
|
|
}
|
|
|
|
// prefixWriter prefixes every line written to w.
|
|
type prefixWriter struct {
|
|
w io.Writer
|
|
prefix string
|
|
}
|
|
|
|
func (p prefixWriter) Write(b []byte) (int, error) {
|
|
for _, line := range strings.SplitAfter(string(b), "\n") {
|
|
if line != "" {
|
|
fmt.Fprint(p.w, p.prefix+line)
|
|
}
|
|
}
|
|
return len(b), nil
|
|
}
|