Skip to content

Commit 3ccb0b6

Browse files
authored
Merge pull request #15 from mutablelogic/v1
Claude and Tablewriter improvements
2 parents 367fa6d + ee20129 commit 3ccb0b6

File tree

16 files changed

+271
-39
lines changed

16 files changed

+271
-39
lines changed

cmd/api/anthropic.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/djthorpe/go-tablewriter"
8+
"github.com/mutablelogic/go-client"
9+
"github.com/mutablelogic/go-client/pkg/anthropic"
10+
)
11+
12+
///////////////////////////////////////////////////////////////////////////////
13+
// GLOBALS
14+
15+
var (
16+
anthropicName = "claude"
17+
anthropicClient *anthropic.Client
18+
)
19+
20+
///////////////////////////////////////////////////////////////////////////////
21+
// LIFECYCLE
22+
23+
func anthropicRegister(flags *Flags) {
24+
// Register flags required
25+
flags.String(anthropicName, "anthropic-api-key", "${ANTHROPIC_API_KEY}", "API Key")
26+
27+
flags.Register(Cmd{
28+
Name: anthropicName,
29+
Description: "Interact with Claude, from https://www.anthropic.com/api",
30+
Parse: anthropicParse,
31+
Fn: []Fn{
32+
{Name: "chat", Call: anthropicChat, Description: "Chat with Claude", MinArgs: 1},
33+
},
34+
})
35+
}
36+
37+
func anthropicParse(flags *Flags, opts ...client.ClientOpt) error {
38+
apiKey := flags.GetString("anthropic-api-key")
39+
if apiKey == "" {
40+
return fmt.Errorf("missing -anthropic-api-key flag")
41+
}
42+
if client, err := anthropic.New(flags.GetString("anthropic-api-key"), opts...); err != nil {
43+
return err
44+
} else {
45+
anthropicClient = client
46+
}
47+
48+
// Return success
49+
return nil
50+
}
51+
52+
///////////////////////////////////////////////////////////////////////////////
53+
// METHODS
54+
55+
func anthropicChat(w *tablewriter.Writer, args []string) error {
56+
// Request -> Response
57+
message := anthropic.NewMessage("user", strings.Join(args, " "))
58+
responses, err := anthropicClient.Messages(message)
59+
if err != nil {
60+
return err
61+
}
62+
63+
// Write table
64+
return w.Write(responses)
65+
}

cmd/api/bitwarden.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ import (
1919
// TYPES
2020

2121
type bwCipher struct {
22-
Name string
23-
Username string
24-
URI string
25-
Folder string
22+
Name string `json:"name,wrap"`
23+
Username string `json:"username,width:30"`
24+
URI string `json:"uri,width:40"`
25+
Folder string `json:"folder,width:36"`
2626
}
2727

2828
///////////////////////////////////////////////////////////////////////////////
@@ -99,7 +99,7 @@ func bwParse(flags *Flags, opts ...client.ClientOpt) error {
9999
///////////////////////////////////////////////////////////////////////////////
100100
// API METHODS
101101

102-
func bwAuth(w *tablewriter.TableWriter, _ []string) error {
102+
func bwAuth(w *tablewriter.Writer, _ []string) error {
103103
// Load session or create a new one
104104
session, err := bwReadSession()
105105
if err != nil {
@@ -136,7 +136,7 @@ func bwAuth(w *tablewriter.TableWriter, _ []string) error {
136136
return nil
137137
}
138138

139-
func bwSync(w *tablewriter.TableWriter, _ []string) error {
139+
func bwSync(w *tablewriter.Writer, _ []string) error {
140140
// Load session or create a new one
141141
session, err := bwReadSession()
142142
if err != nil {
@@ -167,7 +167,7 @@ func bwSync(w *tablewriter.TableWriter, _ []string) error {
167167
return nil
168168
}
169169

170-
func bwFolders(w *tablewriter.TableWriter, _ []string) error {
170+
func bwFolders(w *tablewriter.Writer, _ []string) error {
171171
// Load the profile
172172
profile, err := bwReadProfile()
173173
if err != nil {
@@ -214,7 +214,7 @@ func bwFolders(w *tablewriter.TableWriter, _ []string) error {
214214
return nil
215215
}
216216

217-
func bwLogins(w *tablewriter.TableWriter, _ []string) error {
217+
func bwLogins(w *tablewriter.Writer, _ []string) error {
218218
// Load the profile
219219
profile, err := bwReadProfile()
220220
if err != nil {
@@ -269,7 +269,7 @@ func bwLogins(w *tablewriter.TableWriter, _ []string) error {
269269
return nil
270270
}
271271

272-
func bwGetPassword(w *tablewriter.TableWriter, _ []string) error {
272+
func bwGetPassword(w *tablewriter.Writer, _ []string) error {
273273
return ErrNotImplemented
274274
}
275275

cmd/api/cmd.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type Fn struct {
2222
Description string
2323
MinArgs uint
2424
MaxArgs uint
25-
Call func(*tablewriter.TableWriter, []string) error
25+
Call func(*tablewriter.Writer, []string) error
2626
}
2727

2828
///////////////////////////////////////////////////////////////////////////////
@@ -48,7 +48,8 @@ func (c *Cmd) Get(args []string) (*Fn, []string, error) {
4848
// Check number of arguments
4949
if fn.MinArgs != 0 && nargs < fn.MinArgs {
5050
return nil, nil, fmt.Errorf("not enough arguments for %q", fn.Name)
51-
} else if nargs > fn.MaxArgs {
51+
}
52+
if fn.MaxArgs != 0 && nargs > fn.MaxArgs {
5253
return nil, nil, fmt.Errorf("too many arguments for %q", fn.Name)
5354
}
5455

cmd/api/flags.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,12 @@ func (flags *Flags) Parse(args []string) error {
9090
}
9191

9292
// Parse the commands
93-
for _, cmd := range flags.cmds {
94-
if err := cmd.Parse(flags, opts...); err != nil {
95-
fmt.Fprintf(os.Stderr, "%v: %v\n", cmd.Name, err)
96-
return err
93+
if flags.NArg() > 0 {
94+
if cmd := flags.GetCommandSet(flags.Arg(0)); cmd != nil {
95+
if err := cmd.Parse(flags, opts...); err != nil {
96+
fmt.Fprintf(os.Stderr, "%v: %v\n", cmd.Name, err)
97+
return err
98+
}
9799
}
98100
}
99101

cmd/api/ipify.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func ipifyParse(flags *Flags, opts ...client.ClientOpt) error {
3131
return nil
3232
}
3333

34-
func ipifyGetAddress(w *tablewriter.TableWriter, _ []string) error {
34+
func ipifyGetAddress(w *tablewriter.Writer, _ []string) error {
3535
addr, err := ipifyClient.Get()
3636
if err != nil {
3737
return err

cmd/api/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ func main() {
1717
ipifyRegister(flags)
1818
bwRegister(flags)
1919
newsapiRegister(flags)
20+
anthropicRegister(flags)
2021

2122
// Parse
2223
if err := flags.Parse(os.Args[1:]); errors.Is(err, ErrHelp) {

cmd/api/newsapi.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func inewsapiParse(flags *Flags, opts ...client.ClientOpt) error {
6666
///////////////////////////////////////////////////////////////////////////////
6767
// METHODS
6868

69-
func newsapiSources(w *tablewriter.TableWriter, _ []string) error {
69+
func newsapiSources(w *tablewriter.Writer, _ []string) error {
7070
// Set options
7171
opts := []newsapi.Opt{}
7272
if newsapiCategory != "" {
@@ -89,7 +89,7 @@ func newsapiSources(w *tablewriter.TableWriter, _ []string) error {
8989
return w.Write(sources)
9090
}
9191

92-
func newsapiHeadlines(w *tablewriter.TableWriter, _ []string) error {
92+
func newsapiHeadlines(w *tablewriter.Writer, _ []string) error {
9393
// Set options
9494
opts := []newsapi.Opt{}
9595
if newsapiCategory != "" {
@@ -112,7 +112,7 @@ func newsapiHeadlines(w *tablewriter.TableWriter, _ []string) error {
112112
return w.Write(articles)
113113
}
114114

115-
func newsapiArticles(w *tablewriter.TableWriter, args []string) error {
115+
func newsapiArticles(w *tablewriter.Writer, args []string) error {
116116
// Set options
117117
opts := []newsapi.Opt{}
118118
if newsapiCategory != "" {

pkg/anthropic/client.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
anthropic implements an API client for anthropic (https://docs.anthropic.com/en/api/getting-started)
3+
*/
4+
package anthropic
5+
6+
import (
7+
// Packages
8+
"github.com/mutablelogic/go-client"
9+
)
10+
11+
///////////////////////////////////////////////////////////////////////////////
12+
// TYPES
13+
14+
type Client struct {
15+
*client.Client
16+
}
17+
18+
///////////////////////////////////////////////////////////////////////////////
19+
// GLOBALS
20+
21+
const (
22+
endPoint = "https://api.anthropic.com/v1"
23+
defaultVersion = "2023-06-01"
24+
defaultMessageModel = "claude-3-haiku-20240307"
25+
defaultMaxTokens = 4096
26+
)
27+
28+
///////////////////////////////////////////////////////////////////////////////
29+
// LIFECYCLE
30+
31+
// Create a new client
32+
func New(ApiKey string, opts ...client.ClientOpt) (*Client, error) {
33+
// Create client
34+
opts = append(opts, client.OptEndpoint(endPoint))
35+
opts = append(opts, client.OptHeader("x-api-key", ApiKey), client.OptHeader("anthropic-version", defaultVersion))
36+
client, err := client.New(opts...)
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
// Return the client
42+
return &Client{client}, nil
43+
}

pkg/anthropic/client_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package anthropic_test
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
// Packages
8+
opts "github.com/mutablelogic/go-client"
9+
anthropic "github.com/mutablelogic/go-client/pkg/anthropic"
10+
assert "github.com/stretchr/testify/assert"
11+
)
12+
13+
func Test_client_001(t *testing.T) {
14+
assert := assert.New(t)
15+
client, err := anthropic.New(GetApiKey(t), opts.OptTrace(os.Stderr, true))
16+
assert.NoError(err)
17+
assert.NotNil(client)
18+
t.Log(client)
19+
}
20+
21+
///////////////////////////////////////////////////////////////////////////////
22+
// ENVIRONMENT
23+
24+
func GetApiKey(t *testing.T) string {
25+
key := os.Getenv("ANTHROPIC_API_KEY")
26+
if key == "" {
27+
t.Skip("ANTHROPIC_API_KEY not set")
28+
t.SkipNow()
29+
}
30+
return key
31+
}

pkg/anthropic/message.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package anthropic
2+
3+
import (
4+
// Packages
5+
"github.com/mutablelogic/go-client"
6+
schema "github.com/mutablelogic/go-client/pkg/openai/schema"
7+
)
8+
9+
///////////////////////////////////////////////////////////////////////////////
10+
// TYPES
11+
12+
type reqMessage struct {
13+
Model string `json:"model"`
14+
Messages []schema.Message `json:"messages,omitempty"`
15+
MaxTokens int `json:"max_tokens,omitempty"`
16+
}
17+
18+
type respMessage struct {
19+
Id string `json:"id"`
20+
Type string `json:"type,omitempty"`
21+
Role string `json:"role"`
22+
Model string `json:"model"`
23+
StopReason string `json:"stop_reason"`
24+
StopSequence string `json:"stop_sequence"`
25+
Content []schema.Content `json:"content"`
26+
Usage struct {
27+
InputTokens int `json:"input_tokens"`
28+
OuputTokens int `json:"output_tokens"`
29+
} `json:"usage"`
30+
}
31+
32+
///////////////////////////////////////////////////////////////////////////////
33+
// PUBLIC METHODS
34+
35+
func NewMessage(role, text string) schema.Message {
36+
return schema.Message{
37+
Role: role,
38+
Content: text,
39+
}
40+
}
41+
42+
///////////////////////////////////////////////////////////////////////////////
43+
// API CALLS
44+
45+
// Send a structured list of input messages with text and/or image content,
46+
// and the model will generate the next message in the conversation.
47+
func (c *Client) Messages(message schema.Message) ([]schema.Content, error) {
48+
var request reqMessage
49+
var response respMessage
50+
51+
request.Model = defaultMessageModel
52+
request.MaxTokens = defaultMaxTokens
53+
request.Messages = []schema.Message{message}
54+
55+
// Return the response
56+
if payload, err := client.NewJSONRequest(request); err != nil {
57+
return nil, err
58+
} else if err := c.Do(payload, &response, client.OptPath("messages")); err != nil {
59+
return nil, err
60+
} else {
61+
return response.Content, nil
62+
}
63+
}

0 commit comments

Comments
 (0)