Commit 840fab5

bryfry <bryon@fryer.io>
2025-08-09 06:58:42
init
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
+}