Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion cli/tapes/main.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package main

import (
"fmt"
"os"

tapescmder "github.com/papercomputeco/tapes/cmd/tapes"
)

func main() {
cmd := tapescmder.NewTapesCmd()
if err := cmd.Execute(); err != nil {
err := cmd.Execute()
if err != nil {
fmt.Printf("Error executing root command: %v", err)
os.Exit(1)
}
}
7 changes: 6 additions & 1 deletion cli/tapesapi/main.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package main

import (
"fmt"
"os"

apicmder "github.com/papercomputeco/tapes/cmd/tapes/serve/api"
)

func main() {
cmd := apicmder.NewAPICmd()

cmd.Use = "tapesapi"
cmd.PersistentFlags().BoolP("debug", "d", false, "Enable debug logging")
cmd.PersistentFlags().String("config-dir", "", "Override path to .tapes/ config directory")

if err := cmd.Execute(); err != nil {
err := cmd.Execute()
if err != nil {
fmt.Printf("Error executing root command: %v", err)
os.Exit(1)
}
}
7 changes: 6 additions & 1 deletion cli/tapesprox/main.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package main

import (
"fmt"
"os"

proxycmder "github.com/papercomputeco/tapes/cmd/tapes/serve/proxy"
)

func main() {
cmd := proxycmder.NewProxyCmd()

cmd.Use = "tapesproxy"
cmd.PersistentFlags().BoolP("debug", "d", false, "Enable debug logging")
cmd.PersistentFlags().String("config-dir", "", "Override path to .tapes/ config directory")

if err := cmd.Execute(); err != nil {
err := cmd.Execute()
if err != nil {
fmt.Printf("Error executing root command: %v", err)
os.Exit(1)
}
}
42 changes: 32 additions & 10 deletions cmd/tapes/chat/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ import (
"github.com/spf13/cobra"
"go.uber.org/zap"

"github.com/papercomputeco/tapes/pkg/config"
"github.com/papercomputeco/tapes/pkg/dotdir"
"github.com/papercomputeco/tapes/pkg/logger"
"github.com/papercomputeco/tapes/pkg/utils"
)

type chatCommander struct {
proxy string
api string
model string
debug bool
proxyTarget string
apiTarget string
model string
debug bool

logger *zap.Logger
}
Expand Down Expand Up @@ -73,7 +74,7 @@ or "tapes checkout" (no hash provided) to clear the checkout and start fresh.

Examples:
tapes chat --model llama3.2
tapes chat --model llama3.2 --proxy http://localhost:8080`
tapes chat --model llama3.2 --proxy-target http://localhost:8080`

const chatShortDesc string = "Experimental: Interactive LLM chat through the tapes proxy"

Expand All @@ -84,6 +85,27 @@ func NewChatCmd() *cobra.Command {
Use: "chat",
Short: chatShortDesc,
Long: chatLongDesc,
PreRunE: func(cmd *cobra.Command, _ []string) error {
configDir, _ := cmd.Flags().GetString("config-dir")
cfger, err := config.NewConfiger(configDir)
if err != nil {
return fmt.Errorf("loading config: %w", err)
}

cfg, err := cfger.LoadConfig()
if err != nil {
return fmt.Errorf("loading config: %w", err)
}

if !cmd.Flags().Changed("api-target") {
cmder.apiTarget = cfg.Client.APITarget
}

if !cmd.Flags().Changed("proxy-target") {
cmder.proxyTarget = cfg.Client.ProxyTarget
}
return nil
},
RunE: func(cmd *cobra.Command, _ []string) error {
var err error
cmder.debug, err = cmd.Flags().GetBool("debug")
Expand All @@ -95,9 +117,9 @@ func NewChatCmd() *cobra.Command {
},
}

cmd.PersistentFlags().StringVarP(&cmder.api, "api", "a", "http://localhost:8081", "Tapes API server address")

cmd.Flags().StringVarP(&cmder.proxy, "proxy", "p", "http://localhost:8080", "Tapes proxy address")
defaults := config.NewDefaultConfig()
cmd.Flags().StringVarP(&cmder.apiTarget, "api-target", "a", defaults.Client.APITarget, "Tapes API server URL")
cmd.Flags().StringVarP(&cmder.proxyTarget, "proxy-target", "p", defaults.Client.ProxyTarget, "Tapes proxy URL")
cmd.Flags().StringVarP(&cmder.model, "model", "m", "gemma3:latest", "Model name (e.g., gemma3:1b, ministral-3:latest)")

return cmd
Expand Down Expand Up @@ -199,13 +221,13 @@ func (c *chatCommander) sendAndStream(messages []ollamaMessage) (string, error)
}

c.logger.Debug("sending chat request",
zap.String("proxy", c.proxy),
zap.String("proxy_target", c.proxyTarget),
zap.String("model", c.model),
zap.Int("message_count", len(messages)),
)

// POST to the proxy's Ollama-compatible chat endpoint
url := c.proxy + "/api/chat"
url := c.proxyTarget + "/api/chat"
httpReq, err := http.NewRequestWithContext(context.Background(), http.MethodPost, url, bytes.NewReader(body))
if err != nil {
return "", fmt.Errorf("creating request: %w", err)
Expand Down
10 changes: 4 additions & 6 deletions cmd/tapes/chat/chat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,16 @@ var _ = Describe("NewChatCmd", func() {
Expect(flag.Shorthand).To(Equal("m"))
})

It("has --proxy flag with default value", func() {
It("has --proxy-target flag", func() {
cmd := chatcmder.NewChatCmd()
flag := cmd.Flags().Lookup("proxy")
flag := cmd.Flags().Lookup("proxy-target")
Expect(flag).NotTo(BeNil())
Expect(flag.DefValue).To(Equal("http://localhost:8080"))
})

It("has persistent --api flag with default value", func() {
It("has --api-target flag", func() {
cmd := chatcmder.NewChatCmd()
flag := cmd.PersistentFlags().Lookup("api")
flag := cmd.Flags().Lookup("api-target")
Expect(flag).NotTo(BeNil())
Expect(flag.DefValue).To(Equal("http://localhost:8081"))
})
})

Expand Down
36 changes: 25 additions & 11 deletions cmd/tapes/checkout/checkout.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@ import (
"github.com/spf13/cobra"
"go.uber.org/zap"

"github.com/papercomputeco/tapes/pkg/config"
"github.com/papercomputeco/tapes/pkg/dotdir"
"github.com/papercomputeco/tapes/pkg/llm"
"github.com/papercomputeco/tapes/pkg/logger"
"github.com/papercomputeco/tapes/pkg/utils"
)

type checkoutCommander struct {
hash string
api string
debug bool
hash string
apiTarget string
debug bool

logger *zap.Logger
}
Expand Down Expand Up @@ -69,6 +70,23 @@ func NewCheckoutCmd() *cobra.Command {
Short: checkoutShortDesc,
Long: checkoutLongDesc,
Args: cobra.MaximumNArgs(1),
PreRunE: func(cmd *cobra.Command, _ []string) error {
configDir, _ := cmd.Flags().GetString("config-dir")
cfger, err := config.NewConfiger(configDir)
if err != nil {
return fmt.Errorf("loading config: %w", err)
}

cfg, err := cfger.LoadConfig()
if err != nil {
return fmt.Errorf("loading config: %w", err)
}

if !cmd.Flags().Changed("api-target") {
cmder.apiTarget = cfg.Client.APITarget
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
cmder.hash = args[0]
Expand All @@ -80,16 +98,12 @@ func NewCheckoutCmd() *cobra.Command {
return fmt.Errorf("could not get debug flag: %w", err)
}

cmder.api, err = cmd.Flags().GetString("api")
if err != nil {
return fmt.Errorf("could not get api flag: %w", err)
}

return cmder.run()
},
}

cmd.PersistentFlags().StringVarP(&cmder.api, "api", "a", "http://localhost:8081", "Tapes API server address")
defaults := config.NewDefaultConfig()
cmd.Flags().StringVarP(&cmder.apiTarget, "api-target", "a", defaults.Client.APITarget, "Tapes API server URL")

return cmd
}
Expand All @@ -110,7 +124,7 @@ func (c *checkoutCommander) run() error {

c.logger.Debug("checking out conversation",
zap.String("hash", c.hash),
zap.String("api", c.api),
zap.String("api_target", c.apiTarget),
)

// Fetch the conversation history from the API
Expand Down Expand Up @@ -149,7 +163,7 @@ func (c *checkoutCommander) run() error {

// fetchHistory calls the API to get the conversation history for a given hash.
func (c *checkoutCommander) fetchHistory(hash string) (*historyResponse, error) {
url := fmt.Sprintf("%s/dag/history/%s", c.api, hash)
url := fmt.Sprintf("%s/dag/history/%s", c.apiTarget, hash)

client := &http.Client{Timeout: 10 * time.Second}
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
Expand Down
47 changes: 47 additions & 0 deletions cmd/tapes/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Package configcmder provides the config command for managing persistent
// tapes configuration stored in the .tapes/ directory.
package configcmder

import (
"github.com/spf13/cobra"
)

const configLongDesc string = `Manage persistent tapes configuration.

Configuration is stored as config.toml in the .tapes/ directory and provides
default values for command flags. CLI flags always take precedence over
config file values.

Keys use dotted notation matching the TOML section structure:
proxy.provider, proxy.upstream, proxy.listen,
api.listen, storage.sqlite_path,
client.proxy_target, client.api_target,
vector_store.provider, vector_store.target,
embedding.provider, embedding.target, embedding.model, embedding.dimensions

Use subcommands to get, set, or list configuration values:
tapes config set <key> <value> Set a configuration value
tapes config get <key> Get a configuration value
tapes config list List all configuration values

Examples:
tapes config set proxy.provider anthropic
tapes config set embedding.model nomic-embed-text
tapes config get proxy.provider
tapes config list`

const configShortDesc string = "Manage persistent tapes configuration"

func NewConfigCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: configShortDesc,
Long: configLongDesc,
}

cmd.AddCommand(newSetCmd())
cmd.AddCommand(newGetCmd())
cmd.AddCommand(newListCmd())

return cmd
}
13 changes: 13 additions & 0 deletions cmd/tapes/config/config_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package configcmder_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestConfig(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Config Command Suite")
}
Loading