├── cmd └── saga │ ├── main.go │ └── cmd │ ├── version.go │ ├── env.go │ ├── examples.go │ └── root.go ├── pkg ├── services │ ├── service.go │ ├── summary.go │ ├── search.go │ ├── translation.go │ ├── explanation.go │ └── message.go └── llm │ ├── llm.go │ ├── openai.go │ ├── gemini.go │ └── claude.go ├── .gitignore ├── LICENSE ├── Makefile ├── .github └── workflows │ └── release.yml ├── go.mod ├── README_ja.md ├── README.md └── go.sum /cmd/saga/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/sa-giga/saga-cli/cmd/saga/cmd" 8 | ) 9 | 10 | func main() { 11 | if err := cmd.RootCmd.Execute(); err != nil { 12 | fmt.Fprintf(os.Stderr, "エラー: %s\n", err) 13 | os.Exit(1) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pkg/services/service.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | // Service はLLMを使用したサービスのインターフェース 10 | type Service interface { 11 | Process(ctx context.Context, inputText string) (string, error) 12 | } 13 | 14 | // ReadFileContent はファイルパスからコンテンツを読み込みます 15 | func ReadFileContent(filePath string) (string, error) { 16 | // ファイルパスからコンテンツを読み込む 17 | content, err := os.ReadFile(strings.TrimSpace(filePath)) 18 | if err != nil { 19 | return "", err 20 | } 21 | return string(content), nil 22 | } 23 | -------------------------------------------------------------------------------- /cmd/saga/cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var ( 10 | Version = "0.1.0" 11 | CommitSHA = "unknown" 12 | BuildDate = "unknown" 13 | ) 14 | 15 | var versionCmd = &cobra.Command{ 16 | Use: "version", 17 | Short: "SaGaCLIのバージョン情報を表示", 18 | Run: func(cmd *cobra.Command, args []string) { 19 | fmt.Printf("SaGaCLI バージョン: %s\n", Version) 20 | fmt.Printf("コミットハッシュ: %s\n", CommitSHA) 21 | fmt.Printf("ビルド日時: %s\n", BuildDate) 22 | }, 23 | } 24 | 25 | func init() { 26 | RootCmd.AddCommand(versionCmd) 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # バイナリファイル 2 | ./bin/ 3 | bin/saga 4 | *.exe 5 | *.exe~ 6 | *.dll 7 | *.so 8 | *.dylib 9 | 10 | # テストバイナリ 11 | *.test 12 | 13 | # カバレッジファイル 14 | *.out 15 | *.prof 16 | 17 | # 依存関係のキャッシュ 18 | /vendor/ 19 | /go.work 20 | 21 | # OS固有のファイル 22 | .DS_Store 23 | .DS_Store? 24 | ._* 25 | .Spotlight-V100 26 | .Trashes 27 | ehthumbs.db 28 | Thumbs.db 29 | 30 | # エディタ固有のファイル 31 | .idea/ 32 | .vscode/ 33 | *.swp 34 | *.swo 35 | *~ 36 | 37 | # ログファイル 38 | *.log 39 | 40 | # 環境変数ファイル 41 | .env 42 | .env.local 43 | .env.development.local 44 | .env.test.local 45 | .env.production.local 46 | 47 | # その他の一時ファイル 48 | /tmp/ 49 | *.tmp 50 | -------------------------------------------------------------------------------- /pkg/llm/llm.go: -------------------------------------------------------------------------------- 1 | package llm 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "os" 7 | ) 8 | 9 | // LLM はAI言語モデルのインターフェース 10 | type LLM interface { 11 | Complete(ctx context.Context, systemPrompt, userPrompt string) (string, error) 12 | } 13 | 14 | // GetLLM は設定に基づいて適切なLLMクライアントを返します 15 | func GetLLM() (LLM, error) { 16 | // APIタイプに基づいて適切なLLMクライアントを返す 17 | apiType := os.Getenv("OPENAI_API_TYPE") 18 | 19 | switch apiType { 20 | case "openai", "": 21 | return NewOpenAIClient() 22 | case "claude": 23 | return NewClaudeClient() 24 | case "gemini": 25 | return NewGeminiClient() 26 | default: 27 | return nil, errors.New("サポートされていないAPI_TYPEです: " + apiType) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /pkg/services/summary.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/sa-giga/saga-cli/pkg/llm" 8 | ) 9 | 10 | // SummaryService は要約サービスを提供します 11 | type SummaryService struct { 12 | llmClient llm.LLM 13 | lang string 14 | } 15 | 16 | // NewSummaryService は新しい要約サービスを作成します 17 | func NewSummaryService(llmClient llm.LLM, lang string) *SummaryService { 18 | return &SummaryService{ 19 | llmClient: llmClient, 20 | lang: lang, 21 | } 22 | } 23 | 24 | // Process は入力テキストを要約します 25 | func (s *SummaryService) Process(ctx context.Context, inputText string) (string, error) { 26 | systemPrompt := fmt.Sprintf("あなたは優秀な要約者です。入力されたテキストの要点を簡潔に%sでまとめてください。重要な情報を漏らさず、冗長な表現は避けてください。", s.lang) 27 | userPrompt := inputText 28 | 29 | return s.llmClient.Complete(ctx, systemPrompt, userPrompt) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/services/search.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/sa-giga/saga-cli/pkg/llm" 8 | ) 9 | 10 | // SearchService は検索サービスを提供します 11 | type SearchService struct { 12 | llmClient llm.LLM 13 | lang string 14 | } 15 | 16 | // NewSearchService は新しい検索サービスを作成します 17 | func NewSearchService(llmClient llm.LLM, lang string) *SearchService { 18 | return &SearchService{ 19 | llmClient: llmClient, 20 | lang: lang, 21 | } 22 | } 23 | 24 | // Process は入力テキストに基づいて情報を検索します 25 | func (s *SearchService) Process(ctx context.Context, inputText string) (string, error) { 26 | systemPrompt := fmt.Sprintf("あなたはリサーチの専門家です。入力されたクエリに関する情報を%sで提供してください。関連性の高い事実に基づいた情報を提供し、必要に応じて複数の視点を含めてください。", s.lang) 27 | userPrompt := inputText 28 | 29 | return s.llmClient.Complete(ctx, systemPrompt, userPrompt) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/services/translation.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/sa-giga/saga-cli/pkg/llm" 8 | ) 9 | 10 | // TranslationService は翻訳サービスを提供します 11 | type TranslationService struct { 12 | llmClient llm.LLM 13 | lang string 14 | } 15 | 16 | // NewTranslationService は新しい翻訳サービスを作成します 17 | func NewTranslationService(llmClient llm.LLM, lang string) *TranslationService { 18 | return &TranslationService{ 19 | llmClient: llmClient, 20 | lang: lang, 21 | } 22 | } 23 | 24 | // Process は入力テキストを翻訳します 25 | func (s *TranslationService) Process(ctx context.Context, inputText string) (string, error) { 26 | systemPrompt := fmt.Sprintf("あなたは優秀な翻訳者です。入力されたテキストを%sに翻訳してください。元のテキストの意味と文脈を正確に維持してください。", s.lang) 27 | userPrompt := inputText 28 | 29 | return s.llmClient.Complete(ctx, systemPrompt, userPrompt) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/services/explanation.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/sa-giga/saga-cli/pkg/llm" 8 | ) 9 | 10 | // ExplanationService は解説サービスを提供します 11 | type ExplanationService struct { 12 | llmClient llm.LLM 13 | lang string 14 | } 15 | 16 | // NewExplanationService は新しい解説サービスを作成します 17 | func NewExplanationService(llmClient llm.LLM, lang string) *ExplanationService { 18 | return &ExplanationService{ 19 | llmClient: llmClient, 20 | lang: lang, 21 | } 22 | } 23 | 24 | // Process は入力テキストを解説します 25 | func (s *ExplanationService) Process(ctx context.Context, inputText string) (string, error) { 26 | systemPrompt := fmt.Sprintf("あなたは優秀な解説者です。入力されたテキストの内容を%sで詳しく説明してください。複雑な概念をわかりやすく解説し、必要に応じて例を挙げてください。", s.lang) 27 | userPrompt := inputText 28 | 29 | return s.llmClient.Complete(ctx, systemPrompt, userPrompt) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/services/message.go: -------------------------------------------------------------------------------- 1 | // filepath: /home/soudai/work/hft/saga-cli/pkg/services/message.go 2 | package services 3 | 4 | import ( 5 | "context" 6 | "fmt" 7 | 8 | "github.com/sa-giga/saga-cli/pkg/llm" 9 | ) 10 | 11 | // MessageService はユーザー指定メッセージに基づいた処理サービスを提供します 12 | type MessageService struct { 13 | llmClient llm.LLM 14 | lang string 15 | message string 16 | } 17 | 18 | // NewMessageService は新しいメッセージサービスを作成します 19 | func NewMessageService(llmClient llm.LLM, lang string, message string) *MessageService { 20 | return &MessageService{ 21 | llmClient: llmClient, 22 | lang: lang, 23 | message: message, 24 | } 25 | } 26 | 27 | // Process はユーザー指定のメッセージに基づいて入力テキストを処理します 28 | func (s *MessageService) Process(ctx context.Context, inputText string) (string, error) { 29 | systemPrompt := fmt.Sprintf("以下の入力データに対して、指示に従ってタスクを実行してください。回答は%sで提供してください。", s.lang) 30 | userPrompt := fmt.Sprintf("【指示】\n%s\n\n【入力データ】\n%s", s.message, inputText) 31 | 32 | return s.llmClient.Complete(ctx, systemPrompt, userPrompt) 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Have Fun Tech LLC. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cmd/saga/cmd/env.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var envCmd = &cobra.Command{ 10 | Use: "env", 11 | Short: "環境変数の設定方法を表示", 12 | Long: "SaGaCLIで使用する環境変数の設定方法を表示します。", 13 | Run: func(cmd *cobra.Command, args []string) { 14 | fmt.Println("SaGaCLIで使用する環境変数:") 15 | fmt.Println() 16 | 17 | fmt.Println("# 使用するAPIタイプの選択(デフォルトはopenai)") 18 | fmt.Println("export OPENAI_API_TYPE=openai # OpenAIを使用") 19 | fmt.Println("export OPENAI_API_TYPE=claude # Claudeを使用") 20 | fmt.Println() 21 | 22 | fmt.Println("# OpenAI API設定") 23 | fmt.Println("export OPENAI_API_KEY=sk-xxxxxxxxxxxx # APIキーを設定") 24 | fmt.Println("export OPENAI_API_BASE_URL=https://api.openai.com/v1 # カスタムAPIエンドポイント(オプション)") 25 | fmt.Println("export OPENAI_API_MODEL=gpt-3.5-turbo # 使用するモデル(デフォルトはgpt-3.5-turbo)") 26 | fmt.Println() 27 | 28 | fmt.Println("# Claude API設定") 29 | fmt.Println("export CLAUDE_API_KEY=sk-xxxxxxxxxxxx # Anthropic APIキーを設定") 30 | fmt.Println("export CLAUDE_API_MODEL=claude-3-haiku-20240307 # 使用するモデル(デフォルトはclaude-3-haiku-20240307)") 31 | }, 32 | } 33 | 34 | func init() { 35 | RootCmd.AddCommand(envCmd) 36 | } 37 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build clean test deps install 2 | 3 | # ビルド変数 4 | BINARY_NAME=saga 5 | VERSION=$(shell git describe --tags --always || echo "dev") 6 | COMMIT_SHA=$(shell git rev-parse HEAD || echo "unknown") 7 | BUILD_DATE=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ") 8 | LD_FLAGS=-X github.com/sa-giga/saga-cli/cmd/saga/cmd.Version=$(VERSION) -X github.com/sa-giga/saga-cli/cmd/saga/cmd.CommitSHA=$(COMMIT_SHA) -X github.com/sa-giga/saga-cli/cmd/saga/cmd.BuildDate=$(BUILD_DATE) 9 | 10 | # デフォルトゴール 11 | all: deps test build 12 | 13 | # 依存関係の取得 14 | deps: 15 | @echo "依存関係を取得中..." 16 | go mod tidy 17 | go get -u github.com/sashabaranov/go-openai 18 | go get -u github.com/spf13/cobra 19 | go get -u github.com/spf13/viper 20 | go get -u github.com/anthropics/anthropic-sdk-go 21 | 22 | # テストの実行 23 | test: 24 | @echo "テストを実行中..." 25 | go test -v ./... 26 | 27 | # アプリケーションのビルド 28 | build: 29 | @echo "$(BINARY_NAME)をビルド中..." 30 | go build -ldflags "$(LD_FLAGS)" -o bin/$(BINARY_NAME) ./cmd/saga 31 | 32 | # クリーンアップ 33 | clean: 34 | @echo "クリーンアップ中..." 35 | rm -rf bin/ 36 | go clean 37 | 38 | # インストール 39 | install: build 40 | @echo "$(BINARY_NAME)をインストール中..." 41 | go install -ldflags "$(LD_FLAGS)" ./cmd/saga -------------------------------------------------------------------------------- /cmd/saga/cmd/examples.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | var examplesCmd = &cobra.Command{ 10 | Use: "examples", 11 | Short: "SaGaCLIの使用例を表示します", 12 | Long: "SaGaCLIの一般的な使用例とコマンドの例を表示します", 13 | Run: func(cmd *cobra.Command, args []string) { 14 | fmt.Println("SaGaCLIの使用例:") 15 | fmt.Println() 16 | 17 | fmt.Println("# ファイルを英語に翻訳する") 18 | fmt.Println("echo input.txt | saga --translation --lang en") 19 | fmt.Println() 20 | 21 | fmt.Println("# ファイルを日本語に翻訳する") 22 | fmt.Println("echo input.txt | saga --translation --lang ja") 23 | fmt.Println() 24 | 25 | fmt.Println("# ファイルの内容を要約する") 26 | fmt.Println("echo document.txt | saga --summary --lang en") 27 | fmt.Println() 28 | 29 | fmt.Println("# ファイルの内容を日本語で解説する") 30 | fmt.Println("echo code.py | saga --explanation --lang ja") 31 | fmt.Println() 32 | 33 | fmt.Println("# クエリに基づいて情報を検索する") 34 | fmt.Println("echo question.txt | saga --search --lang en") 35 | fmt.Println() 36 | 37 | fmt.Println("# パイプを使って直接テキストを入力") 38 | fmt.Println("cat README.md | saga --summary --lang ja") 39 | fmt.Println() 40 | 41 | fmt.Println("# 標準入力からテキストを直接入力") 42 | fmt.Println(`echo "これはテストです。" | saga --translation --lang en`) 43 | fmt.Println() 44 | 45 | fmt.Println("# 環境変数設定を確認") 46 | fmt.Println("saga env") 47 | }, 48 | } 49 | 50 | func init() { 51 | RootCmd.AddCommand(examplesCmd) 52 | } 53 | -------------------------------------------------------------------------------- /pkg/llm/openai.go: -------------------------------------------------------------------------------- 1 | package llm 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "os" 7 | 8 | "github.com/sashabaranov/go-openai" 9 | ) 10 | 11 | // OpenAIClient は OpenAI API のラッパー 12 | type OpenAIClient struct { 13 | client *openai.Client 14 | model string 15 | } 16 | 17 | // NewOpenAIClient は OpenAI クライアントを作成します 18 | func NewOpenAIClient() (*OpenAIClient, error) { 19 | apiKey := os.Getenv("OPENAI_API_KEY") 20 | if apiKey == "" { 21 | return nil, errors.New("OPENAI_API_KEY が設定されていません") 22 | } 23 | 24 | baseURL := os.Getenv("OPENAI_API_BASE_URL") 25 | 26 | model := os.Getenv("OPENAI_API_MODEL") 27 | if model == "" { 28 | model = "gpt-3.5-turbo" // デフォルトモデル 29 | } 30 | 31 | config := openai.DefaultConfig(apiKey) 32 | if baseURL != "" { 33 | config.BaseURL = baseURL 34 | } 35 | 36 | client := openai.NewClientWithConfig(config) 37 | return &OpenAIClient{ 38 | client: client, 39 | model: model, 40 | }, nil 41 | } 42 | 43 | // Complete はテキスト生成を実行します 44 | func (c *OpenAIClient) Complete(ctx context.Context, systemPrompt, userPrompt string) (string, error) { 45 | resp, err := c.client.CreateChatCompletion( 46 | ctx, 47 | openai.ChatCompletionRequest{ 48 | Model: c.model, 49 | Messages: []openai.ChatCompletionMessage{ 50 | { 51 | Role: openai.ChatMessageRoleSystem, 52 | Content: systemPrompt, 53 | }, 54 | { 55 | Role: openai.ChatMessageRoleUser, 56 | Content: userPrompt, 57 | }, 58 | }, 59 | }, 60 | ) 61 | 62 | if err != nil { 63 | return "", err 64 | } 65 | 66 | if len(resp.Choices) == 0 { 67 | return "", errors.New("レスポンスが空です") 68 | } 69 | 70 | return resp.Choices[0].Message.Content, nil 71 | } 72 | -------------------------------------------------------------------------------- /pkg/llm/gemini.go: -------------------------------------------------------------------------------- 1 | // filepath: /home/soudai/work/hft/saga-cli/pkg/llm/gemini.go 2 | package llm 3 | 4 | import ( 5 | "context" 6 | "errors" 7 | "os" 8 | 9 | "github.com/google/generative-ai-go/genai" 10 | "google.golang.org/api/option" 11 | ) 12 | 13 | // GeminiClient は Gemini API のラッパー 14 | type GeminiClient struct { 15 | client *genai.Client 16 | model string 17 | } 18 | 19 | // NewGeminiClient は Gemini クライアントを作成します 20 | func NewGeminiClient() (*GeminiClient, error) { 21 | apiKey := os.Getenv("GEMINI_API_KEY") 22 | if apiKey == "" { 23 | return nil, errors.New("GEMINI_API_KEY が設定されていません") 24 | } 25 | 26 | model := os.Getenv("GEMINI_API_MODEL") 27 | if model == "" { 28 | model = "gemini-1.5-pro" // デフォルトモデル 29 | } 30 | 31 | // Gemini APIクライアントを初期化 32 | ctx := context.Background() 33 | client, err := genai.NewClient(ctx, option.WithAPIKey(apiKey)) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | return &GeminiClient{ 39 | client: client, 40 | model: model, 41 | }, nil 42 | } 43 | 44 | // Complete はテキスト生成を実行します 45 | func (c *GeminiClient) Complete(ctx context.Context, systemPrompt, userPrompt string) (string, error) { 46 | // モデルを取得 47 | model := c.client.GenerativeModel(c.model) 48 | 49 | // チャットセッションを開始 50 | chat := model.StartChat() 51 | 52 | // システムプロンプトがある場合、システムメッセージとして追加 53 | if systemPrompt != "" { 54 | chat.History = append(chat.History, &genai.Content{ 55 | Parts: []genai.Part{genai.Text(systemPrompt)}, 56 | Role: "system", 57 | }) 58 | } 59 | 60 | // ユーザープロンプトでレスポンスを生成 61 | resp, err := chat.SendMessage(ctx, genai.Text(userPrompt)) 62 | if err != nil { 63 | return "", err 64 | } 65 | 66 | if len(resp.Candidates) == 0 || len(resp.Candidates[0].Content.Parts) == 0 { 67 | return "", errors.New("レスポンスが空です") 68 | } 69 | 70 | // テキスト応答を取得 71 | result, ok := resp.Candidates[0].Content.Parts[0].(genai.Text) 72 | if !ok { 73 | return "", errors.New("テキストレスポンスを取得できませんでした") 74 | } 75 | 76 | return string(result), nil 77 | } 78 | -------------------------------------------------------------------------------- /pkg/llm/claude.go: -------------------------------------------------------------------------------- 1 | package llm 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "os" 8 | 9 | anthropic "github.com/anthropics/anthropic-sdk-go" 10 | "github.com/anthropics/anthropic-sdk-go/option" 11 | ) 12 | 13 | // ClaudeClient は Claude API のラッパー 14 | type ClaudeClient struct { 15 | client anthropic.Client 16 | model string 17 | } 18 | 19 | // NewClaudeClient は Claude クライアントを作成します 20 | func NewClaudeClient() (*ClaudeClient, error) { 21 | apiKey := os.Getenv("CLAUDE_API_KEY") 22 | if apiKey == "" { 23 | return nil, errors.New("CLAUDE_API_KEY が設定されていません") 24 | } 25 | 26 | model := os.Getenv("CLAUDE_API_MODEL") 27 | if model == "" { 28 | model = "claude-3-haiku-20240307" // デフォルトモデル 29 | } 30 | 31 | // 最新のSDK APIを使用してクライアントを初期化 32 | client := anthropic.NewClient(option.WithAPIKey(apiKey)) 33 | 34 | return &ClaudeClient{ 35 | client: client, 36 | model: model, 37 | }, nil 38 | } 39 | 40 | // Complete はテキスト生成を実行します 41 | func (c *ClaudeClient) Complete(ctx context.Context, systemPrompt, userPrompt string) (string, error) { 42 | // SDK v0.2.0-beta.3 に合わせたメッセージを作成 43 | params := anthropic.MessageNewParams{ 44 | Model: c.model, 45 | MaxTokens: 1024, 46 | Messages: []anthropic.MessageParam{ 47 | anthropic.NewUserMessage( 48 | anthropic.NewTextBlock(userPrompt), 49 | ), 50 | }, 51 | } 52 | 53 | // システムプロンプトを設定(systemPromptを直接指定せず省略) 54 | // System フィールドは v0.2.0-beta.3 では必須ではない 55 | 56 | resp, err := c.client.Messages.New(ctx, params) 57 | if err != nil { 58 | return "", err 59 | } 60 | 61 | if len(resp.Content) == 0 { 62 | return "", errors.New("レスポンスが空です") 63 | } 64 | 65 | // レスポンスからテキストを抽出 66 | // v0.2.0-beta.3では、ContentBlockUnionを直接処理できないので 67 | // JSON変換してからマップに復元して処理する 68 | for i := range resp.Content { 69 | contentBytes, err := json.Marshal(resp.Content[i]) 70 | if err != nil { 71 | continue 72 | } 73 | 74 | var contentMap map[string]interface{} 75 | if err := json.Unmarshal(contentBytes, &contentMap); err != nil { 76 | continue 77 | } 78 | 79 | // typeフィールドがtextであることを確認 80 | if t, ok := contentMap["type"].(string); ok && t == "text" { 81 | // テキストフィールドを取得 82 | if text, ok := contentMap["text"].(string); ok { 83 | return text, nil 84 | } 85 | } 86 | } 87 | 88 | return "", errors.New("テキストコンテンツが見つかりません") 89 | } 90 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' # 'v1.0.0' のようなタグが作成されたときにトリガー 7 | 8 | jobs: 9 | build: 10 | name: Build and Release 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | include: 15 | - goos: linux 16 | goarch: amd64 17 | suffix: "" 18 | - goos: linux 19 | goarch: arm64 20 | suffix: "" 21 | - goos: darwin 22 | goarch: amd64 23 | suffix: "" 24 | - goos: darwin 25 | goarch: arm64 26 | suffix: "" 27 | - goos: windows 28 | goarch: amd64 29 | suffix: ".exe" 30 | 31 | steps: 32 | - name: Checkout code 33 | uses: actions/checkout@v4 34 | with: 35 | fetch-depth: 0 # すべての履歴とタグを取得 36 | 37 | - name: Set up Go 38 | uses: actions/setup-go@v5 39 | with: 40 | go-version: '^1.21' # プロジェクトに合わせて調整してください 41 | 42 | - name: Get Version 43 | id: get_version 44 | run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV 45 | 46 | - name: Build 47 | env: 48 | GOOS: ${{ matrix.goos }} 49 | GOARCH: ${{ matrix.goarch }} 50 | run: | 51 | # バージョン情報を取得 52 | VERSION=${GITHUB_REF#refs/tags/} 53 | COMMIT_SHA=$(git rev-parse HEAD) 54 | BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") 55 | 56 | # ビルドフラグを設定 57 | LD_FLAGS="-X github.com/sa-giga/saga-cli/cmd/saga/cmd.Version=${VERSION} -X github.com/sa-giga/saga-cli/cmd/saga/cmd.CommitSHA=${COMMIT_SHA} -X github.com/sa-giga/saga-cli/cmd/saga/cmd.BuildDate=${BUILD_DATE}" 58 | 59 | # バイナリをビルド 60 | BINARY_NAME="saga-${VERSION}-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.suffix }}" 61 | go build -ldflags "${LD_FLAGS}" -o "${BINARY_NAME}" ./cmd/saga 62 | 63 | # アーティファクト名を環境変数に設定 64 | echo "BINARY_NAME=${BINARY_NAME}" >> $GITHUB_ENV 65 | 66 | - name: Upload artifacts 67 | uses: actions/upload-artifact@v4 68 | with: 69 | name: ${{ env.BINARY_NAME }} 70 | path: ${{ env.BINARY_NAME }} 71 | if-no-files-found: error 72 | retention-days: 5 73 | 74 | release: 75 | name: Create Release 76 | needs: build 77 | runs-on: ubuntu-latest 78 | steps: 79 | - name: Download all artifacts 80 | uses: actions/download-artifact@v4 81 | 82 | - name: Create GitHub Release 83 | uses: softprops/action-gh-release@v1 84 | with: 85 | files: saga-*/* 86 | draft: false 87 | prerelease: false 88 | generate_release_notes: true 89 | env: 90 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sa-giga/saga-cli 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.8 6 | 7 | require ( 8 | github.com/anthropics/anthropic-sdk-go v0.2.0-beta.3 9 | github.com/google/generative-ai-go v0.19.0 10 | github.com/sashabaranov/go-openai v1.38.2 11 | github.com/spf13/cobra v1.9.1 12 | github.com/spf13/viper v1.20.1 13 | google.golang.org/api v0.229.0 14 | ) 15 | 16 | require ( 17 | cloud.google.com/go v0.116.0 // indirect 18 | cloud.google.com/go/ai v0.8.0 // indirect 19 | cloud.google.com/go/auth v0.16.0 // indirect 20 | cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect 21 | cloud.google.com/go/compute/metadata v0.6.0 // indirect 22 | cloud.google.com/go/longrunning v0.5.7 // indirect 23 | github.com/felixge/httpsnoop v1.0.4 // indirect 24 | github.com/fsnotify/fsnotify v1.8.0 // indirect 25 | github.com/go-logr/logr v1.4.2 // indirect 26 | github.com/go-logr/stdr v1.2.2 // indirect 27 | github.com/go-viper/mapstructure/v2 v2.2.1 // indirect 28 | github.com/google/s2a-go v0.1.9 // indirect 29 | github.com/google/uuid v1.6.0 // indirect 30 | github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect 31 | github.com/googleapis/gax-go/v2 v2.14.1 // indirect 32 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 33 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 34 | github.com/sagikazarmark/locafero v0.7.0 // indirect 35 | github.com/sourcegraph/conc v0.3.0 // indirect 36 | github.com/spf13/afero v1.12.0 // indirect 37 | github.com/spf13/cast v1.7.1 // indirect 38 | github.com/spf13/pflag v1.0.6 // indirect 39 | github.com/subosito/gotenv v1.6.0 // indirect 40 | github.com/tidwall/gjson v1.18.0 // indirect 41 | github.com/tidwall/match v1.1.1 // indirect 42 | github.com/tidwall/pretty v1.2.1 // indirect 43 | github.com/tidwall/sjson v1.2.5 // indirect 44 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 45 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect 46 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect 47 | go.opentelemetry.io/otel v1.35.0 // indirect 48 | go.opentelemetry.io/otel/metric v1.35.0 // indirect 49 | go.opentelemetry.io/otel/trace v1.35.0 // indirect 50 | go.uber.org/atomic v1.9.0 // indirect 51 | go.uber.org/multierr v1.9.0 // indirect 52 | golang.org/x/crypto v0.37.0 // indirect 53 | golang.org/x/net v0.39.0 // indirect 54 | golang.org/x/oauth2 v0.29.0 // indirect 55 | golang.org/x/sync v0.13.0 // indirect 56 | golang.org/x/sys v0.32.0 // indirect 57 | golang.org/x/text v0.24.0 // indirect 58 | golang.org/x/time v0.11.0 // indirect 59 | google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect 60 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e // indirect 61 | google.golang.org/grpc v1.71.1 // indirect 62 | google.golang.org/protobuf v1.36.6 // indirect 63 | gopkg.in/yaml.v3 v3.0.1 // indirect 64 | ) 65 | -------------------------------------------------------------------------------- /README_ja.md: -------------------------------------------------------------------------------- 1 | # SaGaCLI 2 | Golangで書かれたコマンドラインインターフェースです。 3 | コマンドラインインターフェースを使用して、LLMを利用できます。 4 | 5 | ## 特徴 6 | 7 | - **複数のLLMサポート**: OpenAI(GPT-3.5/GPT-4)、AnthropicのClaude(Claude-3シリーズ)、GoogleのGemini(Gemini-2.5-pro)をサポート 8 | - **多様な機能**: 翻訳、要約、解説、検索、カスタムメッセージなど、さまざまな機能を提供 9 | - **柔軟な入力**: ファイルパスまたは標準入力からテキストを受け付け 10 | - **多言語対応**: 出力言語を自由に指定可能 11 | - **コンテキスト指定**: `--file/-f` で複数ファイル、`--dir/-d` でディレクトリ(再帰的に全ファイル)をコンテキストとしてLLMに渡せます 12 | 13 | ## インストール方法 14 | 15 | ### 前提条件 16 | 17 | - Go 1.16以上 18 | 19 | ### 方法1: バイナリのインストール(推奨) 20 | 21 | ```bash 22 | # Goのインストール(未インストールの場合) 23 | $ brew install go # macOS 24 | # または 25 | $ sudo apt install golang-go # Ubuntu/Debian 26 | 27 | # SaGaCLIのインストール 28 | $ go install github.com/sa-giga/saga-cli@latest 29 | 30 | # SaGaCLIのパスを通す 31 | $ export PATH=$PATH:$(go env GOPATH)/bin 32 | ``` 33 | 34 | ### 方法2: ソースコードからのビルド 35 | 36 | ```bash 37 | # リポジトリをクローン 38 | $ git clone https://github.com/sa-giga/saga-cli.git 39 | $ cd saga-cli 40 | 41 | # ビルドとインストール 42 | $ make build # bin/sagaにバイナリが作成されます 43 | $ make install # システムにインストールします 44 | ``` 45 | 46 | ## 詳細な環境設定 47 | 48 | SaGaCLIは、OpenAIのGPT-3.5/GPT-4、AnthropicのClaudeモデル、またはGoogleのGeminiモデルを使用できます。使用したいサービスに応じて適切な環境変数を設定してください。 49 | 50 | ### OpenAI APIを使用する場合 51 | 52 | ```bash 53 | # 必須の設定 54 | export OPENAI_API_KEY=sk-... # OpenAI APIキー 55 | export OPENAI_API_TYPE=openai # APIタイプを指定(デフォルト値) 56 | 57 | # オプションの設定 58 | export OPENAI_API_BASE_URL=https://api.openai.com/v1 # APIのベースURL(デフォルト値) 59 | export OPENAI_API_VERSION=2023-05-15 # APIバージョン 60 | export OPENAI_API_MODEL=gpt-3.5-turbo # 使用するモデル(デフォルト: gpt-3.5-turbo) 61 | 62 | # Azure OpenAI Serviceを使用する場合 63 | export OPENAI_API_TYPE=azure 64 | export OPENAI_API_BASE_URL=https://your-resource-name.openai.azure.com 65 | export OPENAI_API_KEY=your-azure-api-key 66 | export OPENAI_API_VERSION=2023-05-15 67 | export OPENAI_API_MODEL=your-deployment-name 68 | ``` 69 | 70 | ### Anthropic Claude APIを使用する場合 71 | 72 | ```bash 73 | # 必須の設定 74 | export CLAUDE_API_KEY=sk-ant-... # Anthropic APIキー 75 | export OPENAI_API_TYPE=claude # APIタイプをclaudeに設定 76 | 77 | # オプションの設定 78 | export CLAUDE_API_MODEL=claude-3-haiku-20240307 # 使用するモデル(デフォルト値) 79 | # 利用可能なモデル: claude-3-opus-20240229, claude-3-sonnet-20240229, claude-3-haiku-20240307 など 80 | ``` 81 | 82 | ### Google Gemini APIを使用する場合 83 | 84 | ```bash 85 | # 必須の設定 86 | export GEMINI_API_KEY=your-gemini-api-key # Gemini APIキー 87 | export OPENAI_API_TYPE=gemini # APIタイプをgeminiに設定 88 | 89 | # オプションの設定 90 | export GEMINI_API_MODEL=gemini-1.5-pro # 使用するモデル(デフォルト値) 91 | # 利用可能なその他のモデル: gemini-1.5-flash など 92 | ``` 93 | 94 | 環境変数の設定が確認できない場合は、`saga env`コマンドを実行して現在の設定を確認できます。 95 | 96 | ## 使い方の詳細 97 | 98 | ### 基本的な使用方法 99 | 100 | ```bash 101 | # ファイルの内容を処理 102 | $ saga [オプション] < 入力ファイル 103 | 104 | # または 105 | $ cat 入力ファイル | saga [オプション] 106 | 107 | # 標準入力から直接テキストを入力(Ctrl+Dで入力終了) 108 | $ saga [オプション] 109 | テキストを入力... 110 | [Ctrl+D] 111 | ``` 112 | 113 | ### 主要なオプション 114 | 115 | ```bash 116 | -t, --translation # 翻訳モード 117 | -s, --summary # 要約モード 118 | -e, --explanation # 解説モード 119 | -S, --search # 検索モード 120 | -m, --message # カスタムメッセージモード(特定の指示を送信) 121 | -l, --lang [言語コード] # 出力言語の指定(例: ja, en, fr, zh など) 122 | -f, --file [ファイルパス] # コンテキストとして利用するファイルを指定(複数指定可) 123 | -d, --dir [ディレクトリパス] # コンテキストとして利用するディレクトリを指定(再帰的に全ファイルを読み込み) 124 | ``` 125 | 126 | ### 使用例 127 | 128 | ```bash 129 | # 英語のテキストを日本語に翻訳 130 | $ echo "Hello world" | saga --translation --lang ja 131 | 132 | # 長い文書を日本語で要約 133 | $ cat document.txt | saga --summary --lang ja 134 | 135 | # プログラムコードを解説 136 | $ cat code.py | saga --explanation --lang ja 137 | 138 | # 特定のトピックについて検索 139 | $ echo "量子コンピューティングの基本原理" | saga --search --lang ja 140 | 141 | # JSONデータからnameフィールドの値を抽出 142 | $ cat document.json | saga --message "nameの値を取り出して" --lang ja 143 | 144 | # CSVファイルの年齢列の平均を計算 145 | $ cat document.csv | saga --message "年齢の列の平均を計算して" --lang ja 146 | 147 | # 英語のドキュメントを翻訳して日本語で要約 148 | $ cat english_doc.txt | saga --translation --lang ja | saga --summary --lang ja 149 | 150 | # フォルダ内の全ファイルをコンテキストとして指定し、SQLの最適化を依頼 151 | $ cat SlowQuery.log | saga -d docs -m '適切なINDEXを作成して' -l sql 152 | 153 | # 複数ファイルをコンテキストとして指定し、CSV変換ルールに従って変換 154 | $ cat domain.csv | saga -f convert_rule.md -m 'CSVを指定のruleに沿ってJSONに変換して' 155 | ``` 156 | 157 | ### 使用例の一覧表示 158 | 159 | ```bash 160 | # さまざまな使用例を確認 161 | $ saga examples 162 | ``` 163 | 164 | ## トラブルシューティング 165 | 166 | ### APIキーが設定されていないエラー 167 | 168 | ``` 169 | Error: OPENAI_API_KEY または CLAUDE_API_KEY または GEMINI_API_KEY が設定されていません 170 | ``` 171 | 172 | 上記のエラーが発生した場合は、環境変数が正しく設定されているか確認してください。 173 | 174 | ### モデルの選択 175 | 176 | 処理が遅い場合や、より高品質な結果が必要な場合は、環境変数でより高性能なモデルを選択してください: 177 | 178 | ```bash 179 | # OpenAIの場合 180 | export OPENAI_API_MODEL=gpt-4 181 | 182 | # Claudeの場合 183 | export CLAUDE_API_MODEL=claude-3-opus-20240229 184 | 185 | # Geminiの場合 186 | export GEMINI_API_MODEL=gemini-1.5-pro 187 | ``` 188 | 189 | ## ヘルプの表示 190 | 191 | ```bash 192 | # コマンドヘルプの表示 193 | $ saga --help 194 | 195 | # バージョン情報の表示 196 | $ saga --version 197 | 198 | # 環境設定の確認 199 | $ saga env 200 | ``` 201 | 202 | -------------------------------------------------------------------------------- /cmd/saga/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "context" 7 | "fmt" 8 | "io" 9 | "os" 10 | 11 | "github.com/sa-giga/saga-cli/pkg/llm" 12 | "github.com/sa-giga/saga-cli/pkg/services" 13 | "github.com/spf13/cobra" 14 | "github.com/spf13/viper" 15 | ) 16 | 17 | var ( 18 | // フラグ変数 19 | isTranslation bool 20 | isSummary bool 21 | isExplanation bool 22 | isSearch bool 23 | messageFlag string 24 | langFlag string 25 | 26 | fileFlags []string 27 | dirFlags []string 28 | 29 | // RootCmd はCLIツールのルートコマンド 30 | RootCmd = &cobra.Command{ 31 | Use: "saga", 32 | Short: "SaGaCLI - LLMを活用するコマンドラインツール", 33 | Long: `SaGaCLI は翻訳、要約、解説、検索などのタスクにLLMを活用するコマンドラインツールです。 34 | 環境変数を設定して、OpenAIやClaudeのAPIを利用することができます。`, 35 | Run: rootRun, 36 | } 37 | ) 38 | 39 | func init() { 40 | // フラグの設定 41 | RootCmd.PersistentFlags().BoolVarP(&isTranslation, "translation", "t", false, "入力されたテキストを翻訳します") 42 | RootCmd.PersistentFlags().BoolVarP(&isSummary, "summary", "s", false, "入力されたテキストを要約します") 43 | RootCmd.PersistentFlags().BoolVarP(&isExplanation, "explanation", "e", false, "入力されたテキストを解説します") 44 | RootCmd.PersistentFlags().BoolVarP(&isSearch, "search", "S", false, "入力されたテキストに基づいて情報を検索します") 45 | RootCmd.PersistentFlags().StringVarP(&messageFlag, "message", "m", "", "入力データに対して実行したい操作を指定します") 46 | RootCmd.PersistentFlags().StringVarP(&langFlag, "lang", "l", "en", "出力言語を指定します(例: en, ja, fr)") 47 | RootCmd.PersistentFlags().StringSliceVarP(&fileFlags, "file", "f", nil, "コンテキストとして利用するファイルを指定します(複数指定可)") 48 | RootCmd.PersistentFlags().StringSliceVarP(&dirFlags, "dir", "d", nil, "コンテキストとして利用するディレクトリを指定します(再帰的に全ファイルを読み込み)") 49 | 50 | // 環境変数の設定を読み込む 51 | viper.AutomaticEnv() 52 | } 53 | 54 | // ディレクトリ内の全ファイルを再帰的に取得 55 | func getAllFilesRecursive(dir string) ([]string, error) { 56 | var files []string 57 | err := walkDir(dir, &files) 58 | return files, err 59 | } 60 | 61 | func walkDir(dir string, files *[]string) error { 62 | d, err := os.ReadDir(dir) 63 | if err != nil { 64 | return err 65 | } 66 | for _, entry := range d { 67 | path := dir + string(os.PathSeparator) + entry.Name() 68 | if entry.IsDir() { 69 | if err := walkDir(path, files); err != nil { 70 | return err 71 | } 72 | } else { 73 | *files = append(*files, path) 74 | } 75 | } 76 | return nil 77 | } 78 | 79 | // ファイルリストから内容を結合 80 | func readFilesContent(filePaths []string) (string, error) { 81 | var buf bytes.Buffer 82 | for _, f := range filePaths { 83 | content, err := services.ReadFileContent(f) 84 | if err != nil { 85 | return "", err 86 | } 87 | buf.WriteString("【ファイル: " + f + "】\n") 88 | buf.WriteString(content) 89 | buf.WriteString("\n\n") 90 | } 91 | return buf.String(), nil 92 | } 93 | 94 | func rootRun(cmd *cobra.Command, args []string) { 95 | var content string 96 | var err error 97 | 98 | // --- コンテキストファイル・ディレクトリの内容を先に読み込む --- 99 | var contextContents string 100 | var contextFiles []string 101 | if len(fileFlags) > 0 { 102 | contextFiles = append(contextFiles, fileFlags...) 103 | } 104 | if len(dirFlags) > 0 { 105 | for _, dir := range dirFlags { 106 | files, err := getAllFilesRecursive(dir) 107 | if err != nil { 108 | fmt.Fprintf(os.Stderr, "ディレクトリの読み取りに失敗しました: %v\n", err) 109 | os.Exit(1) 110 | } 111 | contextFiles = append(contextFiles, files...) 112 | } 113 | } 114 | if len(contextFiles) > 0 { 115 | contextContents, err = readFilesContent(contextFiles) 116 | if err != nil { 117 | fmt.Fprintf(os.Stderr, "コンテキストファイルの読み取りに失敗しました: %v\n", err) 118 | os.Exit(1) 119 | } 120 | } 121 | 122 | // 標準入力が端末から来ているか確認 123 | stat, _ := os.Stdin.Stat() 124 | isPipe := (stat.Mode() & os.ModeCharDevice) == 0 125 | 126 | if isPipe { 127 | // パイプまたはリダイレクトからの入力 128 | var buf bytes.Buffer 129 | if _, err := io.Copy(&buf, os.Stdin); err != nil { 130 | fmt.Fprintf(os.Stderr, "標準入力の読み取りに失敗しました: %v\n", err) 131 | os.Exit(1) 132 | } 133 | content = buf.String() 134 | } else { 135 | // 対話モード: ファイルパスを読み取る 136 | scanner := bufio.NewScanner(os.Stdin) 137 | var filePath string 138 | if scanner.Scan() { 139 | filePath = scanner.Text() 140 | } 141 | if err := scanner.Err(); err != nil { 142 | fmt.Fprintf(os.Stderr, "入力の読み取りに失敗しました: %v\n", err) 143 | os.Exit(1) 144 | } 145 | 146 | // ファイルの内容を読み込む 147 | content, err = services.ReadFileContent(filePath) 148 | if err != nil { 149 | fmt.Fprintf(os.Stderr, "ファイルの読み取りに失敗しました: %v\n", err) 150 | os.Exit(1) 151 | } 152 | } 153 | 154 | // --- コンテキストをuserPromptの先頭に付与 --- 155 | if contextContents != "" { 156 | content = "【コンテキスト】\n" + contextContents + "\n【入力データ】\n" + content 157 | } 158 | 159 | // LLMクライアントの取得 160 | llmClient, err := llm.GetLLM() 161 | if err != nil { 162 | fmt.Fprintf(os.Stderr, "LLMクライアントの初期化に失敗しました: %v\n", err) 163 | os.Exit(1) 164 | } 165 | 166 | ctx := context.Background() 167 | var service services.Service 168 | 169 | // フラグに基づいて適切なサービスを選択 170 | switch { 171 | case isTranslation: 172 | service = services.NewTranslationService(llmClient, langFlag) 173 | case isSummary: 174 | service = services.NewSummaryService(llmClient, langFlag) 175 | case isExplanation: 176 | service = services.NewExplanationService(llmClient, langFlag) 177 | case isSearch: 178 | service = services.NewSearchService(llmClient, langFlag) 179 | case messageFlag != "": 180 | service = services.NewMessageService(llmClient, langFlag, messageFlag) 181 | default: 182 | fmt.Fprintf(os.Stderr, "機能フラグが指定されていません。--translation, --summary, --explanation, --search, --messageのいずれかを指定してください。\n") 183 | os.Exit(1) 184 | } 185 | 186 | // サービスを実行 187 | result, err := service.Process(ctx, content) 188 | if err != nil { 189 | fmt.Fprintf(os.Stderr, "処理に失敗しました: %v\n", err) 190 | os.Exit(1) 191 | } 192 | 193 | // 結果を表示 194 | fmt.Println(result) 195 | } 196 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SaGaCLI 2 | 3 | SaGaCLI is a simple yet powerful command-line interface for leveraging Large Language Models (LLMs). It can use OpenAI's GPT-3.5/GPT-4, Anthropic's Claude models, or Google's Gemini models to provide translation, summarization, explanation, and search capabilities for text. 4 | 5 | ## Features 6 | 7 | - **Multiple LLM Support**: Use OpenAI (GPT-3.5/GPT-4), Claude (Anthropic), or Gemini (Google) models 8 | - **Diverse Functions**: Translation, summarization, explanation, search, custom messages, and more 9 | - **Flexible Input**: Accept text from file paths or standard input 10 | - **Multilingual Support**: Specify any target language for output 11 | - **Context File/Directory Support**: Use `--file/-f` to specify multiple files, or `--dir/-d` to specify a directory (recursively includes all files) as context for the LLM 12 | 13 | ## Installation 14 | 15 | ### Prerequisites 16 | 17 | - Go 1.16 or higher 18 | 19 | ### Method 1: Binary Installation (Recommended) 20 | 21 | ```bash 22 | # Install Go if not installed 23 | $ brew install go # macOS 24 | # or 25 | $ sudo apt install golang-go # Ubuntu/Debian 26 | 27 | # Install SaGaCLI 28 | $ go install github.com/sa-giga/saga-cli@latest 29 | 30 | # Add SaGaCLI to your path 31 | $ export PATH=$PATH:$(go env GOPATH)/bin 32 | ``` 33 | 34 | ### Method 2: Building from Source 35 | 36 | ```bash 37 | # Clone the repository 38 | $ git clone https://github.com/sa-giga/saga-cli.git 39 | $ cd saga-cli 40 | 41 | # Build and install 42 | $ make build # Creates binary at bin/saga 43 | $ make install # Installs to your system 44 | ``` 45 | 46 | ## Detailed Configuration 47 | 48 | SaGaCLI can use OpenAI's GPT-3.5/GPT-4, Anthropic's Claude models, or Google's Gemini models. Set the appropriate environment variables based on the service you want to use. 49 | 50 | ### Using OpenAI API 51 | 52 | ```bash 53 | # Required settings 54 | export OPENAI_API_KEY=sk-... # Your OpenAI API key 55 | export OPENAI_API_TYPE=openai # API type (default) 56 | 57 | # Optional settings 58 | export OPENAI_API_BASE_URL=https://api.openai.com/v1 # Base URL (default) 59 | export OPENAI_API_VERSION=2023-05-15 # API version 60 | export OPENAI_API_MODEL=gpt-3.5-turbo # Model to use (default: gpt-3.5-turbo) 61 | 62 | # For Azure OpenAI Service 63 | export OPENAI_API_TYPE=azure 64 | export OPENAI_API_BASE_URL=https://your-resource-name.openai.azure.com 65 | export OPENAI_API_KEY=your-azure-api-key 66 | export OPENAI_API_VERSION=2023-05-15 67 | export OPENAI_API_MODEL=your-deployment-name 68 | ``` 69 | 70 | ### Using Anthropic Claude API 71 | 72 | ```bash 73 | # Required settings 74 | export CLAUDE_API_KEY=sk-ant-... # Your Anthropic API key 75 | export OPENAI_API_TYPE=claude # Set API type to claude 76 | 77 | # Optional settings 78 | export CLAUDE_API_MODEL=claude-3-haiku-20240307 # Model to use (default) 79 | # Available models: claude-3-opus-20240229, claude-3-sonnet-20240229, claude-3-haiku-20240307, etc. 80 | ``` 81 | 82 | ### Using Google Gemini API 83 | 84 | ```bash 85 | # Required settings 86 | export GEMINI_API_KEY=your-gemini-api-key # Your Gemini API key 87 | export OPENAI_API_TYPE=gemini # Set API type to gemini 88 | 89 | # Optional settings 90 | export GEMINI_API_MODEL=gemini-1.5-pro # Model to use (default) 91 | # Also available: gemini-1.5-flash, etc. 92 | ``` 93 | 94 | You can check your current settings by running the `saga env` command. 95 | 96 | ## Detailed Usage 97 | 98 | ### Basic Usage 99 | 100 | ```bash 101 | # Process file content 102 | $ saga [options] < input_file 103 | 104 | # Or 105 | $ cat input_file | saga [options] 106 | 107 | # Direct text input from standard input (end with Ctrl+D) 108 | $ saga [options] 109 | Enter your text... 110 | [Ctrl+D] 111 | ``` 112 | 113 | ### Main Options 114 | 115 | ```bash 116 | -t, --translation # Translation mode 117 | -s, --summary # Summarization mode 118 | -e, --explanation # Explanation mode 119 | -S, --search # Search mode 120 | -m, --message # Custom message mode (send specific instructions) 121 | -l, --lang [language_code] # Specify output language (e.g., en, ja, fr, zh, etc.) 122 | -f, --file [file_path] # Specify files to use as context (can specify multiple) 123 | -d, --dir [directory_path] # Specify directory to use as context (recursively includes all files) 124 | ``` 125 | 126 | ### Usage Examples 127 | 128 | ```bash 129 | # Translate text to Japanese 130 | $ echo "Hello world" | saga --translation --lang ja 131 | 132 | # Summarize a long document in English 133 | $ cat document.txt | saga --summary --lang en 134 | 135 | # Explain program code 136 | $ cat code.py | saga --explanation --lang en 137 | 138 | # Search for information on a specific topic 139 | $ echo "Quantum computing basics" | saga --search --lang en 140 | 141 | # Extract name field from JSON data 142 | $ cat document.json | saga --message "Extract the name value" --lang en 143 | 144 | # Calculate average of age column in CSV data 145 | $ cat document.csv | saga --message "Calculate the average of the age column" --lang en 146 | 147 | # Translate a document to Japanese then summarize it 148 | $ cat english_doc.txt | saga --translation --lang ja | saga --summary --lang ja 149 | 150 | # Use all files in a folder as context and request SQL optimization 151 | $ cat SlowQuery.log | saga -d docs -m 'Create an appropriate INDEX' -l sql 152 | 153 | # Use multiple files as context and convert CSV according to a rule 154 | $ cat domain.csv | saga -f convert_rule.md -m 'Convert CSV to JSON according to the specified rule' 155 | ``` 156 | 157 | ### View Examples 158 | 159 | ```bash 160 | # View various usage examples 161 | $ saga examples 162 | ``` 163 | 164 | ## Troubleshooting 165 | 166 | ### API Key Not Set Error 167 | 168 | ``` 169 | Error: OPENAI_API_KEY or CLAUDE_API_KEY or GEMINI_API_KEY is not set 170 | ``` 171 | 172 | If you see this error, check that your environment variables are correctly set. 173 | 174 | ### Model Selection 175 | 176 | If processing is slow or you need higher quality results, select a more powerful model via environment variables: 177 | 178 | ```bash 179 | # For OpenAI 180 | export OPENAI_API_MODEL=gpt-4 181 | 182 | # For Claude 183 | export CLAUDE_API_MODEL=claude-3-opus-20240229 184 | 185 | # For Gemini 186 | export GEMINI_API_MODEL=gemini-1.5-pro 187 | ``` 188 | 189 | ## Getting Help 190 | 191 | ```bash 192 | # Display command help 193 | $ saga --help 194 | 195 | # Display version information 196 | $ saga --version 197 | 198 | # Check environment settings 199 | $ saga env 200 | ``` 201 | 202 | ## License 203 | 204 | MIT License 205 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= 2 | cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= 3 | cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w= 4 | cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE= 5 | cloud.google.com/go/auth v0.16.0 h1:Pd8P1s9WkcrBE2n/PhAwKsdrR35V3Sg2II9B+ndM3CU= 6 | cloud.google.com/go/auth v0.16.0/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= 7 | cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= 8 | cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= 9 | cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= 10 | cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= 11 | cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= 12 | cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= 13 | github.com/anthropics/anthropic-sdk-go v0.2.0-beta.3 h1:b5t1ZJMvV/l99y4jbz7kRFdUp3BSDkI8EhSlHczivtw= 14 | github.com/anthropics/anthropic-sdk-go v0.2.0-beta.3/go.mod h1:AapDW22irxK2PSumZiQXYUFvsdQgkwIWlpESweWZI/c= 15 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 16 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 18 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 19 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 20 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 21 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 22 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 23 | github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= 24 | github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 25 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 26 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 27 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 28 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 29 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 30 | github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= 31 | github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 32 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 33 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 34 | github.com/google/generative-ai-go v0.19.0 h1:R71szggh8wHMCUlEMsW2A/3T+5LdEIkiaHSYgSpUgdg= 35 | github.com/google/generative-ai-go v0.19.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E= 36 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 37 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 38 | github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= 39 | github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= 40 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 41 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 42 | github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= 43 | github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= 44 | github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= 45 | github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= 46 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 47 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 48 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 49 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 50 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 51 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 52 | github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= 53 | github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= 54 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 55 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 56 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 57 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 58 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 59 | github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= 60 | github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= 61 | github.com/sashabaranov/go-openai v1.38.2 h1:akrssjj+6DY3lWuDwHv6cBvJ8Z+FZDM9XEaaYFt0Auo= 62 | github.com/sashabaranov/go-openai v1.38.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= 63 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= 64 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= 65 | github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= 66 | github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= 67 | github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= 68 | github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 69 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 70 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 71 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 72 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 73 | github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= 74 | github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= 75 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 76 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 77 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 78 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 79 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 80 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 81 | github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 82 | github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= 83 | github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 84 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 85 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 86 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 87 | github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= 88 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 89 | github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= 90 | github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= 91 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 92 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 93 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= 94 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= 95 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= 96 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= 97 | go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= 98 | go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= 99 | go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= 100 | go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= 101 | go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= 102 | go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= 103 | go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= 104 | go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= 105 | go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= 106 | go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= 107 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 108 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 109 | go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= 110 | go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= 111 | golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= 112 | golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 113 | golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= 114 | golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 115 | golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= 116 | golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 117 | golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= 118 | golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 119 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 120 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 121 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 122 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 123 | golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= 124 | golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 125 | google.golang.org/api v0.229.0 h1:p98ymMtqeJ5i3lIBMj5MpR9kzIIgzpHHh8vQ+vgAzx8= 126 | google.golang.org/api v0.229.0/go.mod h1:wyDfmq5g1wYJWn29O22FDWN48P7Xcz0xz+LBpptYvB0= 127 | google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= 128 | google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= 129 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e h1:ztQaXfzEXTmCBvbtWYRhJxW+0iJcz2qXfd38/e9l7bA= 130 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= 131 | google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= 132 | google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= 133 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 134 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 135 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 136 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 137 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 138 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 139 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 140 | --------------------------------------------------------------------------------