Spaces:
Runtime error
Runtime error
Merge pull request #83 from EnvisionMindCa/codex/create-cross-platform-cli-app-for-api
Browse files- cli-go/README.md +33 -0
- cli-go/cmd/cat.go +27 -0
- cli-go/cmd/chat.go +83 -0
- cli-go/cmd/ls.go +37 -0
- cli-go/cmd/rm.go +21 -0
- cli-go/cmd/root.go +39 -0
- cli-go/cmd/upload.go +28 -0
- cli-go/cmd/write.go +26 -0
- cli-go/go.mod +16 -0
- cli-go/go.sum +21 -0
- cli-go/internal/client/client.go +218 -0
- cli-go/main.go +7 -0
cli-go/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Go CLI for LLM Backend
|
2 |
+
|
3 |
+
This folder contains a cross-platform command line client written in Go. The CLI can be built as a single executable for Windows, Linux and macOS.
|
4 |
+
|
5 |
+
## Features
|
6 |
+
|
7 |
+
- Interactive colour-coded chat sessions
|
8 |
+
- Upload documents to the API
|
9 |
+
- List, read, write and delete files in the VM
|
10 |
+
|
11 |
+
## Building
|
12 |
+
|
13 |
+
Install Go 1.20 or later and run:
|
14 |
+
|
15 |
+
```bash
|
16 |
+
cd cli-go
|
17 |
+
go build -o llmcli
|
18 |
+
```
|
19 |
+
|
20 |
+
For other platforms pass `GOOS` and `GOARCH` environment variables:
|
21 |
+
|
22 |
+
```bash
|
23 |
+
GOOS=windows GOARCH=amd64 go build -o llmcli.exe
|
24 |
+
```
|
25 |
+
|
26 |
+
## Usage
|
27 |
+
|
28 |
+
```
|
29 |
+
./llmcli --user yourname --server http://localhost:8000 chat
|
30 |
+
```
|
31 |
+
|
32 |
+
Use `--help` on any subcommand for details.
|
33 |
+
|
cli-go/cmd/cat.go
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package cmd
|
2 |
+
|
3 |
+
import (
|
4 |
+
"context"
|
5 |
+
"fmt"
|
6 |
+
|
7 |
+
"github.com/spf13/cobra"
|
8 |
+
|
9 |
+
"llm-cli/internal/client"
|
10 |
+
)
|
11 |
+
|
12 |
+
func newCatCmd() *cobra.Command {
|
13 |
+
return &cobra.Command{
|
14 |
+
Use: "cat <path>",
|
15 |
+
Short: "Print a file from the VM",
|
16 |
+
Args: cobra.ExactArgs(1),
|
17 |
+
RunE: func(cmd *cobra.Command, args []string) error {
|
18 |
+
c := client.New(server)
|
19 |
+
content, err := c.ReadFile(context.Background(), user, args[0])
|
20 |
+
if err != nil {
|
21 |
+
return err
|
22 |
+
}
|
23 |
+
fmt.Print(content)
|
24 |
+
return nil
|
25 |
+
},
|
26 |
+
}
|
27 |
+
}
|
cli-go/cmd/chat.go
ADDED
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package cmd
|
2 |
+
|
3 |
+
import (
|
4 |
+
"bufio"
|
5 |
+
"fmt"
|
6 |
+
"os"
|
7 |
+
"strconv"
|
8 |
+
|
9 |
+
"github.com/fatih/color"
|
10 |
+
"github.com/spf13/cobra"
|
11 |
+
|
12 |
+
"llm-cli/internal/client"
|
13 |
+
)
|
14 |
+
|
15 |
+
func newChatCmd() *cobra.Command {
|
16 |
+
cmd := &cobra.Command{
|
17 |
+
Use: "chat",
|
18 |
+
Short: "Start an interactive chat session",
|
19 |
+
RunE: runChat,
|
20 |
+
}
|
21 |
+
return cmd
|
22 |
+
}
|
23 |
+
|
24 |
+
func runChat(cmd *cobra.Command, args []string) error {
|
25 |
+
ctx := cmd.Context()
|
26 |
+
c := client.New(server)
|
27 |
+
|
28 |
+
sessions, err := c.ListSessions(ctx, user)
|
29 |
+
if err != nil {
|
30 |
+
return err
|
31 |
+
}
|
32 |
+
|
33 |
+
session := "default"
|
34 |
+
if len(sessions) > 0 {
|
35 |
+
fmt.Println("Existing sessions:")
|
36 |
+
for i, s := range sessions {
|
37 |
+
fmt.Printf(" %d. %s\n", i+1, s)
|
38 |
+
}
|
39 |
+
fmt.Printf("Select session number or enter new name [%d]: ", len(sessions))
|
40 |
+
var choice string
|
41 |
+
fmt.Scanln(&choice)
|
42 |
+
if n, err := strconv.Atoi(choice); err == nil && n >= 1 && n <= len(sessions) {
|
43 |
+
session = sessions[n-1]
|
44 |
+
} else if choice != "" {
|
45 |
+
session = choice
|
46 |
+
}
|
47 |
+
}
|
48 |
+
|
49 |
+
cyan := color.New(color.FgCyan).SprintFunc()
|
50 |
+
green := color.New(color.FgGreen).SprintFunc()
|
51 |
+
yellow := color.New(color.FgYellow).SprintFunc()
|
52 |
+
|
53 |
+
fmt.Printf("Chatting as %s in session '%s'\n", green(user), session)
|
54 |
+
|
55 |
+
scanner := bufio.NewScanner(os.Stdin)
|
56 |
+
for {
|
57 |
+
fmt.Printf("%s> ", cyan("You"))
|
58 |
+
if !scanner.Scan() {
|
59 |
+
break
|
60 |
+
}
|
61 |
+
line := scanner.Text()
|
62 |
+
if line == "exit" || line == "quit" {
|
63 |
+
break
|
64 |
+
}
|
65 |
+
stream, err := c.ChatStream(ctx, user, session, line)
|
66 |
+
if err != nil {
|
67 |
+
fmt.Println("error:", err)
|
68 |
+
continue
|
69 |
+
}
|
70 |
+
r := bufio.NewReader(stream)
|
71 |
+
for {
|
72 |
+
part, err := r.ReadString('\n')
|
73 |
+
if len(part) > 0 {
|
74 |
+
fmt.Print(yellow(part))
|
75 |
+
}
|
76 |
+
if err != nil {
|
77 |
+
break
|
78 |
+
}
|
79 |
+
}
|
80 |
+
stream.Close()
|
81 |
+
}
|
82 |
+
return nil
|
83 |
+
}
|
cli-go/cmd/ls.go
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package cmd
|
2 |
+
|
3 |
+
import (
|
4 |
+
"context"
|
5 |
+
"fmt"
|
6 |
+
|
7 |
+
"github.com/spf13/cobra"
|
8 |
+
|
9 |
+
"llm-cli/internal/client"
|
10 |
+
)
|
11 |
+
|
12 |
+
func newLsCmd() *cobra.Command {
|
13 |
+
return &cobra.Command{
|
14 |
+
Use: "ls [path]",
|
15 |
+
Short: "List directory contents in the VM",
|
16 |
+
Args: cobra.MaximumNArgs(1),
|
17 |
+
RunE: func(cmd *cobra.Command, args []string) error {
|
18 |
+
path := "/data"
|
19 |
+
if len(args) == 1 {
|
20 |
+
path = args[0]
|
21 |
+
}
|
22 |
+
c := client.New(server)
|
23 |
+
entries, err := c.ListDir(context.Background(), user, path)
|
24 |
+
if err != nil {
|
25 |
+
return err
|
26 |
+
}
|
27 |
+
for _, e := range entries {
|
28 |
+
if e.IsDir {
|
29 |
+
fmt.Println(e.Name + "/")
|
30 |
+
} else {
|
31 |
+
fmt.Println(e.Name)
|
32 |
+
}
|
33 |
+
}
|
34 |
+
return nil
|
35 |
+
},
|
36 |
+
}
|
37 |
+
}
|
cli-go/cmd/rm.go
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package cmd
|
2 |
+
|
3 |
+
import (
|
4 |
+
"context"
|
5 |
+
|
6 |
+
"github.com/spf13/cobra"
|
7 |
+
|
8 |
+
"llm-cli/internal/client"
|
9 |
+
)
|
10 |
+
|
11 |
+
func newRmCmd() *cobra.Command {
|
12 |
+
return &cobra.Command{
|
13 |
+
Use: "rm <path>",
|
14 |
+
Short: "Remove a file or directory in the VM",
|
15 |
+
Args: cobra.ExactArgs(1),
|
16 |
+
RunE: func(cmd *cobra.Command, args []string) error {
|
17 |
+
c := client.New(server)
|
18 |
+
return c.DeleteFile(context.Background(), user, args[0])
|
19 |
+
},
|
20 |
+
}
|
21 |
+
}
|
cli-go/cmd/root.go
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package cmd
|
2 |
+
|
3 |
+
import (
|
4 |
+
"fmt"
|
5 |
+
"os"
|
6 |
+
|
7 |
+
"github.com/spf13/cobra"
|
8 |
+
)
|
9 |
+
|
10 |
+
var (
|
11 |
+
server string
|
12 |
+
user string
|
13 |
+
)
|
14 |
+
|
15 |
+
func NewRootCmd() *cobra.Command {
|
16 |
+
cmd := &cobra.Command{
|
17 |
+
Use: "llmcli",
|
18 |
+
Short: "CLI client for the LLM backend",
|
19 |
+
}
|
20 |
+
|
21 |
+
cmd.PersistentFlags().StringVarP(&server, "server", "s", "http://localhost:8000", "API server URL")
|
22 |
+
cmd.PersistentFlags().StringVarP(&user, "user", "u", "default", "User name")
|
23 |
+
|
24 |
+
cmd.AddCommand(newChatCmd())
|
25 |
+
cmd.AddCommand(newUploadCmd())
|
26 |
+
cmd.AddCommand(newLsCmd())
|
27 |
+
cmd.AddCommand(newCatCmd())
|
28 |
+
cmd.AddCommand(newWriteCmd())
|
29 |
+
cmd.AddCommand(newRmCmd())
|
30 |
+
|
31 |
+
return cmd
|
32 |
+
}
|
33 |
+
|
34 |
+
func Execute() {
|
35 |
+
if err := NewRootCmd().Execute(); err != nil {
|
36 |
+
fmt.Fprintln(os.Stderr, err)
|
37 |
+
os.Exit(1)
|
38 |
+
}
|
39 |
+
}
|
cli-go/cmd/upload.go
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package cmd
|
2 |
+
|
3 |
+
import (
|
4 |
+
"context"
|
5 |
+
"fmt"
|
6 |
+
|
7 |
+
"github.com/spf13/cobra"
|
8 |
+
|
9 |
+
"llm-cli/internal/client"
|
10 |
+
)
|
11 |
+
|
12 |
+
func newUploadCmd() *cobra.Command {
|
13 |
+
return &cobra.Command{
|
14 |
+
Use: "upload [file]",
|
15 |
+
Short: "Upload a document to the VM",
|
16 |
+
Args: cobra.ExactArgs(1),
|
17 |
+
RunE: func(cmd *cobra.Command, args []string) error {
|
18 |
+
ctx := context.Background()
|
19 |
+
c := client.New(server)
|
20 |
+
path, err := c.UploadDocument(ctx, user, "default", args[0])
|
21 |
+
if err != nil {
|
22 |
+
return err
|
23 |
+
}
|
24 |
+
fmt.Println("Uploaded to", path)
|
25 |
+
return nil
|
26 |
+
},
|
27 |
+
}
|
28 |
+
}
|
cli-go/cmd/write.go
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package cmd
|
2 |
+
|
3 |
+
import (
|
4 |
+
"context"
|
5 |
+
"os"
|
6 |
+
|
7 |
+
"github.com/spf13/cobra"
|
8 |
+
|
9 |
+
"llm-cli/internal/client"
|
10 |
+
)
|
11 |
+
|
12 |
+
func newWriteCmd() *cobra.Command {
|
13 |
+
return &cobra.Command{
|
14 |
+
Use: "write <path> <file>",
|
15 |
+
Short: "Write a local file to a path in the VM",
|
16 |
+
Args: cobra.ExactArgs(2),
|
17 |
+
RunE: func(cmd *cobra.Command, args []string) error {
|
18 |
+
data, err := os.ReadFile(args[1])
|
19 |
+
if err != nil {
|
20 |
+
return err
|
21 |
+
}
|
22 |
+
c := client.New(server)
|
23 |
+
return c.WriteFile(context.Background(), user, args[0], string(data))
|
24 |
+
},
|
25 |
+
}
|
26 |
+
}
|
cli-go/go.mod
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
module llm-cli
|
2 |
+
|
3 |
+
go 1.23.8
|
4 |
+
|
5 |
+
require (
|
6 |
+
github.com/fatih/color v1.16.0
|
7 |
+
github.com/spf13/cobra v1.7.0
|
8 |
+
)
|
9 |
+
|
10 |
+
require (
|
11 |
+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
12 |
+
github.com/mattn/go-colorable v0.1.13 // indirect
|
13 |
+
github.com/mattn/go-isatty v0.0.20 // indirect
|
14 |
+
github.com/spf13/pflag v1.0.5 // indirect
|
15 |
+
golang.org/x/sys v0.14.0 // indirect
|
16 |
+
)
|
cli-go/go.sum
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
2 |
+
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
3 |
+
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
4 |
+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
5 |
+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
6 |
+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
7 |
+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
8 |
+
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
9 |
+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
10 |
+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
11 |
+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
12 |
+
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
13 |
+
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
14 |
+
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
15 |
+
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
16 |
+
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
17 |
+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
18 |
+
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
19 |
+
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
20 |
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
21 |
+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
cli-go/internal/client/client.go
ADDED
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package client
|
2 |
+
|
3 |
+
import (
|
4 |
+
"bytes"
|
5 |
+
"context"
|
6 |
+
"encoding/json"
|
7 |
+
"fmt"
|
8 |
+
"io"
|
9 |
+
"mime/multipart"
|
10 |
+
"net/http"
|
11 |
+
"os"
|
12 |
+
"path/filepath"
|
13 |
+
"time"
|
14 |
+
)
|
15 |
+
|
16 |
+
type Client struct {
|
17 |
+
baseURL string
|
18 |
+
httpClient *http.Client
|
19 |
+
}
|
20 |
+
|
21 |
+
func New(baseURL string) *Client {
|
22 |
+
return &Client{
|
23 |
+
baseURL: baseURL,
|
24 |
+
httpClient: &http.Client{Timeout: 30 * time.Second},
|
25 |
+
}
|
26 |
+
}
|
27 |
+
|
28 |
+
type SessionInfo struct {
|
29 |
+
Sessions []string `json:"sessions"`
|
30 |
+
}
|
31 |
+
|
32 |
+
func (c *Client) ListSessions(ctx context.Context, user string) ([]string, error) {
|
33 |
+
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
|
34 |
+
fmt.Sprintf("%s/sessions/%s", c.baseURL, user), nil)
|
35 |
+
if err != nil {
|
36 |
+
return nil, err
|
37 |
+
}
|
38 |
+
resp, err := c.httpClient.Do(req)
|
39 |
+
if err != nil {
|
40 |
+
return nil, err
|
41 |
+
}
|
42 |
+
defer resp.Body.Close()
|
43 |
+
if resp.StatusCode != http.StatusOK {
|
44 |
+
return nil, fmt.Errorf("list sessions failed: %s", resp.Status)
|
45 |
+
}
|
46 |
+
var data SessionInfo
|
47 |
+
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
48 |
+
return nil, err
|
49 |
+
}
|
50 |
+
return data.Sessions, nil
|
51 |
+
}
|
52 |
+
|
53 |
+
type ChatRequest struct {
|
54 |
+
User string `json:"user"`
|
55 |
+
Session string `json:"session"`
|
56 |
+
Prompt string `json:"prompt"`
|
57 |
+
}
|
58 |
+
|
59 |
+
func (c *Client) ChatStream(ctx context.Context, user, session, prompt string) (io.ReadCloser, error) {
|
60 |
+
body, err := json.Marshal(ChatRequest{User: user, Session: session, Prompt: prompt})
|
61 |
+
if err != nil {
|
62 |
+
return nil, err
|
63 |
+
}
|
64 |
+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/chat/stream", bytes.NewReader(body))
|
65 |
+
if err != nil {
|
66 |
+
return nil, err
|
67 |
+
}
|
68 |
+
req.Header.Set("Content-Type", "application/json")
|
69 |
+
resp, err := c.httpClient.Do(req)
|
70 |
+
if err != nil {
|
71 |
+
return nil, err
|
72 |
+
}
|
73 |
+
if resp.StatusCode != http.StatusOK {
|
74 |
+
defer resp.Body.Close()
|
75 |
+
b, _ := io.ReadAll(resp.Body)
|
76 |
+
return nil, fmt.Errorf("chat failed: %s - %s", resp.Status, string(b))
|
77 |
+
}
|
78 |
+
return resp.Body, nil
|
79 |
+
}
|
80 |
+
|
81 |
+
type UploadResp struct {
|
82 |
+
Path string `json:"path"`
|
83 |
+
}
|
84 |
+
|
85 |
+
func (c *Client) UploadDocument(ctx context.Context, user, session, path string) (string, error) {
|
86 |
+
file, err := os.Open(path)
|
87 |
+
if err != nil {
|
88 |
+
return "", err
|
89 |
+
}
|
90 |
+
defer file.Close()
|
91 |
+
|
92 |
+
buf := &bytes.Buffer{}
|
93 |
+
writer := multipart.NewWriter(buf)
|
94 |
+
_ = writer.WriteField("user", user)
|
95 |
+
_ = writer.WriteField("session", session)
|
96 |
+
fw, err := writer.CreateFormFile("file", filepath.Base(path))
|
97 |
+
if err != nil {
|
98 |
+
return "", err
|
99 |
+
}
|
100 |
+
if _, err = io.Copy(fw, file); err != nil {
|
101 |
+
return "", err
|
102 |
+
}
|
103 |
+
writer.Close()
|
104 |
+
|
105 |
+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/upload", buf)
|
106 |
+
if err != nil {
|
107 |
+
return "", err
|
108 |
+
}
|
109 |
+
req.Header.Set("Content-Type", writer.FormDataContentType())
|
110 |
+
|
111 |
+
resp, err := c.httpClient.Do(req)
|
112 |
+
if err != nil {
|
113 |
+
return "", err
|
114 |
+
}
|
115 |
+
defer resp.Body.Close()
|
116 |
+
if resp.StatusCode != http.StatusOK {
|
117 |
+
b, _ := io.ReadAll(resp.Body)
|
118 |
+
return "", fmt.Errorf("upload failed: %s - %s", resp.Status, string(b))
|
119 |
+
}
|
120 |
+
var out UploadResp
|
121 |
+
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
122 |
+
return "", err
|
123 |
+
}
|
124 |
+
return out.Path, nil
|
125 |
+
}
|
126 |
+
|
127 |
+
type DirEntry struct {
|
128 |
+
Name string `json:"name"`
|
129 |
+
IsDir bool `json:"is_dir"`
|
130 |
+
}
|
131 |
+
|
132 |
+
type DirList struct {
|
133 |
+
Entries []DirEntry `json:"entries"`
|
134 |
+
}
|
135 |
+
|
136 |
+
func (c *Client) ListDir(ctx context.Context, user, path string) ([]DirEntry, error) {
|
137 |
+
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
|
138 |
+
fmt.Sprintf("%s/vm/%s/list?path=%s", c.baseURL, user, path), nil)
|
139 |
+
if err != nil {
|
140 |
+
return nil, err
|
141 |
+
}
|
142 |
+
resp, err := c.httpClient.Do(req)
|
143 |
+
if err != nil {
|
144 |
+
return nil, err
|
145 |
+
}
|
146 |
+
defer resp.Body.Close()
|
147 |
+
if resp.StatusCode != http.StatusOK {
|
148 |
+
return nil, fmt.Errorf("list dir failed: %s", resp.Status)
|
149 |
+
}
|
150 |
+
var out DirList
|
151 |
+
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
152 |
+
return nil, err
|
153 |
+
}
|
154 |
+
return out.Entries, nil
|
155 |
+
}
|
156 |
+
|
157 |
+
type FileContent struct {
|
158 |
+
Content string `json:"content"`
|
159 |
+
}
|
160 |
+
|
161 |
+
func (c *Client) ReadFile(ctx context.Context, user, path string) (string, error) {
|
162 |
+
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
|
163 |
+
fmt.Sprintf("%s/vm/%s/file?path=%s", c.baseURL, user, path), nil)
|
164 |
+
if err != nil {
|
165 |
+
return "", err
|
166 |
+
}
|
167 |
+
resp, err := c.httpClient.Do(req)
|
168 |
+
if err != nil {
|
169 |
+
return "", err
|
170 |
+
}
|
171 |
+
defer resp.Body.Close()
|
172 |
+
if resp.StatusCode != http.StatusOK {
|
173 |
+
return "", fmt.Errorf("read file failed: %s", resp.Status)
|
174 |
+
}
|
175 |
+
var out FileContent
|
176 |
+
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
177 |
+
return "", err
|
178 |
+
}
|
179 |
+
return out.Content, nil
|
180 |
+
}
|
181 |
+
|
182 |
+
func (c *Client) WriteFile(ctx context.Context, user, path, content string) error {
|
183 |
+
data, _ := json.Marshal(map[string]string{"path": path, "content": content})
|
184 |
+
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
|
185 |
+
fmt.Sprintf("%s/vm/%s/file", c.baseURL, user), bytes.NewReader(data))
|
186 |
+
if err != nil {
|
187 |
+
return err
|
188 |
+
}
|
189 |
+
req.Header.Set("Content-Type", "application/json")
|
190 |
+
resp, err := c.httpClient.Do(req)
|
191 |
+
if err != nil {
|
192 |
+
return err
|
193 |
+
}
|
194 |
+
defer resp.Body.Close()
|
195 |
+
if resp.StatusCode != http.StatusOK {
|
196 |
+
b, _ := io.ReadAll(resp.Body)
|
197 |
+
return fmt.Errorf("write file failed: %s - %s", resp.Status, string(b))
|
198 |
+
}
|
199 |
+
return nil
|
200 |
+
}
|
201 |
+
|
202 |
+
func (c *Client) DeleteFile(ctx context.Context, user, path string) error {
|
203 |
+
req, err := http.NewRequestWithContext(ctx, http.MethodDelete,
|
204 |
+
fmt.Sprintf("%s/vm/%s/file?path=%s", c.baseURL, user, path), nil)
|
205 |
+
if err != nil {
|
206 |
+
return err
|
207 |
+
}
|
208 |
+
resp, err := c.httpClient.Do(req)
|
209 |
+
if err != nil {
|
210 |
+
return err
|
211 |
+
}
|
212 |
+
defer resp.Body.Close()
|
213 |
+
if resp.StatusCode != http.StatusOK {
|
214 |
+
b, _ := io.ReadAll(resp.Body)
|
215 |
+
return fmt.Errorf("delete file failed: %s - %s", resp.Status, string(b))
|
216 |
+
}
|
217 |
+
return nil
|
218 |
+
}
|
cli-go/main.go
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package main
|
2 |
+
|
3 |
+
import "llm-cli/cmd"
|
4 |
+
|
5 |
+
func main() {
|
6 |
+
cmd.Execute()
|
7 |
+
}
|