From a62ce9a0b132a55a34e7281b2a322ca7c160ba40 Mon Sep 17 00:00:00 2001 From: nathabonfim59 Date: Thu, 7 Nov 2024 23:59:58 -0300 Subject: [PATCH 1/9] feat(copilot): add login and chat flow WIP(copilot): support for login with device code WIP(copilot): separate copilot client_id into const variable WIP(copilot): load AccessToken from cache WIP(copilot): login flow and chat --- cmd/copilot/main.go | 472 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 472 insertions(+) create mode 100644 cmd/copilot/main.go diff --git a/cmd/copilot/main.go b/cmd/copilot/main.go new file mode 100644 index 00000000000..68d926c1539 --- /dev/null +++ b/cmd/copilot/main.go @@ -0,0 +1,472 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/sanity-io/litter" +) + +// ICopilotChat defines the interface for chat operations +type ICopilotChat interface { + Authenticate() error + IsAuthenticated() bool + Chat(request Request) (string, error) +} + +var _ ICopilotChat = &CopilotChat{} + +type Role string + +const ( + RoleUser Role = "user" + RoleAssistant Role = "assistant" + RoleSystem Role = "system" +) + +type Model string + +const ( + Gpt4o Model = "gpt-4o-2024-05-13" + Gpt4 Model = "gpt-4" + Gpt3_5Turbo Model = "gpt-3.5-turbo" + O1Preview Model = "o1-preview-2024-09-12" + O1Mini Model = "o1-mini-2024-09-12" + Claude3_5Sonnet Model = "claude-3.5-sonnet" +) + +const ( + COPILOT_CHAT_COMPLETION_URL = "https://api.githubcopilot.com/chat/completions" + COPILOT_CHAT_AUTH_URL = "https://api.github.com/copilot_internal/v2/token" + EDITOR_VERSION = "Lazygit/0.44.0" + COPILOT_INTEGRATION_ID = "vscode-chat" +) +const ( + CACHE_FILE_NAME = ".copilot_auth.json" +) +const ( + CHECK_INTERVAL = 30 * time.Second + MAX_AUTH_TIME = 5 * time.Minute +) +const ( + GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98" +) + +type ChatMessage struct { + Role Role `json:"role"` + Content string `json:"content"` +} + +type Request struct { + Intent bool `json:"intent"` + N int `json:"n"` + Stream bool `json:"stream"` + Temperature float32 `json:"temperature"` + Model Model `json:"model"` + Messages []ChatMessage `json:"messages"` +} + +type ApiTokenResponse struct { + Token string `json:"token"` + ExpiresAt int64 `json:"expires_at"` +} + +type ApiToken struct { + ApiKey string + ExpiresAt time.Time +} +type CacheData struct { + OAuthToken string `json:"oauth_token"` + ApiKey string `json:"api_key"` + ExpiresAt time.Time `json:"expires_at"` +} +type DeviceCodeResponse struct { + DeviceCode string `json:"device_code"` + UserCode string `json:"user_code"` + VerificationUri string `json:"verification_uri"` + ExpiresIn int `json:"expires_in"` + Interval int `json:"interval"` +} + +type DeviceTokenResponse struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + Scope string `json:"scope"` + Error string `json:"error,omitempty"` +} + +type CopilotChat struct { + OAuthToken string + ApiToken *ApiToken + Client *http.Client + mu sync.Mutex +} + +func NewCopilotChat(client *http.Client) *CopilotChat { + if client == nil { + client = &http.Client{} + } + + chat := &CopilotChat{ + Client: client, + } + + if err := chat.loadFromCache(); err != nil { + log.Printf("Warning: Failed to load from cache: %v", err) + } + + return chat +} + +func getCacheFilePath() (string, error) { + execPath, err := os.Executable() + if err != nil { + return "", fmt.Errorf("failed to get executable path: %v", err) + } + return filepath.Join(filepath.Dir(execPath), CACHE_FILE_NAME), nil +} + +func (self *CopilotChat) saveToCache() error { + cacheFilePath, err := getCacheFilePath() + if err != nil { + return err + } + + data := CacheData{ + OAuthToken: self.OAuthToken, + ApiKey: self.ApiToken.ApiKey, + ExpiresAt: self.ApiToken.ExpiresAt, + } + + fileData, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("failed to marshal cache data: %v", err) + } + + if err := os.WriteFile(cacheFilePath, fileData, 0600); err != nil { + return fmt.Errorf("failed to write cache file: %v", err) + } + + return nil +} + +func (self *CopilotChat) loadFromCache() error { + cacheFilePath, err := getCacheFilePath() + if err != nil { + return err + } + + fileData, err := os.ReadFile(cacheFilePath) + if err != nil { + if os.IsNotExist(err) { + return nil // Cache doesn't exist yet, not an error + } + return fmt.Errorf("failed to read cache file: %v", err) + } + + var data CacheData + if err := json.Unmarshal(fileData, &data); err != nil { + return fmt.Errorf("failed to unmarshal cache data: %v", err) + } + + // Always load OAuth token if it exists + if data.OAuthToken != "" { + self.OAuthToken = data.OAuthToken + } + + // If we have a valid API key, use it + if data.ApiKey != "" && data.ExpiresAt.After(time.Now()) { + self.ApiToken = &ApiToken{ + ApiKey: data.ApiKey, + ExpiresAt: data.ExpiresAt, + } + fmt.Println("Loaded valid API key from cache") + return nil + } + + // If we have OAuth token but no valid API key, fetch a new one + if self.OAuthToken != "" { + fmt.Println("OAuth token found, fetching new API key...") + apiTokenReq, err := http.NewRequest(http.MethodGet, COPILOT_CHAT_AUTH_URL, nil) + if err != nil { + return fmt.Errorf("failed to create API token request: %v", err) + } + apiTokenReq.Header.Set("Authorization", fmt.Sprintf("token %s", self.OAuthToken)) + apiTokenReq.Header.Set("Accept", "application/json") + apiTokenReq.Header.Set("Editor-Version", EDITOR_VERSION) + apiTokenReq.Header.Set("Copilot-Integration-Id", COPILOT_INTEGRATION_ID) + + apiTokenResp, err := self.Client.Do(apiTokenReq) + if err != nil { + return fmt.Errorf("failed to get API token: %v", err) + } + defer apiTokenResp.Body.Close() + + var apiTokenResponse ApiTokenResponse + if err := json.NewDecoder(apiTokenResp.Body).Decode(&apiTokenResponse); err != nil { + return fmt.Errorf("failed to decode API token response: %v", err) + } + + self.ApiToken = &ApiToken{ + ApiKey: apiTokenResponse.Token, + ExpiresAt: time.Unix(apiTokenResponse.ExpiresAt, 0), + } + + // Save the new API key to cache + if err := self.saveToCache(); err != nil { + log.Printf("Warning: Failed to save new API key to cache: %v", err) + } + + fmt.Println("Successfully fetched new API key") + + return nil + } + + return nil +} + +func (self *CopilotChat) Authenticate() error { + // Try to load from cache first + if err := self.loadFromCache(); err == nil { + return nil + } + + self.mu.Lock() + defer self.mu.Unlock() + + // Step 1: Request device and user codes + deviceCodeReq, err := http.NewRequest( + http.MethodPost, + "https://github.com/login/device/code", + strings.NewReader(fmt.Sprintf( + "client_id=%s&scope=copilot", + GITHUB_CLIENT_ID, + )), + ) + if err != nil { + return fmt.Errorf("failed to create device code request: %v", err) + } + deviceCodeReq.Header.Set("Accept", "application/json") + deviceCodeReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + resp, err := self.Client.Do(deviceCodeReq) + if err != nil { + return fmt.Errorf("failed to get device code: %v", err) + } + defer resp.Body.Close() + + var deviceCode DeviceCodeResponse + if err := json.NewDecoder(resp.Body).Decode(&deviceCode); err != nil { + return fmt.Errorf("failed to decode device code response: %v", err) + } + + // Step 2: Display user code and verification URL + fmt.Printf("\nPlease visit: %s\n", deviceCode.VerificationUri) + fmt.Printf("And enter code: %s\n\n", deviceCode.UserCode) + + // Step 3: Poll for the access token with timeout + startTime := time.Now() + attempts := 0 + + for { + if time.Since(startTime) >= MAX_AUTH_TIME { + return fmt.Errorf("authentication timed out after 5 minutes") + } + + time.Sleep(CHECK_INTERVAL) + attempts++ + fmt.Printf("Checking for authentication... attempt %d\n", attempts) + + tokenReq, err := http.NewRequest(http.MethodPost, "https://github.com/login/oauth/access_token", + strings.NewReader(fmt.Sprintf( + "client_id=%s&device_code=%s&grant_type=urn:ietf:params:oauth:grant-type:device_code", GITHUB_CLIENT_ID, + deviceCode.DeviceCode))) + if err != nil { + return fmt.Errorf("failed to create token request: %v", err) + } + tokenReq.Header.Set("Accept", "application/json") + tokenReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + tokenResp, err := self.Client.Do(tokenReq) + if err != nil { + return fmt.Errorf("failed to get access token: %v", err) + } + + var tokenResponse DeviceTokenResponse + if err := json.NewDecoder(tokenResp.Body).Decode(&tokenResponse); err != nil { + tokenResp.Body.Close() + return fmt.Errorf("failed to decode token response: %v", err) + } + tokenResp.Body.Close() + + if tokenResponse.Error == "authorization_pending" { + fmt.Println("Login not detected. Please visit the URL and enter the code.") + continue + } + if tokenResponse.Error != "" { + if time.Since(startTime) >= MAX_AUTH_TIME { + return fmt.Errorf("authentication timed out after 5 minutes") + } + continue + } + + // Successfully got the access token + self.OAuthToken = tokenResponse.AccessToken + + // Now get the Copilot API token + apiTokenReq, err := http.NewRequest(http.MethodGet, COPILOT_CHAT_AUTH_URL, nil) + if err != nil { + return fmt.Errorf("failed to create API token request: %v", err) + } + apiTokenReq.Header.Set("Authorization", fmt.Sprintf("token %s", self.OAuthToken)) + apiTokenReq.Header.Set("Accept", "application/json") + apiTokenReq.Header.Set("Editor-Version", EDITOR_VERSION) + apiTokenReq.Header.Set("Copilot-Integration-Id", COPILOT_INTEGRATION_ID) + + apiTokenResp, err := self.Client.Do(apiTokenReq) + if err != nil { + return fmt.Errorf("failed to get API token: %v", err) + } + defer apiTokenResp.Body.Close() + + var apiTokenResponse ApiTokenResponse + + var buf bytes.Buffer + tee := io.TeeReader(apiTokenResp.Body, &buf) + + // Debug response + respBody, _ := io.ReadAll(tee) + fmt.Printf("API Token Response: %s\n", string(respBody)) + + // Decode using the buffer + if err := json.Unmarshal(buf.Bytes(), &apiTokenResponse); err != nil { + return fmt.Errorf("failed to decode API token response: %v", err) + } + + // Re-create reader for JSON decoding + apiTokenResp.Body = io.NopCloser(bytes.NewBuffer(respBody)) + + self.ApiToken = &ApiToken{ + ApiKey: apiTokenResponse.Token, + ExpiresAt: time.Unix(apiTokenResponse.ExpiresAt, 0), + } + + fmt.Println("Successfully authenticated!") + // Save the new credentials to cache + if err := self.saveToCache(); err != nil { + log.Printf("Warning: Failed to save credentials to cache: %v", err) + } + return nil + } +} + +func (self *CopilotChat) IsAuthenticated() bool { + if self.ApiToken == nil { + return false + } + return self.ApiToken.ExpiresAt.After(time.Now()) +} + +func (self *CopilotChat) Chat(request Request) (string, error) { + fmt.Println("Chatting with Copilot...") + + if !self.IsAuthenticated() { + fmt.Println("Not authenticated with Copilot. Authenticating...") + if err := self.Authenticate(); err != nil { + return "", fmt.Errorf("authentication failed: %v", err) + } + } + + apiKey := self.ApiToken.ApiKey + fmt.Println("Authenticated with Copilot!") + fmt.Println("API Key: ", apiKey) + + litter.Dump(self) + + requestBody, err := json.Marshal(request) + if err != nil { + return "", err + } + fmt.Println("Mounting request body: ", string(requestBody)) + + self.mu.Lock() + defer self.mu.Unlock() + + req, err := http.NewRequest(http.MethodPost, COPILOT_CHAT_COMPLETION_URL, strings.NewReader(string(requestBody))) + if err != nil { + return "", err + } + + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Editor-Version", EDITOR_VERSION) + req.Header.Set("Copilot-Integration-Id", COPILOT_INTEGRATION_ID) + + response, err := self.Client.Do(req) + if err != nil { + return "", err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + body, _ := io.ReadAll(response.Body) + return "", fmt.Errorf("failed to get completion: %s", string(body)) + } + + responseBody, err := io.ReadAll(response.Body) + if err != nil { + return "", err + } + + return string(responseBody), nil +} + +func main() { + client := &http.Client{} + fmt.Println("Starting...") + copilotChat := NewCopilotChat(client) + + fmt.Println("Chatting...") + + err := copilotChat.Authenticate() + if err != nil { + if strings.Contains(err.Error(), "timed out") { + log.Fatalf("Authentication process timed out. Please try again later.") + } + log.Fatalf("Error during authentication: %v", err) + } + + fmt.Println("Authenticated!") + + messages := []ChatMessage{ + { + Role: RoleUser, + Content: "Describe what is Lazygit in one sentence", + }, + } + + request := Request{ + Intent: true, + N: 1, + Stream: false, + Temperature: 0.1, + Model: Gpt4o, + Messages: messages, + } + + response, err := copilotChat.Chat(request) + if err != nil { + log.Fatalf("Error during chat: %v", err) + } + + fmt.Println(response) +} From 9a225b980b1d934e95fb44ac68513615eeebcce1 Mon Sep 17 00:00:00 2001 From: nathabonfim59 Date: Fri, 8 Nov 2024 02:24:51 -0300 Subject: [PATCH 2/9] feat(copilot): structured responses and error treatments --- cmd/copilot/main.go | 70 +++++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/cmd/copilot/main.go b/cmd/copilot/main.go index 68d926c1539..67fe72ac7fa 100644 --- a/cmd/copilot/main.go +++ b/cmd/copilot/main.go @@ -1,7 +1,6 @@ package main import ( - "bytes" "encoding/json" "fmt" "io" @@ -75,6 +74,46 @@ type Request struct { Messages []ChatMessage `json:"messages"` } +type ContentFilterResult struct { + Filtered bool `json:"filtered"` + Severity string `json:"severity"` +} + +type ContentFilterResults struct { + Hate ContentFilterResult `json:"hate"` + SelfHarm ContentFilterResult `json:"self_harm"` + Sexual ContentFilterResult `json:"sexual"` + Violence ContentFilterResult `json:"violence"` +} + +type ChatResponse struct { + Choices []ResponseChoice `json:"choices"` + Created int64 `json:"created"` + ID string `json:"id"` + Model string `json:"model"` + SystemFingerprint string `json:"system_fingerprint"` + PromptFilterResults []PromptFilterResult `json:"prompt_filter_results"` + Usage Usage `json:"usage"` +} + +type ResponseChoice struct { + ContentFilterResults ContentFilterResults `json:"content_filter_results"` + FinishReason string `json:"finish_reason"` + Index int `json:"index"` + Message ChatMessage `json:"message"` +} + +type PromptFilterResult struct { + ContentFilterResults ContentFilterResults `json:"content_filter_results"` + PromptIndex int `json:"prompt_index"` +} + +type Usage struct { + CompletionTokens int `json:"completion_tokens"` + PromptTokens int `json:"prompt_tokens"` + TotalTokens int `json:"total_tokens"` +} + type ApiTokenResponse struct { Token string `json:"token"` ExpiresAt int64 `json:"expires_at"` @@ -236,7 +275,7 @@ func (self *CopilotChat) loadFromCache() error { func (self *CopilotChat) Authenticate() error { // Try to load from cache first - if err := self.loadFromCache(); err == nil { + if err := self.loadFromCache(); err == nil && self.IsAuthenticated() { return nil } @@ -339,22 +378,10 @@ func (self *CopilotChat) Authenticate() error { defer apiTokenResp.Body.Close() var apiTokenResponse ApiTokenResponse - - var buf bytes.Buffer - tee := io.TeeReader(apiTokenResp.Body, &buf) - - // Debug response - respBody, _ := io.ReadAll(tee) - fmt.Printf("API Token Response: %s\n", string(respBody)) - - // Decode using the buffer - if err := json.Unmarshal(buf.Bytes(), &apiTokenResponse); err != nil { + if err := json.NewDecoder(apiTokenResp.Body).Decode(&apiTokenResponse); err != nil { return fmt.Errorf("failed to decode API token response: %v", err) } - // Re-create reader for JSON decoding - apiTokenResp.Body = io.NopCloser(bytes.NewBuffer(respBody)) - self.ApiToken = &ApiToken{ ApiKey: apiTokenResponse.Token, ExpiresAt: time.Unix(apiTokenResponse.ExpiresAt, 0), @@ -422,12 +449,17 @@ func (self *CopilotChat) Chat(request Request) (string, error) { return "", fmt.Errorf("failed to get completion: %s", string(body)) } - responseBody, err := io.ReadAll(response.Body) - if err != nil { - return "", err + var chatResponse ChatResponse + decoder := json.NewDecoder(response.Body) + if err := decoder.Decode(&chatResponse); err != nil { + return "", fmt.Errorf("failed to decode response: %v", err) + } + + if len(chatResponse.Choices) == 0 { + return "", fmt.Errorf("no choices in response") } - return string(responseBody), nil + return chatResponse.Choices[0].Message.Content, nil } func main() { From ef4da68cae0978f17d02ec5b6e31974a0f81b647 Mon Sep 17 00:00:00 2001 From: nathabonfim59 Date: Fri, 8 Nov 2024 02:32:16 -0300 Subject: [PATCH 3/9] refactor(copilot): set common headers in one place --- cmd/copilot/main.go | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/cmd/copilot/main.go b/cmd/copilot/main.go index 67fe72ac7fa..a382e170c6f 100644 --- a/cmd/copilot/main.go +++ b/cmd/copilot/main.go @@ -240,9 +240,7 @@ func (self *CopilotChat) loadFromCache() error { return fmt.Errorf("failed to create API token request: %v", err) } apiTokenReq.Header.Set("Authorization", fmt.Sprintf("token %s", self.OAuthToken)) - apiTokenReq.Header.Set("Accept", "application/json") - apiTokenReq.Header.Set("Editor-Version", EDITOR_VERSION) - apiTokenReq.Header.Set("Copilot-Integration-Id", COPILOT_INTEGRATION_ID) + setHeaders(apiTokenReq, "") apiTokenResp, err := self.Client.Do(apiTokenReq) if err != nil { @@ -273,6 +271,17 @@ func (self *CopilotChat) loadFromCache() error { return nil } + + +func setHeaders(req *http.Request, contentType string) { + req.Header.Set("Accept", "application/json") + if contentType != "" { + req.Header.Set("Content-Type", contentType) + } + req.Header.Set("Editor-Version", EDITOR_VERSION) + req.Header.Set("Copilot-Integration-Id", COPILOT_INTEGRATION_ID) +} + func (self *CopilotChat) Authenticate() error { // Try to load from cache first if err := self.loadFromCache(); err == nil && self.IsAuthenticated() { @@ -367,9 +376,7 @@ func (self *CopilotChat) Authenticate() error { return fmt.Errorf("failed to create API token request: %v", err) } apiTokenReq.Header.Set("Authorization", fmt.Sprintf("token %s", self.OAuthToken)) - apiTokenReq.Header.Set("Accept", "application/json") - apiTokenReq.Header.Set("Editor-Version", EDITOR_VERSION) - apiTokenReq.Header.Set("Copilot-Integration-Id", COPILOT_INTEGRATION_ID) + setHeaders(apiTokenReq, "") apiTokenResp, err := self.Client.Do(apiTokenReq) if err != nil { @@ -434,9 +441,7 @@ func (self *CopilotChat) Chat(request Request) (string, error) { } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey)) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Editor-Version", EDITOR_VERSION) - req.Header.Set("Copilot-Integration-Id", COPILOT_INTEGRATION_ID) + setHeaders(req, "") response, err := self.Client.Do(req) if err != nil { From 960dbf027c888f2e848aa9fb1a16713998433f13 Mon Sep 17 00:00:00 2001 From: nathabonfim59 Date: Fri, 8 Nov 2024 02:38:45 -0300 Subject: [PATCH 4/9] refactor(copilot): contralize ApiToken in one place --- cmd/copilot/main.go | 81 ++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 49 deletions(-) diff --git a/cmd/copilot/main.go b/cmd/copilot/main.go index a382e170c6f..c9828275eb2 100644 --- a/cmd/copilot/main.go +++ b/cmd/copilot/main.go @@ -235,42 +235,44 @@ func (self *CopilotChat) loadFromCache() error { // If we have OAuth token but no valid API key, fetch a new one if self.OAuthToken != "" { fmt.Println("OAuth token found, fetching new API key...") - apiTokenReq, err := http.NewRequest(http.MethodGet, COPILOT_CHAT_AUTH_URL, nil) - if err != nil { - return fmt.Errorf("failed to create API token request: %v", err) + if err := self.fetchNewApiToken(); err != nil { + return fmt.Errorf("failed to fetch new API token: %v", err) } - apiTokenReq.Header.Set("Authorization", fmt.Sprintf("token %s", self.OAuthToken)) - setHeaders(apiTokenReq, "") + fmt.Println("Successfully fetched new API key") + return nil + } - apiTokenResp, err := self.Client.Do(apiTokenReq) - if err != nil { - return fmt.Errorf("failed to get API token: %v", err) - } - defer apiTokenResp.Body.Close() + return nil +} - var apiTokenResponse ApiTokenResponse - if err := json.NewDecoder(apiTokenResp.Body).Decode(&apiTokenResponse); err != nil { - return fmt.Errorf("failed to decode API token response: %v", err) - } - self.ApiToken = &ApiToken{ - ApiKey: apiTokenResponse.Token, - ExpiresAt: time.Unix(apiTokenResponse.ExpiresAt, 0), - } +func (self *CopilotChat) fetchNewApiToken() error { + apiTokenReq, err := http.NewRequest(http.MethodGet, COPILOT_CHAT_AUTH_URL, nil) + if err != nil { + return fmt.Errorf("failed to create API token request: %v", err) + } - // Save the new API key to cache - if err := self.saveToCache(); err != nil { - log.Printf("Warning: Failed to save new API key to cache: %v", err) - } + apiTokenReq.Header.Set("Authorization", fmt.Sprintf("token %s", self.OAuthToken)) + setHeaders(apiTokenReq, "") - fmt.Println("Successfully fetched new API key") + apiTokenResp, err := self.Client.Do(apiTokenReq) + if err != nil { + return fmt.Errorf("failed to get API token: %v", err) + } + defer apiTokenResp.Body.Close() - return nil - } + var apiTokenResponse ApiTokenResponse + if err := json.NewDecoder(apiTokenResp.Body).Decode(&apiTokenResponse); err != nil { + return fmt.Errorf("failed to decode API token response: %v", err) + } - return nil -} + self.ApiToken = &ApiToken{ + ApiKey: apiTokenResponse.Token, + ExpiresAt: time.Unix(apiTokenResponse.ExpiresAt, 0), + } + return self.saveToCache() +} func setHeaders(req *http.Request, contentType string) { @@ -370,28 +372,9 @@ func (self *CopilotChat) Authenticate() error { // Successfully got the access token self.OAuthToken = tokenResponse.AccessToken - // Now get the Copilot API token - apiTokenReq, err := http.NewRequest(http.MethodGet, COPILOT_CHAT_AUTH_URL, nil) - if err != nil { - return fmt.Errorf("failed to create API token request: %v", err) - } - apiTokenReq.Header.Set("Authorization", fmt.Sprintf("token %s", self.OAuthToken)) - setHeaders(apiTokenReq, "") - - apiTokenResp, err := self.Client.Do(apiTokenReq) - if err != nil { - return fmt.Errorf("failed to get API token: %v", err) - } - defer apiTokenResp.Body.Close() - - var apiTokenResponse ApiTokenResponse - if err := json.NewDecoder(apiTokenResp.Body).Decode(&apiTokenResponse); err != nil { - return fmt.Errorf("failed to decode API token response: %v", err) - } - - self.ApiToken = &ApiToken{ - ApiKey: apiTokenResponse.Token, - ExpiresAt: time.Unix(apiTokenResponse.ExpiresAt, 0), + // Now get the Copilot API token using fetchNewApiToken + if err := self.fetchNewApiToken(); err != nil { + return fmt.Errorf("failed to fetch API token: %v", err) } fmt.Println("Successfully authenticated!") From 72ff5eab1b26f4bc96b1ca83dbba62e201a3d9d6 Mon Sep 17 00:00:00 2001 From: nathabonfim59 Date: Fri, 8 Nov 2024 02:53:34 -0300 Subject: [PATCH 5/9] WIP(copilot): add model token limits --- cmd/copilot/main.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/cmd/copilot/main.go b/cmd/copilot/main.go index c9828275eb2..8509da9b8a7 100644 --- a/cmd/copilot/main.go +++ b/cmd/copilot/main.go @@ -150,6 +150,27 @@ type CopilotChat struct { mu sync.Mutex } +// TODO: import a library to count the number of tokens in a string +func (m Model) MaxTokenCount() int { + switch m { + case Gpt4o: + return 64000 + case Gpt4: + return 32768 + case Gpt3_5Turbo: + return 12288 + case O1Mini: + return 20000 + case O1Preview: + return 20000 + case Claude3_5Sonnet: + return 200000 + default: + return 0 + } +} + + func NewCopilotChat(client *http.Client) *CopilotChat { if client == nil { client = &http.Client{} @@ -327,6 +348,7 @@ func (self *CopilotChat) Authenticate() error { startTime := time.Now() attempts := 0 + // FIXME: There is probably a better way to do this for { if time.Since(startTime) >= MAX_AUTH_TIME { return fmt.Errorf("authentication timed out after 5 minutes") From 98073dc09ae9d312b90f65fa690394d39ff68fed Mon Sep 17 00:00:00 2001 From: nathabonfim59 Date: Sun, 17 Nov 2024 01:25:24 -0300 Subject: [PATCH 6/9] lib(copilot): add `zelando/go-keyring` 0.2.6 for saving copilot's access token --- go.mod | 8 +- go.sum | 24 +- .../al.essio.dev/pkg/shellescape/.gitignore | 32 + .../pkg/shellescape/.golangci.yml | 59 ++ .../pkg/shellescape/.goreleaser.yml | 54 + vendor/al.essio.dev/pkg/shellescape/AUTHORS | 1 + .../pkg/shellescape/CODE_OF_CONDUCT.md | 76 ++ vendor/al.essio.dev/pkg/shellescape/LICENSE | 21 + vendor/al.essio.dev/pkg/shellescape/Makefile | 25 + vendor/al.essio.dev/pkg/shellescape/README.md | 61 ++ .../pkg/shellescape/shellescape.go | 66 ++ .../danieljoos/wincred/.gitattributes | 1 + .../github.com/danieljoos/wincred/.gitignore | 25 + vendor/github.com/danieljoos/wincred/LICENSE | 21 + .../github.com/danieljoos/wincred/README.md | 145 +++ .../danieljoos/wincred/conversion.go | 116 ++ .../wincred/conversion_unsupported.go | 11 + vendor/github.com/danieljoos/wincred/sys.go | 148 +++ .../danieljoos/wincred/sys_unsupported.go | 38 + vendor/github.com/danieljoos/wincred/types.go | 69 ++ .../github.com/danieljoos/wincred/wincred.go | 114 ++ .../github.com/godbus/dbus/v5/CONTRIBUTING.md | 50 + vendor/github.com/godbus/dbus/v5/LICENSE | 25 + vendor/github.com/godbus/dbus/v5/MAINTAINERS | 3 + vendor/github.com/godbus/dbus/v5/README.md | 46 + vendor/github.com/godbus/dbus/v5/auth.go | 257 +++++ .../godbus/dbus/v5/auth_anonymous.go | 16 + .../godbus/dbus/v5/auth_external.go | 26 + vendor/github.com/godbus/dbus/v5/auth_sha1.go | 102 ++ vendor/github.com/godbus/dbus/v5/call.go | 69 ++ vendor/github.com/godbus/dbus/v5/conn.go | 996 ++++++++++++++++++ .../github.com/godbus/dbus/v5/conn_darwin.go | 37 + .../github.com/godbus/dbus/v5/conn_other.go | 90 ++ vendor/github.com/godbus/dbus/v5/conn_unix.go | 17 + .../github.com/godbus/dbus/v5/conn_windows.go | 15 + vendor/github.com/godbus/dbus/v5/dbus.go | 430 ++++++++ vendor/github.com/godbus/dbus/v5/decoder.go | 292 +++++ .../godbus/dbus/v5/default_handler.go | 342 ++++++ vendor/github.com/godbus/dbus/v5/doc.go | 71 ++ vendor/github.com/godbus/dbus/v5/encoder.go | 235 +++++ vendor/github.com/godbus/dbus/v5/escape.go | 84 ++ vendor/github.com/godbus/dbus/v5/export.go | 463 ++++++++ vendor/github.com/godbus/dbus/v5/homedir.go | 25 + vendor/github.com/godbus/dbus/v5/match.go | 89 ++ vendor/github.com/godbus/dbus/v5/message.go | 390 +++++++ vendor/github.com/godbus/dbus/v5/object.go | 174 +++ vendor/github.com/godbus/dbus/v5/sequence.go | 24 + .../godbus/dbus/v5/sequential_handler.go | 125 +++ .../godbus/dbus/v5/server_interfaces.go | 107 ++ vendor/github.com/godbus/dbus/v5/sig.go | 293 ++++++ .../godbus/dbus/v5/transport_darwin.go | 6 + .../godbus/dbus/v5/transport_generic.go | 52 + .../godbus/dbus/v5/transport_nonce_tcp.go | 39 + .../godbus/dbus/v5/transport_tcp.go | 41 + .../godbus/dbus/v5/transport_unix.go | 212 ++++ .../dbus/v5/transport_unixcred_dragonfly.go | 95 ++ .../dbus/v5/transport_unixcred_freebsd.go | 92 ++ .../dbus/v5/transport_unixcred_linux.go | 25 + .../dbus/v5/transport_unixcred_netbsd.go | 14 + .../dbus/v5/transport_unixcred_openbsd.go | 14 + .../godbus/dbus/v5/transport_zos.go | 6 + vendor/github.com/godbus/dbus/v5/variant.go | 150 +++ .../godbus/dbus/v5/variant_lexer.go | 284 +++++ .../godbus/dbus/v5/variant_parser.go | 817 ++++++++++++++ .../testify/assert/assertion_compare.go | 64 +- .../assert/assertion_compare_can_convert.go | 16 - .../assert/assertion_compare_legacy.go | 16 - .../testify/assert/assertion_format.go | 244 +++-- .../testify/assert/assertion_forward.go | 483 +++++---- .../testify/assert/assertion_order.go | 24 +- .../stretchr/testify/assert/assertions.go | 541 +++++++--- .../github.com/stretchr/testify/assert/doc.go | 43 +- .../testify/assert/http_assertions.go | 39 +- .../github.com/zalando/go-keyring/.gitignore | 23 + .../zalando/go-keyring/CONTRIBUTING.md | 12 + vendor/github.com/zalando/go-keyring/LICENSE | 21 + .../github.com/zalando/go-keyring/MAINTAINERS | 2 + .../github.com/zalando/go-keyring/README.md | 144 +++ .../github.com/zalando/go-keyring/SECURITY.md | 8 + .../github.com/zalando/go-keyring/keyring.go | 50 + .../zalando/go-keyring/keyring_darwin.go | 140 +++ .../zalando/go-keyring/keyring_fallback.go | 27 + .../zalando/go-keyring/keyring_mock.go | 71 ++ .../zalando/go-keyring/keyring_unix.go | 182 ++++ .../zalando/go-keyring/keyring_windows.go | 103 ++ .../secret_service/secret_service.go | 257 +++++ vendor/golang.org/x/sys/unix/README.md | 2 +- vendor/golang.org/x/sys/unix/mkerrors.sh | 4 +- vendor/golang.org/x/sys/unix/syscall_aix.go | 2 +- vendor/golang.org/x/sys/unix/syscall_linux.go | 63 +- .../x/sys/unix/syscall_linux_arm64.go | 2 + .../x/sys/unix/syscall_linux_loong64.go | 2 + .../x/sys/unix/syscall_linux_riscv64.go | 2 + .../golang.org/x/sys/unix/vgetrandom_linux.go | 13 + .../x/sys/unix/vgetrandom_unsupported.go | 11 + vendor/golang.org/x/sys/unix/zerrors_linux.go | 13 +- .../x/sys/unix/zerrors_linux_386.go | 5 + .../x/sys/unix/zerrors_linux_amd64.go | 5 + .../x/sys/unix/zerrors_linux_arm.go | 5 + .../x/sys/unix/zerrors_linux_arm64.go | 5 + .../x/sys/unix/zerrors_linux_loong64.go | 5 + .../x/sys/unix/zerrors_linux_mips.go | 5 + .../x/sys/unix/zerrors_linux_mips64.go | 5 + .../x/sys/unix/zerrors_linux_mips64le.go | 5 + .../x/sys/unix/zerrors_linux_mipsle.go | 5 + .../x/sys/unix/zerrors_linux_ppc.go | 5 + .../x/sys/unix/zerrors_linux_ppc64.go | 5 + .../x/sys/unix/zerrors_linux_ppc64le.go | 5 + .../x/sys/unix/zerrors_linux_riscv64.go | 5 + .../x/sys/unix/zerrors_linux_s390x.go | 5 + .../x/sys/unix/zerrors_linux_sparc64.go | 5 + .../golang.org/x/sys/unix/zsyscall_linux.go | 17 - .../x/sys/unix/zsysnum_linux_amd64.go | 1 + .../x/sys/unix/zsysnum_linux_arm64.go | 2 +- .../x/sys/unix/zsysnum_linux_loong64.go | 2 + .../x/sys/unix/zsysnum_linux_riscv64.go | 2 +- vendor/golang.org/x/sys/unix/ztypes_linux.go | 88 +- .../golang.org/x/sys/windows/dll_windows.go | 2 +- vendor/modules.txt | 19 +- 119 files changed, 10077 insertions(+), 610 deletions(-) create mode 100644 vendor/al.essio.dev/pkg/shellescape/.gitignore create mode 100644 vendor/al.essio.dev/pkg/shellescape/.golangci.yml create mode 100644 vendor/al.essio.dev/pkg/shellescape/.goreleaser.yml create mode 100644 vendor/al.essio.dev/pkg/shellescape/AUTHORS create mode 100644 vendor/al.essio.dev/pkg/shellescape/CODE_OF_CONDUCT.md create mode 100644 vendor/al.essio.dev/pkg/shellescape/LICENSE create mode 100644 vendor/al.essio.dev/pkg/shellescape/Makefile create mode 100644 vendor/al.essio.dev/pkg/shellescape/README.md create mode 100644 vendor/al.essio.dev/pkg/shellescape/shellescape.go create mode 100644 vendor/github.com/danieljoos/wincred/.gitattributes create mode 100644 vendor/github.com/danieljoos/wincred/.gitignore create mode 100644 vendor/github.com/danieljoos/wincred/LICENSE create mode 100644 vendor/github.com/danieljoos/wincred/README.md create mode 100644 vendor/github.com/danieljoos/wincred/conversion.go create mode 100644 vendor/github.com/danieljoos/wincred/conversion_unsupported.go create mode 100644 vendor/github.com/danieljoos/wincred/sys.go create mode 100644 vendor/github.com/danieljoos/wincred/sys_unsupported.go create mode 100644 vendor/github.com/danieljoos/wincred/types.go create mode 100644 vendor/github.com/danieljoos/wincred/wincred.go create mode 100644 vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md create mode 100644 vendor/github.com/godbus/dbus/v5/LICENSE create mode 100644 vendor/github.com/godbus/dbus/v5/MAINTAINERS create mode 100644 vendor/github.com/godbus/dbus/v5/README.md create mode 100644 vendor/github.com/godbus/dbus/v5/auth.go create mode 100644 vendor/github.com/godbus/dbus/v5/auth_anonymous.go create mode 100644 vendor/github.com/godbus/dbus/v5/auth_external.go create mode 100644 vendor/github.com/godbus/dbus/v5/auth_sha1.go create mode 100644 vendor/github.com/godbus/dbus/v5/call.go create mode 100644 vendor/github.com/godbus/dbus/v5/conn.go create mode 100644 vendor/github.com/godbus/dbus/v5/conn_darwin.go create mode 100644 vendor/github.com/godbus/dbus/v5/conn_other.go create mode 100644 vendor/github.com/godbus/dbus/v5/conn_unix.go create mode 100644 vendor/github.com/godbus/dbus/v5/conn_windows.go create mode 100644 vendor/github.com/godbus/dbus/v5/dbus.go create mode 100644 vendor/github.com/godbus/dbus/v5/decoder.go create mode 100644 vendor/github.com/godbus/dbus/v5/default_handler.go create mode 100644 vendor/github.com/godbus/dbus/v5/doc.go create mode 100644 vendor/github.com/godbus/dbus/v5/encoder.go create mode 100644 vendor/github.com/godbus/dbus/v5/escape.go create mode 100644 vendor/github.com/godbus/dbus/v5/export.go create mode 100644 vendor/github.com/godbus/dbus/v5/homedir.go create mode 100644 vendor/github.com/godbus/dbus/v5/match.go create mode 100644 vendor/github.com/godbus/dbus/v5/message.go create mode 100644 vendor/github.com/godbus/dbus/v5/object.go create mode 100644 vendor/github.com/godbus/dbus/v5/sequence.go create mode 100644 vendor/github.com/godbus/dbus/v5/sequential_handler.go create mode 100644 vendor/github.com/godbus/dbus/v5/server_interfaces.go create mode 100644 vendor/github.com/godbus/dbus/v5/sig.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_darwin.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_generic.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_nonce_tcp.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_tcp.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_unix.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_unixcred_dragonfly.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_unixcred_freebsd.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_unixcred_linux.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_unixcred_netbsd.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_unixcred_openbsd.go create mode 100644 vendor/github.com/godbus/dbus/v5/transport_zos.go create mode 100644 vendor/github.com/godbus/dbus/v5/variant.go create mode 100644 vendor/github.com/godbus/dbus/v5/variant_lexer.go create mode 100644 vendor/github.com/godbus/dbus/v5/variant_parser.go delete mode 100644 vendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go delete mode 100644 vendor/github.com/stretchr/testify/assert/assertion_compare_legacy.go create mode 100644 vendor/github.com/zalando/go-keyring/.gitignore create mode 100644 vendor/github.com/zalando/go-keyring/CONTRIBUTING.md create mode 100644 vendor/github.com/zalando/go-keyring/LICENSE create mode 100644 vendor/github.com/zalando/go-keyring/MAINTAINERS create mode 100644 vendor/github.com/zalando/go-keyring/README.md create mode 100644 vendor/github.com/zalando/go-keyring/SECURITY.md create mode 100644 vendor/github.com/zalando/go-keyring/keyring.go create mode 100644 vendor/github.com/zalando/go-keyring/keyring_darwin.go create mode 100644 vendor/github.com/zalando/go-keyring/keyring_fallback.go create mode 100644 vendor/github.com/zalando/go-keyring/keyring_mock.go create mode 100644 vendor/github.com/zalando/go-keyring/keyring_unix.go create mode 100644 vendor/github.com/zalando/go-keyring/keyring_windows.go create mode 100644 vendor/github.com/zalando/go-keyring/secret_service/secret_service.go create mode 100644 vendor/golang.org/x/sys/unix/vgetrandom_linux.go create mode 100644 vendor/golang.org/x/sys/unix/vgetrandom_unsupported.go diff --git a/go.mod b/go.mod index 95587f286ba..716ca1ca65a 100644 --- a/go.mod +++ b/go.mod @@ -35,8 +35,9 @@ require ( github.com/spf13/afero v1.9.5 github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad github.com/stefanhaller/git-todo-parser v0.0.7-0.20240406123903-fd957137b6e2 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.9.0 github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 + github.com/zalando/go-keyring v0.2.6 golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 golang.org/x/sync v0.8.0 gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0 @@ -44,8 +45,10 @@ require ( ) require ( + al.essio.dev/pkg/shellescape v1.5.1 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect + github.com/danieljoos/wincred v1.2.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emirpasic/gods v1.12.0 // indirect github.com/fatih/color v1.9.0 // indirect @@ -54,6 +57,7 @@ require ( github.com/go-git/go-billy/v5 v5.0.0 // indirect github.com/go-logfmt/logfmt v0.5.0 // indirect github.com/gobwas/glob v0.2.3 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/google/go-cmp v0.5.6 // indirect github.com/invopop/jsonschema v0.10.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect @@ -75,7 +79,7 @@ require ( github.com/xanzy/ssh-agent v0.2.1 // indirect golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.25.0 // indirect + golang.org/x/sys v0.26.0 // indirect golang.org/x/term v0.24.0 // indirect golang.org/x/text v0.18.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 12ce5def99d..de9efcac8f2 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho= +al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -68,6 +70,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0= +github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -109,6 +113,8 @@ github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -161,6 +167,8 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -286,18 +294,16 @@ github.com/stefanhaller/git-todo-parser v0.0.7-0.20240406123903-fd957137b6e2 h1: github.com/stefanhaller/git-todo-parser v0.0.7-0.20240406123903-fd957137b6e2/go.mod h1:HFt9hGqMzgQ+gVxMKcvTvGaFz4Y0yYycqqAp2V3wcJY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= github.com/thoas/go-funk v0.9.1/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= github.com/urfave/cli v1.20.1-0.20180226030253-8e01ec4cd3e2/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -312,6 +318,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s= +github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -475,8 +483,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= diff --git a/vendor/al.essio.dev/pkg/shellescape/.gitignore b/vendor/al.essio.dev/pkg/shellescape/.gitignore new file mode 100644 index 00000000000..657fca3f9f2 --- /dev/null +++ b/vendor/al.essio.dev/pkg/shellescape/.gitignore @@ -0,0 +1,32 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +.idea/ + +escargs + +config.hcl + +.DS_Store diff --git a/vendor/al.essio.dev/pkg/shellescape/.golangci.yml b/vendor/al.essio.dev/pkg/shellescape/.golangci.yml new file mode 100644 index 00000000000..836dabbba4f --- /dev/null +++ b/vendor/al.essio.dev/pkg/shellescape/.golangci.yml @@ -0,0 +1,59 @@ +# run: +# # timeout for analysis, e.g. 30s, 5m, default is 1m +# timeout: 5m + +linters: + disable-all: true + enable: + - bodyclose + - dogsled + - goconst + - gocritic + - gofmt + - goimports + - gosec + - gosimple + - govet + - ineffassign + - misspell + - prealloc + - exportloopref + - revive + - staticcheck + - stylecheck + - typecheck + - unconvert + - unparam + - unused + - misspell + - wsl + +issues: + exclude-rules: + - text: "Use of weak random number generator" + linters: + - gosec + - text: "comment on exported var" + linters: + - golint + - text: "don't use an underscore in package name" + linters: + - golint + - text: "ST1003:" + linters: + - stylecheck + # FIXME: Disabled until golangci-lint updates stylecheck with this fix: + # https://github.com/dominikh/go-tools/issues/389 + - text: "ST1016:" + linters: + - stylecheck + +linters-settings: + dogsled: + max-blank-identifiers: 3 + maligned: + # print struct with more effective memory layout or not, false by default + suggest-new: true + +run: + tests: false diff --git a/vendor/al.essio.dev/pkg/shellescape/.goreleaser.yml b/vendor/al.essio.dev/pkg/shellescape/.goreleaser.yml new file mode 100644 index 00000000000..0915eb869b4 --- /dev/null +++ b/vendor/al.essio.dev/pkg/shellescape/.goreleaser.yml @@ -0,0 +1,54 @@ +# This is an example goreleaser.yaml file with some sane defaults. +# Make sure to check the documentation at http://goreleaser.com +before: + hooks: + # You may remove this if you don't use go modules. + - go mod download + # you may remove this if you don't need go generate + - go generate ./... +builds: + - env: + - CGO_ENABLED=0 + - >- + {{- if eq .Os "darwin" }} + {{- if eq .Arch "amd64"}}CC=o64-clang{{- end }} + {{- if eq .Arch "arm64"}}CC=aarch64-apple-darwin20.2-clang{{- end }} + {{- end }} + {{- if eq .Os "windows" }} + {{- if eq .Arch "amd64" }}CC=x86_64-w64-mingw32-gcc{{- end }} + {{- end }} + main: ./cmd/escargs + goos: + - linux + - windows + - darwin + - freebsd + goarch: + - amd64 + - arm64 + - arm + goarm: + - 6 + - 7 + goamd64: + - v2 + - v3 + ignore: + - goos: darwin + goarch: 386 + - goos: linux + goarch: arm + goarm: 7 + - goarm: mips64 + - gomips: hardfloat + - goamd64: v4 +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ .Tag }}-next" +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' diff --git a/vendor/al.essio.dev/pkg/shellescape/AUTHORS b/vendor/al.essio.dev/pkg/shellescape/AUTHORS new file mode 100644 index 00000000000..4a647a6f41b --- /dev/null +++ b/vendor/al.essio.dev/pkg/shellescape/AUTHORS @@ -0,0 +1 @@ +Alessio Treglia diff --git a/vendor/al.essio.dev/pkg/shellescape/CODE_OF_CONDUCT.md b/vendor/al.essio.dev/pkg/shellescape/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..e8eda606200 --- /dev/null +++ b/vendor/al.essio.dev/pkg/shellescape/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at alessio@debian.org. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/vendor/al.essio.dev/pkg/shellescape/LICENSE b/vendor/al.essio.dev/pkg/shellescape/LICENSE new file mode 100644 index 00000000000..9f760679f40 --- /dev/null +++ b/vendor/al.essio.dev/pkg/shellescape/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Alessio Treglia + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/al.essio.dev/pkg/shellescape/Makefile b/vendor/al.essio.dev/pkg/shellescape/Makefile new file mode 100644 index 00000000000..f92895c0f09 --- /dev/null +++ b/vendor/al.essio.dev/pkg/shellescape/Makefile @@ -0,0 +1,25 @@ + +#!/usr/bin/make -f + +VERSION := $(shell git describe) + +all: build + +build: + go build -a -v + +install: + go install ./cmd/escargs + +escargs: build + go build -v \ + -ldflags="-X 'main.version=$(VERSION)'" \ + ./cmd/escargs + +clean: + rm -rfv escargs + +uninstall: + rm -v $(shell go env GOPATH)/bin/escargs + +.PHONY: build clean install uninstall diff --git a/vendor/al.essio.dev/pkg/shellescape/README.md b/vendor/al.essio.dev/pkg/shellescape/README.md new file mode 100644 index 00000000000..f83dd5b16ca --- /dev/null +++ b/vendor/al.essio.dev/pkg/shellescape/README.md @@ -0,0 +1,61 @@ +![Build](https://github.com/alessio/shellescape/workflows/Build/badge.svg) +[![GoDoc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/alessio/shellescape?tab=overview) +[![sourcegraph](https://sourcegraph.com/github.com/alessio/shellescape/-/badge.svg)](https://sourcegraph.com/github.com/alessio/shellescape) +[![codecov](https://codecov.io/gh/alessio/shellescape/branch/master/graph/badge.svg)](https://codecov.io/gh/alessio/shellescape) +[![Coverage](https://gocover.io/_badge/github.com/alessio/shellescape)](https://gocover.io/github.com/alessio/shellescape) +[![Go Report Card](https://goreportcard.com/badge/github.com/alessio/shellescape)](https://goreportcard.com/report/github.com/alessio/shellescape) + +# shellescape +Escape arbitrary strings for safe use as command line arguments. +## Contents of the package + +This package provides the `shellescape.Quote()` function that returns a +shell-escaped copy of a string. This functionality could be helpful +in those cases where it is known that the output of a Go program will +be appended to/used in the context of shell programs' command line arguments. + +This work was inspired by the Python original package +[shellescape](https://pypi.python.org/pypi/shellescape). + +## Usage + +The following snippet shows a typical unsafe idiom: + +```go +package main + +import ( + "fmt" + "os" +) + +func main() { + fmt.Printf("ls -l %s\n", os.Args[1]) +} +``` +_[See in Go Playground](https://play.golang.org/p/Wj2WoUfH_d)_ + +Especially when creating pipeline of commands which might end up being +executed by a shell interpreter, it is particularly unsafe to not +escape arguments. + +`shellescape.Quote()` comes in handy and to safely escape strings: + +```go +package main + +import ( + "fmt" + "os" + + "al.essio.dev/pkg/shellescape" +) + +func main() { + fmt.Printf("ls -l %s\n", shellescape.Quote(os.Args[1])) +} +``` +_[See in Go Playground](https://go.dev/play/p/GeguukpSUTk)_ + +## The escargs utility +__escargs__ reads lines from the standard input and prints shell-escaped versions. Unlinke __xargs__, blank lines on the standard input are not discarded. diff --git a/vendor/al.essio.dev/pkg/shellescape/shellescape.go b/vendor/al.essio.dev/pkg/shellescape/shellescape.go new file mode 100644 index 00000000000..f3d0d9c0a2f --- /dev/null +++ b/vendor/al.essio.dev/pkg/shellescape/shellescape.go @@ -0,0 +1,66 @@ +/* +Package shellescape provides the shellescape.Quote to escape arbitrary +strings for a safe use as command line arguments in the most common +POSIX shells. + +The original Python package which this work was inspired by can be found +at https://pypi.python.org/pypi/shellescape. +*/ +package shellescape // "import al.essio.dev/pkg/shellescape" + +/* +The functionality provided by shellescape.Quote could be helpful +in those cases where it is known that the output of a Go program will +be appended to/used in the context of shell programs' command line arguments. +*/ + +import ( + "regexp" + "strings" + "unicode" +) + +var pattern *regexp.Regexp + +func init() { + pattern = regexp.MustCompile(`[^\w@%+=:,./-]`) +} + +// Quote returns a shell-escaped version of the string s. The returned value +// is a string that can safely be used as one token in a shell command line. +func Quote(s string) string { + if len(s) == 0 { + return "''" + } + + if pattern.MatchString(s) { + return "'" + strings.ReplaceAll(s, "'", "'\"'\"'") + "'" + } + + return s +} + +// QuoteCommand returns a shell-escaped version of the slice of strings. +// The returned value is a string that can safely be used as shell command arguments. +func QuoteCommand(args []string) string { + l := make([]string, len(args)) + + for i, s := range args { + l[i] = Quote(s) + } + + return strings.Join(l, " ") +} + +// StripUnsafe remove non-printable runes, e.g. control characters in +// a string that is meant for consumption by terminals that support +// control characters. +func StripUnsafe(s string) string { + return strings.Map(func(r rune) rune { + if unicode.IsPrint(r) { + return r + } + + return -1 + }, s) +} diff --git a/vendor/github.com/danieljoos/wincred/.gitattributes b/vendor/github.com/danieljoos/wincred/.gitattributes new file mode 100644 index 00000000000..d207b1802b2 --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/.gitattributes @@ -0,0 +1 @@ +*.go text eol=lf diff --git a/vendor/github.com/danieljoos/wincred/.gitignore b/vendor/github.com/danieljoos/wincred/.gitignore new file mode 100644 index 00000000000..6142c06916d --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/.gitignore @@ -0,0 +1,25 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test + +coverage.txt diff --git a/vendor/github.com/danieljoos/wincred/LICENSE b/vendor/github.com/danieljoos/wincred/LICENSE new file mode 100644 index 00000000000..2f436f1b30c --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Daniel Joos + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/danieljoos/wincred/README.md b/vendor/github.com/danieljoos/wincred/README.md new file mode 100644 index 00000000000..8a879b0cefe --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/README.md @@ -0,0 +1,145 @@ +wincred +======= + +Go wrapper around the Windows Credential Manager API functions. + +[![GitHub release](https://img.shields.io/github/release/danieljoos/wincred.svg?style=flat-square)](https://github.com/danieljoos/wincred/releases/latest) +[![Test Status](https://img.shields.io/github/actions/workflow/status/danieljoos/wincred/test.yml?label=test&logo=github&style=flat-square)](https://github.com/danieljoos/wincred/actions?query=workflow%3Atest) +[![Go Report Card](https://goreportcard.com/badge/github.com/danieljoos/wincred)](https://goreportcard.com/report/github.com/danieljoos/wincred) +[![Codecov](https://img.shields.io/codecov/c/github/danieljoos/wincred?logo=codecov&style=flat-square)](https://codecov.io/gh/danieljoos/wincred) +[![PkgGoDev](https://img.shields.io/badge/go.dev-docs-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/danieljoos/wincred) + +Installation +------------ + +```Go +go get github.com/danieljoos/wincred +``` + + +Usage +----- + +See the following examples: + +### Create and store a new generic credential object +```Go +package main + +import ( + "fmt" + "github.com/danieljoos/wincred" +) + +func main() { + cred := wincred.NewGenericCredential("myGoApplication") + cred.CredentialBlob = []byte("my secret") + err := cred.Write() + + if err != nil { + fmt.Println(err) + } +} +``` + +### Retrieve a credential object +```Go +package main + +import ( + "fmt" + "github.com/danieljoos/wincred" +) + +func main() { + cred, err := wincred.GetGenericCredential("myGoApplication") + if err == nil { + fmt.Println(string(cred.CredentialBlob)) + } +} +``` + +### Remove a credential object +```Go +package main + +import ( + "fmt" + "github.com/danieljoos/wincred" +) + +func main() { + cred, err := wincred.GetGenericCredential("myGoApplication") + if err != nil { + fmt.Println(err) + return + } + cred.Delete() +} +``` + +### List all available credentials +```Go +package main + +import ( + "fmt" + "github.com/danieljoos/wincred" +) + +func main() { + creds, err := wincred.List() + if err != nil { + fmt.Println(err) + return + } + for i := range(creds) { + fmt.Println(creds[i].TargetName) + } +} +``` + +Hints +----- + +### Encoding + +The credential objects simply store byte arrays without specific meaning or encoding. +For sharing between different applications, it might make sense to apply an explicit string encoding - for example **UTF-16 LE** (used nearly everywhere in the Win32 API). + +```Go +package main + +import ( + "fmt" + "os" + + "github.com/danieljoos/wincred" + "golang.org/x/text/encoding/unicode" + "golang.org/x/text/transform" +) + +func main() { + cred := wincred.NewGenericCredential("myGoApplication") + + encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder() + blob, _, err := transform.Bytes(encoder, []byte("mysecret")) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + cred.CredentialBlob = blob + err = cred.Write() + + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +``` + +### Limitations + +The size of a credential blob is limited to **2560 Bytes** by the Windows API. diff --git a/vendor/github.com/danieljoos/wincred/conversion.go b/vendor/github.com/danieljoos/wincred/conversion.go new file mode 100644 index 00000000000..bc04f50f1ff --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/conversion.go @@ -0,0 +1,116 @@ +// +build windows + +package wincred + +import ( + "encoding/binary" + "reflect" + "time" + "unsafe" + + syscall "golang.org/x/sys/windows" +) + +// utf16ToByte creates a byte array from a given UTF 16 char array. +func utf16ToByte(wstr []uint16) (result []byte) { + result = make([]byte, len(wstr)*2) + for i := range wstr { + binary.LittleEndian.PutUint16(result[(i*2):(i*2)+2], wstr[i]) + } + return +} + +// utf16FromString creates a UTF16 char array from a string. +func utf16FromString(str string) []uint16 { + res, err := syscall.UTF16FromString(str) + if err != nil { + return []uint16{} + } + return res +} + +// goBytes copies the given C byte array to a Go byte array (see `C.GoBytes`). +// This function avoids having cgo as dependency. +func goBytes(src uintptr, len uint32) []byte { + if src == uintptr(0) { + return []byte{} + } + rv := make([]byte, len) + copy(rv, *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ + Data: src, + Len: int(len), + Cap: int(len), + }))) + return rv +} + +// Convert the given CREDENTIAL struct to a more usable structure +func sysToCredential(cred *sysCREDENTIAL) (result *Credential) { + if cred == nil { + return nil + } + result = new(Credential) + result.Comment = syscall.UTF16PtrToString(cred.Comment) + result.TargetName = syscall.UTF16PtrToString(cred.TargetName) + result.TargetAlias = syscall.UTF16PtrToString(cred.TargetAlias) + result.UserName = syscall.UTF16PtrToString(cred.UserName) + result.LastWritten = time.Unix(0, cred.LastWritten.Nanoseconds()) + result.Persist = CredentialPersistence(cred.Persist) + result.CredentialBlob = goBytes(cred.CredentialBlob, cred.CredentialBlobSize) + result.Attributes = make([]CredentialAttribute, cred.AttributeCount) + attrSlice := *(*[]sysCREDENTIAL_ATTRIBUTE)(unsafe.Pointer(&reflect.SliceHeader{ + Data: cred.Attributes, + Len: int(cred.AttributeCount), + Cap: int(cred.AttributeCount), + })) + for i, attr := range attrSlice { + resultAttr := &result.Attributes[i] + resultAttr.Keyword = syscall.UTF16PtrToString(attr.Keyword) + resultAttr.Value = goBytes(attr.Value, attr.ValueSize) + } + return result +} + +// Convert the given Credential object back to a CREDENTIAL struct, which can be used for calling the +// Windows APIs +func sysFromCredential(cred *Credential) (result *sysCREDENTIAL) { + if cred == nil { + return nil + } + result = new(sysCREDENTIAL) + result.Flags = 0 + result.Type = 0 + result.TargetName, _ = syscall.UTF16PtrFromString(cred.TargetName) + result.Comment, _ = syscall.UTF16PtrFromString(cred.Comment) + result.LastWritten = syscall.NsecToFiletime(cred.LastWritten.UnixNano()) + result.CredentialBlobSize = uint32(len(cred.CredentialBlob)) + if len(cred.CredentialBlob) > 0 { + result.CredentialBlob = uintptr(unsafe.Pointer(&cred.CredentialBlob[0])) + } else { + result.CredentialBlob = 0 + } + result.Persist = uint32(cred.Persist) + result.AttributeCount = uint32(len(cred.Attributes)) + attributes := make([]sysCREDENTIAL_ATTRIBUTE, len(cred.Attributes)) + if len(attributes) > 0 { + result.Attributes = uintptr(unsafe.Pointer(&attributes[0])) + } else { + result.Attributes = 0 + } + for i := range cred.Attributes { + inAttr := &cred.Attributes[i] + outAttr := &attributes[i] + outAttr.Keyword, _ = syscall.UTF16PtrFromString(inAttr.Keyword) + outAttr.Flags = 0 + outAttr.ValueSize = uint32(len(inAttr.Value)) + if len(inAttr.Value) > 0 { + outAttr.Value = uintptr(unsafe.Pointer(&inAttr.Value[0])) + } else { + outAttr.Value = 0 + } + } + result.TargetAlias, _ = syscall.UTF16PtrFromString(cred.TargetAlias) + result.UserName, _ = syscall.UTF16PtrFromString(cred.UserName) + + return +} diff --git a/vendor/github.com/danieljoos/wincred/conversion_unsupported.go b/vendor/github.com/danieljoos/wincred/conversion_unsupported.go new file mode 100644 index 00000000000..a1ea7207504 --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/conversion_unsupported.go @@ -0,0 +1,11 @@ +// +build !windows + +package wincred + +func utf16ToByte(...interface{}) []byte { + return nil +} + +func utf16FromString(...interface{}) []uint16 { + return nil +} diff --git a/vendor/github.com/danieljoos/wincred/sys.go b/vendor/github.com/danieljoos/wincred/sys.go new file mode 100644 index 00000000000..fb8a6ac0f65 --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/sys.go @@ -0,0 +1,148 @@ +//go:build windows +// +build windows + +package wincred + +import ( + "reflect" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") + procCredRead = modadvapi32.NewProc("CredReadW") + procCredWrite proc = modadvapi32.NewProc("CredWriteW") + procCredDelete proc = modadvapi32.NewProc("CredDeleteW") + procCredFree proc = modadvapi32.NewProc("CredFree") + procCredEnumerate = modadvapi32.NewProc("CredEnumerateW") +) + +// Interface for syscall.Proc: helps testing +type proc interface { + Call(a ...uintptr) (r1, r2 uintptr, lastErr error) +} + +// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw +type sysCREDENTIAL struct { + Flags uint32 + Type uint32 + TargetName *uint16 + Comment *uint16 + LastWritten windows.Filetime + CredentialBlobSize uint32 + CredentialBlob uintptr + Persist uint32 + AttributeCount uint32 + Attributes uintptr + TargetAlias *uint16 + UserName *uint16 +} + +// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credential_attributew +type sysCREDENTIAL_ATTRIBUTE struct { + Keyword *uint16 + Flags uint32 + ValueSize uint32 + Value uintptr +} + +// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw +type sysCRED_TYPE uint32 + +const ( + sysCRED_TYPE_GENERIC sysCRED_TYPE = 0x1 + sysCRED_TYPE_DOMAIN_PASSWORD sysCRED_TYPE = 0x2 + sysCRED_TYPE_DOMAIN_CERTIFICATE sysCRED_TYPE = 0x3 + sysCRED_TYPE_DOMAIN_VISIBLE_PASSWORD sysCRED_TYPE = 0x4 + sysCRED_TYPE_GENERIC_CERTIFICATE sysCRED_TYPE = 0x5 + sysCRED_TYPE_DOMAIN_EXTENDED sysCRED_TYPE = 0x6 + + // https://docs.microsoft.com/en-us/windows/desktop/Debug/system-error-codes + sysERROR_NOT_FOUND = windows.Errno(1168) + sysERROR_INVALID_PARAMETER = windows.Errno(87) + sysERROR_BAD_USERNAME = windows.Errno(2202) +) + +// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credreadw +func sysCredRead(targetName string, typ sysCRED_TYPE) (*Credential, error) { + var pcred *sysCREDENTIAL + targetNamePtr, _ := windows.UTF16PtrFromString(targetName) + ret, _, err := syscall.SyscallN( + procCredRead.Addr(), + uintptr(unsafe.Pointer(targetNamePtr)), + uintptr(typ), + 0, + uintptr(unsafe.Pointer(&pcred)), + ) + if ret == 0 { + return nil, err + } + defer procCredFree.Call(uintptr(unsafe.Pointer(pcred))) + + return sysToCredential(pcred), nil +} + +// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credwritew +func sysCredWrite(cred *Credential, typ sysCRED_TYPE) error { + ncred := sysFromCredential(cred) + ncred.Type = uint32(typ) + ret, _, err := procCredWrite.Call( + uintptr(unsafe.Pointer(ncred)), + 0, + ) + if ret == 0 { + return err + } + + return nil +} + +// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-creddeletew +func sysCredDelete(cred *Credential, typ sysCRED_TYPE) error { + targetNamePtr, _ := windows.UTF16PtrFromString(cred.TargetName) + ret, _, err := procCredDelete.Call( + uintptr(unsafe.Pointer(targetNamePtr)), + uintptr(typ), + 0, + ) + if ret == 0 { + return err + } + + return nil +} + +// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credenumeratew +func sysCredEnumerate(filter string, all bool) ([]*Credential, error) { + var count int + var pcreds uintptr + var filterPtr *uint16 + if !all { + filterPtr, _ = windows.UTF16PtrFromString(filter) + } + ret, _, err := syscall.SyscallN( + procCredEnumerate.Addr(), + uintptr(unsafe.Pointer(filterPtr)), + 0, + uintptr(unsafe.Pointer(&count)), + uintptr(unsafe.Pointer(&pcreds)), + ) + if ret == 0 { + return nil, err + } + defer procCredFree.Call(pcreds) + credsSlice := *(*[]*sysCREDENTIAL)(unsafe.Pointer(&reflect.SliceHeader{ + Data: pcreds, + Len: count, + Cap: count, + })) + creds := make([]*Credential, count, count) + for i, cred := range credsSlice { + creds[i] = sysToCredential(cred) + } + + return creds, nil +} diff --git a/vendor/github.com/danieljoos/wincred/sys_unsupported.go b/vendor/github.com/danieljoos/wincred/sys_unsupported.go new file mode 100644 index 00000000000..746639ad822 --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/sys_unsupported.go @@ -0,0 +1,38 @@ +//go:build !windows +// +build !windows + +package wincred + +import ( + "errors" + "syscall" +) + +const ( + sysCRED_TYPE_GENERIC = 0 + sysCRED_TYPE_DOMAIN_PASSWORD = 0 + sysCRED_TYPE_DOMAIN_CERTIFICATE = 0 + sysCRED_TYPE_DOMAIN_VISIBLE_PASSWORD = 0 + sysCRED_TYPE_GENERIC_CERTIFICATE = 0 + sysCRED_TYPE_DOMAIN_EXTENDED = 0 + + sysERROR_NOT_FOUND = syscall.Errno(1) + sysERROR_INVALID_PARAMETER = syscall.Errno(1) + sysERROR_BAD_USERNAME = syscall.Errno(1) +) + +func sysCredRead(...interface{}) (*Credential, error) { + return nil, errors.New("Operation not supported") +} + +func sysCredWrite(...interface{}) error { + return errors.New("Operation not supported") +} + +func sysCredDelete(...interface{}) error { + return errors.New("Operation not supported") +} + +func sysCredEnumerate(...interface{}) ([]*Credential, error) { + return nil, errors.New("Operation not supported") +} diff --git a/vendor/github.com/danieljoos/wincred/types.go b/vendor/github.com/danieljoos/wincred/types.go new file mode 100644 index 00000000000..28debc93265 --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/types.go @@ -0,0 +1,69 @@ +package wincred + +import ( + "time" +) + +// CredentialPersistence describes one of three persistence modes of a credential. +// A detailed description of the available modes can be found on +// Docs: https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw +type CredentialPersistence uint32 + +const ( + // PersistSession indicates that the credential only persists for the life + // of the current Windows login session. Such a credential is not visible in + // any other logon session, even from the same user. + PersistSession CredentialPersistence = 0x1 + + // PersistLocalMachine indicates that the credential persists for this and + // all subsequent logon sessions on this local machine/computer. It is + // however not visible for logon sessions of this user on a different + // machine. + PersistLocalMachine CredentialPersistence = 0x2 + + // PersistEnterprise indicates that the credential persists for this and all + // subsequent logon sessions for this user. It is also visible for logon + // sessions on different computers. + PersistEnterprise CredentialPersistence = 0x3 +) + +// CredentialAttribute represents an application-specific attribute of a credential. +type CredentialAttribute struct { + Keyword string + Value []byte +} + +// Credential is the basic credential structure. +// A credential is identified by its target name. +// The actual credential secret is available in the CredentialBlob field. +type Credential struct { + TargetName string + Comment string + LastWritten time.Time + CredentialBlob []byte + Attributes []CredentialAttribute + TargetAlias string + UserName string + Persist CredentialPersistence +} + +// GenericCredential holds a credential for generic usage. +// It is typically defined and used by applications that need to manage user +// secrets. +// +// More information about the available kinds of credentials of the Windows +// Credential Management API can be found on Docs: +// https://docs.microsoft.com/en-us/windows/desktop/SecAuthN/kinds-of-credentials +type GenericCredential struct { + Credential +} + +// DomainPassword holds a domain credential that is typically used by the +// operating system for user logon. +// +// More information about the available kinds of credentials of the Windows +// Credential Management API can be found on Docs: +// https://docs.microsoft.com/en-us/windows/desktop/SecAuthN/kinds-of-credentials +type DomainPassword struct { + Credential +} diff --git a/vendor/github.com/danieljoos/wincred/wincred.go b/vendor/github.com/danieljoos/wincred/wincred.go new file mode 100644 index 00000000000..5632ee90cd7 --- /dev/null +++ b/vendor/github.com/danieljoos/wincred/wincred.go @@ -0,0 +1,114 @@ +// Package wincred provides primitives for accessing the Windows Credentials Management API. +// This includes functions for retrieval, listing and storage of credentials as well as Go structures for convenient access to the credential data. +// +// A more detailed description of Windows Credentials Management can be found on +// Docs: https://docs.microsoft.com/en-us/windows/desktop/SecAuthN/credentials-management +package wincred + +import "errors" + +const ( + // ErrElementNotFound is the error that is returned if a requested element cannot be found. + // This error constant can be used to check if a credential could not be found. + ErrElementNotFound = sysERROR_NOT_FOUND + + // ErrInvalidParameter is the error that is returned for invalid parameters. + // This error constant can be used to check if the given function parameters were invalid. + // For example when trying to create a new generic credential with an empty target name. + ErrInvalidParameter = sysERROR_INVALID_PARAMETER + + // ErrBadUsername is returned when the credential's username is invalid. + ErrBadUsername = sysERROR_BAD_USERNAME +) + +// GetGenericCredential fetches the generic credential with the given name from Windows credential manager. +// It returns nil and an error if the credential could not be found or an error occurred. +func GetGenericCredential(targetName string) (*GenericCredential, error) { + cred, err := sysCredRead(targetName, sysCRED_TYPE_GENERIC) + if cred != nil { + return &GenericCredential{Credential: *cred}, err + } + return nil, err +} + +// NewGenericCredential creates a new generic credential object with the given name. +// The persist mode of the newly created object is set to a default value that indicates local-machine-wide storage. +// The credential object is NOT yet persisted to the Windows credential vault. +func NewGenericCredential(targetName string) (result *GenericCredential) { + result = new(GenericCredential) + result.TargetName = targetName + result.Persist = PersistLocalMachine + return +} + +// Write persists the generic credential object to Windows credential manager. +func (t *GenericCredential) Write() (err error) { + err = sysCredWrite(&t.Credential, sysCRED_TYPE_GENERIC) + return +} + +// Delete removes the credential object from Windows credential manager. +func (t *GenericCredential) Delete() (err error) { + err = sysCredDelete(&t.Credential, sysCRED_TYPE_GENERIC) + return +} + +// GetDomainPassword fetches the domain-password credential with the given target host name from Windows credential manager. +// It returns nil and an error if the credential could not be found or an error occurred. +func GetDomainPassword(targetName string) (*DomainPassword, error) { + cred, err := sysCredRead(targetName, sysCRED_TYPE_DOMAIN_PASSWORD) + if cred != nil { + return &DomainPassword{Credential: *cred}, err + } + return nil, err +} + +// NewDomainPassword creates a new domain-password credential used for login to the given target host name. +// The persist mode of the newly created object is set to a default value that indicates local-machine-wide storage. +// The credential object is NOT yet persisted to the Windows credential vault. +func NewDomainPassword(targetName string) (result *DomainPassword) { + result = new(DomainPassword) + result.TargetName = targetName + result.Persist = PersistLocalMachine + return +} + +// Write persists the domain-password credential to Windows credential manager. +func (t *DomainPassword) Write() (err error) { + err = sysCredWrite(&t.Credential, sysCRED_TYPE_DOMAIN_PASSWORD) + return +} + +// Delete removes the domain-password credential from Windows credential manager. +func (t *DomainPassword) Delete() (err error) { + err = sysCredDelete(&t.Credential, sysCRED_TYPE_DOMAIN_PASSWORD) + return +} + +// SetPassword sets the CredentialBlob field of a domain password credential to the given string. +func (t *DomainPassword) SetPassword(pw string) { + t.CredentialBlob = utf16ToByte(utf16FromString(pw)) +} + +// List retrieves all credentials of the Credentials store. +func List() ([]*Credential, error) { + creds, err := sysCredEnumerate("", true) + if err != nil && errors.Is(err, ErrElementNotFound) { + // Ignore ERROR_NOT_FOUND and return an empty list instead + creds = []*Credential{} + err = nil + } + return creds, err +} + +// FilteredList retrieves the list of credentials from the Credentials store that match the given filter. +// The filter string defines the prefix followed by an asterisk for the `TargetName` attribute of the credentials. +func FilteredList(filter string) ([]*Credential, error) { + creds, err := sysCredEnumerate(filter, false) + if err != nil && errors.Is(err, ErrElementNotFound) { + // Ignore ERROR_NOT_FOUND and return an empty list instead + creds = []*Credential{} + err = nil + } + return creds, err +} diff --git a/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md b/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md new file mode 100644 index 00000000000..c88f9b2bdd0 --- /dev/null +++ b/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# How to Contribute + +## Getting Started + +- Fork the repository on GitHub +- Read the [README](README.markdown) for build and test instructions +- Play with the project, submit bugs, submit patches! + +## Contribution Flow + +This is a rough outline of what a contributor's workflow looks like: + +- Create a topic branch from where you want to base your work (usually master). +- Make commits of logical units. +- Make sure your commit messages are in the proper format (see below). +- Push your changes to a topic branch in your fork of the repository. +- Make sure the tests pass, and add any new tests as appropriate. +- Submit a pull request to the original repository. + +Thanks for your contributions! + +### Format of the Commit Message + +We follow a rough convention for commit messages that is designed to answer two +questions: what changed and why. The subject line should feature the what and +the body of the commit should describe the why. + +``` +scripts: add the test-cluster command + +this uses tmux to setup a test cluster that you can easily kill and +start for debugging. + +Fixes #38 +``` + +The format can be described more formally as follows: + +``` +: + + + +