-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial commit of the functional CLI tool
- Loading branch information
Showing
4 changed files
with
211 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# Kagi FastGPT CLI | ||
|
||
`kagi` is a quick-and-dirty CLI for querying the [Kagi search engine](https://kagi.com/) with their [FastGPT API](https://help.kagi.com/kagi/api/fastgpt.html) | ||
|
||
## Installation | ||
|
||
First, build + install the `kagi` CLI with: | ||
|
||
``` | ||
go install github.com/bcspragu/kagi/cmd/kagi@latest | ||
``` | ||
|
||
Then, create an API key + add API credits, following [the official Kagi instructions](https://help.kagi.com/kagi/api/fastgpt.html#quick-start). Add the API key to your `~/.bashrc` or equivalent with something like: | ||
|
||
``` | ||
export KAGI_API_KEY=... | ||
``` | ||
|
||
And you should be good to go! Try running `kagi <query>` to test it out. | ||
|
||
As an aside, I'm **really** not a fan of storing sensitive credentials in accessible-to-everything-all-the-time environment variables, and if anyone has a good + ergonomic alternative (e.g. involving `pass` or credential helpers), I'm all ears. | ||
|
||
## Example Output | ||
|
||
``` | ||
$ kagi net/http golang | ||
===== OUTPUT ===== | ||
The net/http package is part of the Go standard library and provides HTTP client and server functionality. [1][2][3] | ||
===== REFERENCES ===== | ||
1. http package - net/http - Go Packages - https://pkg.go.dev/net/http | ||
- Package http provides HTTP client and server implementations. ... Manually configuring HTTP/2 via the golang.org/x/net/http2 package takes precedence over... | ||
2. Writing Web Applications - The Go Programming Language - https://go.dev/doc/articles/wiki/ | ||
- Covered in this tutorial: Creating a data structure with load and save methods; Using the net/http package to build web applications; Using the html/... | ||
3. How To Make an HTTP Server in Go | DigitalOcean - https://www.digitalocean.com/community/tutorials/how-to-make-an-http-server-in-go | ||
- Apr 21, 2022 ... The net/http package not only includes the ability to make HTTP requests, but also provides an HTTP server you can use to handle those requests. | ||
``` | ||
|
||
## Troubleshooting | ||
|
||
If the `kagi` tool isn't working for you, make sure: | ||
|
||
- You've reloaded your `~/.bashrc` or equivalent after adding your API key, e.g. with `source ~/.bashrc` or opening a new shell. | ||
- Run `echo $KAGI_API_KEY` to confirm it's set in the current shell | ||
- You've 'topped up' your API credits | ||
- Confirm there's a non-zero 'Credit remaining' balance in [the Kagi billing UI](https://kagi.com/settings?p=billing_api) | ||
|
||
If you're still having issues after that, feel free to file an issue, including any error output. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// Package api provides a Kagi API client. | ||
package api | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
) | ||
|
||
type Client struct { | ||
http *http.Client | ||
} | ||
|
||
func NewClient(tkn string) *Client { | ||
return &Client{ | ||
http: &http.Client{ | ||
Transport: &roundTripper{tkn: tkn}, | ||
}, | ||
} | ||
} | ||
|
||
type FastGPTResponse struct { | ||
Meta FastGPTResponseMeta `json:"meta"` | ||
Data FastGPTResponseData `json:"data"` | ||
Errors []FastGPTResponseError `json:"error"` | ||
} | ||
|
||
type FastGPTResponseMeta struct { | ||
ID string `json:"id"` | ||
Node string `json:"node"` | ||
Milliseconds int `json:"ms"` | ||
} | ||
|
||
type FastGPTResponseData struct { | ||
Output string `json:"output"` | ||
Tokens int `json:"tokens"` | ||
References []FastGPTResponseReference `json:"references"` | ||
} | ||
|
||
type FastGPTResponseReference struct { | ||
Title string `json:"title"` | ||
Snippet string `json:"snippet"` | ||
Link string `json:"url"` | ||
} | ||
|
||
type FastGPTResponseError struct { | ||
Code int `json:"code"` | ||
Message string `json:"msg"` | ||
// There's also "ref", but it was null so I don't know its type> | ||
} | ||
|
||
type FastGPTRequest struct { | ||
Query string `json:"query"` | ||
WebSearch bool `json:"web_search"` | ||
Cache bool `json:"cache"` | ||
} | ||
|
||
func (c *Client) QueryFastGPT(query string) (*FastGPTResponse, error) { | ||
var buf bytes.Buffer | ||
req := FastGPTRequest{ | ||
Query: query, | ||
WebSearch: true, | ||
Cache: true, | ||
} | ||
if err := json.NewEncoder(&buf).Encode(req); err != nil { | ||
return nil, fmt.Errorf("failed to encode request: %w", err) | ||
} | ||
resp, err := c.http.Post("https://kagi.com/api/v0/fastgpt", "application/json", &buf) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to query FastGPT API: %w", err) | ||
} | ||
defer resp.Body.Close() | ||
|
||
if resp.StatusCode != http.StatusOK { | ||
body, _ := ioutil.ReadAll(resp.Body) | ||
return nil, fmt.Errorf("unexpected status code %d, body %q", resp.StatusCode, string(body)) | ||
} | ||
|
||
var gptResp FastGPTResponse | ||
if err := json.NewDecoder(resp.Body).Decode(&gptResp); err != nil { | ||
return nil, fmt.Errorf("failed to decode FastGPT API response body: %w", err) | ||
} | ||
|
||
if len(gptResp.Errors) > 0 { | ||
e := gptResp.Errors[0] | ||
return nil, fmt.Errorf("received %d error(s) from the API: [%d] %q", len(gptResp.Errors), e.Code, e.Message) | ||
} | ||
|
||
return &gptResp, nil | ||
} | ||
|
||
type roundTripper struct { | ||
tkn string | ||
} | ||
|
||
func (rt *roundTripper) RoundTrip(r *http.Request) (*http.Response, error) { | ||
r.Header.Add("Authorization", "Bot "+rt.tkn) | ||
return http.DefaultTransport.RoundTrip(r) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"log" | ||
"os" | ||
"strings" | ||
|
||
"github.com/bcspragu/kagi/api" | ||
) | ||
|
||
func main() { | ||
if err := run(os.Args); err != nil { | ||
log.Fatal(err) | ||
} | ||
} | ||
|
||
func run(args []string) error { | ||
var query string | ||
switch len(args) { | ||
case 0, 1: | ||
return errors.New("usage: kagi <query>") | ||
case 2: | ||
query = args[1] | ||
default: | ||
query = strings.Join(args[1:], " ") | ||
} | ||
if len(args) < 2 { | ||
} | ||
apiKey := os.Getenv("KAGI_API_KEY") | ||
if apiKey == "" { | ||
return errors.New("no KAGI_API_KEY was set") | ||
} | ||
|
||
client := api.NewClient(apiKey) | ||
|
||
resp, err := client.QueryFastGPT(query) | ||
if err != nil { | ||
return fmt.Errorf("error performing query: %w", err) | ||
} | ||
|
||
fmt.Println("===== OUTPUT =====") | ||
fmt.Println() | ||
fmt.Println(resp.Data.Output) | ||
fmt.Println() | ||
fmt.Println("===== REFERENCES =====") | ||
fmt.Println() | ||
|
||
for i, ref := range resp.Data.References { | ||
fmt.Printf("%d. %s - %s\n - %s\n\n", i+1, ref.Title, ref.Link, ref.Snippet) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module github.com/bcspragu/kagi | ||
|
||
go 1.20 |