Commit 840fab5
2025-08-09 06:58:42
Changed files (4)
go.mod
@@ -0,0 +1,13 @@
+module 8b
+
+go 1.24.2
+
+require (
+ github.com/sashabaranov/go-openai v1.40.5
+ github.com/spf13/cobra v1.9.1
+)
+
+require (
+ github.com/inconshreveable/mousetrap v1.1.0 // indirect
+ github.com/spf13/pflag v1.0.6 // indirect
+)
go.sum
@@ -0,0 +1,12 @@
+github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/sashabaranov/go-openai v1.40.5 h1:SwIlNdWflzR1Rxd1gv3pUg6pwPc6cQ2uMoHs8ai+/NY=
+github.com/sashabaranov/go-openai v1.40.5/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
+github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
+github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
+github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
+github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
main.go
@@ -0,0 +1,78 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+
+ "github.com/spf13/cobra"
+)
+
+type M8B interface {
+ Complete(ctx context.Context, prompt string) (<-chan string, error)
+}
+
+func Execute() error {
+ root := &cobra.Command{
+ Use: "8b",
+ RunE: m8b,
+ }
+
+ return root.Execute()
+}
+
+func m8b(cmd *cobra.Command, args []string) error {
+
+ var prompt string
+ stat, err := os.Stdin.Stat()
+ if err != nil {
+ err := fmt.Errorf("reading stdin: %w", err)
+ return err
+ }
+ prompt = strings.Join(args, " ")
+ if (stat.Mode() & os.ModeCharDevice) == 0 {
+ // stdin is being piped
+ bytes, err := io.ReadAll(os.Stdin)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "Error reading stdin:", err)
+ os.Exit(1)
+ }
+ prompt = string(bytes)
+ }
+
+ client, err := NewOpenAIClient("gpt-4o")
+ if err != nil {
+ err := fmt.Errorf("setting up client: %w", err)
+ return err
+ }
+
+ ctx := context.TODO()
+ respChan, err := client.Complete(ctx, prompt)
+
+ if err != nil {
+ err := fmt.Errorf("starting client: %w", err)
+ return err
+ }
+
+ for {
+ select {
+ case <-ctx.Done():
+ return nil
+ case token, ok := <-respChan:
+ if !ok {
+ fmt.Println()
+ return nil
+ }
+ fmt.Print(token)
+ }
+ }
+}
+
+func main() {
+ err := Execute()
+ if err != nil {
+ os.Exit(1)
+ }
+}
openai_m8b.go
@@ -0,0 +1,64 @@
+package main
+
+import (
+ "context"
+ "errors"
+ "os"
+
+ openai "github.com/sashabaranov/go-openai"
+)
+
+type OpenAIClient struct {
+ client *openai.Client
+ model string
+}
+
+var (
+ ErrNoApiKey = errors.New("no api key set")
+)
+
+func NewOpenAIClient(model string) (M8B, error) {
+ apiKey, apiKeyFound := os.LookupEnv("OPENAI_API_KEY")
+ if !apiKeyFound {
+ return nil, ErrNoApiKey
+ }
+ return &OpenAIClient{
+ client: openai.NewClient(apiKey),
+ model: model,
+ }, nil
+}
+
+func (o *OpenAIClient) Complete(ctx context.Context, prompt string) (<-chan string, error) {
+ out := make(chan string)
+
+ req := openai.ChatCompletionRequest{
+ Model: o.model,
+ Messages: []openai.ChatCompletionMessage{
+ {Role: openai.ChatMessageRoleUser, Content: prompt},
+ },
+ Stream: true,
+ }
+
+ stream, err := o.client.CreateChatCompletionStream(ctx, req)
+ if err != nil {
+ close(out)
+ return nil, err
+ }
+
+ go func() {
+ defer stream.Close()
+ defer close(out)
+
+ for {
+ resp, err := stream.Recv()
+ if err != nil {
+ return
+ }
+ if len(resp.Choices) > 0 {
+ out <- resp.Choices[0].Delta.Content
+ }
+ }
+ }()
+
+ return out, nil
+}