Merge pull request from charmbracelet/the-great-cleanup
.github/workflows/build.yml+2
@@ -7,3 +7,5 @@ jobs:
8 go-version: ""
8 go-version: ""
9 go-version-file: ./go.mod
9 go-version-file: ./go.mod
11+ gh_pat: "${{ secrets.PERSONAL_ACCESS_TOKEN }}"
@@ -43,7 +43,7 @@ Thumbs.db
44**/.crush/**
44**/.crush/**
49completions/
49completions/
@@ -6,7 +6,7 @@ linters:
6 # - goconst
6 # - goconst
9− - gomoddirectives
9+ # - gomoddirectives
10 - goprintffuncname
10 - goprintffuncname
12 - misspell
12 - misspell
@@ -0,0 +1,99 @@
11+ "github.com/charmbracelet/crush/internal/config"
12+ "github.com/charmbracelet/log/v2"
13+ "github.com/nxadm/tail"
14+ "github.com/spf13/cobra"
18+ rootCmd.AddCommand(logsCmd)
21+var logsCmd = &cobra.Command{
23+ Short: "View crush logs",
24+ Long: `View the logs generated by Crush. This command allows you to see the log output for debugging and monitoring purposes.`,
25+ RunE: func(cmd *cobra.Command, args []string) error {
26+ cwd, err := cmd.Flags().GetString("cwd")
28+ return fmt.Errorf("failed to get current working directory: %v", err)
30+ log.SetLevel(log.DebugLevel)
31+ cfg, err := config.Load(cwd, false)
33+ return fmt.Errorf("failed to load configuration: %v", err)
35+ logsFile := filepath.Join(cfg.WorkingDir(), cfg.Options.DataDirectory, "logs", "crush.log")
36+ _, err = os.Stat(logsFile)
37+ if os.IsNotExist(err) {
38+ log.Warn("Looks like you are not in a crush project. No logs found.")
41+ t, err := tail.TailFile(logsFile, tail.Config{Follow: true, ReOpen: true, Logger: tail.DiscardingLogger})
43+ return fmt.Errorf("failed to tail log file: %v", err)
46+ // Print the text of each received line
47+ for line := range t.Lines {
48+ var data map[string]any
49+ if err := json.Unmarshal([]byte(line.Text), &data); err != nil {
53+ level := data["level"]
56+ for k := range data {
57+ keys = append(keys, k)
60+ for _, k := range keys {
62+ case "msg", "level", "time":
65+ source, ok := data[k].(map[string]any)
69+ sourceFile := fmt.Sprintf("%s:%d", source["file"], int(source["line"].(float64)))
70+ otherData = append(otherData, "source", sourceFile)
73+ otherData = append(otherData, k, data[k])
76+ log.SetTimeFunction(func(_ time.Time) time.Time {
77+ // parse the timestamp from the log line if available
78+ t, err := time.Parse(time.RFC3339, data["time"].(string))
80+ return time.Now() // fallback to current time if parsing fails
86+ log.Info(msg, otherData...)
88+ log.Debug(msg, otherData...)
90+ log.Error(msg, otherData...)
92+ log.Warn(msg, otherData...)
94+ log.Info(msg, otherData...)
@@ -4,6 +4,7 @@ import (
@@ -14,7 +15,7 @@ import (
14 "github.com/charmbracelet/crush/internal/db"
15 "github.com/charmbracelet/crush/internal/db"
15 "github.com/charmbracelet/crush/internal/format"
16 "github.com/charmbracelet/crush/internal/format"
16 "github.com/charmbracelet/crush/internal/llm/agent"
17 "github.com/charmbracelet/crush/internal/llm/agent"
17− "github.com/charmbracelet/crush/internal/logging"
18+ "github.com/charmbracelet/crush/internal/log"
18 "github.com/charmbracelet/crush/internal/pubsub"
19 "github.com/charmbracelet/crush/internal/pubsub"
19 "github.com/charmbracelet/crush/internal/tui"
20 "github.com/charmbracelet/crush/internal/tui"
20 "github.com/charmbracelet/crush/internal/version"
21 "github.com/charmbracelet/crush/internal/version"
@@ -36,7 +37,7 @@ to assist developers in writing, debugging, and understanding code directly from
36 # Run with debug logging
37 # Run with debug logging
39− # Run with debug logging in a specific directory
40+ # Run with debug slog.in a specific directory
40 crush -d -c /path/to/project
41 crush -d -c /path/to/project
42 # Print version
43 # Print version
@@ -75,7 +76,7 @@ to assist developers in writing, debugging, and understanding code directly from
78− _, err := config.Init(cwd, debug)
79+ cfg, err := config.Init(cwd, debug)
79 if err != nil {
80 if err != nil {
80 return err
81 return err
@@ -85,25 +86,25 @@ to assist developers in writing, debugging, and understanding code directly from
85 defer cancel()
86 defer cancel()
87 // Connect DB, this will also run migrations
88 // Connect DB, this will also run migrations
88− conn, err := db.Connect(ctx)
89+ conn, err := db.Connect(ctx, cfg.Options.DataDirectory)
89 if err != nil {
90 if err != nil {
90 return err
91 return err
93− app, err := app.New(ctx, conn)
94+ app, err := app.New(ctx, conn, cfg)
94 if err != nil {
95 if err != nil {
95− logging.Error("Failed to create app: %v", err)
96+ slog.Error(fmt.Sprintf("Failed to create app instance: %v", err))
96 return err
97 return err
98 // Defer shutdown here so it runs for both interactive and non-interactive modes
99 // Defer shutdown here so it runs for both interactive and non-interactive modes
99 defer app.Shutdown()
100 defer app.Shutdown()
101 // Initialize MCP tools early for both modes
102 // Initialize MCP tools early for both modes
102− initMCPTools(ctx, app)
103+ initMCPTools(ctx, app, cfg)
104 prompt, err = maybePrependStdin(prompt)
105 prompt, err = maybePrependStdin(prompt)
105 if err != nil {
106 if err != nil {
106− logging.Error("Failed to read stdin: %v", err)
107+ slog.Error(fmt.Sprintf("Failed to read from stdin: %v", err))
107 return err
108 return err
@@ -132,18 +133,18 @@ to assist developers in writing, debugging, and understanding code directly from
132 // Set up message handling for the TUI
133 // Set up message handling for the TUI
133 go func() {
134 go func() {
134 defer tuiWg.Done()
135 defer tuiWg.Done()
135− defer logging.RecoverPanic("TUI-message-handler", func() {
136+ defer log.RecoverPanic("TUI-message-handler", func() {
136 attemptTUIRecovery(program)
137 attemptTUIRecovery(program)
141 case <-tuiCtx.Done():
142 case <-tuiCtx.Done():
142− logging.Info("TUI message handler shutting down")
143+ slog.Info("TUI message handler shutting down")
144 case msg, ok := <-ch:
145 case msg, ok := <-ch:
146− logging.Info("TUI message channel closed")
147+ slog.Info("TUI message channel closed")
149 program.Send(msg)
150 program.Send(msg)
@@ -165,7 +166,7 @@ to assist developers in writing, debugging, and understanding code directly from
165 // Wait for TUI message handler to finish
166 // Wait for TUI message handler to finish
166 tuiWg.Wait()
167 tuiWg.Wait()
168− logging.Info("All goroutines cleaned up")
169+ slog.Info("All goroutines cleaned up")
171 // Run the TUI
172 // Run the TUI
@@ -173,35 +174,35 @@ to assist developers in writing, debugging, and understanding code directly from
173 cleanup()
174 cleanup()
175 if err != nil {
176 if err != nil {
176− logging.Error("TUI error: %v", err)
177+ slog.Error(fmt.Sprintf("TUI run error: %v", err))
177 return fmt.Errorf("TUI error: %v", err)
178 return fmt.Errorf("TUI error: %v", err)
180− logging.Info("TUI exited with result: %v", result)
181+ slog.Info(fmt.Sprintf("TUI exited with result: %v", result))
181 return nil
182 return nil
185// attemptTUIRecovery tries to recover the TUI after a panic
186// attemptTUIRecovery tries to recover the TUI after a panic
186func attemptTUIRecovery(program *tea.Program) {
187func attemptTUIRecovery(program *tea.Program) {
187− logging.Info("Attempting to recover TUI after panic")
188+ slog.Info("Attempting to recover TUI after panic")
189 // We could try to restart the TUI or gracefully exit
190 // We could try to restart the TUI or gracefully exit
190 // For now, we'll just quit the program to avoid further issues
191 // For now, we'll just quit the program to avoid further issues
191 program.Quit()
192 program.Quit()
194−func initMCPTools(ctx context.Context, app *app.App) {
195+func initMCPTools(ctx context.Context, app *app.App, cfg *config.Config) {
195 go func() {
196 go func() {
196− defer logging.RecoverPanic("MCP-goroutine", nil)
197+ defer log.RecoverPanic("MCP-goroutine", nil)
198 // Create a context with timeout for the initial MCP tools fetch
199 // Create a context with timeout for the initial MCP tools fetch
199 ctxWithTimeout, cancel := context.WithTimeout(ctx, 30*time.Second)
200 ctxWithTimeout, cancel := context.WithTimeout(ctx, 30*time.Second)
200 defer cancel()
201 defer cancel()
202 // Set this up once with proper error handling
203 // Set this up once with proper error handling
203− agent.GetMcpTools(ctxWithTimeout, app.Permissions)
204+ agent.GetMcpTools(ctxWithTimeout, app.Permissions, cfg)
204− logging.Info("MCP message handling goroutine exiting")
205+ slog.Info("MCP message handling goroutine exiting")
@@ -215,7 +216,7 @@ func setupSubscriber[T any](
215 wg.Add(1)
216 wg.Add(1)
216 go func() {
217 go func() {
217 defer wg.Done()
218 defer wg.Done()
218− defer logging.RecoverPanic(fmt.Sprintf("subscription-%s", name), nil)
219+ defer log.RecoverPanic(fmt.Sprintf("subscription-%s", name), nil)
220 subCh := subscriber(ctx)
221 subCh := subscriber(ctx)
@@ -223,7 +224,7 @@ func setupSubscriber[T any](
224 case event, ok := <-subCh:
225 case event, ok := <-subCh:
226− logging.Info("subscription channel closed", "name", name)
227+ slog.Info("subscription channel closed", "name", name)
@@ -232,13 +233,13 @@ func setupSubscriber[T any](
233 case outputCh <- msg:
234 case outputCh <- msg:
234 case <-time.After(2 * time.Second):
235 case <-time.After(2 * time.Second):
235− logging.Warn("message dropped due to slow consumer", "name", name)
236+ slog.Warn("message dropped due to slow consumer", "name", name)
236 case <-ctx.Done():
237 case <-ctx.Done():
237− logging.Info("subscription cancelled", "name", name)
238+ slog.Info("subscription cancelled", "name", name)
240 case <-ctx.Done():
241 case <-ctx.Done():
241− logging.Info("subscription cancelled", "name", name)
242+ slog.Info("subscription cancelled", "name", name)
@@ -251,7 +252,6 @@ func setupSubscriptions(app *app.App, parentCtx context.Context) (chan tea.Msg,
251 wg := sync.WaitGroup{}
252 wg := sync.WaitGroup{}
252 ctx, cancel := context.WithCancel(parentCtx) // Inherit from parent context
253 ctx, cancel := context.WithCancel(parentCtx) // Inherit from parent context
254− setupSubscriber(ctx, &wg, "logging", logging.Subscribe, ch)
255 setupSubscriber(ctx, &wg, "sessions", app.Sessions.Subscribe, ch)
255 setupSubscriber(ctx, &wg, "sessions", app.Sessions.Subscribe, ch)
256 setupSubscriber(ctx, &wg, "messages", app.Messages.Subscribe, ch)
256 setupSubscriber(ctx, &wg, "messages", app.Messages.Subscribe, ch)
257 setupSubscriber(ctx, &wg, "permissions", app.Permissions.Subscribe, ch)
257 setupSubscriber(ctx, &wg, "permissions", app.Permissions.Subscribe, ch)
@@ -259,22 +259,22 @@ func setupSubscriptions(app *app.App, parentCtx context.Context) (chan tea.Msg,
259 setupSubscriber(ctx, &wg, "history", app.History.Subscribe, ch)
259 setupSubscriber(ctx, &wg, "history", app.History.Subscribe, ch)
261 cleanupFunc := func() {
261 cleanupFunc := func() {
262− logging.Info("Cancelling all subscriptions")
262+ slog.Info("Cancelling all subscriptions")
263 cancel() // Signal all goroutines to stop
263 cancel() // Signal all goroutines to stop
265 waitCh := make(chan struct{})
265 waitCh := make(chan struct{})
266 go func() {
266 go func() {
267− defer logging.RecoverPanic("subscription-cleanup", nil)
267+ defer log.RecoverPanic("subscription-cleanup", nil)
268 wg.Wait()
268 wg.Wait()
269 close(waitCh)
269 close(waitCh)
273 case <-waitCh:
273 case <-waitCh:
274− logging.Info("All subscription goroutines completed successfully")
274+ slog.Info("All subscription goroutines completed successfully")
275 close(ch) // Only close after all writers are confirmed done
275 close(ch) // Only close after all writers are confirmed done
276 case <-time.After(5 * time.Second):
276 case <-time.After(5 * time.Second):
277− logging.Warn("Timed out waiting for some subscription goroutines to complete")
277+ slog.Warn("Timed out waiting for some subscription goroutines to complete")
278 close(ch)
278 close(ch)
@@ -292,9 +292,10 @@ func Execute() {
294func init() {
294func init() {
295+ rootCmd.PersistentFlags().StringP("cwd", "c", "", "Current working directory")
295 rootCmd.Flags().BoolP("help", "h", false, "Help")
297 rootCmd.Flags().BoolP("help", "h", false, "Help")
296 rootCmd.Flags().BoolP("debug", "d", false, "Debug")
298 rootCmd.Flags().BoolP("debug", "d", false, "Debug")
297− rootCmd.Flags().StringP("cwd", "c", "", "Current working directory")
298 rootCmd.Flags().StringP("prompt", "p", "", "Prompt to run in non-interactive mode")
299 rootCmd.Flags().StringP("prompt", "p", "", "Prompt to run in non-interactive mode")
300 // Add format flag with validation logic
301 // Add format flag with validation logic
cmd/schema/README.mddeleted−182
@@ -1,182 +0,0 @@
1−# Crush Configuration Schema Generator
3−This tool automatically generates a JSON Schema for the Crush configuration file by using Go reflection to analyze the configuration structs. The schema provides validation, autocompletion, and documentation for configuration files.
7−- **Automated Generation**: Uses reflection to automatically generate schemas from Go structs
8−- **Always Up-to-Date**: Schema stays in sync with code changes automatically
9−- **Comprehensive**: Includes all configuration options, types, and validation rules
10−- **Enhanced**: Adds provider enums, model lists, and custom descriptions
11−- **Extensible**: Easy to add new fields and modify existing ones
17−go run cmd/schema/main.go > crush-schema.json
19−# Or use the task runner
27−1. **Reflects on Config Structs**: Analyzes the `config.Config` struct and all related types
28−2. **Generates Base Schema**: Creates JSON Schema definitions for all struct fields
29−3. **Enhances with Runtime Data**: Adds provider lists, model enums, and tool lists from the actual codebase
30−4. **Adds Custom Descriptions**: Provides meaningful descriptions for configuration options
31−5. **Sets Default Values**: Includes appropriate defaults for optional fields
35−The generated schema includes:
37−- **Type Safety**: Proper type definitions for all configuration fields
38−- **Validation**: Required fields, enum constraints, and format validation
39−- **Documentation**: Descriptions for all configuration options
40−- **Defaults**: Default values for optional settings
41−- **Provider Enums**: Current list of supported providers
42−- **Model Enums**: Available models from all configured providers
43−- **Tool Lists**: Valid tool names for agent configurations
44−- **Cross-References**: Proper relationships between different config sections
46−## Adding New Configuration Fields
48−To add new configuration options:
50−1. **Add to Config Structs**: Add the field to the appropriate struct in `internal/config/`
51−2. **Add JSON Tags**: Include proper JSON tags with field names
52−3. **Regenerate Schema**: Run the schema generator to update the JSON schema
53−4. **Update Validation**: Add any custom validation logic if needed
58− // ... existing fields ...
60− // New field with JSON tag and description
61− NewFeature bool `json:"new_feature,omitempty"`
65−The schema generator will automatically:
66−- Detect the new field
67−- Generate appropriate JSON schema
68−- Add type information
69−- Include in validation
73−### Editor Integration
75−Most modern editors support JSON Schema:
77−**VS Code**: Add to your workspace settings:
82− "fileMatch": ["crush.json", ".crush.json"],
83− "url": "./crush-schema.json"
89−**JetBrains IDEs**: Configure in Settings → Languages & Frameworks → Schemas and DTDs → JSON Schema Mappings
94−# Using jsonschema (Python)
95−pip install jsonschema
96−jsonschema -i crush.json crush-schema.json
98−# Using ajv-cli (Node.js)
99−npm install -g ajv-cli
100−ajv validate -s crush-schema.json -d crush.json
103−### Configuration Example
109− "model_id": "claude-3-5-sonnet-20241022",
110− "provider": "anthropic",
111− "reasoning_effort": "medium",
115− "model_id": "claude-3-5-haiku-20241022",
116− "provider": "anthropic"
122− "provider_type": "anthropic",
123− "api_key": "your-api-key",
135− "id": "custom-agent",
136− "name": "Custom Agent",
137− "description": "A custom agent for specific tasks",
139− "allowed_tools": ["glob", "grep", "view"],
141− "filesystem": ["read", "write"]
147− "command": "mcp-filesystem",
148− "args": ["--root", "/workspace"],
154− "command": "typescript-language-server",
155− "args": ["--stdio"],
165− "data_directory": ".crush",
168− "compact_mode": false
176−The schema generator is designed to be maintenance-free. As long as:
178−- Configuration structs have proper JSON tags
179−- New enums are added to the enhancement functions
180−- The generator is run after significant config changes
182−The schema will stay current with the codebase automatically.
cmd/schema/main.godeleted−155
@@ -1,155 +0,0 @@
8− "github.com/charmbracelet/crush/internal/config"
9− "github.com/invopop/jsonschema"
13− // Create a new reflector
14− r := &jsonschema.Reflector{
15− // Use anonymous schemas to avoid ID conflicts
17− // Expand the root struct instead of referencing it
18− ExpandedStruct: true,
19− AllowAdditionalProperties: true,
22− // Generate schema for the main Config struct
23− schema := r.Reflect(&config.Config{})
25− // Enhance the schema with additional information
26− enhanceSchema(schema)
28− // Set the schema metadata
29− schema.Version = "https://json-schema.org/draft/2020-12/schema"
30− schema.Title = "Crush Configuration"
31− schema.Description = "Configuration schema for the Crush application"
33− // Pretty print the schema
34− encoder := json.NewEncoder(os.Stdout)
35− encoder.SetIndent("", " ")
36− if err := encoder.Encode(schema); err != nil {
37− fmt.Fprintf(os.Stderr, "Error encoding schema: %v\n", err)
42−// enhanceSchema adds additional enhancements to the generated schema
43−func enhanceSchema(schema *jsonschema.Schema) {
44− // Add provider enums
45− addProviderEnums(schema)
48− addModelEnums(schema)
53− // Add default context paths
54− addDefaultContextPaths(schema)
57−// addProviderEnums adds provider enums to the schema
58−func addProviderEnums(schema *jsonschema.Schema) {
59− providers := config.Providers()
60− var providerIDs []any
61− for _, p := range providers {
62− providerIDs = append(providerIDs, string(p.ID))
65− // Add to PreferredModel provider field
66− if schema.Definitions != nil {
67− if preferredModelDef, exists := schema.Definitions["PreferredModel"]; exists {
68− if providerProp, exists := preferredModelDef.Properties.Get("provider"); exists {
69− providerProp.Enum = providerIDs
73− // Add to ProviderConfig ID field
74− if providerConfigDef, exists := schema.Definitions["ProviderConfig"]; exists {
75− if idProp, exists := providerConfigDef.Properties.Get("id"); exists {
76− idProp.Enum = providerIDs
82−// addModelEnums adds model enums to the schema
83−func addModelEnums(schema *jsonschema.Schema) {
84− providers := config.Providers()
86− for _, p := range providers {
87− for _, m := range p.Models {
88− modelIDs = append(modelIDs, m.ID)
92− // Add to PreferredModel model_id field
93− if schema.Definitions != nil {
94− if preferredModelDef, exists := schema.Definitions["PreferredModel"]; exists {
95− if modelIDProp, exists := preferredModelDef.Properties.Get("model_id"); exists {
96− modelIDProp.Enum = modelIDs
102−// addToolEnums adds tool enums to the schema
103−func addToolEnums(schema *jsonschema.Schema) {
105− "bash", "edit", "fetch", "glob", "grep", "ls", "sourcegraph", "view", "write", "agent",
108− if schema.Definitions != nil {
109− if agentDef, exists := schema.Definitions["Agent"]; exists {
110− if allowedToolsProp, exists := agentDef.Properties.Get("allowed_tools"); exists {
111− if allowedToolsProp.Items != nil {
112− allowedToolsProp.Items.Enum = tools
119−// addDefaultContextPaths adds default context paths to the schema
120−func addDefaultContextPaths(schema *jsonschema.Schema) {
121− defaultContextPaths := []any{
122− ".github/copilot-instructions.md",
137− if schema.Definitions != nil {
138− if optionsDef, exists := schema.Definitions["Options"]; exists {
139− if contextPathsProp, exists := optionsDef.Properties.Get("context_paths"); exists {
140− contextPathsProp.Default = defaultContextPaths
145− // Also add to root properties if they exist
146− if schema.Properties != nil {
147− if optionsProp, exists := schema.Properties.Get("options"); exists {
148− if optionsProp.Properties != nil {
149− if contextPathsProp, exists := optionsProp.Properties.Get("context_paths"); exists {
150− contextPathsProp.Default = defaultContextPaths
crush-schema.jsondeleted−700
@@ -1,700 +0,0 @@
2− "$schema": "https://json-schema.org/draft/2020-12/schema",
15− "description": "Unique identifier for the agent"
20− "description": "Display name of the agent"
24− "title": "Description",
25− "description": "Description of what the agent does"
30− "description": "Whether this agent is disabled",
41− "title": "Model Type",
42− "description": "Type of model to use (large or small)"
61− "title": "Allowed Tools",
62− "description": "List of tools this agent is allowed to use (if nil all tools are allowed)"
65− "additionalProperties": {
72− "title": "Allowed MCP",
73− "description": "Map of MCP servers this agent can use and their allowed tools"
80− "title": "Allowed LSP",
81− "description": "List of LSP servers this agent can use (if nil all LSPs are allowed)"
88− "title": "Context Paths",
89− "description": "Custom context paths for this agent (additive to global context paths)"
102− "description": "Whether this LSP server is enabled",
108− "description": "Command to execute for the LSP server"
115− "title": "Arguments",
116− "description": "Command line arguments for the LSP server"
120− "description": "LSP server specific options"
133− "description": "Command to execute for stdio MCP servers"
140− "title": "Environment",
141− "description": "Environment variables for the MCP server"
148− "title": "Arguments",
149− "description": "Command line arguments for the MCP server"
161− "description": "Type of MCP connection",
167− "description": "URL for SSE MCP servers"
170− "additionalProperties": {
175− "description": "HTTP headers for SSE MCP servers"
187− "title": "Model ID",
188− "description": "Unique identifier for the model"
192− "title": "Model Name",
193− "description": "Display name of the model"
198− "title": "Input Cost",
199− "description": "Cost per 1 million input tokens"
201− "cost_per_1m_out": {
204− "title": "Output Cost",
205− "description": "Cost per 1 million output tokens"
207− "cost_per_1m_in_cached": {
210− "title": "Cached Input Cost",
211− "description": "Cost per 1 million cached input tokens"
213− "cost_per_1m_out_cached": {
216− "title": "Cached Output Cost",
217− "description": "Cost per 1 million cached output tokens"
222− "title": "Context Window",
223− "description": "Maximum context window size in tokens"
225− "default_max_tokens": {
228− "title": "Default Max Tokens",
229− "description": "Default maximum tokens for responses"
233− "title": "Can Reason",
234− "description": "Whether the model supports reasoning capabilities"
236− "reasoning_effort": {
238− "title": "Reasoning Effort",
239− "description": "Default reasoning effort level for reasoning models"
241− "has_reasoning_effort": {
243− "title": "Has Reasoning Effort",
244− "description": "Whether the model supports reasoning effort configuration"
246− "supports_attachments": {
248− "title": "Supports Images",
249− "description": "Whether the model supports image attachments"
257− "default_max_tokens"
267− "title": "Context Paths",
268− "description": "List of paths to search for context files",
270− ".github/copilot-instructions.md",
286− "$ref": "#/$defs/TUIOptions",
287− "title": "TUI Options",
288− "description": "Terminal UI configuration options"
293− "description": "Enable debug logging",
298− "title": "Debug LSP",
299− "description": "Enable LSP debug logging",
302− "disable_auto_summarize": {
304− "title": "Disable Auto Summarize",
305− "description": "Disable automatic conversation summarization",
310− "title": "Data Directory",
311− "description": "Directory for storing application data",
322− "claude-opus-4-20250514",
323− "claude-sonnet-4-20250514",
324− "claude-3-7-sonnet-20250219",
325− "claude-3-5-haiku-20241022",
326− "claude-3-5-sonnet-20240620",
327− "claude-3-5-sonnet-20241022",
328− "codex-mini-latest",
341− "codex-mini-latest",
352− "anthropic.claude-opus-4-20250514-v1:0",
353− "anthropic.claude-sonnet-4-20250514-v1:0",
354− "anthropic.claude-3-7-sonnet-20250219-v1:0",
355− "anthropic.claude-3-5-haiku-20241022-v1:0",
360− "mistralai/mistral-small-3.2-24b-instruct:free",
361− "mistralai/mistral-small-3.2-24b-instruct",
362− "minimax/minimax-m1:extended",
363− "minimax/minimax-m1",
364− "google/gemini-2.5-flash-lite-preview-06-17",
365− "google/gemini-2.5-flash",
366− "google/gemini-2.5-pro",
370− "mistralai/magistral-small-2506",
371− "mistralai/magistral-medium-2506",
372− "mistralai/magistral-medium-2506:thinking",
373− "google/gemini-2.5-pro-preview",
374− "deepseek/deepseek-r1-0528",
375− "anthropic/claude-opus-4",
376− "anthropic/claude-sonnet-4",
377− "mistralai/devstral-small:free",
378− "mistralai/devstral-small",
379− "google/gemini-2.5-flash-preview-05-20",
380− "google/gemini-2.5-flash-preview-05-20:thinking",
381− "openai/codex-mini",
382− "mistralai/mistral-medium-3",
383− "google/gemini-2.5-pro-preview-05-06",
384− "arcee-ai/caller-large",
385− "arcee-ai/virtuoso-large",
386− "arcee-ai/virtuoso-medium-v2",
387− "qwen/qwen3-30b-a3b",
390− "qwen/qwen3-235b-a22b",
391− "google/gemini-2.5-flash-preview",
392− "google/gemini-2.5-flash-preview:thinking",
393− "openai/o4-mini-high",
397− "openai/gpt-4.1-mini",
398− "openai/gpt-4.1-nano",
399− "x-ai/grok-3-mini-beta",
401− "meta-llama/llama-4-maverick",
402− "meta-llama/llama-4-scout",
403− "all-hands/openhands-lm-32b-v0.1",
404− "google/gemini-2.5-pro-exp-03-25",
405− "deepseek/deepseek-chat-v3-0324:free",
406− "deepseek/deepseek-chat-v3-0324",
407− "mistralai/mistral-small-3.1-24b-instruct:free",
408− "mistralai/mistral-small-3.1-24b-instruct",
409− "ai21/jamba-1.6-large",
410− "ai21/jamba-1.6-mini",
411− "openai/gpt-4.5-preview",
412− "google/gemini-2.0-flash-lite-001",
413− "anthropic/claude-3.7-sonnet",
414− "anthropic/claude-3.7-sonnet:beta",
415− "anthropic/claude-3.7-sonnet:thinking",
416− "mistralai/mistral-saba",
417− "openai/o3-mini-high",
418− "google/gemini-2.0-flash-001",
423− "mistralai/mistral-small-24b-instruct-2501",
424− "deepseek/deepseek-r1-distill-llama-70b",
425− "deepseek/deepseek-r1",
426− "mistralai/codestral-2501",
427− "deepseek/deepseek-chat",
430− "meta-llama/llama-3.3-70b-instruct",
431− "amazon/nova-lite-v1",
432− "amazon/nova-micro-v1",
433− "amazon/nova-pro-v1",
434− "openai/gpt-4o-2024-11-20",
435− "mistralai/mistral-large-2411",
436− "mistralai/mistral-large-2407",
437− "mistralai/pixtral-large-2411",
438− "thedrummer/unslopnemo-12b",
439− "anthropic/claude-3.5-haiku:beta",
440− "anthropic/claude-3.5-haiku",
441− "anthropic/claude-3.5-haiku-20241022:beta",
442− "anthropic/claude-3.5-haiku-20241022",
443− "anthropic/claude-3.5-sonnet:beta",
444− "anthropic/claude-3.5-sonnet",
446− "mistralai/ministral-8b",
447− "mistralai/ministral-3b",
448− "nvidia/llama-3.1-nemotron-70b-instruct",
449− "google/gemini-flash-1.5-8b",
450− "meta-llama/llama-3.2-11b-vision-instruct",
451− "meta-llama/llama-3.2-3b-instruct",
452− "qwen/qwen-2.5-72b-instruct",
453− "mistralai/pixtral-12b",
454− "cohere/command-r-plus-08-2024",
455− "cohere/command-r-08-2024",
456− "microsoft/phi-3.5-mini-128k-instruct",
457− "nousresearch/hermes-3-llama-3.1-70b",
458− "openai/gpt-4o-2024-08-06",
459− "meta-llama/llama-3.1-405b-instruct",
460− "meta-llama/llama-3.1-70b-instruct",
461− "meta-llama/llama-3.1-8b-instruct",
462− "mistralai/mistral-nemo",
463− "openai/gpt-4o-mini",
464− "openai/gpt-4o-mini-2024-07-18",
465− "anthropic/claude-3.5-sonnet-20240620:beta",
466− "anthropic/claude-3.5-sonnet-20240620",
467− "mistralai/mistral-7b-instruct-v0.3",
468− "mistralai/mistral-7b-instruct:free",
469− "mistralai/mistral-7b-instruct",
470− "microsoft/phi-3-mini-128k-instruct",
471− "microsoft/phi-3-medium-128k-instruct",
472− "google/gemini-flash-1.5",
473− "openai/gpt-4o-2024-05-13",
475− "openai/gpt-4o:extended",
476− "meta-llama/llama-3-8b-instruct",
477− "meta-llama/llama-3-70b-instruct",
478− "mistralai/mixtral-8x22b-instruct",
479− "openai/gpt-4-turbo",
480− "google/gemini-pro-1.5",
481− "cohere/command-r-plus",
482− "cohere/command-r-plus-04-2024",
484− "anthropic/claude-3-haiku:beta",
485− "anthropic/claude-3-haiku",
486− "anthropic/claude-3-opus:beta",
487− "anthropic/claude-3-opus",
488− "anthropic/claude-3-sonnet:beta",
489− "anthropic/claude-3-sonnet",
490− "cohere/command-r-03-2024",
491− "mistralai/mistral-large",
492− "openai/gpt-3.5-turbo-0613",
493− "openai/gpt-4-turbo-preview",
494− "mistralai/mistral-small",
495− "mistralai/mistral-tiny",
496− "mistralai/mixtral-8x7b-instruct",
497− "openai/gpt-4-1106-preview",
498− "mistralai/mistral-7b-instruct-v0.1",
499− "openai/gpt-3.5-turbo-16k",
503− "title": "Model ID",
504− "description": "ID of the preferred model"
518− "title": "Provider",
519− "description": "Provider for the preferred model"
521− "reasoning_effort": {
523− "title": "Reasoning Effort",
524− "description": "Override reasoning effort for this model"
529− "title": "Max Tokens",
530− "description": "Override max tokens for this model"
535− "description": "Enable thinking for reasoning models",
545− "PreferredModels": {
548− "$ref": "#/$defs/PreferredModel",
549− "title": "Large Model",
550− "description": "Preferred model configuration for large model type"
553− "$ref": "#/$defs/PreferredModel",
554− "title": "Small Model",
555− "description": "Preferred model configuration for small model type"
574− "title": "Provider ID",
575− "description": "Unique identifier for the provider"
579− "title": "Base URL",
580− "description": "Base URL for the provider API (required for custom providers)"
584− "title": "Provider Type",
585− "description": "Type of the provider (openai"
590− "description": "API key for authenticating with the provider"
594− "title": "Disabled",
595− "description": "Whether this provider is disabled",
599− "additionalProperties": {
603− "title": "Extra Headers",
604− "description": "Additional HTTP headers to send with requests"
607− "additionalProperties": {
611− "title": "Extra Parameters",
612− "description": "Additional provider-specific parameters"
614− "default_large_model": {
616− "title": "Default Large Model",
617− "description": "Default model ID for large model type"
619− "default_small_model": {
621− "title": "Default Small Model",
622− "description": "Default model ID for small model type"
626− "$ref": "#/$defs/Model"
630− "description": "List of available models for this provider"
642− "title": "Compact Mode",
643− "description": "Enable compact mode for the TUI",
655− "$ref": "#/$defs/PreferredModels",
657− "description": "Preferred model configurations for large and small model types"
660− "additionalProperties": {
661− "$ref": "#/$defs/ProviderConfig"
664− "title": "Providers",
665− "description": "LLM provider configurations"
668− "additionalProperties": {
669− "$ref": "#/$defs/Agent"
673− "description": "Agent configurations for different tasks"
676− "additionalProperties": {
677− "$ref": "#/$defs/MCP"
681− "description": "Model Control Protocol server configurations"
684− "additionalProperties": {
685− "$ref": "#/$defs/LSPConfig"
689− "description": "Language Server Protocol configurations"
692− "$ref": "#/$defs/Options",
694− "description": "General application options and settings"
698− "title": "Crush Configuration",
699− "description": "Configuration schema for the Crush application"
@@ -1,15 +1,7 @@
2− "$schema": "./crush-schema.json",
5 "command": "gopls"
4 "command": "gopls"
11− "url": "https://mcp.context7.com/mcp",
@@ -1 +1 @@
1−{"flagWords":[],"words":["afero","alecthomas","bubbletea","charmbracelet","charmtone","Charple","crush","diffview","Emph","filepicker","Focusable","fsext","GROQ","Guac","imageorient","Lanczos","lipgloss","lsps","lucasb","nfnt","oksvg","Preproc","rasterx","rivo","Sourcegraph","srwiley","Strikethrough","termenv","textinput","trashhalo","uniseg","Unticked","genai","jsonschema"],"version":"0.2","language":"en"}
1+{"words":["afero","alecthomas","bubbletea","charmbracelet","charmtone","Charple","crush","diffview","Emph","filepicker","Focusable","fsext","GROQ","Guac","imageorient","Lanczos","lipgloss","lsps","lucasb","nfnt","oksvg","Preproc","rasterx","rivo","Sourcegraph","srwiley","Strikethrough","termenv","textinput","trashhalo","uniseg","Unticked","genai","jsonschema","preconfigured","jsons","qjebbs","LOCALAPPDATA","USERPROFILE","stretchr","cursorrules","VERTEXAI","VERTEXAI"],"flagWords":[],"language":"en","version":"0.2"}
@@ -2,6 +2,10 @@ module github.com/charmbracelet/crush
5+replace github.com/charmbracelet/bubbletea/v2 => github.com/charmbracelet/bubbletea-internal/v2 v2.0.0-20250703182356-a42fb608faaf
7+replace github.com/charmbracelet/lipgloss/v2 => github.com/charmbracelet/lipgloss-internal/v2 v2.0.0-20250703152138-ff346e83e819
6 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0
10 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0
7 github.com/JohannesKaufmann/html-to-markdown v1.6.0
11 github.com/JohannesKaufmann/html-to-markdown v1.6.0
@@ -13,34 +17,47 @@ require (
13 github.com/bmatcuk/doublestar/v4 v4.8.1
17 github.com/bmatcuk/doublestar/v4 v4.8.1
14 github.com/charlievieth/fastwalk v1.0.11
18 github.com/charlievieth/fastwalk v1.0.11
15 github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250607113720-eb5e1cf3b09e
19 github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250607113720-eb5e1cf3b09e
16− github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.3.0.20250609143341-c76fa36f1b94
20+ github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.1
17 github.com/charmbracelet/fang v0.1.0
21 github.com/charmbracelet/fang v0.1.0
18 github.com/charmbracelet/glamour/v2 v2.0.0-20250516160903-6f1e2c8f9ebe
22 github.com/charmbracelet/glamour/v2 v2.0.0-20250516160903-6f1e2c8f9ebe
19 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.2.0.20250703152125-8e1c474f8a71
23 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.2.0.20250703152125-8e1c474f8a71
20− github.com/charmbracelet/x/ansi v0.9.3-0.20250602153603-fb931ed90413
24+ github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706
25+ github.com/charmbracelet/x/ansi v0.9.3
21 github.com/charmbracelet/x/exp/charmtone v0.0.0-20250627134340-c144409e381c
26 github.com/charmbracelet/x/exp/charmtone v0.0.0-20250627134340-c144409e381c
22 github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a
27 github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a
23 github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec
28 github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec
24 github.com/fsnotify/fsnotify v1.8.0
29 github.com/fsnotify/fsnotify v1.8.0
25− github.com/go-logfmt/logfmt v0.6.0
26 github.com/google/uuid v1.6.0
30 github.com/google/uuid v1.6.0
27− github.com/invopop/jsonschema v0.13.0
31+ github.com/joho/godotenv v1.5.1
28 github.com/mark3labs/mcp-go v0.32.0
32 github.com/mark3labs/mcp-go v0.32.0
29 github.com/muesli/termenv v0.16.0
33 github.com/muesli/termenv v0.16.0
30 github.com/ncruces/go-sqlite3 v0.25.0
34 github.com/ncruces/go-sqlite3 v0.25.0
31 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
35 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
36+ github.com/nxadm/tail v1.4.11
32 github.com/openai/openai-go v1.8.2
37 github.com/openai/openai-go v1.8.2
33 github.com/pressly/goose/v3 v3.24.2
38 github.com/pressly/goose/v3 v3.24.2
39+ github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c
34 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
40 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
35 github.com/sahilm/fuzzy v0.1.1
41 github.com/sahilm/fuzzy v0.1.1
36 github.com/spf13/cobra v1.9.1
42 github.com/spf13/cobra v1.9.1
37 github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c
43 github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c
38 github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef
44 github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef
39 github.com/stretchr/testify v1.10.0
45 github.com/stretchr/testify v1.10.0
46+ golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
47+ gopkg.in/natefinch/lumberjack.v2 v2.2.1
40 mvdan.cc/sh/v3 v3.11.0
48 mvdan.cc/sh/v3 v3.11.0
52+ github.com/go-logfmt/logfmt v0.6.0 // indirect
43−require github.com/spf13/cast v1.7.1 // indirect
53+ github.com/spf13/cast v1.7.1 // indirect
54+ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
58+ github.com/charmbracelet/ultraviolet v0.0.0-20250707134318-0fdaa64b8c5e // indirect
59+ github.com/charmbracelet/x/termios v0.1.1 // indirect
46 cloud.google.com/go v0.116.0 // indirect
63 cloud.google.com/go v0.116.0 // indirect
@@ -67,13 +84,10 @@ require (
67 github.com/aws/smithy-go v1.20.3 // indirect
84 github.com/aws/smithy-go v1.20.3 // indirect
68 github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
85 github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
69 github.com/aymerick/douceur v0.2.0 // indirect
86 github.com/aymerick/douceur v0.2.0 // indirect
70− github.com/bahlo/generic-list-go v0.2.0 // indirect
71− github.com/buger/jsonparser v1.1.1 // indirect
72 github.com/charmbracelet/colorprofile v0.3.1 // indirect
87 github.com/charmbracelet/colorprofile v0.3.1 // indirect
73 github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa // indirect
88 github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa // indirect
74 github.com/charmbracelet/x/exp/slice v0.0.0-20250611152503-f53cdd7e01ef
89 github.com/charmbracelet/x/exp/slice v0.0.0-20250611152503-f53cdd7e01ef
75− github.com/charmbracelet/x/input v0.3.5-0.20250509021451-13796e822d86 // indirect
90+ github.com/charmbracelet/x/term v0.2.1
76− github.com/charmbracelet/x/term v0.2.1 // indirect
77 github.com/charmbracelet/x/windows v0.2.1 // indirect
91 github.com/charmbracelet/x/windows v0.2.1 // indirect
78 github.com/davecgh/go-spew v1.1.1 // indirect
92 github.com/davecgh/go-spew v1.1.1 // indirect
79 github.com/disintegration/gift v1.1.2 // indirect
93 github.com/disintegration/gift v1.1.2 // indirect
@@ -92,7 +106,6 @@ require (
92 github.com/inconshreveable/mousetrap v1.1.0 // indirect
106 github.com/inconshreveable/mousetrap v1.1.0 // indirect
93 github.com/kylelemons/godebug v1.1.0 // indirect
107 github.com/kylelemons/godebug v1.1.0 // indirect
94 github.com/lucasb-eyer/go-colorful v1.2.0
108 github.com/lucasb-eyer/go-colorful v1.2.0
95− github.com/mailru/easyjson v0.7.7 // indirect
96 github.com/mattn/go-isatty v0.0.20 // indirect
109 github.com/mattn/go-isatty v0.0.20 // indirect
97 github.com/mattn/go-runewidth v0.0.16 // indirect
110 github.com/mattn/go-runewidth v0.0.16 // indirect
98 github.com/mfridman/interpolate v0.0.2 // indirect
111 github.com/mfridman/interpolate v0.0.2 // indirect
@@ -114,7 +127,6 @@ require (
114 github.com/tidwall/match v1.1.1 // indirect
127 github.com/tidwall/match v1.1.1 // indirect
115 github.com/tidwall/pretty v1.2.1 // indirect
128 github.com/tidwall/pretty v1.2.1 // indirect
116 github.com/tidwall/sjson v1.2.5 // indirect
129 github.com/tidwall/sjson v1.2.5 // indirect
117− github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
118 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
130 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
119 github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
131 github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
120 github.com/yuin/goldmark v1.7.8 // indirect
132 github.com/yuin/goldmark v1.7.8 // indirect
@@ -128,8 +140,8 @@ require (
128 golang.org/x/crypto v0.37.0 // indirect
140 golang.org/x/crypto v0.37.0 // indirect
129 golang.org/x/image v0.26.0 // indirect
141 golang.org/x/image v0.26.0 // indirect
130 golang.org/x/net v0.39.0 // indirect
142 golang.org/x/net v0.39.0 // indirect
131− golang.org/x/sync v0.13.0 // indirect
143+ golang.org/x/sync v0.15.0 // indirect
132− golang.org/x/sys v0.32.0 // indirect
144+ golang.org/x/sys v0.33.0 // indirect
133 golang.org/x/term v0.31.0 // indirect
145 golang.org/x/term v0.31.0 // indirect
134 golang.org/x/text v0.24.0 // indirect
146 golang.org/x/text v0.24.0 // indirect
135 google.golang.org/genai v1.3.0
147 google.golang.org/genai v1.3.0
@@ -64,30 +64,28 @@ github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3v
64github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
64github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
65github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
65github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
66github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
66github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
67−github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
68−github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
69github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
67github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
70github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
68github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
71−github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
72−github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
73github.com/charlievieth/fastwalk v1.0.11 h1:5sLT/q9+d9xMdpKExawLppqvXFZCVKf6JHnr2u/ufj8=
69github.com/charlievieth/fastwalk v1.0.11 h1:5sLT/q9+d9xMdpKExawLppqvXFZCVKf6JHnr2u/ufj8=
74github.com/charlievieth/fastwalk v1.0.11/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
70github.com/charlievieth/fastwalk v1.0.11/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
75github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250607113720-eb5e1cf3b09e h1:99Ugtt633rqauFsXjZobZmtkNpeaWialfj8dl6COC6A=
71github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250607113720-eb5e1cf3b09e h1:99Ugtt633rqauFsXjZobZmtkNpeaWialfj8dl6COC6A=
76github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250607113720-eb5e1cf3b09e/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw=
72github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250607113720-eb5e1cf3b09e/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw=
77−github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.3.0.20250609143341-c76fa36f1b94 h1:QIi50k+uNTJmp2sMs+33D1m/EWr/7OPTJ8x92AY3eOc=
73+github.com/charmbracelet/bubbletea-internal/v2 v2.0.0-20250703182356-a42fb608faaf h1:td6+JCUKCFW0liyAotsxnGicPxyar4cggCUCT/g0mfQ=
78−github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.3.0.20250609143341-c76fa36f1b94/go.mod h1:oOn1YZGZyJHxJfh4sFAna9vDzxJRNuErLETr/lnlB/I=
74+github.com/charmbracelet/bubbletea-internal/v2 v2.0.0-20250703182356-a42fb608faaf/go.mod h1:LTnqM6sOjG1yY5MDFlqefgN+0EFGTV3PU5QoDq9EUDU=
79github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
75github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
80github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
76github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
81github.com/charmbracelet/fang v0.1.0 h1:SlZS2crf3/zQh7Mr4+W+7QR1k+L08rrPX5rm5z3d7Wg=
77github.com/charmbracelet/fang v0.1.0 h1:SlZS2crf3/zQh7Mr4+W+7QR1k+L08rrPX5rm5z3d7Wg=
82github.com/charmbracelet/fang v0.1.0/go.mod h1:Zl/zeUQ8EtQuGyiV0ZKZlZPDowKRTzu8s/367EpN/fc=
78github.com/charmbracelet/fang v0.1.0/go.mod h1:Zl/zeUQ8EtQuGyiV0ZKZlZPDowKRTzu8s/367EpN/fc=
83github.com/charmbracelet/glamour/v2 v2.0.0-20250516160903-6f1e2c8f9ebe h1:i6ce4CcAlPpTj2ER69m1DBeLZ3RRcHnKExuwhKa3GfY=
79github.com/charmbracelet/glamour/v2 v2.0.0-20250516160903-6f1e2c8f9ebe h1:i6ce4CcAlPpTj2ER69m1DBeLZ3RRcHnKExuwhKa3GfY=
84github.com/charmbracelet/glamour/v2 v2.0.0-20250516160903-6f1e2c8f9ebe/go.mod h1:p3Q+aN4eQKeM5jhrmXPMgPrlKbmc59rWSnMsSA3udhk=
80github.com/charmbracelet/glamour/v2 v2.0.0-20250516160903-6f1e2c8f9ebe/go.mod h1:p3Q+aN4eQKeM5jhrmXPMgPrlKbmc59rWSnMsSA3udhk=
85−github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1.0.20250523195325-2d1af06b557c h1:177KMz8zHRlEZJsWzafbKYh6OdjgvTspoH+UjaxgIXY=
81+github.com/charmbracelet/lipgloss-internal/v2 v2.0.0-20250703152138-ff346e83e819 h1:2i1WGevIAY9eV/vHWwe5Diu58DQ6h9RvKTDpo13zmdA=
86−github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1.0.20250523195325-2d1af06b557c/go.mod h1:EJWvaCrhOhNGVZMvcjc0yVryl4qqpMs8tz0r9WyEkdQ=
82+github.com/charmbracelet/lipgloss-internal/v2 v2.0.0-20250703152138-ff346e83e819/go.mod h1:kz1eyf3LJ8c5/R2RH/1Pk0kb9BjvbVcY49WCQemk4sg=
87−github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.2.0.20250703152125-8e1c474f8a71 h1:X0tsNa2UHCKNw+illiavosasVzqioRo32SRV35iwr2I=
83+github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706 h1:WkwO6Ks3mSIGnGuSdKl9qDSyfbYK50z2wc2gGMggegE=
88−github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.2.0.20250703152125-8e1c474f8a71/go.mod h1:EJWvaCrhOhNGVZMvcjc0yVryl4qqpMs8tz0r9WyEkdQ=
84+github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706/go.mod h1:mjJGp00cxcfvD5xdCa+bso251Jt4owrQvuimJtVmEmM=
89−github.com/charmbracelet/x/ansi v0.9.3-0.20250602153603-fb931ed90413 h1:L07QkDqRF274IZ2UJ/mCTL8DR95efU9BNWLYCDXEjvQ=
85+github.com/charmbracelet/ultraviolet v0.0.0-20250707134318-0fdaa64b8c5e h1:wimsDgs/7NRciipaniyugwqHJNTCjJna68bobyPqYx8=
86+github.com/charmbracelet/ultraviolet v0.0.0-20250707134318-0fdaa64b8c5e/go.mod h1:/O+B00+dYG6lqRAWIaNxSvywnDrIH6dmLYQAsH0LRTg=
87+github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
90−github.com/charmbracelet/x/ansi v0.9.3-0.20250602153603-fb931ed90413/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
88+github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
91github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa h1:lphz0Z3rsiOtMYiz8axkT24i9yFiueDhJbzyNUADmME=
89github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa h1:lphz0Z3rsiOtMYiz8axkT24i9yFiueDhJbzyNUADmME=
92github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa/go.mod h1:xBlh2Yi3DL3zy/2n15kITpg0YZardf/aa/hgUaIM6Rk=
90github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa/go.mod h1:xBlh2Yi3DL3zy/2n15kITpg0YZardf/aa/hgUaIM6Rk=
93github.com/charmbracelet/x/exp/charmtone v0.0.0-20250627134340-c144409e381c h1:2GELBLPgfSbHU53bsQhR9XIgNuVZ6w+Rz8RWV5Lq+A4=
91github.com/charmbracelet/x/exp/charmtone v0.0.0-20250627134340-c144409e381c h1:2GELBLPgfSbHU53bsQhR9XIgNuVZ6w+Rz8RWV5Lq+A4=
@@ -96,10 +94,10 @@ github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a h1:FsHE
96github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
94github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
97github.com/charmbracelet/x/exp/slice v0.0.0-20250611152503-f53cdd7e01ef h1:v7qwsZ2OxzlwvpKwz8dtZXp7fIJlcDEUOyFBNE4fz4Q=
95github.com/charmbracelet/x/exp/slice v0.0.0-20250611152503-f53cdd7e01ef h1:v7qwsZ2OxzlwvpKwz8dtZXp7fIJlcDEUOyFBNE4fz4Q=
98github.com/charmbracelet/x/exp/slice v0.0.0-20250611152503-f53cdd7e01ef/go.mod h1:vI5nDVMWi6veaYH+0Fmvpbe/+cv/iJfMntdh+N0+Tms=
96github.com/charmbracelet/x/exp/slice v0.0.0-20250611152503-f53cdd7e01ef/go.mod h1:vI5nDVMWi6veaYH+0Fmvpbe/+cv/iJfMntdh+N0+Tms=
99−github.com/charmbracelet/x/input v0.3.5-0.20250509021451-13796e822d86 h1:BxAEmOBIDajkgao3EsbBxKQCYvgYPGdT62WASLvtf4Y=
100−github.com/charmbracelet/x/input v0.3.5-0.20250509021451-13796e822d86/go.mod h1:62Rp/6EtTxoeJDSdtpA3tJp3y3ZRpsiekBSje+K8htA=
101github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
97github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
102github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
98github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
99+github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
100+github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
103github.com/charmbracelet/x/windows v0.2.1 h1:3x7vnbpQrjpuq/4L+I4gNsG5htYoCiA5oe9hLjAij5I=
101github.com/charmbracelet/x/windows v0.2.1 h1:3x7vnbpQrjpuq/4L+I4gNsG5htYoCiA5oe9hLjAij5I=
104github.com/charmbracelet/x/windows v0.2.1/go.mod h1:ptZp16h40gDYqs5TSawSVW+yiLB13j4kSMA0lSCHL0M=
102github.com/charmbracelet/x/windows v0.2.1/go.mod h1:ptZp16h40gDYqs5TSawSVW+yiLB13j4kSMA0lSCHL0M=
105github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
103github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
@@ -120,6 +118,7 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
120github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
118github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
121github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
119github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
122github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
120github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
121+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
123github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
122github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
124github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
123github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
125github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
124github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
@@ -153,9 +152,8 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
153github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
152github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
154github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
153github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
155github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
154github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
156−github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
155+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
157−github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
156+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
158−github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
159github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
157github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
160github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
158github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
161github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
159github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -167,8 +165,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
167github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
165github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
168github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
166github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
169github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
167github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
170−github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
171−github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
172github.com/mark3labs/mcp-go v0.32.0 h1:fgwmbfL2gbd67obg57OfV2Dnrhs1HtSdlY/i5fn7MU8=
168github.com/mark3labs/mcp-go v0.32.0 h1:fgwmbfL2gbd67obg57OfV2Dnrhs1HtSdlY/i5fn7MU8=
173github.com/mark3labs/mcp-go v0.32.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
169github.com/mark3labs/mcp-go v0.32.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
174github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
170github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -199,6 +195,8 @@ github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt
199github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
195github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
200github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
196github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
201github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
197github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
198+github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
199+github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
202github.com/openai/openai-go v1.8.2 h1:UqSkJ1vCOPUpz9Ka5tS0324EJFEuOvMc+lA/EarJWP8=
200github.com/openai/openai-go v1.8.2 h1:UqSkJ1vCOPUpz9Ka5tS0324EJFEuOvMc+lA/EarJWP8=
203github.com/openai/openai-go v1.8.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
201github.com/openai/openai-go v1.8.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
204github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
202github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
@@ -208,6 +206,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
208github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
206github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
209github.com/pressly/goose/v3 v3.24.2 h1:c/ie0Gm8rnIVKvnDQ/scHErv46jrDv9b4I0WRcFJzYU=
207github.com/pressly/goose/v3 v3.24.2 h1:c/ie0Gm8rnIVKvnDQ/scHErv46jrDv9b4I0WRcFJzYU=
210github.com/pressly/goose/v3 v3.24.2/go.mod h1:kjefwFB0eR4w30Td2Gj2Mznyw94vSP+2jJYkOVNbD1k=
208github.com/pressly/goose/v3 v3.24.2/go.mod h1:kjefwFB0eR4w30Td2Gj2Mznyw94vSP+2jJYkOVNbD1k=
209+github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c h1:kmzxiX+OB0knCo1V0dkEkdPelzCdAzCURCfmFArn2/A=
210+github.com/qjebbs/go-jsons v0.0.0-20221222033332-a534c5fc1c4c/go.mod h1:wNJrtinHyC3YSf6giEh4FJN8+yZV7nXBjvmfjhBIcw4=
211github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
211github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
212github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
212github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
213github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
213github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@@ -256,8 +256,6 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
256github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
256github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
257github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
257github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
258github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
258github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
259−github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
260−github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
261github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
259github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
262github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
260github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
263github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
261github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
@@ -311,13 +309,14 @@ golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
311golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
309golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
312golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
310golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
313golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
311golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
314−golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
312+golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
315−golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
313+golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
316golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
314golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
317golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
315golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
318golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
316golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
319golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
317golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
320golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
318golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
319+golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
321golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
320golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
322golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
321golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
323golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
322golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -326,8 +325,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
326golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
325golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
327golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
326golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
328golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
327golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
329−golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
328+golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
330−golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
329+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
331golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
330golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
332golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
331golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
333golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
332golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -364,6 +363,10 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
364gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
363gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
365gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
364gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
366gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
365gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
366+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
367+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
368+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
369+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
367gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
370gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
368gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
371gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
369gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
372gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
internal/app/app.go+15−12
@@ -5,6 +5,7 @@ import (
5 "database/sql"
5 "database/sql"
@@ -14,7 +15,7 @@ import (
14 "github.com/charmbracelet/crush/internal/format"
15 "github.com/charmbracelet/crush/internal/format"
15 "github.com/charmbracelet/crush/internal/history"
16 "github.com/charmbracelet/crush/internal/history"
16 "github.com/charmbracelet/crush/internal/llm/agent"
17 "github.com/charmbracelet/crush/internal/llm/agent"
17− "github.com/charmbracelet/crush/internal/logging"
18+
18 "github.com/charmbracelet/crush/internal/lsp"
19 "github.com/charmbracelet/crush/internal/lsp"
19 "github.com/charmbracelet/crush/internal/message"
20 "github.com/charmbracelet/crush/internal/message"
20 "github.com/charmbracelet/crush/internal/permission"
21 "github.com/charmbracelet/crush/internal/permission"
@@ -36,9 +37,11 @@ type App struct {
36 watcherCancelFuncs []context.CancelFunc
37 watcherCancelFuncs []context.CancelFunc
37 cancelFuncsMutex sync.Mutex
38 cancelFuncsMutex sync.Mutex
38 watcherWG sync.WaitGroup
39 watcherWG sync.WaitGroup
41+ config *config.Config
41−func New(ctx context.Context, conn *sql.DB) (*App, error) {
44+func New(ctx context.Context, conn *sql.DB, cfg *config.Config) (*App, error) {
42 q := db.New(conn)
45 q := db.New(conn)
43 sessions := session.NewService(q)
46 sessions := session.NewService(q)
44 messages := message.NewService(q)
47 messages := message.NewService(q)
@@ -48,16 +51,16 @@ func New(ctx context.Context, conn *sql.DB) (*App, error) {
48 Sessions: sessions,
51 Sessions: sessions,
49 Messages: messages,
52 Messages: messages,
50 History: files,
53 History: files,
51− Permissions: permission.NewPermissionService(),
54+ Permissions: permission.NewPermissionService(cfg.WorkingDir()),
52 LSPClients: make(map[string]*lsp.Client),
55 LSPClients: make(map[string]*lsp.Client),
55 // Initialize LSP clients in the background
59 // Initialize LSP clients in the background
56 go app.initLSPClients(ctx)
60 go app.initLSPClients(ctx)
58− cfg := config.Get()
62+ // TODO: remove the concept of agent config most likely
60− coderAgentCfg := cfg.Agents[config.AgentCoder]
63+ coderAgentCfg := cfg.Agents["coder"]
61 if coderAgentCfg.ID == "" {
64 if coderAgentCfg.ID == "" {
62 return nil, fmt.Errorf("coder agent configuration is missing")
65 return nil, fmt.Errorf("coder agent configuration is missing")
@@ -72,7 +75,7 @@ func New(ctx context.Context, conn *sql.DB) (*App, error) {
72 app.LSPClients,
75 app.LSPClients,
74 if err != nil {
77 if err != nil {
75− logging.Error("Failed to create coder agent", err)
78+ slog.Error("Failed to create coder agent", "err", err)
76 return nil, err
79 return nil, err
@@ -81,7 +84,7 @@ func New(ctx context.Context, conn *sql.DB) (*App, error) {
82// RunNonInteractive handles the execution flow when a prompt is provided via CLI flag.
85// RunNonInteractive handles the execution flow when a prompt is provided via CLI flag.
83func (a *App) RunNonInteractive(ctx context.Context, prompt string, outputFormat string, quiet bool) error {
86func (a *App) RunNonInteractive(ctx context.Context, prompt string, outputFormat string, quiet bool) error {
84− logging.Info("Running in non-interactive mode")
87+ slog.Info("Running in non-interactive mode")
86 // Start spinner if not in quiet mode
89 // Start spinner if not in quiet mode
87 var spinner *format.Spinner
90 var spinner *format.Spinner
@@ -106,7 +109,7 @@ func (a *App) RunNonInteractive(ctx context.Context, prompt string, outputFormat
106 if err != nil {
109 if err != nil {
107 return fmt.Errorf("failed to create session for non-interactive mode: %w", err)
110 return fmt.Errorf("failed to create session for non-interactive mode: %w", err)
109− logging.Info("Created session for non-interactive run", "session_id", sess.ID)
112+ slog.Info("Created session for non-interactive run", "session_id", sess.ID)
111 // Automatically approve all permission requests for this non-interactive session
114 // Automatically approve all permission requests for this non-interactive session
112 a.Permissions.AutoApproveSession(sess.ID)
115 a.Permissions.AutoApproveSession(sess.ID)
@@ -119,7 +122,7 @@ func (a *App) RunNonInteractive(ctx context.Context, prompt string, outputFormat
119 result := <-done
122 result := <-done
120 if result.Error != nil {
123 if result.Error != nil {
121 if errors.Is(result.Error, context.Canceled) || errors.Is(result.Error, agent.ErrRequestCancelled) {
124 if errors.Is(result.Error, context.Canceled) || errors.Is(result.Error, agent.ErrRequestCancelled) {
122− logging.Info("Agent processing cancelled", "session_id", sess.ID)
125+ slog.Info("Agent processing cancelled", "session_id", sess.ID)
123 return nil
126 return nil
125 return fmt.Errorf("agent processing failed: %w", result.Error)
128 return fmt.Errorf("agent processing failed: %w", result.Error)
@@ -138,7 +141,7 @@ func (a *App) RunNonInteractive(ctx context.Context, prompt string, outputFormat
139 fmt.Println(format.FormatOutput(content, outputFormat))
142 fmt.Println(format.FormatOutput(content, outputFormat))
141− logging.Info("Non-interactive run completed", "session_id", sess.ID)
144+ slog.Info("Non-interactive run completed", "session_id", sess.ID)
143 return nil
146 return nil
@@ -162,7 +165,7 @@ func (app *App) Shutdown() {
162 for name, client := range clients {
165 for name, client := range clients {
163 shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
166 shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
164 if err := client.Shutdown(shutdownCtx); err != nil {
167 if err := client.Shutdown(shutdownCtx); err != nil {
165− logging.Error("Failed to shutdown LSP client", "name", name, "error", err)
168+ slog.Error("Failed to shutdown LSP client", "name", name, "error", err)
internal/app/lsp.go+17−20
@@ -2,34 +2,32 @@ package app
7− "github.com/charmbracelet/crush/internal/config"
8+ "github.com/charmbracelet/crush/internal/log"
8− "github.com/charmbracelet/crush/internal/logging"
9 "github.com/charmbracelet/crush/internal/lsp"
9 "github.com/charmbracelet/crush/internal/lsp"
10 "github.com/charmbracelet/crush/internal/lsp/watcher"
10 "github.com/charmbracelet/crush/internal/lsp/watcher"
13func (app *App) initLSPClients(ctx context.Context) {
13func (app *App) initLSPClients(ctx context.Context) {
16 // Initialize LSP clients
14 // Initialize LSP clients
17− for name, clientConfig := range cfg.LSP {
15+ for name, clientConfig := range app.config.LSP {
18 // Start each client initialization in its own goroutine
16 // Start each client initialization in its own goroutine
19 go app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
17 go app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
21− logging.Info("LSP clients initialization started in background")
19+ slog.Info("LSP clients initialization started in background")
24// createAndStartLSPClient creates a new LSP client, initializes it, and starts its workspace watcher
22// createAndStartLSPClient creates a new LSP client, initializes it, and starts its workspace watcher
25func (app *App) createAndStartLSPClient(ctx context.Context, name string, command string, args ...string) {
23func (app *App) createAndStartLSPClient(ctx context.Context, name string, command string, args ...string) {
26 // Create a specific context for initialization with a timeout
24 // Create a specific context for initialization with a timeout
27− logging.Info("Creating LSP client", "name", name, "command", command, "args", args)
25+ slog.Info("Creating LSP client", "name", name, "command", command, "args", args)
29 // Create the LSP client
27 // Create the LSP client
30 lspClient, err := lsp.NewClient(ctx, command, args...)
28 lspClient, err := lsp.NewClient(ctx, command, args...)
31 if err != nil {
29 if err != nil {
32− logging.Error("Failed to create LSP client for", name, err)
30+ slog.Error("Failed to create LSP client for", name, err)
@@ -38,9 +36,9 @@ func (app *App) createAndStartLSPClient(ctx context.Context, name string, comman
38 defer cancel()
36 defer cancel()
40 // Initialize with the initialization context
38 // Initialize with the initialization context
41− _, err = lspClient.InitializeLSPClient(initCtx, config.WorkingDirectory())
39+ _, err = lspClient.InitializeLSPClient(initCtx, app.config.WorkingDir())
42 if err != nil {
40 if err != nil {
43− logging.Error("Initialize failed", "name", name, "error", err)
41+ slog.Error("Initialize failed", "name", name, "error", err)
44 // Clean up the client to prevent resource leaks
42 // Clean up the client to prevent resource leaks
45 lspClient.Close()
43 lspClient.Close()
@@ -48,15 +46,15 @@ func (app *App) createAndStartLSPClient(ctx context.Context, name string, comman
49 // Wait for the server to be ready
47 // Wait for the server to be ready
50 if err := lspClient.WaitForServerReady(initCtx); err != nil {
48 if err := lspClient.WaitForServerReady(initCtx); err != nil {
51− logging.Error("Server failed to become ready", "name", name, "error", err)
49+ slog.Error("Server failed to become ready", "name", name, "error", err)
52 // We'll continue anyway, as some functionality might still work
50 // We'll continue anyway, as some functionality might still work
53 lspClient.SetServerState(lsp.StateError)
51 lspClient.SetServerState(lsp.StateError)
55− logging.Info("LSP server is ready", "name", name)
53+ slog.Info("LSP server is ready", "name", name)
56 lspClient.SetServerState(lsp.StateReady)
54 lspClient.SetServerState(lsp.StateReady)
59− logging.Info("LSP client initialized", "name", name)
57+ slog.Info("LSP client initialized", "name", name)
61 // Create a child context that can be canceled when the app is shutting down
59 // Create a child context that can be canceled when the app is shutting down
62 watchCtx, cancelFunc := context.WithCancel(ctx)
60 watchCtx, cancelFunc := context.WithCancel(ctx)
@@ -86,22 +84,21 @@ func (app *App) createAndStartLSPClient(ctx context.Context, name string, comman
86// runWorkspaceWatcher executes the workspace watcher for an LSP client
84// runWorkspaceWatcher executes the workspace watcher for an LSP client
87func (app *App) runWorkspaceWatcher(ctx context.Context, name string, workspaceWatcher *watcher.WorkspaceWatcher) {
85func (app *App) runWorkspaceWatcher(ctx context.Context, name string, workspaceWatcher *watcher.WorkspaceWatcher) {
88 defer app.watcherWG.Done()
86 defer app.watcherWG.Done()
89− defer logging.RecoverPanic("LSP-"+name, func() {
87+ defer log.RecoverPanic("LSP-"+name, func() {
90 // Try to restart the client
88 // Try to restart the client
91 app.restartLSPClient(ctx, name)
89 app.restartLSPClient(ctx, name)
94− workspaceWatcher.WatchWorkspace(ctx, config.WorkingDirectory())
92+ workspaceWatcher.WatchWorkspace(ctx, app.config.WorkingDir())
95− logging.Info("Workspace watcher stopped", "client", name)
93+ slog.Info("Workspace watcher stopped", "client", name)
98// restartLSPClient attempts to restart a crashed or failed LSP client
96// restartLSPClient attempts to restart a crashed or failed LSP client
99func (app *App) restartLSPClient(ctx context.Context, name string) {
97func (app *App) restartLSPClient(ctx context.Context, name string) {
100 // Get the original configuration
98 // Get the original configuration
102− clientConfig, exists := cfg.LSP[name]
99+ clientConfig, exists := app.config.LSP[name]
103 if !exists {
100 if !exists {
104− logging.Error("Cannot restart client, configuration not found", "client", name)
101+ slog.Error("Cannot restart client, configuration not found", "client", name)
@@ -122,5 +119,5 @@ func (app *App) restartLSPClient(ctx context.Context, name string) {
123 // Create a new client using the shared function
120 // Create a new client using the shared function
124 app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
121 app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
125− logging.Info("Successfully restarted LSP client", "client", name)
122+ slog.Info("Successfully restarted LSP client", "client", name)
internal/config/config.go+159−1354
@@ -1,28 +1,17 @@
1package config
1package config
15 "github.com/charmbracelet/crush/internal/fur/provider"
8 "github.com/charmbracelet/crush/internal/fur/provider"
16− "github.com/charmbracelet/crush/internal/logging"
17− "github.com/invopop/jsonschema"
21 defaultDataDirectory = ".crush"
13 defaultDataDirectory = ".crush"
22 defaultLogLevel = "info"
14 defaultLogLevel = "info"
25− MaxTokensFallbackDefault = 4096
28var defaultContextPaths = []string{
17var defaultContextPaths = []string{
@@ -41,82 +30,51 @@ var defaultContextPaths = []string{
41 "CRUSH.local.md",
30 "CRUSH.local.md",
44−type AgentID string
33+type SelectedModelType string
47− AgentCoder AgentID = "coder"
36+ SelectedModelTypeLarge SelectedModelType = "large"
48− AgentTask AgentID = "task"
37+ SelectedModelTypeSmall SelectedModelType = "small"
51−type ModelType string
40+type SelectedModel struct {
41+ // The model id as used by the provider API.
43+ Model string `json:"model"`
44+ // The model provider, same as the key/id used in the providers config.
46+ Provider string `json:"provider"`
53−const (
48+ // Only used by models that use the openai provider and need this set.
54− LargeModel ModelType = "large"
49+ ReasoningEffort string `json:"reasoning_effort,omitempty"`
55− SmallModel ModelType = "small"
58−type Model struct {
51+ // Overrides the default model configuration.
59− ID string `json:"id" jsonschema:"title=Model ID,description=Unique identifier for the model, the API model"`
52+ MaxTokens int64 `json:"max_tokens,omitempty"`
60− Name string `json:"name" jsonschema:"title=Model Name,description=Display name of the model"`
61− CostPer1MIn float64 `json:"cost_per_1m_in,omitempty" jsonschema:"title=Input Cost,description=Cost per 1 million input tokens,minimum=0"`
62− CostPer1MOut float64 `json:"cost_per_1m_out,omitempty" jsonschema:"title=Output Cost,description=Cost per 1 million output tokens,minimum=0"`
63− CostPer1MInCached float64 `json:"cost_per_1m_in_cached,omitempty" jsonschema:"title=Cached Input Cost,description=Cost per 1 million cached input tokens,minimum=0"`
64− CostPer1MOutCached float64 `json:"cost_per_1m_out_cached,omitempty" jsonschema:"title=Cached Output Cost,description=Cost per 1 million cached output tokens,minimum=0"`
65− ContextWindow int64 `json:"context_window" jsonschema:"title=Context Window,description=Maximum context window size in tokens,minimum=1"`
66− DefaultMaxTokens int64 `json:"default_max_tokens" jsonschema:"title=Default Max Tokens,description=Default maximum tokens for responses,minimum=1"`
67− CanReason bool `json:"can_reason,omitempty" jsonschema:"title=Can Reason,description=Whether the model supports reasoning capabilities"`
68− ReasoningEffort string `json:"reasoning_effort,omitempty" jsonschema:"title=Reasoning Effort,description=Default reasoning effort level for reasoning models"`
69− HasReasoningEffort bool `json:"has_reasoning_effort,omitempty" jsonschema:"title=Has Reasoning Effort,description=Whether the model supports reasoning effort configuration"`
70− SupportsImages bool `json:"supports_attachments,omitempty" jsonschema:"title=Supports Images,description=Whether the model supports image attachments"`
73−type VertexAIOptions struct {
54+ // Used by anthropic models that can reason to indicate if the model should think.
74− APIKey string `json:"api_key,omitempty"`
75− Project string `json:"project,omitempty"`
76− Location string `json:"location,omitempty"`
55+ Think bool `json:"think,omitempty"`
79type ProviderConfig struct {
58type ProviderConfig struct {
80− ID provider.InferenceProvider `json:"id,omitempty" jsonschema:"title=Provider ID,description=Unique identifier for the provider"`
59+ // The provider's id.
60+ ID string `json:"id,omitempty"`
61+ // The provider's API endpoint.
81− BaseURL string `json:"base_url,omitempty" jsonschema:"title=Base URL,description=Base URL for the provider API (required for custom providers)"`
62+ BaseURL string `json:"base_url,omitempty"`
82− ProviderType provider.Type `json:"provider_type" jsonschema:"title=Provider Type,description=Type of the provider (openai, anthropic, etc.)"`
63+ // The provider type, e.g. "openai", "anthropic", etc. if empty it defaults to openai.
64+ Type provider.Type `json:"type,omitempty"`
65+ // The provider's API key.
83− APIKey string `json:"api_key,omitempty" jsonschema:"title=API Key,description=API key for authenticating with the provider"`
66+ APIKey string `json:"api_key,omitempty"`
84− Disabled bool `json:"disabled,omitempty" jsonschema:"title=Disabled,description=Whether this provider is disabled,default=false"`
67+ // Marks the provider as disabled.
85− ExtraHeaders map[string]string `json:"extra_headers,omitempty" jsonschema:"title=Extra Headers,description=Additional HTTP headers to send with requests"`
68+ Disable bool `json:"disable,omitempty"`
86− // used for e.x for vertex to set the project
87− ExtraParams map[string]string `json:"extra_params,omitempty" jsonschema:"title=Extra Parameters,description=Additional provider-specific parameters"`
89− DefaultLargeModel string `json:"default_large_model,omitempty" jsonschema:"title=Default Large Model,description=Default model ID for large model type"`
90− DefaultSmallModel string `json:"default_small_model,omitempty" jsonschema:"title=Default Small Model,description=Default model ID for small model type"`
92− Models []Model `json:"models,omitempty" jsonschema:"title=Models,description=List of available models for this provider"`
96− ID AgentID `json:"id,omitempty" jsonschema:"title=Agent ID,description=Unique identifier for the agent,enum=coder,enum=task"`
97− Name string `json:"name,omitempty" jsonschema:"title=Name,description=Display name of the agent"`
98− Description string `json:"description,omitempty" jsonschema:"title=Description,description=Description of what the agent does"`
99− // This is the id of the system prompt used by the agent
100− Disabled bool `json:"disabled,omitempty" jsonschema:"title=Disabled,description=Whether this agent is disabled,default=false"`
102− Model ModelType `json:"model" jsonschema:"title=Model Type,description=Type of model to use (large or small),enum=large,enum=small"`
104− // The available tools for the agent
70+ // Extra headers to send with each request to the provider.
105− // if this is nil, all tools are available
71+ ExtraHeaders map[string]string
106− AllowedTools []string `json:"allowed_tools,omitempty" jsonschema:"title=Allowed Tools,description=List of tools this agent is allowed to use (if nil all tools are allowed)"`
108− // this tells us which MCPs are available for this agent
73+ // Used to pass extra parameters to the provider.
109− // if this is empty all mcps are available
74+ ExtraParams map[string]string `json:"-"`
110− // the string array is the list of tools from the AllowedMCP the agent has available
111− // if the string array is nil, all tools from the AllowedMCP are available
112− AllowedMCP map[string][]string `json:"allowed_mcp,omitempty" jsonschema:"title=Allowed MCP,description=Map of MCP servers this agent can use and their allowed tools"`
114− // The list of LSPs that this agent can use
76+ // The provider models
115− // if this is nil, all LSPs are available
77+ Models []provider.Model `json:"models,omitempty"`
116− AllowedLSP []string `json:"allowed_lsp,omitempty" jsonschema:"title=Allowed LSP,description=List of LSP servers this agent can use (if nil all LSPs are allowed)"`
118− // Overrides the context paths for this agent
119− ContextPaths []string `json:"context_paths,omitempty" jsonschema:"title=Context Paths,description=Custom context paths for this agent (additive to global context paths)"`
122type MCPType string
80type MCPType string
@@ -127,1358 +85,205 @@ const (
127 MCPHttp MCPType = "http"
85 MCPHttp MCPType = "http"
130−type MCP struct {
88+type MCPConfig struct {
131− Command string `json:"command,omitempty" jsonschema:"title=Command,description=Command to execute for stdio MCP servers"`
89+ Command string `json:"command,omitempty" `
132− Env []string `json:"env,omitempty" jsonschema:"title=Environment,description=Environment variables for the MCP server"`
90+ Env []string `json:"env,omitempty"`
133− Args []string `json:"args,omitempty" jsonschema:"title=Arguments,description=Command line arguments for the MCP server"`
91+ Args []string `json:"args,omitempty"`
134− Type MCPType `json:"type" jsonschema:"title=Type,description=Type of MCP connection,enum=stdio,enum=sse,enum=http,default=stdio"`
92+ Type MCPType `json:"type"`
135− URL string `json:"url,omitempty" jsonschema:"title=URL,description=URL for SSE MCP servers"`
93+ URL string `json:"url,omitempty"`
136 // TODO: maybe make it possible to get the value from the env
95 // TODO: maybe make it possible to get the value from the env
137− Headers map[string]string `json:"headers,omitempty" jsonschema:"title=Headers,description=HTTP headers for SSE MCP servers"`
96+ Headers map[string]string `json:"headers,omitempty"`
140type LSPConfig struct {
99type LSPConfig struct {
141− Disabled bool `json:"enabled,omitempty" jsonschema:"title=Enabled,description=Whether this LSP server is enabled,default=true"`
100+ Disabled bool `json:"enabled,omitempty"`
142− Command string `json:"command" jsonschema:"title=Command,description=Command to execute for the LSP server"`
101+ Command string `json:"command"`
143− Args []string `json:"args,omitempty" jsonschema:"title=Arguments,description=Command line arguments for the LSP server"`
102+ Args []string `json:"args,omitempty"`
144− Options any `json:"options,omitempty" jsonschema:"title=Options,description=LSP server specific options"`
103+ Options any `json:"options,omitempty"`
147type TUIOptions struct {
106type TUIOptions struct {
148− CompactMode bool `json:"compact_mode" jsonschema:"title=Compact Mode,description=Enable compact mode for the TUI,default=false"`
107+ CompactMode bool `json:"compact_mode,omitempty"`
149 // Here we can add themes later or any TUI related options
108 // Here we can add themes later or any TUI related options
152type Options struct {
111type Options struct {
153− ContextPaths []string `json:"context_paths,omitempty" jsonschema:"title=Context Paths,description=List of paths to search for context files"`
112+ ContextPaths []string `json:"context_paths,omitempty"`
154− TUI TUIOptions `json:"tui,omitempty" jsonschema:"title=TUI Options,description=Terminal UI configuration options"`
113+ TUI *TUIOptions `json:"tui,omitempty"`
155− Debug bool `json:"debug,omitempty" jsonschema:"title=Debug,description=Enable debug logging,default=false"`
114+ Debug bool `json:"debug,omitempty"`
156− DebugLSP bool `json:"debug_lsp,omitempty" jsonschema:"title=Debug LSP,description=Enable LSP debug logging,default=false"`
115+ DebugLSP bool `json:"debug_lsp,omitempty"`
157− DisableAutoSummarize bool `json:"disable_auto_summarize,omitempty" jsonschema:"title=Disable Auto Summarize,description=Disable automatic conversation summarization,default=false"`
116+ DisableAutoSummarize bool `json:"disable_auto_summarize,omitempty"`
158 // Relative to the cwd
117 // Relative to the cwd
159− DataDirectory string `json:"data_directory,omitempty" jsonschema:"title=Data Directory,description=Directory for storing application data,default=.crush"`
118+ DataDirectory string `json:"data_directory,omitempty"`
162−type PreferredModel struct {
121+type MCPs map[string]MCPConfig
163− ModelID string `json:"model_id" jsonschema:"title=Model ID,description=ID of the preferred model"`
164− Provider provider.InferenceProvider `json:"provider" jsonschema:"title=Provider,description=Provider for the preferred model"`
165− // ReasoningEffort overrides the default reasoning effort for this model
166− ReasoningEffort string `json:"reasoning_effort,omitempty" jsonschema:"title=Reasoning Effort,description=Override reasoning effort for this model"`
167− // MaxTokens overrides the default max tokens for this model
168− MaxTokens int64 `json:"max_tokens,omitempty" jsonschema:"title=Max Tokens,description=Override max tokens for this model,minimum=1"`
170− // Think indicates if the model should think, only applicable for anthropic reasoning models
171− Think bool `json:"think,omitempty" jsonschema:"title=Think,description=Enable thinking for reasoning models,default=false"`
174−type PreferredModels struct {
123+type MCP struct {
175− Large PreferredModel `json:"large,omitempty" jsonschema:"title=Large Model,description=Preferred model configuration for large model type"`
124+ Name string `json:"name"`
176− Small PreferredModel `json:"small,omitempty" jsonschema:"title=Small Model,description=Preferred model configuration for small model type"`
125+ MCP MCPConfig `json:"mcp"`
180− Models PreferredModels `json:"models,omitempty" jsonschema:"title=Models,description=Preferred model configurations for large and small model types"`
181− // List of configured providers
182− Providers map[provider.InferenceProvider]ProviderConfig `json:"providers,omitempty" jsonschema:"title=Providers,description=LLM provider configurations"`
184− // List of configured agents
185− Agents map[AgentID]Agent `json:"agents,omitempty" jsonschema:"title=Agents,description=Agent configurations for different tasks"`
187− // List of configured MCPs
188− MCP map[string]MCP `json:"mcp,omitempty" jsonschema:"title=MCP,description=Model Control Protocol server configurations"`
190− // List of configured LSPs
191− LSP map[string]LSPConfig `json:"lsp,omitempty" jsonschema:"title=LSP,description=Language Server Protocol configurations"`
193− // Miscellaneous options
194− Options Options `json:"options,omitempty" jsonschema:"title=Options,description=General application options and settings"`
198− instance *Config // The single instance of the Singleton
200− once sync.Once // Ensures the initialization happens only once
204−func readConfigFile(path string) (*Config, error) {
206− if _, err := os.Stat(path); err != nil && !os.IsNotExist(err) {
207− // some other error occurred while checking the file
209− } else if err == nil {
210− // config file exists, read it
211− file, err := os.ReadFile(path)
216− if err := json.Unmarshal(file, cfg); err != nil {
220− // config file does not exist, create a new one
226−func loadConfig(cwd string, debug bool) (*Config, error) {
128+func (m MCPs) Sorted() []MCP {
227− // First read the global config file
129+ sorted := make([]MCP, 0, len(m))
228− cfgPath := ConfigPath()
130+ for k, v := range m {
229−
131+ sorted = append(sorted, MCP{
230− cfg := defaultConfigBasedOnEnv()
132+ Name: k,
231− cfg.Options.Debug = debug
133+ MCP: v,
232− defaultLevel := slog.LevelInfo
134+ })
233− if cfg.Options.Debug {
234− defaultLevel = slog.LevelDebug
236− if os.Getenv("CRUSH_DEV_DEBUG") == "true" {
136+ slices.SortFunc(sorted, func(a, b MCP) int {
237− loggingFile := fmt.Sprintf("%s/%s", cfg.Options.DataDirectory, "debug.log")
137+ return strings.Compare(a.Name, b.Name)
239− // if file does not exist create it
240− if _, err := os.Stat(loggingFile); os.IsNotExist(err) {
241− if err := os.MkdirAll(cfg.Options.DataDirectory, 0o755); err != nil {
242− return cfg, fmt.Errorf("failed to create directory: %w", err)
244− if _, err := os.Create(loggingFile); err != nil {
245− return cfg, fmt.Errorf("failed to create log file: %w", err)
249− messagesPath := fmt.Sprintf("%s/%s", cfg.Options.DataDirectory, "messages")
251− if _, err := os.Stat(messagesPath); os.IsNotExist(err) {
252− if err := os.MkdirAll(messagesPath, 0o756); err != nil {
253− return cfg, fmt.Errorf("failed to create directory: %w", err)
256− logging.MessageDir = messagesPath
258− sloggingFileWriter, err := os.OpenFile(loggingFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
260− return cfg, fmt.Errorf("failed to open log file: %w", err)
263− logger := slog.New(slog.NewTextHandler(sloggingFileWriter, &slog.HandlerOptions{
264− Level: defaultLevel,
266− slog.SetDefault(logger)
269− logger := slog.New(slog.NewTextHandler(logging.NewWriter(), &slog.HandlerOptions{
270− Level: defaultLevel,
272− slog.SetDefault(logger)
275− priorityOrderedConfigFiles := []string{
276− cfgPath, // Global config file
277− filepath.Join(cwd, "crush.json"), // Local config file
278− filepath.Join(cwd, ".crush.json"), // Local config file
281− configs := make([]*Config, 0)
282− for _, path := range priorityOrderedConfigFiles {
283− localConfig, err := readConfigFile(path)
285− return nil, fmt.Errorf("failed to read config file %s: %w", path, err)
287− if localConfig != nil {
288− // If the config file was read successfully, add it to the list
289− configs = append(configs, localConfig)
294− mergeOptions(cfg, configs...)
296− mergeProviderConfigs(cfg, configs...)
297− // no providers found the app is not initialized yet
298− if len(cfg.Providers) == 0 {
301− preferredProvider := getPreferredProvider(cfg.Providers)
302− if preferredProvider != nil {
303− cfg.Models = PreferredModels{
304− Large: PreferredModel{
305− ModelID: preferredProvider.DefaultLargeModel,
306− Provider: preferredProvider.ID,
308− Small: PreferredModel{
309− ModelID: preferredProvider.DefaultSmallModel,
310− Provider: preferredProvider.ID,
314− // No valid providers found, set empty models
315− cfg.Models = PreferredModels{}
318− mergeModels(cfg, configs...)
320− agents := map[AgentID]Agent{
324− Description: "An agent that helps with executing coding tasks.",
326− ContextPaths: cfg.Options.ContextPaths,
327− // All tools allowed
332− Description: "An agent that helps with searching for context and finding implementation details.",
334− ContextPaths: cfg.Options.ContextPaths,
335− AllowedTools: []string{
342− // NO MCPs or LSPs by default
343− AllowedMCP: map[string][]string{},
344− AllowedLSP: []string{},
348− mergeAgents(cfg, configs...)
349− mergeMCPs(cfg, configs...)
350− mergeLSPs(cfg, configs...)
352− // Validate the final configuration
353− if err := cfg.Validate(); err != nil {
354− return cfg, fmt.Errorf("configuration validation failed: %w", err)
360−func Init(workingDir string, debug bool) (*Config, error) {
364− instance, err = loadConfig(cwd, debug)
366− logging.Error("Failed to load config", "error", err)
370− return instance, err
139+ return sorted
373−func Get() *Config {
142+type LSPs map[string]LSPConfig
374− if instance == nil {
375− // TODO: Handle this better
376− panic("Config not initialized. Call InitConfig first.")
381−func getPreferredProvider(configuredProviders map[provider.InferenceProvider]ProviderConfig) *ProviderConfig {
382− providers := Providers()
383− for _, p := range providers {
384− if providerConfig, ok := configuredProviders[p.ID]; ok && !providerConfig.Disabled {
385− return &providerConfig
388− // if none found return the first configured provider
389− for _, providerConfig := range configuredProviders {
390− if !providerConfig.Disabled {
391− return &providerConfig
397−func mergeProviderConfig(p provider.InferenceProvider, base, other ProviderConfig) ProviderConfig {
398− if other.APIKey != "" {
399− base.APIKey = other.APIKey
401− // Only change these options if the provider is not a known provider
402− if !slices.Contains(provider.KnownProviders(), p) {
403− if other.BaseURL != "" {
404− base.BaseURL = other.BaseURL
406− if other.ProviderType != "" {
407− base.ProviderType = other.ProviderType
409− if len(other.ExtraHeaders) > 0 {
410− if base.ExtraHeaders == nil {
411− base.ExtraHeaders = make(map[string]string)
413− maps.Copy(base.ExtraHeaders, other.ExtraHeaders)
415− if len(other.ExtraParams) > 0 {
416− if base.ExtraParams == nil {
417− base.ExtraParams = make(map[string]string)
419− maps.Copy(base.ExtraParams, other.ExtraParams)
424− base.Disabled = other.Disabled
427− if other.DefaultLargeModel != "" {
428− base.DefaultLargeModel = other.DefaultLargeModel
430− // Add new models if they don't exist
431− if other.Models != nil {
432− for _, model := range other.Models {
433− // check if the model already exists
435− for _, existingModel := range base.Models {
436− if existingModel.ID == model.ID {
442− base.Models = append(base.Models, model)
450−func validateProvider(p provider.InferenceProvider, providerConfig ProviderConfig) error {
144+type LSP struct {
451− if !slices.Contains(provider.KnownProviders(), p) {
145+ Name string `json:"name"`
452− if providerConfig.ProviderType != provider.TypeOpenAI {
146+ LSP LSPConfig `json:"lsp"`
453− return errors.New("invalid provider type: " + string(providerConfig.ProviderType))
455− if providerConfig.BaseURL == "" {
456− return errors.New("base URL must be set for custom providers")
458− if providerConfig.APIKey == "" {
459− return errors.New("API key must be set for custom providers")
465−func mergeModels(base *Config, others ...*Config) {
149+func (l LSPs) Sorted() []LSP {
150+ sorted := make([]LSP, 0, len(l))
466− for _, cfg := range others {
151+ for k, v := range l {
467− if cfg == nil {
152+ sorted = append(sorted, LSP{
468− continue
153+ Name: k,
470− if cfg.Models.Large.ModelID != "" && cfg.Models.Large.Provider != "" {
471− base.Models.Large = cfg.Models.Large
474− if cfg.Models.Small.ModelID != "" && cfg.Models.Small.Provider != "" {
475− base.Models.Small = cfg.Models.Small
157+ slices.SortFunc(sorted, func(a, b LSP) int {
158+ return strings.Compare(a.Name, b.Name)
480−func mergeOptions(base *Config, others ...*Config) {
163+type Agent struct {
481− for _, cfg := range others {
164+ ID string `json:"id,omitempty"`
482− if cfg == nil {
165+ Name string `json:"name,omitempty"`
483− continue
166+ Description string `json:"description,omitempty"`
484− }
167+ // This is the id of the system prompt used by the agent
485− baseOptions := base.Options
168+ Disabled bool `json:"disabled,omitempty"`
486− other := cfg.Options
487− if len(other.ContextPaths) > 0 {
488− baseOptions.ContextPaths = append(baseOptions.ContextPaths, other.ContextPaths...)
491− if other.TUI.CompactMode {
170+ Model SelectedModelType `json:"model"`
492− baseOptions.TUI.CompactMode = other.TUI.CompactMode
495− if other.Debug {
172+ // The available tools for the agent
496− baseOptions.Debug = other.Debug
173+ // if this is nil, all tools are available
497− }
174+ AllowedTools []string `json:"allowed_tools,omitempty"`
499− if other.DebugLSP {
176+ // this tells us which MCPs are available for this agent
500− baseOptions.DebugLSP = other.DebugLSP
177+ // if this is empty all mcps are available
501− }
178+ // the string array is the list of tools from the AllowedMCP the agent has available
179+ // if the string array is nil, all tools from the AllowedMCP are available
180+ AllowedMCP map[string][]string `json:"allowed_mcp,omitempty"`
503− if other.DisableAutoSummarize {
182+ // The list of LSPs that this agent can use
504− baseOptions.DisableAutoSummarize = other.DisableAutoSummarize
183+ // if this is nil, all LSPs are available
505− }
184+ AllowedLSP []string `json:"allowed_lsp,omitempty"`
507− if other.DataDirectory != "" {
186+ // Overrides the context paths for this agent
508− baseOptions.DataDirectory = other.DataDirectory
187+ ContextPaths []string `json:"context_paths,omitempty"`
510− base.Options = baseOptions
514−func mergeAgents(base *Config, others ...*Config) {
190+// Config holds the configuration for crush.
515− for _, cfg := range others {
191+type Config struct {
516− if cfg == nil {
192+ // We currently only support large/small as values here.
517− continue
193+ Models map[SelectedModelType]SelectedModel `json:"models,omitempty"`
519− for agentID, newAgent := range cfg.Agents {
520− if _, ok := base.Agents[agentID]; !ok {
521− newAgent.ID = agentID
522− if newAgent.Model == "" {
523− newAgent.Model = LargeModel
525− if len(newAgent.ContextPaths) > 0 {
526− newAgent.ContextPaths = append(base.Options.ContextPaths, newAgent.ContextPaths...)
528− newAgent.ContextPaths = base.Options.ContextPaths
530− base.Agents[agentID] = newAgent
532− baseAgent := base.Agents[agentID]
534− if agentID == AgentCoder || agentID == AgentTask {
195+ // The providers that are configured
535− if newAgent.Model != "" {
196+ Providers map[string]ProviderConfig `json:"providers,omitempty"`
536− baseAgent.Model = newAgent.Model
538− if newAgent.AllowedMCP != nil {
539− baseAgent.AllowedMCP = newAgent.AllowedMCP
541− if newAgent.AllowedLSP != nil {
542− baseAgent.AllowedLSP = newAgent.AllowedLSP
544− // Context paths are additive for known agents too
545− if len(newAgent.ContextPaths) > 0 {
546− baseAgent.ContextPaths = append(baseAgent.ContextPaths, newAgent.ContextPaths...)
549− if newAgent.Name != "" {
550− baseAgent.Name = newAgent.Name
552− if newAgent.Description != "" {
553− baseAgent.Description = newAgent.Description
555− if newAgent.Model != "" {
556− baseAgent.Model = newAgent.Model
557− } else if baseAgent.Model == "" {
558− baseAgent.Model = LargeModel
561− baseAgent.Disabled = newAgent.Disabled
198+ MCP MCPs `json:"mcp,omitempty"`
563− if newAgent.AllowedTools != nil {
200+ LSP LSPs `json:"lsp,omitempty"`
564− baseAgent.AllowedTools = newAgent.AllowedTools
566− if newAgent.AllowedMCP != nil {
567− baseAgent.AllowedMCP = newAgent.AllowedMCP
569− if newAgent.AllowedLSP != nil {
570− baseAgent.AllowedLSP = newAgent.AllowedLSP
572− if len(newAgent.ContextPaths) > 0 {
573− baseAgent.ContextPaths = append(baseAgent.ContextPaths, newAgent.ContextPaths...)
577− base.Agents[agentID] = baseAgent
202+ Options *Options `json:"options,omitempty"`
583−func mergeMCPs(base *Config, others ...*Config) {
204+ // Internal
584− for _, cfg := range others {
205+ workingDir string `json:"-"`
585− if cfg == nil {
206+ // TODO: most likely remove this concept when I come back to it
586− continue
207+ Agents map[string]Agent `json:"-"`
587− }
208+ // TODO: find a better way to do this this should probably not be part of the config
588− maps.Copy(base.MCP, cfg.MCP)
209+ resolver VariableResolver
592−func mergeLSPs(base *Config, others ...*Config) {
212+func (c *Config) WorkingDir() string {
593− for _, cfg := range others {
213+ return c.workingDir
597− maps.Copy(base.LSP, cfg.LSP)
601−func mergeProviderConfigs(base *Config, others ...*Config) {
216+func (c *Config) EnabledProviders() []ProviderConfig {
602− for _, cfg := range others {
217+ enabled := make([]ProviderConfig, 0, len(c.Providers))
606− for providerName, p := range cfg.Providers {
608− if _, ok := base.Providers[providerName]; !ok {
609− if slices.Contains(provider.KnownProviders(), providerName) {
610− providers := Providers()
611− for _, providerDef := range providers {
612− if providerDef.ID == providerName {
613− logging.Info("Using default provider config for", "provider", providerName)
614− baseProvider := getDefaultProviderConfig(providerDef, providerDef.APIKey)
615− base.Providers[providerName] = mergeProviderConfig(providerName, baseProvider, p)
620− base.Providers[providerName] = p
623− base.Providers[providerName] = mergeProviderConfig(providerName, base.Providers[providerName], p)
628− finalProviders := make(map[provider.InferenceProvider]ProviderConfig)
629− for providerName, providerConfig := range base.Providers {
218+ for _, p := range c.Providers {
630− err := validateProvider(providerName, providerConfig)
219+ if !p.Disable {
631− if err != nil {
220+ enabled = append(enabled, p)
632− logging.Warn("Skipping provider", "name", providerName, "error", err)
633− continue // Skip invalid providers
635− finalProviders[providerName] = providerConfig
637− base.Providers = finalProviders
223+ return enabled
640−func providerDefaultConfig(providerID provider.InferenceProvider) ProviderConfig {
226+// IsConfigured return true if at least one provider is configured
641− switch providerID {
227+func (c *Config) IsConfigured() bool {
642− case provider.InferenceProviderAnthropic:
228+ return len(c.EnabledProviders()) > 0
643− return ProviderConfig{
645− ProviderType: provider.TypeAnthropic,
647− case provider.InferenceProviderOpenAI:
648− return ProviderConfig{
650− ProviderType: provider.TypeOpenAI,
652− case provider.InferenceProviderGemini:
653− return ProviderConfig{
655− ProviderType: provider.TypeGemini,
657− case provider.InferenceProviderBedrock:
658− return ProviderConfig{
660− ProviderType: provider.TypeBedrock,
662− case provider.InferenceProviderAzure:
663− return ProviderConfig{
665− ProviderType: provider.TypeAzure,
667− case provider.InferenceProviderOpenRouter:
668− return ProviderConfig{
670− ProviderType: provider.TypeOpenAI,
671− BaseURL: "https://openrouter.ai/api/v1",
672− ExtraHeaders: map[string]string{
673− "HTTP-Referer": "crush.charm.land",
677− case provider.InferenceProviderXAI:
678− return ProviderConfig{
680− ProviderType: provider.TypeXAI,
681− BaseURL: "https://api.x.ai/v1",
683− case provider.InferenceProviderVertexAI:
684− return ProviderConfig{
686− ProviderType: provider.TypeVertexAI,
689− return ProviderConfig{
691− ProviderType: provider.TypeOpenAI,
696−func getDefaultProviderConfig(p provider.Provider, apiKey string) ProviderConfig {
231+func (c *Config) GetModel(provider, model string) *provider.Model {
697− providerConfig := providerDefaultConfig(p.ID)
232+ if providerConfig, ok := c.Providers[provider]; ok {
698− providerConfig.APIKey = apiKey
699− providerConfig.DefaultLargeModel = p.DefaultLargeModelID
700− providerConfig.DefaultSmallModel = p.DefaultSmallModelID
701− baseURL := p.APIEndpoint
702− if strings.HasPrefix(baseURL, "$") {
703− envVar := strings.TrimPrefix(baseURL, "$")
704− baseURL = os.Getenv(envVar)
706− providerConfig.BaseURL = baseURL
707− for _, model := range p.Models {
233+ for _, m := range providerConfig.Models {
708− configModel := Model{
234+ if m.ID == model {
711− CostPer1MIn: model.CostPer1MIn,
712− CostPer1MOut: model.CostPer1MOut,
713− CostPer1MInCached: model.CostPer1MInCached,
714− CostPer1MOutCached: model.CostPer1MOutCached,
715− ContextWindow: model.ContextWindow,
716− DefaultMaxTokens: model.DefaultMaxTokens,
717− CanReason: model.CanReason,
718− SupportsImages: model.SupportsImages,
720− // Set reasoning effort for reasoning models
721− if model.HasReasoningEffort && model.DefaultReasoningEffort != "" {
722− configModel.HasReasoningEffort = model.HasReasoningEffort
723− configModel.ReasoningEffort = model.DefaultReasoningEffort
725− providerConfig.Models = append(providerConfig.Models, configModel)
727− return providerConfig
235+ return &m
730−func defaultConfigBasedOnEnv() *Config {
733− DataDirectory: defaultDataDirectory,
734− ContextPaths: defaultContextPaths,
736− Providers: make(map[provider.InferenceProvider]ProviderConfig),
737− Agents: make(map[AgentID]Agent),
738− LSP: make(map[string]LSPConfig),
739− MCP: make(map[string]MCP),
742− providers := Providers()
744− for _, p := range providers {
745− if strings.HasPrefix(p.APIKey, "$") {
746− envVar := strings.TrimPrefix(p.APIKey, "$")
747− if apiKey := os.Getenv(envVar); apiKey != "" {
748− cfg.Providers[p.ID] = getDefaultProviderConfig(p, apiKey)
752− // TODO: support local models
754− if useVertexAI := os.Getenv("GOOGLE_GENAI_USE_VERTEXAI"); useVertexAI == "true" {
755− providerConfig := providerDefaultConfig(provider.InferenceProviderVertexAI)
756− providerConfig.ExtraParams = map[string]string{
757− "project": os.Getenv("GOOGLE_CLOUD_PROJECT"),
758− "location": os.Getenv("GOOGLE_CLOUD_LOCATION"),
760− // Find the VertexAI provider definition to get default models
761− for _, p := range providers {
762− if p.ID == provider.InferenceProviderVertexAI {
763− providerConfig.DefaultLargeModel = p.DefaultLargeModelID
764− providerConfig.DefaultSmallModel = p.DefaultSmallModelID
765− for _, model := range p.Models {
766− configModel := Model{
769− CostPer1MIn: model.CostPer1MIn,
770− CostPer1MOut: model.CostPer1MOut,
771− CostPer1MInCached: model.CostPer1MInCached,
772− CostPer1MOutCached: model.CostPer1MOutCached,
773− ContextWindow: model.ContextWindow,
774− DefaultMaxTokens: model.DefaultMaxTokens,
775− CanReason: model.CanReason,
776− SupportsImages: model.SupportsImages,
778− // Set reasoning effort for reasoning models
779− if model.HasReasoningEffort && model.DefaultReasoningEffort != "" {
780− configModel.HasReasoningEffort = model.HasReasoningEffort
781− configModel.ReasoningEffort = model.DefaultReasoningEffort
783− providerConfig.Models = append(providerConfig.Models, configModel)
788− cfg.Providers[provider.InferenceProviderVertexAI] = providerConfig
791− if hasAWSCredentials() {
792− providerConfig := providerDefaultConfig(provider.InferenceProviderBedrock)
793− providerConfig.ExtraParams = map[string]string{
794− "region": os.Getenv("AWS_DEFAULT_REGION"),
796− if providerConfig.ExtraParams["region"] == "" {
797− providerConfig.ExtraParams["region"] = os.Getenv("AWS_REGION")
799− // Find the Bedrock provider definition to get default models
800− for _, p := range providers {
801− if p.ID == provider.InferenceProviderBedrock {
802− providerConfig.DefaultLargeModel = p.DefaultLargeModelID
803− providerConfig.DefaultSmallModel = p.DefaultSmallModelID
804− for _, model := range p.Models {
805− configModel := Model{
808− CostPer1MIn: model.CostPer1MIn,
809− CostPer1MOut: model.CostPer1MOut,
810− CostPer1MInCached: model.CostPer1MInCached,
811− CostPer1MOutCached: model.CostPer1MOutCached,
812− ContextWindow: model.ContextWindow,
813− DefaultMaxTokens: model.DefaultMaxTokens,
814− CanReason: model.CanReason,
815− SupportsImages: model.SupportsImages,
817− // Set reasoning effort for reasoning models
818− if model.HasReasoningEffort && model.DefaultReasoningEffort != "" {
819− configModel.HasReasoningEffort = model.HasReasoningEffort
820− configModel.ReasoningEffort = model.DefaultReasoningEffort
822− providerConfig.Models = append(providerConfig.Models, configModel)
827− cfg.Providers[provider.InferenceProviderBedrock] = providerConfig
832−func hasAWSCredentials() bool {
833− if os.Getenv("AWS_ACCESS_KEY_ID") != "" && os.Getenv("AWS_SECRET_ACCESS_KEY") != "" {
837− if os.Getenv("AWS_PROFILE") != "" || os.Getenv("AWS_DEFAULT_PROFILE") != "" {
838− return true
239+ return nil
841− if os.Getenv("AWS_REGION") != "" || os.Getenv("AWS_DEFAULT_REGION") != "" {
845− if os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") != "" ||
846− os.Getenv("AWS_CONTAINER_CREDENTIALS_FULL_URI") != "" {
853−func WorkingDirectory() string {
857−// TODO: Handle error state
242+func (c *Config) GetProviderForModel(modelType SelectedModelType) *ProviderConfig {
858−
243+ model, ok := c.Models[modelType]
859−func GetAgentModel(agentID AgentID) Model {
861− agent, ok := cfg.Agents[agentID]
863− logging.Error("Agent not found", "agent_id", agentID)
867− var model PreferredModel
868− switch agent.Model {
870− model = cfg.Models.Large
872− model = cfg.Models.Small
874− logging.Warn("Unknown model type for agent", "agent_id", agentID, "model_type", agent.Model)
875− model = cfg.Models.Large // Fallback to large model
877− providerConfig, ok := cfg.Providers[model.Provider]
879− logging.Error("Provider not found for agent", "agent_id", agentID, "provider", model.Provider)
880− return Model{}
245+ return nil
882−
247+ if providerConfig, ok := c.Providers[model.Provider]; ok {
883− for _, m := range providerConfig.Models {
884− if m.ID == model.ModelID {
885− return m
248+ return &providerConfig
889− logging.Error("Model not found for agent", "agent_id", agentID, "model", agent.Model)
890− return Model{}
250+ return nil
893−func GetAgentProvider(agentID AgentID) ProviderConfig {
253+func (c *Config) GetModelByType(modelType SelectedModelType) *provider.Model {
894− cfg := Get()
254+ model, ok := c.Models[modelType]
895− agent, ok := cfg.Agents[agentID]
897− logging.Error("Agent not found", "agent_id", agentID)
898− return ProviderConfig{}
256+ return nil
900−
258+ return c.GetModel(model.Provider, model.Model)
901− var model PreferredModel
902− switch agent.Model {
904− model = cfg.Models.Large
906− model = cfg.Models.Small
908− logging.Warn("Unknown model type for agent", "agent_id", agentID, "model_type", agent.Model)
909− model = cfg.Models.Large // Fallback to large model
912− providerConfig, ok := cfg.Providers[model.Provider]
914− logging.Error("Provider not found for agent", "agent_id", agentID, "provider", model.Provider)
915− return ProviderConfig{}
918− return providerConfig
921−func GetProviderModel(provider provider.InferenceProvider, modelID string) Model {
261+func (c *Config) LargeModel() *provider.Model {
922− cfg := Get()
262+ model, ok := c.Models[SelectedModelTypeLarge]
923− providerConfig, ok := cfg.Providers[provider]
925− logging.Error("Provider not found", "provider", provider)
926− return Model{}
264+ return nil
928−
266+ return c.GetModel(model.Provider, model.Model)
929− for _, model := range providerConfig.Models {
930− if model.ID == modelID {
935− logging.Error("Model not found for provider", "provider", provider, "model_id", modelID)
939−func GetModel(modelType ModelType) Model {
269+func (c *Config) SmallModel() *provider.Model {
940− cfg := Get()
270+ model, ok := c.Models[SelectedModelTypeSmall]
941− var model PreferredModel
944− model = cfg.Models.Large
946− model = cfg.Models.Small
948− model = cfg.Models.Large // Fallback to large model
950− providerConfig, ok := cfg.Providers[model.Provider]
952− return Model{}
272+ return nil
954−
274+ return c.GetModel(model.Provider, model.Model)
955− for _, m := range providerConfig.Models {
956− if m.ID == model.ModelID {
963−func UpdatePreferredModel(modelType ModelType, model PreferredModel) error {
277+func (c *Config) Resolve(key string) (string, error) {
964− cfg := Get()
278+ if c.resolver == nil {
965− switch modelType {
279+ return "", fmt.Errorf("no variable resolver configured")
967− cfg.Models.Large = model
969− cfg.Models.Small = model
971− return fmt.Errorf("unknown model type: %s", modelType)
973− return nil
281+ return c.resolver.ResolveValue(key)
976−// ValidationError represents a configuration validation error
977−type ValidationError struct {
982−func (e ValidationError) Error() string {
284+// TODO: maybe handle this better
983− return fmt.Sprintf("validation error in %s: %s", e.Field, e.Message)
285+func UpdatePreferredModel(modelType SelectedModelType, model SelectedModel) error {
985−
287+ cfg.Models[modelType] = model
986−// ValidationErrors represents multiple validation errors
987−type ValidationErrors []ValidationError
989−func (e ValidationErrors) Error() string {
991− return "no validation errors"
997− var messages []string
998− for _, err := range e {
999− messages = append(messages, err.Error())
1001− return fmt.Sprintf("multiple validation errors: %s", strings.Join(messages, "; "))
1004−// HasErrors returns true if there are any validation errors
1005−func (e ValidationErrors) HasErrors() bool {
1009−// Add appends a new validation error
1010−func (e *ValidationErrors) Add(field, message string) {
1011− *e = append(*e, ValidationError{Field: field, Message: message})
1014−// Validate performs comprehensive validation of the configuration
1015−func (c *Config) Validate() error {
1016− var errors ValidationErrors
1018− // Validate providers
1019− c.validateProviders(&errors)
1022− c.validateModels(&errors)
1025− c.validateAgents(&errors)
1027− // Validate options
1028− c.validateOptions(&errors)
1030− // Validate MCP configurations
1031− c.validateMCPs(&errors)
1033− // Validate LSP configurations
1034− c.validateLSPs(&errors)
1036− // Validate cross-references
1037− c.validateCrossReferences(&errors)
1039− // Validate completeness
1040− c.validateCompleteness(&errors)
1042− if errors.HasErrors() {
1046 return nil
288 return nil
1049−// validateProviders validates all provider configurations
1050−func (c *Config) validateProviders(errors *ValidationErrors) {
1051− if c.Providers == nil {
1052− c.Providers = make(map[provider.InferenceProvider]ProviderConfig)
1055− knownProviders := provider.KnownProviders()
1056− validTypes := []provider.Type{
1057− provider.TypeOpenAI,
1058− provider.TypeAnthropic,
1059− provider.TypeGemini,
1060− provider.TypeAzure,
1061− provider.TypeBedrock,
1062− provider.TypeVertexAI,
1066− for providerID, providerConfig := range c.Providers {
1067− fieldPrefix := fmt.Sprintf("providers.%s", providerID)
1069− // Validate API key for non-disabled providers
1070− if !providerConfig.Disabled && providerConfig.APIKey == "" {
1071− // Special case for AWS Bedrock and VertexAI which may use other auth methods
1072− if providerID != provider.InferenceProviderBedrock && providerID != provider.InferenceProviderVertexAI {
1073− errors.Add(fieldPrefix+".api_key", "API key is required for non-disabled providers")
1077− // Validate provider type
1078− validType := slices.Contains(validTypes, providerConfig.ProviderType)
1080− errors.Add(fieldPrefix+".provider_type", fmt.Sprintf("invalid provider type: %s", providerConfig.ProviderType))
1083− // Validate custom providers
1084− isKnownProvider := slices.Contains(knownProviders, providerID)
1086− if !isKnownProvider {
1087− // Custom provider validation
1088− if providerConfig.BaseURL == "" {
1089− errors.Add(fieldPrefix+".base_url", "BaseURL is required for custom providers")
1091− if providerConfig.ProviderType != provider.TypeOpenAI {
1092− errors.Add(fieldPrefix+".provider_type", "custom providers currently only support OpenAI type")
1097− modelIDs := make(map[string]bool)
1098− for i, model := range providerConfig.Models {
1099− modelFieldPrefix := fmt.Sprintf("%s.models[%d]", fieldPrefix, i)
1101− // Check for duplicate model IDs
1102− if modelIDs[model.ID] {
1103− errors.Add(modelFieldPrefix+".id", fmt.Sprintf("duplicate model ID: %s", model.ID))
1105− modelIDs[model.ID] = true
1107− // Validate required model fields
1108− if model.ID == "" {
1109− errors.Add(modelFieldPrefix+".id", "model ID is required")
1111− if model.Name == "" {
1112− errors.Add(modelFieldPrefix+".name", "model name is required")
1114− if model.ContextWindow <= 0 {
1115− errors.Add(modelFieldPrefix+".context_window", "context window must be positive")
1117− if model.DefaultMaxTokens <= 0 {
1118− errors.Add(modelFieldPrefix+".default_max_tokens", "default max tokens must be positive")
1120− if model.DefaultMaxTokens > model.ContextWindow {
1121− errors.Add(modelFieldPrefix+".default_max_tokens", "default max tokens cannot exceed context window")
1124− // Validate cost fields
1125− if model.CostPer1MIn < 0 {
1126− errors.Add(modelFieldPrefix+".cost_per_1m_in", "cost per 1M input tokens cannot be negative")
1128− if model.CostPer1MOut < 0 {
1129− errors.Add(modelFieldPrefix+".cost_per_1m_out", "cost per 1M output tokens cannot be negative")
1131− if model.CostPer1MInCached < 0 {
1132− errors.Add(modelFieldPrefix+".cost_per_1m_in_cached", "cached cost per 1M input tokens cannot be negative")
1134− if model.CostPer1MOutCached < 0 {
1135− errors.Add(modelFieldPrefix+".cost_per_1m_out_cached", "cached cost per 1M output tokens cannot be negative")
1139− // Validate default model references
1140− if providerConfig.DefaultLargeModel != "" {
1141− if !modelIDs[providerConfig.DefaultLargeModel] {
1142− errors.Add(fieldPrefix+".default_large_model", fmt.Sprintf("default large model '%s' not found in provider models", providerConfig.DefaultLargeModel))
1145− if providerConfig.DefaultSmallModel != "" {
1146− if !modelIDs[providerConfig.DefaultSmallModel] {
1147− errors.Add(fieldPrefix+".default_small_model", fmt.Sprintf("default small model '%s' not found in provider models", providerConfig.DefaultSmallModel))
1151− // Validate provider-specific requirements
1152− c.validateProviderSpecific(providerID, providerConfig, errors)
1156−// validateProviderSpecific validates provider-specific requirements
1157−func (c *Config) validateProviderSpecific(providerID provider.InferenceProvider, providerConfig ProviderConfig, errors *ValidationErrors) {
1158− fieldPrefix := fmt.Sprintf("providers.%s", providerID)
1160− switch providerID {
1161− case provider.InferenceProviderVertexAI:
1162− if !providerConfig.Disabled {
1163− if providerConfig.ExtraParams == nil {
1164− errors.Add(fieldPrefix+".extra_params", "VertexAI requires extra_params configuration")
1166− if providerConfig.ExtraParams["project"] == "" {
1167− errors.Add(fieldPrefix+".extra_params.project", "VertexAI requires project parameter")
1169− if providerConfig.ExtraParams["location"] == "" {
1170− errors.Add(fieldPrefix+".extra_params.location", "VertexAI requires location parameter")
1174− case provider.InferenceProviderBedrock:
1175− if !providerConfig.Disabled {
1176− if providerConfig.ExtraParams == nil || providerConfig.ExtraParams["region"] == "" {
1177− errors.Add(fieldPrefix+".extra_params.region", "Bedrock requires region parameter")
1179− // Check for AWS credentials in environment
1180− if !hasAWSCredentials() {
1181− errors.Add(fieldPrefix, "Bedrock requires AWS credentials in environment")
1187−// validateModels validates preferred model configurations
1188−func (c *Config) validateModels(errors *ValidationErrors) {
1189− // Validate large model
1190− if c.Models.Large.ModelID != "" || c.Models.Large.Provider != "" {
1191− if c.Models.Large.ModelID == "" {
1192− errors.Add("models.large.model_id", "large model ID is required when provider is set")
1194− if c.Models.Large.Provider == "" {
1195− errors.Add("models.large.provider", "large model provider is required when model ID is set")
1198− // Check if provider exists and is not disabled
1199− if providerConfig, exists := c.Providers[c.Models.Large.Provider]; exists {
1200− if providerConfig.Disabled {
1201− errors.Add("models.large.provider", "large model provider is disabled")
1204− // Check if model exists in provider
1205− modelExists := false
1206− for _, model := range providerConfig.Models {
1207− if model.ID == c.Models.Large.ModelID {
1213− errors.Add("models.large.model_id", fmt.Sprintf("large model '%s' not found in provider '%s'", c.Models.Large.ModelID, c.Models.Large.Provider))
1216− errors.Add("models.large.provider", fmt.Sprintf("large model provider '%s' not found", c.Models.Large.Provider))
1220− // Validate small model
1221− if c.Models.Small.ModelID != "" || c.Models.Small.Provider != "" {
1222− if c.Models.Small.ModelID == "" {
1223− errors.Add("models.small.model_id", "small model ID is required when provider is set")
1225− if c.Models.Small.Provider == "" {
1226− errors.Add("models.small.provider", "small model provider is required when model ID is set")
1229− // Check if provider exists and is not disabled
1230− if providerConfig, exists := c.Providers[c.Models.Small.Provider]; exists {
1231− if providerConfig.Disabled {
1232− errors.Add("models.small.provider", "small model provider is disabled")
1235− // Check if model exists in provider
1236− modelExists := false
1237− for _, model := range providerConfig.Models {
1238− if model.ID == c.Models.Small.ModelID {
1244− errors.Add("models.small.model_id", fmt.Sprintf("small model '%s' not found in provider '%s'", c.Models.Small.ModelID, c.Models.Small.Provider))
1247− errors.Add("models.small.provider", fmt.Sprintf("small model provider '%s' not found", c.Models.Small.Provider))
1252−// validateAgents validates agent configurations
1253−func (c *Config) validateAgents(errors *ValidationErrors) {
1254− if c.Agents == nil {
1255− c.Agents = make(map[AgentID]Agent)
1258− validTools := []string{
1259− "bash", "edit", "fetch", "glob", "grep", "ls", "sourcegraph", "view", "write", "agent",
1262− for agentID, agent := range c.Agents {
1263− fieldPrefix := fmt.Sprintf("agents.%s", agentID)
1265− // Validate agent ID consistency
1266− if agent.ID != agentID {
1267− errors.Add(fieldPrefix+".id", fmt.Sprintf("agent ID mismatch: expected '%s', got '%s'", agentID, agent.ID))
1270− // Validate required fields
1271− if agent.ID == "" {
1272− errors.Add(fieldPrefix+".id", "agent ID is required")
1274− if agent.Name == "" {
1275− errors.Add(fieldPrefix+".name", "agent name is required")
1278− // Validate model type
1279− if agent.Model != LargeModel && agent.Model != SmallModel {
1280− errors.Add(fieldPrefix+".model", fmt.Sprintf("invalid model type: %s (must be 'large' or 'small')", agent.Model))
1283− // Validate allowed tools
1284− if agent.AllowedTools != nil {
1285− for i, tool := range agent.AllowedTools {
1286− validTool := slices.Contains(validTools, tool)
1288− errors.Add(fmt.Sprintf("%s.allowed_tools[%d]", fieldPrefix, i), fmt.Sprintf("unknown tool: %s", tool))
1293− // Validate MCP references
1294− if agent.AllowedMCP != nil {
1295− for mcpName := range agent.AllowedMCP {
1296− if _, exists := c.MCP[mcpName]; !exists {
1297− errors.Add(fieldPrefix+".allowed_mcp", fmt.Sprintf("referenced MCP '%s' not found", mcpName))
1302− // Validate LSP references
1303− if agent.AllowedLSP != nil {
1304− for _, lspName := range agent.AllowedLSP {
1305− if _, exists := c.LSP[lspName]; !exists {
1306− errors.Add(fieldPrefix+".allowed_lsp", fmt.Sprintf("referenced LSP '%s' not found", lspName))
1311− // Validate context paths (basic path validation)
1312− for i, contextPath := range agent.ContextPaths {
1313− if contextPath == "" {
1314− errors.Add(fmt.Sprintf("%s.context_paths[%d]", fieldPrefix, i), "context path cannot be empty")
1316− // Check for invalid characters in path
1317− if strings.Contains(contextPath, "\x00") {
1318− errors.Add(fmt.Sprintf("%s.context_paths[%d]", fieldPrefix, i), "context path contains invalid characters")
1322− // Validate known agents maintain their core properties
1323− if agentID == AgentCoder {
1324− if agent.Name != "Coder" {
1325− errors.Add(fieldPrefix+".name", "coder agent name cannot be changed")
1327− if agent.Description != "An agent that helps with executing coding tasks." {
1328− errors.Add(fieldPrefix+".description", "coder agent description cannot be changed")
1330− } else if agentID == AgentTask {
1331− if agent.Name != "Task" {
1332− errors.Add(fieldPrefix+".name", "task agent name cannot be changed")
1334− if agent.Description != "An agent that helps with searching for context and finding implementation details." {
1335− errors.Add(fieldPrefix+".description", "task agent description cannot be changed")
1337− expectedTools := []string{"glob", "grep", "ls", "sourcegraph", "view"}
1338− if agent.AllowedTools != nil && !slices.Equal(agent.AllowedTools, expectedTools) {
1339− errors.Add(fieldPrefix+".allowed_tools", "task agent allowed tools cannot be changed")
1345−// validateOptions validates configuration options
1346−func (c *Config) validateOptions(errors *ValidationErrors) {
1347− // Validate data directory
1348− if c.Options.DataDirectory == "" {
1349− errors.Add("options.data_directory", "data directory is required")
1352− // Validate context paths
1353− for i, contextPath := range c.Options.ContextPaths {
1354− if contextPath == "" {
1355− errors.Add(fmt.Sprintf("options.context_paths[%d]", i), "context path cannot be empty")
1357− if strings.Contains(contextPath, "\x00") {
1358− errors.Add(fmt.Sprintf("options.context_paths[%d]", i), "context path contains invalid characters")
1363−// validateMCPs validates MCP configurations
1364−func (c *Config) validateMCPs(errors *ValidationErrors) {
1366− c.MCP = make(map[string]MCP)
1369− for mcpName, mcpConfig := range c.MCP {
1370− fieldPrefix := fmt.Sprintf("mcp.%s", mcpName)
1372− // Validate MCP type
1373− if mcpConfig.Type != MCPStdio && mcpConfig.Type != MCPSse && mcpConfig.Type != MCPHttp {
1374− errors.Add(fieldPrefix+".type", fmt.Sprintf("invalid MCP type: %s (must be 'stdio' or 'sse' or 'http')", mcpConfig.Type))
1377− // Validate based on type
1378− if mcpConfig.Type == MCPStdio {
1379− if mcpConfig.Command == "" {
1380− errors.Add(fieldPrefix+".command", "command is required for stdio MCP")
1382− } else if mcpConfig.Type == MCPSse {
1383− if mcpConfig.URL == "" {
1384− errors.Add(fieldPrefix+".url", "URL is required for SSE MCP")
1390−// validateLSPs validates LSP configurations
1391−func (c *Config) validateLSPs(errors *ValidationErrors) {
1393− c.LSP = make(map[string]LSPConfig)
1396− for lspName, lspConfig := range c.LSP {
1397− fieldPrefix := fmt.Sprintf("lsp.%s", lspName)
1399− if lspConfig.Command == "" {
1400− errors.Add(fieldPrefix+".command", "command is required for LSP")
1405−// validateCrossReferences validates cross-references between different config sections
1406−func (c *Config) validateCrossReferences(errors *ValidationErrors) {
1407− // Validate that agents can use their assigned model types
1408− for agentID, agent := range c.Agents {
1409− fieldPrefix := fmt.Sprintf("agents.%s", agentID)
1411− var preferredModel PreferredModel
1412− switch agent.Model {
1414− preferredModel = c.Models.Large
1416− preferredModel = c.Models.Small
1419− if preferredModel.Provider != "" {
1420− if providerConfig, exists := c.Providers[preferredModel.Provider]; exists {
1421− if providerConfig.Disabled {
1422− errors.Add(fieldPrefix+".model", fmt.Sprintf("agent cannot use model type '%s' because provider '%s' is disabled", agent.Model, preferredModel.Provider))
1429−// validateCompleteness validates that the configuration is complete and usable
1430−func (c *Config) validateCompleteness(errors *ValidationErrors) {
1431− // Check for at least one valid, non-disabled provider
1432− hasValidProvider := false
1433− for _, providerConfig := range c.Providers {
1434− if !providerConfig.Disabled {
1435− hasValidProvider = true
1439− if !hasValidProvider {
1440− errors.Add("providers", "at least one non-disabled provider is required")
1443− // Check that default agents exist
1444− if _, exists := c.Agents[AgentCoder]; !exists {
1445− errors.Add("agents", "coder agent is required")
1447− if _, exists := c.Agents[AgentTask]; !exists {
1448− errors.Add("agents", "task agent is required")
1451− // Check that preferred models are set if providers exist
1452− if hasValidProvider {
1453− if c.Models.Large.ModelID == "" || c.Models.Large.Provider == "" {
1454− errors.Add("models.large", "large preferred model must be configured when providers are available")
1456− if c.Models.Small.ModelID == "" || c.Models.Small.Provider == "" {
1457− errors.Add("models.small", "small preferred model must be configured when providers are available")
1462−// JSONSchemaExtend adds custom schema properties for AgentID
1463−func (AgentID) JSONSchemaExtend(schema *jsonschema.Schema) {
1464− schema.Enum = []any{
1465− string(AgentCoder),
1470−// JSONSchemaExtend adds custom schema properties for ModelType
1471−func (ModelType) JSONSchemaExtend(schema *jsonschema.Schema) {
1472− schema.Enum = []any{
1473− string(LargeModel),
1474− string(SmallModel),
1478−// JSONSchemaExtend adds custom schema properties for MCPType
1479−func (MCPType) JSONSchemaExtend(schema *jsonschema.Schema) {
1480− schema.Enum = []any{
internal/config/config_test.godeleted−2075
@@ -1,2075 +0,0 @@
10− "github.com/charmbracelet/crush/internal/fur/provider"
11− "github.com/stretchr/testify/assert"
12− "github.com/stretchr/testify/require"
16− // Clear all environment variables that could affect config
17− envVarsToUnset := []string{
23− "OPENROUTER_API_KEY",
25− // Google Cloud / VertexAI
26− "GOOGLE_GENAI_USE_VERTEXAI",
27− "GOOGLE_CLOUD_PROJECT",
28− "GOOGLE_CLOUD_LOCATION",
32− "AWS_SECRET_ACCESS_KEY",
34− "AWS_DEFAULT_REGION",
36− "AWS_DEFAULT_PROFILE",
37− "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI",
38− "AWS_CONTAINER_CREDENTIALS_FULL_URI",
44− for _, envVar := range envVarsToUnset {
54− // Enable mock providers for all tests to avoid API calls
55− UseMockProviders = true
59−// Core Configuration Loading Tests
61−func TestInit_ValidWorkingDirectory(t *testing.T) {
63− testConfigDir = t.TempDir()
64− cwdDir := t.TempDir()
66− cfg, err := Init(cwdDir, false)
68− require.NoError(t, err)
69− assert.NotNil(t, cfg)
70− assert.Equal(t, cwdDir, WorkingDirectory())
71− assert.Equal(t, defaultDataDirectory, cfg.Options.DataDirectory)
72− assert.Equal(t, defaultContextPaths, cfg.Options.ContextPaths)
75−func TestInit_WithDebugFlag(t *testing.T) {
77− testConfigDir = t.TempDir()
78− cwdDir := t.TempDir()
80− cfg, err := Init(cwdDir, true)
82− require.NoError(t, err)
83− assert.True(t, cfg.Options.Debug)
86−func TestInit_SingletonBehavior(t *testing.T) {
88− testConfigDir = t.TempDir()
89− cwdDir := t.TempDir()
91− cfg1, err1 := Init(cwdDir, false)
92− cfg2, err2 := Init(cwdDir, false)
94− require.NoError(t, err1)
95− require.NoError(t, err2)
96− assert.Same(t, cfg1, cfg2)
99−func TestGet_BeforeInitialization(t *testing.T) {
102− assert.Panics(t, func() {
107−func TestGet_AfterInitialization(t *testing.T) {
109− testConfigDir = t.TempDir()
110− cwdDir := t.TempDir()
112− cfg1, err := Init(cwdDir, false)
113− require.NoError(t, err)
116− assert.Same(t, cfg1, cfg2)
119−func TestLoadConfig_NoConfigFiles(t *testing.T) {
121− testConfigDir = t.TempDir()
122− cwdDir := t.TempDir()
124− cfg, err := Init(cwdDir, false)
126− require.NoError(t, err)
127− assert.Len(t, cfg.Providers, 0)
128− assert.Equal(t, defaultContextPaths, cfg.Options.ContextPaths)
131−func TestLoadConfig_OnlyGlobalConfig(t *testing.T) {
133− testConfigDir = t.TempDir()
134− cwdDir := t.TempDir()
136− globalConfig := Config{
137− Providers: map[provider.InferenceProvider]ProviderConfig{
138− provider.InferenceProviderOpenAI: {
139− ID: provider.InferenceProviderOpenAI,
141− ProviderType: provider.TypeOpenAI,
142− DefaultLargeModel: "gpt-4",
143− DefaultSmallModel: "gpt-3.5-turbo",
150− ContextWindow: 8192,
151− DefaultMaxTokens: 4096,
154− ID: "gpt-3.5-turbo",
155− Name: "GPT-3.5 Turbo",
158− ContextWindow: 4096,
159− DefaultMaxTokens: 4096,
165− ContextPaths: []string{"custom-context.md"},
169− configPath := filepath.Join(testConfigDir, "crush.json")
170− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
172− data, err := json.Marshal(globalConfig)
173− require.NoError(t, err)
174− require.NoError(t, os.WriteFile(configPath, data, 0o644))
176− cfg, err := Init(cwdDir, false)
178− require.NoError(t, err)
179− assert.Len(t, cfg.Providers, 1)
180− assert.Contains(t, cfg.Providers, provider.InferenceProviderOpenAI)
181− assert.Contains(t, cfg.Options.ContextPaths, "custom-context.md")
184−func TestLoadConfig_OnlyLocalConfig(t *testing.T) {
186− testConfigDir = t.TempDir()
187− cwdDir := t.TempDir()
189− localConfig := Config{
190− Providers: map[provider.InferenceProvider]ProviderConfig{
191− provider.InferenceProviderAnthropic: {
192− ID: provider.InferenceProviderAnthropic,
193− APIKey: "local-key",
194− ProviderType: provider.TypeAnthropic,
195− DefaultLargeModel: "claude-3-opus",
196− DefaultSmallModel: "claude-3-haiku",
199− ID: "claude-3-opus",
200− Name: "Claude 3 Opus",
203− ContextWindow: 200000,
204− DefaultMaxTokens: 4096,
207− ID: "claude-3-haiku",
208− Name: "Claude 3 Haiku",
211− ContextWindow: 200000,
212− DefaultMaxTokens: 4096,
218− TUI: TUIOptions{CompactMode: true},
222− localConfigPath := filepath.Join(cwdDir, "crush.json")
223− data, err := json.Marshal(localConfig)
224− require.NoError(t, err)
225− require.NoError(t, os.WriteFile(localConfigPath, data, 0o644))
227− cfg, err := Init(cwdDir, false)
229− require.NoError(t, err)
230− assert.Len(t, cfg.Providers, 1)
231− assert.Contains(t, cfg.Providers, provider.InferenceProviderAnthropic)
232− assert.True(t, cfg.Options.TUI.CompactMode)
235−func TestLoadConfig_BothGlobalAndLocal(t *testing.T) {
237− testConfigDir = t.TempDir()
238− cwdDir := t.TempDir()
240− globalConfig := Config{
241− Providers: map[provider.InferenceProvider]ProviderConfig{
242− provider.InferenceProviderOpenAI: {
243− ID: provider.InferenceProviderOpenAI,
244− APIKey: "global-key",
245− ProviderType: provider.TypeOpenAI,
246− DefaultLargeModel: "gpt-4",
247− DefaultSmallModel: "gpt-3.5-turbo",
254− ContextWindow: 8192,
255− DefaultMaxTokens: 4096,
258− ID: "gpt-3.5-turbo",
259− Name: "GPT-3.5 Turbo",
262− ContextWindow: 4096,
263− DefaultMaxTokens: 4096,
269− ContextPaths: []string{"global-context.md"},
273− configPath := filepath.Join(testConfigDir, "crush.json")
274− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
275− data, err := json.Marshal(globalConfig)
276− require.NoError(t, err)
277− require.NoError(t, os.WriteFile(configPath, data, 0o644))
279− localConfig := Config{
280− Providers: map[provider.InferenceProvider]ProviderConfig{
281− provider.InferenceProviderOpenAI: {
282− APIKey: "local-key", // Override global
284− provider.InferenceProviderAnthropic: {
285− ID: provider.InferenceProviderAnthropic,
286− APIKey: "anthropic-key",
287− ProviderType: provider.TypeAnthropic,
288− DefaultLargeModel: "claude-3-opus",
289− DefaultSmallModel: "claude-3-haiku",
292− ID: "claude-3-opus",
293− Name: "Claude 3 Opus",
296− ContextWindow: 200000,
297− DefaultMaxTokens: 4096,
300− ID: "claude-3-haiku",
301− Name: "Claude 3 Haiku",
304− ContextWindow: 200000,
305− DefaultMaxTokens: 4096,
311− ContextPaths: []string{"local-context.md"},
312− TUI: TUIOptions{CompactMode: true},
316− localConfigPath := filepath.Join(cwdDir, "crush.json")
317− data, err = json.Marshal(localConfig)
318− require.NoError(t, err)
319− require.NoError(t, os.WriteFile(localConfigPath, data, 0o644))
321− cfg, err := Init(cwdDir, false)
323− require.NoError(t, err)
324− assert.Len(t, cfg.Providers, 2)
326− openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
327− assert.Equal(t, "local-key", openaiProvider.APIKey)
329− assert.Contains(t, cfg.Providers, provider.InferenceProviderAnthropic)
331− assert.Contains(t, cfg.Options.ContextPaths, "global-context.md")
332− assert.Contains(t, cfg.Options.ContextPaths, "local-context.md")
333− assert.True(t, cfg.Options.TUI.CompactMode)
336−func TestLoadConfig_MalformedGlobalJSON(t *testing.T) {
338− testConfigDir = t.TempDir()
339− cwdDir := t.TempDir()
341− configPath := filepath.Join(testConfigDir, "crush.json")
342− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
343− require.NoError(t, os.WriteFile(configPath, []byte(`{invalid json`), 0o644))
345− _, err := Init(cwdDir, false)
346− assert.Error(t, err)
349−func TestLoadConfig_MalformedLocalJSON(t *testing.T) {
351− testConfigDir = t.TempDir()
352− cwdDir := t.TempDir()
354− localConfigPath := filepath.Join(cwdDir, "crush.json")
355− require.NoError(t, os.WriteFile(localConfigPath, []byte(`{invalid json`), 0o644))
357− _, err := Init(cwdDir, false)
358− assert.Error(t, err)
361−func TestConfigWithoutEnv(t *testing.T) {
363− testConfigDir = t.TempDir()
364− cwdDir := t.TempDir()
366− cfg, _ := Init(cwdDir, false)
367− assert.Len(t, cfg.Providers, 0)
370−func TestConfigWithEnv(t *testing.T) {
372− testConfigDir = t.TempDir()
373− cwdDir := t.TempDir()
375− os.Setenv("ANTHROPIC_API_KEY", "test-anthropic-key")
376− os.Setenv("OPENAI_API_KEY", "test-openai-key")
377− os.Setenv("GEMINI_API_KEY", "test-gemini-key")
378− os.Setenv("XAI_API_KEY", "test-xai-key")
379− os.Setenv("OPENROUTER_API_KEY", "test-openrouter-key")
381− cfg, _ := Init(cwdDir, false)
382− assert.Len(t, cfg.Providers, 5)
385−// Environment Variable Tests
387−func TestEnvVars_NoEnvironmentVariables(t *testing.T) {
389− testConfigDir = t.TempDir()
390− cwdDir := t.TempDir()
392− cfg, err := Init(cwdDir, false)
394− require.NoError(t, err)
395− assert.Len(t, cfg.Providers, 0)
398−func TestEnvVars_AllSupportedAPIKeys(t *testing.T) {
400− testConfigDir = t.TempDir()
401− cwdDir := t.TempDir()
403− os.Setenv("ANTHROPIC_API_KEY", "test-anthropic-key")
404− os.Setenv("OPENAI_API_KEY", "test-openai-key")
405− os.Setenv("GEMINI_API_KEY", "test-gemini-key")
406− os.Setenv("XAI_API_KEY", "test-xai-key")
407− os.Setenv("OPENROUTER_API_KEY", "test-openrouter-key")
409− cfg, err := Init(cwdDir, false)
411− require.NoError(t, err)
412− assert.Len(t, cfg.Providers, 5)
414− anthropicProvider := cfg.Providers[provider.InferenceProviderAnthropic]
415− assert.Equal(t, "test-anthropic-key", anthropicProvider.APIKey)
416− assert.Equal(t, provider.TypeAnthropic, anthropicProvider.ProviderType)
418− openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
419− assert.Equal(t, "test-openai-key", openaiProvider.APIKey)
420− assert.Equal(t, provider.TypeOpenAI, openaiProvider.ProviderType)
422− geminiProvider := cfg.Providers[provider.InferenceProviderGemini]
423− assert.Equal(t, "test-gemini-key", geminiProvider.APIKey)
424− assert.Equal(t, provider.TypeGemini, geminiProvider.ProviderType)
426− xaiProvider := cfg.Providers[provider.InferenceProviderXAI]
427− assert.Equal(t, "test-xai-key", xaiProvider.APIKey)
428− assert.Equal(t, provider.TypeXAI, xaiProvider.ProviderType)
430− openrouterProvider := cfg.Providers[provider.InferenceProviderOpenRouter]
431− assert.Equal(t, "test-openrouter-key", openrouterProvider.APIKey)
432− assert.Equal(t, provider.TypeOpenAI, openrouterProvider.ProviderType)
433− assert.Equal(t, "https://openrouter.ai/api/v1", openrouterProvider.BaseURL)
436−func TestEnvVars_PartialEnvironmentVariables(t *testing.T) {
438− testConfigDir = t.TempDir()
439− cwdDir := t.TempDir()
441− os.Setenv("ANTHROPIC_API_KEY", "test-anthropic-key")
442− os.Setenv("OPENAI_API_KEY", "test-openai-key")
444− cfg, err := Init(cwdDir, false)
446− require.NoError(t, err)
447− assert.Len(t, cfg.Providers, 2)
448− assert.Contains(t, cfg.Providers, provider.InferenceProviderAnthropic)
449− assert.Contains(t, cfg.Providers, provider.InferenceProviderOpenAI)
450− assert.NotContains(t, cfg.Providers, provider.InferenceProviderGemini)
453−func TestEnvVars_VertexAIConfiguration(t *testing.T) {
455− testConfigDir = t.TempDir()
456− cwdDir := t.TempDir()
458− os.Setenv("GOOGLE_GENAI_USE_VERTEXAI", "true")
459− os.Setenv("GOOGLE_CLOUD_PROJECT", "test-project")
460− os.Setenv("GOOGLE_CLOUD_LOCATION", "us-central1")
462− cfg, err := Init(cwdDir, false)
464− require.NoError(t, err)
465− assert.Contains(t, cfg.Providers, provider.InferenceProviderVertexAI)
467− vertexProvider := cfg.Providers[provider.InferenceProviderVertexAI]
468− assert.Equal(t, provider.TypeVertexAI, vertexProvider.ProviderType)
469− assert.Equal(t, "test-project", vertexProvider.ExtraParams["project"])
470− assert.Equal(t, "us-central1", vertexProvider.ExtraParams["location"])
473−func TestEnvVars_VertexAIWithoutUseFlag(t *testing.T) {
475− testConfigDir = t.TempDir()
476− cwdDir := t.TempDir()
478− os.Setenv("GOOGLE_CLOUD_PROJECT", "test-project")
479− os.Setenv("GOOGLE_CLOUD_LOCATION", "us-central1")
481− cfg, err := Init(cwdDir, false)
483− require.NoError(t, err)
484− assert.NotContains(t, cfg.Providers, provider.InferenceProviderVertexAI)
487−func TestEnvVars_AWSBedrockWithAccessKeys(t *testing.T) {
489− testConfigDir = t.TempDir()
490− cwdDir := t.TempDir()
492− os.Setenv("AWS_ACCESS_KEY_ID", "test-access-key")
493− os.Setenv("AWS_SECRET_ACCESS_KEY", "test-secret-key")
494− os.Setenv("AWS_DEFAULT_REGION", "us-east-1")
496− cfg, err := Init(cwdDir, false)
498− require.NoError(t, err)
499− assert.Contains(t, cfg.Providers, provider.InferenceProviderBedrock)
501− bedrockProvider := cfg.Providers[provider.InferenceProviderBedrock]
502− assert.Equal(t, provider.TypeBedrock, bedrockProvider.ProviderType)
503− assert.Equal(t, "us-east-1", bedrockProvider.ExtraParams["region"])
506−func TestEnvVars_AWSBedrockWithProfile(t *testing.T) {
508− testConfigDir = t.TempDir()
509− cwdDir := t.TempDir()
511− os.Setenv("AWS_PROFILE", "test-profile")
512− os.Setenv("AWS_REGION", "eu-west-1")
514− cfg, err := Init(cwdDir, false)
516− require.NoError(t, err)
517− assert.Contains(t, cfg.Providers, provider.InferenceProviderBedrock)
519− bedrockProvider := cfg.Providers[provider.InferenceProviderBedrock]
520− assert.Equal(t, "eu-west-1", bedrockProvider.ExtraParams["region"])
523−func TestEnvVars_AWSBedrockWithContainerCredentials(t *testing.T) {
525− testConfigDir = t.TempDir()
526− cwdDir := t.TempDir()
528− os.Setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/v2/credentials/test")
529− os.Setenv("AWS_DEFAULT_REGION", "ap-southeast-1")
531− cfg, err := Init(cwdDir, false)
533− require.NoError(t, err)
534− assert.Contains(t, cfg.Providers, provider.InferenceProviderBedrock)
537−func TestEnvVars_AWSBedrockRegionPriority(t *testing.T) {
539− testConfigDir = t.TempDir()
540− cwdDir := t.TempDir()
542− os.Setenv("AWS_ACCESS_KEY_ID", "test-key")
543− os.Setenv("AWS_SECRET_ACCESS_KEY", "test-secret")
544− os.Setenv("AWS_DEFAULT_REGION", "us-west-2")
545− os.Setenv("AWS_REGION", "us-east-1")
547− cfg, err := Init(cwdDir, false)
549− require.NoError(t, err)
550− bedrockProvider := cfg.Providers[provider.InferenceProviderBedrock]
551− assert.Equal(t, "us-west-2", bedrockProvider.ExtraParams["region"])
554−func TestEnvVars_AWSBedrockFallbackRegion(t *testing.T) {
556− testConfigDir = t.TempDir()
557− cwdDir := t.TempDir()
559− os.Setenv("AWS_ACCESS_KEY_ID", "test-key")
560− os.Setenv("AWS_SECRET_ACCESS_KEY", "test-secret")
561− os.Setenv("AWS_REGION", "us-east-1")
563− cfg, err := Init(cwdDir, false)
565− require.NoError(t, err)
566− bedrockProvider := cfg.Providers[provider.InferenceProviderBedrock]
567− assert.Equal(t, "us-east-1", bedrockProvider.ExtraParams["region"])
570−func TestEnvVars_NoAWSCredentials(t *testing.T) {
572− testConfigDir = t.TempDir()
573− cwdDir := t.TempDir()
575− cfg, err := Init(cwdDir, false)
577− require.NoError(t, err)
578− assert.NotContains(t, cfg.Providers, provider.InferenceProviderBedrock)
581−func TestEnvVars_CustomEnvironmentVariables(t *testing.T) {
583− testConfigDir = t.TempDir()
584− cwdDir := t.TempDir()
586− os.Setenv("ANTHROPIC_API_KEY", "resolved-anthropic-key")
588− cfg, err := Init(cwdDir, false)
590− require.NoError(t, err)
591− if len(cfg.Providers) > 0 {
592− if anthropicProvider, exists := cfg.Providers[provider.InferenceProviderAnthropic]; exists {
593− assert.Equal(t, "resolved-anthropic-key", anthropicProvider.APIKey)
598−func TestEnvVars_CombinedEnvironmentVariables(t *testing.T) {
600− testConfigDir = t.TempDir()
601− cwdDir := t.TempDir()
603− os.Setenv("ANTHROPIC_API_KEY", "test-anthropic")
604− os.Setenv("OPENAI_API_KEY", "test-openai")
605− os.Setenv("GOOGLE_GENAI_USE_VERTEXAI", "true")
606− os.Setenv("GOOGLE_CLOUD_PROJECT", "test-project")
607− os.Setenv("GOOGLE_CLOUD_LOCATION", "us-central1")
608− os.Setenv("AWS_ACCESS_KEY_ID", "test-aws-key")
609− os.Setenv("AWS_SECRET_ACCESS_KEY", "test-aws-secret")
610− os.Setenv("AWS_DEFAULT_REGION", "us-west-1")
612− cfg, err := Init(cwdDir, false)
614− require.NoError(t, err)
616− expectedProviders := []provider.InferenceProvider{
617− provider.InferenceProviderAnthropic,
618− provider.InferenceProviderOpenAI,
619− provider.InferenceProviderVertexAI,
620− provider.InferenceProviderBedrock,
623− for _, expectedProvider := range expectedProviders {
624− assert.Contains(t, cfg.Providers, expectedProvider)
628−func TestHasAWSCredentials_AccessKeys(t *testing.T) {
631− os.Setenv("AWS_ACCESS_KEY_ID", "test-key")
632− os.Setenv("AWS_SECRET_ACCESS_KEY", "test-secret")
634− assert.True(t, hasAWSCredentials())
637−func TestHasAWSCredentials_Profile(t *testing.T) {
640− os.Setenv("AWS_PROFILE", "test-profile")
642− assert.True(t, hasAWSCredentials())
645−func TestHasAWSCredentials_DefaultProfile(t *testing.T) {
648− os.Setenv("AWS_DEFAULT_PROFILE", "default")
650− assert.True(t, hasAWSCredentials())
653−func TestHasAWSCredentials_Region(t *testing.T) {
656− os.Setenv("AWS_REGION", "us-east-1")
658− assert.True(t, hasAWSCredentials())
661−func TestHasAWSCredentials_ContainerCredentials(t *testing.T) {
664− os.Setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/v2/credentials/test")
666− assert.True(t, hasAWSCredentials())
669−func TestHasAWSCredentials_NoCredentials(t *testing.T) {
672− assert.False(t, hasAWSCredentials())
675−func TestProviderMerging_GlobalToBase(t *testing.T) {
677− testConfigDir = t.TempDir()
678− cwdDir := t.TempDir()
680− globalConfig := Config{
681− Providers: map[provider.InferenceProvider]ProviderConfig{
682− provider.InferenceProviderOpenAI: {
683− ID: provider.InferenceProviderOpenAI,
684− APIKey: "global-openai-key",
685− ProviderType: provider.TypeOpenAI,
686− DefaultLargeModel: "gpt-4",
687− DefaultSmallModel: "gpt-3.5-turbo",
692− ContextWindow: 8192,
693− DefaultMaxTokens: 4096,
696− ID: "gpt-3.5-turbo",
697− Name: "GPT-3.5 Turbo",
698− ContextWindow: 4096,
699− DefaultMaxTokens: 2048,
706− configPath := filepath.Join(testConfigDir, "crush.json")
707− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
708− data, err := json.Marshal(globalConfig)
709− require.NoError(t, err)
710− require.NoError(t, os.WriteFile(configPath, data, 0o644))
712− cfg, err := Init(cwdDir, false)
714− require.NoError(t, err)
715− assert.Len(t, cfg.Providers, 1)
717− openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
718− assert.Equal(t, "global-openai-key", openaiProvider.APIKey)
719− assert.Equal(t, "gpt-4", openaiProvider.DefaultLargeModel)
720− assert.Equal(t, "gpt-4o", openaiProvider.DefaultSmallModel)
721− assert.GreaterOrEqual(t, len(openaiProvider.Models), 2)
724−func TestProviderMerging_LocalToBase(t *testing.T) {
726− testConfigDir = t.TempDir()
727− cwdDir := t.TempDir()
729− localConfig := Config{
730− Providers: map[provider.InferenceProvider]ProviderConfig{
731− provider.InferenceProviderAnthropic: {
732− ID: provider.InferenceProviderAnthropic,
733− APIKey: "local-anthropic-key",
734− ProviderType: provider.TypeAnthropic,
735− DefaultLargeModel: "claude-3-opus",
736− DefaultSmallModel: "claude-3-haiku",
739− ID: "claude-3-opus",
740− Name: "Claude 3 Opus",
741− ContextWindow: 200000,
742− DefaultMaxTokens: 4096,
747− ID: "claude-3-haiku",
748− Name: "Claude 3 Haiku",
749− ContextWindow: 200000,
750− DefaultMaxTokens: 4096,
759− localConfigPath := filepath.Join(cwdDir, "crush.json")
760− data, err := json.Marshal(localConfig)
761− require.NoError(t, err)
762− require.NoError(t, os.WriteFile(localConfigPath, data, 0o644))
764− cfg, err := Init(cwdDir, false)
766− require.NoError(t, err)
767− assert.Len(t, cfg.Providers, 1)
769− anthropicProvider := cfg.Providers[provider.InferenceProviderAnthropic]
770− assert.Equal(t, "local-anthropic-key", anthropicProvider.APIKey)
771− assert.Equal(t, "claude-3-opus", anthropicProvider.DefaultLargeModel)
772− assert.Equal(t, "claude-3-5-haiku-20241022", anthropicProvider.DefaultSmallModel)
773− assert.GreaterOrEqual(t, len(anthropicProvider.Models), 2)
776−func TestProviderMerging_ConflictingSettings(t *testing.T) {
778− testConfigDir = t.TempDir()
779− cwdDir := t.TempDir()
781− globalConfig := Config{
782− Providers: map[provider.InferenceProvider]ProviderConfig{
783− provider.InferenceProviderOpenAI: {
784− ID: provider.InferenceProviderOpenAI,
785− APIKey: "global-key",
786− ProviderType: provider.TypeOpenAI,
787− DefaultLargeModel: "gpt-4",
788− DefaultSmallModel: "gpt-3.5-turbo",
793− ContextWindow: 8192,
794− DefaultMaxTokens: 4096,
797− ID: "gpt-3.5-turbo",
798− Name: "GPT-3.5 Turbo",
799− ContextWindow: 4096,
800− DefaultMaxTokens: 2048,
804− Name: "GPT-4 Turbo",
805− ContextWindow: 128000,
806− DefaultMaxTokens: 4096,
813− configPath := filepath.Join(testConfigDir, "crush.json")
814− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
815− data, err := json.Marshal(globalConfig)
816− require.NoError(t, err)
817− require.NoError(t, os.WriteFile(configPath, data, 0o644))
819− // Create local config that overrides
820− localConfig := Config{
821− Providers: map[provider.InferenceProvider]ProviderConfig{
822− provider.InferenceProviderOpenAI: {
823− APIKey: "local-key",
824− DefaultLargeModel: "gpt-4-turbo",
829− localConfigPath := filepath.Join(cwdDir, "crush.json")
830− data, err = json.Marshal(localConfig)
831− require.NoError(t, err)
832− require.NoError(t, os.WriteFile(localConfigPath, data, 0o644))
834− cfg, err := Init(cwdDir, false)
836− require.NoError(t, err)
838− openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
839− assert.Equal(t, "local-key", openaiProvider.APIKey)
840− assert.Equal(t, "gpt-4-turbo", openaiProvider.DefaultLargeModel)
841− assert.False(t, openaiProvider.Disabled)
842− assert.Equal(t, "gpt-4o", openaiProvider.DefaultSmallModel)
845−func TestProviderMerging_CustomVsKnownProviders(t *testing.T) {
847− testConfigDir = t.TempDir()
848− cwdDir := t.TempDir()
850− customProviderID := provider.InferenceProvider("custom-provider")
852− globalConfig := Config{
853− Providers: map[provider.InferenceProvider]ProviderConfig{
854− provider.InferenceProviderOpenAI: {
855− ID: provider.InferenceProviderOpenAI,
856− APIKey: "openai-key",
857− BaseURL: "should-not-override",
858− ProviderType: provider.TypeAnthropic,
859− DefaultLargeModel: "gpt-4",
860− DefaultSmallModel: "gpt-3.5-turbo",
865− ContextWindow: 8192,
866− DefaultMaxTokens: 4096,
869− ID: "gpt-3.5-turbo",
870− Name: "GPT-3.5 Turbo",
871− ContextWindow: 4096,
872− DefaultMaxTokens: 2048,
877− ID: customProviderID,
878− APIKey: "custom-key",
879− BaseURL: "https://custom.api.com",
880− ProviderType: provider.TypeOpenAI,
881− DefaultLargeModel: "custom-large",
882− DefaultSmallModel: "custom-small",
886− Name: "Custom Large",
887− ContextWindow: 8192,
888− DefaultMaxTokens: 4096,
892− Name: "Custom Small",
893− ContextWindow: 4096,
894− DefaultMaxTokens: 2048,
901− localConfig := Config{
902− Providers: map[provider.InferenceProvider]ProviderConfig{
903− provider.InferenceProviderOpenAI: {
904− BaseURL: "https://should-not-change.com",
905− ProviderType: provider.TypeGemini, // Should not change
908− BaseURL: "https://updated-custom.api.com",
909− ProviderType: provider.TypeOpenAI,
914− configPath := filepath.Join(testConfigDir, "crush.json")
915− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
916− data, err := json.Marshal(globalConfig)
917− require.NoError(t, err)
918− require.NoError(t, os.WriteFile(configPath, data, 0o644))
920− localConfigPath := filepath.Join(cwdDir, "crush.json")
921− data, err = json.Marshal(localConfig)
922− require.NoError(t, err)
923− require.NoError(t, os.WriteFile(localConfigPath, data, 0o644))
925− cfg, err := Init(cwdDir, false)
927− require.NoError(t, err)
929− openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
930− assert.NotEqual(t, "https://should-not-change.com", openaiProvider.BaseURL)
931− assert.NotEqual(t, provider.TypeGemini, openaiProvider.ProviderType)
933− customProvider := cfg.Providers[customProviderID]
934− assert.Equal(t, "custom-key", customProvider.APIKey)
935− assert.Equal(t, "https://updated-custom.api.com", customProvider.BaseURL)
936− assert.Equal(t, provider.TypeOpenAI, customProvider.ProviderType)
939−func TestProviderValidation_CustomProviderMissingBaseURL(t *testing.T) {
941− testConfigDir = t.TempDir()
942− cwdDir := t.TempDir()
944− customProviderID := provider.InferenceProvider("custom-provider")
946− globalConfig := Config{
947− Providers: map[provider.InferenceProvider]ProviderConfig{
949− ID: customProviderID,
950− APIKey: "custom-key",
951− ProviderType: provider.TypeOpenAI,
956− configPath := filepath.Join(testConfigDir, "crush.json")
957− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
958− data, err := json.Marshal(globalConfig)
959− require.NoError(t, err)
960− require.NoError(t, os.WriteFile(configPath, data, 0o644))
962− cfg, err := Init(cwdDir, false)
964− require.NoError(t, err)
965− assert.NotContains(t, cfg.Providers, customProviderID)
968−func TestProviderValidation_CustomProviderMissingAPIKey(t *testing.T) {
970− testConfigDir = t.TempDir()
971− cwdDir := t.TempDir()
973− customProviderID := provider.InferenceProvider("custom-provider")
975− globalConfig := Config{
976− Providers: map[provider.InferenceProvider]ProviderConfig{
978− ID: customProviderID,
979− BaseURL: "https://custom.api.com",
980− ProviderType: provider.TypeOpenAI,
985− configPath := filepath.Join(testConfigDir, "crush.json")
986− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
987− data, err := json.Marshal(globalConfig)
988− require.NoError(t, err)
989− require.NoError(t, os.WriteFile(configPath, data, 0o644))
991− cfg, err := Init(cwdDir, false)
993− require.NoError(t, err)
994− assert.NotContains(t, cfg.Providers, customProviderID)
997−func TestProviderValidation_CustomProviderInvalidType(t *testing.T) {
999− testConfigDir = t.TempDir()
1000− cwdDir := t.TempDir()
1002− customProviderID := provider.InferenceProvider("custom-provider")
1004− globalConfig := Config{
1005− Providers: map[provider.InferenceProvider]ProviderConfig{
1006− customProviderID: {
1007− ID: customProviderID,
1008− APIKey: "custom-key",
1009− BaseURL: "https://custom.api.com",
1010− ProviderType: provider.Type("invalid-type"),
1015− configPath := filepath.Join(testConfigDir, "crush.json")
1016− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1017− data, err := json.Marshal(globalConfig)
1018− require.NoError(t, err)
1019− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1021− cfg, err := Init(cwdDir, false)
1023− require.NoError(t, err)
1024− assert.NotContains(t, cfg.Providers, customProviderID)
1027−func TestProviderValidation_KnownProviderValid(t *testing.T) {
1029− testConfigDir = t.TempDir()
1030− cwdDir := t.TempDir()
1032− globalConfig := Config{
1033− Providers: map[provider.InferenceProvider]ProviderConfig{
1034− provider.InferenceProviderOpenAI: {
1035− ID: provider.InferenceProviderOpenAI,
1036− APIKey: "openai-key",
1037− ProviderType: provider.TypeOpenAI,
1038− DefaultLargeModel: "gpt-4",
1039− DefaultSmallModel: "gpt-3.5-turbo",
1044− ContextWindow: 8192,
1045− DefaultMaxTokens: 4096,
1048− ID: "gpt-3.5-turbo",
1049− Name: "GPT-3.5 Turbo",
1050− ContextWindow: 4096,
1051− DefaultMaxTokens: 2048,
1058− configPath := filepath.Join(testConfigDir, "crush.json")
1059− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1060− data, err := json.Marshal(globalConfig)
1061− require.NoError(t, err)
1062− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1064− cfg, err := Init(cwdDir, false)
1066− require.NoError(t, err)
1067− assert.Contains(t, cfg.Providers, provider.InferenceProviderOpenAI)
1070−func TestProviderValidation_DisabledProvider(t *testing.T) {
1072− testConfigDir = t.TempDir()
1073− cwdDir := t.TempDir()
1075− globalConfig := Config{
1076− Providers: map[provider.InferenceProvider]ProviderConfig{
1077− provider.InferenceProviderOpenAI: {
1078− ID: provider.InferenceProviderOpenAI,
1079− APIKey: "openai-key",
1080− ProviderType: provider.TypeOpenAI,
1082− DefaultLargeModel: "gpt-4",
1083− DefaultSmallModel: "gpt-3.5-turbo",
1088− ContextWindow: 8192,
1089− DefaultMaxTokens: 4096,
1092− ID: "gpt-3.5-turbo",
1093− Name: "GPT-3.5 Turbo",
1094− ContextWindow: 4096,
1095− DefaultMaxTokens: 2048,
1099− provider.InferenceProviderAnthropic: {
1100− ID: provider.InferenceProviderAnthropic,
1101− APIKey: "anthropic-key",
1102− ProviderType: provider.TypeAnthropic,
1103− Disabled: false, // This one is enabled
1104− DefaultLargeModel: "claude-3-opus",
1105− DefaultSmallModel: "claude-3-haiku",
1108− ID: "claude-3-opus",
1109− Name: "Claude 3 Opus",
1110− ContextWindow: 200000,
1111− DefaultMaxTokens: 4096,
1114− ID: "claude-3-haiku",
1115− Name: "Claude 3 Haiku",
1116− ContextWindow: 200000,
1117− DefaultMaxTokens: 4096,
1124− configPath := filepath.Join(testConfigDir, "crush.json")
1125− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1126− data, err := json.Marshal(globalConfig)
1127− require.NoError(t, err)
1128− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1130− cfg, err := Init(cwdDir, false)
1132− require.NoError(t, err)
1133− assert.Contains(t, cfg.Providers, provider.InferenceProviderOpenAI)
1134− assert.True(t, cfg.Providers[provider.InferenceProviderOpenAI].Disabled)
1135− assert.Contains(t, cfg.Providers, provider.InferenceProviderAnthropic)
1136− assert.False(t, cfg.Providers[provider.InferenceProviderAnthropic].Disabled)
1139−func TestProviderModels_AddingNewModels(t *testing.T) {
1141− testConfigDir = t.TempDir()
1142− cwdDir := t.TempDir()
1144− globalConfig := Config{
1145− Providers: map[provider.InferenceProvider]ProviderConfig{
1146− provider.InferenceProviderOpenAI: {
1147− ID: provider.InferenceProviderOpenAI,
1148− APIKey: "openai-key",
1149− ProviderType: provider.TypeOpenAI,
1150− DefaultLargeModel: "gpt-4",
1151− DefaultSmallModel: "gpt-4-turbo",
1156− ContextWindow: 8192,
1157− DefaultMaxTokens: 4096,
1164− localConfig := Config{
1165− Providers: map[provider.InferenceProvider]ProviderConfig{
1166− provider.InferenceProviderOpenAI: {
1170− Name: "GPT-4 Turbo",
1171− ContextWindow: 128000,
1172− DefaultMaxTokens: 4096,
1179− configPath := filepath.Join(testConfigDir, "crush.json")
1180− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1181− data, err := json.Marshal(globalConfig)
1182− require.NoError(t, err)
1183− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1185− localConfigPath := filepath.Join(cwdDir, "crush.json")
1186− data, err = json.Marshal(localConfig)
1187− require.NoError(t, err)
1188− require.NoError(t, os.WriteFile(localConfigPath, data, 0o644))
1190− cfg, err := Init(cwdDir, false)
1192− require.NoError(t, err)
1194− openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
1195− assert.GreaterOrEqual(t, len(openaiProvider.Models), 2)
1197− modelIDs := make([]string, len(openaiProvider.Models))
1198− for i, model := range openaiProvider.Models {
1199− modelIDs[i] = model.ID
1201− assert.Contains(t, modelIDs, "gpt-4")
1202− assert.Contains(t, modelIDs, "gpt-4-turbo")
1205−func TestProviderModels_DuplicateModelHandling(t *testing.T) {
1207− testConfigDir = t.TempDir()
1208− cwdDir := t.TempDir()
1210− globalConfig := Config{
1211− Providers: map[provider.InferenceProvider]ProviderConfig{
1212− provider.InferenceProviderOpenAI: {
1213− ID: provider.InferenceProviderOpenAI,
1214− APIKey: "openai-key",
1215− ProviderType: provider.TypeOpenAI,
1216− DefaultLargeModel: "gpt-4",
1217− DefaultSmallModel: "gpt-4",
1222− ContextWindow: 8192,
1223− DefaultMaxTokens: 4096,
1230− localConfig := Config{
1231− Providers: map[provider.InferenceProvider]ProviderConfig{
1232− provider.InferenceProviderOpenAI: {
1236− Name: "GPT-4 Updated",
1237− ContextWindow: 16384,
1238− DefaultMaxTokens: 8192,
1245− configPath := filepath.Join(testConfigDir, "crush.json")
1246− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1247− data, err := json.Marshal(globalConfig)
1248− require.NoError(t, err)
1249− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1251− localConfigPath := filepath.Join(cwdDir, "crush.json")
1252− data, err = json.Marshal(localConfig)
1253− require.NoError(t, err)
1254− require.NoError(t, os.WriteFile(localConfigPath, data, 0o644))
1256− cfg, err := Init(cwdDir, false)
1258− require.NoError(t, err)
1260− openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
1261− assert.GreaterOrEqual(t, len(openaiProvider.Models), 1)
1263− // Find the first model that matches our test data
1264− var testModel *Model
1265− for _, model := range openaiProvider.Models {
1266− if model.ID == "gpt-4" {
1272− // If gpt-4 not found, use the first available model
1273− if testModel == nil {
1274− testModel = &openaiProvider.Models[0]
1277− assert.NotEmpty(t, testModel.ID)
1278− assert.NotEmpty(t, testModel.Name)
1279− assert.Greater(t, testModel.ContextWindow, int64(0))
1282−func TestProviderModels_ModelCostAndCapabilities(t *testing.T) {
1284− testConfigDir = t.TempDir()
1285− cwdDir := t.TempDir()
1287− globalConfig := Config{
1288− Providers: map[provider.InferenceProvider]ProviderConfig{
1289− provider.InferenceProviderOpenAI: {
1290− ID: provider.InferenceProviderOpenAI,
1291− APIKey: "openai-key",
1292− ProviderType: provider.TypeOpenAI,
1293− DefaultLargeModel: "gpt-4",
1294− DefaultSmallModel: "gpt-4",
1300− CostPer1MOut: 60.0,
1301− CostPer1MInCached: 15.0,
1302− CostPer1MOutCached: 30.0,
1303− ContextWindow: 8192,
1304− DefaultMaxTokens: 4096,
1306− ReasoningEffort: "medium",
1307− SupportsImages: true,
1314− configPath := filepath.Join(testConfigDir, "crush.json")
1315− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1316− data, err := json.Marshal(globalConfig)
1317− require.NoError(t, err)
1318− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1320− cfg, err := Init(cwdDir, false)
1322− require.NoError(t, err)
1324− openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
1325− require.GreaterOrEqual(t, len(openaiProvider.Models), 1)
1327− // Find the test model or use the first one
1328− var testModel *Model
1329− for _, model := range openaiProvider.Models {
1330− if model.ID == "gpt-4" {
1336− if testModel == nil {
1337− testModel = &openaiProvider.Models[0]
1340− // Only test the custom properties if this is actually our test model
1341− if testModel.ID == "gpt-4" {
1342− assert.Equal(t, 30.0, testModel.CostPer1MIn)
1343− assert.Equal(t, 60.0, testModel.CostPer1MOut)
1344− assert.Equal(t, 15.0, testModel.CostPer1MInCached)
1345− assert.Equal(t, 30.0, testModel.CostPer1MOutCached)
1346− assert.True(t, testModel.CanReason)
1347− assert.Equal(t, "medium", testModel.ReasoningEffort)
1348− assert.True(t, testModel.SupportsImages)
1352−func TestDefaultAgents_CoderAgent(t *testing.T) {
1354− testConfigDir = t.TempDir()
1355− cwdDir := t.TempDir()
1357− os.Setenv("ANTHROPIC_API_KEY", "test-key")
1359− cfg, err := Init(cwdDir, false)
1361− require.NoError(t, err)
1362− assert.Contains(t, cfg.Agents, AgentCoder)
1364− coderAgent := cfg.Agents[AgentCoder]
1365− assert.Equal(t, AgentCoder, coderAgent.ID)
1366− assert.Equal(t, "Coder", coderAgent.Name)
1367− assert.Equal(t, "An agent that helps with executing coding tasks.", coderAgent.Description)
1368− assert.Equal(t, LargeModel, coderAgent.Model)
1369− assert.False(t, coderAgent.Disabled)
1370− assert.Equal(t, cfg.Options.ContextPaths, coderAgent.ContextPaths)
1371− assert.Nil(t, coderAgent.AllowedTools)
1374−func TestDefaultAgents_TaskAgent(t *testing.T) {
1376− testConfigDir = t.TempDir()
1377− cwdDir := t.TempDir()
1379− os.Setenv("ANTHROPIC_API_KEY", "test-key")
1381− cfg, err := Init(cwdDir, false)
1383− require.NoError(t, err)
1384− assert.Contains(t, cfg.Agents, AgentTask)
1386− taskAgent := cfg.Agents[AgentTask]
1387− assert.Equal(t, AgentTask, taskAgent.ID)
1388− assert.Equal(t, "Task", taskAgent.Name)
1389− assert.Equal(t, "An agent that helps with searching for context and finding implementation details.", taskAgent.Description)
1390− assert.Equal(t, LargeModel, taskAgent.Model)
1391− assert.False(t, taskAgent.Disabled)
1392− assert.Equal(t, cfg.Options.ContextPaths, taskAgent.ContextPaths)
1394− expectedTools := []string{"glob", "grep", "ls", "sourcegraph", "view"}
1395− assert.Equal(t, expectedTools, taskAgent.AllowedTools)
1397− assert.Equal(t, map[string][]string{}, taskAgent.AllowedMCP)
1398− assert.Equal(t, []string{}, taskAgent.AllowedLSP)
1401−func TestAgentMerging_CustomAgent(t *testing.T) {
1403− testConfigDir = t.TempDir()
1404− cwdDir := t.TempDir()
1406− os.Setenv("ANTHROPIC_API_KEY", "test-key")
1408− globalConfig := Config{
1409− Agents: map[AgentID]Agent{
1410− AgentID("custom-agent"): {
1411− ID: AgentID("custom-agent"),
1412− Name: "Custom Agent",
1413− Description: "A custom agent for testing",
1415− AllowedTools: []string{"glob", "grep"},
1416− AllowedMCP: map[string][]string{"mcp1": {"tool1", "tool2"}},
1417− AllowedLSP: []string{"typescript", "go"},
1418− ContextPaths: []string{"custom-context.md"},
1421− MCP: map[string]MCP{
1424− Command: "test-mcp-command",
1425− Args: []string{"--test"},
1428− LSP: map[string]LSPConfig{
1430− Command: "typescript-language-server",
1431− Args: []string{"--stdio"},
1440− configPath := filepath.Join(testConfigDir, "crush.json")
1441− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1442− data, err := json.Marshal(globalConfig)
1443− require.NoError(t, err)
1444− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1446− cfg, err := Init(cwdDir, false)
1448− require.NoError(t, err)
1450− assert.Contains(t, cfg.Agents, AgentCoder)
1451− assert.Contains(t, cfg.Agents, AgentTask)
1452− assert.Contains(t, cfg.Agents, AgentID("custom-agent"))
1454− customAgent := cfg.Agents[AgentID("custom-agent")]
1455− assert.Equal(t, "Custom Agent", customAgent.Name)
1456− assert.Equal(t, "A custom agent for testing", customAgent.Description)
1457− assert.Equal(t, SmallModel, customAgent.Model)
1458− assert.Equal(t, []string{"glob", "grep"}, customAgent.AllowedTools)
1459− assert.Equal(t, map[string][]string{"mcp1": {"tool1", "tool2"}}, customAgent.AllowedMCP)
1460− assert.Equal(t, []string{"typescript", "go"}, customAgent.AllowedLSP)
1461− expectedContextPaths := append(defaultContextPaths, "custom-context.md")
1462− assert.Equal(t, expectedContextPaths, customAgent.ContextPaths)
1465−func TestAgentMerging_ModifyDefaultCoderAgent(t *testing.T) {
1467− testConfigDir = t.TempDir()
1468− cwdDir := t.TempDir()
1470− os.Setenv("ANTHROPIC_API_KEY", "test-key")
1472− globalConfig := Config{
1473− Agents: map[AgentID]Agent{
1476− AllowedMCP: map[string][]string{"mcp1": {"tool1"}},
1477− AllowedLSP: []string{"typescript"},
1478− ContextPaths: []string{"coder-specific.md"},
1481− MCP: map[string]MCP{
1484− Command: "test-mcp-command",
1485− Args: []string{"--test"},
1488− LSP: map[string]LSPConfig{
1490− Command: "typescript-language-server",
1491− Args: []string{"--stdio"},
1496− configPath := filepath.Join(testConfigDir, "crush.json")
1497− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1498− data, err := json.Marshal(globalConfig)
1499− require.NoError(t, err)
1500− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1502− cfg, err := Init(cwdDir, false)
1504− require.NoError(t, err)
1506− coderAgent := cfg.Agents[AgentCoder]
1507− assert.Equal(t, AgentCoder, coderAgent.ID)
1508− assert.Equal(t, "Coder", coderAgent.Name)
1509− assert.Equal(t, "An agent that helps with executing coding tasks.", coderAgent.Description)
1511− expectedContextPaths := append(cfg.Options.ContextPaths, "coder-specific.md")
1512− assert.Equal(t, expectedContextPaths, coderAgent.ContextPaths)
1514− assert.Equal(t, SmallModel, coderAgent.Model)
1515− assert.Equal(t, map[string][]string{"mcp1": {"tool1"}}, coderAgent.AllowedMCP)
1516− assert.Equal(t, []string{"typescript"}, coderAgent.AllowedLSP)
1519−func TestAgentMerging_ModifyDefaultTaskAgent(t *testing.T) {
1521− testConfigDir = t.TempDir()
1522− cwdDir := t.TempDir()
1524− os.Setenv("ANTHROPIC_API_KEY", "test-key")
1526− globalConfig := Config{
1527− Agents: map[AgentID]Agent{
1530− AllowedMCP: map[string][]string{"search-mcp": nil},
1531− AllowedLSP: []string{"python"},
1532− Name: "Search Agent",
1533− Description: "Custom search agent",
1535− AllowedTools: []string{"glob", "grep", "view"},
1538− MCP: map[string]MCP{
1541− Command: "search-mcp-command",
1542− Args: []string{"--search"},
1545− LSP: map[string]LSPConfig{
1553− configPath := filepath.Join(testConfigDir, "crush.json")
1554− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1555− data, err := json.Marshal(globalConfig)
1556− require.NoError(t, err)
1557− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1559− cfg, err := Init(cwdDir, false)
1561− require.NoError(t, err)
1563− taskAgent := cfg.Agents[AgentTask]
1564− assert.Equal(t, "Task", taskAgent.Name)
1565− assert.Equal(t, "An agent that helps with searching for context and finding implementation details.", taskAgent.Description)
1566− assert.False(t, taskAgent.Disabled)
1567− assert.Equal(t, []string{"glob", "grep", "ls", "sourcegraph", "view"}, taskAgent.AllowedTools)
1569− assert.Equal(t, SmallModel, taskAgent.Model)
1570− assert.Equal(t, map[string][]string{"search-mcp": nil}, taskAgent.AllowedMCP)
1571− assert.Equal(t, []string{"python"}, taskAgent.AllowedLSP)
1574−func TestAgentMerging_LocalOverridesGlobal(t *testing.T) {
1576− testConfigDir = t.TempDir()
1577− cwdDir := t.TempDir()
1579− os.Setenv("ANTHROPIC_API_KEY", "test-key")
1581− globalConfig := Config{
1582− Agents: map[AgentID]Agent{
1583− AgentID("test-agent"): {
1584− ID: AgentID("test-agent"),
1585− Name: "Global Agent",
1586− Description: "Global description",
1588− AllowedTools: []string{"glob"},
1593− configPath := filepath.Join(testConfigDir, "crush.json")
1594− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1595− data, err := json.Marshal(globalConfig)
1596− require.NoError(t, err)
1597− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1599− // Create local config that overrides
1600− localConfig := Config{
1601− Agents: map[AgentID]Agent{
1602− AgentID("test-agent"): {
1603− Name: "Local Agent",
1604− Description: "Local description",
1607− AllowedTools: []string{"grep", "view"},
1608− AllowedMCP: map[string][]string{"local-mcp": {"tool1"}},
1611− MCP: map[string]MCP{
1614− Command: "local-mcp-command",
1615− Args: []string{"--local"},
1620− localConfigPath := filepath.Join(cwdDir, "crush.json")
1621− data, err = json.Marshal(localConfig)
1622− require.NoError(t, err)
1623− require.NoError(t, os.WriteFile(localConfigPath, data, 0o644))
1625− cfg, err := Init(cwdDir, false)
1627− require.NoError(t, err)
1629− testAgent := cfg.Agents[AgentID("test-agent")]
1630− assert.Equal(t, "Local Agent", testAgent.Name)
1631− assert.Equal(t, "Local description", testAgent.Description)
1632− assert.Equal(t, SmallModel, testAgent.Model)
1633− assert.True(t, testAgent.Disabled)
1634− assert.Equal(t, []string{"grep", "view"}, testAgent.AllowedTools)
1635− assert.Equal(t, map[string][]string{"local-mcp": {"tool1"}}, testAgent.AllowedMCP)
1638−func TestAgentModelTypeAssignment(t *testing.T) {
1640− testConfigDir = t.TempDir()
1641− cwdDir := t.TempDir()
1643− os.Setenv("ANTHROPIC_API_KEY", "test-key")
1645− globalConfig := Config{
1646− Agents: map[AgentID]Agent{
1647− AgentID("large-agent"): {
1648− ID: AgentID("large-agent"),
1649− Name: "Large Model Agent",
1652− AgentID("small-agent"): {
1653− ID: AgentID("small-agent"),
1654− Name: "Small Model Agent",
1657− AgentID("default-agent"): {
1658− ID: AgentID("default-agent"),
1659− Name: "Default Model Agent",
1664− configPath := filepath.Join(testConfigDir, "crush.json")
1665− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1666− data, err := json.Marshal(globalConfig)
1667− require.NoError(t, err)
1668− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1670− cfg, err := Init(cwdDir, false)
1672− require.NoError(t, err)
1674− assert.Equal(t, LargeModel, cfg.Agents[AgentID("large-agent")].Model)
1675− assert.Equal(t, SmallModel, cfg.Agents[AgentID("small-agent")].Model)
1676− assert.Equal(t, LargeModel, cfg.Agents[AgentID("default-agent")].Model)
1679−func TestAgentContextPathOverrides(t *testing.T) {
1681− testConfigDir = t.TempDir()
1682− cwdDir := t.TempDir()
1684− os.Setenv("ANTHROPIC_API_KEY", "test-key")
1686− globalConfig := Config{
1688− ContextPaths: []string{"global-context.md", "shared-context.md"},
1690− Agents: map[AgentID]Agent{
1691− AgentID("custom-context-agent"): {
1692− ID: AgentID("custom-context-agent"),
1693− Name: "Custom Context Agent",
1694− ContextPaths: []string{"agent-specific.md", "custom.md"},
1696− AgentID("default-context-agent"): {
1697− ID: AgentID("default-context-agent"),
1698− Name: "Default Context Agent",
1703− configPath := filepath.Join(testConfigDir, "crush.json")
1704− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1705− data, err := json.Marshal(globalConfig)
1706− require.NoError(t, err)
1707− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1709− cfg, err := Init(cwdDir, false)
1711− require.NoError(t, err)
1713− customAgent := cfg.Agents[AgentID("custom-context-agent")]
1714− expectedCustomPaths := append(defaultContextPaths, "global-context.md", "shared-context.md", "agent-specific.md", "custom.md")
1715− assert.Equal(t, expectedCustomPaths, customAgent.ContextPaths)
1717− defaultAgent := cfg.Agents[AgentID("default-context-agent")]
1718− expectedContextPaths := append(defaultContextPaths, "global-context.md", "shared-context.md")
1719− assert.Equal(t, expectedContextPaths, defaultAgent.ContextPaths)
1721− coderAgent := cfg.Agents[AgentCoder]
1722− assert.Equal(t, expectedContextPaths, coderAgent.ContextPaths)
1725−func TestOptionsMerging_ContextPaths(t *testing.T) {
1727− testConfigDir = t.TempDir()
1728− cwdDir := t.TempDir()
1730− os.Setenv("ANTHROPIC_API_KEY", "test-key")
1732− globalConfig := Config{
1734− ContextPaths: []string{"global1.md", "global2.md"},
1738− configPath := filepath.Join(testConfigDir, "crush.json")
1739− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1740− data, err := json.Marshal(globalConfig)
1741− require.NoError(t, err)
1742− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1744− localConfig := Config{
1746− ContextPaths: []string{"local1.md", "local2.md"},
1750− localConfigPath := filepath.Join(cwdDir, "crush.json")
1751− data, err = json.Marshal(localConfig)
1752− require.NoError(t, err)
1753− require.NoError(t, os.WriteFile(localConfigPath, data, 0o644))
1755− cfg, err := Init(cwdDir, false)
1757− require.NoError(t, err)
1759− expectedContextPaths := append(defaultContextPaths, "global1.md", "global2.md", "local1.md", "local2.md")
1760− assert.Equal(t, expectedContextPaths, cfg.Options.ContextPaths)
1763−func TestOptionsMerging_TUIOptions(t *testing.T) {
1765− testConfigDir = t.TempDir()
1766− cwdDir := t.TempDir()
1768− os.Setenv("ANTHROPIC_API_KEY", "test-key")
1770− globalConfig := Config{
1773− CompactMode: false,
1778− configPath := filepath.Join(testConfigDir, "crush.json")
1779− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1780− data, err := json.Marshal(globalConfig)
1781− require.NoError(t, err)
1782− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1784− localConfig := Config{
1792− localConfigPath := filepath.Join(cwdDir, "crush.json")
1793− data, err = json.Marshal(localConfig)
1794− require.NoError(t, err)
1795− require.NoError(t, os.WriteFile(localConfigPath, data, 0o644))
1797− cfg, err := Init(cwdDir, false)
1799− require.NoError(t, err)
1801− assert.True(t, cfg.Options.TUI.CompactMode)
1804−func TestOptionsMerging_DebugFlags(t *testing.T) {
1806− testConfigDir = t.TempDir()
1807− cwdDir := t.TempDir()
1809− os.Setenv("ANTHROPIC_API_KEY", "test-key")
1811− globalConfig := Config{
1815− DisableAutoSummarize: false,
1819− configPath := filepath.Join(testConfigDir, "crush.json")
1820− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1821− data, err := json.Marshal(globalConfig)
1822− require.NoError(t, err)
1823− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1825− localConfig := Config{
1828− DisableAutoSummarize: true,
1832− localConfigPath := filepath.Join(cwdDir, "crush.json")
1833− data, err = json.Marshal(localConfig)
1834− require.NoError(t, err)
1835− require.NoError(t, os.WriteFile(localConfigPath, data, 0o644))
1837− cfg, err := Init(cwdDir, false)
1839− require.NoError(t, err)
1841− assert.False(t, cfg.Options.Debug)
1842− assert.True(t, cfg.Options.DebugLSP)
1843− assert.True(t, cfg.Options.DisableAutoSummarize)
1846−func TestOptionsMerging_DataDirectory(t *testing.T) {
1848− testConfigDir = t.TempDir()
1849− cwdDir := t.TempDir()
1851− os.Setenv("ANTHROPIC_API_KEY", "test-key")
1853− globalConfig := Config{
1855− DataDirectory: "global-data",
1859− configPath := filepath.Join(testConfigDir, "crush.json")
1860− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1861− data, err := json.Marshal(globalConfig)
1862− require.NoError(t, err)
1863− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1865− localConfig := Config{
1867− DataDirectory: "local-data",
1871− localConfigPath := filepath.Join(cwdDir, "crush.json")
1872− data, err = json.Marshal(localConfig)
1873− require.NoError(t, err)
1874− require.NoError(t, os.WriteFile(localConfigPath, data, 0o644))
1876− cfg, err := Init(cwdDir, false)
1878− require.NoError(t, err)
1880− assert.Equal(t, "local-data", cfg.Options.DataDirectory)
1883−func TestOptionsMerging_DefaultValues(t *testing.T) {
1885− testConfigDir = t.TempDir()
1886− cwdDir := t.TempDir()
1888− os.Setenv("ANTHROPIC_API_KEY", "test-key")
1890− cfg, err := Init(cwdDir, false)
1892− require.NoError(t, err)
1894− assert.Equal(t, defaultDataDirectory, cfg.Options.DataDirectory)
1895− assert.Equal(t, defaultContextPaths, cfg.Options.ContextPaths)
1896− assert.False(t, cfg.Options.TUI.CompactMode)
1897− assert.False(t, cfg.Options.Debug)
1898− assert.False(t, cfg.Options.DebugLSP)
1899− assert.False(t, cfg.Options.DisableAutoSummarize)
1902−func TestOptionsMerging_DebugFlagFromInit(t *testing.T) {
1904− testConfigDir = t.TempDir()
1905− cwdDir := t.TempDir()
1907− os.Setenv("ANTHROPIC_API_KEY", "test-key")
1909− globalConfig := Config{
1915− configPath := filepath.Join(testConfigDir, "crush.json")
1916− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1917− data, err := json.Marshal(globalConfig)
1918− require.NoError(t, err)
1919− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1921− cfg, err := Init(cwdDir, true)
1923− require.NoError(t, err)
1925− // Debug flag from Init should take precedence
1926− assert.True(t, cfg.Options.Debug)
1929−func TestOptionsMerging_ComplexScenario(t *testing.T) {
1931− testConfigDir = t.TempDir()
1932− cwdDir := t.TempDir()
1934− // Set up a provider
1935− os.Setenv("ANTHROPIC_API_KEY", "test-key")
1937− // Create global config with various options
1938− globalConfig := Config{
1940− ContextPaths: []string{"global-context.md"},
1941− DataDirectory: "global-data",
1944− DisableAutoSummarize: false,
1946− CompactMode: false,
1951− configPath := filepath.Join(testConfigDir, "crush.json")
1952− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1953− data, err := json.Marshal(globalConfig)
1954− require.NoError(t, err)
1955− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1957− // Create local config that partially overrides
1958− localConfig := Config{
1960− ContextPaths: []string{"local-context.md"},
1961− DebugLSP: true, // Override
1962− DisableAutoSummarize: true, // Override
1964− CompactMode: true, // Override
1966− // DataDirectory and Debug not specified - should keep global values
1970− localConfigPath := filepath.Join(cwdDir, "crush.json")
1971− data, err = json.Marshal(localConfig)
1972− require.NoError(t, err)
1973− require.NoError(t, os.WriteFile(localConfigPath, data, 0o644))
1975− cfg, err := Init(cwdDir, false)
1977− require.NoError(t, err)
1979− // Check merged results
1980− expectedContextPaths := append(defaultContextPaths, "global-context.md", "local-context.md")
1981− assert.Equal(t, expectedContextPaths, cfg.Options.ContextPaths)
1982− assert.Equal(t, "global-data", cfg.Options.DataDirectory) // From global
1983− assert.False(t, cfg.Options.Debug) // From global
1984− assert.True(t, cfg.Options.DebugLSP) // From local
1985− assert.True(t, cfg.Options.DisableAutoSummarize) // From local
1986− assert.True(t, cfg.Options.TUI.CompactMode) // From local
1989−// Model Selection Tests
1991−func TestModelSelection_PreferredModelSelection(t *testing.T) {
1993− testConfigDir = t.TempDir()
1994− cwdDir := t.TempDir()
1996− // Set up multiple providers to test selection logic
1997− os.Setenv("ANTHROPIC_API_KEY", "test-anthropic-key")
1998− os.Setenv("OPENAI_API_KEY", "test-openai-key")
2000− cfg, err := Init(cwdDir, false)
2002− require.NoError(t, err)
2003− require.Len(t, cfg.Providers, 2)
2005− // Should have preferred models set
2006− assert.NotEmpty(t, cfg.Models.Large.ModelID)
2007− assert.NotEmpty(t, cfg.Models.Large.Provider)
2008− assert.NotEmpty(t, cfg.Models.Small.ModelID)
2009− assert.NotEmpty(t, cfg.Models.Small.Provider)
2011− // Both should use the same provider (first available)
2012− assert.Equal(t, cfg.Models.Large.Provider, cfg.Models.Small.Provider)
2015−func TestValidation_InvalidModelReference(t *testing.T) {
2017− testConfigDir = t.TempDir()
2018− cwdDir := t.TempDir()
2020− globalConfig := Config{
2021− Providers: map[provider.InferenceProvider]ProviderConfig{
2022− provider.InferenceProviderOpenAI: {
2023− ID: provider.InferenceProviderOpenAI,
2024− APIKey: "test-key",
2025− ProviderType: provider.TypeOpenAI,
2026− DefaultLargeModel: "non-existent-model",
2027− DefaultSmallModel: "gpt-3.5-turbo",
2030− ID: "gpt-3.5-turbo",
2031− Name: "GPT-3.5 Turbo",
2032− ContextWindow: 4096,
2033− DefaultMaxTokens: 2048,
2040− configPath := filepath.Join(testConfigDir, "crush.json")
2041− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
2042− data, err := json.Marshal(globalConfig)
2043− require.NoError(t, err)
2044− require.NoError(t, os.WriteFile(configPath, data, 0o644))
2046− _, err = Init(cwdDir, false)
2047− assert.Error(t, err)
2050−func TestValidation_InvalidAgentModelType(t *testing.T) {
2052− testConfigDir = t.TempDir()
2053− cwdDir := t.TempDir()
2055− os.Setenv("ANTHROPIC_API_KEY", "test-key")
2057− globalConfig := Config{
2058− Agents: map[AgentID]Agent{
2059− AgentID("invalid-agent"): {
2060− ID: AgentID("invalid-agent"),
2061− Name: "Invalid Agent",
2062− Model: ModelType("invalid"),
2067− configPath := filepath.Join(testConfigDir, "crush.json")
2068− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
2069− data, err := json.Marshal(globalConfig)
2070− require.NoError(t, err)
2071− require.NoError(t, os.WriteFile(configPath, data, 0o644))
2073− _, err = Init(cwdDir, false)
2074− assert.Error(t, err)
internal/config/fs.godeleted−71
@@ -1,71 +0,0 @@
10−var testConfigDir string
12−func baseConfigPath() string {
13− if testConfigDir != "" {
17− xdgConfigHome := os.Getenv("XDG_CONFIG_HOME")
18− if xdgConfigHome != "" {
19− return filepath.Join(xdgConfigHome, "crush")
22− // return the path to the main config directory
23− // for windows, it should be in `%LOCALAPPDATA%/crush/`
24− // for linux and macOS, it should be in `$HOME/.config/crush/`
25− if runtime.GOOS == "windows" {
26− localAppData := os.Getenv("LOCALAPPDATA")
27− if localAppData == "" {
28− localAppData = filepath.Join(os.Getenv("USERPROFILE"), "AppData", "Local")
30− return filepath.Join(localAppData, appName)
33− return filepath.Join(os.Getenv("HOME"), ".config", appName)
36−func baseDataPath() string {
37− if testConfigDir != "" {
41− xdgDataHome := os.Getenv("XDG_DATA_HOME")
42− if xdgDataHome != "" {
43− return filepath.Join(xdgDataHome, appName)
46− // return the path to the main data directory
47− // for windows, it should be in `%LOCALAPPDATA%/crush/`
48− // for linux and macOS, it should be in `$HOME/.local/share/crush/`
49− if runtime.GOOS == "windows" {
50− localAppData := os.Getenv("LOCALAPPDATA")
51− if localAppData == "" {
52− localAppData = filepath.Join(os.Getenv("USERPROFILE"), "AppData", "Local")
54− return filepath.Join(localAppData, appName)
57− return filepath.Join(os.Getenv("HOME"), ".local", "share", appName)
60−func ConfigPath() string {
61− return filepath.Join(baseConfigPath(), fmt.Sprintf("%s.json", appName))
64−func CrushInitialized() bool {
65− cfgPath := ConfigPath()
66− if _, err := os.Stat(cfgPath); os.IsNotExist(err) {
67− // config file does not exist, so Crush is not initialized
internal/config/init.go+32−12
@@ -5,27 +5,49 @@ import (
6 "path/filepath"
6 "path/filepath"
11− // InitFlagFilename is the name of the file that indicates whether the project has been initialized
12 InitFlagFilename = "init"
13 InitFlagFilename = "init"
15−// ProjectInitFlag represents the initialization status for a project directory
16type ProjectInitFlag struct {
16type ProjectInitFlag struct {
17 Initialized bool `json:"initialized"`
17 Initialized bool `json:"initialized"`
20−// ProjectNeedsInitialization checks if the current project needs initialization
20+// TODO: we need to remove the global config instance keeping it now just until everything is migrated
22+ instance atomic.Pointer[Config]
24+ once sync.Once // Ensures the initialization happens only once
27+func Init(workingDir string, debug bool) (*Config, error) {
32+ cfg, err = Load(cwd, debug)
36+ return instance.Load(), err
40+ return instance.Load()
21func ProjectNeedsInitialization() (bool, error) {
43func ProjectNeedsInitialization() (bool, error) {
22− if instance == nil {
45+ if cfg == nil {
23 return false, fmt.Errorf("config not loaded")
46 return false, fmt.Errorf("config not loaded")
26− flagFilePath := filepath.Join(instance.Options.DataDirectory, InitFlagFilename)
49+ flagFilePath := filepath.Join(cfg.Options.DataDirectory, InitFlagFilename)
28− // Check if the flag file exists
29 _, err := os.Stat(flagFilePath)
51 _, err := os.Stat(flagFilePath)
30 if err == nil {
52 if err == nil {
31 return false, nil
53 return false, nil
@@ -35,8 +57,7 @@ func ProjectNeedsInitialization() (bool, error) {
35 return false, fmt.Errorf("failed to check init flag file: %w", err)
57 return false, fmt.Errorf("failed to check init flag file: %w", err)
38− // Check if any variation of CRUSH.md already exists in working directory
39− crushExists, err := crushMdExists(WorkingDirectory())
60+ crushExists, err := crushMdExists(cfg.WorkingDir())
40 if err != nil {
61 if err != nil {
41 return false, fmt.Errorf("failed to check for CRUSH.md files: %w", err)
62 return false, fmt.Errorf("failed to check for CRUSH.md files: %w", err)
@@ -47,7 +68,6 @@ func ProjectNeedsInitialization() (bool, error) {
47 return true, nil
68 return true, nil
50−// crushMdExists checks if any case variation of crush.md exists in the directory
51func crushMdExists(dir string) (bool, error) {
71func crushMdExists(dir string) (bool, error) {
52 entries, err := os.ReadDir(dir)
72 entries, err := os.ReadDir(dir)
53 if err != nil {
73 if err != nil {
@@ -68,12 +88,12 @@ func crushMdExists(dir string) (bool, error) {
68 return false, nil
88 return false, nil
71−// MarkProjectInitialized marks the current project as initialized
72func MarkProjectInitialized() error {
91func MarkProjectInitialized() error {
73− if instance == nil {
93+ if cfg == nil {
74 return fmt.Errorf("config not loaded")
94 return fmt.Errorf("config not loaded")
76− flagFilePath := filepath.Join(instance.Options.DataDirectory, InitFlagFilename)
96+ flagFilePath := filepath.Join(cfg.Options.DataDirectory, InitFlagFilename)
78 file, err := os.Create(flagFilePath)
98 file, err := os.Create(flagFilePath)
79 if err != nil {
99 if err != nil {
internal/config/load.gonew file+539
@@ -0,0 +1,539 @@
13+ "github.com/charmbracelet/crush/internal/env"
14+ "github.com/charmbracelet/crush/internal/fur/client"
15+ "github.com/charmbracelet/crush/internal/fur/provider"
16+ "github.com/charmbracelet/crush/internal/log"
17+ "golang.org/x/exp/slog"
20+// LoadReader config via io.Reader.
21+func LoadReader(fd io.Reader) (*Config, error) {
22+ data, err := io.ReadAll(fd)
28+ err = json.Unmarshal(data, &config)
35+// Load loads the configuration from the default paths.
36+func Load(workingDir string, debug bool) (*Config, error) {
37+ // uses default config paths
38+ configPaths := []string{
41+ filepath.Join(workingDir, fmt.Sprintf("%s.json", appName)),
42+ filepath.Join(workingDir, fmt.Sprintf(".%s.json", appName)),
44+ cfg, err := loadFromConfigPaths(configPaths)
46+ return nil, fmt.Errorf("failed to load config from paths %v: %w", configPaths, err)
49+ cfg.setDefaults(workingDir)
52+ cfg.Options.Debug = true
57+ filepath.Join(cfg.Options.DataDirectory, "logs", fmt.Sprintf("%s.log", appName)),
62+ return nil, fmt.Errorf("failed to load config: %w", err)
65+ // Load known providers, this loads the config from fur
66+ providers, err := LoadProviders(client.New())
67+ if err != nil || len(providers) == 0 {
68+ return nil, fmt.Errorf("failed to load providers: %w", err)
72+ // Configure providers
73+ valueResolver := NewShellVariableResolver(env)
74+ cfg.resolver = valueResolver
75+ if err := cfg.configureProviders(env, valueResolver, providers); err != nil {
76+ return nil, fmt.Errorf("failed to configure providers: %w", err)
79+ if !cfg.IsConfigured() {
80+ slog.Warn("No providers configured")
84+ if err := cfg.configureSelectedModels(providers); err != nil {
85+ return nil, fmt.Errorf("failed to configure selected models: %w", err)
88+ // TODO: remove the agents concept from the config
89+ agents := map[string]Agent{
93+ Description: "An agent that helps with executing coding tasks.",
94+ Model: SelectedModelTypeLarge,
95+ ContextPaths: cfg.Options.ContextPaths,
101+ Description: "An agent that helps with searching for context and finding implementation details.",
102+ Model: SelectedModelTypeLarge,
103+ ContextPaths: cfg.Options.ContextPaths,
104+ AllowedTools: []string{
111+ // NO MCPs or LSPs by default
112+ AllowedMCP: map[string][]string{},
113+ AllowedLSP: []string{},
121+func (cfg *Config) configureProviders(env env.Env, resolver VariableResolver, knownProviders []provider.Provider) error {
122+ knownProviderNames := make(map[string]bool)
123+ for _, p := range knownProviders {
124+ knownProviderNames[string(p.ID)] = true
125+ config, configExists := cfg.Providers[string(p.ID)]
126+ // if the user configured a known provider we need to allow it to override a couple of parameters
129+ slog.Debug("Skipping provider due to disable flag", "provider", p.ID)
130+ delete(cfg.Providers, string(p.ID))
133+ if config.BaseURL != "" {
134+ p.APIEndpoint = config.BaseURL
136+ if config.APIKey != "" {
137+ p.APIKey = config.APIKey
139+ if len(config.Models) > 0 {
140+ models := []provider.Model{}
141+ seen := make(map[string]bool)
143+ for _, model := range config.Models {
147+ seen[model.ID] = true
148+ if model.Model == "" {
149+ model.Model = model.ID
151+ models = append(models, model)
153+ for _, model := range p.Models {
157+ seen[model.ID] = true
158+ if model.Model == "" {
159+ model.Model = model.ID
161+ models = append(models, model)
167+ prepared := ProviderConfig{
169+ BaseURL: p.APIEndpoint,
172+ Disable: config.Disable,
173+ ExtraHeaders: config.ExtraHeaders,
174+ ExtraParams: make(map[string]string),
179+ // Handle specific providers that require additional configuration
180+ case provider.InferenceProviderVertexAI:
181+ if !hasVertexCredentials(env) {
183+ slog.Warn("Skipping Vertex AI provider due to missing credentials")
184+ delete(cfg.Providers, string(p.ID))
188+ prepared.ExtraParams["project"] = env.Get("GOOGLE_CLOUD_PROJECT")
189+ prepared.ExtraParams["location"] = env.Get("GOOGLE_CLOUD_LOCATION")
190+ case provider.InferenceProviderBedrock:
191+ if !hasAWSCredentials(env) {
193+ slog.Warn("Skipping Bedrock provider due to missing AWS credentials")
194+ delete(cfg.Providers, string(p.ID))
198+ for _, model := range p.Models {
199+ if !strings.HasPrefix(model.ID, "anthropic.") {
200+ return fmt.Errorf("bedrock provider only supports anthropic models for now, found: %s", model.ID)
204+ // if the provider api or endpoint are missing we skip them
205+ v, err := resolver.ResolveValue(p.APIKey)
206+ if v == "" || err != nil {
208+ slog.Warn("Skipping provider due to missing API key", "provider", p.ID)
209+ delete(cfg.Providers, string(p.ID))
214+ cfg.Providers[string(p.ID)] = prepared
217+ // validate the custom providers
218+ for id, providerConfig := range cfg.Providers {
219+ if knownProviderNames[id] {
223+ // Make sure the provider ID is set
224+ providerConfig.ID = id
225+ // default to OpenAI if not set
226+ if providerConfig.Type == "" {
227+ providerConfig.Type = provider.TypeOpenAI
230+ if providerConfig.Disable {
231+ slog.Debug("Skipping custom provider due to disable flag", "provider", id)
232+ delete(cfg.Providers, id)
235+ if providerConfig.APIKey == "" {
236+ slog.Warn("Skipping custom provider due to missing API key", "provider", id)
237+ delete(cfg.Providers, id)
240+ if providerConfig.BaseURL == "" {
241+ slog.Warn("Skipping custom provider due to missing API endpoint", "provider", id)
242+ delete(cfg.Providers, id)
245+ if len(providerConfig.Models) == 0 {
246+ slog.Warn("Skipping custom provider because the provider has no models", "provider", id)
247+ delete(cfg.Providers, id)
250+ if providerConfig.Type != provider.TypeOpenAI {
251+ slog.Warn("Skipping custom provider because the provider type is not supported", "provider", id, "type", providerConfig.Type)
252+ delete(cfg.Providers, id)
256+ apiKey, err := resolver.ResolveValue(providerConfig.APIKey)
257+ if apiKey == "" || err != nil {
258+ slog.Warn("Skipping custom provider due to missing API key", "provider", id, "error", err)
259+ delete(cfg.Providers, id)
262+ baseURL, err := resolver.ResolveValue(providerConfig.BaseURL)
263+ if baseURL == "" || err != nil {
264+ slog.Warn("Skipping custom provider due to missing API endpoint", "provider", id, "error", err)
265+ delete(cfg.Providers, id)
269+ cfg.Providers[id] = providerConfig
274+func (cfg *Config) setDefaults(workingDir string) {
275+ cfg.workingDir = workingDir
276+ if cfg.Options == nil {
277+ cfg.Options = &Options{}
279+ if cfg.Options.TUI == nil {
280+ cfg.Options.TUI = &TUIOptions{}
282+ if cfg.Options.ContextPaths == nil {
283+ cfg.Options.ContextPaths = []string{}
285+ if cfg.Options.DataDirectory == "" {
286+ cfg.Options.DataDirectory = filepath.Join(workingDir, defaultDataDirectory)
288+ if cfg.Providers == nil {
289+ cfg.Providers = make(map[string]ProviderConfig)
291+ if cfg.Models == nil {
292+ cfg.Models = make(map[SelectedModelType]SelectedModel)
295+ cfg.MCP = make(map[string]MCPConfig)
298+ cfg.LSP = make(map[string]LSPConfig)
301+ // Add the default context paths if they are not already present
302+ cfg.Options.ContextPaths = append(defaultContextPaths, cfg.Options.ContextPaths...)
303+ slices.Sort(cfg.Options.ContextPaths)
304+ cfg.Options.ContextPaths = slices.Compact(cfg.Options.ContextPaths)
307+func (cfg *Config) defaultModelSelection(knownProviders []provider.Provider) (largeModel SelectedModel, smallModel SelectedModel, err error) {
308+ if len(knownProviders) == 0 && len(cfg.Providers) == 0 {
309+ err = fmt.Errorf("no providers configured, please configure at least one provider")
313+ // Use the first provider enabled based on the known providers order
314+ // if no provider found that is known use the first provider configured
315+ for _, p := range knownProviders {
316+ providerConfig, ok := cfg.Providers[string(p.ID)]
317+ if !ok || providerConfig.Disable {
320+ defaultLargeModel := cfg.GetModel(string(p.ID), p.DefaultLargeModelID)
321+ if defaultLargeModel == nil {
322+ err = fmt.Errorf("default large model %s not found for provider %s", p.DefaultLargeModelID, p.ID)
325+ largeModel = SelectedModel{
326+ Provider: string(p.ID),
327+ Model: defaultLargeModel.ID,
328+ MaxTokens: defaultLargeModel.DefaultMaxTokens,
329+ ReasoningEffort: defaultLargeModel.DefaultReasoningEffort,
332+ defaultSmallModel := cfg.GetModel(string(p.ID), p.DefaultSmallModelID)
333+ if defaultSmallModel == nil {
334+ err = fmt.Errorf("default small model %s not found for provider %s", p.DefaultSmallModelID, p.ID)
336+ smallModel = SelectedModel{
337+ Provider: string(p.ID),
338+ Model: defaultSmallModel.ID,
339+ MaxTokens: defaultSmallModel.DefaultMaxTokens,
340+ ReasoningEffort: defaultSmallModel.DefaultReasoningEffort,
345+ enabledProviders := cfg.EnabledProviders()
346+ slices.SortFunc(enabledProviders, func(a, b ProviderConfig) int {
347+ return strings.Compare(a.ID, b.ID)
350+ if len(enabledProviders) == 0 {
351+ err = fmt.Errorf("no providers configured, please configure at least one provider")
355+ providerConfig := enabledProviders[0]
356+ if len(providerConfig.Models) == 0 {
357+ err = fmt.Errorf("provider %s has no models configured", providerConfig.ID)
360+ defaultLargeModel := cfg.GetModel(providerConfig.ID, providerConfig.Models[0].ID)
361+ largeModel = SelectedModel{
362+ Provider: providerConfig.ID,
363+ Model: defaultLargeModel.ID,
364+ MaxTokens: defaultLargeModel.DefaultMaxTokens,
366+ defaultSmallModel := cfg.GetModel(providerConfig.ID, providerConfig.Models[0].ID)
367+ smallModel = SelectedModel{
368+ Provider: providerConfig.ID,
369+ Model: defaultSmallModel.ID,
370+ MaxTokens: defaultSmallModel.DefaultMaxTokens,
375+func (cfg *Config) configureSelectedModels(knownProviders []provider.Provider) error {
376+ large, small, err := cfg.defaultModelSelection(knownProviders)
378+ return fmt.Errorf("failed to select default models: %w", err)
381+ largeModelSelected, largeModelConfigured := cfg.Models[SelectedModelTypeLarge]
382+ if largeModelConfigured {
383+ if largeModelSelected.Model != "" {
384+ large.Model = largeModelSelected.Model
386+ if largeModelSelected.Provider != "" {
387+ large.Provider = largeModelSelected.Provider
389+ model := cfg.GetModel(large.Provider, large.Model)
391+ return fmt.Errorf("large model %s not found for provider %s", large.Model, large.Provider)
393+ if largeModelSelected.MaxTokens > 0 {
394+ large.MaxTokens = largeModelSelected.MaxTokens
396+ large.MaxTokens = model.DefaultMaxTokens
398+ if largeModelSelected.ReasoningEffort != "" {
399+ large.ReasoningEffort = largeModelSelected.ReasoningEffort
401+ large.Think = largeModelSelected.Think
403+ smallModelSelected, smallModelConfigured := cfg.Models[SelectedModelTypeSmall]
404+ if smallModelConfigured {
405+ if smallModelSelected.Model != "" {
406+ small.Model = smallModelSelected.Model
408+ if smallModelSelected.Provider != "" {
409+ small.Provider = smallModelSelected.Provider
412+ model := cfg.GetModel(small.Provider, small.Model)
414+ return fmt.Errorf("large model %s not found for provider %s", large.Model, large.Provider)
416+ if smallModelSelected.MaxTokens > 0 {
417+ small.MaxTokens = smallModelSelected.MaxTokens
419+ small.MaxTokens = model.DefaultMaxTokens
421+ small.ReasoningEffort = smallModelSelected.ReasoningEffort
422+ small.Think = smallModelSelected.Think
425+ // validate the selected models
426+ largeModel := cfg.GetModel(large.Provider, large.Model)
427+ if largeModel == nil {
428+ return fmt.Errorf("large model %s not found for provider %s", large.Model, large.Provider)
430+ smallModel := cfg.GetModel(small.Provider, small.Model)
431+ if smallModel == nil {
432+ return fmt.Errorf("small model %s not found for provider %s", small.Model, small.Provider)
434+ cfg.Models[SelectedModelTypeLarge] = large
435+ cfg.Models[SelectedModelTypeSmall] = small
439+func loadFromConfigPaths(configPaths []string) (*Config, error) {
440+ var configs []io.Reader
442+ for _, path := range configPaths {
443+ fd, err := os.Open(path)
445+ if os.IsNotExist(err) {
448+ return nil, fmt.Errorf("failed to open config file %s: %w", path, err)
452+ configs = append(configs, fd)
455+ return loadFromReaders(configs)
458+func loadFromReaders(readers []io.Reader) (*Config, error) {
459+ if len(readers) == 0 {
460+ return &Config{}, nil
463+ merged, err := Merge(readers)
465+ return nil, fmt.Errorf("failed to merge configuration readers: %w", err)
468+ return LoadReader(merged)
471+func hasVertexCredentials(env env.Env) bool {
472+ useVertex := env.Get("GOOGLE_GENAI_USE_VERTEXAI") == "true"
473+ hasProject := env.Get("GOOGLE_CLOUD_PROJECT") != ""
474+ hasLocation := env.Get("GOOGLE_CLOUD_LOCATION") != ""
475+ return useVertex && hasProject && hasLocation
478+func hasAWSCredentials(env env.Env) bool {
479+ if env.Get("AWS_ACCESS_KEY_ID") != "" && env.Get("AWS_SECRET_ACCESS_KEY") != "" {
483+ if env.Get("AWS_PROFILE") != "" || env.Get("AWS_DEFAULT_PROFILE") != "" {
487+ if env.Get("AWS_REGION") != "" || env.Get("AWS_DEFAULT_REGION") != "" {
491+ if env.Get("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") != "" ||
492+ env.Get("AWS_CONTAINER_CREDENTIALS_FULL_URI") != "" {
499+func globalConfig() string {
500+ xdgConfigHome := os.Getenv("XDG_CONFIG_HOME")
501+ if xdgConfigHome != "" {
502+ return filepath.Join(xdgConfigHome, appName, fmt.Sprintf("%s.json", appName))
505+ // return the path to the main config directory
506+ // for windows, it should be in `%LOCALAPPDATA%/crush/`
507+ // for linux and macOS, it should be in `$HOME/.config/crush/`
508+ if runtime.GOOS == "windows" {
509+ localAppData := os.Getenv("LOCALAPPDATA")
510+ if localAppData == "" {
511+ localAppData = filepath.Join(os.Getenv("USERPROFILE"), "AppData", "Local")
513+ return filepath.Join(localAppData, appName, fmt.Sprintf("%s.json", appName))
516+ return filepath.Join(os.Getenv("HOME"), ".config", appName, fmt.Sprintf("%s.json", appName))
519+// globalConfigData returns the path to the main data directory for the application.
520+// this config is used when the app overrides configurations instead of updating the global config.
521+func globalConfigData() string {
522+ xdgDataHome := os.Getenv("XDG_DATA_HOME")
523+ if xdgDataHome != "" {
524+ return filepath.Join(xdgDataHome, appName, fmt.Sprintf("%s.json", appName))
527+ // return the path to the main data directory
528+ // for windows, it should be in `%LOCALAPPDATA%/crush/`
529+ // for linux and macOS, it should be in `$HOME/.local/share/crush/`
530+ if runtime.GOOS == "windows" {
531+ localAppData := os.Getenv("LOCALAPPDATA")
532+ if localAppData == "" {
533+ localAppData = filepath.Join(os.Getenv("USERPROFILE"), "AppData", "Local")
535+ return filepath.Join(localAppData, appName, fmt.Sprintf("%s.json", appName))
538+ return filepath.Join(os.Getenv("HOME"), ".local", "share", appName, fmt.Sprintf("%s.json", appName))
internal/config/load_test.gonew file+1150
@@ -0,0 +1,1150 @@
11+ "github.com/charmbracelet/crush/internal/env"
12+ "github.com/charmbracelet/crush/internal/fur/provider"
13+ "github.com/stretchr/testify/assert"
16+func TestMain(m *testing.M) {
17+ slog.SetDefault(slog.New(slog.NewTextHandler(io.Discard, nil)))
23+func TestConfig_LoadFromReaders(t *testing.T) {
24+ data1 := strings.NewReader(`{"providers": {"openai": {"api_key": "key1", "base_url": "https://api.openai.com/v1"}}}`)
25+ data2 := strings.NewReader(`{"providers": {"openai": {"api_key": "key2", "base_url": "https://api.openai.com/v2"}}}`)
26+ data3 := strings.NewReader(`{"providers": {"openai": {}}}`)
28+ loadedConfig, err := loadFromReaders([]io.Reader{data1, data2, data3})
30+ assert.NoError(t, err)
31+ assert.NotNil(t, loadedConfig)
32+ assert.Len(t, loadedConfig.Providers, 1)
33+ assert.Equal(t, "key2", loadedConfig.Providers["openai"].APIKey)
34+ assert.Equal(t, "https://api.openai.com/v2", loadedConfig.Providers["openai"].BaseURL)
37+func TestConfig_setDefaults(t *testing.T) {
40+ cfg.setDefaults("/tmp")
42+ assert.NotNil(t, cfg.Options)
43+ assert.NotNil(t, cfg.Options.TUI)
44+ assert.NotNil(t, cfg.Options.ContextPaths)
45+ assert.NotNil(t, cfg.Providers)
46+ assert.NotNil(t, cfg.Models)
47+ assert.NotNil(t, cfg.LSP)
48+ assert.NotNil(t, cfg.MCP)
49+ assert.Equal(t, filepath.Join("/tmp", ".crush"), cfg.Options.DataDirectory)
50+ for _, path := range defaultContextPaths {
51+ assert.Contains(t, cfg.Options.ContextPaths, path)
53+ assert.Equal(t, "/tmp", cfg.workingDir)
56+func TestConfig_configureProviders(t *testing.T) {
57+ knownProviders := []provider.Provider{
60+ APIKey: "$OPENAI_API_KEY",
61+ APIEndpoint: "https://api.openai.com/v1",
62+ Models: []provider.Model{{
69+ cfg.setDefaults("/tmp")
70+ env := env.NewFromMap(map[string]string{
71+ "OPENAI_API_KEY": "test-key",
73+ resolver := NewEnvironmentVariableResolver(env)
74+ err := cfg.configureProviders(env, resolver, knownProviders)
75+ assert.NoError(t, err)
76+ assert.Len(t, cfg.Providers, 1)
78+ // We want to make sure that we keep the configured API key as a placeholder
79+ assert.Equal(t, "$OPENAI_API_KEY", cfg.Providers["openai"].APIKey)
82+func TestConfig_configureProvidersWithOverride(t *testing.T) {
83+ knownProviders := []provider.Provider{
86+ APIKey: "$OPENAI_API_KEY",
87+ APIEndpoint: "https://api.openai.com/v1",
88+ Models: []provider.Model{{
95+ Providers: map[string]ProviderConfig{
98+ BaseURL: "https://api.openai.com/v2",
99+ Models: []provider.Model{
105+ ID: "another-model",
111+ cfg.setDefaults("/tmp")
113+ env := env.NewFromMap(map[string]string{
114+ "OPENAI_API_KEY": "test-key",
116+ resolver := NewEnvironmentVariableResolver(env)
117+ err := cfg.configureProviders(env, resolver, knownProviders)
118+ assert.NoError(t, err)
119+ assert.Len(t, cfg.Providers, 1)
121+ // We want to make sure that we keep the configured API key as a placeholder
122+ assert.Equal(t, "xyz", cfg.Providers["openai"].APIKey)
123+ assert.Equal(t, "https://api.openai.com/v2", cfg.Providers["openai"].BaseURL)
124+ assert.Len(t, cfg.Providers["openai"].Models, 2)
125+ assert.Equal(t, "Updated", cfg.Providers["openai"].Models[0].Model)
128+func TestConfig_configureProvidersWithNewProvider(t *testing.T) {
129+ knownProviders := []provider.Provider{
132+ APIKey: "$OPENAI_API_KEY",
133+ APIEndpoint: "https://api.openai.com/v1",
134+ Models: []provider.Model{{
141+ Providers: map[string]ProviderConfig{
144+ BaseURL: "https://api.someendpoint.com/v2",
145+ Models: []provider.Model{
153+ cfg.setDefaults("/tmp")
154+ env := env.NewFromMap(map[string]string{
155+ "OPENAI_API_KEY": "test-key",
157+ resolver := NewEnvironmentVariableResolver(env)
158+ err := cfg.configureProviders(env, resolver, knownProviders)
159+ assert.NoError(t, err)
160+ // Should be to because of the env variable
161+ assert.Len(t, cfg.Providers, 2)
163+ // We want to make sure that we keep the configured API key as a placeholder
164+ assert.Equal(t, "xyz", cfg.Providers["custom"].APIKey)
165+ // Make sure we set the ID correctly
166+ assert.Equal(t, "custom", cfg.Providers["custom"].ID)
167+ assert.Equal(t, "https://api.someendpoint.com/v2", cfg.Providers["custom"].BaseURL)
168+ assert.Len(t, cfg.Providers["custom"].Models, 1)
170+ _, ok := cfg.Providers["openai"]
171+ assert.True(t, ok, "OpenAI provider should still be present")
174+func TestConfig_configureProvidersBedrockWithCredentials(t *testing.T) {
175+ knownProviders := []provider.Provider{
177+ ID: provider.InferenceProviderBedrock,
180+ Models: []provider.Model{{
181+ ID: "anthropic.claude-sonnet-4-20250514-v1:0",
187+ cfg.setDefaults("/tmp")
188+ env := env.NewFromMap(map[string]string{
189+ "AWS_ACCESS_KEY_ID": "test-key-id",
190+ "AWS_SECRET_ACCESS_KEY": "test-secret-key",
192+ resolver := NewEnvironmentVariableResolver(env)
193+ err := cfg.configureProviders(env, resolver, knownProviders)
194+ assert.NoError(t, err)
195+ assert.Len(t, cfg.Providers, 1)
197+ bedrockProvider, ok := cfg.Providers["bedrock"]
198+ assert.True(t, ok, "Bedrock provider should be present")
199+ assert.Len(t, bedrockProvider.Models, 1)
200+ assert.Equal(t, "anthropic.claude-sonnet-4-20250514-v1:0", bedrockProvider.Models[0].ID)
203+func TestConfig_configureProvidersBedrockWithoutCredentials(t *testing.T) {
204+ knownProviders := []provider.Provider{
206+ ID: provider.InferenceProviderBedrock,
209+ Models: []provider.Model{{
210+ ID: "anthropic.claude-sonnet-4-20250514-v1:0",
216+ cfg.setDefaults("/tmp")
217+ env := env.NewFromMap(map[string]string{})
218+ resolver := NewEnvironmentVariableResolver(env)
219+ err := cfg.configureProviders(env, resolver, knownProviders)
220+ assert.NoError(t, err)
221+ // Provider should not be configured without credentials
222+ assert.Len(t, cfg.Providers, 0)
225+func TestConfig_configureProvidersBedrockWithoutUnsupportedModel(t *testing.T) {
226+ knownProviders := []provider.Provider{
228+ ID: provider.InferenceProviderBedrock,
231+ Models: []provider.Model{{
232+ ID: "some-random-model",
238+ cfg.setDefaults("/tmp")
239+ env := env.NewFromMap(map[string]string{
240+ "AWS_ACCESS_KEY_ID": "test-key-id",
241+ "AWS_SECRET_ACCESS_KEY": "test-secret-key",
243+ resolver := NewEnvironmentVariableResolver(env)
244+ err := cfg.configureProviders(env, resolver, knownProviders)
245+ assert.Error(t, err)
248+func TestConfig_configureProvidersVertexAIWithCredentials(t *testing.T) {
249+ knownProviders := []provider.Provider{
251+ ID: provider.InferenceProviderVertexAI,
254+ Models: []provider.Model{{
261+ cfg.setDefaults("/tmp")
262+ env := env.NewFromMap(map[string]string{
263+ "GOOGLE_GENAI_USE_VERTEXAI": "true",
264+ "GOOGLE_CLOUD_PROJECT": "test-project",
265+ "GOOGLE_CLOUD_LOCATION": "us-central1",
267+ resolver := NewEnvironmentVariableResolver(env)
268+ err := cfg.configureProviders(env, resolver, knownProviders)
269+ assert.NoError(t, err)
270+ assert.Len(t, cfg.Providers, 1)
272+ vertexProvider, ok := cfg.Providers["vertexai"]
273+ assert.True(t, ok, "VertexAI provider should be present")
274+ assert.Len(t, vertexProvider.Models, 1)
275+ assert.Equal(t, "gemini-pro", vertexProvider.Models[0].ID)
276+ assert.Equal(t, "test-project", vertexProvider.ExtraParams["project"])
277+ assert.Equal(t, "us-central1", vertexProvider.ExtraParams["location"])
280+func TestConfig_configureProvidersVertexAIWithoutCredentials(t *testing.T) {
281+ knownProviders := []provider.Provider{
283+ ID: provider.InferenceProviderVertexAI,
286+ Models: []provider.Model{{
293+ cfg.setDefaults("/tmp")
294+ env := env.NewFromMap(map[string]string{
295+ "GOOGLE_GENAI_USE_VERTEXAI": "false",
296+ "GOOGLE_CLOUD_PROJECT": "test-project",
297+ "GOOGLE_CLOUD_LOCATION": "us-central1",
299+ resolver := NewEnvironmentVariableResolver(env)
300+ err := cfg.configureProviders(env, resolver, knownProviders)
301+ assert.NoError(t, err)
302+ // Provider should not be configured without proper credentials
303+ assert.Len(t, cfg.Providers, 0)
306+func TestConfig_configureProvidersVertexAIMissingProject(t *testing.T) {
307+ knownProviders := []provider.Provider{
309+ ID: provider.InferenceProviderVertexAI,
312+ Models: []provider.Model{{
319+ cfg.setDefaults("/tmp")
320+ env := env.NewFromMap(map[string]string{
321+ "GOOGLE_GENAI_USE_VERTEXAI": "true",
322+ "GOOGLE_CLOUD_LOCATION": "us-central1",
324+ resolver := NewEnvironmentVariableResolver(env)
325+ err := cfg.configureProviders(env, resolver, knownProviders)
326+ assert.NoError(t, err)
327+ // Provider should not be configured without project
328+ assert.Len(t, cfg.Providers, 0)
331+func TestConfig_configureProvidersSetProviderID(t *testing.T) {
332+ knownProviders := []provider.Provider{
335+ APIKey: "$OPENAI_API_KEY",
336+ APIEndpoint: "https://api.openai.com/v1",
337+ Models: []provider.Model{{
344+ cfg.setDefaults("/tmp")
345+ env := env.NewFromMap(map[string]string{
346+ "OPENAI_API_KEY": "test-key",
348+ resolver := NewEnvironmentVariableResolver(env)
349+ err := cfg.configureProviders(env, resolver, knownProviders)
350+ assert.NoError(t, err)
351+ assert.Len(t, cfg.Providers, 1)
353+ // Provider ID should be set
354+ assert.Equal(t, "openai", cfg.Providers["openai"].ID)
357+func TestConfig_EnabledProviders(t *testing.T) {
358+ t.Run("all providers enabled", func(t *testing.T) {
360+ Providers: map[string]ProviderConfig{
374+ enabled := cfg.EnabledProviders()
375+ assert.Len(t, enabled, 2)
378+ t.Run("some providers disabled", func(t *testing.T) {
380+ Providers: map[string]ProviderConfig{
394+ enabled := cfg.EnabledProviders()
395+ assert.Len(t, enabled, 1)
396+ assert.Equal(t, "openai", enabled[0].ID)
399+ t.Run("empty providers map", func(t *testing.T) {
401+ Providers: map[string]ProviderConfig{},
404+ enabled := cfg.EnabledProviders()
405+ assert.Len(t, enabled, 0)
409+func TestConfig_IsConfigured(t *testing.T) {
410+ t.Run("returns true when at least one provider is enabled", func(t *testing.T) {
412+ Providers: map[string]ProviderConfig{
421+ assert.True(t, cfg.IsConfigured())
424+ t.Run("returns false when no providers are configured", func(t *testing.T) {
426+ Providers: map[string]ProviderConfig{},
429+ assert.False(t, cfg.IsConfigured())
432+ t.Run("returns false when all providers are disabled", func(t *testing.T) {
434+ Providers: map[string]ProviderConfig{
448+ assert.False(t, cfg.IsConfigured())
452+func TestConfig_configureProvidersWithDisabledProvider(t *testing.T) {
453+ knownProviders := []provider.Provider{
456+ APIKey: "$OPENAI_API_KEY",
457+ APIEndpoint: "https://api.openai.com/v1",
458+ Models: []provider.Model{{
465+ Providers: map[string]ProviderConfig{
471+ cfg.setDefaults("/tmp")
473+ env := env.NewFromMap(map[string]string{
474+ "OPENAI_API_KEY": "test-key",
476+ resolver := NewEnvironmentVariableResolver(env)
477+ err := cfg.configureProviders(env, resolver, knownProviders)
478+ assert.NoError(t, err)
480+ // Provider should be removed from config when disabled
481+ assert.Len(t, cfg.Providers, 0)
482+ _, exists := cfg.Providers["openai"]
483+ assert.False(t, exists)
486+func TestConfig_configureProvidersCustomProviderValidation(t *testing.T) {
487+ t.Run("custom provider with missing API key is removed", func(t *testing.T) {
489+ Providers: map[string]ProviderConfig{
491+ BaseURL: "https://api.custom.com/v1",
492+ Models: []provider.Model{{
498+ cfg.setDefaults("/tmp")
500+ env := env.NewFromMap(map[string]string{})
501+ resolver := NewEnvironmentVariableResolver(env)
502+ err := cfg.configureProviders(env, resolver, []provider.Provider{})
503+ assert.NoError(t, err)
505+ assert.Len(t, cfg.Providers, 0)
506+ _, exists := cfg.Providers["custom"]
507+ assert.False(t, exists)
510+ t.Run("custom provider with missing BaseURL is removed", func(t *testing.T) {
512+ Providers: map[string]ProviderConfig{
515+ Models: []provider.Model{{
521+ cfg.setDefaults("/tmp")
523+ env := env.NewFromMap(map[string]string{})
524+ resolver := NewEnvironmentVariableResolver(env)
525+ err := cfg.configureProviders(env, resolver, []provider.Provider{})
526+ assert.NoError(t, err)
528+ assert.Len(t, cfg.Providers, 0)
529+ _, exists := cfg.Providers["custom"]
530+ assert.False(t, exists)
533+ t.Run("custom provider with no models is removed", func(t *testing.T) {
535+ Providers: map[string]ProviderConfig{
538+ BaseURL: "https://api.custom.com/v1",
539+ Models: []provider.Model{},
543+ cfg.setDefaults("/tmp")
545+ env := env.NewFromMap(map[string]string{})
546+ resolver := NewEnvironmentVariableResolver(env)
547+ err := cfg.configureProviders(env, resolver, []provider.Provider{})
548+ assert.NoError(t, err)
550+ assert.Len(t, cfg.Providers, 0)
551+ _, exists := cfg.Providers["custom"]
552+ assert.False(t, exists)
555+ t.Run("custom provider with unsupported type is removed", func(t *testing.T) {
557+ Providers: map[string]ProviderConfig{
560+ BaseURL: "https://api.custom.com/v1",
561+ Type: "unsupported",
562+ Models: []provider.Model{{
568+ cfg.setDefaults("/tmp")
570+ env := env.NewFromMap(map[string]string{})
571+ resolver := NewEnvironmentVariableResolver(env)
572+ err := cfg.configureProviders(env, resolver, []provider.Provider{})
573+ assert.NoError(t, err)
575+ assert.Len(t, cfg.Providers, 0)
576+ _, exists := cfg.Providers["custom"]
577+ assert.False(t, exists)
580+ t.Run("valid custom provider is kept and ID is set", func(t *testing.T) {
582+ Providers: map[string]ProviderConfig{
585+ BaseURL: "https://api.custom.com/v1",
586+ Type: provider.TypeOpenAI,
587+ Models: []provider.Model{{
593+ cfg.setDefaults("/tmp")
595+ env := env.NewFromMap(map[string]string{})
596+ resolver := NewEnvironmentVariableResolver(env)
597+ err := cfg.configureProviders(env, resolver, []provider.Provider{})
598+ assert.NoError(t, err)
600+ assert.Len(t, cfg.Providers, 1)
601+ customProvider, exists := cfg.Providers["custom"]
602+ assert.True(t, exists)
603+ assert.Equal(t, "custom", customProvider.ID)
604+ assert.Equal(t, "test-key", customProvider.APIKey)
605+ assert.Equal(t, "https://api.custom.com/v1", customProvider.BaseURL)
608+ t.Run("disabled custom provider is removed", func(t *testing.T) {
610+ Providers: map[string]ProviderConfig{
613+ BaseURL: "https://api.custom.com/v1",
614+ Type: provider.TypeOpenAI,
616+ Models: []provider.Model{{
622+ cfg.setDefaults("/tmp")
624+ env := env.NewFromMap(map[string]string{})
625+ resolver := NewEnvironmentVariableResolver(env)
626+ err := cfg.configureProviders(env, resolver, []provider.Provider{})
627+ assert.NoError(t, err)
629+ assert.Len(t, cfg.Providers, 0)
630+ _, exists := cfg.Providers["custom"]
631+ assert.False(t, exists)
635+func TestConfig_configureProvidersEnhancedCredentialValidation(t *testing.T) {
636+ t.Run("VertexAI provider removed when credentials missing with existing config", func(t *testing.T) {
637+ knownProviders := []provider.Provider{
639+ ID: provider.InferenceProviderVertexAI,
642+ Models: []provider.Model{{
649+ Providers: map[string]ProviderConfig{
651+ BaseURL: "custom-url",
655+ cfg.setDefaults("/tmp")
657+ env := env.NewFromMap(map[string]string{
658+ "GOOGLE_GENAI_USE_VERTEXAI": "false",
660+ resolver := NewEnvironmentVariableResolver(env)
661+ err := cfg.configureProviders(env, resolver, knownProviders)
662+ assert.NoError(t, err)
664+ assert.Len(t, cfg.Providers, 0)
665+ _, exists := cfg.Providers["vertexai"]
666+ assert.False(t, exists)
669+ t.Run("Bedrock provider removed when AWS credentials missing with existing config", func(t *testing.T) {
670+ knownProviders := []provider.Provider{
672+ ID: provider.InferenceProviderBedrock,
675+ Models: []provider.Model{{
676+ ID: "anthropic.claude-sonnet-4-20250514-v1:0",
682+ Providers: map[string]ProviderConfig{
684+ BaseURL: "custom-url",
688+ cfg.setDefaults("/tmp")
690+ env := env.NewFromMap(map[string]string{})
691+ resolver := NewEnvironmentVariableResolver(env)
692+ err := cfg.configureProviders(env, resolver, knownProviders)
693+ assert.NoError(t, err)
695+ assert.Len(t, cfg.Providers, 0)
696+ _, exists := cfg.Providers["bedrock"]
697+ assert.False(t, exists)
700+ t.Run("provider removed when API key missing with existing config", func(t *testing.T) {
701+ knownProviders := []provider.Provider{
704+ APIKey: "$MISSING_API_KEY",
705+ APIEndpoint: "https://api.openai.com/v1",
706+ Models: []provider.Model{{
713+ Providers: map[string]ProviderConfig{
715+ BaseURL: "custom-url",
719+ cfg.setDefaults("/tmp")
721+ env := env.NewFromMap(map[string]string{})
722+ resolver := NewEnvironmentVariableResolver(env)
723+ err := cfg.configureProviders(env, resolver, knownProviders)
724+ assert.NoError(t, err)
726+ assert.Len(t, cfg.Providers, 0)
727+ _, exists := cfg.Providers["openai"]
728+ assert.False(t, exists)
731+ t.Run("known provider should still be added if the endpoint is missing the client will use default endpoints", func(t *testing.T) {
732+ knownProviders := []provider.Provider{
735+ APIKey: "$OPENAI_API_KEY",
736+ APIEndpoint: "$MISSING_ENDPOINT",
737+ Models: []provider.Model{{
744+ Providers: map[string]ProviderConfig{
750+ cfg.setDefaults("/tmp")
752+ env := env.NewFromMap(map[string]string{
753+ "OPENAI_API_KEY": "test-key",
755+ resolver := NewEnvironmentVariableResolver(env)
756+ err := cfg.configureProviders(env, resolver, knownProviders)
757+ assert.NoError(t, err)
759+ assert.Len(t, cfg.Providers, 1)
760+ _, exists := cfg.Providers["openai"]
761+ assert.True(t, exists)
765+func TestConfig_defaultModelSelection(t *testing.T) {
766+ t.Run("default behavior uses the default models for given provider", func(t *testing.T) {
767+ knownProviders := []provider.Provider{
771+ DefaultLargeModelID: "large-model",
772+ DefaultSmallModelID: "small-model",
773+ Models: []provider.Model{
776+ DefaultMaxTokens: 1000,
780+ DefaultMaxTokens: 500,
787+ cfg.setDefaults("/tmp")
788+ env := env.NewFromMap(map[string]string{})
789+ resolver := NewEnvironmentVariableResolver(env)
790+ err := cfg.configureProviders(env, resolver, knownProviders)
791+ assert.NoError(t, err)
793+ large, small, err := cfg.defaultModelSelection(knownProviders)
794+ assert.NoError(t, err)
795+ assert.Equal(t, "large-model", large.Model)
796+ assert.Equal(t, "openai", large.Provider)
797+ assert.Equal(t, int64(1000), large.MaxTokens)
798+ assert.Equal(t, "small-model", small.Model)
799+ assert.Equal(t, "openai", small.Provider)
800+ assert.Equal(t, int64(500), small.MaxTokens)
802+ t.Run("should error if no providers configured", func(t *testing.T) {
803+ knownProviders := []provider.Provider{
806+ APIKey: "$MISSING_KEY",
807+ DefaultLargeModelID: "large-model",
808+ DefaultSmallModelID: "small-model",
809+ Models: []provider.Model{
812+ DefaultMaxTokens: 1000,
816+ DefaultMaxTokens: 500,
823+ cfg.setDefaults("/tmp")
824+ env := env.NewFromMap(map[string]string{})
825+ resolver := NewEnvironmentVariableResolver(env)
826+ err := cfg.configureProviders(env, resolver, knownProviders)
827+ assert.NoError(t, err)
829+ _, _, err = cfg.defaultModelSelection(knownProviders)
830+ assert.Error(t, err)
832+ t.Run("should error if model is missing", func(t *testing.T) {
833+ knownProviders := []provider.Provider{
837+ DefaultLargeModelID: "large-model",
838+ DefaultSmallModelID: "small-model",
839+ Models: []provider.Model{
841+ ID: "not-large-model",
842+ DefaultMaxTokens: 1000,
846+ DefaultMaxTokens: 500,
853+ cfg.setDefaults("/tmp")
854+ env := env.NewFromMap(map[string]string{})
855+ resolver := NewEnvironmentVariableResolver(env)
856+ err := cfg.configureProviders(env, resolver, knownProviders)
857+ assert.NoError(t, err)
858+ _, _, err = cfg.defaultModelSelection(knownProviders)
859+ assert.Error(t, err)
862+ t.Run("should configure the default models with a custom provider", func(t *testing.T) {
863+ knownProviders := []provider.Provider{
866+ APIKey: "$MISSING", // will not be included in the config
867+ DefaultLargeModelID: "large-model",
868+ DefaultSmallModelID: "small-model",
869+ Models: []provider.Model{
871+ ID: "not-large-model",
872+ DefaultMaxTokens: 1000,
876+ DefaultMaxTokens: 500,
883+ Providers: map[string]ProviderConfig{
886+ BaseURL: "https://api.custom.com/v1",
887+ Models: []provider.Model{
890+ DefaultMaxTokens: 600,
896+ cfg.setDefaults("/tmp")
897+ env := env.NewFromMap(map[string]string{})
898+ resolver := NewEnvironmentVariableResolver(env)
899+ err := cfg.configureProviders(env, resolver, knownProviders)
900+ assert.NoError(t, err)
901+ large, small, err := cfg.defaultModelSelection(knownProviders)
902+ assert.NoError(t, err)
903+ assert.Equal(t, "model", large.Model)
904+ assert.Equal(t, "custom", large.Provider)
905+ assert.Equal(t, int64(600), large.MaxTokens)
906+ assert.Equal(t, "model", small.Model)
907+ assert.Equal(t, "custom", small.Provider)
908+ assert.Equal(t, int64(600), small.MaxTokens)
911+ t.Run("should fail if no model configured", func(t *testing.T) {
912+ knownProviders := []provider.Provider{
915+ APIKey: "$MISSING", // will not be included in the config
916+ DefaultLargeModelID: "large-model",
917+ DefaultSmallModelID: "small-model",
918+ Models: []provider.Model{
920+ ID: "not-large-model",
921+ DefaultMaxTokens: 1000,
925+ DefaultMaxTokens: 500,
932+ Providers: map[string]ProviderConfig{
935+ BaseURL: "https://api.custom.com/v1",
936+ Models: []provider.Model{},
940+ cfg.setDefaults("/tmp")
941+ env := env.NewFromMap(map[string]string{})
942+ resolver := NewEnvironmentVariableResolver(env)
943+ err := cfg.configureProviders(env, resolver, knownProviders)
944+ assert.NoError(t, err)
945+ _, _, err = cfg.defaultModelSelection(knownProviders)
946+ assert.Error(t, err)
948+ t.Run("should use the default provider first", func(t *testing.T) {
949+ knownProviders := []provider.Provider{
953+ DefaultLargeModelID: "large-model",
954+ DefaultSmallModelID: "small-model",
955+ Models: []provider.Model{
958+ DefaultMaxTokens: 1000,
962+ DefaultMaxTokens: 500,
969+ Providers: map[string]ProviderConfig{
972+ BaseURL: "https://api.custom.com/v1",
973+ Models: []provider.Model{
976+ DefaultMaxTokens: 1000,
982+ cfg.setDefaults("/tmp")
983+ env := env.NewFromMap(map[string]string{})
984+ resolver := NewEnvironmentVariableResolver(env)
985+ err := cfg.configureProviders(env, resolver, knownProviders)
986+ assert.NoError(t, err)
987+ large, small, err := cfg.defaultModelSelection(knownProviders)
988+ assert.NoError(t, err)
989+ assert.Equal(t, "large-model", large.Model)
990+ assert.Equal(t, "openai", large.Provider)
991+ assert.Equal(t, int64(1000), large.MaxTokens)
992+ assert.Equal(t, "small-model", small.Model)
993+ assert.Equal(t, "openai", small.Provider)
994+ assert.Equal(t, int64(500), small.MaxTokens)
998+func TestConfig_configureSelectedModels(t *testing.T) {
999+ t.Run("should override defaults", func(t *testing.T) {
1000+ knownProviders := []provider.Provider{
1004+ DefaultLargeModelID: "large-model",
1005+ DefaultSmallModelID: "small-model",
1006+ Models: []provider.Model{
1008+ ID: "larger-model",
1009+ DefaultMaxTokens: 2000,
1013+ DefaultMaxTokens: 1000,
1017+ DefaultMaxTokens: 500,
1024+ Models: map[SelectedModelType]SelectedModel{
1026+ Model: "larger-model",
1030+ cfg.setDefaults("/tmp")
1031+ env := env.NewFromMap(map[string]string{})
1032+ resolver := NewEnvironmentVariableResolver(env)
1033+ err := cfg.configureProviders(env, resolver, knownProviders)
1034+ assert.NoError(t, err)
1036+ err = cfg.configureSelectedModels(knownProviders)
1037+ assert.NoError(t, err)
1038+ large := cfg.Models[SelectedModelTypeLarge]
1039+ small := cfg.Models[SelectedModelTypeSmall]
1040+ assert.Equal(t, "larger-model", large.Model)
1041+ assert.Equal(t, "openai", large.Provider)
1042+ assert.Equal(t, int64(2000), large.MaxTokens)
1043+ assert.Equal(t, "small-model", small.Model)
1044+ assert.Equal(t, "openai", small.Provider)
1045+ assert.Equal(t, int64(500), small.MaxTokens)
1047+ t.Run("should be possible to use multiple providers", func(t *testing.T) {
1048+ knownProviders := []provider.Provider{
1052+ DefaultLargeModelID: "large-model",
1053+ DefaultSmallModelID: "small-model",
1054+ Models: []provider.Model{
1057+ DefaultMaxTokens: 1000,
1061+ DefaultMaxTokens: 500,
1068+ DefaultLargeModelID: "a-large-model",
1069+ DefaultSmallModelID: "a-small-model",
1070+ Models: []provider.Model{
1072+ ID: "a-large-model",
1073+ DefaultMaxTokens: 1000,
1076+ ID: "a-small-model",
1077+ DefaultMaxTokens: 200,
1084+ Models: map[SelectedModelType]SelectedModel{
1086+ Model: "a-small-model",
1087+ Provider: "anthropic",
1092+ cfg.setDefaults("/tmp")
1093+ env := env.NewFromMap(map[string]string{})
1094+ resolver := NewEnvironmentVariableResolver(env)
1095+ err := cfg.configureProviders(env, resolver, knownProviders)
1096+ assert.NoError(t, err)
1098+ err = cfg.configureSelectedModels(knownProviders)
1099+ assert.NoError(t, err)
1100+ large := cfg.Models[SelectedModelTypeLarge]
1101+ small := cfg.Models[SelectedModelTypeSmall]
1102+ assert.Equal(t, "large-model", large.Model)
1103+ assert.Equal(t, "openai", large.Provider)
1104+ assert.Equal(t, int64(1000), large.MaxTokens)
1105+ assert.Equal(t, "a-small-model", small.Model)
1106+ assert.Equal(t, "anthropic", small.Provider)
1107+ assert.Equal(t, int64(300), small.MaxTokens)
1110+ t.Run("should override the max tokens only", func(t *testing.T) {
1111+ knownProviders := []provider.Provider{
1115+ DefaultLargeModelID: "large-model",
1116+ DefaultSmallModelID: "small-model",
1117+ Models: []provider.Model{
1120+ DefaultMaxTokens: 1000,
1124+ DefaultMaxTokens: 500,
1131+ Models: map[SelectedModelType]SelectedModel{
1137+ cfg.setDefaults("/tmp")
1138+ env := env.NewFromMap(map[string]string{})
1139+ resolver := NewEnvironmentVariableResolver(env)
1140+ err := cfg.configureProviders(env, resolver, knownProviders)
1141+ assert.NoError(t, err)
1143+ err = cfg.configureSelectedModels(knownProviders)
1144+ assert.NoError(t, err)
1145+ large := cfg.Models[SelectedModelTypeLarge]
1146+ assert.Equal(t, "large-model", large.Model)
1147+ assert.Equal(t, "openai", large.Provider)
1148+ assert.Equal(t, int64(100), large.MaxTokens)
internal/config/merge.gonew file+16
@@ -0,0 +1,16 @@
7+ "github.com/qjebbs/go-jsons"
10+func Merge(data []io.Reader) (io.Reader, error) {
11+ got, err := jsons.Merge(data)
15+ return bytes.NewReader(got), nil
internal/config/merge_test.gonew file+27
@@ -0,0 +1,27 @@
9+func TestMerge(t *testing.T) {
10+ data1 := strings.NewReader(`{"foo": "bar"}`)
11+ data2 := strings.NewReader(`{"baz": "qux"}`)
13+ merged, err := Merge([]io.Reader{data1, data2})
15+ t.Fatalf("expected no error, got %v", err)
18+ expected := `{"baz":"qux","foo":"bar"}`
19+ got, err := io.ReadAll(merged)
21+ t.Fatalf("expected no error reading merged data, got %v", err)
24+ if string(got) != expected {
25+ t.Errorf("expected %s, got %s", expected, string(got))
internal/config/provider.go+51−36
@@ -4,27 +4,44 @@ import (
4 "encoding/json"
4 "encoding/json"
6 "path/filepath"
6 "path/filepath"
9 "github.com/charmbracelet/crush/internal/fur/client"
10 "github.com/charmbracelet/crush/internal/fur/client"
10 "github.com/charmbracelet/crush/internal/fur/provider"
11 "github.com/charmbracelet/crush/internal/fur/provider"
13−var fur = client.New()
14+type ProviderClient interface {
15+ GetProviders() ([]provider.Provider, error)
16− providerOnc sync.Once // Ensures the initialization happens only once
19+ providerOnce sync.Once
17 providerList []provider.Provider
20 providerList []provider.Provider
18− // UseMockProviders can be set to true in tests to avoid API calls
19− UseMockProviders bool
22−func providersPath() string {
23+// file to cache provider data
23− return filepath.Join(baseDataPath(), "providers.json")
24+func providerCacheFileData() string {
25+ xdgDataHome := os.Getenv("XDG_DATA_HOME")
26+ if xdgDataHome != "" {
27+ return filepath.Join(xdgDataHome, appName, "providers.json")
30+ // return the path to the main data directory
31+ // for windows, it should be in `%LOCALAPPDATA%/crush/`
32+ // for linux and macOS, it should be in `$HOME/.local/share/crush/`
33+ if runtime.GOOS == "windows" {
34+ localAppData := os.Getenv("LOCALAPPDATA")
35+ if localAppData == "" {
36+ localAppData = filepath.Join(os.Getenv("USERPROFILE"), "AppData", "Local")
38+ return filepath.Join(localAppData, appName, "providers.json")
41+ return filepath.Join(os.Getenv("HOME"), ".local", "share", appName, "providers.json")
26−func saveProviders(providers []provider.Provider) error {
44+func saveProvidersInCache(path string, providers []provider.Provider) error {
27− path := providersPath()
28 dir := filepath.Dir(path)
45 dir := filepath.Dir(path)
29 if err := os.MkdirAll(dir, 0o755); err != nil {
46 if err := os.MkdirAll(dir, 0o755); err != nil {
30 return err
47 return err
@@ -38,8 +55,7 @@ func saveProviders(providers []provider.Provider) error {
38 return os.WriteFile(path, data, 0o644)
55 return os.WriteFile(path, data, 0o644)
41−func loadProviders() ([]provider.Provider, error) {
58+func loadProvidersFromCache(path string) ([]provider.Provider, error) {
42− path := providersPath()
43 data, err := os.ReadFile(path)
59 data, err := os.ReadFile(path)
44 if err != nil {
60 if err != nil {
45 return nil, err
61 return nil, err
@@ -50,34 +66,33 @@ func loadProviders() ([]provider.Provider, error) {
50 return providers, err
66 return providers, err
53−func Providers() []provider.Provider {
69+func loadProviders(path string, client ProviderClient) ([]provider.Provider, error) {
54− providerOnc.Do(func() {
70+ providers, err := client.GetProviders()
55− // Use mock providers when testing
71+ if err != nil {
56− if UseMockProviders {
72+ fallbackToCache, err := loadProvidersFromCache(path)
57− providerList = MockProviders()
73+ if err != nil {
58− return
74+ return nil, err
60−
76+ providers = fallbackToCache
61− // Try to get providers from upstream API
62− if providers, err := fur.GetProviders(); err == nil {
63− providerList = providers
64− // Save providers locally for future fallback
65− _ = saveProviders(providers)
67− // If upstream fails, try to load from local cache
78+ if err := saveProvidersInCache(path, providerList); err != nil {
68− if localProviders, localErr := loadProviders(); localErr == nil {
79+ return nil, err
69− providerList = localProviders
71− // If both fail, return empty list
72− providerList = []provider.Provider{}
76− return providerList
82+ return providers, nil
85+func Providers() ([]provider.Provider, error) {
86+ return LoadProviders(client.New())
79−// ResetProviders resets the provider cache. Useful for testing.
89+func LoadProviders(client ProviderClient) ([]provider.Provider, error) {
80−func ResetProviders() {
90+ var err error
81− providerOnc = sync.Once{}
91+ providerOnce.Do(func() {
82− providerList = nil
92+ providerList, err = loadProviders(providerCacheFileData(), client)
97+ return providerList, nil
internal/config/provider_mock.godeleted−293
@@ -1,293 +0,0 @@
4− "github.com/charmbracelet/crush/internal/fur/provider"
7−// MockProviders returns a mock list of providers for testing.
8−// This avoids making API calls during tests and provides consistent test data.
9−// Simplified version with only default models from each provider.
10−func MockProviders() []provider.Provider {
11− return []provider.Provider{
14− ID: provider.InferenceProviderAnthropic,
15− APIKey: "$ANTHROPIC_API_KEY",
16− APIEndpoint: "$ANTHROPIC_API_ENDPOINT",
17− Type: provider.TypeAnthropic,
18− DefaultLargeModelID: "claude-sonnet-4-20250514",
19− DefaultSmallModelID: "claude-3-5-haiku-20241022",
20− Models: []provider.Model{
22− ID: "claude-sonnet-4-20250514",
23− Name: "Claude Sonnet 4",
26− CostPer1MInCached: 3.75,
27− CostPer1MOutCached: 0.3,
28− ContextWindow: 200000,
29− DefaultMaxTokens: 50000,
31− SupportsImages: true,
34− ID: "claude-3-5-haiku-20241022",
35− Name: "Claude 3.5 Haiku",
38− CostPer1MInCached: 1.0,
39− CostPer1MOutCached: 0.08,
40− ContextWindow: 200000,
41− DefaultMaxTokens: 5000,
43− SupportsImages: true,
49− ID: provider.InferenceProviderOpenAI,
50− APIKey: "$OPENAI_API_KEY",
51− APIEndpoint: "$OPENAI_API_ENDPOINT",
52− Type: provider.TypeOpenAI,
53− DefaultLargeModelID: "codex-mini-latest",
54− DefaultSmallModelID: "gpt-4o",
55− Models: []provider.Model{
57− ID: "codex-mini-latest",
61− CostPer1MInCached: 0.0,
62− CostPer1MOutCached: 0.375,
63− ContextWindow: 200000,
64− DefaultMaxTokens: 50000,
66− HasReasoningEffort: true,
67− DefaultReasoningEffort: "medium",
68− SupportsImages: true,
75− CostPer1MInCached: 0.0,
76− CostPer1MOutCached: 1.25,
77− ContextWindow: 128000,
78− DefaultMaxTokens: 20000,
80− SupportsImages: true,
85− Name: "Google Gemini",
86− ID: provider.InferenceProviderGemini,
87− APIKey: "$GEMINI_API_KEY",
88− APIEndpoint: "$GEMINI_API_ENDPOINT",
89− Type: provider.TypeGemini,
90− DefaultLargeModelID: "gemini-2.5-pro",
91− DefaultSmallModelID: "gemini-2.5-flash",
92− Models: []provider.Model{
94− ID: "gemini-2.5-pro",
95− Name: "Gemini 2.5 Pro",
98− CostPer1MInCached: 1.625,
99− CostPer1MOutCached: 0.31,
100− ContextWindow: 1048576,
101− DefaultMaxTokens: 50000,
103− SupportsImages: true,
106− ID: "gemini-2.5-flash",
107− Name: "Gemini 2.5 Flash",
110− CostPer1MInCached: 0.3833,
111− CostPer1MOutCached: 0.075,
112− ContextWindow: 1048576,
113− DefaultMaxTokens: 50000,
115− SupportsImages: true,
121− ID: provider.InferenceProviderXAI,
122− APIKey: "$XAI_API_KEY",
123− APIEndpoint: "https://api.x.ai/v1",
124− Type: provider.TypeXAI,
125− DefaultLargeModelID: "grok-3",
126− DefaultSmallModelID: "grok-3-mini",
127− Models: []provider.Model{
133− CostPer1MInCached: 0.0,
134− CostPer1MOutCached: 0.75,
135− ContextWindow: 131072,
136− DefaultMaxTokens: 20000,
138− SupportsImages: false,
142− Name: "Grok 3 Mini",
145− CostPer1MInCached: 0.0,
146− CostPer1MOutCached: 0.075,
147− ContextWindow: 131072,
148− DefaultMaxTokens: 20000,
150− SupportsImages: false,
155− Name: "Azure OpenAI",
156− ID: provider.InferenceProviderAzure,
157− APIKey: "$AZURE_OPENAI_API_KEY",
158− APIEndpoint: "$AZURE_OPENAI_API_ENDPOINT",
159− Type: provider.TypeAzure,
160− DefaultLargeModelID: "o4-mini",
161− DefaultSmallModelID: "gpt-4o",
162− Models: []provider.Model{
168− CostPer1MInCached: 0.0,
169− CostPer1MOutCached: 0.275,
170− ContextWindow: 200000,
171− DefaultMaxTokens: 50000,
173− HasReasoningEffort: false,
174− DefaultReasoningEffort: "medium",
175− SupportsImages: true,
182− CostPer1MInCached: 0.0,
183− CostPer1MOutCached: 1.25,
184− ContextWindow: 128000,
185− DefaultMaxTokens: 20000,
187− SupportsImages: true,
192− Name: "AWS Bedrock",
193− ID: provider.InferenceProviderBedrock,
194− Type: provider.TypeBedrock,
195− DefaultLargeModelID: "anthropic.claude-sonnet-4-20250514-v1:0",
196− DefaultSmallModelID: "anthropic.claude-3-5-haiku-20241022-v1:0",
197− Models: []provider.Model{
199− ID: "anthropic.claude-sonnet-4-20250514-v1:0",
200− Name: "AWS Claude Sonnet 4",
203− CostPer1MInCached: 3.75,
204− CostPer1MOutCached: 0.3,
205− ContextWindow: 200000,
206− DefaultMaxTokens: 50000,
208− SupportsImages: true,
211− ID: "anthropic.claude-3-5-haiku-20241022-v1:0",
212− Name: "AWS Claude 3.5 Haiku",
215− CostPer1MInCached: 1.0,
216− CostPer1MOutCached: 0.08,
217− ContextWindow: 200000,
218− DefaultMaxTokens: 50000,
220− SupportsImages: true,
225− Name: "Google Vertex AI",
226− ID: provider.InferenceProviderVertexAI,
227− Type: provider.TypeVertexAI,
228− DefaultLargeModelID: "gemini-2.5-pro",
229− DefaultSmallModelID: "gemini-2.5-flash",
230− Models: []provider.Model{
232− ID: "gemini-2.5-pro",
233− Name: "Gemini 2.5 Pro",
236− CostPer1MInCached: 1.625,
237− CostPer1MOutCached: 0.31,
238− ContextWindow: 1048576,
239− DefaultMaxTokens: 50000,
241− SupportsImages: true,
244− ID: "gemini-2.5-flash",
245− Name: "Gemini 2.5 Flash",
248− CostPer1MInCached: 0.3833,
249− CostPer1MOutCached: 0.075,
250− ContextWindow: 1048576,
251− DefaultMaxTokens: 50000,
253− SupportsImages: true,
259− ID: provider.InferenceProviderOpenRouter,
260− APIKey: "$OPENROUTER_API_KEY",
261− APIEndpoint: "https://openrouter.ai/api/v1",
262− Type: provider.TypeOpenAI,
263− DefaultLargeModelID: "anthropic/claude-sonnet-4",
264− DefaultSmallModelID: "anthropic/claude-haiku-3.5",
265− Models: []provider.Model{
267− ID: "anthropic/claude-sonnet-4",
268− Name: "Anthropic: Claude Sonnet 4",
271− CostPer1MInCached: 3.75,
272− CostPer1MOutCached: 0.3,
273− ContextWindow: 200000,
274− DefaultMaxTokens: 32000,
276− SupportsImages: true,
279− ID: "anthropic/claude-haiku-3.5",
280− Name: "Anthropic: Claude 3.5 Haiku",
283− CostPer1MInCached: 1.0,
284− CostPer1MOutCached: 0.08,
285− ContextWindow: 200000,
286− DefaultMaxTokens: 4096,
288− SupportsImages: true,
internal/config/provider_test.go+52−60
@@ -1,81 +1,73 @@
1package config
1package config
6 "github.com/charmbracelet/crush/internal/fur/provider"
9 "github.com/charmbracelet/crush/internal/fur/provider"
7 "github.com/stretchr/testify/assert"
10 "github.com/stretchr/testify/assert"
8− "github.com/stretchr/testify/require"
11−func TestProviders_MockEnabled(t *testing.T) {
13+type mockProviderClient struct {
12− originalUseMock := UseMockProviders
14+ shouldFail bool
13− UseMockProviders = true
15− UseMockProviders = originalUseMock
20− providers := Providers()
21− require.NotEmpty(t, providers)
23− providerIDs := make(map[provider.InferenceProvider]bool)
17+func (m *mockProviderClient) GetProviders() ([]provider.Provider, error) {
24− for _, p := range providers {
18+ if m.shouldFail {
25− providerIDs[p.ID] = true
19+ return nil, errors.New("failed to load providers")
27−
21+ return []provider.Provider{
28− assert.True(t, providerIDs[provider.InferenceProviderAnthropic])
22+ {
29− assert.True(t, providerIDs[provider.InferenceProviderOpenAI])
23+ Name: "Mock",
30− assert.True(t, providerIDs[provider.InferenceProviderGemini])
24+ },
33−func TestProviders_ResetFunctionality(t *testing.T) {
28+func TestProvider_loadProvidersNoIssues(t *testing.T) {
34− UseMockProviders = true
29+ client := &mockProviderClient{shouldFail: false}
35− defer func() {
30+ tmpPath := t.TempDir() + "/providers.json"
36− UseMockProviders = false
31+ providers, err := loadProviders(tmpPath, client)
37− ResetProviders()
32+ assert.NoError(t, err)
38− }()
33+ assert.NotNil(t, providers)
39−
34+ assert.Len(t, providers, 1)
40− providers1 := Providers()
41− require.NotEmpty(t, providers1)
44− providers2 := Providers()
45− require.NotEmpty(t, providers2)
47− assert.Equal(t, len(providers1), len(providers2))
36+ // check if file got saved
37+ fileInfo, err := os.Stat(tmpPath)
38+ assert.NoError(t, err)
39+ assert.False(t, fileInfo.IsDir(), "Expected a file, not a directory")
50−func TestProviders_ModelCapabilities(t *testing.T) {
42+func TestProvider_loadProvidersWithIssues(t *testing.T) {
51− originalUseMock := UseMockProviders
43+ client := &mockProviderClient{shouldFail: true}
52− UseMockProviders = true
44+ tmpPath := t.TempDir() + "/providers.json"
53− defer func() {
45+ // store providers to a temporary file
54− UseMockProviders = originalUseMock
59− providers := Providers()
61− var openaiProvider provider.Provider
46+ oldProviders := []provider.Provider{
62− for _, p := range providers {
47+ {
63− if p.ID == provider.InferenceProviderOpenAI {
48+ Name: "OldProvider",
64− openaiProvider = p
49+ },
66− }
51+ data, err := json.Marshal(oldProviders)
53+ t.Fatalf("Failed to marshal old providers: %v", err)
68− require.NotEmpty(t, openaiProvider.ID)
70− var foundReasoning, foundNonReasoning bool
56+ err = os.WriteFile(tmpPath, data, 0o644)
71− for _, model := range openaiProvider.Models {
57+ if err != nil {
72− if model.CanReason && model.HasReasoningEffort {
58+ t.Fatalf("Failed to write old providers to file: %v", err)
73− foundReasoning = true
74− } else if !model.CanReason {
75− foundNonReasoning = true
60+ providers, err := loadProviders(tmpPath, client)
61+ assert.NoError(t, err)
62+ assert.NotNil(t, providers)
63+ assert.Len(t, providers, 1)
64+ assert.Equal(t, "OldProvider", providers[0].Name, "Expected to keep old provider when loading fails")
79− assert.True(t, foundReasoning)
67+func TestProvider_loadProvidersWithIssuesAndNoCache(t *testing.T) {
80− assert.True(t, foundNonReasoning)
68+ client := &mockProviderClient{shouldFail: true}
69+ tmpPath := t.TempDir() + "/providers.json"
70+ providers, err := loadProviders(tmpPath, client)
72+ assert.Nil(t, providers, "Expected nil providers when loading fails and no cache exists")
internal/config/resolve.gonew file+90
@@ -0,0 +1,90 @@
9+ "github.com/charmbracelet/crush/internal/env"
10+ "github.com/charmbracelet/crush/internal/shell"
13+type VariableResolver interface {
14+ ResolveValue(value string) (string, error)
17+type Shell interface {
18+ Exec(ctx context.Context, command string) (stdout, stderr string, err error)
21+type shellVariableResolver struct {
26+func NewShellVariableResolver(env env.Env) VariableResolver {
27+ return &shellVariableResolver{
29+ shell: shell.NewShell(
37+// ResolveValue is a method for resolving values, such as environment variables.
38+// it will expect strings that start with `$` to be resolved as environment variables or shell commands.
39+// if the string does not start with `$`, it will return the string as is.
40+func (r *shellVariableResolver) ResolveValue(value string) (string, error) {
41+ if !strings.HasPrefix(value, "$") {
45+ if strings.HasPrefix(value, "$(") && strings.HasSuffix(value, ")") {
46+ command := strings.TrimSuffix(strings.TrimPrefix(value, "$("), ")")
47+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
50+ stdout, _, err := r.shell.Exec(ctx, command)
52+ return "", fmt.Errorf("command execution failed: %w", err)
54+ return strings.TrimSpace(stdout), nil
57+ if strings.HasPrefix(value, "$") {
58+ varName := strings.TrimPrefix(value, "$")
59+ value = r.env.Get(varName)
61+ return "", fmt.Errorf("environment variable %q not set", varName)
65+ return "", fmt.Errorf("invalid value format: %s", value)
68+type environmentVariableResolver struct {
72+func NewEnvironmentVariableResolver(env env.Env) VariableResolver {
73+ return &environmentVariableResolver{
78+// ResolveValue resolves environment variables from the provided env.Env.
79+func (r *environmentVariableResolver) ResolveValue(value string) (string, error) {
80+ if !strings.HasPrefix(value, "$") {
84+ varName := strings.TrimPrefix(value, "$")
85+ resolvedValue := r.env.Get(varName)
86+ if resolvedValue == "" {
87+ return "", fmt.Errorf("environment variable %q not set", varName)
89+ return resolvedValue, nil
internal/config/resolve_test.gonew file+177
@@ -0,0 +1,177 @@
8+ "github.com/charmbracelet/crush/internal/env"
9+ "github.com/stretchr/testify/assert"
12+// mockShell implements the Shell interface for testing
13+type mockShell struct {
14+ execFunc func(ctx context.Context, command string) (stdout, stderr string, err error)
17+func (m *mockShell) Exec(ctx context.Context, command string) (stdout, stderr string, err error) {
18+ if m.execFunc != nil {
19+ return m.execFunc(ctx, command)
24+func TestShellVariableResolver_ResolveValue(t *testing.T) {
28+ envVars map[string]string
29+ shellFunc func(ctx context.Context, command string) (stdout, stderr string, err error)
34+ name: "non-variable string returns as-is",
35+ value: "plain-string",
36+ expected: "plain-string",
39+ name: "environment variable resolution",
41+ envVars: map[string]string{"HOME": "/home/user"},
42+ expected: "/home/user",
45+ name: "missing environment variable returns error",
46+ value: "$MISSING_VAR",
47+ envVars: map[string]string{},
51+ name: "shell command execution",
52+ value: "$(echo hello)",
53+ shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
54+ if command == "echo hello" {
55+ return "hello\n", "", nil
57+ return "", "", errors.New("unexpected command")
62+ name: "shell command with whitespace trimming",
63+ value: "$(echo ' spaced ')",
64+ shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
65+ if command == "echo ' spaced '" {
66+ return " spaced \n", "", nil
68+ return "", "", errors.New("unexpected command")
73+ name: "shell command execution error",
75+ shellFunc: func(ctx context.Context, command string) (stdout, stderr string, err error) {
76+ return "", "", errors.New("command failed")
81+ name: "invalid format returns error",
87+ for _, tt := range tests {
88+ t.Run(tt.name, func(t *testing.T) {
89+ testEnv := env.NewFromMap(tt.envVars)
90+ resolver := &shellVariableResolver{
91+ shell: &mockShell{execFunc: tt.shellFunc},
95+ result, err := resolver.ResolveValue(tt.value)
100+ assert.NoError(t, err)
101+ assert.Equal(t, tt.expected, result)
107+func TestEnvironmentVariableResolver_ResolveValue(t *testing.T) {
111+ envVars map[string]string
116+ name: "non-variable string returns as-is",
117+ value: "plain-string",
118+ expected: "plain-string",
121+ name: "environment variable resolution",
123+ envVars: map[string]string{"HOME": "/home/user"},
124+ expected: "/home/user",
127+ name: "environment variable with complex value",
129+ envVars: map[string]string{"PATH": "/usr/bin:/bin:/usr/local/bin"},
130+ expected: "/usr/bin:/bin:/usr/local/bin",
133+ name: "missing environment variable returns error",
134+ value: "$MISSING_VAR",
135+ envVars: map[string]string{},
139+ name: "empty environment variable returns error",
140+ value: "$EMPTY_VAR",
141+ envVars: map[string]string{"EMPTY_VAR": ""},
146+ for _, tt := range tests {
147+ t.Run(tt.name, func(t *testing.T) {
148+ testEnv := env.NewFromMap(tt.envVars)
149+ resolver := NewEnvironmentVariableResolver(testEnv)
151+ result, err := resolver.ResolveValue(tt.value)
154+ assert.Error(t, err)
156+ assert.NoError(t, err)
157+ assert.Equal(t, tt.expected, result)
163+func TestNewShellVariableResolver(t *testing.T) {
164+ testEnv := env.NewFromMap(map[string]string{"TEST": "value"})
165+ resolver := NewShellVariableResolver(testEnv)
167+ assert.NotNil(t, resolver)
168+ assert.Implements(t, (*VariableResolver)(nil), resolver)
171+func TestNewEnvironmentVariableResolver(t *testing.T) {
172+ testEnv := env.NewFromMap(map[string]string{"TEST": "value"})
173+ resolver := NewEnvironmentVariableResolver(testEnv)
175+ assert.NotNil(t, resolver)
176+ assert.Implements(t, (*VariableResolver)(nil), resolver)
internal/config/shell.godeleted−73
@@ -1,73 +0,0 @@
10− "github.com/charmbracelet/crush/internal/logging"
11− "github.com/charmbracelet/crush/internal/shell"
14−// ExecuteCommand executes a shell command and returns the output
15−// This is a shared utility that can be used by both provider config and tools
16−func ExecuteCommand(ctx context.Context, command string, workingDir string) (string, error) {
17− if workingDir == "" {
18− workingDir = WorkingDirectory()
21− persistentShell := shell.NewShell(&shell.Options{WorkingDir: workingDir})
23− stdout, stderr, err := persistentShell.Exec(ctx, command)
25− logging.Debug("Command execution failed", "command", command, "error", err, "stderr", stderr)
26− return "", fmt.Errorf("command execution failed: %w", err)
29− return strings.TrimSpace(stdout), nil
32−// ResolveAPIKey resolves an API key that can be either:
33−// - A direct string value
34−// - An environment variable (prefixed with $)
35−// - A shell command (wrapped in $(...))
36−func ResolveAPIKey(apiKey string) (string, error) {
37− if !strings.HasPrefix(apiKey, "$") {
41− if strings.HasPrefix(apiKey, "$(") && strings.HasSuffix(apiKey, ")") {
42− command := strings.TrimSuffix(strings.TrimPrefix(apiKey, "$("), ")")
43− logging.Debug("Resolving API key from command", "command", command)
44− return resolveCommandAPIKey(command)
47− envVar := strings.TrimPrefix(apiKey, "$")
48− if value := os.Getenv(envVar); value != "" {
49− logging.Debug("Resolved environment variable", "envVar", envVar, "value", value)
53− logging.Debug("Environment variable not found", "envVar", envVar)
55− return "", fmt.Errorf("environment variable %s not found", envVar)
58−// resolveCommandAPIKey executes a command to get an API key, with caching support
59−func resolveCommandAPIKey(command string) (string, error) {
60− ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
63− logging.Debug("Executing command for API key", "command", command)
65− workingDir := WorkingDirectory()
67− result, err := ExecuteCommand(ctx, command, workingDir)
69− return "", fmt.Errorf("failed to execute API key command: %w", err)
71− logging.Debug("Command executed successfully", "command", command, "result", result)
internal/config/validation_test.godeleted−462
@@ -1,462 +0,0 @@
6− "github.com/charmbracelet/crush/internal/fur/provider"
7− "github.com/stretchr/testify/assert"
8− "github.com/stretchr/testify/require"
11−func TestConfig_Validate_ValidConfig(t *testing.T) {
13− Models: PreferredModels{
14− Large: PreferredModel{
16− Provider: provider.InferenceProviderOpenAI,
18− Small: PreferredModel{
19− ModelID: "gpt-3.5-turbo",
20− Provider: provider.InferenceProviderOpenAI,
23− Providers: map[provider.InferenceProvider]ProviderConfig{
24− provider.InferenceProviderOpenAI: {
25− ID: provider.InferenceProviderOpenAI,
27− ProviderType: provider.TypeOpenAI,
28− DefaultLargeModel: "gpt-4",
29− DefaultSmallModel: "gpt-3.5-turbo",
35− DefaultMaxTokens: 4096,
41− Name: "GPT-3.5 Turbo",
43− DefaultMaxTokens: 2048,
50− Agents: map[AgentID]Agent{
54− Description: "An agent that helps with executing coding tasks.",
56− ContextPaths: []string{"CRUSH.md"},
61− Description: "An agent that helps with searching for context and finding implementation details.",
63− ContextPaths: []string{"CRUSH.md"},
64− AllowedTools: []string{"glob", "grep", "ls", "sourcegraph", "view"},
65− AllowedMCP: map[string][]string{},
66− AllowedLSP: []string{},
69− MCP: map[string]MCP{},
70− LSP: map[string]LSPConfig{},
72− DataDirectory: ".crush",
73− ContextPaths: []string{"CRUSH.md"},
77− err := cfg.Validate()
78− assert.NoError(t, err)
81−func TestConfig_Validate_MissingAPIKey(t *testing.T) {
83− Providers: map[provider.InferenceProvider]ProviderConfig{
84− provider.InferenceProviderOpenAI: {
85− ID: provider.InferenceProviderOpenAI,
86− ProviderType: provider.TypeOpenAI,
91− DataDirectory: ".crush",
92− ContextPaths: []string{"CRUSH.md"},
96− err := cfg.Validate()
97− require.Error(t, err)
98− assert.Contains(t, err.Error(), "API key is required")
101−func TestConfig_Validate_InvalidProviderType(t *testing.T) {
103− Providers: map[provider.InferenceProvider]ProviderConfig{
104− provider.InferenceProviderOpenAI: {
105− ID: provider.InferenceProviderOpenAI,
107− ProviderType: provider.Type("invalid"),
111− DataDirectory: ".crush",
112− ContextPaths: []string{"CRUSH.md"},
116− err := cfg.Validate()
117− require.Error(t, err)
118− assert.Contains(t, err.Error(), "invalid provider type")
121−func TestConfig_Validate_CustomProviderMissingBaseURL(t *testing.T) {
122− customProvider := provider.InferenceProvider("custom-provider")
124− Providers: map[provider.InferenceProvider]ProviderConfig{
128− ProviderType: provider.TypeOpenAI,
129− // Missing BaseURL for custom provider
133− DataDirectory: ".crush",
134− ContextPaths: []string{"CRUSH.md"},
138− err := cfg.Validate()
139− require.Error(t, err)
140− assert.Contains(t, err.Error(), "BaseURL is required for custom providers")
143−func TestConfig_Validate_DuplicateModelIDs(t *testing.T) {
145− Providers: map[provider.InferenceProvider]ProviderConfig{
146− provider.InferenceProviderOpenAI: {
147− ID: provider.InferenceProviderOpenAI,
149− ProviderType: provider.TypeOpenAI,
154− ContextWindow: 8192,
155− DefaultMaxTokens: 4096,
158− ID: "gpt-4", // Duplicate ID
159− Name: "GPT-4 Duplicate",
160− ContextWindow: 8192,
161− DefaultMaxTokens: 4096,
167− DataDirectory: ".crush",
168− ContextPaths: []string{"CRUSH.md"},
172− err := cfg.Validate()
173− require.Error(t, err)
174− assert.Contains(t, err.Error(), "duplicate model ID")
177−func TestConfig_Validate_InvalidModelFields(t *testing.T) {
179− Providers: map[provider.InferenceProvider]ProviderConfig{
180− provider.InferenceProviderOpenAI: {
181− ID: provider.InferenceProviderOpenAI,
183− ProviderType: provider.TypeOpenAI,
188− ContextWindow: 0, // Invalid context window
189− DefaultMaxTokens: -1, // Invalid max tokens
190− CostPer1MIn: -5.0, // Negative cost
196− DataDirectory: ".crush",
197− ContextPaths: []string{"CRUSH.md"},
201− err := cfg.Validate()
202− require.Error(t, err)
203− validationErr := err.(ValidationErrors)
204− assert.True(t, len(validationErr) >= 4) // Should have multiple validation errors
207−func TestConfig_Validate_DefaultModelNotFound(t *testing.T) {
209− Providers: map[provider.InferenceProvider]ProviderConfig{
210− provider.InferenceProviderOpenAI: {
211− ID: provider.InferenceProviderOpenAI,
213− ProviderType: provider.TypeOpenAI,
214− DefaultLargeModel: "nonexistent-model",
219− ContextWindow: 8192,
220− DefaultMaxTokens: 4096,
226− DataDirectory: ".crush",
227− ContextPaths: []string{"CRUSH.md"},
231− err := cfg.Validate()
232− require.Error(t, err)
233− assert.Contains(t, err.Error(), "default large model 'nonexistent-model' not found")
236−func TestConfig_Validate_AgentIDMismatch(t *testing.T) {
238− Agents: map[AgentID]Agent{
240− ID: AgentTask, // Wrong ID
245− DataDirectory: ".crush",
246− ContextPaths: []string{"CRUSH.md"},
250− err := cfg.Validate()
251− require.Error(t, err)
252− assert.Contains(t, err.Error(), "agent ID mismatch")
255−func TestConfig_Validate_InvalidAgentModelType(t *testing.T) {
257− Agents: map[AgentID]Agent{
261− Model: ModelType("invalid"),
265− DataDirectory: ".crush",
266− ContextPaths: []string{"CRUSH.md"},
270− err := cfg.Validate()
271− require.Error(t, err)
272− assert.Contains(t, err.Error(), "invalid model type")
275−func TestConfig_Validate_UnknownTool(t *testing.T) {
277− Agents: map[AgentID]Agent{
278− AgentID("custom-agent"): {
279− ID: AgentID("custom-agent"),
280− Name: "Custom Agent",
282− AllowedTools: []string{"unknown-tool"},
286− DataDirectory: ".crush",
287− ContextPaths: []string{"CRUSH.md"},
291− err := cfg.Validate()
292− require.Error(t, err)
293− assert.Contains(t, err.Error(), "unknown tool")
296−func TestConfig_Validate_MCPReference(t *testing.T) {
298− Agents: map[AgentID]Agent{
299− AgentID("custom-agent"): {
300− ID: AgentID("custom-agent"),
301− Name: "Custom Agent",
303− AllowedMCP: map[string][]string{"nonexistent-mcp": nil},
306− MCP: map[string]MCP{}, // Empty MCP map
308− DataDirectory: ".crush",
309− ContextPaths: []string{"CRUSH.md"},
313− err := cfg.Validate()
314− require.Error(t, err)
315− assert.Contains(t, err.Error(), "referenced MCP 'nonexistent-mcp' not found")
318−func TestConfig_Validate_InvalidMCPType(t *testing.T) {
320− MCP: map[string]MCP{
322− Type: MCPType("invalid"),
326− DataDirectory: ".crush",
327− ContextPaths: []string{"CRUSH.md"},
331− err := cfg.Validate()
332− require.Error(t, err)
333− assert.Contains(t, err.Error(), "invalid MCP type")
336−func TestConfig_Validate_MCPMissingCommand(t *testing.T) {
338− MCP: map[string]MCP{
345− DataDirectory: ".crush",
346− ContextPaths: []string{"CRUSH.md"},
350− err := cfg.Validate()
351− require.Error(t, err)
352− assert.Contains(t, err.Error(), "command is required for stdio MCP")
355−func TestConfig_Validate_LSPMissingCommand(t *testing.T) {
357− LSP: map[string]LSPConfig{
363− DataDirectory: ".crush",
364− ContextPaths: []string{"CRUSH.md"},
368− err := cfg.Validate()
369− require.Error(t, err)
370− assert.Contains(t, err.Error(), "command is required for LSP")
373−func TestConfig_Validate_NoValidProviders(t *testing.T) {
375− Providers: map[provider.InferenceProvider]ProviderConfig{
376− provider.InferenceProviderOpenAI: {
377− ID: provider.InferenceProviderOpenAI,
379− ProviderType: provider.TypeOpenAI,
380− Disabled: true, // Disabled
384− DataDirectory: ".crush",
385− ContextPaths: []string{"CRUSH.md"},
389− err := cfg.Validate()
390− require.Error(t, err)
391− assert.Contains(t, err.Error(), "at least one non-disabled provider is required")
394−func TestConfig_Validate_MissingDefaultAgents(t *testing.T) {
396− Providers: map[provider.InferenceProvider]ProviderConfig{
397− provider.InferenceProviderOpenAI: {
398− ID: provider.InferenceProviderOpenAI,
400− ProviderType: provider.TypeOpenAI,
403− Agents: map[AgentID]Agent{}, // Missing default agents
405− DataDirectory: ".crush",
406− ContextPaths: []string{"CRUSH.md"},
410− err := cfg.Validate()
411− require.Error(t, err)
412− assert.Contains(t, err.Error(), "coder agent is required")
413− assert.Contains(t, err.Error(), "task agent is required")
416−func TestConfig_Validate_KnownAgentProtection(t *testing.T) {
418− Agents: map[AgentID]Agent{
421− Name: "Modified Coder", // Should not be allowed
422− Description: "Modified description", // Should not be allowed
427− DataDirectory: ".crush",
428− ContextPaths: []string{"CRUSH.md"},
432− err := cfg.Validate()
433− require.Error(t, err)
434− assert.Contains(t, err.Error(), "coder agent name cannot be changed")
435− assert.Contains(t, err.Error(), "coder agent description cannot be changed")
438−func TestConfig_Validate_EmptyDataDirectory(t *testing.T) {
441− DataDirectory: "", // Empty
442− ContextPaths: []string{"CRUSH.md"},
446− err := cfg.Validate()
447− require.Error(t, err)
448− assert.Contains(t, err.Error(), "data directory is required")
451−func TestConfig_Validate_EmptyContextPath(t *testing.T) {
454− DataDirectory: ".crush",
455− ContextPaths: []string{""}, // Empty context path
459− err := cfg.Validate()
460− require.Error(t, err)
461− assert.Contains(t, err.Error(), "context path cannot be empty")
internal/db/connect.go+6−9
@@ -4,20 +4,17 @@ import (
5 "database/sql"
5 "database/sql"
8 "path/filepath"
9 "path/filepath"
10 _ "github.com/ncruces/go-sqlite3/driver"
11 _ "github.com/ncruces/go-sqlite3/driver"
11 _ "github.com/ncruces/go-sqlite3/embed"
12 _ "github.com/ncruces/go-sqlite3/embed"
13− "github.com/charmbracelet/crush/internal/config"
14− "github.com/charmbracelet/crush/internal/logging"
16 "github.com/pressly/goose/v3"
14 "github.com/pressly/goose/v3"
19−func Connect(ctx context.Context) (*sql.DB, error) {
17+func Connect(ctx context.Context, dataDir string) (*sql.DB, error) {
20− dataDir := config.Get().Options.DataDirectory
21 if dataDir == "" {
18 if dataDir == "" {
22 return nil, fmt.Errorf("data.dir is not set")
19 return nil, fmt.Errorf("data.dir is not set")
@@ -48,21 +45,21 @@ func Connect(ctx context.Context) (*sql.DB, error) {
49 for _, pragma := range pragmas {
46 for _, pragma := range pragmas {
50 if _, err = db.ExecContext(ctx, pragma); err != nil {
47 if _, err = db.ExecContext(ctx, pragma); err != nil {
51− logging.Error("Failed to set pragma", pragma, err)
48+ slog.Error("Failed to set pragma", pragma, err)
53− logging.Debug("Set pragma", "pragma", pragma)
50+ slog.Debug("Set pragma", "pragma", pragma)
57 goose.SetBaseFS(FS)
54 goose.SetBaseFS(FS)
59 if err := goose.SetDialect("sqlite3"); err != nil {
56 if err := goose.SetDialect("sqlite3"); err != nil {
60− logging.Error("Failed to set dialect", "error", err)
57+ slog.Error("Failed to set dialect", "error", err)
61 return nil, fmt.Errorf("failed to set dialect: %w", err)
58 return nil, fmt.Errorf("failed to set dialect: %w", err)
64 if err := goose.Up(db, "migrations"); err != nil {
61 if err := goose.Up(db, "migrations"); err != nil {
65− logging.Error("Failed to apply migrations", "error", err)
62+ slog.Error("Failed to apply migrations", "error", err)
66 return nil, fmt.Errorf("failed to apply migrations: %w", err)
63 return nil, fmt.Errorf("failed to apply migrations: %w", err)
68 return db, nil
65 return db, nil
@@ -4,15 +4,10 @@ import (
6 "github.com/aymanbagabas/go-udiff"
6 "github.com/aymanbagabas/go-udiff"
7− "github.com/charmbracelet/crush/internal/config"
10// GenerateDiff creates a unified diff from two file contents
9// GenerateDiff creates a unified diff from two file contents
11func GenerateDiff(beforeContent, afterContent, fileName string) (string, int, int) {
10func GenerateDiff(beforeContent, afterContent, fileName string) (string, int, int) {
12− // remove the cwd prefix and ensure consistent path format
13− // this prevents issues with absolute paths in different environments
14− cwd := config.WorkingDirectory()
15− fileName = strings.TrimPrefix(fileName, cwd)
16 fileName = strings.TrimPrefix(fileName, "/")
11 fileName = strings.TrimPrefix(fileName, "/")
internal/env/env.gonew file+58
@@ -0,0 +1,58 @@
6+ Get(key string) string
12+// Get implements Env.
13+func (o *osEnv) Get(key string) string {
14+ return os.Getenv(key)
17+func (o *osEnv) Env() []string {
33+// Get implements Env.
34+func (m *mapEnv) Get(key string) string {
35+ if value, ok := m.m[key]; ok {
41+// Env implements Env.
42+func (m *mapEnv) Env() []string {
46+ env := make([]string, 0, len(m.m))
47+ for k, v := range m.m {
48+ env = append(env, k+"="+v)
53+func NewFromMap(m map[string]string) Env {
55+ m = make(map[string]string)
internal/env/env_test.gonew file+142
@@ -0,0 +1,142 @@
8+ "github.com/stretchr/testify/assert"
11+func TestOsEnv_Get(t *testing.T) {
14+ // Test getting an existing environment variable
15+ os.Setenv("TEST_VAR", "test_value")
16+ defer os.Unsetenv("TEST_VAR")
18+ value := env.Get("TEST_VAR")
19+ assert.Equal(t, "test_value", value)
21+ // Test getting a non-existent environment variable
22+ value = env.Get("NON_EXISTENT_VAR")
23+ assert.Equal(t, "", value)
26+func TestOsEnv_Env(t *testing.T) {
31+ // Environment should not be empty in normal circumstances
32+ assert.NotNil(t, envVars)
33+ assert.Greater(t, len(envVars), 0)
35+ // Each environment variable should be in key=value format
36+ for _, envVar := range envVars {
37+ assert.Contains(t, envVar, "=")
41+func TestNewFromMap(t *testing.T) {
42+ testMap := map[string]string{
47+ env := NewFromMap(testMap)
48+ assert.NotNil(t, env)
49+ assert.IsType(t, &mapEnv{}, env)
52+func TestMapEnv_Get(t *testing.T) {
53+ testMap := map[string]string{
58+ env := NewFromMap(testMap)
60+ // Test getting existing keys
61+ assert.Equal(t, "value1", env.Get("KEY1"))
62+ assert.Equal(t, "value2", env.Get("KEY2"))
64+ // Test getting non-existent key
65+ assert.Equal(t, "", env.Get("NON_EXISTENT"))
68+func TestMapEnv_Env(t *testing.T) {
69+ t.Run("with values", func(t *testing.T) {
70+ testMap := map[string]string{
75+ env := NewFromMap(testMap)
78+ assert.Len(t, envVars, 2)
80+ // Convert to map for easier testing (order is not guaranteed)
81+ envMap := make(map[string]string)
82+ for _, envVar := range envVars {
83+ parts := strings.SplitN(envVar, "=", 2)
84+ assert.Len(t, parts, 2)
85+ envMap[parts[0]] = parts[1]
88+ assert.Equal(t, "value1", envMap["KEY1"])
89+ assert.Equal(t, "value2", envMap["KEY2"])
92+ t.Run("empty map", func(t *testing.T) {
93+ env := NewFromMap(map[string]string{})
95+ assert.Nil(t, envVars)
98+ t.Run("nil map", func(t *testing.T) {
99+ env := NewFromMap(nil)
100+ envVars := env.Env()
101+ assert.Nil(t, envVars)
105+func TestMapEnv_GetEmptyValue(t *testing.T) {
106+ testMap := map[string]string{
108+ "NORMAL_KEY": "value",
111+ env := NewFromMap(testMap)
113+ // Test that empty values are returned correctly
114+ assert.Equal(t, "", env.Get("EMPTY_KEY"))
115+ assert.Equal(t, "value", env.Get("NORMAL_KEY"))
118+func TestMapEnv_EnvFormat(t *testing.T) {
119+ testMap := map[string]string{
120+ "KEY_WITH_EQUALS": "value=with=equals",
121+ "KEY_WITH_SPACES": "value with spaces",
124+ env := NewFromMap(testMap)
125+ envVars := env.Env()
127+ assert.Len(t, envVars, 2)
129+ // Check that the format is correct even with special characters
130+ found := make(map[string]bool)
131+ for _, envVar := range envVars {
132+ if envVar == "KEY_WITH_EQUALS=value=with=equals" {
133+ found["equals"] = true
135+ if envVar == "KEY_WITH_SPACES=value with spaces" {
136+ found["spaces"] = true
140+ assert.True(t, found["equals"], "Should handle values with equals signs")
141+ assert.True(t, found["spaces"], "Should handle values with spaces")
internal/fsext/fileutil.go+4−3
@@ -2,6 +2,7 @@ package fsext
7 "path/filepath"
8 "path/filepath"
@@ -11,7 +12,7 @@ import (
12 "github.com/bmatcuk/doublestar/v4"
13 "github.com/bmatcuk/doublestar/v4"
13 "github.com/charlievieth/fastwalk"
14 "github.com/charlievieth/fastwalk"
14− "github.com/charmbracelet/crush/internal/logging"
15+
15 ignore "github.com/sabhiram/go-gitignore"
16 ignore "github.com/sabhiram/go-gitignore"
@@ -24,11 +25,11 @@ func init() {
24 var err error
25 var err error
25 rgPath, err = exec.LookPath("rg")
26 rgPath, err = exec.LookPath("rg")
26 if err != nil {
27 if err != nil {
27− logging.Warn("Ripgrep (rg) not found in $PATH. Some features might be limited or slower.")
28+ slog.Warn("Ripgrep (rg) not found in $PATH. Some features might be limited or slower.")
29 fzfPath, err = exec.LookPath("fzf")
30 fzfPath, err = exec.LookPath("fzf")
30 if err != nil {
31 if err != nil {
31− logging.Warn("FZF not found in $PATH. Some features might be limited or slower.")
32+ slog.Warn("FZF not found in $PATH. Some features might be limited or slower.")
internal/fur/provider/provider.go+1−1
@@ -45,7 +45,7 @@ type Provider struct {
45// Model represents an AI model configuration.
45// Model represents an AI model configuration.
46type Model struct {
46type Model struct {
47 ID string `json:"id"`
47 ID string `json:"id"`
48− Name string `json:"model"`
48+ Model string `json:"model"`
49 CostPer1MIn float64 `json:"cost_per_1m_in"`
49 CostPer1MIn float64 `json:"cost_per_1m_in"`
50 CostPer1MOut float64 `json:"cost_per_1m_out"`
50 CostPer1MOut float64 `json:"cost_per_1m_out"`
51 CostPer1MInCached float64 `json:"cost_per_1m_in_cached"`
51 CostPer1MInCached float64 `json:"cost_per_1m_in_cached"`
internal/llm/agent/agent.go+60−78
@@ -4,17 +4,19 @@ import (
12 "github.com/charmbracelet/crush/internal/config"
13 "github.com/charmbracelet/crush/internal/config"
14+ fur "github.com/charmbracelet/crush/internal/fur/provider"
13 "github.com/charmbracelet/crush/internal/history"
15 "github.com/charmbracelet/crush/internal/history"
14 "github.com/charmbracelet/crush/internal/llm/prompt"
16 "github.com/charmbracelet/crush/internal/llm/prompt"
15 "github.com/charmbracelet/crush/internal/llm/provider"
17 "github.com/charmbracelet/crush/internal/llm/provider"
16 "github.com/charmbracelet/crush/internal/llm/tools"
18 "github.com/charmbracelet/crush/internal/llm/tools"
17− "github.com/charmbracelet/crush/internal/logging"
19+ "github.com/charmbracelet/crush/internal/log"
18 "github.com/charmbracelet/crush/internal/lsp"
20 "github.com/charmbracelet/crush/internal/lsp"
19 "github.com/charmbracelet/crush/internal/message"
21 "github.com/charmbracelet/crush/internal/message"
20 "github.com/charmbracelet/crush/internal/permission"
22 "github.com/charmbracelet/crush/internal/permission"
@@ -49,7 +51,7 @@ type AgentEvent struct {
50type Service interface {
52type Service interface {
51 pubsub.Suscriber[AgentEvent]
53 pubsub.Suscriber[AgentEvent]
52− Model() config.Model
54+ Model() fur.Model
53 Run(ctx context.Context, sessionID string, content string, attachments ...message.Attachment) (<-chan AgentEvent, error)
55 Run(ctx context.Context, sessionID string, content string, attachments ...message.Attachment) (<-chan AgentEvent, error)
54 Cancel(sessionID string)
56 Cancel(sessionID string)
55 CancelAll()
57 CancelAll()
@@ -76,9 +78,9 @@ type agent struct {
76 activeRequests sync.Map
78 activeRequests sync.Map
79−var agentPromptMap = map[config.AgentID]prompt.PromptID{
81+var agentPromptMap = map[string]prompt.PromptID{
80− config.AgentCoder: prompt.PromptCoder,
82+ "coder": prompt.PromptCoder,
81− config.AgentTask: prompt.PromptTask,
83+ "task": prompt.PromptTask,
84func NewAgent(
86func NewAgent(
@@ -92,25 +94,26 @@ func NewAgent(
92) (Service, error) {
94) (Service, error) {
93 ctx := context.Background()
95 ctx := context.Background()
94 cfg := config.Get()
96 cfg := config.Get()
95− otherTools := GetMcpTools(ctx, permissions)
97+ otherTools := GetMcpTools(ctx, permissions, cfg)
96 if len(lspClients) > 0 {
98 if len(lspClients) > 0 {
97 otherTools = append(otherTools, tools.NewDiagnosticsTool(lspClients))
99 otherTools = append(otherTools, tools.NewDiagnosticsTool(lspClients))
102+ cwd := cfg.WorkingDir()
100 allTools := []tools.BaseTool{
103 allTools := []tools.BaseTool{
101− tools.NewBashTool(permissions),
104+ tools.NewBashTool(permissions, cwd),
102− tools.NewEditTool(lspClients, permissions, history),
105+ tools.NewEditTool(lspClients, permissions, history, cwd),
103− tools.NewFetchTool(permissions),
106+ tools.NewFetchTool(permissions, cwd),
104− tools.NewGlobTool(),
107+ tools.NewGlobTool(cwd),
105− tools.NewGrepTool(),
108+ tools.NewGrepTool(cwd),
106− tools.NewLsTool(),
109+ tools.NewLsTool(cwd),
107 tools.NewSourcegraphTool(),
110 tools.NewSourcegraphTool(),
108− tools.NewViewTool(lspClients),
111+ tools.NewViewTool(lspClients, cwd),
109− tools.NewWriteTool(lspClients, permissions, history),
112+ tools.NewWriteTool(lspClients, permissions, history, cwd),
112− if agentCfg.ID == config.AgentCoder {
115+ if agentCfg.ID == "coder" {
113− taskAgentCfg := config.Get().Agents[config.AgentTask]
116+ taskAgentCfg := config.Get().Agents["task"]
114 if taskAgentCfg.ID == "" {
117 if taskAgentCfg.ID == "" {
115 return nil, fmt.Errorf("task agent not found in config")
118 return nil, fmt.Errorf("task agent not found in config")
@@ -130,13 +133,13 @@ func NewAgent(
132 allTools = append(allTools, otherTools...)
135 allTools = append(allTools, otherTools...)
133− providerCfg := config.GetAgentProvider(agentCfg.ID)
136+ providerCfg := config.Get().GetProviderForModel(agentCfg.Model)
134− if providerCfg.ID == "" {
137+ if providerCfg == nil {
135 return nil, fmt.Errorf("provider for agent %s not found in config", agentCfg.Name)
138 return nil, fmt.Errorf("provider for agent %s not found in config", agentCfg.Name)
137− model := config.GetAgentModel(agentCfg.ID)
140+ model := config.Get().GetModelByType(agentCfg.Model)
139− if model.ID == "" {
142+ if model == nil {
140 return nil, fmt.Errorf("model not found for agent %s", agentCfg.Name)
143 return nil, fmt.Errorf("model not found for agent %s", agentCfg.Name)
@@ -148,51 +151,40 @@ func NewAgent(
148 provider.WithModel(agentCfg.Model),
151 provider.WithModel(agentCfg.Model),
149 provider.WithSystemMessage(prompt.GetPrompt(promptID, providerCfg.ID)),
152 provider.WithSystemMessage(prompt.GetPrompt(promptID, providerCfg.ID)),
151− agentProvider, err := provider.NewProvider(providerCfg, opts...)
154+ agentProvider, err := provider.NewProvider(*providerCfg, opts...)
152 if err != nil {
155 if err != nil {
153 return nil, err
156 return nil, err
156− smallModelCfg := cfg.Models.Small
159+ smallModelCfg := cfg.Models[config.SelectedModelTypeSmall]
157− var smallModel config.Model
159− var smallModelProviderCfg config.ProviderConfig
160+ var smallModelProviderCfg *config.ProviderConfig
160 if smallModelCfg.Provider == providerCfg.ID {
161 if smallModelCfg.Provider == providerCfg.ID {
161 smallModelProviderCfg = providerCfg
162 smallModelProviderCfg = providerCfg
163− for _, p := range cfg.Providers {
164− if p.ID == smallModelCfg.Provider {
165− smallModelProviderCfg = p
164+ smallModelProviderCfg = cfg.GetProviderForModel(config.SelectedModelTypeSmall)
169 if smallModelProviderCfg.ID == "" {
166 if smallModelProviderCfg.ID == "" {
170 return nil, fmt.Errorf("provider %s not found in config", smallModelCfg.Provider)
167 return nil, fmt.Errorf("provider %s not found in config", smallModelCfg.Provider)
173− for _, m := range smallModelProviderCfg.Models {
170+ smallModel := cfg.GetModelByType(config.SelectedModelTypeSmall)
174− if m.ID == smallModelCfg.ModelID {
179 if smallModel.ID == "" {
171 if smallModel.ID == "" {
180− return nil, fmt.Errorf("model %s not found in provider %s", smallModelCfg.ModelID, smallModelProviderCfg.ID)
172+ return nil, fmt.Errorf("model %s not found in provider %s", smallModelCfg.Model, smallModelProviderCfg.ID)
183 titleOpts := []provider.ProviderClientOption{
175 titleOpts := []provider.ProviderClientOption{
184− provider.WithModel(config.SmallModel),
176+ provider.WithModel(config.SelectedModelTypeSmall),
185 provider.WithSystemMessage(prompt.GetPrompt(prompt.PromptTitle, smallModelProviderCfg.ID)),
177 provider.WithSystemMessage(prompt.GetPrompt(prompt.PromptTitle, smallModelProviderCfg.ID)),
187− titleProvider, err := provider.NewProvider(smallModelProviderCfg, titleOpts...)
179+ titleProvider, err := provider.NewProvider(*smallModelProviderCfg, titleOpts...)
188 if err != nil {
180 if err != nil {
189 return nil, err
181 return nil, err
191 summarizeOpts := []provider.ProviderClientOption{
183 summarizeOpts := []provider.ProviderClientOption{
192− provider.WithModel(config.SmallModel),
184+ provider.WithModel(config.SelectedModelTypeSmall),
193 provider.WithSystemMessage(prompt.GetPrompt(prompt.PromptSummarizer, smallModelProviderCfg.ID)),
185 provider.WithSystemMessage(prompt.GetPrompt(prompt.PromptSummarizer, smallModelProviderCfg.ID)),
195− summarizeProvider, err := provider.NewProvider(smallModelProviderCfg, summarizeOpts...)
187+ summarizeProvider, err := provider.NewProvider(*smallModelProviderCfg, summarizeOpts...)
196 if err != nil {
188 if err != nil {
197 return nil, err
189 return nil, err
@@ -225,15 +217,15 @@ func NewAgent(
225 return agent, nil
217 return agent, nil
228−func (a *agent) Model() config.Model {
220+func (a *agent) Model() fur.Model {
229− return config.GetAgentModel(a.agentCfg.ID)
221+ return *config.Get().GetModelByType(a.agentCfg.Model)
232func (a *agent) Cancel(sessionID string) {
224func (a *agent) Cancel(sessionID string) {
233 // Cancel regular requests
225 // Cancel regular requests
234 if cancelFunc, exists := a.activeRequests.LoadAndDelete(sessionID); exists {
226 if cancelFunc, exists := a.activeRequests.LoadAndDelete(sessionID); exists {
235 if cancel, ok := cancelFunc.(context.CancelFunc); ok {
227 if cancel, ok := cancelFunc.(context.CancelFunc); ok {
236− logging.InfoPersist(fmt.Sprintf("Request cancellation initiated for session: %s", sessionID))
228+ slog.Info(fmt.Sprintf("Request cancellation initiated for session: %s", sessionID))
@@ -241,7 +233,7 @@ func (a *agent) Cancel(sessionID string) {
241 // Also check for summarize requests
233 // Also check for summarize requests
242 if cancelFunc, exists := a.activeRequests.LoadAndDelete(sessionID + "-summarize"); exists {
234 if cancelFunc, exists := a.activeRequests.LoadAndDelete(sessionID + "-summarize"); exists {
243 if cancel, ok := cancelFunc.(context.CancelFunc); ok {
235 if cancel, ok := cancelFunc.(context.CancelFunc); ok {
244− logging.InfoPersist(fmt.Sprintf("Summarize cancellation initiated for session: %s", sessionID))
236+ slog.Info(fmt.Sprintf("Summarize cancellation initiated for session: %s", sessionID))
@@ -335,8 +327,8 @@ func (a *agent) Run(ctx context.Context, sessionID string, content string, attac
336 a.activeRequests.Store(sessionID, cancel)
328 a.activeRequests.Store(sessionID, cancel)
337 go func() {
329 go func() {
338− logging.Debug("Request started", "sessionID", sessionID)
330+ slog.Debug("Request started", "sessionID", sessionID)
339− defer logging.RecoverPanic("agent.Run", func() {
331+ defer log.RecoverPanic("agent.Run", func() {
340 events <- a.err(fmt.Errorf("panic while running the agent"))
332 events <- a.err(fmt.Errorf("panic while running the agent"))
342 var attachmentParts []message.ContentPart
334 var attachmentParts []message.ContentPart
@@ -345,9 +337,9 @@ func (a *agent) Run(ctx context.Context, sessionID string, content string, attac
346 result := a.processGeneration(genCtx, sessionID, content, attachmentParts)
338 result := a.processGeneration(genCtx, sessionID, content, attachmentParts)
347 if result.Error != nil && !errors.Is(result.Error, ErrRequestCancelled) && !errors.Is(result.Error, context.Canceled) {
339 if result.Error != nil && !errors.Is(result.Error, ErrRequestCancelled) && !errors.Is(result.Error, context.Canceled) {
348− logging.ErrorPersist(result.Error.Error())
340+ slog.Error(result.Error.Error())
350− logging.Debug("Request completed", "sessionID", sessionID)
342+ slog.Debug("Request completed", "sessionID", sessionID)
351 a.activeRequests.Delete(sessionID)
343 a.activeRequests.Delete(sessionID)
353 a.Publish(pubsub.CreatedEvent, result)
345 a.Publish(pubsub.CreatedEvent, result)
@@ -366,12 +358,12 @@ func (a *agent) processGeneration(ctx context.Context, sessionID, content string
367 if len(msgs) == 0 {
359 if len(msgs) == 0 {
368 go func() {
360 go func() {
369− defer logging.RecoverPanic("agent.Run", func() {
361+ defer log.RecoverPanic("agent.Run", func() {
370− logging.ErrorPersist("panic while generating title")
362+ slog.Error("panic while generating title")
372 titleErr := a.generateTitle(context.Background(), sessionID, content)
364 titleErr := a.generateTitle(context.Background(), sessionID, content)
373 if titleErr != nil && !errors.Is(titleErr, context.Canceled) && !errors.Is(titleErr, context.DeadlineExceeded) {
365 if titleErr != nil && !errors.Is(titleErr, context.Canceled) && !errors.Is(titleErr, context.DeadlineExceeded) {
374− logging.ErrorPersist(fmt.Sprintf("failed to generate title: %v", titleErr))
366+ slog.Error(fmt.Sprintf("failed to generate title: %v", titleErr))
@@ -418,11 +410,7 @@ func (a *agent) processGeneration(ctx context.Context, sessionID, content string
418 return a.err(fmt.Errorf("failed to process events: %w", err))
410 return a.err(fmt.Errorf("failed to process events: %w", err))
420 if cfg.Options.Debug {
412 if cfg.Options.Debug {
421− seqId := (len(msgHistory) + 1) / 2
422− toolResultFilepath := logging.WriteToolResultsJson(sessionID, seqId, toolResults)
423− logging.Info("Result", "message", agentMessage.FinishReason(), "toolResults", "{}", "filepath", toolResultFilepath)
425− logging.Info("Result", "message", agentMessage.FinishReason(), "toolResults", toolResults)
413+ slog.Info("Result", "message", agentMessage.FinishReason(), "toolResults", toolResults)
427 if (agentMessage.FinishReason() == message.FinishReasonToolUse) && toolResults != nil {
415 if (agentMessage.FinishReason() == message.FinishReasonToolUse) && toolResults != nil {
428 // We are not done, we need to respond with the tool response
416 // We are not done, we need to respond with the tool response
@@ -581,22 +569,22 @@ func (a *agent) processEvent(ctx context.Context, sessionID string, assistantMsg
581 assistantMsg.AppendContent(event.Content)
569 assistantMsg.AppendContent(event.Content)
582 return a.messages.Update(ctx, *assistantMsg)
570 return a.messages.Update(ctx, *assistantMsg)
583 case provider.EventToolUseStart:
571 case provider.EventToolUseStart:
584− logging.Info("Tool call started", "toolCall", event.ToolCall)
572+ slog.Info("Tool call started", "toolCall", event.ToolCall)
585 assistantMsg.AddToolCall(*event.ToolCall)
573 assistantMsg.AddToolCall(*event.ToolCall)
586 return a.messages.Update(ctx, *assistantMsg)
574 return a.messages.Update(ctx, *assistantMsg)
587 case provider.EventToolUseDelta:
575 case provider.EventToolUseDelta:
588 assistantMsg.AppendToolCallInput(event.ToolCall.ID, event.ToolCall.Input)
576 assistantMsg.AppendToolCallInput(event.ToolCall.ID, event.ToolCall.Input)
589 return a.messages.Update(ctx, *assistantMsg)
577 return a.messages.Update(ctx, *assistantMsg)
590 case provider.EventToolUseStop:
578 case provider.EventToolUseStop:
591− logging.Info("Finished tool call", "toolCall", event.ToolCall)
579+ slog.Info("Finished tool call", "toolCall", event.ToolCall)
592 assistantMsg.FinishToolCall(event.ToolCall.ID)
580 assistantMsg.FinishToolCall(event.ToolCall.ID)
593 return a.messages.Update(ctx, *assistantMsg)
581 return a.messages.Update(ctx, *assistantMsg)
594 case provider.EventError:
582 case provider.EventError:
595 if errors.Is(event.Error, context.Canceled) {
583 if errors.Is(event.Error, context.Canceled) {
596− logging.InfoPersist(fmt.Sprintf("Event processing canceled for session: %s", sessionID))
584+ slog.Info(fmt.Sprintf("Event processing canceled for session: %s", sessionID))
597 return context.Canceled
585 return context.Canceled
599− logging.ErrorPersist(event.Error.Error())
587+ slog.Error(event.Error.Error())
600 return event.Error
588 return event.Error
601 case provider.EventComplete:
589 case provider.EventComplete:
602 assistantMsg.SetToolCalls(event.Response.ToolCalls)
590 assistantMsg.SetToolCalls(event.Response.ToolCalls)
@@ -610,7 +598,7 @@ func (a *agent) processEvent(ctx context.Context, sessionID string, assistantMsg
610 return nil
598 return nil
613−func (a *agent) TrackUsage(ctx context.Context, sessionID string, model config.Model, usage provider.TokenUsage) error {
601+func (a *agent) TrackUsage(ctx context.Context, sessionID string, model fur.Model, usage provider.TokenUsage) error {
614 sess, err := a.sessions.Get(ctx, sessionID)
602 sess, err := a.sessions.Get(ctx, sessionID)
615 if err != nil {
603 if err != nil {
616 return fmt.Errorf("failed to get session: %w", err)
604 return fmt.Errorf("failed to get session: %w", err)
@@ -819,7 +807,7 @@ func (a *agent) UpdateModel() error {
819 cfg := config.Get()
807 cfg := config.Get()
821 // Get current provider configuration
809 // Get current provider configuration
822− currentProviderCfg := config.GetAgentProvider(a.agentCfg.ID)
810+ currentProviderCfg := cfg.GetProviderForModel(a.agentCfg.Model)
823 if currentProviderCfg.ID == "" {
811 if currentProviderCfg.ID == "" {
824 return fmt.Errorf("provider for agent %s not found in config", a.agentCfg.Name)
812 return fmt.Errorf("provider for agent %s not found in config", a.agentCfg.Name)
@@ -827,7 +815,7 @@ func (a *agent) UpdateModel() error {
827 // Check if provider has changed
815 // Check if provider has changed
828 if string(currentProviderCfg.ID) != a.providerID {
816 if string(currentProviderCfg.ID) != a.providerID {
829 // Provider changed, need to recreate the main provider
817 // Provider changed, need to recreate the main provider
830− model := config.GetAgentModel(a.agentCfg.ID)
818+ model := cfg.GetModelByType(a.agentCfg.Model)
831 if model.ID == "" {
819 if model.ID == "" {
832 return fmt.Errorf("model not found for agent %s", a.agentCfg.Name)
820 return fmt.Errorf("model not found for agent %s", a.agentCfg.Name)
@@ -842,7 +830,7 @@ func (a *agent) UpdateModel() error {
842 provider.WithSystemMessage(prompt.GetPrompt(promptID, currentProviderCfg.ID)),
830 provider.WithSystemMessage(prompt.GetPrompt(promptID, currentProviderCfg.ID)),
845− newProvider, err := provider.NewProvider(currentProviderCfg, opts...)
833+ newProvider, err := provider.NewProvider(*currentProviderCfg, opts...)
846 if err != nil {
834 if err != nil {
847 return fmt.Errorf("failed to create new provider: %w", err)
835 return fmt.Errorf("failed to create new provider: %w", err)
@@ -853,7 +841,7 @@ func (a *agent) UpdateModel() error {
855 // Check if small model provider has changed (affects title and summarize providers)
843 // Check if small model provider has changed (affects title and summarize providers)
856− smallModelCfg := cfg.Models.Small
844+ smallModelCfg := cfg.Models[config.SelectedModelTypeSmall]
857 var smallModelProviderCfg config.ProviderConfig
845 var smallModelProviderCfg config.ProviderConfig
859 for _, p := range cfg.Providers {
847 for _, p := range cfg.Providers {
@@ -869,20 +857,14 @@ func (a *agent) UpdateModel() error {
870 // Check if summarize provider has changed
858 // Check if summarize provider has changed
871 if string(smallModelProviderCfg.ID) != a.summarizeProviderID {
859 if string(smallModelProviderCfg.ID) != a.summarizeProviderID {
872− var smallModel config.Model
860+ smallModel := cfg.GetModelByType(config.SelectedModelTypeSmall)
873− for _, m := range smallModelProviderCfg.Models {
874− if m.ID == smallModelCfg.ModelID {
879− if smallModel.ID == "" {
861+ if smallModel == nil {
880− return fmt.Errorf("model %s not found in provider %s", smallModelCfg.ModelID, smallModelProviderCfg.ID)
862+ return fmt.Errorf("model %s not found in provider %s", smallModelCfg.Model, smallModelProviderCfg.ID)
883 // Recreate title provider
865 // Recreate title provider
884 titleOpts := []provider.ProviderClientOption{
866 titleOpts := []provider.ProviderClientOption{
885− provider.WithModel(config.SmallModel),
867+ provider.WithModel(config.SelectedModelTypeSmall),
886 provider.WithSystemMessage(prompt.GetPrompt(prompt.PromptTitle, smallModelProviderCfg.ID)),
868 provider.WithSystemMessage(prompt.GetPrompt(prompt.PromptTitle, smallModelProviderCfg.ID)),
887 // We want the title to be short, so we limit the max tokens
869 // We want the title to be short, so we limit the max tokens
888 provider.WithMaxTokens(40),
870 provider.WithMaxTokens(40),
@@ -894,7 +876,7 @@ func (a *agent) UpdateModel() error {
895 // Recreate summarize provider
877 // Recreate summarize provider
896 summarizeOpts := []provider.ProviderClientOption{
878 summarizeOpts := []provider.ProviderClientOption{
897− provider.WithModel(config.SmallModel),
879+ provider.WithModel(config.SelectedModelTypeSmall),
898 provider.WithSystemMessage(prompt.GetPrompt(prompt.PromptSummarizer, smallModelProviderCfg.ID)),
880 provider.WithSystemMessage(prompt.GetPrompt(prompt.PromptSummarizer, smallModelProviderCfg.ID)),
900 newSummarizeProvider, err := provider.NewProvider(smallModelProviderCfg, summarizeOpts...)
882 newSummarizeProvider, err := provider.NewProvider(smallModelProviderCfg, summarizeOpts...)
internal/llm/agent/mcp-tools.go+19−16
@@ -4,10 +4,11 @@ import (
5 "encoding/json"
5 "encoding/json"
8 "github.com/charmbracelet/crush/internal/config"
9 "github.com/charmbracelet/crush/internal/config"
9 "github.com/charmbracelet/crush/internal/llm/tools"
10 "github.com/charmbracelet/crush/internal/llm/tools"
10− "github.com/charmbracelet/crush/internal/logging"
11+
11 "github.com/charmbracelet/crush/internal/permission"
12 "github.com/charmbracelet/crush/internal/permission"
12 "github.com/charmbracelet/crush/internal/version"
13 "github.com/charmbracelet/crush/internal/version"
@@ -19,8 +20,9 @@ import (
19type mcpTool struct {
20type mcpTool struct {
20 mcpName string
21 mcpName string
21 tool mcp.Tool
22 tool mcp.Tool
22− mcpConfig config.MCP
23+ mcpConfig config.MCPConfig
23 permissions permission.Service
24 permissions permission.Service
26type MCPClient interface {
28type MCPClient interface {
@@ -97,7 +99,7 @@ func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolRes
97 p := b.permissions.Request(
99 p := b.permissions.Request(
98 permission.CreatePermissionRequest{
100 permission.CreatePermissionRequest{
99 SessionID: sessionID,
101 SessionID: sessionID,
100− Path: config.WorkingDirectory(),
102+ Path: b.workingDir,
101 ToolName: b.Info().Name,
103 ToolName: b.Info().Name,
102 Action: "execute",
104 Action: "execute",
103 Description: permissionDescription,
105 Description: permissionDescription,
@@ -142,18 +144,19 @@ func (b *mcpTool) Run(ctx context.Context, params tools.ToolCall) (tools.ToolRes
142 return tools.NewTextErrorResponse("invalid mcp type"), nil
144 return tools.NewTextErrorResponse("invalid mcp type"), nil
145−func NewMcpTool(name string, tool mcp.Tool, permissions permission.Service, mcpConfig config.MCP) tools.BaseTool {
147+func NewMcpTool(name string, tool mcp.Tool, permissions permission.Service, mcpConfig config.MCPConfig, workingDir string) tools.BaseTool {
146 return &mcpTool{
148 return &mcpTool{
147 mcpName: name,
149 mcpName: name,
148 tool: tool,
150 tool: tool,
149 mcpConfig: mcpConfig,
151 mcpConfig: mcpConfig,
150 permissions: permissions,
152 permissions: permissions,
153+ workingDir: workingDir,
154var mcpTools []tools.BaseTool
157var mcpTools []tools.BaseTool
156−func getTools(ctx context.Context, name string, m config.MCP, permissions permission.Service, c MCPClient) []tools.BaseTool {
159+func getTools(ctx context.Context, name string, m config.MCPConfig, permissions permission.Service, c MCPClient, workingDir string) []tools.BaseTool {
157 var stdioTools []tools.BaseTool
160 var stdioTools []tools.BaseTool
158 initRequest := mcp.InitializeRequest{}
161 initRequest := mcp.InitializeRequest{}
159 initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
162 initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
@@ -164,27 +167,27 @@ func getTools(ctx context.Context, name string, m config.MCP, permissions permis
165 _, err := c.Initialize(ctx, initRequest)
168 _, err := c.Initialize(ctx, initRequest)
166 if err != nil {
169 if err != nil {
167− logging.Error("error initializing mcp client", "error", err)
170+ slog.Error("error initializing mcp client", "error", err)
168 return stdioTools
171 return stdioTools
170 toolsRequest := mcp.ListToolsRequest{}
173 toolsRequest := mcp.ListToolsRequest{}
171 tools, err := c.ListTools(ctx, toolsRequest)
174 tools, err := c.ListTools(ctx, toolsRequest)
172 if err != nil {
175 if err != nil {
173− logging.Error("error listing tools", "error", err)
176+ slog.Error("error listing tools", "error", err)
174 return stdioTools
177 return stdioTools
176 for _, t := range tools.Tools {
179 for _, t := range tools.Tools {
177− stdioTools = append(stdioTools, NewMcpTool(name, t, permissions, m))
180+ stdioTools = append(stdioTools, NewMcpTool(name, t, permissions, m, workingDir))
179 defer c.Close()
182 defer c.Close()
180 return stdioTools
183 return stdioTools
183−func GetMcpTools(ctx context.Context, permissions permission.Service) []tools.BaseTool {
186+func GetMcpTools(ctx context.Context, permissions permission.Service, cfg *config.Config) []tools.BaseTool {
184 if len(mcpTools) > 0 {
187 if len(mcpTools) > 0 {
185 return mcpTools
188 return mcpTools
187− for name, m := range config.Get().MCP {
190+ for name, m := range cfg.MCP {
188 switch m.Type {
191 switch m.Type {
189 case config.MCPStdio:
192 case config.MCPStdio:
190 c, err := client.NewStdioMCPClient(
193 c, err := client.NewStdioMCPClient(
@@ -193,31 +196,31 @@ func GetMcpTools(ctx context.Context, permissions permission.Service) []tools.Ba
193 m.Args...,
196 m.Args...,
195 if err != nil {
198 if err != nil {
196− logging.Error("error creating mcp client", "error", err)
199+ slog.Error("error creating mcp client", "error", err)
200− mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c)...)
203+ mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
201 case config.MCPHttp:
204 case config.MCPHttp:
202 c, err := client.NewStreamableHttpClient(
205 c, err := client.NewStreamableHttpClient(
204 transport.WithHTTPHeaders(m.Headers),
207 transport.WithHTTPHeaders(m.Headers),
206 if err != nil {
209 if err != nil {
207− logging.Error("error creating mcp client", "error", err)
210+ slog.Error("error creating mcp client", "error", err)
210− mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c)...)
213+ mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
211 case config.MCPSse:
214 case config.MCPSse:
212 c, err := client.NewSSEMCPClient(
215 c, err := client.NewSSEMCPClient(
214 client.WithHeaders(m.Headers),
217 client.WithHeaders(m.Headers),
216 if err != nil {
219 if err != nil {
217− logging.Error("error creating mcp client", "error", err)
220+ slog.Error("error creating mcp client", "error", err)
220− mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c)...)
223+ mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c, cfg.WorkingDir())...)
internal/llm/prompt/coder.go+8−8
@@ -3,6 +3,7 @@ package prompt
7 "path/filepath"
8 "path/filepath"
@@ -11,15 +12,14 @@ import (
11 "github.com/charmbracelet/crush/internal/config"
12 "github.com/charmbracelet/crush/internal/config"
12 "github.com/charmbracelet/crush/internal/fur/provider"
13 "github.com/charmbracelet/crush/internal/fur/provider"
13 "github.com/charmbracelet/crush/internal/llm/tools"
14 "github.com/charmbracelet/crush/internal/llm/tools"
14− "github.com/charmbracelet/crush/internal/logging"
17−func CoderPrompt(p provider.InferenceProvider, contextFiles ...string) string {
17+func CoderPrompt(p string, contextFiles ...string) string {
18 var basePrompt string
18 var basePrompt string
19 switch p {
19 switch p {
20− case provider.InferenceProviderOpenAI:
20+ case string(provider.InferenceProviderOpenAI):
21 basePrompt = baseOpenAICoderPrompt
21 basePrompt = baseOpenAICoderPrompt
22− case provider.InferenceProviderGemini, provider.InferenceProviderVertexAI:
22+ case string(provider.InferenceProviderGemini), string(provider.InferenceProviderVertexAI):
23 basePrompt = baseGeminiCoderPrompt
23 basePrompt = baseGeminiCoderPrompt
25 basePrompt = baseAnthropicCoderPrompt
25 basePrompt = baseAnthropicCoderPrompt
@@ -28,8 +28,8 @@ func CoderPrompt(p provider.InferenceProvider, contextFiles ...string) string {
29 basePrompt = fmt.Sprintf("%s\n\n%s\n%s", basePrompt, envInfo, lspInformation())
29 basePrompt = fmt.Sprintf("%s\n\n%s\n%s", basePrompt, envInfo, lspInformation())
31− contextContent := getContextFromPaths(contextFiles)
31+ contextContent := getContextFromPaths(config.Get().WorkingDir(), contextFiles)
32− logging.Debug("Context content", "Context", contextContent)
32+ slog.Debug("Context content", "Context", contextContent)
33 if contextContent != "" {
33 if contextContent != "" {
34 return fmt.Sprintf("%s\n\n# Project-Specific Context\n Make sure to follow the instructions in the context below\n%s", basePrompt, contextContent)
34 return fmt.Sprintf("%s\n\n# Project-Specific Context\n Make sure to follow the instructions in the context below\n%s", basePrompt, contextContent)
@@ -380,11 +380,11 @@ Your core function is efficient and safe assistance. Balance extreme conciseness
382func getEnvironmentInfo() string {
382func getEnvironmentInfo() string {
383− cwd := config.WorkingDirectory()
383+ cwd := config.Get().WorkingDir()
384 isGit := isGitRepo(cwd)
384 isGit := isGitRepo(cwd)
385 platform := runtime.GOOS
385 platform := runtime.GOOS
386 date := time.Now().Format("1/2/2006")
386 date := time.Now().Format("1/2/2006")
387− ls := tools.NewLsTool()
387+ ls := tools.NewLsTool(cwd)
388 r, _ := ls.Run(context.Background(), tools.ToolCall{
388 r, _ := ls.Run(context.Background(), tools.ToolCall{
389 Input: `{"path":"."}`,
389 Input: `{"path":"."}`,
internal/llm/prompt/prompt.go+6−9
@@ -5,9 +5,6 @@ import (
5 "path/filepath"
5 "path/filepath"
9− "github.com/charmbracelet/crush/internal/config"
10− "github.com/charmbracelet/crush/internal/fur/provider"
13type PromptID string
10type PromptID string
@@ -20,25 +17,25 @@ const (
20 PromptDefault PromptID = "default"
17 PromptDefault PromptID = "default"
23−func GetPrompt(promptID PromptID, provider provider.InferenceProvider, contextPaths ...string) string {
20+func GetPrompt(promptID PromptID, provider string, contextPaths ...string) string {
24 basePrompt := ""
21 basePrompt := ""
25 switch promptID {
22 switch promptID {
26 case PromptCoder:
23 case PromptCoder:
27 basePrompt = CoderPrompt(provider)
24 basePrompt = CoderPrompt(provider)
28 case PromptTitle:
25 case PromptTitle:
29− basePrompt = TitlePrompt(provider)
26+ basePrompt = TitlePrompt()
30 case PromptTask:
27 case PromptTask:
31− basePrompt = TaskPrompt(provider)
28+ basePrompt = TaskPrompt()
32 case PromptSummarizer:
29 case PromptSummarizer:
33− basePrompt = SummarizerPrompt(provider)
30+ basePrompt = SummarizerPrompt()
35 basePrompt = "You are a helpful assistant"
32 basePrompt = "You are a helpful assistant"
37 return basePrompt
34 return basePrompt
40−func getContextFromPaths(contextPaths []string) string {
37+func getContextFromPaths(workingDir string, contextPaths []string) string {
41− return processContextPaths(config.WorkingDirectory(), contextPaths)
38+ return processContextPaths(workingDir, contextPaths)
44func processContextPaths(workDir string, paths []string) string {
41func processContextPaths(workDir string, paths []string) string {
internal/llm/prompt/prompt_test.godeleted−56
@@ -1,56 +0,0 @@
9− "github.com/charmbracelet/crush/internal/config"
10− "github.com/stretchr/testify/assert"
11− "github.com/stretchr/testify/require"
14−func TestGetContextFromPaths(t *testing.T) {
17− tmpDir := t.TempDir()
18− _, err := config.Init(tmpDir, false)
20− t.Fatalf("Failed to load config: %v", err)
22− testFiles := []string{
24− "directory/file_a.txt",
25− "directory/file_b.txt",
26− "directory/file_c.txt",
29− createTestFiles(t, tmpDir, testFiles)
31− context := getContextFromPaths(
37− expectedContext := fmt.Sprintf("# From:%s/file.txt\nfile.txt: test content\n# From:%s/directory/file_a.txt\ndirectory/file_a.txt: test content\n# From:%s/directory/file_b.txt\ndirectory/file_b.txt: test content\n# From:%s/directory/file_c.txt\ndirectory/file_c.txt: test content", tmpDir, tmpDir, tmpDir, tmpDir)
38− assert.Equal(t, expectedContext, context)
41−func createTestFiles(t *testing.T, tmpDir string, testFiles []string) {
43− for _, path := range testFiles {
44− fullPath := filepath.Join(tmpDir, path)
45− if path[len(path)-1] == '/' {
46− err := os.MkdirAll(fullPath, 0o755)
47− require.NoError(t, err)
49− dir := filepath.Dir(fullPath)
50− err := os.MkdirAll(dir, 0o755)
51− require.NoError(t, err)
52− err = os.WriteFile(fullPath, []byte(path+": test content"), 0o644)
53− require.NoError(t, err)
internal/llm/prompt/summarizer.go+1−5
@@ -1,10 +1,6 @@
1package prompt
1package prompt
4− "github.com/charmbracelet/crush/internal/fur/provider"
7−func SummarizerPrompt(_ provider.InferenceProvider) string {
3+func SummarizerPrompt() string {
8 return `You are a helpful AI assistant tasked with summarizing conversations.
4 return `You are a helpful AI assistant tasked with summarizing conversations.
10When asked to summarize, provide a detailed but concise summary of the conversation.
6When asked to summarize, provide a detailed but concise summary of the conversation.
internal/llm/prompt/task.go+1−3
@@ -2,11 +2,9 @@ package prompt
6− "github.com/charmbracelet/crush/internal/fur/provider"
9−func TaskPrompt(_ provider.InferenceProvider) string {
7+func TaskPrompt() string {
10 agentPrompt := `You are an agent for Crush. Given the user's prompt, you should use the tools available to you to answer the user's question.
8 agentPrompt := `You are an agent for Crush. Given the user's prompt, you should use the tools available to you to answer the user's question.
121. IMPORTANT: You should be concise, direct, and to the point, since your responses will be displayed on a command line interface. Answer the user's question directly, without elaboration, explanation, or details. One word answers are best. Avoid introductions, conclusions, and explanations. You MUST avoid text before/after your response, such as "The answer is <answer>.", "Here is the content of the file..." or "Based on the information provided, the answer is..." or "Here is what I will do next...".
101. IMPORTANT: You should be concise, direct, and to the point, since your responses will be displayed on a command line interface. Answer the user's question directly, without elaboration, explanation, or details. One word answers are best. Avoid introductions, conclusions, and explanations. You MUST avoid text before/after your response, such as "The answer is <answer>.", "Here is the content of the file..." or "Based on the information provided, the answer is..." or "Here is what I will do next...".
internal/llm/prompt/title.go+1−5
@@ -1,10 +1,6 @@
1package prompt
1package prompt
4− "github.com/charmbracelet/crush/internal/fur/provider"
7−func TitlePrompt(_ provider.InferenceProvider) string {
3+func TitlePrompt() string {
8 return `you will generate a short title based on the first message a user begins a conversation with
4 return `you will generate a short title based on the first message a user begins a conversation with
9- ensure it is not more than 50 characters long
5- ensure it is not more than 50 characters long
10- the title should be a summary of the user's message
6- the title should be a summary of the user's message
internal/llm/provider/anthropic.go+16−15
@@ -6,6 +6,7 @@ import (
@@ -16,7 +17,6 @@ import (
16 "github.com/charmbracelet/crush/internal/config"
17 "github.com/charmbracelet/crush/internal/config"
17 "github.com/charmbracelet/crush/internal/fur/provider"
18 "github.com/charmbracelet/crush/internal/fur/provider"
18 "github.com/charmbracelet/crush/internal/llm/tools"
19 "github.com/charmbracelet/crush/internal/llm/tools"
19− "github.com/charmbracelet/crush/internal/logging"
20 "github.com/charmbracelet/crush/internal/message"
20 "github.com/charmbracelet/crush/internal/message"
@@ -92,7 +92,7 @@ func (a *anthropicClient) convertMessages(messages []message.Message) (anthropic
94 if len(blocks) == 0 {
94 if len(blocks) == 0 {
95− logging.Warn("There is a message without content, investigate, this should not happen")
95+ slog.Warn("There is a message without content, investigate, this should not happen")
98 anthropicMessages = append(anthropicMessages, anthropic.NewAssistantMessage(blocks...))
98 anthropicMessages = append(anthropicMessages, anthropic.NewAssistantMessage(blocks...))
@@ -153,9 +153,9 @@ func (a *anthropicClient) preparedMessages(messages []anthropic.MessageParam, to
153 model := a.providerOptions.model(a.providerOptions.modelType)
153 model := a.providerOptions.model(a.providerOptions.modelType)
154 var thinkingParam anthropic.ThinkingConfigParamUnion
154 var thinkingParam anthropic.ThinkingConfigParamUnion
155 cfg := config.Get()
155 cfg := config.Get()
156− modelConfig := cfg.Models.Large
156+ modelConfig := cfg.Models[config.SelectedModelTypeLarge]
157− if a.providerOptions.modelType == config.SmallModel {
157+ if a.providerOptions.modelType == config.SelectedModelTypeSmall {
158− modelConfig = cfg.Models.Small
158+ modelConfig = cfg.Models[config.SelectedModelTypeSmall]
160 temperature := anthropic.Float(0)
160 temperature := anthropic.Float(0)
@@ -207,7 +207,7 @@ func (a *anthropicClient) send(ctx context.Context, messages []message.Message,
207 preparedMessages := a.preparedMessages(a.convertMessages(messages), a.convertTools(tools))
207 preparedMessages := a.preparedMessages(a.convertMessages(messages), a.convertTools(tools))
208 if cfg.Options.Debug {
208 if cfg.Options.Debug {
209 jsonData, _ := json.Marshal(preparedMessages)
209 jsonData, _ := json.Marshal(preparedMessages)
210− logging.Debug("Prepared messages", "messages", string(jsonData))
210+ slog.Debug("Prepared messages", "messages", string(jsonData))
213 anthropicResponse, err := a.client.Messages.New(
213 anthropicResponse, err := a.client.Messages.New(
@@ -216,13 +216,13 @@ func (a *anthropicClient) send(ctx context.Context, messages []message.Message,
217 // If there is an error we are going to see if we can retry the call
217 // If there is an error we are going to see if we can retry the call
218 if err != nil {
218 if err != nil {
219− logging.Error("Error in Anthropic API call", "error", err)
219+ slog.Error("Error in Anthropic API call", "error", err)
220 retry, after, retryErr := a.shouldRetry(attempts, err)
220 retry, after, retryErr := a.shouldRetry(attempts, err)
221 if retryErr != nil {
221 if retryErr != nil {
222 return nil, retryErr
222 return nil, retryErr
224 if retry {
224 if retry {
225− logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
225+ slog.Warn(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries))
227 case <-ctx.Done():
227 case <-ctx.Done():
228 return nil, ctx.Err()
228 return nil, ctx.Err()
@@ -259,7 +259,7 @@ func (a *anthropicClient) stream(ctx context.Context, messages []message.Message
259 preparedMessages := a.preparedMessages(a.convertMessages(messages), a.convertTools(tools))
259 preparedMessages := a.preparedMessages(a.convertMessages(messages), a.convertTools(tools))
260 if cfg.Options.Debug {
260 if cfg.Options.Debug {
261 jsonData, _ := json.Marshal(preparedMessages)
261 jsonData, _ := json.Marshal(preparedMessages)
262− logging.Debug("Prepared messages", "messages", string(jsonData))
262+ slog.Debug("Prepared messages", "messages", string(jsonData))
265 anthropicStream := a.client.Messages.NewStreaming(
265 anthropicStream := a.client.Messages.NewStreaming(
@@ -273,7 +273,7 @@ func (a *anthropicClient) stream(ctx context.Context, messages []message.Message
273 event := anthropicStream.Current()
273 event := anthropicStream.Current()
274 err := accumulatedMessage.Accumulate(event)
274 err := accumulatedMessage.Accumulate(event)
275 if err != nil {
275 if err != nil {
276− logging.Warn("Error accumulating message", "error", err)
276+ slog.Warn("Error accumulating message", "error", err)
@@ -364,7 +364,7 @@ func (a *anthropicClient) stream(ctx context.Context, messages []message.Message
366 if retry {
366 if retry {
367− logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
367+ slog.Warn(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries))
369 case <-ctx.Done():
369 case <-ctx.Done():
370 // context cancelled
370 // context cancelled
@@ -399,7 +399,7 @@ func (a *anthropicClient) shouldRetry(attempts int, err error) (bool, int64, err
401 if apiErr.StatusCode == 401 {
401 if apiErr.StatusCode == 401 {
402− a.providerOptions.apiKey, err = config.ResolveAPIKey(a.providerOptions.config.APIKey)
402+ a.providerOptions.apiKey, err = config.Get().Resolve(a.providerOptions.config.APIKey)
403 if err != nil {
403 if err != nil {
404 return false, 0, fmt.Errorf("failed to resolve API key: %w", err)
404 return false, 0, fmt.Errorf("failed to resolve API key: %w", err)
@@ -411,7 +411,7 @@ func (a *anthropicClient) shouldRetry(attempts int, err error) (bool, int64, err
411 if apiErr.StatusCode == 400 {
411 if apiErr.StatusCode == 400 {
412 if adjusted, ok := a.handleContextLimitError(apiErr); ok {
412 if adjusted, ok := a.handleContextLimitError(apiErr); ok {
413 a.adjustedMaxTokens = adjusted
413 a.adjustedMaxTokens = adjusted
414− logging.Debug("Adjusted max_tokens due to context limit", "new_max_tokens", adjusted)
414+ slog.Debug("Adjusted max_tokens due to context limit", "new_max_tokens", adjusted)
415 return true, 0, nil
415 return true, 0, nil
@@ -438,7 +438,8 @@ func (a *anthropicClient) shouldRetry(attempts int, err error) (bool, int64, err
438func (a *anthropicClient) handleContextLimitError(apiErr *anthropic.Error) (int, bool) {
438func (a *anthropicClient) handleContextLimitError(apiErr *anthropic.Error) (int, bool) {
439 // Parse error message like: "input length and max_tokens exceed context limit: 154978 + 50000 > 200000"
439 // Parse error message like: "input length and max_tokens exceed context limit: 154978 + 50000 > 200000"
440 errorMsg := apiErr.Error()
440 errorMsg := apiErr.Error()
441− re := regexp.MustCompile(`input length and max_tokens exceed context limit: (\d+) \+ (\d+) > (\d+)`)
442+ re := regexp.MustCompile("input length and `max_tokens` exceed context limit: (\\d+) \\+ (\\d+) > (\\d+)")
442 matches := re.FindStringSubmatch(errorMsg)
443 matches := re.FindStringSubmatch(errorMsg)
444 if len(matches) != 4 {
445 if len(matches) != 4 {
@@ -490,6 +491,6 @@ func (a *anthropicClient) usage(msg anthropic.Message) TokenUsage {
493−func (a *anthropicClient) Model() config.Model {
494+func (a *anthropicClient) Model() provider.Model {
494 return a.providerOptions.model(a.providerOptions.modelType)
495 return a.providerOptions.model(a.providerOptions.modelType)
internal/llm/provider/bedrock.go+5−4
@@ -7,6 +7,7 @@ import (
9 "github.com/charmbracelet/crush/internal/config"
9 "github.com/charmbracelet/crush/internal/config"
10+ "github.com/charmbracelet/crush/internal/fur/provider"
10 "github.com/charmbracelet/crush/internal/llm/tools"
11 "github.com/charmbracelet/crush/internal/llm/tools"
11 "github.com/charmbracelet/crush/internal/message"
12 "github.com/charmbracelet/crush/internal/message"
@@ -31,14 +32,14 @@ func newBedrockClient(opts providerClientOptions) BedrockClient {
34− opts.model = func(modelType config.ModelType) config.Model {
35+ opts.model = func(modelType config.SelectedModelType) provider.Model {
35− model := config.GetModel(modelType)
36+ model := config.Get().GetModelByType(modelType)
37 // Prefix the model name with region
38 // Prefix the model name with region
38 regionPrefix := region[:2]
39 regionPrefix := region[:2]
39 modelName := model.ID
40 modelName := model.ID
40 model.ID = fmt.Sprintf("%s.%s", regionPrefix, modelName)
41 model.ID = fmt.Sprintf("%s.%s", regionPrefix, modelName)
41− return model
42+ return *model
44 model := opts.model(opts.modelType)
45 model := opts.model(opts.modelType)
@@ -87,6 +88,6 @@ func (b *bedrockClient) stream(ctx context.Context, messages []message.Message,
87 return b.childProvider.stream(ctx, messages, tools)
88 return b.childProvider.stream(ctx, messages, tools)
90−func (b *bedrockClient) Model() config.Model {
91+func (b *bedrockClient) Model() provider.Model {
91 return b.providerOptions.model(b.providerOptions.modelType)
92 return b.providerOptions.model(b.providerOptions.modelType)
internal/llm/provider/gemini.go+15−14
@@ -6,12 +6,13 @@ import (
12 "github.com/charmbracelet/crush/internal/config"
13 "github.com/charmbracelet/crush/internal/config"
14+ "github.com/charmbracelet/crush/internal/fur/provider"
13 "github.com/charmbracelet/crush/internal/llm/tools"
15 "github.com/charmbracelet/crush/internal/llm/tools"
14− "github.com/charmbracelet/crush/internal/logging"
15 "github.com/charmbracelet/crush/internal/message"
16 "github.com/charmbracelet/crush/internal/message"
16 "github.com/google/uuid"
17 "github.com/google/uuid"
17 "google.golang.org/genai"
18 "google.golang.org/genai"
@@ -27,7 +28,7 @@ type GeminiClient ProviderClient
27func newGeminiClient(opts providerClientOptions) GeminiClient {
28func newGeminiClient(opts providerClientOptions) GeminiClient {
28 client, err := createGeminiClient(opts)
29 client, err := createGeminiClient(opts)
29 if err != nil {
30 if err != nil {
30− logging.Error("Failed to create Gemini client", "error", err)
31+ slog.Error("Failed to create Gemini client", "error", err)
31 return nil
32 return nil
@@ -167,12 +168,12 @@ func (g *geminiClient) send(ctx context.Context, messages []message.Message, too
167 cfg := config.Get()
168 cfg := config.Get()
168 if cfg.Options.Debug {
169 if cfg.Options.Debug {
169 jsonData, _ := json.Marshal(geminiMessages)
170 jsonData, _ := json.Marshal(geminiMessages)
170− logging.Debug("Prepared messages", "messages", string(jsonData))
171+ slog.Debug("Prepared messages", "messages", string(jsonData))
173− modelConfig := cfg.Models.Large
174+ modelConfig := cfg.Models[config.SelectedModelTypeLarge]
174− if g.providerOptions.modelType == config.SmallModel {
175+ if g.providerOptions.modelType == config.SelectedModelTypeSmall {
175− modelConfig = cfg.Models.Small
176+ modelConfig = cfg.Models[config.SelectedModelTypeSmall]
178 maxTokens := model.DefaultMaxTokens
179 maxTokens := model.DefaultMaxTokens
@@ -209,7 +210,7 @@ func (g *geminiClient) send(ctx context.Context, messages []message.Message, too
209 return nil, retryErr
210 return nil, retryErr
211 if retry {
212 if retry {
212− logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
213+ slog.Warn(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries))
214 case <-ctx.Done():
215 case <-ctx.Done():
215 return nil, ctx.Err()
216 return nil, ctx.Err()
@@ -265,12 +266,12 @@ func (g *geminiClient) stream(ctx context.Context, messages []message.Message, t
265 cfg := config.Get()
266 cfg := config.Get()
266 if cfg.Options.Debug {
267 if cfg.Options.Debug {
267 jsonData, _ := json.Marshal(geminiMessages)
268 jsonData, _ := json.Marshal(geminiMessages)
268− logging.Debug("Prepared messages", "messages", string(jsonData))
269+ slog.Debug("Prepared messages", "messages", string(jsonData))
271− modelConfig := cfg.Models.Large
272+ modelConfig := cfg.Models[config.SelectedModelTypeLarge]
272− if g.providerOptions.modelType == config.SmallModel {
273+ if g.providerOptions.modelType == config.SelectedModelTypeSmall {
273− modelConfig = cfg.Models.Small
274+ modelConfig = cfg.Models[config.SelectedModelTypeSmall]
275 maxTokens := model.DefaultMaxTokens
276 maxTokens := model.DefaultMaxTokens
276 if modelConfig.MaxTokens > 0 {
277 if modelConfig.MaxTokens > 0 {
@@ -322,7 +323,7 @@ func (g *geminiClient) stream(ctx context.Context, messages []message.Message, t
324 if retry {
325 if retry {
325− logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
326+ slog.Warn(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries))
327 case <-ctx.Done():
328 case <-ctx.Done():
328 if ctx.Err() != nil {
329 if ctx.Err() != nil {
@@ -424,7 +425,7 @@ func (g *geminiClient) shouldRetry(attempts int, err error) (bool, int64, error)
425 // Check for token expiration (401 Unauthorized)
426 // Check for token expiration (401 Unauthorized)
426 if contains(errMsg, "unauthorized", "invalid api key", "api key expired") {
427 if contains(errMsg, "unauthorized", "invalid api key", "api key expired") {
427− g.providerOptions.apiKey, err = config.ResolveAPIKey(g.providerOptions.config.APIKey)
428+ g.providerOptions.apiKey, err = config.Get().Resolve(g.providerOptions.config.APIKey)
428 if err != nil {
429 if err != nil {
429 return false, 0, fmt.Errorf("failed to resolve API key: %w", err)
430 return false, 0, fmt.Errorf("failed to resolve API key: %w", err)
@@ -462,7 +463,7 @@ func (g *geminiClient) usage(resp *genai.GenerateContentResponse) TokenUsage {
465−func (g *geminiClient) Model() config.Model {
466+func (g *geminiClient) Model() provider.Model {
466 return g.providerOptions.model(g.providerOptions.modelType)
467 return g.providerOptions.model(g.providerOptions.modelType)
internal/llm/provider/openai.go+12−15
@@ -6,12 +6,12 @@ import (
11 "github.com/charmbracelet/crush/internal/config"
12 "github.com/charmbracelet/crush/internal/config"
12 "github.com/charmbracelet/crush/internal/fur/provider"
13 "github.com/charmbracelet/crush/internal/fur/provider"
13 "github.com/charmbracelet/crush/internal/llm/tools"
14 "github.com/charmbracelet/crush/internal/llm/tools"
14− "github.com/charmbracelet/crush/internal/logging"
15 "github.com/charmbracelet/crush/internal/message"
15 "github.com/charmbracelet/crush/internal/message"
16 "github.com/openai/openai-go"
16 "github.com/openai/openai-go"
17 "github.com/openai/openai-go/option"
17 "github.com/openai/openai-go/option"
@@ -148,15 +148,12 @@ func (o *openaiClient) preparedParams(messages []openai.ChatCompletionMessagePar
148 model := o.providerOptions.model(o.providerOptions.modelType)
148 model := o.providerOptions.model(o.providerOptions.modelType)
149 cfg := config.Get()
149 cfg := config.Get()
151− modelConfig := cfg.Models.Large
151+ modelConfig := cfg.Models[config.SelectedModelTypeLarge]
152− if o.providerOptions.modelType == config.SmallModel {
152+ if o.providerOptions.modelType == config.SelectedModelTypeSmall {
153− modelConfig = cfg.Models.Small
153+ modelConfig = cfg.Models[config.SelectedModelTypeSmall]
156− reasoningEffort := model.ReasoningEffort
157− if modelConfig.ReasoningEffort != "" {
158− reasoningEffort = modelConfig.ReasoningEffort
156+ reasoningEffort := modelConfig.ReasoningEffort
161 params := openai.ChatCompletionNewParams{
158 params := openai.ChatCompletionNewParams{
162 Model: openai.ChatModel(model.ID),
159 Model: openai.ChatModel(model.ID),
@@ -197,7 +194,7 @@ func (o *openaiClient) send(ctx context.Context, messages []message.Message, too
197 cfg := config.Get()
194 cfg := config.Get()
198 if cfg.Options.Debug {
195 if cfg.Options.Debug {
199 jsonData, _ := json.Marshal(params)
196 jsonData, _ := json.Marshal(params)
200− logging.Debug("Prepared messages", "messages", string(jsonData))
197+ slog.Debug("Prepared messages", "messages", string(jsonData))
202 attempts := 0
199 attempts := 0
@@ -213,7 +210,7 @@ func (o *openaiClient) send(ctx context.Context, messages []message.Message, too
213 return nil, retryErr
210 return nil, retryErr
215 if retry {
212 if retry {
216− logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
213+ slog.Warn(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries))
218 case <-ctx.Done():
215 case <-ctx.Done():
219 return nil, ctx.Err()
216 return nil, ctx.Err()
@@ -254,7 +251,7 @@ func (o *openaiClient) stream(ctx context.Context, messages []message.Message, t
254 cfg := config.Get()
251 cfg := config.Get()
255 if cfg.Options.Debug {
252 if cfg.Options.Debug {
256 jsonData, _ := json.Marshal(params)
253 jsonData, _ := json.Marshal(params)
257− logging.Debug("Prepared messages", "messages", string(jsonData))
254+ slog.Debug("Prepared messages", "messages", string(jsonData))
260 attempts := 0
257 attempts := 0
@@ -291,7 +288,7 @@ func (o *openaiClient) stream(ctx context.Context, messages []message.Message, t
291 if err == nil || errors.Is(err, io.EOF) {
288 if err == nil || errors.Is(err, io.EOF) {
292 if cfg.Options.Debug {
289 if cfg.Options.Debug {
293 jsonData, _ := json.Marshal(acc.ChatCompletion)
290 jsonData, _ := json.Marshal(acc.ChatCompletion)
294− logging.Debug("Response", "messages", string(jsonData))
291+ slog.Debug("Response", "messages", string(jsonData))
296 resultFinishReason := acc.ChatCompletion.Choices[0].FinishReason
293 resultFinishReason := acc.ChatCompletion.Choices[0].FinishReason
297 if resultFinishReason == "" {
294 if resultFinishReason == "" {
@@ -329,7 +326,7 @@ func (o *openaiClient) stream(ctx context.Context, messages []message.Message, t
331 if retry {
328 if retry {
332− logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
329+ slog.Warn(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries))
334 case <-ctx.Done():
331 case <-ctx.Done():
335 // context cancelled
332 // context cancelled
@@ -363,7 +360,7 @@ func (o *openaiClient) shouldRetry(attempts int, err error) (bool, int64, error)
364 // Check for token expiration (401 Unauthorized)
361 // Check for token expiration (401 Unauthorized)
365 if apiErr.StatusCode == 401 {
362 if apiErr.StatusCode == 401 {
366− o.providerOptions.apiKey, err = config.ResolveAPIKey(o.providerOptions.config.APIKey)
363+ o.providerOptions.apiKey, err = config.Get().Resolve(o.providerOptions.config.APIKey)
367 if err != nil {
364 if err != nil {
368 return false, 0, fmt.Errorf("failed to resolve API key: %w", err)
365 return false, 0, fmt.Errorf("failed to resolve API key: %w", err)
@@ -420,6 +417,6 @@ func (o *openaiClient) usage(completion openai.ChatCompletion) TokenUsage {
423−func (a *openaiClient) Model() config.Model {
420+func (a *openaiClient) Model() provider.Model {
424 return a.providerOptions.model(a.providerOptions.modelType)
421 return a.providerOptions.model(a.providerOptions.modelType)
internal/llm/provider/provider.go+11−11
@@ -55,15 +55,15 @@ type Provider interface {
56 StreamResponse(ctx context.Context, messages []message.Message, tools []tools.BaseTool) <-chan ProviderEvent
56 StreamResponse(ctx context.Context, messages []message.Message, tools []tools.BaseTool) <-chan ProviderEvent
58− Model() config.Model
58+ Model() provider.Model
61type providerClientOptions struct {
61type providerClientOptions struct {
62 baseURL string
62 baseURL string
63 config config.ProviderConfig
63 config config.ProviderConfig
64 apiKey string
64 apiKey string
65− modelType config.ModelType
65+ modelType config.SelectedModelType
66− model func(config.ModelType) config.Model
66+ model func(config.SelectedModelType) provider.Model
67 disableCache bool
67 disableCache bool
68 systemMessage string
68 systemMessage string
69 maxTokens int64
69 maxTokens int64
@@ -77,7 +77,7 @@ type ProviderClient interface {
77 send(ctx context.Context, messages []message.Message, tools []tools.BaseTool) (*ProviderResponse, error)
77 send(ctx context.Context, messages []message.Message, tools []tools.BaseTool) (*ProviderResponse, error)
78 stream(ctx context.Context, messages []message.Message, tools []tools.BaseTool) <-chan ProviderEvent
78 stream(ctx context.Context, messages []message.Message, tools []tools.BaseTool) <-chan ProviderEvent
80− Model() config.Model
80+ Model() provider.Model
83type baseProvider[C ProviderClient] struct {
83type baseProvider[C ProviderClient] struct {
@@ -106,11 +106,11 @@ func (p *baseProvider[C]) StreamResponse(ctx context.Context, messages []message
106 return p.client.stream(ctx, messages, tools)
106 return p.client.stream(ctx, messages, tools)
109−func (p *baseProvider[C]) Model() config.Model {
109+func (p *baseProvider[C]) Model() provider.Model {
110 return p.client.Model()
110 return p.client.Model()
113−func WithModel(model config.ModelType) ProviderClientOption {
113+func WithModel(model config.SelectedModelType) ProviderClientOption {
114 return func(options *providerClientOptions) {
114 return func(options *providerClientOptions) {
115 options.modelType = model
115 options.modelType = model
@@ -135,7 +135,7 @@ func WithMaxTokens(maxTokens int64) ProviderClientOption {
137func NewProvider(cfg config.ProviderConfig, opts ...ProviderClientOption) (Provider, error) {
137func NewProvider(cfg config.ProviderConfig, opts ...ProviderClientOption) (Provider, error) {
138− resolvedAPIKey, err := config.ResolveAPIKey(cfg.APIKey)
138+ resolvedAPIKey, err := config.Get().Resolve(cfg.APIKey)
139 if err != nil {
139 if err != nil {
140 return nil, fmt.Errorf("failed to resolve API key for provider %s: %w", cfg.ID, err)
140 return nil, fmt.Errorf("failed to resolve API key for provider %s: %w", cfg.ID, err)
@@ -145,14 +145,14 @@ func NewProvider(cfg config.ProviderConfig, opts ...ProviderClientOption) (Provi
145 config: cfg,
145 config: cfg,
146 apiKey: resolvedAPIKey,
146 apiKey: resolvedAPIKey,
147 extraHeaders: cfg.ExtraHeaders,
147 extraHeaders: cfg.ExtraHeaders,
148− model: func(tp config.ModelType) config.Model {
148+ model: func(tp config.SelectedModelType) provider.Model {
149− return config.GetModel(tp)
149+ return *config.Get().GetModelByType(tp)
152 for _, o := range opts {
152 for _, o := range opts {
153 o(&clientOptions)
153 o(&clientOptions)
155− switch cfg.ProviderType {
155+ switch cfg.Type {
156 case provider.TypeAnthropic:
156 case provider.TypeAnthropic:
157 return &baseProvider[AnthropicClient]{
157 return &baseProvider[AnthropicClient]{
158 options: clientOptions,
158 options: clientOptions,
@@ -190,5 +190,5 @@ func NewProvider(cfg config.ProviderConfig, opts ...ProviderClientOption) (Provi
190 client: newOpenAIClient(clientOptions),
190 client: newOpenAIClient(clientOptions),
193− return nil, fmt.Errorf("provider not supported: %s", cfg.ProviderType)
193+ return nil, fmt.Errorf("provider not supported: %s", cfg.Type)
internal/llm/provider/vertexai.go+2−2
@@ -2,8 +2,8 @@ package provider
6− "github.com/charmbracelet/crush/internal/logging"
7 "google.golang.org/genai"
7 "google.golang.org/genai"
@@ -18,7 +18,7 @@ func newVertexAIClient(opts providerClientOptions) VertexAIClient {
18 Backend: genai.BackendVertexAI,
18 Backend: genai.BackendVertexAI,
20 if err != nil {
20 if err != nil {
21− logging.Error("Failed to create VertexAI client", "error", err)
21+ slog.Error("Failed to create VertexAI client", "error", err)
22 return nil
22 return nil
internal/llm/tools/bash.go+5−4
@@ -8,7 +8,6 @@ import (
11− "github.com/charmbracelet/crush/internal/config"
12 "github.com/charmbracelet/crush/internal/permission"
11 "github.com/charmbracelet/crush/internal/permission"
13 "github.com/charmbracelet/crush/internal/shell"
12 "github.com/charmbracelet/crush/internal/shell"
@@ -29,6 +28,7 @@ type BashResponseMetadata struct {
30type bashTool struct {
29type bashTool struct {
31 permissions permission.Service
30 permissions permission.Service
@@ -244,9 +244,10 @@ Important:
244- Never update git config`, bannedCommandsStr, MaxOutputLength)
244- Never update git config`, bannedCommandsStr, MaxOutputLength)
247−func NewBashTool(permission permission.Service) BaseTool {
247+func NewBashTool(permission permission.Service, workingDir string) BaseTool {
248 return &bashTool{
248 return &bashTool{
249 permissions: permission,
249 permissions: permission,
250+ workingDir: workingDir,
@@ -317,7 +318,7 @@ func (b *bashTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
317 p := b.permissions.Request(
318 p := b.permissions.Request(
318 permission.CreatePermissionRequest{
319 permission.CreatePermissionRequest{
319 SessionID: sessionID,
320 SessionID: sessionID,
320− Path: config.WorkingDirectory(),
321+ Path: b.workingDir,
321 ToolName: BashToolName,
322 ToolName: BashToolName,
322 Action: "execute",
323 Action: "execute",
323 Description: fmt.Sprintf("Execute command: %s", params.Command),
324 Description: fmt.Sprintf("Execute command: %s", params.Command),
@@ -337,7 +338,7 @@ func (b *bashTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
337 defer cancel()
338 defer cancel()
339 stdout, stderr, err := shell.
340 stdout, stderr, err := shell.
340− GetPersistentShell(config.WorkingDirectory()).
341+ GetPersistentShell(b.workingDir).
341 Exec(ctx, params.Command)
342 Exec(ctx, params.Command)
342 interrupted := shell.IsInterrupt(err)
343 interrupted := shell.IsInterrupt(err)
343 exitCode := shell.ExitCode(err)
344 exitCode := shell.ExitCode(err)
internal/llm/tools/edit.go+17−16
@@ -4,15 +4,15 @@ import (
5 "encoding/json"
5 "encoding/json"
8 "path/filepath"
9 "path/filepath"
12− "github.com/charmbracelet/crush/internal/config"
13 "github.com/charmbracelet/crush/internal/diff"
13 "github.com/charmbracelet/crush/internal/diff"
14 "github.com/charmbracelet/crush/internal/history"
14 "github.com/charmbracelet/crush/internal/history"
15− "github.com/charmbracelet/crush/internal/logging"
15+
16 "github.com/charmbracelet/crush/internal/lsp"
16 "github.com/charmbracelet/crush/internal/lsp"
17 "github.com/charmbracelet/crush/internal/permission"
17 "github.com/charmbracelet/crush/internal/permission"
@@ -40,6 +40,7 @@ type editTool struct {
40 lspClients map[string]*lsp.Client
40 lspClients map[string]*lsp.Client
41 permissions permission.Service
41 permissions permission.Service
42 files history.Service
42 files history.Service
@@ -98,11 +99,12 @@ WINDOWS NOTES:
98Remember: when making multiple file edits in a row to the same file, you should prefer to send all edits in a single message with multiple calls to this tool, rather than multiple messages with a single call each.`
99Remember: when making multiple file edits in a row to the same file, you should prefer to send all edits in a single message with multiple calls to this tool, rather than multiple messages with a single call each.`
101−func NewEditTool(lspClients map[string]*lsp.Client, permissions permission.Service, files history.Service) BaseTool {
102+func NewEditTool(lspClients map[string]*lsp.Client, permissions permission.Service, files history.Service, workingDir string) BaseTool {
102 return &editTool{
103 return &editTool{
103 lspClients: lspClients,
104 lspClients: lspClients,
104 permissions: permissions,
105 permissions: permissions,
105 files: files,
106 files: files,
107+ workingDir: workingDir,
@@ -143,8 +145,7 @@ func (e *editTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
145 if !filepath.IsAbs(params.FilePath) {
147 if !filepath.IsAbs(params.FilePath) {
146− wd := config.WorkingDirectory()
147− params.FilePath = filepath.Join(wd, params.FilePath)
148+ params.FilePath = filepath.Join(e.workingDir, params.FilePath)
150 var response ToolResponse
151 var response ToolResponse
@@ -205,9 +206,9 @@ func (e *editTool) createNewFile(ctx context.Context, filePath, content string)
205 _, additions, removals := diff.GenerateDiff(
206 _, additions, removals := diff.GenerateDiff(
208− filePath,
209+ strings.TrimPrefix(filePath, e.workingDir),
210− rootDir := config.WorkingDirectory()
211+ rootDir := e.workingDir
211 permissionPath := filepath.Dir(filePath)
212 permissionPath := filepath.Dir(filePath)
212 if strings.HasPrefix(filePath, rootDir) {
213 if strings.HasPrefix(filePath, rootDir) {
213 permissionPath = rootDir
214 permissionPath = rootDir
@@ -246,7 +247,7 @@ func (e *editTool) createNewFile(ctx context.Context, filePath, content string)
246 _, err = e.files.CreateVersion(ctx, sessionID, filePath, content)
247 _, err = e.files.CreateVersion(ctx, sessionID, filePath, content)
247 if err != nil {
248 if err != nil {
248 // Log error but don't fail the operation
249 // Log error but don't fail the operation
249− logging.Debug("Error creating file history version", "error", err)
250+ slog.Debug("Error creating file history version", "error", err)
252 recordFileWrite(filePath)
253 recordFileWrite(filePath)
@@ -317,10 +318,10 @@ func (e *editTool) deleteContent(ctx context.Context, filePath, oldString string
317 _, additions, removals := diff.GenerateDiff(
318 _, additions, removals := diff.GenerateDiff(
318 oldContent,
319 oldContent,
319 newContent,
320 newContent,
320− filePath,
321+ strings.TrimPrefix(filePath, e.workingDir),
323− rootDir := config.WorkingDirectory()
324+ rootDir := e.workingDir
324 permissionPath := filepath.Dir(filePath)
325 permissionPath := filepath.Dir(filePath)
325 if strings.HasPrefix(filePath, rootDir) {
326 if strings.HasPrefix(filePath, rootDir) {
326 permissionPath = rootDir
327 permissionPath = rootDir
@@ -361,13 +362,13 @@ func (e *editTool) deleteContent(ctx context.Context, filePath, oldString string
361 // User Manually changed the content store an intermediate version
362 // User Manually changed the content store an intermediate version
362 _, err = e.files.CreateVersion(ctx, sessionID, filePath, oldContent)
363 _, err = e.files.CreateVersion(ctx, sessionID, filePath, oldContent)
363 if err != nil {
364 if err != nil {
364− logging.Debug("Error creating file history version", "error", err)
365+ slog.Debug("Error creating file history version", "error", err)
367 // Store the new version
368 // Store the new version
368 _, err = e.files.CreateVersion(ctx, sessionID, filePath, "")
369 _, err = e.files.CreateVersion(ctx, sessionID, filePath, "")
369 if err != nil {
370 if err != nil {
370− logging.Debug("Error creating file history version", "error", err)
371+ slog.Debug("Error creating file history version", "error", err)
373 recordFileWrite(filePath)
374 recordFileWrite(filePath)
@@ -440,9 +441,9 @@ func (e *editTool) replaceContent(ctx context.Context, filePath, oldString, newS
440 _, additions, removals := diff.GenerateDiff(
441 _, additions, removals := diff.GenerateDiff(
441 oldContent,
442 oldContent,
442 newContent,
443 newContent,
443− filePath,
444+ strings.TrimPrefix(filePath, e.workingDir),
445− rootDir := config.WorkingDirectory()
446+ rootDir := e.workingDir
446 permissionPath := filepath.Dir(filePath)
447 permissionPath := filepath.Dir(filePath)
447 if strings.HasPrefix(filePath, rootDir) {
448 if strings.HasPrefix(filePath, rootDir) {
448 permissionPath = rootDir
449 permissionPath = rootDir
@@ -483,13 +484,13 @@ func (e *editTool) replaceContent(ctx context.Context, filePath, oldString, newS
483 // User Manually changed the content store an intermediate version
484 // User Manually changed the content store an intermediate version
484 _, err = e.files.CreateVersion(ctx, sessionID, filePath, oldContent)
485 _, err = e.files.CreateVersion(ctx, sessionID, filePath, oldContent)
485 if err != nil {
486 if err != nil {
486− logging.Debug("Error creating file history version", "error", err)
487+ slog.Debug("Error creating file history version", "error", err)
489 // Store the new version
490 // Store the new version
490 _, err = e.files.CreateVersion(ctx, sessionID, filePath, newContent)
491 _, err = e.files.CreateVersion(ctx, sessionID, filePath, newContent)
491 if err != nil {
492 if err != nil {
492− logging.Debug("Error creating file history version", "error", err)
493+ slog.Debug("Error creating file history version", "error", err)
495 recordFileWrite(filePath)
496 recordFileWrite(filePath)
internal/llm/tools/fetch.go+4−3
@@ -11,7 +11,6 @@ import (
12 md "github.com/JohannesKaufmann/html-to-markdown"
12 md "github.com/JohannesKaufmann/html-to-markdown"
13 "github.com/PuerkitoBio/goquery"
13 "github.com/PuerkitoBio/goquery"
14− "github.com/charmbracelet/crush/internal/config"
15 "github.com/charmbracelet/crush/internal/permission"
14 "github.com/charmbracelet/crush/internal/permission"
@@ -30,6 +29,7 @@ type FetchPermissionsParams struct {
30type fetchTool struct {
29type fetchTool struct {
31 client *http.Client
30 client *http.Client
32 permissions permission.Service
31 permissions permission.Service
@@ -65,7 +65,7 @@ TIPS:
65- Set appropriate timeouts for potentially slow websites`
65- Set appropriate timeouts for potentially slow websites`
68−func NewFetchTool(permissions permission.Service) BaseTool {
68+func NewFetchTool(permissions permission.Service, workingDir string) BaseTool {
69 return &fetchTool{
69 return &fetchTool{
70 client: &http.Client{
70 client: &http.Client{
71 Timeout: 30 * time.Second,
71 Timeout: 30 * time.Second,
@@ -76,6 +76,7 @@ func NewFetchTool(permissions permission.Service) BaseTool {
78 permissions: permissions,
78 permissions: permissions,
79+ workingDir: workingDir,
@@ -133,7 +134,7 @@ func (t *fetchTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
133 p := t.permissions.Request(
134 p := t.permissions.Request(
134 permission.CreatePermissionRequest{
135 permission.CreatePermissionRequest{
135 SessionID: sessionID,
136 SessionID: sessionID,
136− Path: config.WorkingDirectory(),
137+ Path: t.workingDir,
137 ToolName: FetchToolName,
138 ToolName: FetchToolName,
138 Action: "fetch",
139 Action: "fetch",
139 Description: fmt.Sprintf("Fetch content from URL: %s", params.URL),
140 Description: fmt.Sprintf("Fetch content from URL: %s", params.URL),
internal/llm/tools/glob.go+10−7
@@ -5,14 +5,13 @@ import (
6 "encoding/json"
6 "encoding/json"
9 "path/filepath"
10 "path/filepath"
13− "github.com/charmbracelet/crush/internal/config"
14 "github.com/charmbracelet/crush/internal/fsext"
14 "github.com/charmbracelet/crush/internal/fsext"
15− "github.com/charmbracelet/crush/internal/logging"
@@ -68,10 +67,14 @@ type GlobResponseMetadata struct {
68 Truncated bool `json:"truncated"`
67 Truncated bool `json:"truncated"`
71−type globTool struct{}
70+type globTool struct {
73−func NewGlobTool() BaseTool {
74+func NewGlobTool(workingDir string) BaseTool {
74− return &globTool{}
75+ return &globTool{
76+ workingDir: workingDir,
77func (g *globTool) Name() string {
80func (g *globTool) Name() string {
@@ -108,7 +111,7 @@ func (g *globTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
109 searchPath := params.Path
112 searchPath := params.Path
110 if searchPath == "" {
113 if searchPath == "" {
111− searchPath = config.WorkingDirectory()
114+ searchPath = g.workingDir
114 files, truncated, err := globFiles(params.Pattern, searchPath, 100)
117 files, truncated, err := globFiles(params.Pattern, searchPath, 100)
@@ -143,7 +146,7 @@ func globFiles(pattern, searchPath string, limit int) ([]string, bool, error) {
143 if err == nil {
146 if err == nil {
144 return matches, len(matches) >= limit && limit > 0, nil
147 return matches, len(matches) >= limit && limit > 0, nil
146− logging.Warn(fmt.Sprintf("Ripgrep execution failed: %v. Falling back to doublestar.", err))
149+ slog.Warn(fmt.Sprintf("Ripgrep execution failed: %v. Falling back to doublestar.", err))
149 return fsext.GlobWithDoubleStar(pattern, searchPath, limit)
152 return fsext.GlobWithDoubleStar(pattern, searchPath, limit)
internal/llm/tools/grep.go+8−5
@@ -16,7 +16,6 @@ import (
19− "github.com/charmbracelet/crush/internal/config"
20 "github.com/charmbracelet/crush/internal/fsext"
19 "github.com/charmbracelet/crush/internal/fsext"
@@ -89,7 +88,9 @@ type GrepResponseMetadata struct {
89 Truncated bool `json:"truncated"`
88 Truncated bool `json:"truncated"`
92−type grepTool struct{}
91+type grepTool struct {
95 GrepToolName = "grep"
96 GrepToolName = "grep"
@@ -136,8 +137,10 @@ TIPS:
136- Use literal_text=true when searching for exact text containing special characters like dots, parentheses, etc.`
137- Use literal_text=true when searching for exact text containing special characters like dots, parentheses, etc.`
139−func NewGrepTool() BaseTool {
140+func NewGrepTool(workingDir string) BaseTool {
140− return &grepTool{}
141+ return &grepTool{
142+ workingDir: workingDir,
143func (g *grepTool) Name() string {
146func (g *grepTool) Name() string {
@@ -200,7 +203,7 @@ func (g *grepTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
201 searchPath := params.Path
204 searchPath := params.Path
202 if searchPath == "" {
205 if searchPath == "" {
203− searchPath = config.WorkingDirectory()
206+ searchPath = g.workingDir
206 matches, truncated, err := searchFiles(searchPattern, searchPath, params.Include, 100)
209 matches, truncated, err := searchFiles(searchPattern, searchPath, params.Include, 100)
internal/llm/tools/ls.go+9−6
@@ -8,7 +8,6 @@ import (
8 "path/filepath"
8 "path/filepath"
11− "github.com/charmbracelet/crush/internal/config"
12 "github.com/charmbracelet/crush/internal/fsext"
11 "github.com/charmbracelet/crush/internal/fsext"
@@ -29,7 +28,9 @@ type LSResponseMetadata struct {
29 Truncated bool `json:"truncated"`
28 Truncated bool `json:"truncated"`
32−type lsTool struct{}
31+type lsTool struct {
35 LSToolName = "ls"
36 LSToolName = "ls"
@@ -70,8 +71,10 @@ TIPS:
70- Combine with other tools for more effective exploration`
71- Combine with other tools for more effective exploration`
73−func NewLsTool() BaseTool {
74+func NewLsTool(workingDir string) BaseTool {
74− return &lsTool{}
75+ return &lsTool{
76+ workingDir: workingDir,
77func (l *lsTool) Name() string {
80func (l *lsTool) Name() string {
@@ -107,11 +110,11 @@ func (l *lsTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) {
108 searchPath := params.Path
111 searchPath := params.Path
109 if searchPath == "" {
112 if searchPath == "" {
110− searchPath = config.WorkingDirectory()
113+ searchPath = l.workingDir
113 if !filepath.IsAbs(searchPath) {
116 if !filepath.IsAbs(searchPath) {
114− searchPath = filepath.Join(config.WorkingDirectory(), searchPath)
117+ searchPath = filepath.Join(l.workingDir, searchPath)
117 if _, err := os.Stat(searchPath); os.IsNotExist(err) {
120 if _, err := os.Stat(searchPath); os.IsNotExist(err) {
internal/llm/tools/view.go+5−4
@@ -10,7 +10,6 @@ import (
10 "path/filepath"
10 "path/filepath"
13− "github.com/charmbracelet/crush/internal/config"
14 "github.com/charmbracelet/crush/internal/lsp"
13 "github.com/charmbracelet/crush/internal/lsp"
@@ -22,6 +21,7 @@ type ViewParams struct {
23type viewTool struct {
22type viewTool struct {
24 lspClients map[string]*lsp.Client
23 lspClients map[string]*lsp.Client
27type ViewResponseMetadata struct {
27type ViewResponseMetadata struct {
@@ -71,9 +71,10 @@ TIPS:
71- When viewing large files, use the offset parameter to read specific sections`
71- When viewing large files, use the offset parameter to read specific sections`
74−func NewViewTool(lspClients map[string]*lsp.Client) BaseTool {
74+func NewViewTool(lspClients map[string]*lsp.Client, workingDir string) BaseTool {
75 return &viewTool{
75 return &viewTool{
76− lspClients,
76+ lspClients: lspClients,
77+ workingDir: workingDir,
@@ -117,7 +118,7 @@ func (v *viewTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
117 // Handle relative paths
118 // Handle relative paths
118 filePath := params.FilePath
119 filePath := params.FilePath
119 if !filepath.IsAbs(filePath) {
120 if !filepath.IsAbs(filePath) {
120− filePath = filepath.Join(config.WorkingDirectory(), filePath)
121+ filePath = filepath.Join(v.workingDir, filePath)
123 // Check if file exists
124 // Check if file exists
internal/llm/tools/write.go+10−8
@@ -4,15 +4,15 @@ import (
5 "encoding/json"
5 "encoding/json"
8 "path/filepath"
9 "path/filepath"
12− "github.com/charmbracelet/crush/internal/config"
13 "github.com/charmbracelet/crush/internal/diff"
13 "github.com/charmbracelet/crush/internal/diff"
14 "github.com/charmbracelet/crush/internal/history"
14 "github.com/charmbracelet/crush/internal/history"
15− "github.com/charmbracelet/crush/internal/logging"
15+
16 "github.com/charmbracelet/crush/internal/lsp"
16 "github.com/charmbracelet/crush/internal/lsp"
17 "github.com/charmbracelet/crush/internal/permission"
17 "github.com/charmbracelet/crush/internal/permission"
@@ -32,6 +32,7 @@ type writeTool struct {
32 lspClients map[string]*lsp.Client
32 lspClients map[string]*lsp.Client
33 permissions permission.Service
33 permissions permission.Service
34 files history.Service
34 files history.Service
37type WriteResponseMetadata struct {
38type WriteResponseMetadata struct {
@@ -76,11 +77,12 @@ TIPS:
76- Always include descriptive comments when making changes to existing code`
77- Always include descriptive comments when making changes to existing code`
79−func NewWriteTool(lspClients map[string]*lsp.Client, permissions permission.Service, files history.Service) BaseTool {
80+func NewWriteTool(lspClients map[string]*lsp.Client, permissions permission.Service, files history.Service, workingDir string) BaseTool {
80 return &writeTool{
81 return &writeTool{
81 lspClients: lspClients,
82 lspClients: lspClients,
82 permissions: permissions,
83 permissions: permissions,
83 files: files,
84 files: files,
85+ workingDir: workingDir,
@@ -122,7 +124,7 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
123 filePath := params.FilePath
125 filePath := params.FilePath
124 if !filepath.IsAbs(filePath) {
126 if !filepath.IsAbs(filePath) {
125− filePath = filepath.Join(config.WorkingDirectory(), filePath)
127+ filePath = filepath.Join(w.workingDir, filePath)
128 fileInfo, err := os.Stat(filePath)
130 fileInfo, err := os.Stat(filePath)
@@ -167,10 +169,10 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
167 diff, additions, removals := diff.GenerateDiff(
169 diff, additions, removals := diff.GenerateDiff(
168 oldContent,
170 oldContent,
169 params.Content,
171 params.Content,
170− filePath,
172+ strings.TrimPrefix(filePath, w.workingDir),
173− rootDir := config.WorkingDirectory()
175+ rootDir := w.workingDir
174 permissionPath := filepath.Dir(filePath)
176 permissionPath := filepath.Dir(filePath)
175 if strings.HasPrefix(filePath, rootDir) {
177 if strings.HasPrefix(filePath, rootDir) {
176 permissionPath = rootDir
178 permissionPath = rootDir
@@ -211,13 +213,13 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
211 // User Manually changed the content store an intermediate version
213 // User Manually changed the content store an intermediate version
212 _, err = w.files.CreateVersion(ctx, sessionID, filePath, oldContent)
214 _, err = w.files.CreateVersion(ctx, sessionID, filePath, oldContent)
213 if err != nil {
215 if err != nil {
214− logging.Debug("Error creating file history version", "error", err)
216+ slog.Debug("Error creating file history version", "error", err)
217 // Store the new version
219 // Store the new version
218 _, err = w.files.CreateVersion(ctx, sessionID, filePath, params.Content)
220 _, err = w.files.CreateVersion(ctx, sessionID, filePath, params.Content)
219 if err != nil {
221 if err != nil {
220− logging.Debug("Error creating file history version", "error", err)
222+ slog.Debug("Error creating file history version", "error", err)
223 recordFileWrite(filePath)
225 recordFileWrite(filePath)
internal/log/log.gonew file+61
@@ -0,0 +1,61 @@
11+ "gopkg.in/natefinch/lumberjack.v2"
14+var initOnce sync.Once
16+func Init(logFile string, debug bool) {
18+ logRotator := &lumberjack.Logger{
20+ MaxSize: 10, // Max size in MB
21+ MaxBackups: 0, // Number of backups
23+ Compress: false, // Enable compression
26+ level := slog.LevelInfo
28+ level = slog.LevelDebug
31+ logger := slog.NewJSONHandler(logRotator, &slog.HandlerOptions{
36+ slog.SetDefault(slog.New(logger))
40+func RecoverPanic(name string, cleanup func()) {
41+ if r := recover(); r != nil {
42+ // Create a timestamped panic log file
43+ timestamp := time.Now().Format("20060102-150405")
44+ filename := fmt.Sprintf("crush-panic-%s-%s.log", name, timestamp)
46+ file, err := os.Create(filename)
50+ // Write panic information and stack trace
51+ fmt.Fprintf(file, "Panic in %s: %v\n\n", name, r)
52+ fmt.Fprintf(file, "Time: %s\n\n", time.Now().Format(time.RFC3339))
53+ fmt.Fprintf(file, "Stack Trace:\n%s\n", debug.Stack())
55+ // Execute cleanup function if provided
internal/logging/logger.godeleted−209
@@ -1,209 +0,0 @@
16−func getCaller() string {
18− if _, file, line, ok := runtime.Caller(2); ok {
19− // caller = fmt.Sprintf("%s:%d", filepath.Base(file), line)
20− caller = fmt.Sprintf("%s:%d", file, line)
27−func Info(msg string, args ...any) {
28− source := getCaller()
29− slog.Info(msg, append([]any{"source", source}, args...)...)
32−func Debug(msg string, args ...any) {
33− // slog.Debug(msg, args...)
34− source := getCaller()
35− slog.Debug(msg, append([]any{"source", source}, args...)...)
38−func Warn(msg string, args ...any) {
39− slog.Warn(msg, args...)
42−func Error(msg string, args ...any) {
43− slog.Error(msg, args...)
46−func InfoPersist(msg string, args ...any) {
47− args = append(args, persistKeyArg, true)
48− slog.Info(msg, args...)
51−func DebugPersist(msg string, args ...any) {
52− args = append(args, persistKeyArg, true)
53− slog.Debug(msg, args...)
56−func WarnPersist(msg string, args ...any) {
57− args = append(args, persistKeyArg, true)
58− slog.Warn(msg, args...)
61−func ErrorPersist(msg string, args ...any) {
62− args = append(args, persistKeyArg, true)
63− slog.Error(msg, args...)
66−// RecoverPanic is a common function to handle panics gracefully.
67−// It logs the error, creates a panic log file with stack trace,
68−// and executes an optional cleanup function before returning.
69−func RecoverPanic(name string, cleanup func()) {
70− if r := recover(); r != nil {
72− ErrorPersist(fmt.Sprintf("Panic in %s: %v", name, r))
74− // Create a timestamped panic log file
75− timestamp := time.Now().Format("20060102-150405")
76− filename := fmt.Sprintf("crush-panic-%s-%s.log", name, timestamp)
78− file, err := os.Create(filename)
80− ErrorPersist(fmt.Sprintf("Failed to create panic log: %v", err))
84− // Write panic information and stack trace
85− fmt.Fprintf(file, "Panic in %s: %v\n\n", name, r)
86− fmt.Fprintf(file, "Time: %s\n\n", time.Now().Format(time.RFC3339))
87− fmt.Fprintf(file, "Stack Trace:\n%s\n", debug.Stack())
89− InfoPersist(fmt.Sprintf("Panic details written to %s", filename))
92− // Execute cleanup function if provided
99−// Message Logging for Debug
100−var MessageDir string
102−func GetSessionPrefix(sessionId string) string {
103− return sessionId[:8]
106−var sessionLogMutex sync.Mutex
108−func AppendToSessionLogFile(sessionId string, filename string, content string) string {
109− if MessageDir == "" || sessionId == "" {
112− sessionPrefix := GetSessionPrefix(sessionId)
114− sessionLogMutex.Lock()
115− defer sessionLogMutex.Unlock()
117− sessionPath := fmt.Sprintf("%s/%s", MessageDir, sessionPrefix)
118− if _, err := os.Stat(sessionPath); os.IsNotExist(err) {
119− if err := os.MkdirAll(sessionPath, 0o766); err != nil {
120− Error("Failed to create session directory", "dirpath", sessionPath, "error", err)
125− filePath := fmt.Sprintf("%s/%s", sessionPath, filename)
127− f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
129− Error("Failed to open session log file", "filepath", filePath, "error", err)
134− // Append chunk to file
135− _, err = f.WriteString(content)
137− Error("Failed to write chunk to session log file", "filepath", filePath, "error", err)
143−func WriteRequestMessageJson(sessionId string, requestSeqId int, message any) string {
144− if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
147− msgJson, err := json.Marshal(message)
149− Error("Failed to marshal message", "session_id", sessionId, "request_seq_id", requestSeqId, "error", err)
152− return WriteRequestMessage(sessionId, requestSeqId, string(msgJson))
155−func WriteRequestMessage(sessionId string, requestSeqId int, message string) string {
156− if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
159− filename := fmt.Sprintf("%d_request.json", requestSeqId)
161− return AppendToSessionLogFile(sessionId, filename, message)
164−func AppendToStreamSessionLogJson(sessionId string, requestSeqId int, jsonableChunk any) string {
165− if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
168− chunkJson, err := json.Marshal(jsonableChunk)
170− Error("Failed to marshal message", "session_id", sessionId, "request_seq_id", requestSeqId, "error", err)
173− return AppendToStreamSessionLog(sessionId, requestSeqId, string(chunkJson))
176−func AppendToStreamSessionLog(sessionId string, requestSeqId int, chunk string) string {
177− if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
180− filename := fmt.Sprintf("%d_response_stream.log", requestSeqId)
181− return AppendToSessionLogFile(sessionId, filename, chunk)
184−func WriteChatResponseJson(sessionId string, requestSeqId int, response any) string {
185− if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
188− responseJson, err := json.Marshal(response)
190− Error("Failed to marshal response", "session_id", sessionId, "request_seq_id", requestSeqId, "error", err)
193− filename := fmt.Sprintf("%d_response.json", requestSeqId)
195− return AppendToSessionLogFile(sessionId, filename, string(responseJson))
198−func WriteToolResultsJson(sessionId string, requestSeqId int, toolResults any) string {
199− if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
202− toolResultsJson, err := json.Marshal(toolResults)
204− Error("Failed to marshal tool results", "session_id", sessionId, "request_seq_id", requestSeqId, "error", err)
207− filename := fmt.Sprintf("%d_tool_results.json", requestSeqId)
208− return AppendToSessionLogFile(sessionId, filename, string(toolResultsJson))
internal/logging/message.godeleted−21
@@ -1,21 +0,0 @@
7−// LogMessage is the event payload for a log message
8−type LogMessage struct {
12− Persist bool // used when we want to show the mesage in the status bar
13− PersistTime time.Duration // used when we want to show the mesage in the status bar
14− Message string `json:"msg"`
internal/logging/writer.godeleted−102
@@ -1,102 +0,0 @@
11− "github.com/charmbracelet/crush/internal/pubsub"
12− "github.com/go-logfmt/logfmt"
16− persistKeyArg = "$_persist"
17− PersistTimeArg = "$_persist_time"
21− messages []LogMessage
22− *pubsub.Broker[LogMessage]
26−func (l *LogData) Add(msg LogMessage) {
28− defer l.lock.Unlock()
29− l.messages = append(l.messages, msg)
30− l.Publish(pubsub.CreatedEvent, msg)
33−func (l *LogData) List() []LogMessage {
35− defer l.lock.Unlock()
39−var defaultLogData = &LogData{
40− messages: make([]LogMessage, 0),
41− Broker: pubsub.NewBroker[LogMessage](),
46−func (w *writer) Write(p []byte) (int, error) {
47− d := logfmt.NewDecoder(bytes.NewReader(p))
51− ID: fmt.Sprintf("%d", time.Now().UnixNano()),
55− switch string(d.Key()) {
57− parsed, err := time.Parse(time.RFC3339, string(d.Value()))
59− return 0, fmt.Errorf("parsing time: %w", err)
63− msg.Level = strings.ToLower(string(d.Value()))
65− msg.Message = string(d.Value())
67− if string(d.Key()) == persistKeyArg {
69− } else if string(d.Key()) == PersistTimeArg {
70− parsed, err := time.ParseDuration(string(d.Value()))
74− msg.PersistTime = parsed
76− msg.Attributes = append(msg.Attributes, Attr{
77− Key: string(d.Key()),
78− Value: string(d.Value()),
83− defaultLogData.Add(msg)
91−func NewWriter() *writer {
96−func Subscribe(ctx context.Context) <-chan pubsub.Event[LogMessage] {
97− return defaultLogData.Subscribe(ctx)
100−func List() []LogMessage {
101− return defaultLogData.List()
internal/lsp/client.go+22−21
@@ -6,6 +6,7 @@ import (
6 "encoding/json"
6 "encoding/json"
11 "path/filepath"
12 "path/filepath"
@@ -15,7 +16,7 @@ import (
17 "github.com/charmbracelet/crush/internal/config"
18 "github.com/charmbracelet/crush/internal/config"
18− "github.com/charmbracelet/crush/internal/logging"
19+ "github.com/charmbracelet/crush/internal/log"
19 "github.com/charmbracelet/crush/internal/lsp/protocol"
20 "github.com/charmbracelet/crush/internal/lsp/protocol"
@@ -96,17 +97,17 @@ func NewClient(ctx context.Context, command string, args ...string) (*Client, er
96 go func() {
97 go func() {
97 scanner := bufio.NewScanner(stderr)
98 scanner := bufio.NewScanner(stderr)
98 for scanner.Scan() {
99 for scanner.Scan() {
99− logging.Error("LSP Server", "err", scanner.Text())
100+ slog.Error("LSP Server", "err", scanner.Text())
101 if err := scanner.Err(); err != nil {
102 if err := scanner.Err(); err != nil {
102− logging.Error("Error reading", "err", err)
103+ slog.Error("Error reading", "err", err)
106 // Start message handling loop
107 // Start message handling loop
107 go func() {
108 go func() {
108− defer logging.RecoverPanic("LSP-message-handler", func() {
109+ defer log.RecoverPanic("LSP-message-handler", func() {
109− logging.ErrorPersist("LSP message handler crashed, LSP functionality may be impaired")
110+ slog.Error("LSP message handler crashed, LSP functionality may be impaired")
111 client.handleMessages()
112 client.handleMessages()
@@ -300,7 +301,7 @@ func (c *Client) WaitForServerReady(ctx context.Context) error {
300 defer ticker.Stop()
301 defer ticker.Stop()
302 if cfg.Options.DebugLSP {
303 if cfg.Options.DebugLSP {
303− logging.Debug("Waiting for LSP server to be ready...")
304+ slog.Debug("Waiting for LSP server to be ready...")
306 // Determine server type for specialized initialization
307 // Determine server type for specialized initialization
@@ -309,7 +310,7 @@ func (c *Client) WaitForServerReady(ctx context.Context) error {
309 // For TypeScript-like servers, we need to open some key files first
310 // For TypeScript-like servers, we need to open some key files first
310 if serverType == ServerTypeTypeScript {
311 if serverType == ServerTypeTypeScript {
311 if cfg.Options.DebugLSP {
312 if cfg.Options.DebugLSP {
312− logging.Debug("TypeScript-like server detected, opening key configuration files")
313+ slog.Debug("TypeScript-like server detected, opening key configuration files")
314 c.openKeyConfigFiles(ctx)
315 c.openKeyConfigFiles(ctx)
@@ -326,15 +327,15 @@ func (c *Client) WaitForServerReady(ctx context.Context) error {
326 // Server responded successfully
327 // Server responded successfully
327 c.SetServerState(StateReady)
328 c.SetServerState(StateReady)
328 if cfg.Options.DebugLSP {
329 if cfg.Options.DebugLSP {
329− logging.Debug("LSP server is ready")
330+ slog.Debug("LSP server is ready")
331 return nil
332 return nil
333− logging.Debug("LSP server not ready yet", "error", err, "serverType", serverType)
334+ slog.Debug("LSP server not ready yet", "error", err, "serverType", serverType)
336 if cfg.Options.DebugLSP {
337 if cfg.Options.DebugLSP {
337− logging.Debug("LSP server not ready yet", "error", err, "serverType", serverType)
338+ slog.Debug("LSP server not ready yet", "error", err, "serverType", serverType)
@@ -376,7 +377,7 @@ func (c *Client) detectServerType() ServerType {
377// openKeyConfigFiles opens important configuration files that help initialize the server
378// openKeyConfigFiles opens important configuration files that help initialize the server
378func (c *Client) openKeyConfigFiles(ctx context.Context) {
379func (c *Client) openKeyConfigFiles(ctx context.Context) {
379− workDir := config.WorkingDirectory()
380+ workDir := config.Get().WorkingDir()
380 serverType := c.detectServerType()
381 serverType := c.detectServerType()
382 var filesToOpen []string
383 var filesToOpen []string
@@ -409,9 +410,9 @@ func (c *Client) openKeyConfigFiles(ctx context.Context) {
409 if _, err := os.Stat(file); err == nil {
410 if _, err := os.Stat(file); err == nil {
410 // File exists, try to open it
411 // File exists, try to open it
411 if err := c.OpenFile(ctx, file); err != nil {
412 if err := c.OpenFile(ctx, file); err != nil {
412− logging.Debug("Failed to open key config file", "file", file, "error", err)
413+ slog.Debug("Failed to open key config file", "file", file, "error", err)
414− logging.Debug("Opened key config file for initialization", "file", file)
415+ slog.Debug("Opened key config file for initialization", "file", file)
@@ -464,7 +465,7 @@ func (c *Client) pingTypeScriptServer(ctx context.Context) error {
466 // If we have no open TypeScript files, try to find and open one
467 // If we have no open TypeScript files, try to find and open one
467− workDir := config.WorkingDirectory()
468+ workDir := config.Get().WorkingDir()
468 err := filepath.WalkDir(workDir, func(path string, d os.DirEntry, err error) error {
469 err := filepath.WalkDir(workDir, func(path string, d os.DirEntry, err error) error {
469 if err != nil {
470 if err != nil {
470 return err
471 return err
@@ -487,7 +488,7 @@ func (c *Client) pingTypeScriptServer(ctx context.Context) error {
487 return nil
488 return nil
489 if err != nil {
490 if err != nil {
490− logging.Debug("Error walking directory for TypeScript files", "error", err)
491+ slog.Debug("Error walking directory for TypeScript files", "error", err)
493 // Final fallback - just try a generic capability
494 // Final fallback - just try a generic capability
@@ -527,7 +528,7 @@ func (c *Client) openTypeScriptFiles(ctx context.Context, workDir string) {
527 if err := c.OpenFile(ctx, path); err == nil {
528 if err := c.OpenFile(ctx, path); err == nil {
528 filesOpened++
529 filesOpened++
529 if cfg.Options.DebugLSP {
530 if cfg.Options.DebugLSP {
530− logging.Debug("Opened TypeScript file for initialization", "file", path)
531+ slog.Debug("Opened TypeScript file for initialization", "file", path)
@@ -536,11 +537,11 @@ func (c *Client) openTypeScriptFiles(ctx context.Context, workDir string) {
538 if err != nil && cfg.Options.DebugLSP {
539 if err != nil && cfg.Options.DebugLSP {
539− logging.Debug("Error walking directory for TypeScript files", "error", err)
540+ slog.Debug("Error walking directory for TypeScript files", "error", err)
542 if cfg.Options.DebugLSP {
543 if cfg.Options.DebugLSP {
543− logging.Debug("Opened TypeScript files for initialization", "count", filesOpened)
544+ slog.Debug("Opened TypeScript files for initialization", "count", filesOpened)
@@ -681,7 +682,7 @@ func (c *Client) CloseFile(ctx context.Context, filepath string) error {
683 if cfg.Options.DebugLSP {
684 if cfg.Options.DebugLSP {
684− logging.Debug("Closing file", "file", filepath)
685+ slog.Debug("Closing file", "file", filepath)
686 if err := c.Notify(ctx, "textDocument/didClose", params); err != nil {
687 if err := c.Notify(ctx, "textDocument/didClose", params); err != nil {
687 return err
688 return err
@@ -720,12 +721,12 @@ func (c *Client) CloseAllFiles(ctx context.Context) {
720 for _, filePath := range filesToClose {
721 for _, filePath := range filesToClose {
721 err := c.CloseFile(ctx, filePath)
722 err := c.CloseFile(ctx, filePath)
722 if err != nil && cfg.Options.DebugLSP {
723 if err != nil && cfg.Options.DebugLSP {
723− logging.Warn("Error closing file", "file", filePath, "error", err)
724+ slog.Warn("Error closing file", "file", filePath, "error", err)
727 if cfg.Options.DebugLSP {
728 if cfg.Options.DebugLSP {
728− logging.Debug("Closed all files", "files", filesToClose)
729+ slog.Debug("Closed all files", "files", filesToClose)
internal/lsp/handlers.go+8−7
@@ -2,9 +2,10 @@ package lsp
4 "encoding/json"
4 "encoding/json"
6 "github.com/charmbracelet/crush/internal/config"
7 "github.com/charmbracelet/crush/internal/config"
7− "github.com/charmbracelet/crush/internal/logging"
8+
8 "github.com/charmbracelet/crush/internal/lsp/protocol"
9 "github.com/charmbracelet/crush/internal/lsp/protocol"
9 "github.com/charmbracelet/crush/internal/lsp/util"
10 "github.com/charmbracelet/crush/internal/lsp/util"
@@ -18,7 +19,7 @@ func HandleWorkspaceConfiguration(params json.RawMessage) (any, error) {
18func HandleRegisterCapability(params json.RawMessage) (any, error) {
19func HandleRegisterCapability(params json.RawMessage) (any, error) {
19 var registerParams protocol.RegistrationParams
20 var registerParams protocol.RegistrationParams
20 if err := json.Unmarshal(params, ®isterParams); err != nil {
21 if err := json.Unmarshal(params, ®isterParams); err != nil {
21− logging.Error("Error unmarshaling registration params", "error", err)
22+ slog.Error("Error unmarshaling registration params", "error", err)
22 return nil, err
23 return nil, err
@@ -28,13 +29,13 @@ func HandleRegisterCapability(params json.RawMessage) (any, error) {
28 // Parse the registration options
29 // Parse the registration options
29 optionsJSON, err := json.Marshal(reg.RegisterOptions)
30 optionsJSON, err := json.Marshal(reg.RegisterOptions)
30 if err != nil {
31 if err != nil {
31− logging.Error("Error marshaling registration options", "error", err)
32+ slog.Error("Error marshaling registration options", "error", err)
35 var options protocol.DidChangeWatchedFilesRegistrationOptions
36 var options protocol.DidChangeWatchedFilesRegistrationOptions
36 if err := json.Unmarshal(optionsJSON, &options); err != nil {
37 if err := json.Unmarshal(optionsJSON, &options); err != nil {
37− logging.Error("Error unmarshaling registration options", "error", err)
38+ slog.Error("Error unmarshaling registration options", "error", err)
@@ -54,7 +55,7 @@ func HandleApplyEdit(params json.RawMessage) (any, error) {
55 err := util.ApplyWorkspaceEdit(edit.Edit)
56 err := util.ApplyWorkspaceEdit(edit.Edit)
56 if err != nil {
57 if err != nil {
57− logging.Error("Error applying workspace edit", "error", err)
58+ slog.Error("Error applying workspace edit", "error", err)
58 return protocol.ApplyWorkspaceEditResult{Applied: false, FailureReason: err.Error()}, nil
59 return protocol.ApplyWorkspaceEditResult{Applied: false, FailureReason: err.Error()}, nil
@@ -89,7 +90,7 @@ func HandleServerMessage(params json.RawMessage) {
90 if err := json.Unmarshal(params, &msg); err == nil {
91 if err := json.Unmarshal(params, &msg); err == nil {
91 if cfg.Options.DebugLSP {
92 if cfg.Options.DebugLSP {
92− logging.Debug("Server message", "type", msg.Type, "message", msg.Message)
93+ slog.Debug("Server message", "type", msg.Type, "message", msg.Message)
@@ -97,7 +98,7 @@ func HandleServerMessage(params json.RawMessage) {
97func HandleDiagnostics(client *Client, params json.RawMessage) {
98func HandleDiagnostics(client *Client, params json.RawMessage) {
98 var diagParams protocol.PublishDiagnosticsParams
99 var diagParams protocol.PublishDiagnosticsParams
99 if err := json.Unmarshal(params, &diagParams); err != nil {
100 if err := json.Unmarshal(params, &diagParams); err != nil {
100− logging.Error("Error unmarshaling diagnostics params", "error", err)
101+ slog.Error("Error unmarshaling diagnostics params", "error", err)
internal/lsp/protocol/tsprotocol.go+1−1
@@ -55,7 +55,7 @@ type ApplyWorkspaceEditResult struct {
55 // Indicates whether the edit was applied or not.
55 // Indicates whether the edit was applied or not.
56 Applied bool `json:"applied"`
56 Applied bool `json:"applied"`
57 // An optional textual description for why the edit was not applied.
57 // An optional textual description for why the edit was not applied.
58− // This may be used by the server for diagnostic logging or to provide
58+ // This may be used by the server for diagnostic slog.or to provide
59 // a suitable error for a request that triggered the edit.
59 // a suitable error for a request that triggered the edit.
60 FailureReason string `json:"failureReason,omitempty"`
60 FailureReason string `json:"failureReason,omitempty"`
61 // Depending on the client's failure handling strategy `failedChange` might
61 // Depending on the client's failure handling strategy `failedChange` might
internal/lsp/transport.go+16−16
@@ -6,10 +6,10 @@ import (
6 "encoding/json"
6 "encoding/json"
11 "github.com/charmbracelet/crush/internal/config"
12 "github.com/charmbracelet/crush/internal/config"
12− "github.com/charmbracelet/crush/internal/logging"
15// Write writes an LSP message to the given writer
15// Write writes an LSP message to the given writer
@@ -21,7 +21,7 @@ func WriteMessage(w io.Writer, msg *Message) error {
21 cfg := config.Get()
21 cfg := config.Get()
23 if cfg.Options.DebugLSP {
23 if cfg.Options.DebugLSP {
24− logging.Debug("Sending message to server", "method", msg.Method, "id", msg.ID)
24+ slog.Debug("Sending message to server", "method", msg.Method, "id", msg.ID)
27 _, err = fmt.Fprintf(w, "Content-Length: %d\r\n\r\n", len(data))
27 _, err = fmt.Fprintf(w, "Content-Length: %d\r\n\r\n", len(data))
@@ -50,7 +50,7 @@ func ReadMessage(r *bufio.Reader) (*Message, error) {
50 line = strings.TrimSpace(line)
50 line = strings.TrimSpace(line)
52 if cfg.Options.DebugLSP {
52 if cfg.Options.DebugLSP {
53− logging.Debug("Received header", "line", line)
53+ slog.Debug("Received header", "line", line)
56 if line == "" {
56 if line == "" {
@@ -66,7 +66,7 @@ func ReadMessage(r *bufio.Reader) (*Message, error) {
68 if cfg.Options.DebugLSP {
68 if cfg.Options.DebugLSP {
69− logging.Debug("Content-Length", "length", contentLength)
69+ slog.Debug("Content-Length", "length", contentLength)
72 // Read content
72 // Read content
@@ -77,7 +77,7 @@ func ReadMessage(r *bufio.Reader) (*Message, error) {
79 if cfg.Options.DebugLSP {
79 if cfg.Options.DebugLSP {
80− logging.Debug("Received content", "content", string(content))
80+ slog.Debug("Received content", "content", string(content))
83 // Parse message
83 // Parse message
@@ -96,7 +96,7 @@ func (c *Client) handleMessages() {
96 msg, err := ReadMessage(c.stdout)
96 msg, err := ReadMessage(c.stdout)
97 if err != nil {
97 if err != nil {
98 if cfg.Options.DebugLSP {
98 if cfg.Options.DebugLSP {
99− logging.Error("Error reading message", "error", err)
99+ slog.Error("Error reading message", "error", err)
@@ -104,7 +104,7 @@ func (c *Client) handleMessages() {
104 // Handle server->client request (has both Method and ID)
104 // Handle server->client request (has both Method and ID)
105 if msg.Method != "" && msg.ID != 0 {
105 if msg.Method != "" && msg.ID != 0 {
106 if cfg.Options.DebugLSP {
106 if cfg.Options.DebugLSP {
107− logging.Debug("Received request from server", "method", msg.Method, "id", msg.ID)
107+ slog.Debug("Received request from server", "method", msg.Method, "id", msg.ID)
110 response := &Message{
110 response := &Message{
@@ -144,7 +144,7 @@ func (c *Client) handleMessages() {
145 // Send response back to server
145 // Send response back to server
146 if err := WriteMessage(c.stdin, response); err != nil {
146 if err := WriteMessage(c.stdin, response); err != nil {
147− logging.Error("Error sending response to server", "error", err)
147+ slog.Error("Error sending response to server", "error", err)
@@ -158,11 +158,11 @@ func (c *Client) handleMessages() {
160 if cfg.Options.DebugLSP {
160 if cfg.Options.DebugLSP {
161− logging.Debug("Handling notification", "method", msg.Method)
161+ slog.Debug("Handling notification", "method", msg.Method)
163 go handler(msg.Params)
163 go handler(msg.Params)
164 } else if cfg.Options.DebugLSP {
164 } else if cfg.Options.DebugLSP {
165− logging.Debug("No handler for notification", "method", msg.Method)
165+ slog.Debug("No handler for notification", "method", msg.Method)
@@ -175,12 +175,12 @@ func (c *Client) handleMessages() {
177 if cfg.Options.DebugLSP {
177 if cfg.Options.DebugLSP {
178− logging.Debug("Received response for request", "id", msg.ID)
178+ slog.Debug("Received response for request", "id", msg.ID)
180 ch <- msg
180 ch <- msg
181 close(ch)
181 close(ch)
182 } else if cfg.Options.DebugLSP {
182 } else if cfg.Options.DebugLSP {
183− logging.Debug("No handler for response", "id", msg.ID)
183+ slog.Debug("No handler for response", "id", msg.ID)
@@ -192,7 +192,7 @@ func (c *Client) Call(ctx context.Context, method string, params any, result any
192 id := c.nextID.Add(1)
192 id := c.nextID.Add(1)
194 if cfg.Options.DebugLSP {
194 if cfg.Options.DebugLSP {
195− logging.Debug("Making call", "method", method, "id", id)
195+ slog.Debug("Making call", "method", method, "id", id)
198 msg, err := NewRequest(id, method, params)
198 msg, err := NewRequest(id, method, params)
@@ -218,14 +218,14 @@ func (c *Client) Call(ctx context.Context, method string, params any, result any
220 if cfg.Options.DebugLSP {
220 if cfg.Options.DebugLSP {
221− logging.Debug("Request sent", "method", method, "id", id)
221+ slog.Debug("Request sent", "method", method, "id", id)
224 // Wait for response
224 // Wait for response
225 resp := <-ch
225 resp := <-ch
227 if cfg.Options.DebugLSP {
227 if cfg.Options.DebugLSP {
228− logging.Debug("Received response", "id", id)
228+ slog.Debug("Received response", "id", id)
231 if resp.Error != nil {
231 if resp.Error != nil {
@@ -251,7 +251,7 @@ func (c *Client) Call(ctx context.Context, method string, params any, result any
251func (c *Client) Notify(ctx context.Context, method string, params any) error {
251func (c *Client) Notify(ctx context.Context, method string, params any) error {
252 cfg := config.Get()
252 cfg := config.Get()
253 if cfg.Options.DebugLSP {
253 if cfg.Options.DebugLSP {
254− logging.Debug("Sending notification", "method", method)
254+ slog.Debug("Sending notification", "method", method)
257 msg, err := NewNotification(method, params)
257 msg, err := NewNotification(method, params)
internal/lsp/watcher/watcher.go+42−41
@@ -3,6 +3,7 @@ package watcher
7 "path/filepath"
8 "path/filepath"
@@ -11,7 +12,7 @@ import (
12 "github.com/bmatcuk/doublestar/v4"
13 "github.com/bmatcuk/doublestar/v4"
13 "github.com/charmbracelet/crush/internal/config"
14 "github.com/charmbracelet/crush/internal/config"
14− "github.com/charmbracelet/crush/internal/logging"
15+
15 "github.com/charmbracelet/crush/internal/lsp"
16 "github.com/charmbracelet/crush/internal/lsp"
16 "github.com/charmbracelet/crush/internal/lsp/protocol"
17 "github.com/charmbracelet/crush/internal/lsp/protocol"
17 "github.com/fsnotify/fsnotify"
18 "github.com/fsnotify/fsnotify"
@@ -45,7 +46,7 @@ func NewWorkspaceWatcher(client *lsp.Client) *WorkspaceWatcher {
45func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watchers []protocol.FileSystemWatcher) {
46func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watchers []protocol.FileSystemWatcher) {
46 cfg := config.Get()
47 cfg := config.Get()
48− logging.Debug("Adding file watcher registrations")
49+ slog.Debug("Adding file watcher registrations")
49 w.registrationMu.Lock()
50 w.registrationMu.Lock()
50 defer w.registrationMu.Unlock()
51 defer w.registrationMu.Unlock()
@@ -54,33 +55,33 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
55 // Print detailed registration information for debugging
56 // Print detailed registration information for debugging
56 if cfg.Options.DebugLSP {
57 if cfg.Options.DebugLSP {
57− logging.Debug("Adding file watcher registrations",
58+ slog.Debug("Adding file watcher registrations",
59 "watchers", len(watchers),
60 "watchers", len(watchers),
60 "total", len(w.registrations),
61 "total", len(w.registrations),
63 for i, watcher := range watchers {
64 for i, watcher := range watchers {
64− logging.Debug("Registration", "index", i+1)
65+ slog.Debug("Registration", "index", i+1)
66 // Log the GlobPattern
67 // Log the GlobPattern
67 switch v := watcher.GlobPattern.Value.(type) {
68 switch v := watcher.GlobPattern.Value.(type) {
68 case string:
69 case string:
69− logging.Debug("GlobPattern", "pattern", v)
70+ slog.Debug("GlobPattern", "pattern", v)
70 case protocol.RelativePattern:
71 case protocol.RelativePattern:
71− logging.Debug("GlobPattern", "pattern", v.Pattern)
72+ slog.Debug("GlobPattern", "pattern", v.Pattern)
73 // Log BaseURI details
74 // Log BaseURI details
74 switch u := v.BaseURI.Value.(type) {
75 switch u := v.BaseURI.Value.(type) {
75 case string:
76 case string:
76− logging.Debug("BaseURI", "baseURI", u)
77+ slog.Debug("BaseURI", "baseURI", u)
77 case protocol.DocumentUri:
78 case protocol.DocumentUri:
78− logging.Debug("BaseURI", "baseURI", u)
79+ slog.Debug("BaseURI", "baseURI", u)
80− logging.Debug("BaseURI", "baseURI", u)
81+ slog.Debug("BaseURI", "baseURI", u)
83− logging.Debug("GlobPattern", "unknown type", fmt.Sprintf("%T", v))
84+ slog.Debug("GlobPattern", "unknown type", fmt.Sprintf("%T", v))
86 // Log WatchKind
87 // Log WatchKind
@@ -89,13 +90,13 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
89 watchKind = *watcher.Kind
90 watchKind = *watcher.Kind
92− logging.Debug("WatchKind", "kind", watchKind)
93+ slog.Debug("WatchKind", "kind", watchKind)
96 // Determine server type for specialized handling
97 // Determine server type for specialized handling
97 serverName := getServerNameFromContext(ctx)
98 serverName := getServerNameFromContext(ctx)
98− logging.Debug("Server type detected", "serverName", serverName)
99+ slog.Debug("Server type detected", "serverName", serverName)
100 // Check if this server has sent file watchers
101 // Check if this server has sent file watchers
101 hasFileWatchers := len(watchers) > 0
102 hasFileWatchers := len(watchers) > 0
@@ -123,7 +124,7 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
123 filesOpened += highPriorityFilesOpened
124 filesOpened += highPriorityFilesOpened
125 if cfg.Options.DebugLSP {
126 if cfg.Options.DebugLSP {
126− logging.Debug("Opened high-priority files",
127+ slog.Debug("Opened high-priority files",
127 "count", highPriorityFilesOpened,
128 "count", highPriorityFilesOpened,
128 "serverName", serverName)
129 "serverName", serverName)
@@ -131,7 +132,7 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
131 // If we've already opened enough high-priority files, we might not need more
132 // If we've already opened enough high-priority files, we might not need more
132 if filesOpened >= maxFilesToOpen {
133 if filesOpened >= maxFilesToOpen {
133 if cfg.Options.DebugLSP {
134 if cfg.Options.DebugLSP {
134− logging.Debug("Reached file limit with high-priority files",
135+ slog.Debug("Reached file limit with high-priority files",
135 "filesOpened", filesOpened,
136 "filesOpened", filesOpened,
136 "maxFiles", maxFilesToOpen)
137 "maxFiles", maxFilesToOpen)
@@ -149,7 +150,7 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
149 if d.IsDir() {
150 if d.IsDir() {
150 if path != w.workspacePath && shouldExcludeDir(path) {
151 if path != w.workspacePath && shouldExcludeDir(path) {
151 if cfg.Options.DebugLSP {
152 if cfg.Options.DebugLSP {
152− logging.Debug("Skipping excluded directory", "path", path)
153+ slog.Debug("Skipping excluded directory", "path", path)
154 return filepath.SkipDir
155 return filepath.SkipDir
@@ -177,7 +178,7 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
178 elapsedTime := time.Since(startTime)
179 elapsedTime := time.Since(startTime)
179 if cfg.Options.DebugLSP {
180 if cfg.Options.DebugLSP {
180− logging.Debug("Limited workspace scan complete",
181+ slog.Debug("Limited workspace scan complete",
181 "filesOpened", filesOpened,
182 "filesOpened", filesOpened,
182 "maxFiles", maxFilesToOpen,
183 "maxFiles", maxFilesToOpen,
183 "elapsedTime", elapsedTime.Seconds(),
184 "elapsedTime", elapsedTime.Seconds(),
@@ -186,11 +187,11 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
188 if err != nil && cfg.Options.DebugLSP {
189 if err != nil && cfg.Options.DebugLSP {
189− logging.Debug("Error scanning workspace for files to open", "error", err)
190+ slog.Debug("Error scanning workspace for files to open", "error", err)
192 } else if cfg.Options.DebugLSP {
193 } else if cfg.Options.DebugLSP {
193− logging.Debug("Using on-demand file loading for server", "server", serverName)
194+ slog.Debug("Using on-demand file loading for server", "server", serverName)
@@ -266,7 +267,7 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
266 matches, err := doublestar.Glob(os.DirFS(w.workspacePath), pattern)
267 matches, err := doublestar.Glob(os.DirFS(w.workspacePath), pattern)
267 if err != nil {
268 if err != nil {
268 if cfg.Options.DebugLSP {
269 if cfg.Options.DebugLSP {
269− logging.Debug("Error finding high-priority files", "pattern", pattern, "error", err)
270+ slog.Debug("Error finding high-priority files", "pattern", pattern, "error", err)
@@ -300,12 +301,12 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
300 fullPath := filesToOpen[j]
301 fullPath := filesToOpen[j]
301 if err := w.client.OpenFile(ctx, fullPath); err != nil {
302 if err := w.client.OpenFile(ctx, fullPath); err != nil {
302 if cfg.Options.DebugLSP {
303 if cfg.Options.DebugLSP {
303− logging.Debug("Error opening high-priority file", "path", fullPath, "error", err)
304+ slog.Debug("Error opening high-priority file", "path", fullPath, "error", err)
306 filesOpened++
307 filesOpened++
307 if cfg.Options.DebugLSP {
308 if cfg.Options.DebugLSP {
308− logging.Debug("Opened high-priority file", "path", fullPath)
309+ slog.Debug("Opened high-priority file", "path", fullPath)
@@ -334,7 +335,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
336 serverName := getServerNameFromContext(ctx)
337 serverName := getServerNameFromContext(ctx)
337− logging.Debug("Starting workspace watcher", "workspacePath", workspacePath, "serverName", serverName)
338+ slog.Debug("Starting workspace watcher", "workspacePath", workspacePath, "serverName", serverName)
339 // Register handler for file watcher registrations from the server
340 // Register handler for file watcher registrations from the server
340 lsp.RegisterFileWatchHandler(func(id string, watchers []protocol.FileSystemWatcher) {
341 lsp.RegisterFileWatchHandler(func(id string, watchers []protocol.FileSystemWatcher) {
@@ -343,7 +344,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
344 watcher, err := fsnotify.NewWatcher()
345 watcher, err := fsnotify.NewWatcher()
345 if err != nil {
346 if err != nil {
346− logging.Error("Error creating watcher", "error", err)
347+ slog.Error("Error creating watcher", "error", err)
348 defer watcher.Close()
349 defer watcher.Close()
@@ -357,7 +358,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
357 if d.IsDir() && path != workspacePath {
358 if d.IsDir() && path != workspacePath {
358 if shouldExcludeDir(path) {
359 if shouldExcludeDir(path) {
359 if cfg.Options.DebugLSP {
360 if cfg.Options.DebugLSP {
360− logging.Debug("Skipping excluded directory", "path", path)
361+ slog.Debug("Skipping excluded directory", "path", path)
362 return filepath.SkipDir
363 return filepath.SkipDir
@@ -367,14 +368,14 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
367 if d.IsDir() {
368 if d.IsDir() {
368 err = watcher.Add(path)
369 err = watcher.Add(path)
369 if err != nil {
370 if err != nil {
370− logging.Error("Error watching path", "path", path, "error", err)
371+ slog.Error("Error watching path", "path", path, "error", err)
374 return nil
375 return nil
376 if err != nil {
377 if err != nil {
377− logging.Error("Error walking workspace", "error", err)
378+ slog.Error("Error walking workspace", "error", err)
380 // Event loop
381 // Event loop
@@ -396,7 +397,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
396 // Skip excluded directories
397 // Skip excluded directories
397 if !shouldExcludeDir(event.Name) {
398 if !shouldExcludeDir(event.Name) {
398 if err := watcher.Add(event.Name); err != nil {
399 if err := watcher.Add(event.Name); err != nil {
399− logging.Error("Error adding directory to watcher", "path", event.Name, "error", err)
400+ slog.Error("Error adding directory to watcher", "path", event.Name, "error", err)
@@ -411,7 +412,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
411 // Debug logging
412 // Debug logging
412 if cfg.Options.DebugLSP {
413 if cfg.Options.DebugLSP {
413 matched, kind := w.isPathWatched(event.Name)
414 matched, kind := w.isPathWatched(event.Name)
414− logging.Debug("File event",
415+ slog.Debug("File event",
415 "path", event.Name,
416 "path", event.Name,
416 "operation", event.Op.String(),
417 "operation", event.Op.String(),
417 "watched", matched,
418 "watched", matched,
@@ -431,7 +432,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
431 // Just send the notification if needed
432 // Just send the notification if needed
432 info, err := os.Stat(event.Name)
433 info, err := os.Stat(event.Name)
433 if err != nil {
434 if err != nil {
434− logging.Error("Error getting file info", "path", event.Name, "error", err)
435+ slog.Error("Error getting file info", "path", event.Name, "error", err)
437 if !info.IsDir() && watchKind&protocol.WatchCreate != 0 {
438 if !info.IsDir() && watchKind&protocol.WatchCreate != 0 {
@@ -459,7 +460,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
462− logging.Error("Error watching file", "error", err)
463+ slog.Error("Error watching file", "error", err)
@@ -584,7 +585,7 @@ func matchesSimpleGlob(pattern, path string) bool {
584 // Fall back to simple matching for simpler patterns
585 // Fall back to simple matching for simpler patterns
585 matched, err := filepath.Match(pattern, path)
586 matched, err := filepath.Match(pattern, path)
586 if err != nil {
587 if err != nil {
587− logging.Error("Error matching pattern", "pattern", pattern, "path", path, "error", err)
588+ slog.Error("Error matching pattern", "pattern", pattern, "path", path, "error", err)
588 return false
589 return false
@@ -595,7 +596,7 @@ func matchesSimpleGlob(pattern, path string) bool {
595func (w *WorkspaceWatcher) matchesPattern(path string, pattern protocol.GlobPattern) bool {
596func (w *WorkspaceWatcher) matchesPattern(path string, pattern protocol.GlobPattern) bool {
596 patternInfo, err := pattern.AsPattern()
597 patternInfo, err := pattern.AsPattern()
597 if err != nil {
598 if err != nil {
598− logging.Error("Error parsing pattern", "pattern", pattern, "error", err)
599+ slog.Error("Error parsing pattern", "pattern", pattern, "error", err)
599 return false
600 return false
@@ -620,7 +621,7 @@ func (w *WorkspaceWatcher) matchesPattern(path string, pattern protocol.GlobPatt
620 // Make path relative to basePath for matching
621 // Make path relative to basePath for matching
621 relPath, err := filepath.Rel(basePath, path)
622 relPath, err := filepath.Rel(basePath, path)
622 if err != nil {
623 if err != nil {
623− logging.Error("Error getting relative path", "path", path, "basePath", basePath, "error", err)
624+ slog.Error("Error getting relative path", "path", path, "basePath", basePath, "error", err)
624 return false
625 return false
626 relPath = filepath.ToSlash(relPath)
627 relPath = filepath.ToSlash(relPath)
@@ -663,14 +664,14 @@ func (w *WorkspaceWatcher) handleFileEvent(ctx context.Context, uri string, chan
663 } else if changeType == protocol.FileChangeType(protocol.Changed) && w.client.IsFileOpen(filePath) {
664 } else if changeType == protocol.FileChangeType(protocol.Changed) && w.client.IsFileOpen(filePath) {
664 err := w.client.NotifyChange(ctx, filePath)
665 err := w.client.NotifyChange(ctx, filePath)
665 if err != nil {
666 if err != nil {
666− logging.Error("Error notifying change", "error", err)
667+ slog.Error("Error notifying change", "error", err)
671 // Notify LSP server about the file event using didChangeWatchedFiles
672 // Notify LSP server about the file event using didChangeWatchedFiles
672 if err := w.notifyFileEvent(ctx, uri, changeType); err != nil {
673 if err := w.notifyFileEvent(ctx, uri, changeType); err != nil {
673− logging.Error("Error notifying LSP server about file event", "error", err)
674+ slog.Error("Error notifying LSP server about file event", "error", err)
@@ -678,7 +679,7 @@ func (w *WorkspaceWatcher) handleFileEvent(ctx context.Context, uri string, chan
678func (w *WorkspaceWatcher) notifyFileEvent(ctx context.Context, uri string, changeType protocol.FileChangeType) error {
679func (w *WorkspaceWatcher) notifyFileEvent(ctx context.Context, uri string, changeType protocol.FileChangeType) error {
679 cfg := config.Get()
680 cfg := config.Get()
680 if cfg.Options.DebugLSP {
681 if cfg.Options.DebugLSP {
681− logging.Debug("Notifying file event",
682+ slog.Debug("Notifying file event",
682 "uri", uri,
683 "uri", uri,
683 "changeType", changeType,
684 "changeType", changeType,
@@ -853,7 +854,7 @@ func shouldExcludeFile(filePath string) bool {
853 // Skip large files
854 // Skip large files
854 if info.Size() > maxFileSize {
855 if info.Size() > maxFileSize {
855 if cfg.Options.DebugLSP {
856 if cfg.Options.DebugLSP {
856− logging.Debug("Skipping large file",
857+ slog.Debug("Skipping large file",
857 "path", filePath,
858 "path", filePath,
858 "size", info.Size(),
859 "size", info.Size(),
859 "maxSize", maxFileSize,
860 "maxSize", maxFileSize,
@@ -891,10 +892,10 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
891 // This helps with project initialization for certain language servers
892 // This helps with project initialization for certain language servers
892 if isHighPriorityFile(path, serverName) {
893 if isHighPriorityFile(path, serverName) {
893 if cfg.Options.DebugLSP {
894 if cfg.Options.DebugLSP {
894− logging.Debug("Opening high-priority file", "path", path, "serverName", serverName)
895+ slog.Debug("Opening high-priority file", "path", path, "serverName", serverName)
896 if err := w.client.OpenFile(ctx, path); err != nil && cfg.Options.DebugLSP {
897 if err := w.client.OpenFile(ctx, path); err != nil && cfg.Options.DebugLSP {
897− logging.Error("Error opening high-priority file", "path", path, "error", err)
898+ slog.Error("Error opening high-priority file", "path", path, "error", err)
@@ -906,7 +907,7 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
906 // Check file size - for preloading we're more conservative
907 // Check file size - for preloading we're more conservative
907 if info.Size() > (1 * 1024 * 1024) { // 1MB limit for preloaded files
908 if info.Size() > (1 * 1024 * 1024) { // 1MB limit for preloaded files
908 if cfg.Options.DebugLSP {
909 if cfg.Options.DebugLSP {
909− logging.Debug("Skipping large file for preloading", "path", path, "size", info.Size())
910+ slog.Debug("Skipping large file for preloading", "path", path, "size", info.Size())
@@ -938,7 +939,7 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
938 if shouldOpen {
939 if shouldOpen {
939 // Don't need to check if it's already open - the client.OpenFile handles that
940 // Don't need to check if it's already open - the client.OpenFile handles that
940 if err := w.client.OpenFile(ctx, path); err != nil && cfg.Options.DebugLSP {
941 if err := w.client.OpenFile(ctx, path); err != nil && cfg.Options.DebugLSP {
941− logging.Error("Error opening file", "path", path, "error", err)
942+ slog.Error("Error opening file", "path", path, "error", err)
internal/permission/permission.go+4−3
@@ -6,7 +6,6 @@ import (
9− "github.com/charmbracelet/crush/internal/config"
10 "github.com/charmbracelet/crush/internal/pubsub"
9 "github.com/charmbracelet/crush/internal/pubsub"
11 "github.com/google/uuid"
10 "github.com/google/uuid"
@@ -44,6 +43,7 @@ type Service interface {
44type permissionService struct {
43type permissionService struct {
45 *pubsub.Broker[PermissionRequest]
44 *pubsub.Broker[PermissionRequest]
47 sessionPermissions []PermissionRequest
47 sessionPermissions []PermissionRequest
48 sessionPermissionsMu sync.RWMutex
48 sessionPermissionsMu sync.RWMutex
49 pendingRequests sync.Map
49 pendingRequests sync.Map
@@ -87,7 +87,7 @@ func (s *permissionService) Request(opts CreatePermissionRequest) bool {
88 dir := filepath.Dir(opts.Path)
88 dir := filepath.Dir(opts.Path)
89 if dir == "." {
89 if dir == "." {
90− dir = config.WorkingDirectory()
90+ dir = s.workingDir
92 permission := PermissionRequest{
92 permission := PermissionRequest{
93 ID: uuid.New().String(),
93 ID: uuid.New().String(),
@@ -125,9 +125,10 @@ func (s *permissionService) AutoApproveSession(sessionID string) {
125 s.autoApproveSessionsMu.Unlock()
125 s.autoApproveSessionsMu.Unlock()
128−func NewPermissionService() Service {
128+func NewPermissionService(workingDir string) Service {
129 return &permissionService{
129 return &permissionService{
130 Broker: pubsub.NewBroker[PermissionRequest](),
130 Broker: pubsub.NewBroker[PermissionRequest](),
131+ workingDir: workingDir,
131 sessionPermissions: make([]PermissionRequest, 0),
132 sessionPermissions: make([]PermissionRequest, 0),
internal/shell/persistent.go+3−4
@@ -1,9 +1,8 @@
1package shell
1package shell
6− "github.com/charmbracelet/crush/internal/logging"
9// PersistentShell is a singleton shell instance that maintains state across the application
8// PersistentShell is a singleton shell instance that maintains state across the application
@@ -30,9 +29,9 @@ func GetPersistentShell(cwd string) *PersistentShell {
30 return shellInstance
29 return shellInstance
33−// loggingAdapter adapts the internal logging package to the Logger interface
32+// slog.dapter adapts the internal slog.package to the Logger interface
34type loggingAdapter struct{}
33type loggingAdapter struct{}
36func (l *loggingAdapter) InfoPersist(msg string, keysAndValues ...interface{}) {
35func (l *loggingAdapter) InfoPersist(msg string, keysAndValues ...interface{}) {
37− logging.InfoPersist(msg, keysAndValues...)
36+ slog.Info(msg, keysAndValues...)
internal/tui/components/anim/anim.go+3−2
@@ -261,7 +261,7 @@ func (a Anim) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
263// View renders the current state of the animation.
263// View renders the current state of the animation.
264−func (a Anim) View() tea.View {
264+func (a Anim) View() string {
265 var b strings.Builder
265 var b strings.Builder
266 for i := range a.width {
266 for i := range a.width {
@@ -284,7 +284,8 @@ func (a Anim) View() tea.View {
284 if a.initialized && a.labelWidth > 0 {
284 if a.initialized && a.labelWidth > 0 {
285 b.WriteString(a.ellipsisFrames[a.ellipsisStep/ellipsisAnimSpeed])
285 b.WriteString(a.ellipsisFrames[a.ellipsisStep/ellipsisAnimSpeed])
287− return tea.NewView(b.String())
288+ return b.String()
290// Step is a command that triggers the next step in the animation.
291// Step is a command that triggers the next step in the animation.
internal/tui/components/anim/example/main.go+3−3
@@ -50,20 +50,20 @@ func (m model) View() tea.View {
52 v := tea.NewView("")
52 v := tea.NewView("")
53− v.SetBackgroundColor(m.bgColor)
53+ v.BackgroundColor = m.bgColor
55 if m.quitting {
55 if m.quitting {
59 if a, ok := m.anim.(anim.Anim); ok {
59 if a, ok := m.anim.(anim.Anim); ok {
60− l := lipgloss.NewLayer(a.View().String()).
60+ l := lipgloss.NewLayer(a.View()).
61 Width(a.Width()).
61 Width(a.Width()).
62 X(m.w/2 - a.Width()/2).
62 X(m.w/2 - a.Width()/2).
63 Y(m.h / 2)
63 Y(m.h / 2)
65 v = tea.NewView(lipgloss.NewCanvas(l))
65 v = tea.NewView(lipgloss.NewCanvas(l))
66− v.SetBackgroundColor(m.bgColor)
66+ v.BackgroundColor = m.bgColor
internal/tui/components/chat/chat.go+4−6
@@ -101,12 +101,10 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
103// View renders the message list or an initial screen if empty.
103// View renders the message list or an initial screen if empty.
104−func (m *messageListCmp) View() tea.View {
104+func (m *messageListCmp) View() string {
106− lipgloss.JoinVertical(
105+ return lipgloss.JoinVertical(
107− lipgloss.Left,
106+ lipgloss.Left,
108− m.listCmp.View().String(),
107+ m.listCmp.View(),
internal/tui/components/chat/editor/editor.go+9−11
@@ -14,7 +14,6 @@ import (
14 tea "github.com/charmbracelet/bubbletea/v2"
14 tea "github.com/charmbracelet/bubbletea/v2"
15 "github.com/charmbracelet/crush/internal/app"
15 "github.com/charmbracelet/crush/internal/app"
16 "github.com/charmbracelet/crush/internal/fsext"
16 "github.com/charmbracelet/crush/internal/fsext"
17− "github.com/charmbracelet/crush/internal/logging"
18 "github.com/charmbracelet/crush/internal/message"
17 "github.com/charmbracelet/crush/internal/message"
19 "github.com/charmbracelet/crush/internal/session"
18 "github.com/charmbracelet/crush/internal/session"
20 "github.com/charmbracelet/crush/internal/tui/components/chat"
19 "github.com/charmbracelet/crush/internal/tui/components/chat"
@@ -153,8 +152,7 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
153 return m, nil
152 return m, nil
154 case filepicker.FilePickedMsg:
153 case filepicker.FilePickedMsg:
155 if len(m.attachments) >= maxAttachments {
154 if len(m.attachments) >= maxAttachments {
156− logging.ErrorPersist(fmt.Sprintf("cannot add more than %d images", maxAttachments))
155+ return m, util.ReportError(fmt.Errorf("cannot add more than %d images", maxAttachments))
159 m.attachments = append(m.attachments, msg.Attachment)
157 m.attachments = append(m.attachments, msg.Attachment)
160 return m, nil
158 return m, nil
@@ -265,20 +263,22 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
265 return m, tea.Batch(cmds...)
263 return m, tea.Batch(cmds...)
268−func (m *editorCmp) View() tea.View {
266+func (m *editorCmp) Cursor() *tea.Cursor {
269− t := styles.CurrentTheme()
270 cursor := m.textarea.Cursor()
267 cursor := m.textarea.Cursor()
271 if cursor != nil {
268 if cursor != nil {
272 cursor.X = cursor.X + m.x + 1
269 cursor.X = cursor.X + m.x + 1
273 cursor.Y = cursor.Y + m.y + 1 // adjust for padding
270 cursor.Y = cursor.Y + m.y + 1 // adjust for padding
275+func (m *editorCmp) View() string {
276+ t := styles.CurrentTheme()
275 if len(m.attachments) == 0 {
277 if len(m.attachments) == 0 {
276 content := t.S().Base.Padding(1).Render(
278 content := t.S().Base.Padding(1).Render(
277 m.textarea.View(),
279 m.textarea.View(),
279− view := tea.NewView(content)
280− view.SetCursor(cursor)
281− return view
281+ return content
283 content := t.S().Base.Padding(0, 1, 1, 1).Render(
283 content := t.S().Base.Padding(0, 1, 1, 1).Render(
284 lipgloss.JoinVertical(lipgloss.Top,
284 lipgloss.JoinVertical(lipgloss.Top,
@@ -286,9 +286,7 @@ func (m *editorCmp) View() tea.View {
286 m.textarea.View(),
286 m.textarea.View(),
289− view := tea.NewView(content)
290− view.SetCursor(cursor)
291− return view
289+ return content
294func (m *editorCmp) SetSize(width, height int) tea.Cmd {
292func (m *editorCmp) SetSize(width, height int) tea.Cmd {
internal/tui/components/chat/header/header.go+6−5
@@ -57,9 +57,9 @@ func (p *header) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
57 return p, nil
57 return p, nil
60−func (p *header) View() tea.View {
60+func (p *header) View() string {
61 if p.session.ID == "" {
61 if p.session.ID == "" {
62− return tea.NewView("")
62+ return ""
65 t := styles.CurrentTheme()
65 t := styles.CurrentTheme()
@@ -86,12 +86,12 @@ func (p *header) View() tea.View {
89− return tea.NewView(content)
89+ return content
92func (h *header) details() string {
92func (h *header) details() string {
93 t := styles.CurrentTheme()
93 t := styles.CurrentTheme()
94− cwd := fsext.DirTrim(fsext.PrettyPath(config.WorkingDirectory()), 4)
94+ cwd := fsext.DirTrim(fsext.PrettyPath(config.Get().WorkingDir()), 4)
95 parts := []string{
95 parts := []string{
96 t.S().Muted.Render(cwd),
96 t.S().Muted.Render(cwd),
@@ -111,7 +111,8 @@ func (h *header) details() string {
111 parts = append(parts, t.S().Error.Render(fmt.Sprintf("%s%d", styles.ErrorIcon, errorCount)))
111 parts = append(parts, t.S().Error.Render(fmt.Sprintf("%s%d", styles.ErrorIcon, errorCount)))
114− model := config.GetAgentModel(config.AgentCoder)
114+ agentCfg := config.Get().Agents["coder"]
115+ model := config.Get().GetModelByType(agentCfg.Model)
115 percentage := (float64(h.session.CompletionTokens+h.session.PromptTokens) / float64(model.ContextWindow)) * 100
116 percentage := (float64(h.session.CompletionTokens+h.session.PromptTokens) / float64(model.ContextWindow)) * 100
116 formattedPercentage := t.S().Muted.Render(fmt.Sprintf("%d%%", int(percentage)))
117 formattedPercentage := t.S().Muted.Render(fmt.Sprintf("%d%%", int(percentage)))
117 parts = append(parts, formattedPercentage)
118 parts = append(parts, formattedPercentage)
internal/tui/components/chat/messages/messages.go+10−13
@@ -10,7 +10,6 @@ import (
10 "github.com/charmbracelet/lipgloss/v2"
10 "github.com/charmbracelet/lipgloss/v2"
12 "github.com/charmbracelet/crush/internal/config"
12 "github.com/charmbracelet/crush/internal/config"
13− "github.com/charmbracelet/crush/internal/fur/provider"
14 "github.com/charmbracelet/crush/internal/message"
13 "github.com/charmbracelet/crush/internal/message"
15 "github.com/charmbracelet/crush/internal/tui/components/anim"
14 "github.com/charmbracelet/crush/internal/tui/components/anim"
16 "github.com/charmbracelet/crush/internal/tui/components/core"
15 "github.com/charmbracelet/crush/internal/tui/components/core"
@@ -89,20 +88,20 @@ func (m *messageCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
90// View renders the message component based on its current state.
89// View renders the message component based on its current state.
91// Returns different views for spinning, user, and assistant messages.
90// Returns different views for spinning, user, and assistant messages.
92−func (m *messageCmp) View() tea.View {
91+func (m *messageCmp) View() string {
93 if m.spinning {
92 if m.spinning {
94− return tea.NewView(m.style().PaddingLeft(1).Render(m.anim.View().String()))
93+ return m.style().PaddingLeft(1).Render(m.anim.View())
96 if m.message.ID != "" {
95 if m.message.ID != "" {
97 // this is a user or assistant message
96 // this is a user or assistant message
98 switch m.message.Role {
97 switch m.message.Role {
99 case message.User:
98 case message.User:
100− return tea.NewView(m.renderUserMessage())
99+ return m.renderUserMessage()
102− return tea.NewView(m.renderAssistantMessage())
101+ return m.renderAssistantMessage()
105− return tea.NewView(m.style().Render("No message content"))
104+ return m.style().Render("No message content")
108// GetMessage returns the underlying message data
107// GetMessage returns the underlying message data
@@ -289,20 +288,18 @@ func (m *assistantSectionModel) Update(tea.Msg) (tea.Model, tea.Cmd) {
289 return m, nil
288 return m, nil
292−func (m *assistantSectionModel) View() tea.View {
291+func (m *assistantSectionModel) View() string {
293 t := styles.CurrentTheme()
292 t := styles.CurrentTheme()
294 finishData := m.message.FinishPart()
293 finishData := m.message.FinishPart()
295 finishTime := time.Unix(finishData.Time, 0)
294 finishTime := time.Unix(finishData.Time, 0)
296 duration := finishTime.Sub(m.lastUserMessageTime)
295 duration := finishTime.Sub(m.lastUserMessageTime)
297 infoMsg := t.S().Subtle.Render(duration.String())
296 infoMsg := t.S().Subtle.Render(duration.String())
298 icon := t.S().Subtle.Render(styles.ModelIcon)
297 icon := t.S().Subtle.Render(styles.ModelIcon)
299− model := config.GetProviderModel(provider.InferenceProvider(m.message.Provider), m.message.Model)
298+ model := config.Get().GetModel(m.message.Provider, m.message.Model)
300− modelFormatted := t.S().Muted.Render(model.Name)
299+ modelFormatted := t.S().Muted.Render(model.Model)
301 assistant := fmt.Sprintf("%s %s %s", icon, modelFormatted, infoMsg)
300 assistant := fmt.Sprintf("%s %s %s", icon, modelFormatted, infoMsg)
303− t.S().Base.PaddingLeft(2).Render(
301+ return t.S().Base.PaddingLeft(2).Render(
304− core.Section(assistant, m.width-2),
302+ core.Section(assistant, m.width-2),
internal/tui/components/chat/messages/renderer.go+2−2
@@ -7,10 +7,10 @@ import (
9 "github.com/charmbracelet/crush/internal/fsext"
9 "github.com/charmbracelet/crush/internal/fsext"
10− "github.com/charmbracelet/crush/internal/highlight"
11 "github.com/charmbracelet/crush/internal/llm/agent"
10 "github.com/charmbracelet/crush/internal/llm/agent"
12 "github.com/charmbracelet/crush/internal/llm/tools"
11 "github.com/charmbracelet/crush/internal/llm/tools"
13 "github.com/charmbracelet/crush/internal/tui/components/core"
12 "github.com/charmbracelet/crush/internal/tui/components/core"
13+ "github.com/charmbracelet/crush/internal/tui/highlight"
14 "github.com/charmbracelet/crush/internal/tui/styles"
14 "github.com/charmbracelet/crush/internal/tui/styles"
15 "github.com/charmbracelet/lipgloss/v2"
15 "github.com/charmbracelet/lipgloss/v2"
16 "github.com/charmbracelet/lipgloss/v2/tree"
16 "github.com/charmbracelet/lipgloss/v2/tree"
@@ -542,7 +542,7 @@ func (tr agentRenderer) Render(v *toolCallCmp) string {
543 if v.result.ToolCallID == "" {
543 if v.result.ToolCallID == "" {
544 v.spinning = true
544 v.spinning = true
545− parts = append(parts, v.anim.View().String())
545+ parts = append(parts, v.anim.View())
547 v.spinning = false
547 v.spinning = false
internal/tui/components/chat/messages/tool.go+4−4
@@ -145,19 +145,19 @@ func (m *toolCallCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
146// View renders the tool call component based on its current state.
146// View renders the tool call component based on its current state.
147// Shows either a pending animation or the tool-specific rendered result.
147// Shows either a pending animation or the tool-specific rendered result.
148−func (m *toolCallCmp) View() tea.View {
148+func (m *toolCallCmp) View() string {
149 box := m.style()
149 box := m.style()
151 if !m.call.Finished && !m.cancelled {
151 if !m.call.Finished && !m.cancelled {
152− return tea.NewView(box.Render(m.renderPending()))
152+ return box.Render(m.renderPending())
155 r := registry.lookup(m.call.Name)
155 r := registry.lookup(m.call.Name)
157 if m.isNested {
157 if m.isNested {
158− return tea.NewView(box.Render(r.Render(m)))
158+ return box.Render(r.Render(m))
160− return tea.NewView(box.Render(r.Render(m)))
160+ return box.Render(r.Render(m))
163// State management methods
163// State management methods
internal/tui/components/chat/sidebar/sidebar.go+13−11
@@ -13,7 +13,7 @@ import (
13 "github.com/charmbracelet/crush/internal/diff"
13 "github.com/charmbracelet/crush/internal/diff"
14 "github.com/charmbracelet/crush/internal/fsext"
14 "github.com/charmbracelet/crush/internal/fsext"
15 "github.com/charmbracelet/crush/internal/history"
15 "github.com/charmbracelet/crush/internal/history"
16− "github.com/charmbracelet/crush/internal/logging"
16+
17 "github.com/charmbracelet/crush/internal/lsp"
17 "github.com/charmbracelet/crush/internal/lsp"
18 "github.com/charmbracelet/crush/internal/lsp/protocol"
18 "github.com/charmbracelet/crush/internal/lsp/protocol"
19 "github.com/charmbracelet/crush/internal/pubsub"
19 "github.com/charmbracelet/crush/internal/pubsub"
@@ -94,7 +94,6 @@ func (m *sidebarCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
94 case chat.SessionClearedMsg:
94 case chat.SessionClearedMsg:
95 m.session = session.Session{}
95 m.session = session.Session{}
96 case pubsub.Event[history.File]:
96 case pubsub.Event[history.File]:
97− logging.Info("sidebar", "Received file history event", "file", msg.Payload.Path, "session", msg.Payload.SessionID)
98 return m, m.handleFileHistoryEvent(msg)
97 return m, m.handleFileHistoryEvent(msg)
99 case pubsub.Event[session.Session]:
98 case pubsub.Event[session.Session]:
100 if msg.Type == pubsub.UpdatedEvent {
99 if msg.Type == pubsub.UpdatedEvent {
@@ -106,7 +105,7 @@ func (m *sidebarCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
106 return m, nil
105 return m, nil
109−func (m *sidebarCmp) View() tea.View {
108+func (m *sidebarCmp) View() string {
110 t := styles.CurrentTheme()
109 t := styles.CurrentTheme()
111 parts := []string{}
110 parts := []string{}
112 if !m.compactMode {
111 if !m.compactMode {
@@ -138,9 +137,7 @@ func (m *sidebarCmp) View() tea.View {
138 m.mcpBlock(),
137 m.mcpBlock(),
142− lipgloss.JoinVertical(lipgloss.Left, parts...),
140+ return lipgloss.JoinVertical(lipgloss.Left, parts...)
146func (m *sidebarCmp) handleFileHistoryEvent(event pubsub.Event[history.File]) tea.Cmd {
143func (m *sidebarCmp) handleFileHistoryEvent(event pubsub.Event[history.File]) tea.Cmd {
@@ -161,6 +158,8 @@ func (m *sidebarCmp) handleFileHistoryEvent(event pubsub.Event[history.File]) te
161 before := existing.History.initialVersion.Content
158 before := existing.History.initialVersion.Content
162 after := existing.History.latestVersion.Content
159 after := existing.History.latestVersion.Content
163 path := existing.History.initialVersion.Path
160 path := existing.History.initialVersion.Path
161+ cwd := config.Get().WorkingDir()
162+ path = strings.TrimPrefix(path, cwd)
164 _, additions, deletions := diff.GenerateDiff(before, after, path)
163 _, additions, deletions := diff.GenerateDiff(before, after, path)
165 existing.Additions = additions
164 existing.Additions = additions
166 existing.Deletions = deletions
165 existing.Deletions = deletions
@@ -214,7 +213,9 @@ func (m *sidebarCmp) loadSessionFiles() tea.Msg {
215 sessionFiles := make([]SessionFile, 0, len(fileMap))
214 sessionFiles := make([]SessionFile, 0, len(fileMap))
216 for path, fh := range fileMap {
215 for path, fh := range fileMap {
216+ cwd := config.Get().WorkingDir()
217+ path = strings.TrimPrefix(path, cwd)
217− _, additions, deletions := diff.GenerateDiff(fh.initialVersion.Content, fh.latestVersion.Content, fh.initialVersion.Path)
218+ _, additions, deletions := diff.GenerateDiff(fh.initialVersion.Content, fh.latestVersion.Content, path)
218 sessionFiles = append(sessionFiles, SessionFile{
219 sessionFiles = append(sessionFiles, SessionFile{
219 History: fh,
220 History: fh,
220 FilePath: path,
221 FilePath: path,
@@ -297,7 +298,7 @@ func (m *sidebarCmp) filesBlock() string {
299 extraContent := strings.Join(statusParts, " ")
300 extraContent := strings.Join(statusParts, " ")
300− cwd := config.WorkingDirectory() + string(os.PathSeparator)
301+ cwd := config.Get().WorkingDir() + string(os.PathSeparator)
301 filePath := file.FilePath
302 filePath := file.FilePath
302 filePath = strings.TrimPrefix(filePath, cwd)
303 filePath = strings.TrimPrefix(filePath, cwd)
303 filePath = fsext.DirTrim(fsext.PrettyPath(filePath), 2)
304 filePath = fsext.DirTrim(fsext.PrettyPath(filePath), 2)
@@ -474,12 +475,13 @@ func formatTokensAndCost(tokens, contextWindow int64, cost float64) string {
476func (s *sidebarCmp) currentModelBlock() string {
477func (s *sidebarCmp) currentModelBlock() string {
477− model := config.GetAgentModel(config.AgentCoder)
478+ agentCfg := config.Get().Agents["coder"]
479+ model := config.Get().GetModelByType(agentCfg.Model)
479 t := styles.CurrentTheme()
481 t := styles.CurrentTheme()
481 modelIcon := t.S().Base.Foreground(t.FgSubtle).Render(styles.ModelIcon)
483 modelIcon := t.S().Base.Foreground(t.FgSubtle).Render(styles.ModelIcon)
482− modelName := t.S().Text.Render(model.Name)
484+ modelName := t.S().Text.Render(model.Model)
483 modelInfo := fmt.Sprintf("%s %s", modelIcon, modelName)
485 modelInfo := fmt.Sprintf("%s %s", modelIcon, modelName)
484 parts := []string{
486 parts := []string{
485 modelInfo,
487 modelInfo,
@@ -507,7 +509,7 @@ func (m *sidebarCmp) SetSession(session session.Session) tea.Cmd {
509func cwd() string {
511func cwd() string {
510− cwd := config.WorkingDirectory()
512+ cwd := config.Get().WorkingDir()
511 t := styles.CurrentTheme()
513 t := styles.CurrentTheme()
512 // Replace home directory with ~, unless we're at the top level of the
514 // Replace home directory with ~, unless we're at the top level of the
513 // home directory).
515 // home directory).
internal/tui/components/chat/splash/splash.go+2−2
@@ -60,9 +60,9 @@ func (s *splashCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
62// View implements SplashPage.
62// View implements SplashPage.
63−func (s *splashCmp) View() tea.View {
63+func (s *splashCmp) View() string {
64 content := lipgloss.JoinVertical(lipgloss.Left, s.logoRendered)
64 content := lipgloss.JoinVertical(lipgloss.Left, s.logoRendered)
65− return tea.NewView(content)
65+ return content
68func (s *splashCmp) logoBlock() string {
68func (s *splashCmp) logoBlock() string {
internal/tui/components/completions/completions.go+3−6
@@ -157,15 +157,12 @@ func (c *completionsCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
159// View implements Completions.
159// View implements Completions.
160−func (c *completionsCmp) View() tea.View {
160+func (c *completionsCmp) View() string {
161 if len(c.list.Items()) == 0 {
161 if len(c.list.Items()) == 0 {
162− return tea.NewView(c.style().Render("No completions found"))
162+ return c.style().Render("No completions found")
165− view := tea.NewView(
166− c.style().Render(c.list.View().String()),
165+ return c.style().Render(c.list.View())
171func (c *completionsCmp) style() lipgloss.Style {
168func (c *completionsCmp) style() lipgloss.Style {
internal/tui/components/completions/item.go+2−2
@@ -75,7 +75,7 @@ func (c *completionItemCmp) Update(tea.Msg) (tea.Model, tea.Cmd) {
77// View implements CommandItem.
77// View implements CommandItem.
78−func (c *completionItemCmp) View() tea.View {
78+func (c *completionItemCmp) View() string {
79 t := styles.CurrentTheme()
79 t := styles.CurrentTheme()
81 itemStyle := t.S().Base.Padding(0, 1).Width(c.width)
81 itemStyle := t.S().Base.Padding(0, 1).Width(c.width)
@@ -135,7 +135,7 @@ func (c *completionItemCmp) View() tea.View {
135 parts...,
135 parts...,
138− return tea.NewView(item)
138+ return item
141// Blur implements CommandItem.
141// Blur implements CommandItem.
internal/tui/components/core/helpers.go+1−1
@@ -5,7 +5,7 @@ import (
7 "github.com/alecthomas/chroma/v2"
7 "github.com/alecthomas/chroma/v2"
8− "github.com/charmbracelet/crush/internal/exp/diffview"
8+ "github.com/charmbracelet/crush/internal/tui/exp/diffview"
9 "github.com/charmbracelet/crush/internal/tui/styles"
9 "github.com/charmbracelet/crush/internal/tui/styles"
10 "github.com/charmbracelet/lipgloss/v2"
10 "github.com/charmbracelet/lipgloss/v2"
11 "github.com/charmbracelet/x/ansi"
11 "github.com/charmbracelet/x/ansi"
internal/tui/components/core/layout/container.go+9−5
@@ -72,7 +72,14 @@ func (c *container) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
75−func (c *container) View() tea.View {
75+func (c *container) Cursor() *tea.Cursor {
76+ if cursor, ok := c.content.(util.Cursor); ok {
77+ return cursor.Cursor()
82+func (c *container) View() string {
76 t := styles.CurrentTheme()
83 t := styles.CurrentTheme()
77 width := c.width
84 width := c.width
78 height := c.height
85 height := c.height
@@ -106,10 +113,7 @@ func (c *container) View() tea.View {
106 PaddingLeft(c.paddingLeft)
113 PaddingLeft(c.paddingLeft)
108 contentView := c.content.View()
115 contentView := c.content.View()
109− view := tea.NewView(style.Render(contentView.String()))
116+ return style.Render(contentView)
110− cursor := contentView.Cursor()
111− view.SetCursor(cursor)
115func (c *container) SetSize(width, height int) tea.Cmd {
119func (c *container) SetSize(width, height int) tea.Cmd {
internal/tui/components/core/layout/split.go+28−21
@@ -1,9 +1,11 @@
1package layout
1package layout
4 "github.com/charmbracelet/bubbles/v2/key"
6 "github.com/charmbracelet/bubbles/v2/key"
5 tea "github.com/charmbracelet/bubbletea/v2"
7 tea "github.com/charmbracelet/bubbletea/v2"
6− "github.com/charmbracelet/crush/internal/logging"
8+
7 "github.com/charmbracelet/crush/internal/tui/styles"
9 "github.com/charmbracelet/crush/internal/tui/styles"
8 "github.com/charmbracelet/crush/internal/tui/util"
10 "github.com/charmbracelet/crush/internal/tui/util"
9 "github.com/charmbracelet/lipgloss/v2"
11 "github.com/charmbracelet/lipgloss/v2"
@@ -104,17 +106,34 @@ func (s *splitPaneLayout) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
104 return s, tea.Batch(cmds...)
106 return s, tea.Batch(cmds...)
107−func (s *splitPaneLayout) View() tea.View {
109+func (s *splitPaneLayout) Cursor() *tea.Cursor {
110+ if s.bottomPanel != nil {
111+ if c, ok := s.bottomPanel.(util.Cursor); ok {
114+ } else if s.rightPanel != nil {
115+ if c, ok := s.rightPanel.(util.Cursor); ok {
118+ } else if s.leftPanel != nil {
119+ if c, ok := s.leftPanel.(util.Cursor); ok {
126+func (s *splitPaneLayout) View() string {
108 var topSection string
127 var topSection string
110 if s.leftPanel != nil && s.rightPanel != nil {
129 if s.leftPanel != nil && s.rightPanel != nil {
111 leftView := s.leftPanel.View()
130 leftView := s.leftPanel.View()
112 rightView := s.rightPanel.View()
131 rightView := s.rightPanel.View()
113− topSection = lipgloss.JoinHorizontal(lipgloss.Top, leftView.String(), rightView.String())
132+ topSection = lipgloss.JoinHorizontal(lipgloss.Top, leftView, rightView)
114 } else if s.leftPanel != nil {
133 } else if s.leftPanel != nil {
115− topSection = s.leftPanel.View().String()
134+ topSection = s.leftPanel.View()
116 } else if s.rightPanel != nil {
135 } else if s.rightPanel != nil {
117− topSection = s.rightPanel.View().String()
136+ topSection = s.rightPanel.View()
119 topSection = ""
138 topSection = ""
@@ -123,38 +142,26 @@ func (s *splitPaneLayout) View() tea.View {
124 if s.bottomPanel != nil && topSection != "" {
143 if s.bottomPanel != nil && topSection != "" {
125 bottomView := s.bottomPanel.View()
144 bottomView := s.bottomPanel.View()
126− finalView = lipgloss.JoinVertical(lipgloss.Left, topSection, bottomView.String())
145+ finalView = lipgloss.JoinVertical(lipgloss.Left, topSection, bottomView)
127 } else if s.bottomPanel != nil {
146 } else if s.bottomPanel != nil {
128− finalView = s.bottomPanel.View().String()
147+ finalView = s.bottomPanel.View()
130 finalView = topSection
149 finalView = topSection
133− // TODO: think of a better way to handle multiple cursors
134− var cursor *tea.Cursor
135− if s.bottomPanel != nil {
136− cursor = s.bottomPanel.View().Cursor()
137− } else if s.rightPanel != nil {
138− cursor = s.rightPanel.View().Cursor()
139− } else if s.leftPanel != nil {
140− cursor = s.leftPanel.View().Cursor()
143 t := styles.CurrentTheme()
152 t := styles.CurrentTheme()
145 style := t.S().Base.
154 style := t.S().Base.
146 Width(s.width).
155 Width(s.width).
147 Height(s.height)
156 Height(s.height)
149− view := tea.NewView(style.Render(finalView))
158+ return style.Render(finalView)
150− view.SetCursor(cursor)
154func (s *splitPaneLayout) SetSize(width, height int) tea.Cmd {
161func (s *splitPaneLayout) SetSize(width, height int) tea.Cmd {
155 s.width = width
162 s.width = width
156 s.height = height
163 s.height = height
157− logging.Info("Setting split pane size", "width", width, "height", height)
164+ slog.Info("Setting split pane size", "width", width, "height", height)
159 var topHeight, bottomHeight int
166 var topHeight, bottomHeight int
160 var cmds []tea.Cmd
167 var cmds []tea.Cmd
internal/tui/components/core/list/list.go+13−9
@@ -280,12 +280,20 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
280 return m, nil
280 return m, nil
283+// Cursor returns the current cursor position in the input field.
284+func (m *model) Cursor() *tea.Cursor {
285+ if m.filterable && !m.hideFilterInput {
286+ return m.input.Cursor()
283// View renders the list to a string for display.
291// View renders the list to a string for display.
284// Returns empty string if the list has no dimensions.
292// Returns empty string if the list has no dimensions.
285// Triggers re-rendering if needed before returning content.
293// Triggers re-rendering if needed before returning content.
286−func (m *model) View() tea.View {
294+func (m *model) View() string {
287 if m.viewState.height == 0 || m.viewState.width == 0 {
295 if m.viewState.height == 0 || m.viewState.width == 0 {
288− return tea.NewView("") // No content to display
296+ return "" // No content to display
290 if m.renderState.needsRerender {
298 if m.renderState.needsRerender {
291 m.renderVisible()
299 m.renderVisible()
@@ -303,11 +311,7 @@ func (m *model) View() tea.View {
306− view := tea.NewView(content)
314+ return content
307− if m.filterable && !m.hideFilterInput {
308− view.SetCursor(m.input.Cursor())
313// handleKeyPress processes keyboard input for list navigation.
317// handleKeyPress processes keyboard input for list navigation.
@@ -833,7 +837,7 @@ func (m *model) rerenderItem(inx int) {
833func (m *model) getItemLines(item util.Model) []string {
837func (m *model) getItemLines(item util.Model) []string {
834 var itemLines []string
838 var itemLines []string
836− itemLines = strings.Split(item.View().String(), "\n")
840+ itemLines = strings.Split(item.View(), "\n")
838 if m.gapSize > 0 {
842 if m.gapSize > 0 {
839 gap := make([]string, m.gapSize)
843 gap := make([]string, m.gapSize)
@@ -1261,7 +1265,7 @@ func (m *model) filterSection(sect section, search string) *section {
1262 // Check if section header itself matches
1266 // Check if section header itself matches
1263 if sect.header != nil {
1267 if sect.header != nil {
1264− headerText := strings.ToLower(sect.header.View().String())
1268+ headerText := strings.ToLower(sect.header.View())
1265 if strings.Contains(headerText, search) {
1269 if strings.Contains(headerText, search) {
1266 hasHeaderMatch = true
1270 hasHeaderMatch = true
1267 // If header matches, include all items in the section
1271 // If header matches, include all items in the section
internal/tui/components/core/status/status.go+2−36
@@ -6,8 +6,6 @@ import (
7 "github.com/charmbracelet/bubbles/v2/help"
7 "github.com/charmbracelet/bubbles/v2/help"
8 tea "github.com/charmbracelet/bubbletea/v2"
8 tea "github.com/charmbracelet/bubbletea/v2"
9− "github.com/charmbracelet/crush/internal/logging"
10− "github.com/charmbracelet/crush/internal/pubsub"
11 "github.com/charmbracelet/crush/internal/session"
9 "github.com/charmbracelet/crush/internal/session"
12 "github.com/charmbracelet/crush/internal/tui/styles"
10 "github.com/charmbracelet/crush/internal/tui/styles"
13 "github.com/charmbracelet/crush/internal/tui/util"
11 "github.com/charmbracelet/crush/internal/tui/util"
@@ -58,49 +56,17 @@ func (m *statusCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
58 return m, m.clearMessageCmd(ttl)
56 return m, m.clearMessageCmd(ttl)
59 case util.ClearStatusMsg:
57 case util.ClearStatusMsg:
60 m.info = util.InfoMsg{}
58 m.info = util.InfoMsg{}
62− // Handle persistent logs
63− case pubsub.Event[logging.LogMessage]:
64− if msg.Payload.Persist {
65− switch msg.Payload.Level {
67− m.info = util.InfoMsg{
68− Type: util.InfoTypeError,
69− Msg: msg.Payload.Message,
70− TTL: msg.Payload.PersistTime,
73− m.info = util.InfoMsg{
74− Type: util.InfoTypeInfo,
75− Msg: msg.Payload.Message,
76− TTL: msg.Payload.PersistTime,
79− m.info = util.InfoMsg{
80− Type: util.InfoTypeWarn,
81− Msg: msg.Payload.Message,
82− TTL: msg.Payload.PersistTime,
85− m.info = util.InfoMsg{
86− Type: util.InfoTypeInfo,
87− Msg: msg.Payload.Message,
88− TTL: msg.Payload.PersistTime,
91− return m, m.clearMessageCmd(m.info.TTL)
94 return m, nil
60 return m, nil
97−func (m *statusCmp) View() tea.View {
63+func (m *statusCmp) View() string {
98 t := styles.CurrentTheme()
64 t := styles.CurrentTheme()
99 status := t.S().Base.Padding(0, 1, 1, 1).Render(m.help.View(m.keyMap))
65 status := t.S().Base.Padding(0, 1, 1, 1).Render(m.help.View(m.keyMap))
100 if m.info.Msg != "" {
66 if m.info.Msg != "" {
101 status = m.infoMsg()
67 status = m.infoMsg()
103− return tea.NewView(status)
69+ return status
106func (m *statusCmp) infoMsg() string {
72func (m *statusCmp) infoMsg() string {
internal/tui/components/dialogs/commands/arguments.go+10−10
@@ -139,7 +139,7 @@ func (c *commandArgumentsDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
141// View implements CommandArgumentsDialog.
141// View implements CommandArgumentsDialog.
142−func (c *commandArgumentsDialogCmp) View() tea.View {
142+func (c *commandArgumentsDialogCmp) View() string {
143 t := styles.CurrentTheme()
143 t := styles.CurrentTheme()
144 baseStyle := t.S().Base
144 baseStyle := t.S().Base
@@ -188,19 +188,19 @@ func (c *commandArgumentsDialogCmp) View() tea.View {
188 elements...,
188 elements...,
191− view := tea.NewView(
192− baseStyle.Padding(1, 1, 0, 1).
191+ return baseStyle.Padding(1, 1, 0, 1).
193− Border(lipgloss.RoundedBorder()).
192+ Border(lipgloss.RoundedBorder()).
194− BorderForeground(t.BorderFocus).
193+ BorderForeground(t.BorderFocus).
195− Width(c.width).
194+ Width(c.width).
196− Render(content),
195+ Render(content)
198+func (c *commandArgumentsDialogCmp) Cursor() *tea.Cursor {
198 cursor := c.inputs[c.focusIndex].Cursor()
199 cursor := c.inputs[c.focusIndex].Cursor()
199 if cursor != nil {
200 if cursor != nil {
200 cursor = c.moveCursor(cursor)
201 cursor = c.moveCursor(cursor)
202− view.SetCursor(cursor)
203− return view
203+ return cursor
206func (c *commandArgumentsDialogCmp) moveCursor(cursor *tea.Cursor) *tea.Cursor {
206func (c *commandArgumentsDialogCmp) moveCursor(cursor *tea.Cursor) *tea.Cursor {
internal/tui/components/dialogs/commands/commands.go+14−8
@@ -143,23 +143,29 @@ func (c *commandDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
143 return c, nil
143 return c, nil
146−func (c *commandDialogCmp) View() tea.View {
146+func (c *commandDialogCmp) View() string {
147 t := styles.CurrentTheme()
147 t := styles.CurrentTheme()
148− listView := c.commandList.View()
148+ listView := c.commandList
149 radio := c.commandTypeRadio()
149 radio := c.commandTypeRadio()
150 content := lipgloss.JoinVertical(
150 content := lipgloss.JoinVertical(
151 lipgloss.Left,
151 lipgloss.Left,
152 t.S().Base.Padding(0, 1, 1, 1).Render(core.Title("Commands", c.width-lipgloss.Width(radio)-5)+" "+radio),
152 t.S().Base.Padding(0, 1, 1, 1).Render(core.Title("Commands", c.width-lipgloss.Width(radio)-5)+" "+radio),
153− listView.String(),
153+ listView.View(),
155 t.S().Base.Width(c.width-2).PaddingLeft(1).AlignHorizontal(lipgloss.Left).Render(c.help.View(c.keyMap)),
155 t.S().Base.Width(c.width-2).PaddingLeft(1).AlignHorizontal(lipgloss.Left).Render(c.help.View(c.keyMap)),
157− v := tea.NewView(c.style().Render(content))
157+ return c.style().Render(content)
158− if listView.Cursor() != nil {
158+}
160+func (c *commandDialogCmp) Cursor() *tea.Cursor {
161+ if cursor, ok := c.commandList.(util.Cursor); ok {
162+ cursor := cursor.Cursor()
159− c := c.moveCursor(listView.Cursor())
164+ cursor = c.moveCursor(cursor)
160− v.SetCursor(c)
165+ }
162− return v
168+ return nil
165func (c *commandDialogCmp) commandTypeRadio() string {
171func (c *commandDialogCmp) commandTypeRadio() string {
internal/tui/components/dialogs/commands/item.go+2−2
@@ -35,12 +35,12 @@ func (m *itemSectionModel) Update(tea.Msg) (tea.Model, tea.Cmd) {
35 return m, nil
35 return m, nil
38−func (m *itemSectionModel) View() tea.View {
38+func (m *itemSectionModel) View() string {
39 t := styles.CurrentTheme()
39 t := styles.CurrentTheme()
40 title := ansi.Truncate(m.title, m.width-2, "…")
40 title := ansi.Truncate(m.title, m.width-2, "…")
41 style := t.S().Base.Padding(1, 1, 0, 1)
41 style := t.S().Base.Padding(1, 1, 0, 1)
42 title = t.S().Muted.Render(title)
42 title = t.S().Muted.Render(title)
43− return tea.NewView(style.Render(core.Section(title, m.width-2)))
43+ return style.Render(core.Section(title, m.width-2))
46func (m *itemSectionModel) GetSize() (int, int) {
46func (m *itemSectionModel) GetSize() (int, int) {
internal/tui/components/dialogs/compact/compact.go+2−2
@@ -242,8 +242,8 @@ func (c *compactDialogCmp) render() string {
242 Render(dialogContent)
242 Render(dialogContent)
245−func (c *compactDialogCmp) View() tea.View {
245+func (c *compactDialogCmp) View() string {
246− return tea.NewView(c.render())
246+ return c.render()
249// SetSize sets the size of the component.
249// SetSize sets the size of the component.
internal/tui/components/dialogs/dialogs.go+4−5
@@ -37,7 +37,7 @@ type DialogCmp interface {
37 Dialogs() []DialogModel
37 Dialogs() []DialogModel
38 HasDialogs() bool
38 HasDialogs() bool
39 GetLayers() []*lipgloss.Layer
39 GetLayers() []*lipgloss.Layer
40− ActiveView() *tea.View
40+ ActiveModel() util.Model
41 ActiveDialogID() DialogID
41 ActiveDialogID() DialogID
@@ -132,12 +132,11 @@ func (d dialogCmp) Dialogs() []DialogModel {
132 return d.dialogs
132 return d.dialogs
135−func (d dialogCmp) ActiveView() *tea.View {
135+func (d dialogCmp) ActiveModel() util.Model {
136 if len(d.dialogs) == 0 {
136 if len(d.dialogs) == 0 {
137 return nil
137 return nil
139− view := d.dialogs[len(d.dialogs)-1].View()
139+ return d.dialogs[len(d.dialogs)-1]
143func (d dialogCmp) ActiveDialogID() DialogID {
142func (d dialogCmp) ActiveDialogID() DialogID {
@@ -150,7 +149,7 @@ func (d dialogCmp) ActiveDialogID() DialogID {
150func (d dialogCmp) GetLayers() []*lipgloss.Layer {
149func (d dialogCmp) GetLayers() []*lipgloss.Layer {
151 layers := []*lipgloss.Layer{}
150 layers := []*lipgloss.Layer{}
152 for _, dialog := range d.Dialogs() {
151 for _, dialog := range d.Dialogs() {
153− dialogView := dialog.View().String()
152+ dialogView := dialog.View()
154 row, col := dialog.Position()
153 row, col := dialog.Position()
155 layers = append(layers, lipgloss.NewLayer(dialogView).X(col).Y(row))
154 layers = append(layers, lipgloss.NewLayer(dialogView).X(col).Y(row))
internal/tui/components/dialogs/filepicker/filepicker.go+5−9
@@ -11,7 +11,6 @@ import (
11 "github.com/charmbracelet/bubbles/v2/help"
11 "github.com/charmbracelet/bubbles/v2/help"
12 "github.com/charmbracelet/bubbles/v2/key"
12 "github.com/charmbracelet/bubbles/v2/key"
13 tea "github.com/charmbracelet/bubbletea/v2"
13 tea "github.com/charmbracelet/bubbletea/v2"
14− "github.com/charmbracelet/crush/internal/logging"
15 "github.com/charmbracelet/crush/internal/message"
14 "github.com/charmbracelet/crush/internal/message"
16 "github.com/charmbracelet/crush/internal/tui/components/core"
15 "github.com/charmbracelet/crush/internal/tui/components/core"
17 "github.com/charmbracelet/crush/internal/tui/components/dialogs"
16 "github.com/charmbracelet/crush/internal/tui/components/dialogs"
@@ -119,18 +118,15 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
119 func() tea.Msg {
118 func() tea.Msg {
120 isFileLarge, err := ValidateFileSize(path, maxAttachmentSize)
119 isFileLarge, err := ValidateFileSize(path, maxAttachmentSize)
121 if err != nil {
120 if err != nil {
122− logging.ErrorPersist("unable to read the image")
121+ return util.ReportError(fmt.Errorf("unable to read the image: %w", err))
125 if isFileLarge {
123 if isFileLarge {
126− logging.ErrorPersist("file too large, max 5MB")
124+ return util.ReportError(fmt.Errorf("file too large, max 5MB"))
130 content, err := os.ReadFile(path)
127 content, err := os.ReadFile(path)
131 if err != nil {
128 if err != nil {
132− logging.ErrorPersist("Unable read selected file")
129+ return util.ReportError(fmt.Errorf("unable to read the image: %w", err))
136 mimeBufferSize := min(512, len(content))
132 mimeBufferSize := min(512, len(content))
@@ -148,7 +144,7 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
148 return m, tea.Batch(cmds...)
144 return m, tea.Batch(cmds...)
151−func (m *model) View() tea.View {
147+func (m *model) View() string {
152 t := styles.CurrentTheme()
148 t := styles.CurrentTheme()
154 content := lipgloss.JoinVertical(
150 content := lipgloss.JoinVertical(
@@ -158,7 +154,7 @@ func (m *model) View() tea.View {
158 m.filePicker.View(),
154 m.filePicker.View(),
159 t.S().Base.Width(m.width-2).PaddingLeft(1).AlignHorizontal(lipgloss.Left).Render(m.help.View(m.keyMap)),
155 t.S().Base.Width(m.width-2).PaddingLeft(1).AlignHorizontal(lipgloss.Left).Render(m.help.View(m.keyMap)),
161− return tea.NewView(m.style().Render(content))
157+ return m.style().Render(content)
164func (m *model) currentImage() string {
160func (m *model) currentImage() string {
internal/tui/components/dialogs/init/init.go+2−2
@@ -147,8 +147,8 @@ func (m *initDialogCmp) render() string {
149// View implements tea.Model.
149// View implements tea.Model.
150−func (m *initDialogCmp) View() tea.View {
150+func (m *initDialogCmp) View() string {
151− return tea.NewView(m.render())
151+ return m.render()
154// SetSize sets the size of the component.
154// SetSize sets the size of the component.
internal/tui/components/dialogs/models/models.go+41−31
@@ -31,8 +31,8 @@ const (
32// ModelSelectedMsg is sent when a model is selected
32// ModelSelectedMsg is sent when a model is selected
33type ModelSelectedMsg struct {
33type ModelSelectedMsg struct {
34− Model config.PreferredModel
34+ Model config.SelectedModel
35− ModelType config.ModelType
35+ ModelType config.SelectedModelType
38// CloseModelDialogMsg is sent when a model is selected
38// CloseModelDialogMsg is sent when a model is selected
@@ -115,19 +115,19 @@ func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
115 items := m.modelList.Items()
115 items := m.modelList.Items()
116 selectedItem := items[selectedItemInx].(completions.CompletionItem).Value().(ModelOption)
116 selectedItem := items[selectedItemInx].(completions.CompletionItem).Value().(ModelOption)
118− var modelType config.ModelType
118+ var modelType config.SelectedModelType
119 if m.modelType == LargeModelType {
119 if m.modelType == LargeModelType {
120− modelType = config.LargeModel
120+ modelType = config.SelectedModelTypeLarge
122− modelType = config.SmallModel
122+ modelType = config.SelectedModelTypeSmall
125 return m, tea.Sequence(
125 return m, tea.Sequence(
126 util.CmdHandler(dialogs.CloseDialogMsg{}),
126 util.CmdHandler(dialogs.CloseDialogMsg{}),
127 util.CmdHandler(ModelSelectedMsg{
127 util.CmdHandler(ModelSelectedMsg{
128− Model: config.PreferredModel{
128+ Model: config.SelectedModel{
129− ModelID: selectedItem.Model.ID,
129+ Model: selectedItem.Model.ID,
130− Provider: selectedItem.Provider.ID,
130+ Provider: string(selectedItem.Provider.ID),
132 ModelType: modelType,
132 ModelType: modelType,
@@ -149,23 +149,29 @@ func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
149 return m, nil
149 return m, nil
152−func (m *modelDialogCmp) View() tea.View {
152+func (m *modelDialogCmp) View() string {
153 t := styles.CurrentTheme()
153 t := styles.CurrentTheme()
154 listView := m.modelList.View()
154 listView := m.modelList.View()
155 radio := m.modelTypeRadio()
155 radio := m.modelTypeRadio()
156 content := lipgloss.JoinVertical(
156 content := lipgloss.JoinVertical(
157 lipgloss.Left,
157 lipgloss.Left,
158 t.S().Base.Padding(0, 1, 1, 1).Render(core.Title("Switch Model", m.width-lipgloss.Width(radio)-5)+" "+radio),
158 t.S().Base.Padding(0, 1, 1, 1).Render(core.Title("Switch Model", m.width-lipgloss.Width(radio)-5)+" "+radio),
159− listView.String(),
159+ listView,
161 t.S().Base.Width(m.width-2).PaddingLeft(1).AlignHorizontal(lipgloss.Left).Render(m.help.View(m.keyMap)),
161 t.S().Base.Width(m.width-2).PaddingLeft(1).AlignHorizontal(lipgloss.Left).Render(m.help.View(m.keyMap)),
163− v := tea.NewView(m.style().Render(content))
163+ return m.style().Render(content)
164− if listView.Cursor() != nil {
164+}
166+func (m *modelDialogCmp) Cursor() *tea.Cursor {
167+ if cursor, ok := m.modelList.(util.Cursor); ok {
168+ cursor := cursor.Cursor()
165− c := m.moveCursor(listView.Cursor())
170+ cursor = m.moveCursor(cursor)
166− v.SetCursor(c)
171+ return cursor
168− return v
174+ return nil
171func (m *modelDialogCmp) style() lipgloss.Style {
177func (m *modelDialogCmp) style() lipgloss.Style {
@@ -218,35 +224,39 @@ func (m *modelDialogCmp) modelTypeRadio() string {
218func (m *modelDialogCmp) SetModelType(modelType int) tea.Cmd {
224func (m *modelDialogCmp) SetModelType(modelType int) tea.Cmd {
219 m.modelType = modelType
225 m.modelType = modelType
221− providers := config.Providers()
227+ providers, err := config.Providers()
229+ return util.ReportError(err)
222 modelItems := []util.Model{}
232 modelItems := []util.Model{}
223 selectIndex := 0
233 selectIndex := 0
225 cfg := config.Get()
235 cfg := config.Get()
226− var currentModel config.PreferredModel
236+ var currentModel config.SelectedModel
227 if m.modelType == LargeModelType {
237 if m.modelType == LargeModelType {
228− currentModel = cfg.Models.Large
238+ currentModel = cfg.Models[config.SelectedModelTypeLarge]
230− currentModel = cfg.Models.Small
240+ currentModel = cfg.Models[config.SelectedModelTypeSmall]
233 // Create a map to track which providers we've already added
243 // Create a map to track which providers we've already added
234− addedProviders := make(map[provider.InferenceProvider]bool)
244+ addedProviders := make(map[string]bool)
236 // First, add any configured providers that are not in the known providers list
246 // First, add any configured providers that are not in the known providers list
237 // These should appear at the top of the list
247 // These should appear at the top of the list
238 knownProviders := provider.KnownProviders()
248 knownProviders := provider.KnownProviders()
239 for providerID, providerConfig := range cfg.Providers {
249 for providerID, providerConfig := range cfg.Providers {
240− if providerConfig.Disabled {
250+ if providerConfig.Disable {
244 // Check if this provider is not in the known providers list
254 // Check if this provider is not in the known providers list
245− if !slices.Contains(knownProviders, providerID) {
255+ if !slices.Contains(knownProviders, provider.InferenceProvider(providerID)) {
246 // Convert config provider to provider.Provider format
256 // Convert config provider to provider.Provider format
247 configProvider := provider.Provider{
257 configProvider := provider.Provider{
248 Name: string(providerID), // Use provider ID as name for unknown providers
258 Name: string(providerID), // Use provider ID as name for unknown providers
249− ID: providerID,
259+ ID: provider.InferenceProvider(providerID),
250 Models: make([]provider.Model, len(providerConfig.Models)),
260 Models: make([]provider.Model, len(providerConfig.Models)),
@@ -254,7 +264,7 @@ func (m *modelDialogCmp) SetModelType(modelType int) tea.Cmd {
254 for i, model := range providerConfig.Models {
264 for i, model := range providerConfig.Models {
255 configProvider.Models[i] = provider.Model{
265 configProvider.Models[i] = provider.Model{
256 ID: model.ID,
266 ID: model.ID,
257− Name: model.Name,
267+ Model: model.Model,
258 CostPer1MIn: model.CostPer1MIn,
268 CostPer1MIn: model.CostPer1MIn,
259 CostPer1MOut: model.CostPer1MOut,
269 CostPer1MOut: model.CostPer1MOut,
260 CostPer1MInCached: model.CostPer1MInCached,
270 CostPer1MInCached: model.CostPer1MInCached,
@@ -263,7 +273,7 @@ func (m *modelDialogCmp) SetModelType(modelType int) tea.Cmd {
263 DefaultMaxTokens: model.DefaultMaxTokens,
273 DefaultMaxTokens: model.DefaultMaxTokens,
264 CanReason: model.CanReason,
274 CanReason: model.CanReason,
265 HasReasoningEffort: model.HasReasoningEffort,
275 HasReasoningEffort: model.HasReasoningEffort,
266− DefaultReasoningEffort: model.ReasoningEffort,
276+ DefaultReasoningEffort: model.DefaultReasoningEffort,
267 SupportsImages: model.SupportsImages,
277 SupportsImages: model.SupportsImages,
@@ -275,11 +285,11 @@ func (m *modelDialogCmp) SetModelType(modelType int) tea.Cmd {
276 modelItems = append(modelItems, commands.NewItemSection(name))
286 modelItems = append(modelItems, commands.NewItemSection(name))
277 for _, model := range configProvider.Models {
287 for _, model := range configProvider.Models {
278− modelItems = append(modelItems, completions.NewCompletionItem(model.Name, ModelOption{
288+ modelItems = append(modelItems, completions.NewCompletionItem(model.Model, ModelOption{
279 Provider: configProvider,
289 Provider: configProvider,
280 Model: model,
290 Model: model,
282− if model.ID == currentModel.ModelID && configProvider.ID == currentModel.Provider {
292+ if model.ID == currentModel.Model && string(configProvider.ID) == currentModel.Provider {
283 selectIndex = len(modelItems) - 1 // Set the selected index to the current model
293 selectIndex = len(modelItems) - 1 // Set the selected index to the current model
@@ -290,12 +300,12 @@ func (m *modelDialogCmp) SetModelType(modelType int) tea.Cmd {
290 // Then add the known providers from the predefined list
300 // Then add the known providers from the predefined list
291 for _, provider := range providers {
301 for _, provider := range providers {
292 // Skip if we already added this provider as an unknown provider
302 // Skip if we already added this provider as an unknown provider
293− if addedProviders[provider.ID] {
303+ if addedProviders[string(provider.ID)] {
297 // Check if this provider is configured and not disabled
307 // Check if this provider is configured and not disabled
298− if providerConfig, exists := cfg.Providers[provider.ID]; exists && providerConfig.Disabled {
308+ if providerConfig, exists := cfg.Providers[string(provider.ID)]; exists && providerConfig.Disable {
@@ -305,11 +315,11 @@ func (m *modelDialogCmp) SetModelType(modelType int) tea.Cmd {
306 modelItems = append(modelItems, commands.NewItemSection(name))
316 modelItems = append(modelItems, commands.NewItemSection(name))
307 for _, model := range provider.Models {
317 for _, model := range provider.Models {
308− modelItems = append(modelItems, completions.NewCompletionItem(model.Name, ModelOption{
318+ modelItems = append(modelItems, completions.NewCompletionItem(model.Model, ModelOption{
309 Provider: provider,
319 Provider: provider,
310 Model: model,
320 Model: model,
312− if model.ID == currentModel.ModelID && provider.ID == currentModel.Provider {
322+ if model.ID == currentModel.Model && string(provider.ID) == currentModel.Provider {
313 selectIndex = len(modelItems) - 1 // Set the selected index to the current model
323 selectIndex = len(modelItems) - 1 // Set the selected index to the current model
internal/tui/components/dialogs/permissions/permissions.go+2−2
@@ -478,8 +478,8 @@ func (p *permissionDialogCmp) render() string {
481−func (p *permissionDialogCmp) View() tea.View {
481+func (p *permissionDialogCmp) View() string {
482− return tea.NewView(p.render())
482+ return p.render()
485func (p *permissionDialogCmp) SetSize() tea.Cmd {
485func (p *permissionDialogCmp) SetSize() tea.Cmd {
internal/tui/components/dialogs/quit/quit.go+2−4
@@ -65,7 +65,7 @@ func (q *quitDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
67// View renders the quit dialog with Yes/No buttons.
67// View renders the quit dialog with Yes/No buttons.
68−func (q *quitDialogCmp) View() tea.View {
68+func (q *quitDialogCmp) View() string {
69 t := styles.CurrentTheme()
69 t := styles.CurrentTheme()
70 baseStyle := t.S().Base
70 baseStyle := t.S().Base
71 yesStyle := t.S().Text
71 yesStyle := t.S().Text
@@ -100,9 +100,7 @@ func (q *quitDialogCmp) View() tea.View {
100 Border(lipgloss.RoundedBorder()).
100 Border(lipgloss.RoundedBorder()).
101 BorderForeground(t.BorderFocus)
101 BorderForeground(t.BorderFocus)
104− quitDialogStyle.Render(content),
103+ return quitDialogStyle.Render(content)
108func (q *quitDialogCmp) Position() (int, int) {
106func (q *quitDialogCmp) Position() (int, int) {
internal/tui/components/dialogs/sessions/sessions.go+13−7
@@ -122,23 +122,29 @@ func (s *sessionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
122 return s, nil
122 return s, nil
125−func (s *sessionDialogCmp) View() tea.View {
125+func (s *sessionDialogCmp) View() string {
126 t := styles.CurrentTheme()
126 t := styles.CurrentTheme()
127 listView := s.sessionsList.View()
127 listView := s.sessionsList.View()
128 content := lipgloss.JoinVertical(
128 content := lipgloss.JoinVertical(
129 lipgloss.Left,
129 lipgloss.Left,
130 t.S().Base.Padding(0, 1, 1, 1).Render(core.Title("Switch Session", s.width-4)),
130 t.S().Base.Padding(0, 1, 1, 1).Render(core.Title("Switch Session", s.width-4)),
131− listView.String(),
131+ listView,
133 t.S().Base.Width(s.width-2).PaddingLeft(1).AlignHorizontal(lipgloss.Left).Render(s.help.View(s.keyMap)),
133 t.S().Base.Width(s.width-2).PaddingLeft(1).AlignHorizontal(lipgloss.Left).Render(s.help.View(s.keyMap)),
136− v := tea.NewView(s.style().Render(content))
136+ return s.style().Render(content)
137− if listView.Cursor() != nil {
137+}
139+func (s *sessionDialogCmp) Cursor() *tea.Cursor {
140+ if cursor, ok := s.sessionsList.(util.Cursor); ok {
141+ cursor := cursor.Cursor()
138− c := s.moveCursor(listView.Cursor())
143+ cursor = s.moveCursor(cursor)
139− v.SetCursor(c)
144+ }
141− return v
147+ return nil
144func (s *sessionDialogCmp) style() lipgloss.Style {
150func (s *sessionDialogCmp) style() lipgloss.Style {
internal/tui/components/logs/details.godeleted−176
@@ -1,176 +0,0 @@
8− "github.com/charmbracelet/bubbles/v2/viewport"
9− tea "github.com/charmbracelet/bubbletea/v2"
10− "github.com/charmbracelet/crush/internal/logging"
11− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
12− "github.com/charmbracelet/crush/internal/tui/styles"
13− "github.com/charmbracelet/crush/internal/tui/util"
14− "github.com/charmbracelet/lipgloss/v2"
17−type DetailComponent interface {
22−type detailCmp struct {
24− currentLog logging.LogMessage
25− viewport viewport.Model
28−func (i *detailCmp) Init() tea.Cmd {
29− messages := logging.List()
30− if len(messages) == 0 {
33− i.currentLog = messages[0]
37−func (i *detailCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
38− switch msg := msg.(type) {
40− if msg.ID != i.currentLog.ID {
41− i.currentLog = logging.LogMessage(msg)
49−func (i *detailCmp) updateContent() {
50− var content strings.Builder
51− t := styles.CurrentTheme()
53− if i.currentLog.ID == "" {
54− content.WriteString(t.S().Muted.Render("No log selected"))
55− i.viewport.SetContent(content.String())
59− // Level badge with background color
60− levelStyle := getLevelStyle(i.currentLog.Level)
61− levelBadge := levelStyle.Padding(0, 1).Render(strings.ToUpper(i.currentLog.Level))
63− // Timestamp with relative time
64− timeStr := i.currentLog.Time.Format("2006-01-05 15:04:05 UTC")
65− relativeTime := getRelativeTime(i.currentLog.Time)
66− timeStyle := t.S().Muted
69− header := lipgloss.JoinHorizontal(
73− timeStyle.Render(relativeTime),
76− content.WriteString(levelBadge)
77− content.WriteString("\n\n")
78− content.WriteString(header)
79− content.WriteString("\n\n")
82− messageHeaderStyle := t.S().Base.Foreground(t.Blue).Bold(true)
83− content.WriteString(messageHeaderStyle.Render("Message"))
84− content.WriteString("\n")
85− content.WriteString(i.currentLog.Message)
86− content.WriteString("\n\n")
88− // Attributes section
89− if len(i.currentLog.Attributes) > 0 {
90− attrHeaderStyle := t.S().Base.Foreground(t.Blue).Bold(true)
91− content.WriteString(attrHeaderStyle.Render("Attributes"))
92− content.WriteString("\n")
94− for _, attr := range i.currentLog.Attributes {
95− keyStyle := t.S().Base.Foreground(t.Accent)
96− valueStyle := t.S().Text
97− attrLine := fmt.Sprintf("%s: %s",
98− keyStyle.Render(attr.Key),
99− valueStyle.Render(attr.Value),
101− content.WriteString(attrLine)
102− content.WriteString("\n")
106− i.viewport.SetContent(content.String())
109−func getLevelStyle(level string) lipgloss.Style {
110− t := styles.CurrentTheme()
111− style := t.S().Base.Bold(true)
113− switch strings.ToLower(level) {
115− return style.Foreground(t.White).Background(t.Info)
116− case "warn", "warning":
117− return style.Foreground(t.White).Background(t.Warning)
118− case "error", "err":
119− return style.Foreground(t.White).Background(t.Error)
121− return style.Foreground(t.White).Background(t.Success)
123− return style.Foreground(t.White).Background(t.Error)
125− return style.Foreground(t.FgBase)
129−func getRelativeTime(logTime time.Time) string {
131− diff := now.Sub(logTime)
133− if diff < time.Minute {
134− return fmt.Sprintf("%ds ago", int(diff.Seconds()))
135− } else if diff < time.Hour {
136− return fmt.Sprintf("%dm ago", int(diff.Minutes()))
137− } else if diff < 24*time.Hour {
138− return fmt.Sprintf("%dh ago", int(diff.Hours()))
139− } else if diff < 30*24*time.Hour {
140− return fmt.Sprintf("%dd ago", int(diff.Hours()/24))
141− } else if diff < 365*24*time.Hour {
142− return fmt.Sprintf("%dmo ago", int(diff.Hours()/(24*30)))
144− return fmt.Sprintf("%dy ago", int(diff.Hours()/(24*365)))
148−func (i *detailCmp) View() tea.View {
149− t := styles.CurrentTheme()
150− style := t.S().Base.
151− BorderStyle(lipgloss.RoundedBorder()).
152− BorderForeground(t.BorderFocus).
153− Width(i.width - 2). // Adjust width for border
154− Height(i.height - 2). // Adjust height for border
156− return tea.NewView(style.Render(i.viewport.View()))
159−func (i *detailCmp) GetSize() (int, int) {
160− return i.width, i.height
163−func (i *detailCmp) SetSize(width int, height int) tea.Cmd {
166− i.viewport.SetWidth(i.width - 4)
167− i.viewport.SetHeight(i.height - 4)
172−func NewLogsDetails() DetailComponent {
174− viewport: viewport.New(),
internal/tui/components/logs/table.godeleted−197
@@ -1,197 +0,0 @@
8− "github.com/charmbracelet/bubbles/v2/table"
9− tea "github.com/charmbracelet/bubbletea/v2"
10− "github.com/charmbracelet/crush/internal/logging"
11− "github.com/charmbracelet/crush/internal/pubsub"
12− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
13− "github.com/charmbracelet/crush/internal/tui/styles"
14− "github.com/charmbracelet/crush/internal/tui/util"
15− "github.com/charmbracelet/lipgloss/v2"
18−type TableComponent interface {
23−type tableCmp struct {
25− logs []logging.LogMessage
28−type selectedLogMsg logging.LogMessage
30−func (i *tableCmp) Init() tea.Cmd {
31− i.logs = logging.List()
36−func (i *tableCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
38− switch msg := msg.(type) {
39− case pubsub.Event[logging.LogMessage]:
40− return i, func() tea.Msg {
41− if msg.Type == pubsub.CreatedEvent {
42− rows := i.table.Rows()
43− for _, row := range rows {
44− if row[1] == msg.Payload.ID {
45− return nil // If the log already exists, do not add it again
48− i.logs = append(i.logs, msg.Payload)
52− logToRow(msg.Payload),
58− return selectedLogMsg(msg.Payload)
61− t, cmd := i.table.Update(msg)
62− cmds = append(cmds, cmd)
65− cmds = append(cmds, func() tea.Msg {
66− for _, log := range logging.List() {
67− if log.ID == i.table.SelectedRow()[1] {
68− // If the selected row matches the log ID, return the selected log message
69− return selectedLogMsg(log)
74− return i, tea.Batch(cmds...)
77−func (i *tableCmp) View() tea.View {
78− t := styles.CurrentTheme()
79− defaultStyles := table.DefaultStyles()
82− defaultStyles.Header = defaultStyles.Header.
83− Foreground(t.Primary).
85− BorderStyle(lipgloss.NormalBorder()).
87− BorderForeground(t.Border)
89− // Selected row styling
90− defaultStyles.Selected = defaultStyles.Selected.
91− Foreground(t.FgSelected).
92− Background(t.Primary).
96− defaultStyles.Cell = defaultStyles.Cell.
99− i.table.SetStyles(defaultStyles)
100− return tea.NewView(i.table.View())
103−func (i *tableCmp) GetSize() (int, int) {
104− return i.table.Width(), i.table.Height()
107−func (i *tableCmp) SetSize(width int, height int) tea.Cmd {
108− i.table.SetWidth(width)
109− i.table.SetHeight(height)
111− columnWidth := (width - 10) / 4
112− i.table.SetColumns([]table.Column{
130− Title: "Attributes",
137−func (i *tableCmp) setRows() {
138− rows := []table.Row{}
140− slices.SortFunc(i.logs, func(a, b logging.LogMessage) int {
141− if a.Time.Before(b.Time) {
144− if a.Time.After(b.Time) {
150− for _, log := range i.logs {
151− rows = append(rows, logToRow(log))
153− i.table.SetRows(rows)
156−func logToRow(log logging.LogMessage) table.Row {
157− // Format attributes as JSON string
159− if len(log.Attributes) > 0 {
161− for _, attr := range log.Attributes {
162− parts = append(parts, fmt.Sprintf(`{"Key":"%s","Value":"%s"}`, attr.Key, attr.Value))
164− attrStr = "[" + strings.Join(parts, ",") + "]"
167− // Format time with relative time
168− timeStr := log.Time.Format("2006-01-05 15:04:05 UTC")
169− relativeTime := getRelativeTime(log.Time)
170− fullTimeStr := timeStr + " " + relativeTime
173− strings.ToUpper(log.Level),
181−func NewLogsTable() TableComponent {
182− columns := []table.Column{
187− {Title: "Attributes"},
190− tableModel := table.New(
191− table.WithColumns(columns),
internal/exp/diffview/Taskfile.yaml → internal/tui/exp/diffview/Taskfile.yamlrenamed
The content is the same.
internal/exp/diffview/chroma.go → internal/tui/exp/diffview/chroma.gorenamed
The content is the same.
internal/exp/diffview/diffview.go → internal/tui/exp/diffview/diffview.gorenamed
The content is the same.
internal/exp/diffview/diffview_test.go → internal/tui/exp/diffview/diffview_test.gorenamed+1−1
@@ -7,7 +7,7 @@ import (
9 "github.com/alecthomas/chroma/v2/styles"
9 "github.com/alecthomas/chroma/v2/styles"
10− "github.com/charmbracelet/crush/internal/exp/diffview"
10+ "github.com/charmbracelet/crush/internal/tui/exp/diffview"
11 "github.com/charmbracelet/x/ansi"
11 "github.com/charmbracelet/x/ansi"
12 "github.com/charmbracelet/x/exp/golden"
12 "github.com/charmbracelet/x/exp/golden"
internal/exp/diffview/split.go → internal/tui/exp/diffview/split.gorenamed
The content is the same.
internal/exp/diffview/style.go → internal/tui/exp/diffview/style.gorenamed
The content is the same.
internal/exp/diffview/testdata/TestDefault.after → internal/tui/exp/diffview/testdata/TestDefault.afterrenamed
The content is the same.
internal/exp/diffview/testdata/TestDefault.before → internal/tui/exp/diffview/testdata/TestDefault.beforerenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Split/CustomContextLines/DarkMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Split/CustomContextLines/DarkMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Split/CustomContextLines/LightMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Split/CustomContextLines/LightMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Split/Default/DarkMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Split/Default/DarkMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Split/Default/LightMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Split/Default/LightMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Split/LargeWidth/DarkMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Split/LargeWidth/DarkMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Split/LargeWidth/LightMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Split/LargeWidth/LightMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Split/MultipleHunks/DarkMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Split/MultipleHunks/DarkMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Split/MultipleHunks/LightMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Split/MultipleHunks/LightMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Split/Narrow/DarkMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Split/Narrow/DarkMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Split/Narrow/LightMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Split/Narrow/LightMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Split/NoLineNumbers/DarkMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Split/NoLineNumbers/DarkMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Split/NoLineNumbers/LightMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Split/NoLineNumbers/LightMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Split/NoSyntaxHighlight/DarkMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Split/NoSyntaxHighlight/DarkMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Split/NoSyntaxHighlight/LightMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Split/NoSyntaxHighlight/LightMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Split/SmallWidth/DarkMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Split/SmallWidth/DarkMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Split/SmallWidth/LightMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Split/SmallWidth/LightMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Unified/CustomContextLines/DarkMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Unified/CustomContextLines/DarkMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Unified/CustomContextLines/LightMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Unified/CustomContextLines/LightMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Unified/Default/DarkMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Unified/Default/DarkMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Unified/Default/LightMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Unified/Default/LightMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Unified/LargeWidth/DarkMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Unified/LargeWidth/DarkMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Unified/LargeWidth/LightMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Unified/LargeWidth/LightMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Unified/MultipleHunks/DarkMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Unified/MultipleHunks/DarkMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Unified/MultipleHunks/LightMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Unified/MultipleHunks/LightMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Unified/Narrow/DarkMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Unified/Narrow/DarkMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Unified/Narrow/LightMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Unified/Narrow/LightMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Unified/NoLineNumbers/DarkMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Unified/NoLineNumbers/DarkMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Unified/NoLineNumbers/LightMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Unified/NoLineNumbers/LightMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Unified/NoSyntaxHighlight/DarkMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Unified/NoSyntaxHighlight/DarkMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Unified/NoSyntaxHighlight/LightMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Unified/NoSyntaxHighlight/LightMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Unified/SmallWidth/DarkMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Unified/SmallWidth/DarkMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffView/Unified/SmallWidth/LightMode.golden → internal/tui/exp/diffview/testdata/TestDiffView/Unified/SmallWidth/LightMode.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf001.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf001.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf002.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf002.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf003.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf003.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf004.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf004.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf005.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf005.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf006.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf006.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf007.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf007.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf008.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf008.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf009.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf009.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf010.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf010.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf011.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf011.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf012.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf012.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf013.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf013.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf014.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf014.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf015.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf015.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf016.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf016.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf017.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf017.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf018.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf018.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf019.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf019.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf020.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Split/HeightOf020.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf001.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf001.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf002.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf002.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf003.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf003.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf004.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf004.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf005.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf005.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf006.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf006.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf007.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf007.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf008.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf008.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf009.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf009.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf010.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf010.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf011.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf011.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf012.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf012.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf013.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf013.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf014.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf014.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf015.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf015.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf016.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf016.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf017.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf017.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf018.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf018.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf019.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf019.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf020.golden → internal/tui/exp/diffview/testdata/TestDiffViewHeight/Unified/HeightOf020.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewTabs/Split.golden → internal/tui/exp/diffview/testdata/TestDiffViewTabs/Split.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewTabs/Unified.golden → internal/tui/exp/diffview/testdata/TestDiffViewTabs/Unified.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf001.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf001.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf002.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf002.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf003.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf003.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf004.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf004.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf005.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf005.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf006.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf006.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf007.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf007.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf008.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf008.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf009.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf009.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf010.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf010.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf011.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf011.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf012.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf012.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf013.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf013.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf014.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf014.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf015.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf015.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf016.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf016.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf017.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf017.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf018.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf018.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf019.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf019.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf020.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf020.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf021.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf021.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf022.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf022.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf023.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf023.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf024.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf024.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf025.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf025.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf026.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf026.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf027.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf027.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf028.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf028.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf029.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf029.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf030.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf030.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf031.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf031.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf032.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf032.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf033.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf033.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf034.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf034.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf035.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf035.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf036.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf036.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf037.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf037.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf038.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf038.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf039.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf039.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf040.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf040.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf041.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf041.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf042.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf042.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf043.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf043.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf044.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf044.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf045.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf045.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf046.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf046.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf047.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf047.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf048.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf048.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf049.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf049.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf050.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf050.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf051.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf051.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf052.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf052.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf053.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf053.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf054.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf054.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf055.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf055.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf056.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf056.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf057.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf057.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf058.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf058.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf059.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf059.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf060.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf060.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf061.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf061.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf062.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf062.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf063.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf063.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf064.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf064.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf065.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf065.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf066.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf066.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf067.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf067.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf068.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf068.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf069.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf069.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf070.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf070.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf071.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf071.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf072.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf072.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf073.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf073.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf074.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf074.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf075.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf075.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf076.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf076.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf077.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf077.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf078.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf078.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf079.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf079.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf080.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf080.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf081.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf081.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf082.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf082.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf083.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf083.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf084.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf084.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf085.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf085.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf086.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf086.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf087.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf087.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf088.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf088.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf089.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf089.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf090.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf090.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf091.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf091.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf092.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf092.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf093.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf093.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf094.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf094.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf095.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf095.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf096.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf096.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf097.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf097.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf098.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf098.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf099.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf099.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf100.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf100.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf101.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf101.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf102.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf102.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf103.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf103.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf104.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf104.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf105.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf105.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf106.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf106.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf107.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf107.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf108.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf108.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf109.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf109.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf110.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Split/WidthOf110.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf001.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf001.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf002.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf002.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf003.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf003.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf004.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf004.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf005.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf005.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf006.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf006.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf007.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf007.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf008.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf008.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf009.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf009.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf010.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf010.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf011.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf011.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf012.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf012.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf013.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf013.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf014.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf014.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf015.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf015.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf016.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf016.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf017.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf017.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf018.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf018.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf019.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf019.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf020.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf020.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf021.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf021.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf022.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf022.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf023.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf023.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf024.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf024.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf025.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf025.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf026.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf026.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf027.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf027.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf028.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf028.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf029.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf029.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf030.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf030.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf031.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf031.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf032.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf032.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf033.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf033.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf034.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf034.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf035.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf035.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf036.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf036.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf037.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf037.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf038.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf038.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf039.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf039.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf040.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf040.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf041.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf041.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf042.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf042.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf043.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf043.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf044.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf044.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf045.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf045.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf046.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf046.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf047.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf047.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf048.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf048.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf049.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf049.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf050.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf050.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf051.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf051.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf052.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf052.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf053.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf053.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf054.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf054.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf055.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf055.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf056.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf056.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf057.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf057.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf058.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf058.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf059.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf059.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf060.golden → internal/tui/exp/diffview/testdata/TestDiffViewWidth/Unified/WidthOf060.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf00.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf00.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf01.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf01.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf02.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf02.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf03.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf03.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf04.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf04.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf05.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf05.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf06.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf06.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf07.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf07.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf08.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf08.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf09.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf09.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf10.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf10.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf11.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf11.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf12.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf12.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf13.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf13.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf14.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf14.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf15.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf15.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf16.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf16.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf17.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf17.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf18.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf18.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf19.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf19.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf20.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Split/XOffsetOf20.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf00.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf00.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf01.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf01.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf02.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf02.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf03.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf03.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf04.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf04.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf05.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf05.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf06.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf06.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf07.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf07.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf08.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf08.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf09.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf09.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf10.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf10.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf11.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf11.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf12.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf12.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf13.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf13.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf14.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf14.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf15.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf15.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf16.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf16.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf17.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf17.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf18.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf18.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf19.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf19.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf20.golden → internal/tui/exp/diffview/testdata/TestDiffViewXOffset/Unified/XOffsetOf20.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf00.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf00.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf01.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf01.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf02.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf02.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf03.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf03.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf04.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf04.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf05.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf05.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf06.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf06.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf07.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf07.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf08.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf08.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf09.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf09.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf10.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf10.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf11.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf11.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf12.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf12.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf13.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf13.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf14.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf14.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf15.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf15.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf16.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Split/YOffsetOf16.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf00.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf00.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf01.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf01.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf02.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf02.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf03.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf03.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf04.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf04.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf05.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf05.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf06.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf06.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf07.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf07.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf08.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf08.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf09.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf09.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf10.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf10.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf11.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf11.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf12.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf12.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf13.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf13.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf14.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf14.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf15.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf15.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf16.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffset/Unified/YOffsetOf16.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf00.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf00.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf01.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf01.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf02.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf02.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf03.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf03.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf04.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf04.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf05.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf05.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf06.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf06.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf07.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf07.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf08.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf08.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf09.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf09.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf10.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf10.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf11.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf11.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf12.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf12.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf13.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf13.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf14.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf14.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf15.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf15.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf16.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Split/YOffsetOf16.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf00.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf00.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf01.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf01.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf02.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf02.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf03.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf03.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf04.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf04.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf05.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf05.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf06.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf06.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf07.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf07.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf08.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf08.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf09.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf09.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf10.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf10.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf11.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf11.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf12.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf12.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf13.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf13.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf14.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf14.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf15.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf15.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf16.golden → internal/tui/exp/diffview/testdata/TestDiffViewYOffsetInfinite/Unified/YOffsetOf16.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestMultipleHunks.after → internal/tui/exp/diffview/testdata/TestMultipleHunks.afterrenamed
The content is the same.
internal/exp/diffview/testdata/TestMultipleHunks.before → internal/tui/exp/diffview/testdata/TestMultipleHunks.beforerenamed
The content is the same.
internal/exp/diffview/testdata/TestNarrow.after → internal/tui/exp/diffview/testdata/TestNarrow.afterrenamed
The content is the same.
internal/exp/diffview/testdata/TestNarrow.before → internal/tui/exp/diffview/testdata/TestNarrow.beforerenamed
The content is the same.
internal/exp/diffview/testdata/TestTabs.after → internal/tui/exp/diffview/testdata/TestTabs.afterrenamed
The content is the same.
internal/exp/diffview/testdata/TestTabs.before → internal/tui/exp/diffview/testdata/TestTabs.beforerenamed
The content is the same.
internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLines/Content.golden → internal/tui/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLines/Content.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLines/JSON.golden → internal/tui/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLines/JSON.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLinesPlusOne/Content.golden → internal/tui/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLinesPlusOne/Content.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLinesPlusOne/JSON.golden → internal/tui/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLinesPlusOne/JSON.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLinesPlusTwo/Content.golden → internal/tui/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLinesPlusTwo/Content.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLinesPlusTwo/JSON.golden → internal/tui/exp/diffview/testdata/TestUdiff/ToUnifiedDiff/DefaultContextLinesPlusTwo/JSON.goldenrenamed
The content is the same.
internal/exp/diffview/testdata/TestUdiff/Unified.golden → internal/tui/exp/diffview/testdata/TestUdiff/Unified.goldenrenamed
The content is the same.
internal/exp/diffview/udiff_test.go → internal/tui/exp/diffview/udiff_test.gorenamed
The content is the same.
internal/exp/diffview/util.go → internal/tui/exp/diffview/util.gorenamed
The content is the same.
internal/exp/diffview/util_test.go → internal/tui/exp/diffview/util_test.gorenamed
The content is the same.
internal/tui/exp/list/list.gonew file+89
@@ -0,0 +1,89 @@
4+ tea "github.com/charmbracelet/bubbletea/v2"
5+ "github.com/charmbracelet/crush/internal/tui/components/core/layout"
6+ "github.com/charmbracelet/crush/internal/tui/util"
28+ filterPlaceholder string
31+type listOption func(*list)
33+// WithFilterable enables filtering on the list.
34+func WithFilterable(placeholder string) listOption {
35+ return func(l *list) {
37+ l.filterPlaceholder = placeholder
41+// WithItems sets the initial items for the list.
42+func WithItems(items ...Item) listOption {
43+ return func(l *list) {
48+// WithSize sets the size of the list.
49+func WithSize(width, height int) listOption {
50+ return func(l *list) {
56+// WithGap sets the gap between items in the list.
57+func WithGap(gap int) listOption {
58+ return func(l *list) {
63+func New(opts ...listOption) List {
65+ items: make([]Item, 0),
67+ for _, opt := range opts {
73+// Init implements List.
74+func (l *list) Init() tea.Cmd {
75+ if l.height <= 0 || l.width <= 0 {
81+// Update implements List.
82+func (l *list) Update(tea.Msg) (tea.Model, tea.Cmd) {
83+ panic("unimplemented")
86+// View implements List.
87+func (l *list) View() string {
88+ panic("unimplemented")
internal/highlight/highlight.go → internal/tui/highlight/highlight.gorenamed
The content is the same.
@@ -5,7 +5,6 @@ import (
7type KeyMap struct {
7type KeyMap struct {
9 Quit key.Binding
8 Quit key.Binding
10 Help key.Binding
9 Help key.Binding
11 Commands key.Binding
10 Commands key.Binding
@@ -16,10 +15,6 @@ type KeyMap struct {
17func DefaultKeyMap() KeyMap {
16func DefaultKeyMap() KeyMap {
18 return KeyMap{
17 return KeyMap{
19− Logs: key.NewBinding(
20− key.WithKeys("ctrl+l"),
21− key.WithHelp("ctrl+l", "logs"),
23 Quit: key.NewBinding(
18 Quit: key.NewBinding(
24 key.WithKeys("ctrl+c"),
19 key.WithKeys("ctrl+c"),
25 key.WithHelp("ctrl+c", "quit"),
20 key.WithHelp("ctrl+c", "quit"),
@@ -47,7 +42,6 @@ func (k KeyMap) FullHelp() [][]key.Binding {
47 k.Sessions,
42 k.Sessions,
52 slice = k.prependEscAndTab(slice)
46 slice = k.prependEscAndTab(slice)
53 slice = append(slice, k.pageBindings...)
47 slice = append(slice, k.pageBindings...)
internal/tui/page/chat/chat.go+15−9
@@ -170,11 +170,12 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
170 util.CmdHandler(ChatFocusedMsg{Focused: false}),
170 util.CmdHandler(ChatFocusedMsg{Focused: false}),
172 case key.Matches(msg, p.keyMap.AddAttachment):
172 case key.Matches(msg, p.keyMap.AddAttachment):
173− model := config.GetAgentModel(config.AgentCoder)
173+ agentCfg := config.Get().Agents["coder"]
174+ model := config.Get().GetModelByType(agentCfg.Model)
174 if model.SupportsImages {
175 if model.SupportsImages {
175 return p, util.CmdHandler(OpenFilePickerMsg{})
176 return p, util.CmdHandler(OpenFilePickerMsg{})
177− return p, util.ReportWarn("File attachments are not supported by the current model: " + model.Name)
178+ return p, util.ReportWarn("File attachments are not supported by the current model: " + model.Model)
179 case key.Matches(msg, p.keyMap.Tab):
180 case key.Matches(msg, p.keyMap.Tab):
180 if p.session.ID == "" {
181 if p.session.ID == "" {
@@ -302,7 +303,7 @@ func (p *chatPage) GetSize() (int, int) {
302 return p.layout.GetSize()
303 return p.layout.GetSize()
305−func (p *chatPage) View() tea.View {
306+func (p *chatPage) View() string {
306 if !p.compactMode || p.session.ID == "" {
307 if !p.compactMode || p.session.ID == "" {
307 // If not in compact mode or there is no session, we don't show the header
308 // If not in compact mode or there is no session, we don't show the header
308 return p.layout.View()
309 return p.layout.View()
@@ -310,8 +311,8 @@ func (p *chatPage) View() tea.View {
310 layoutView := p.layout.View()
311 layoutView := p.layout.View()
311 chatView := strings.Join(
312 chatView := strings.Join(
312 []string{
313 []string{
313− p.header.View().String(),
314+ p.header.View(),
314− layoutView.String(),
315+ layoutView,
317 layers := []*lipgloss.Layer{
318 layers := []*lipgloss.Layer{
@@ -326,7 +327,7 @@ func (p *chatPage) View() tea.View {
326 details := style.Render(
327 details := style.Render(
327 lipgloss.JoinVertical(
328 lipgloss.JoinVertical(
328 lipgloss.Left,
329 lipgloss.Left,
329− p.compactSidebar.View().String(),
330+ p.compactSidebar.View(),
@@ -335,9 +336,14 @@ func (p *chatPage) View() tea.View {
335 canvas := lipgloss.NewCanvas(
336 canvas := lipgloss.NewCanvas(
336 layers...,
337 layers...,
338− view := tea.NewView(canvas.Render())
339+ return canvas.Render()
339− view.SetCursor(layoutView.Cursor())
340+}
342+func (p *chatPage) Cursor() *tea.Cursor {
343+ if v, ok := p.layout.(util.Cursor); ok {
340− return view
346+ return nil
343func (p *chatPage) Bindings() []key.Binding {
349func (p *chatPage) Bindings() []key.Binding {
internal/tui/page/logs/keys.godeleted−43
@@ -1,43 +0,0 @@
4− "github.com/charmbracelet/bubbles/v2/key"
11−func DefaultKeyMap() KeyMap {
13− Back: key.NewBinding(
14− key.WithKeys("esc", "backspace"),
15− key.WithHelp("esc/backspace", "back to chat"),
20−// KeyBindings implements layout.KeyMapProvider
21−func (k KeyMap) KeyBindings() []key.Binding {
22− return []key.Binding{
27−// FullHelp implements help.KeyMap.
28−func (k KeyMap) FullHelp() [][]key.Binding {
29− m := [][]key.Binding{}
30− slice := k.KeyBindings()
31− for i := 0; i < len(slice); i += 4 {
32− end := min(i+4, len(slice))
33− m = append(m, slice[i:end])
38−// ShortHelp implements help.KeyMap.
39−func (k KeyMap) ShortHelp() []key.Binding {
40− return []key.Binding{
internal/tui/page/logs/logs.godeleted−100
@@ -1,100 +0,0 @@
4− "github.com/charmbracelet/bubbles/v2/key"
5− tea "github.com/charmbracelet/bubbletea/v2"
6− "github.com/charmbracelet/crush/internal/tui/components/core"
7− "github.com/charmbracelet/crush/internal/tui/components/core/layout"
8− logsComponents "github.com/charmbracelet/crush/internal/tui/components/logs"
9− "github.com/charmbracelet/crush/internal/tui/page"
10− "github.com/charmbracelet/crush/internal/tui/page/chat"
11− "github.com/charmbracelet/crush/internal/tui/styles"
12− "github.com/charmbracelet/crush/internal/tui/util"
13− "github.com/charmbracelet/lipgloss/v2"
16−var LogsPage page.PageID = "logs"
18−type LogPage interface {
23−type logsPage struct {
25− table logsComponents.TableComponent
26− details logsComponents.DetailComponent
30−func (p *logsPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
32− switch msg := msg.(type) {
33− case tea.WindowSizeMsg:
35− p.height = msg.Height
36− return p, p.SetSize(msg.Width, msg.Height)
39− case key.Matches(msg, p.keyMap.Back):
40− return p, util.CmdHandler(page.PageChangeMsg{ID: chat.ChatPageID})
44− table, cmd := p.table.Update(msg)
45− cmds = append(cmds, cmd)
46− p.table = table.(logsComponents.TableComponent)
47− details, cmd := p.details.Update(msg)
48− cmds = append(cmds, cmd)
49− p.details = details.(logsComponents.DetailComponent)
51− return p, tea.Batch(cmds...)
54−func (p *logsPage) View() tea.View {
55− baseStyle := styles.CurrentTheme().S().Base
56− style := baseStyle.Width(p.width).Height(p.height).Padding(1)
57− title := core.Title("Logs", p.width-2)
61− lipgloss.JoinVertical(lipgloss.Top,
63− p.details.View().String(),
64− p.table.View().String(),
70−// GetSize implements LogPage.
71−func (p *logsPage) GetSize() (int, int) {
72− return p.width, p.height
75−// SetSize implements LogPage.
76−func (p *logsPage) SetSize(width int, height int) tea.Cmd {
79− availableHeight := height - 2 // Padding for top and bottom
80− availableHeight -= 1 // title height
82− p.table.SetSize(width-2, availableHeight/2),
83− p.details.SetSize(width-2, availableHeight/2),
87−func (p *logsPage) Init() tea.Cmd {
94−func NewLogsPage() LogPage {
96− details: logsComponents.NewLogsDetails(),
97− table: logsComponents.NewLogsTable(),
98− keyMap: DefaultKeyMap(),
internal/tui/styles/theme.go+1−1
@@ -10,7 +10,7 @@ import (
10 "github.com/charmbracelet/bubbles/v2/textarea"
10 "github.com/charmbracelet/bubbles/v2/textarea"
11 "github.com/charmbracelet/bubbles/v2/textinput"
11 "github.com/charmbracelet/bubbles/v2/textinput"
12 tea "github.com/charmbracelet/bubbletea/v2"
12 tea "github.com/charmbracelet/bubbletea/v2"
13− "github.com/charmbracelet/crush/internal/exp/diffview"
13+ "github.com/charmbracelet/crush/internal/tui/exp/diffview"
14 "github.com/charmbracelet/glamour/v2/ansi"
14 "github.com/charmbracelet/glamour/v2/ansi"
15 "github.com/charmbracelet/lipgloss/v2"
15 "github.com/charmbracelet/lipgloss/v2"
16 "github.com/charmbracelet/x/exp/charmtone"
16 "github.com/charmbracelet/x/exp/charmtone"
internal/tui/tui.go+22−34
@@ -9,7 +9,6 @@ import (
9 "github.com/charmbracelet/crush/internal/app"
9 "github.com/charmbracelet/crush/internal/app"
10 "github.com/charmbracelet/crush/internal/config"
10 "github.com/charmbracelet/crush/internal/config"
11 "github.com/charmbracelet/crush/internal/llm/agent"
11 "github.com/charmbracelet/crush/internal/llm/agent"
12− "github.com/charmbracelet/crush/internal/logging"
13 "github.com/charmbracelet/crush/internal/permission"
12 "github.com/charmbracelet/crush/internal/permission"
14 "github.com/charmbracelet/crush/internal/pubsub"
13 "github.com/charmbracelet/crush/internal/pubsub"
15 cmpChat "github.com/charmbracelet/crush/internal/tui/components/chat"
14 cmpChat "github.com/charmbracelet/crush/internal/tui/components/chat"
@@ -27,7 +26,6 @@ import (
27 "github.com/charmbracelet/crush/internal/tui/components/dialogs/sessions"
26 "github.com/charmbracelet/crush/internal/tui/components/dialogs/sessions"
28 "github.com/charmbracelet/crush/internal/tui/page"
27 "github.com/charmbracelet/crush/internal/tui/page"
29 "github.com/charmbracelet/crush/internal/tui/page/chat"
28 "github.com/charmbracelet/crush/internal/tui/page/chat"
30− "github.com/charmbracelet/crush/internal/tui/page/logs"
31 "github.com/charmbracelet/crush/internal/tui/styles"
29 "github.com/charmbracelet/crush/internal/tui/styles"
32 "github.com/charmbracelet/crush/internal/tui/util"
30 "github.com/charmbracelet/crush/internal/tui/util"
33 "github.com/charmbracelet/lipgloss/v2"
31 "github.com/charmbracelet/lipgloss/v2"
@@ -84,6 +82,9 @@ func (a appModel) Init() tea.Cmd {
84 return nil
82 return nil
85+ // Enable mouse support.
86+ cmds = append(cmds, tea.EnableMouseAllMotion)
87 return tea.Batch(cmds...)
88 return tea.Batch(cmds...)
@@ -135,20 +136,6 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
135 a.selectedSessionID = msg.ID
136 a.selectedSessionID = msg.ID
136 case cmpChat.SessionClearedMsg:
137 case cmpChat.SessionClearedMsg:
137 a.selectedSessionID = ""
138 a.selectedSessionID = ""
139− case pubsub.Event[logging.LogMessage]:
140− // Send to the status component
141− s, statusCmd := a.status.Update(msg)
142− a.status = s.(status.StatusCmp)
143− cmds = append(cmds, statusCmd)
145− // If the current page is logs, update the logs view
146− if a.currentPage == logs.LogsPage {
147− updated, pageCmd := a.pages[a.currentPage].Update(msg)
148− a.pages[a.currentPage] = updated.(util.Model)
149− cmds = append(cmds, pageCmd)
151− return a, tea.Batch(cmds...)
152 // Commands
139 // Commands
153 case commands.SwitchSessionsMsg:
140 case commands.SwitchSessionsMsg:
154 return a, func() tea.Msg {
141 return a, func() tea.Msg {
@@ -176,15 +163,14 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
177 // Update the agent with the new model/provider configuration
164 // Update the agent with the new model/provider configuration
178 if err := a.app.UpdateAgentModel(); err != nil {
165 if err := a.app.UpdateAgentModel(); err != nil {
179− logging.ErrorPersist(fmt.Sprintf("Failed to update agent model: %v", err))
180− return a, util.ReportError(fmt.Errorf("model changed to %s but failed to update agent: %v", msg.Model.ModelID, err))
166+ return a, util.ReportError(fmt.Errorf("model changed to %s but failed to update agent: %v", msg.Model.Model, err))
183 modelTypeName := "large"
169 modelTypeName := "large"
184− if msg.ModelType == config.SmallModel {
170+ if msg.ModelType == config.SelectedModelTypeSmall {
185 modelTypeName = "small"
171 modelTypeName = "small"
187− return a, util.ReportInfo(fmt.Sprintf("%s model changed to %s", modelTypeName, msg.Model.ModelID))
173+ return a, util.ReportInfo(fmt.Sprintf("%s model changed to %s", modelTypeName, msg.Model.Model))
189 // File Picker
175 // File Picker
190 case chat.OpenFilePickerMsg:
176 case chat.OpenFilePickerMsg:
@@ -348,10 +334,6 @@ func (a *appModel) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
350 return tea.Sequence(cmds...)
336 return tea.Sequence(cmds...)
352− case key.Matches(msg, a.keyMap.Logs):
353− return a.moveToPage(logs.LogsPage)
356 if a.dialog.HasDialogs() {
338 if a.dialog.HasDialogs() {
357 u, dialogCmd := a.dialog.Update(msg)
339 u, dialogCmd := a.dialog.Update(msg)
@@ -397,9 +379,9 @@ func (a *appModel) View() tea.View {
397 a.status.SetKeyMap(a.keyMap)
379 a.status.SetKeyMap(a.keyMap)
398 pageView := page.View()
380 pageView := page.View()
399 components := []string{
381 components := []string{
400− pageView.String(),
382+ pageView,
402− components = append(components, a.status.View().String())
384+ components = append(components, a.status.View())
404 appView := lipgloss.JoinVertical(lipgloss.Top, components...)
386 appView := lipgloss.JoinVertical(lipgloss.Top, components...)
405 layers := []*lipgloss.Layer{
387 layers := []*lipgloss.Layer{
@@ -412,14 +394,20 @@ func (a *appModel) View() tea.View {
415− cursor := pageView.Cursor()
397+ var cursor *tea.Cursor
398+ if v, ok := page.(util.Cursor); ok {
416− activeView := a.dialog.ActiveView()
401+ activeView := a.dialog.ActiveModel()
417 if activeView != nil {
402 if activeView != nil {
403+ cursor = nil // Reset cursor if a dialog is active unless it implements util.Cursor
418− cursor = activeView.Cursor()
404+ if v, ok := activeView.(util.Cursor); ok {
421 if a.completions.Open() && cursor != nil {
409 if a.completions.Open() && cursor != nil {
422− cmp := a.completions.View().String()
410+ cmp := a.completions.View()
423 x, y := a.completions.Position()
411 x, y := a.completions.Position()
424 layers = append(
412 layers = append(
@@ -431,10 +419,11 @@ func (a *appModel) View() tea.View {
431 layers...,
419 layers...,
434 t := styles.CurrentTheme()
423 t := styles.CurrentTheme()
435− view := tea.NewView(canvas.Render())
424+ view.Layer = canvas
436− view.SetBackgroundColor(t.BgBase)
425+ view.BackgroundColor = t.BgBase
437− view.SetCursor(cursor)
426+ view.Cursor = cursor
438 return view
427 return view
@@ -453,7 +442,6 @@ func New(app *app.App) tea.Model {
454 pages: map[page.PageID]util.Model{
443 pages: map[page.PageID]util.Model{
455 chat.ChatPageID: chatPage,
444 chat.ChatPageID: chatPage,
456− logs.LogsPage: logs.NewLogsPage(),
459 dialog: dialogs.NewDialogCmp(),
447 dialog: dialogs.NewDialogCmp(),
internal/tui/util/util.go+5−1
@@ -6,9 +6,13 @@ import (
6 tea "github.com/charmbracelet/bubbletea/v2"
6 tea "github.com/charmbracelet/bubbletea/v2"
9+type Cursor interface {
9type Model interface {
13type Model interface {
11− tea.Viewable
15+ tea.ViewModel
14func CmdHandler(msg tea.Msg) tea.Cmd {
18func CmdHandler(msg tea.Msg) tea.Cmd {
@@ -1,25 +1,29 @@
1package main
1package main
7 _ "net/http/pprof" // profiling
9 _ "net/http/pprof" // profiling
11+ _ "github.com/joho/godotenv/autoload" // automatically load .env files
9 "github.com/charmbracelet/crush/cmd"
13 "github.com/charmbracelet/crush/cmd"
10− "github.com/charmbracelet/crush/internal/logging"
14+ "github.com/charmbracelet/crush/internal/log"
13func main() {
17func main() {
14− defer logging.RecoverPanic("main", func() {
18+ defer log.RecoverPanic("main", func() {
15− logging.ErrorPersist("Application terminated due to unhandled panic")
19+ slog.Error("Application terminated due to unhandled panic")
18 if os.Getenv("CRUSH_PROFILE") != "" {
22 if os.Getenv("CRUSH_PROFILE") != "" {
19 go func() {
23 go func() {
20− logging.Info("Serving pprof at localhost:6060")
24+ slog.Info("Serving pprof at localhost:6060")
21 if httpErr := http.ListenAndServe("localhost:6060", nil); httpErr != nil {
25 if httpErr := http.ListenAndServe("localhost:6060", nil); httpErr != nil {
22− logging.Error("Failed to pprof listen: %v", httpErr)
26+ slog.Error(fmt.Sprintf("Failed to pprof listen: %v", httpErr))
@@ -1,51 +0,0 @@
1−## TODOs before release
5− - [x] Make help dependent on the focused pane and page
6−- [x] Implement current model in the sidebar
7−- [x] Implement LSP errors
8−- [x] Implement changed files
9− - [x] Implement initial load
10− - [x] Implement realtime file changes
11−- [ ] Events when tool error
12−- [ ] Support bash commands
13−- [ ] Editor attachments fixes
14− - [ ] Reimplement removing attachments
15−- [ ] Fix the logs view
16− - [ ] Review the implementation
18− - [ ] Make the logs long lived ?
19−- [ ] Add all possible actions to the commands
20−- [ ] Parallel tool calls and permissions
21− - [ ] Run the tools in parallel and add results in parallel
22− - [ ] Show multiple permissions dialogs
23−- [ ] Add another space around buttons
25− - [ ] Should change the help to show the completions stuff
26− - [ ] Should make it wider
27− - [ ] Tab and ctrl+y should accept
28− - [ ] Words should line up
29− - [ ] If there are no completions and cick tab/ctrl+y/enter it should close it
30−- [ ] Investigate messages issues
31− - [ ] Make the agent separator look like the
32− - [ ] Cleanup tool calls (watch all states)
33− - [ ] Weird behavior sometimes the message does not update
34− - [ ] Message length (I saw the message go beyond the correct length when there are errors)
35− - [ ] Address UX issues
36− - [ ] Fix issue with numbers (padding) view tool
37−- [x] Implement responsive mode
38−- [ ] Update interactive mode to use the spinner
39−- [ ] Revisit the core list component
40− - [ ] This component has become super complex we might need to fix this.
41−- [ ] Handle correct LSP and MCP status icon
42−- [x] Investigate ways to make the spinner less CPU intensive
43−- [ ] General cleanup and documentation
44−- [ ] Update the readme
48−- [ ] Revisit the provider/model/configs
49−- [ ] Implement correct persistent shell
50−- [ ] Store file read/write time somewhere so that the we can make sure that even if we restart we do not need to re-read the same file
51−- [ ] Send updates to the UI when new LSP diagnostics are available
vendor/cloud.google.com/go/LICENSEnew file+202
@@ -0,0 +1,202 @@
3+ Version 2.0, January 2004
4+ http://www.apache.org/licenses/
6+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
10+ "License" shall mean the terms and conditions for use, reproduction,
11+ and distribution as defined by Sections 1 through 9 of this document.
13+ "Licensor" shall mean the copyright owner or entity authorized by
14+ the copyright owner that is granting the License.
16+ "Legal Entity" shall mean the union of the acting entity and all
17+ other entities that control, are controlled by, or are under common
18+ control with that entity. For the purposes of this definition,
19+ "control" means (i) the power, direct or indirect, to cause the
20+ direction or management of such entity, whether by contract or
21+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22+ outstanding shares, or (iii) beneficial ownership of such entity.
24+ "You" (or "Your") shall mean an individual or Legal Entity
25+ exercising permissions granted by this License.
27+ "Source" form shall mean the preferred form for making modifications,
28+ including but not limited to software source code, documentation
29+ source, and configuration files.
31+ "Object" form shall mean any form resulting from mechanical
32+ transformation or translation of a Source form, including but
33+ not limited to compiled object code, generated documentation,
34+ and conversions to other media types.
36+ "Work" shall mean the work of authorship, whether in Source or
37+ Object form, made available under the License, as indicated by a
38+ copyright notice that is included in or attached to the work
39+ (an example is provided in the Appendix below).
41+ "Derivative Works" shall mean any work, whether in Source or Object
42+ form, that is based on (or derived from) the Work and for which the
43+ editorial revisions, annotations, elaborations, or other modifications
44+ represent, as a whole, an original work of authorship. For the purposes
45+ of this License, Derivative Works shall not include works that remain
46+ separable from, or merely link (or bind by name) to the interfaces of,
47+ the Work and Derivative Works thereof.
49+ "Contribution" shall mean any work of authorship, including
50+ the original version of the Work and any modifications or additions
51+ to that Work or Derivative Works thereof, that is intentionally
52+ submitted to Licensor for inclusion in the Work by the copyright owner
53+ or by an individual or Legal Entity authorized to submit on behalf of
54+ the copyright owner. For the purposes of this definition, "submitted"
55+ means any form of electronic, verbal, or written communication sent
56+ to the Licensor or its representatives, including but not limited to
57+ communication on electronic mailing lists, source code control systems,
58+ and issue tracking systems that are managed by, or on behalf of, the
59+ Licensor for the purpose of discussing and improving the Work, but
60+ excluding communication that is conspicuously marked or otherwise
61+ designated in writing by the copyright owner as "Not a Contribution."
63+ "Contributor" shall mean Licensor and any individual or Legal Entity
64+ on behalf of whom a Contribution has been received by Licensor and
65+ subsequently incorporated within the Work.
67+ 2. Grant of Copyright License. Subject to the terms and conditions of
68+ this License, each Contributor hereby grants to You a perpetual,
69+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70+ copyright license to reproduce, prepare Derivative Works of,
71+ publicly display, publicly perform, sublicense, and distribute the
72+ Work and such Derivative Works in Source or Object form.
74+ 3. Grant of Patent License. Subject to the terms and conditions of
75+ this License, each Contributor hereby grants to You a perpetual,
76+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77+ (except as stated in this section) patent license to make, have made,
78+ use, offer to sell, sell, import, and otherwise transfer the Work,
79+ where such license applies only to those patent claims licensable
80+ by such Contributor that are necessarily infringed by their
81+ Contribution(s) alone or by combination of their Contribution(s)
82+ with the Work to which such Contribution(s) was submitted. If You
83+ institute patent litigation against any entity (including a
84+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85+ or a Contribution incorporated within the Work constitutes direct
86+ or contributory patent infringement, then any patent licenses
87+ granted to You under this License for that Work shall terminate
88+ as of the date such litigation is filed.
90+ 4. Redistribution. You may reproduce and distribute copies of the
91+ Work or Derivative Works thereof in any medium, with or without
92+ modifications, and in Source or Object form, provided that You
93+ meet the following conditions:
95+ (a) You must give any other recipients of the Work or
96+ Derivative Works a copy of this License; and
98+ (b) You must cause any modified files to carry prominent notices
99+ stating that You changed the files; and
101+ (c) You must retain, in the Source form of any Derivative Works
102+ that You distribute, all copyright, patent, trademark, and
103+ attribution notices from the Source form of the Work,
104+ excluding those notices that do not pertain to any part of
105+ the Derivative Works; and
107+ (d) If the Work includes a "NOTICE" text file as part of its
108+ distribution, then any Derivative Works that You distribute must
109+ include a readable copy of the attribution notices contained
110+ within such NOTICE file, excluding those notices that do not
111+ pertain to any part of the Derivative Works, in at least one
112+ of the following places: within a NOTICE text file distributed
113+ as part of the Derivative Works; within the Source form or
114+ documentation, if provided along with the Derivative Works; or,
115+ within a display generated by the Derivative Works, if and
116+ wherever such third-party notices normally appear. The contents
117+ of the NOTICE file are for informational purposes only and
118+ do not modify the License. You may add Your own attribution
119+ notices within Derivative Works that You distribute, alongside
120+ or as an addendum to the NOTICE text from the Work, provided
121+ that such additional attribution notices cannot be construed
122+ as modifying the License.
124+ You may add Your own copyright statement to Your modifications and
125+ may provide additional or different license terms and conditions
126+ for use, reproduction, or distribution of Your modifications, or
127+ for any such Derivative Works as a whole, provided Your use,
128+ reproduction, and distribution of the Work otherwise complies with
129+ the conditions stated in this License.
131+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132+ any Contribution intentionally submitted for inclusion in the Work
133+ by You to the Licensor shall be under the terms and conditions of
134+ this License, without any additional terms or conditions.
135+ Notwithstanding the above, nothing herein shall supersede or modify
136+ the terms of any separate license agreement you may have executed
137+ with Licensor regarding such Contributions.
139+ 6. Trademarks. This License does not grant permission to use the trade
140+ names, trademarks, service marks, or product names of the Licensor,
141+ except as required for reasonable and customary use in describing the
142+ origin of the Work and reproducing the content of the NOTICE file.
144+ 7. Disclaimer of Warranty. Unless required by applicable law or
145+ agreed to in writing, Licensor provides the Work (and each
146+ Contributor provides its Contributions) on an "AS IS" BASIS,
147+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148+ implied, including, without limitation, any warranties or conditions
149+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150+ PARTICULAR PURPOSE. You are solely responsible for determining the
151+ appropriateness of using or redistributing the Work and assume any
152+ risks associated with Your exercise of permissions under this License.
154+ 8. Limitation of Liability. In no event and under no legal theory,
155+ whether in tort (including negligence), contract, or otherwise,
156+ unless required by applicable law (such as deliberate and grossly
157+ negligent acts) or agreed to in writing, shall any Contributor be
158+ liable to You for damages, including any direct, indirect, special,
159+ incidental, or consequential damages of any character arising as a
160+ result of this License or out of the use or inability to use the
161+ Work (including but not limited to damages for loss of goodwill,
162+ work stoppage, computer failure or malfunction, or any and all
163+ other commercial damages or losses), even if such Contributor
164+ has been advised of the possibility of such damages.
166+ 9. Accepting Warranty or Additional Liability. While redistributing
167+ the Work or Derivative Works thereof, You may choose to offer,
168+ and charge a fee for, acceptance of support, warranty, indemnity,
169+ or other liability obligations and/or rights consistent with this
170+ License. However, in accepting such obligations, You may act only
171+ on Your own behalf and on Your sole responsibility, not on behalf
172+ of any other Contributor, and only if You agree to indemnify,
173+ defend, and hold each Contributor harmless for any liability
174+ incurred by, or claims asserted against, such Contributor by reason
175+ of your accepting any such warranty or additional liability.
177+ END OF TERMS AND CONDITIONS
179+ APPENDIX: How to apply the Apache License to your work.
181+ To apply the Apache License to your work, attach the following
182+ boilerplate notice, with the fields enclosed by brackets "[]"
183+ replaced with your own identifying information. (Don't include
184+ the brackets!) The text should be enclosed in the appropriate
185+ comment syntax for the file format. We also recommend that a
186+ file or class name and description of purpose be included on the
187+ same "printed page" as the copyright notice for easier
188+ identification within third-party archives.
190+ Copyright [yyyy] [name of copyright owner]
192+ Licensed under the Apache License, Version 2.0 (the "License");
193+ you may not use this file except in compliance with the License.
194+ You may obtain a copy of the License at
196+ http://www.apache.org/licenses/LICENSE-2.0
198+ Unless required by applicable law or agreed to in writing, software
199+ distributed under the License is distributed on an "AS IS" BASIS,
200+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201+ See the License for the specific language governing permissions and
202+ limitations under the License.
vendor/cloud.google.com/go/auth/CHANGES.mdnew file+368
@@ -0,0 +1,368 @@
3+## [0.13.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.12.1...auth/v0.13.0) (2024-12-13)
8+* **auth:** Add logging support ([#11079](https://github.com/googleapis/google-cloud-go/issues/11079)) ([c80e31d](https://github.com/googleapis/google-cloud-go/commit/c80e31df5ecb33a810be3dfb9d9e27ac531aa91d))
9+* **auth:** Pass logger from auth layer to metadata package ([#11288](https://github.com/googleapis/google-cloud-go/issues/11288)) ([b552efd](https://github.com/googleapis/google-cloud-go/commit/b552efd6ab34e5dfded18438e0fbfd925805614f))
14+* **auth:** Check compute cred type before non-default flag for DP ([#11255](https://github.com/googleapis/google-cloud-go/issues/11255)) ([4347ca1](https://github.com/googleapis/google-cloud-go/commit/4347ca141892be8ae813399b4b437662a103bc90))
16+## [0.12.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.12.0...auth/v0.12.1) (2024-12-10)
21+* **auth:** Correct typo in link ([#11160](https://github.com/googleapis/google-cloud-go/issues/11160)) ([af6fb46](https://github.com/googleapis/google-cloud-go/commit/af6fb46d7cd694ddbe8c9d63bc4cdcd62b9fb2c1))
23+## [0.12.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.11.0...auth/v0.12.0) (2024-12-04)
28+* **auth:** Add support for providing custom certificate URL ([#11006](https://github.com/googleapis/google-cloud-go/issues/11006)) ([ebf3657](https://github.com/googleapis/google-cloud-go/commit/ebf36579724afb375d3974cf1da38f703e3b7dbc)), refs [#11005](https://github.com/googleapis/google-cloud-go/issues/11005)
33+* **auth:** Ensure endpoints are present in Validator ([#11209](https://github.com/googleapis/google-cloud-go/issues/11209)) ([106cd53](https://github.com/googleapis/google-cloud-go/commit/106cd53309facaef1b8ea78376179f523f6912b9)), refs [#11006](https://github.com/googleapis/google-cloud-go/issues/11006) [#11190](https://github.com/googleapis/google-cloud-go/issues/11190) [#11189](https://github.com/googleapis/google-cloud-go/issues/11189) [#11188](https://github.com/googleapis/google-cloud-go/issues/11188)
35+## [0.11.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.10.2...auth/v0.11.0) (2024-11-21)
40+* **auth:** Add universe domain support to mTLS ([#11159](https://github.com/googleapis/google-cloud-go/issues/11159)) ([117748b](https://github.com/googleapis/google-cloud-go/commit/117748ba1cfd4ae62a6a4feb7e30951cb2bc9344))
42+## [0.10.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.10.1...auth/v0.10.2) (2024-11-12)
47+* **auth:** Restore use of grpc.Dial ([#11118](https://github.com/googleapis/google-cloud-go/issues/11118)) ([2456b94](https://github.com/googleapis/google-cloud-go/commit/2456b943b7b8aaabd4d8bfb7572c0f477ae0db45)), refs [#7556](https://github.com/googleapis/google-cloud-go/issues/7556)
49+## [0.10.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.10.0...auth/v0.10.1) (2024-11-06)
54+* **auth:** Restore Application Default Credentials support to idtoken ([#11083](https://github.com/googleapis/google-cloud-go/issues/11083)) ([8771f2e](https://github.com/googleapis/google-cloud-go/commit/8771f2ea9807ab822083808e0678392edff3b4f2))
55+* **auth:** Skip impersonate universe domain check if empty ([#11086](https://github.com/googleapis/google-cloud-go/issues/11086)) ([87159c1](https://github.com/googleapis/google-cloud-go/commit/87159c1059d4a18d1367ce62746a838a94964ab6))
57+## [0.10.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.9...auth/v0.10.0) (2024-10-30)
62+* **auth:** Add universe domain support to credentials/impersonate ([#10953](https://github.com/googleapis/google-cloud-go/issues/10953)) ([e06cb64](https://github.com/googleapis/google-cloud-go/commit/e06cb6499f7eda3aef08ab18ff197016f667684b))
64+## [0.9.9](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.8...auth/v0.9.9) (2024-10-22)
69+* **auth:** Fallback cert lookups for missing files ([#11013](https://github.com/googleapis/google-cloud-go/issues/11013)) ([bd76695](https://github.com/googleapis/google-cloud-go/commit/bd766957ec238b7c40ddbabb369e612dc9b07313)), refs [#10844](https://github.com/googleapis/google-cloud-go/issues/10844)
70+* **auth:** Replace MDS endpoint universe_domain with universe-domain ([#11000](https://github.com/googleapis/google-cloud-go/issues/11000)) ([6a1586f](https://github.com/googleapis/google-cloud-go/commit/6a1586f2ce9974684affaea84e7b629313b4d114))
72+## [0.9.8](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.7...auth/v0.9.8) (2024-10-09)
77+* **auth:** Restore OpenTelemetry handling in transports ([#10968](https://github.com/googleapis/google-cloud-go/issues/10968)) ([08c6d04](https://github.com/googleapis/google-cloud-go/commit/08c6d04901c1a20e219b2d86df41dbaa6d7d7b55)), refs [#10962](https://github.com/googleapis/google-cloud-go/issues/10962)
78+* **auth:** Try talk to plaintext S2A if credentials can not be found for mTLS-S2A ([#10941](https://github.com/googleapis/google-cloud-go/issues/10941)) ([0f0bf2d](https://github.com/googleapis/google-cloud-go/commit/0f0bf2d18c97dd8b65bcf0099f0802b5631c6287))
80+## [0.9.7](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.6...auth/v0.9.7) (2024-10-01)
85+* **auth:** Restore support for non-default service accounts for DirectPath ([#10937](https://github.com/googleapis/google-cloud-go/issues/10937)) ([a38650e](https://github.com/googleapis/google-cloud-go/commit/a38650edbf420223077498cafa537aec74b37aad)), refs [#10907](https://github.com/googleapis/google-cloud-go/issues/10907)
87+## [0.9.6](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.5...auth/v0.9.6) (2024-09-30)
92+* **auth:** Make aws credentials provider retrieve fresh credentials ([#10920](https://github.com/googleapis/google-cloud-go/issues/10920)) ([250fbf8](https://github.com/googleapis/google-cloud-go/commit/250fbf87d858d865e399a241b7e537c4ff0c3dd8))
94+## [0.9.5](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.4...auth/v0.9.5) (2024-09-25)
99+* **auth:** Restore support for GOOGLE_CLOUD_UNIVERSE_DOMAIN env ([#10915](https://github.com/googleapis/google-cloud-go/issues/10915)) ([94caaaa](https://github.com/googleapis/google-cloud-go/commit/94caaaa061362d0e00ef6214afcc8a0a3e7ebfb2))
100+* **auth:** Skip directpath credentials overwrite when it's not on GCE ([#10833](https://github.com/googleapis/google-cloud-go/issues/10833)) ([7e5e8d1](https://github.com/googleapis/google-cloud-go/commit/7e5e8d10b761b0a6e43e19a028528db361bc07b1))
101+* **auth:** Use new context for non-blocking token refresh ([#10919](https://github.com/googleapis/google-cloud-go/issues/10919)) ([cf7102d](https://github.com/googleapis/google-cloud-go/commit/cf7102d33a21be1e5a9d47a49456b3a57c43b350))
103+## [0.9.4](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.3...auth/v0.9.4) (2024-09-11)
108+* **auth:** Enable self-signed JWT for non-GDU universe domain ([#10831](https://github.com/googleapis/google-cloud-go/issues/10831)) ([f9869f7](https://github.com/googleapis/google-cloud-go/commit/f9869f7903cfd34d1b97c25d0dc5669d2c5138e6))
110+## [0.9.3](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.2...auth/v0.9.3) (2024-09-03)
115+* **auth:** Choose quota project envvar over file when both present ([#10807](https://github.com/googleapis/google-cloud-go/issues/10807)) ([2d8dd77](https://github.com/googleapis/google-cloud-go/commit/2d8dd7700eff92d4b95027be55e26e1e7aa79181)), refs [#10804](https://github.com/googleapis/google-cloud-go/issues/10804)
117+## [0.9.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.1...auth/v0.9.2) (2024-08-30)
122+* **auth:** Handle non-Transport DefaultTransport ([#10733](https://github.com/googleapis/google-cloud-go/issues/10733)) ([98d91dc](https://github.com/googleapis/google-cloud-go/commit/98d91dc8316b247498fab41ab35e57a0446fe556)), refs [#10742](https://github.com/googleapis/google-cloud-go/issues/10742)
123+* **auth:** Make sure quota option takes precedence over env/file ([#10797](https://github.com/googleapis/google-cloud-go/issues/10797)) ([f1b050d](https://github.com/googleapis/google-cloud-go/commit/f1b050d56d804b245cab048c2980d32b0eaceb4e)), refs [#10795](https://github.com/googleapis/google-cloud-go/issues/10795)
128+* **auth:** Fix Go doc comment link ([#10751](https://github.com/googleapis/google-cloud-go/issues/10751)) ([015acfa](https://github.com/googleapis/google-cloud-go/commit/015acfab4d172650928bb1119bc2cd6307b9a437))
130+## [0.9.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.0...auth/v0.9.1) (2024-08-22)
135+* **auth:** Setting expireEarly to default when the value is 0 ([#10732](https://github.com/googleapis/google-cloud-go/issues/10732)) ([5e67869](https://github.com/googleapis/google-cloud-go/commit/5e67869a31e9e8ecb4eeebd2cfa11a761c3b1948))
137+## [0.9.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.8.1...auth/v0.9.0) (2024-08-16)
142+* **auth:** Auth library can talk to S2A over mTLS ([#10634](https://github.com/googleapis/google-cloud-go/issues/10634)) ([5250a13](https://github.com/googleapis/google-cloud-go/commit/5250a13ec95b8d4eefbe0158f82857ff2189cb45))
144+## [0.8.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.8.0...auth/v0.8.1) (2024-08-13)
149+* **auth:** Make default client creation more lenient ([#10669](https://github.com/googleapis/google-cloud-go/issues/10669)) ([1afb9ee](https://github.com/googleapis/google-cloud-go/commit/1afb9ee1ee9de9810722800018133304a0ca34d1)), refs [#10638](https://github.com/googleapis/google-cloud-go/issues/10638)
151+## [0.8.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.7.3...auth/v0.8.0) (2024-08-07)
156+* **auth:** Adds support for X509 workload identity federation ([#10373](https://github.com/googleapis/google-cloud-go/issues/10373)) ([5d07505](https://github.com/googleapis/google-cloud-go/commit/5d075056cbe27bb1da4072a26070c41f8999eb9b))
158+## [0.7.3](https://github.com/googleapis/google-cloud-go/compare/auth/v0.7.2...auth/v0.7.3) (2024-08-01)
163+* **auth/oauth2adapt:** Update dependencies ([257c40b](https://github.com/googleapis/google-cloud-go/commit/257c40bd6d7e59730017cf32bda8823d7a232758))
164+* **auth:** Disable automatic universe domain check for MDS ([#10620](https://github.com/googleapis/google-cloud-go/issues/10620)) ([7cea5ed](https://github.com/googleapis/google-cloud-go/commit/7cea5edd5a0c1e6bca558696f5607879141910e8))
165+* **auth:** Update dependencies ([257c40b](https://github.com/googleapis/google-cloud-go/commit/257c40bd6d7e59730017cf32bda8823d7a232758))
167+## [0.7.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.7.1...auth/v0.7.2) (2024-07-22)
172+* **auth:** Use default client for universe metadata lookup ([#10551](https://github.com/googleapis/google-cloud-go/issues/10551)) ([d9046fd](https://github.com/googleapis/google-cloud-go/commit/d9046fdd1435d1ce48f374806c1def4cb5ac6cd3)), refs [#10544](https://github.com/googleapis/google-cloud-go/issues/10544)
174+## [0.7.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.7.0...auth/v0.7.1) (2024-07-10)
179+* **auth:** Bump google.golang.org/grpc@v1.64.1 ([8ecc4e9](https://github.com/googleapis/google-cloud-go/commit/8ecc4e9622e5bbe9b90384d5848ab816027226c5))
181+## [0.7.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.6.1...auth/v0.7.0) (2024-07-09)
186+* **auth:** Add workload X509 cert provider as a default cert provider ([#10479](https://github.com/googleapis/google-cloud-go/issues/10479)) ([c51ee6c](https://github.com/googleapis/google-cloud-go/commit/c51ee6cf65ce05b4d501083e49d468c75ac1ea63))
191+* **auth/oauth2adapt:** Bump google.golang.org/api@v0.187.0 ([8fa9e39](https://github.com/googleapis/google-cloud-go/commit/8fa9e398e512fd8533fd49060371e61b5725a85b))
192+* **auth:** Bump google.golang.org/api@v0.187.0 ([8fa9e39](https://github.com/googleapis/google-cloud-go/commit/8fa9e398e512fd8533fd49060371e61b5725a85b))
193+* **auth:** Check len of slices, not non-nil ([#10483](https://github.com/googleapis/google-cloud-go/issues/10483)) ([0a966a1](https://github.com/googleapis/google-cloud-go/commit/0a966a183e5f0e811977216d736d875b7233e942))
195+## [0.6.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.6.0...auth/v0.6.1) (2024-07-01)
200+* **auth:** Support gRPC API keys ([#10460](https://github.com/googleapis/google-cloud-go/issues/10460)) ([daa6646](https://github.com/googleapis/google-cloud-go/commit/daa6646d2af5d7fb5b30489f4934c7db89868c7c))
201+* **auth:** Update http and grpc transports to support token exchange over mTLS ([#10397](https://github.com/googleapis/google-cloud-go/issues/10397)) ([c6dfdcf](https://github.com/googleapis/google-cloud-go/commit/c6dfdcf893c3f971eba15026c12db0a960ae81f2))
203+## [0.6.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.5.2...auth/v0.6.0) (2024-06-25)
208+* **auth:** Add non-blocking token refresh for compute MDS ([#10263](https://github.com/googleapis/google-cloud-go/issues/10263)) ([9ac350d](https://github.com/googleapis/google-cloud-go/commit/9ac350da11a49b8e2174d3fc5b1a5070fec78b4e))
213+* **auth:** Return error if envvar detected file returns an error ([#10431](https://github.com/googleapis/google-cloud-go/issues/10431)) ([e52b9a7](https://github.com/googleapis/google-cloud-go/commit/e52b9a7c45468827f5d220ab00965191faeb9d05))
215+## [0.5.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.5.1...auth/v0.5.2) (2024-06-24)
220+* **auth:** Fetch initial token when CachedTokenProviderOptions.DisableAutoRefresh is true ([#10415](https://github.com/googleapis/google-cloud-go/issues/10415)) ([3266763](https://github.com/googleapis/google-cloud-go/commit/32667635ca2efad05cd8c087c004ca07d7406913)), refs [#10414](https://github.com/googleapis/google-cloud-go/issues/10414)
222+## [0.5.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.5.0...auth/v0.5.1) (2024-05-31)
227+* **auth:** Pass through client to 2LO and 3LO flows ([#10290](https://github.com/googleapis/google-cloud-go/issues/10290)) ([685784e](https://github.com/googleapis/google-cloud-go/commit/685784ea84358c15e9214bdecb307d37aa3b6d2f))
229+## [0.5.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.4.2...auth/v0.5.0) (2024-05-28)
234+* **auth:** Adds X509 workload certificate provider ([#10233](https://github.com/googleapis/google-cloud-go/issues/10233)) ([17a9db7](https://github.com/googleapis/google-cloud-go/commit/17a9db73af35e3d1a7a25ac4fd1377a103de6150))
236+## [0.4.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.4.1...auth/v0.4.2) (2024-05-16)
241+* **auth:** Enable client certificates by default only for GDU ([#10151](https://github.com/googleapis/google-cloud-go/issues/10151)) ([7c52978](https://github.com/googleapis/google-cloud-go/commit/7c529786275a39b7e00525f7d5e7be0d963e9e15))
242+* **auth:** Handle non-Transport DefaultTransport ([#10162](https://github.com/googleapis/google-cloud-go/issues/10162)) ([fa3bfdb](https://github.com/googleapis/google-cloud-go/commit/fa3bfdb23aaa45b34394a8b61e753b3587506782)), refs [#10159](https://github.com/googleapis/google-cloud-go/issues/10159)
243+* **auth:** Have refresh time match docs ([#10147](https://github.com/googleapis/google-cloud-go/issues/10147)) ([bcb5568](https://github.com/googleapis/google-cloud-go/commit/bcb5568c07a54dd3d2e869d15f502b0741a609e8))
244+* **auth:** Update compute token fetching error with named prefix ([#10180](https://github.com/googleapis/google-cloud-go/issues/10180)) ([4573504](https://github.com/googleapis/google-cloud-go/commit/4573504828d2928bebedc875d87650ba227829ea))
246+## [0.4.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.4.0...auth/v0.4.1) (2024-05-09)
251+* **auth:** Don't try to detect default creds it opt configured ([#10143](https://github.com/googleapis/google-cloud-go/issues/10143)) ([804632e](https://github.com/googleapis/google-cloud-go/commit/804632e7c5b0b85ff522f7951114485e256eb5bc))
253+## [0.4.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.3.0...auth/v0.4.0) (2024-05-07)
258+* **auth:** Enable client certificates by default ([#10102](https://github.com/googleapis/google-cloud-go/issues/10102)) ([9013e52](https://github.com/googleapis/google-cloud-go/commit/9013e5200a6ec0f178ed91acb255481ffb073a2c))
263+* **auth:** Get s2a logic up to date ([#10093](https://github.com/googleapis/google-cloud-go/issues/10093)) ([4fe9ae4](https://github.com/googleapis/google-cloud-go/commit/4fe9ae4b7101af2a5221d6d6b2e77b479305bb06))
265+## [0.3.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.2.2...auth/v0.3.0) (2024-04-23)
270+* **auth/httptransport:** Add ability to customize transport ([#10023](https://github.com/googleapis/google-cloud-go/issues/10023)) ([72c7f6b](https://github.com/googleapis/google-cloud-go/commit/72c7f6bbec3136cc7a62788fc7186bc33ef6c3b3)), refs [#9812](https://github.com/googleapis/google-cloud-go/issues/9812) [#9814](https://github.com/googleapis/google-cloud-go/issues/9814)
275+* **auth/credentials:** Error on bad file name if explicitly set ([#10018](https://github.com/googleapis/google-cloud-go/issues/10018)) ([55beaa9](https://github.com/googleapis/google-cloud-go/commit/55beaa993aaf052d8be39766afc6777c3c2a0bdd)), refs [#9809](https://github.com/googleapis/google-cloud-go/issues/9809)
277+## [0.2.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.2.1...auth/v0.2.2) (2024-04-19)
282+* **auth:** Add internal opt to skip validation on transports ([#9999](https://github.com/googleapis/google-cloud-go/issues/9999)) ([9e20ef8](https://github.com/googleapis/google-cloud-go/commit/9e20ef89f6287d6bd03b8697d5898dc43b4a77cf)), refs [#9823](https://github.com/googleapis/google-cloud-go/issues/9823)
283+* **auth:** Set secure flag for gRPC conn pools ([#10002](https://github.com/googleapis/google-cloud-go/issues/10002)) ([14e3956](https://github.com/googleapis/google-cloud-go/commit/14e3956dfd736399731b5ee8d9b178ae085cf7ba)), refs [#9833](https://github.com/googleapis/google-cloud-go/issues/9833)
285+## [0.2.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.2.0...auth/v0.2.1) (2024-04-18)
290+* **auth:** Default gRPC token type to Bearer if not set ([#9800](https://github.com/googleapis/google-cloud-go/issues/9800)) ([5284066](https://github.com/googleapis/google-cloud-go/commit/5284066670b6fe65d79089cfe0199c9660f87fc7))
292+## [0.2.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.1.1...auth/v0.2.0) (2024-04-15)
296+In the below mentioned commits there were a few large breaking changes since the
297+last release of the module.
299+1. The `Credentials` type has been moved to the root of the module as it is
300+ becoming the core abstraction for the whole module.
301+2. Because of the above mentioned change many functions that previously
302+ returned a `TokenProvider` now return `Credentials`. Similarly, these
303+ functions have been renamed to be more specific.
304+3. Most places that used to take an optional `TokenProvider` now accept
305+ `Credentials`. You can make a `Credentials` from a `TokenProvider` using the
306+ constructor found in the `auth` package.
307+4. The `detect` package has been renamed to `credentials`. With this change some
308+ function signatures were also updated for better readability.
309+5. Derivative auth flows like `impersonate` and `downscope` have been moved to
310+ be under the new `credentials` package.
312+Although these changes are disruptive we think that they are for the best of the
313+long-term health of the module. We do not expect any more large breaking changes
314+like these in future revisions, even before 1.0.0. This version will be the
315+first version of the auth library that our client libraries start to use and
320+* **auth/credentials/externalaccount:** Add default TokenURL ([#9700](https://github.com/googleapis/google-cloud-go/issues/9700)) ([81830e6](https://github.com/googleapis/google-cloud-go/commit/81830e6848ceefd055aa4d08f933d1154455a0f6))
321+* **auth:** Add downscope.Options.UniverseDomain ([#9634](https://github.com/googleapis/google-cloud-go/issues/9634)) ([52cf7d7](https://github.com/googleapis/google-cloud-go/commit/52cf7d780853594291c4e34302d618299d1f5a1d))
322+* **auth:** Add universe domain to grpctransport and httptransport ([#9663](https://github.com/googleapis/google-cloud-go/issues/9663)) ([67d353b](https://github.com/googleapis/google-cloud-go/commit/67d353beefe3b607c08c891876fbd95ab89e5fe3)), refs [#9670](https://github.com/googleapis/google-cloud-go/issues/9670)
323+* **auth:** Add UniverseDomain to DetectOptions ([#9536](https://github.com/googleapis/google-cloud-go/issues/9536)) ([3618d3f](https://github.com/googleapis/google-cloud-go/commit/3618d3f7061615c0e189f376c75abc201203b501))
324+* **auth:** Make package externalaccount public ([#9633](https://github.com/googleapis/google-cloud-go/issues/9633)) ([a0978d8](https://github.com/googleapis/google-cloud-go/commit/a0978d8e96968399940ebd7d092539772bf9caac))
325+* **auth:** Move credentials to base auth package ([#9590](https://github.com/googleapis/google-cloud-go/issues/9590)) ([1a04baf](https://github.com/googleapis/google-cloud-go/commit/1a04bafa83c27342b9308d785645e1e5423ea10d))
326+* **auth:** Refactor public sigs to use Credentials ([#9603](https://github.com/googleapis/google-cloud-go/issues/9603)) ([69cb240](https://github.com/googleapis/google-cloud-go/commit/69cb240c530b1f7173a9af2555c19e9a1beb56c5))
331+* **auth/oauth2adapt:** Update protobuf dep to v1.33.0 ([30b038d](https://github.com/googleapis/google-cloud-go/commit/30b038d8cac0b8cd5dd4761c87f3f298760dd33a))
332+* **auth:** Fix uint32 conversion ([9221c7f](https://github.com/googleapis/google-cloud-go/commit/9221c7fa12cef9d5fb7ddc92f41f1d6204971c7b))
333+* **auth:** Port sts expires fix ([#9618](https://github.com/googleapis/google-cloud-go/issues/9618)) ([7bec97b](https://github.com/googleapis/google-cloud-go/commit/7bec97b2f51ed3ac4f9b88bf100d301da3f5d1bd))
334+* **auth:** Read universe_domain from all credentials files ([#9632](https://github.com/googleapis/google-cloud-go/issues/9632)) ([16efbb5](https://github.com/googleapis/google-cloud-go/commit/16efbb52e39ea4a319e5ee1e95c0e0305b6d9824))
335+* **auth:** Remove content-type header from idms get requests ([#9508](https://github.com/googleapis/google-cloud-go/issues/9508)) ([8589f41](https://github.com/googleapis/google-cloud-go/commit/8589f41599d265d7c3d46a3d86c9fab2329cbdd9))
336+* **auth:** Update protobuf dep to v1.33.0 ([30b038d](https://github.com/googleapis/google-cloud-go/commit/30b038d8cac0b8cd5dd4761c87f3f298760dd33a))
338+## [0.1.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.1.0...auth/v0.1.1) (2024-03-10)
343+* **auth/impersonate:** Properly send default detect params ([#9529](https://github.com/googleapis/google-cloud-go/issues/9529)) ([5b6b8be](https://github.com/googleapis/google-cloud-go/commit/5b6b8bef577f82707e51f5cc5d258d5bdf90218f)), refs [#9136](https://github.com/googleapis/google-cloud-go/issues/9136)
344+* **auth:** Update grpc-go to v1.56.3 ([343cea8](https://github.com/googleapis/google-cloud-go/commit/343cea8c43b1e31ae21ad50ad31d3b0b60143f8c))
345+* **auth:** Update grpc-go to v1.59.0 ([81a97b0](https://github.com/googleapis/google-cloud-go/commit/81a97b06cb28b25432e4ece595c55a9857e960b7))
347+## 0.1.0 (2023-10-18)
352+* **auth:** Add base auth package ([#8465](https://github.com/googleapis/google-cloud-go/issues/8465)) ([6a45f26](https://github.com/googleapis/google-cloud-go/commit/6a45f26b809b64edae21f312c18d4205f96b180e))
353+* **auth:** Add cert support to httptransport ([#8569](https://github.com/googleapis/google-cloud-go/issues/8569)) ([37e3435](https://github.com/googleapis/google-cloud-go/commit/37e3435f8e98595eafab481bdfcb31a4c56fa993))
354+* **auth:** Add Credentials.UniverseDomain() ([#8654](https://github.com/googleapis/google-cloud-go/issues/8654)) ([af0aa1e](https://github.com/googleapis/google-cloud-go/commit/af0aa1ed8015bc8fe0dd87a7549ae029107cbdb8))
355+* **auth:** Add detect package ([#8491](https://github.com/googleapis/google-cloud-go/issues/8491)) ([d977419](https://github.com/googleapis/google-cloud-go/commit/d977419a3269f6acc193df77a2136a6eb4b4add7))
356+* **auth:** Add downscope package ([#8532](https://github.com/googleapis/google-cloud-go/issues/8532)) ([dda9bff](https://github.com/googleapis/google-cloud-go/commit/dda9bff8ec70e6d104901b4105d13dcaa4e2404c))
357+* **auth:** Add grpctransport package ([#8625](https://github.com/googleapis/google-cloud-go/issues/8625)) ([69a8347](https://github.com/googleapis/google-cloud-go/commit/69a83470bdcc7ed10c6c36d1abc3b7cfdb8a0ee5))
358+* **auth:** Add httptransport package ([#8567](https://github.com/googleapis/google-cloud-go/issues/8567)) ([6898597](https://github.com/googleapis/google-cloud-go/commit/6898597d2ea95d630fcd00fd15c58c75ea843bff))
359+* **auth:** Add idtoken package ([#8580](https://github.com/googleapis/google-cloud-go/issues/8580)) ([a79e693](https://github.com/googleapis/google-cloud-go/commit/a79e693e97e4e3e1c6742099af3dbc58866d88fe))
360+* **auth:** Add impersonate package ([#8578](https://github.com/googleapis/google-cloud-go/issues/8578)) ([e29ba0c](https://github.com/googleapis/google-cloud-go/commit/e29ba0cb7bd3888ab9e808087027dc5a32474c04))
361+* **auth:** Add support for external accounts in detect ([#8508](https://github.com/googleapis/google-cloud-go/issues/8508)) ([62210d5](https://github.com/googleapis/google-cloud-go/commit/62210d5d3e56e8e9f35db8e6ac0defec19582507))
362+* **auth:** Port external account changes ([#8697](https://github.com/googleapis/google-cloud-go/issues/8697)) ([5823db5](https://github.com/googleapis/google-cloud-go/commit/5823db5d633069999b58b9131a7f9cd77e82c899))
367+* **auth/oauth2adapt:** Update golang.org/x/net to v0.17.0 ([174da47](https://github.com/googleapis/google-cloud-go/commit/174da47254fefb12921bbfc65b7829a453af6f5d))
368+* **auth:** Update golang.org/x/net to v0.17.0 ([174da47](https://github.com/googleapis/google-cloud-go/commit/174da47254fefb12921bbfc65b7829a453af6f5d))
vendor/cloud.google.com/go/auth/LICENSEnew file+202
@@ -0,0 +1,202 @@
3+ Version 2.0, January 2004
4+ http://www.apache.org/licenses/
6+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
10+ "License" shall mean the terms and conditions for use, reproduction,
11+ and distribution as defined by Sections 1 through 9 of this document.
13+ "Licensor" shall mean the copyright owner or entity authorized by
14+ the copyright owner that is granting the License.
16+ "Legal Entity" shall mean the union of the acting entity and all
17+ other entities that control, are controlled by, or are under common
18+ control with that entity. For the purposes of this definition,
19+ "control" means (i) the power, direct or indirect, to cause the
20+ direction or management of such entity, whether by contract or
21+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22+ outstanding shares, or (iii) beneficial ownership of such entity.
24+ "You" (or "Your") shall mean an individual or Legal Entity
25+ exercising permissions granted by this License.
27+ "Source" form shall mean the preferred form for making modifications,
28+ including but not limited to software source code, documentation
29+ source, and configuration files.
31+ "Object" form shall mean any form resulting from mechanical
32+ transformation or translation of a Source form, including but
33+ not limited to compiled object code, generated documentation,
34+ and conversions to other media types.
36+ "Work" shall mean the work of authorship, whether in Source or
37+ Object form, made available under the License, as indicated by a
38+ copyright notice that is included in or attached to the work
39+ (an example is provided in the Appendix below).
41+ "Derivative Works" shall mean any work, whether in Source or Object
42+ form, that is based on (or derived from) the Work and for which the
43+ editorial revisions, annotations, elaborations, or other modifications
44+ represent, as a whole, an original work of authorship. For the purposes
45+ of this License, Derivative Works shall not include works that remain
46+ separable from, or merely link (or bind by name) to the interfaces of,
47+ the Work and Derivative Works thereof.
49+ "Contribution" shall mean any work of authorship, including
50+ the original version of the Work and any modifications or additions
51+ to that Work or Derivative Works thereof, that is intentionally
52+ submitted to Licensor for inclusion in the Work by the copyright owner
53+ or by an individual or Legal Entity authorized to submit on behalf of
54+ the copyright owner. For the purposes of this definition, "submitted"
55+ means any form of electronic, verbal, or written communication sent
56+ to the Licensor or its representatives, including but not limited to
57+ communication on electronic mailing lists, source code control systems,
58+ and issue tracking systems that are managed by, or on behalf of, the
59+ Licensor for the purpose of discussing and improving the Work, but
60+ excluding communication that is conspicuously marked or otherwise
61+ designated in writing by the copyright owner as "Not a Contribution."
63+ "Contributor" shall mean Licensor and any individual or Legal Entity
64+ on behalf of whom a Contribution has been received by Licensor and
65+ subsequently incorporated within the Work.
67+ 2. Grant of Copyright License. Subject to the terms and conditions of
68+ this License, each Contributor hereby grants to You a perpetual,
69+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70+ copyright license to reproduce, prepare Derivative Works of,
71+ publicly display, publicly perform, sublicense, and distribute the
72+ Work and such Derivative Works in Source or Object form.
74+ 3. Grant of Patent License. Subject to the terms and conditions of
75+ this License, each Contributor hereby grants to You a perpetual,
76+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77+ (except as stated in this section) patent license to make, have made,
78+ use, offer to sell, sell, import, and otherwise transfer the Work,
79+ where such license applies only to those patent claims licensable
80+ by such Contributor that are necessarily infringed by their
81+ Contribution(s) alone or by combination of their Contribution(s)
82+ with the Work to which such Contribution(s) was submitted. If You
83+ institute patent litigation against any entity (including a
84+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85+ or a Contribution incorporated within the Work constitutes direct
86+ or contributory patent infringement, then any patent licenses
87+ granted to You under this License for that Work shall terminate
88+ as of the date such litigation is filed.
90+ 4. Redistribution. You may reproduce and distribute copies of the
91+ Work or Derivative Works thereof in any medium, with or without
92+ modifications, and in Source or Object form, provided that You
93+ meet the following conditions:
95+ (a) You must give any other recipients of the Work or
96+ Derivative Works a copy of this License; and
98+ (b) You must cause any modified files to carry prominent notices
99+ stating that You changed the files; and
101+ (c) You must retain, in the Source form of any Derivative Works
102+ that You distribute, all copyright, patent, trademark, and
103+ attribution notices from the Source form of the Work,
104+ excluding those notices that do not pertain to any part of
105+ the Derivative Works; and
107+ (d) If the Work includes a "NOTICE" text file as part of its
108+ distribution, then any Derivative Works that You distribute must
109+ include a readable copy of the attribution notices contained
110+ within such NOTICE file, excluding those notices that do not
111+ pertain to any part of the Derivative Works, in at least one
112+ of the following places: within a NOTICE text file distributed
113+ as part of the Derivative Works; within the Source form or
114+ documentation, if provided along with the Derivative Works; or,
115+ within a display generated by the Derivative Works, if and
116+ wherever such third-party notices normally appear. The contents
117+ of the NOTICE file are for informational purposes only and
118+ do not modify the License. You may add Your own attribution
119+ notices within Derivative Works that You distribute, alongside
120+ or as an addendum to the NOTICE text from the Work, provided
121+ that such additional attribution notices cannot be construed
122+ as modifying the License.
124+ You may add Your own copyright statement to Your modifications and
125+ may provide additional or different license terms and conditions
126+ for use, reproduction, or distribution of Your modifications, or
127+ for any such Derivative Works as a whole, provided Your use,
128+ reproduction, and distribution of the Work otherwise complies with
129+ the conditions stated in this License.
131+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132+ any Contribution intentionally submitted for inclusion in the Work
133+ by You to the Licensor shall be under the terms and conditions of
134+ this License, without any additional terms or conditions.
135+ Notwithstanding the above, nothing herein shall supersede or modify
136+ the terms of any separate license agreement you may have executed
137+ with Licensor regarding such Contributions.
139+ 6. Trademarks. This License does not grant permission to use the trade
140+ names, trademarks, service marks, or product names of the Licensor,
141+ except as required for reasonable and customary use in describing the
142+ origin of the Work and reproducing the content of the NOTICE file.
144+ 7. Disclaimer of Warranty. Unless required by applicable law or
145+ agreed to in writing, Licensor provides the Work (and each
146+ Contributor provides its Contributions) on an "AS IS" BASIS,
147+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148+ implied, including, without limitation, any warranties or conditions
149+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150+ PARTICULAR PURPOSE. You are solely responsible for determining the
151+ appropriateness of using or redistributing the Work and assume any
152+ risks associated with Your exercise of permissions under this License.
154+ 8. Limitation of Liability. In no event and under no legal theory,
155+ whether in tort (including negligence), contract, or otherwise,
156+ unless required by applicable law (such as deliberate and grossly
157+ negligent acts) or agreed to in writing, shall any Contributor be
158+ liable to You for damages, including any direct, indirect, special,
159+ incidental, or consequential damages of any character arising as a
160+ result of this License or out of the use or inability to use the
161+ Work (including but not limited to damages for loss of goodwill,
162+ work stoppage, computer failure or malfunction, or any and all
163+ other commercial damages or losses), even if such Contributor
164+ has been advised of the possibility of such damages.
166+ 9. Accepting Warranty or Additional Liability. While redistributing
167+ the Work or Derivative Works thereof, You may choose to offer,
168+ and charge a fee for, acceptance of support, warranty, indemnity,
169+ or other liability obligations and/or rights consistent with this
170+ License. However, in accepting such obligations, You may act only
171+ on Your own behalf and on Your sole responsibility, not on behalf
172+ of any other Contributor, and only if You agree to indemnify,
173+ defend, and hold each Contributor harmless for any liability
174+ incurred by, or claims asserted against, such Contributor by reason
175+ of your accepting any such warranty or additional liability.
177+ END OF TERMS AND CONDITIONS
179+ APPENDIX: How to apply the Apache License to your work.
181+ To apply the Apache License to your work, attach the following
182+ boilerplate notice, with the fields enclosed by brackets "[]"
183+ replaced with your own identifying information. (Don't include
184+ the brackets!) The text should be enclosed in the appropriate
185+ comment syntax for the file format. We also recommend that a
186+ file or class name and description of purpose be included on the
187+ same "printed page" as the copyright notice for easier
188+ identification within third-party archives.
190+ Copyright [yyyy] [name of copyright owner]
192+ Licensed under the Apache License, Version 2.0 (the "License");
193+ you may not use this file except in compliance with the License.
194+ You may obtain a copy of the License at
196+ http://www.apache.org/licenses/LICENSE-2.0
198+ Unless required by applicable law or agreed to in writing, software
199+ distributed under the License is distributed on an "AS IS" BASIS,
200+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201+ See the License for the specific language governing permissions and
202+ limitations under the License.
vendor/cloud.google.com/go/auth/README.mdnew file+40
@@ -0,0 +1,40 @@
1+# Google Auth Library for Go
3+[](https://pkg.go.dev/cloud.google.com/go/auth)
8+go get cloud.google.com/go/auth@latest
13+The most common way this library is used is transitively, by default, from any
14+of our Go client libraries.
18+- To create a credential directly please see examples in the
19+ [credentials](https://pkg.go.dev/cloud.google.com/go/auth/credentials)
21+- To create a authenticated HTTP client please see examples in the
22+ [httptransport](https://pkg.go.dev/cloud.google.com/go/auth/httptransport)
24+- To create a authenticated gRPC connection please see examples in the
25+ [grpctransport](https://pkg.go.dev/cloud.google.com/go/auth/grpctransport)
27+- To create an ID token please see examples in the
28+ [idtoken](https://pkg.go.dev/cloud.google.com/go/auth/credentials/idtoken)
33+Contributions are welcome. Please, see the
34+[CONTRIBUTING](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/main/CONTRIBUTING.md)
37+Please note that this project is released with a Contributor Code of Conduct.
38+By participating in this project you agree to abide by its terms.
39+See [Contributor Code of Conduct](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/main/CONTRIBUTING.md#contributor-code-of-conduct)
vendor/cloud.google.com/go/auth/auth.gonew file+618
@@ -0,0 +1,618 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
15+// Package auth provides utilities for managing Google Cloud credentials,
16+// including functionality for creating, caching, and refreshing OAuth2 tokens.
17+// It offers customizable options for different OAuth2 flows, such as 2-legged
18+// (2LO) and 3-legged (3LO) OAuth, along with support for PKCE and automatic
34+ "cloud.google.com/go/auth/internal"
35+ "cloud.google.com/go/auth/internal/jwt"
36+ "github.com/googleapis/gax-go/v2/internallog"
40+ // Parameter keys for AuthCodeURL method to support PKCE.
41+ codeChallengeKey = "code_challenge"
42+ codeChallengeMethodKey = "code_challenge_method"
44+ // Parameter key for Exchange method to support PKCE.
45+ codeVerifierKey = "code_verifier"
47+ // 3 minutes and 45 seconds before expiration. The shortest MDS cache is 4 minutes,
48+ // so we give it 15 seconds to refresh it's cache before attempting to refresh a token.
49+ defaultExpiryDelta = 225 * time.Second
51+ universeDomainDefault = "googleapis.com"
54+// tokenState represents different states for a [Token].
58+ // fresh indicates that the [Token] is valid. It is not expired or close to
59+ // expired, or the token has no expiry.
60+ fresh tokenState = iota
61+ // stale indicates that the [Token] is close to expired, and should be
62+ // refreshed. The token can be used normally.
64+ // invalid indicates that the [Token] is expired or invalid. The token
65+ // cannot be used for a normal operation.
70+ defaultGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
71+ defaultHeader = &jwt.Header{Algorithm: jwt.HeaderAlgRSA256, Type: jwt.HeaderType}
77+// TokenProvider specifies an interface for anything that can return a token.
78+type TokenProvider interface {
79+ // Token returns a Token or an error.
80+ // The Token returned must be safe to use
82+ // The returned Token must not be modified.
83+ // The context provided must be sent along to any requests that are made in
84+ // the implementing code.
85+ Token(context.Context) (*Token, error)
88+// Token holds the credential token used to authorized requests. All fields are
89+// considered read-only.
91+ // Value is the token used to authorize requests. It is usually an access
92+ // token but may be other types of tokens such as ID tokens in some flows.
94+ // Type is the type of token Value is. If uninitialized, it should be
95+ // assumed to be a "Bearer" token.
97+ // Expiry is the time the token is set to expire.
99+ // Metadata may include, but is not limited to, the body of the token
100+ // response returned by the server.
101+ Metadata map[string]interface{} // TODO(codyoss): maybe make a method to flatten metadata to avoid []string for url.Values
104+// IsValid reports that a [Token] is non-nil, has a [Token.Value], and has not
105+// expired. A token is considered expired if [Token.Expiry] has passed or will
106+// pass in the next 225 seconds.
107+func (t *Token) IsValid() bool {
108+ return t.isValidWithEarlyExpiry(defaultExpiryDelta)
111+// MetadataString is a convenience method for accessing string values in the
112+// token's metadata. Returns an empty string if the metadata is nil or the value
113+// for the given key cannot be cast to a string.
114+func (t *Token) MetadataString(k string) string {
115+ if t.Metadata == nil {
118+ s, ok := t.Metadata[k].(string)
125+func (t *Token) isValidWithEarlyExpiry(earlyExpiry time.Duration) bool {
129+ if t.Expiry.IsZero() {
132+ return !t.Expiry.Round(0).Add(-earlyExpiry).Before(timeNow())
135+func (t *Token) isEmpty() bool {
136+ return t == nil || t.Value == ""
139+// Credentials holds Google credentials, including
140+// [Application Default Credentials].
142+// [Application Default Credentials]: https://developers.google.com/accounts/docs/application-default-credentials
143+type Credentials struct {
145+ projectID CredentialsPropertyProvider
146+ quotaProjectID CredentialsPropertyProvider
147+ // universeDomain is the default service domain for a given Cloud universe.
148+ universeDomain CredentialsPropertyProvider
153+// JSON returns the bytes associated with the the file used to source
154+// credentials if one was used.
155+func (c *Credentials) JSON() []byte {
159+// ProjectID returns the associated project ID from the underlying file or
161+func (c *Credentials) ProjectID(ctx context.Context) (string, error) {
162+ if c.projectID == nil {
163+ return internal.GetProjectID(c.json, ""), nil
165+ v, err := c.projectID.GetProperty(ctx)
169+ return internal.GetProjectID(c.json, v), nil
172+// QuotaProjectID returns the associated quota project ID from the underlying
173+// file or environment.
174+func (c *Credentials) QuotaProjectID(ctx context.Context) (string, error) {
175+ if c.quotaProjectID == nil {
176+ return internal.GetQuotaProject(c.json, ""), nil
178+ v, err := c.quotaProjectID.GetProperty(ctx)
182+ return internal.GetQuotaProject(c.json, v), nil
185+// UniverseDomain returns the default service domain for a given Cloud universe.
186+// The default value is "googleapis.com".
187+func (c *Credentials) UniverseDomain(ctx context.Context) (string, error) {
188+ if c.universeDomain == nil {
189+ return universeDomainDefault, nil
191+ v, err := c.universeDomain.GetProperty(ctx)
196+ return universeDomainDefault, nil
201+// CredentialsPropertyProvider provides an implementation to fetch a property
202+// value for [Credentials].
203+type CredentialsPropertyProvider interface {
204+ GetProperty(context.Context) (string, error)
207+// CredentialsPropertyFunc is a type adapter to allow the use of ordinary
208+// functions as a [CredentialsPropertyProvider].
209+type CredentialsPropertyFunc func(context.Context) (string, error)
211+// GetProperty loads the properly value provided the given context.
212+func (p CredentialsPropertyFunc) GetProperty(ctx context.Context) (string, error) {
216+// CredentialsOptions are used to configure [Credentials].
217+type CredentialsOptions struct {
218+ // TokenProvider is a means of sourcing a token for the credentials. Required.
219+ TokenProvider TokenProvider
220+ // JSON is the raw contents of the credentials file if sourced from a file.
222+ // ProjectIDProvider resolves the project ID associated with the
224+ ProjectIDProvider CredentialsPropertyProvider
225+ // QuotaProjectIDProvider resolves the quota project ID associated with the
227+ QuotaProjectIDProvider CredentialsPropertyProvider
228+ // UniverseDomainProvider resolves the universe domain with the credentials.
229+ UniverseDomainProvider CredentialsPropertyProvider
232+// NewCredentials returns new [Credentials] from the provided options.
233+func NewCredentials(opts *CredentialsOptions) *Credentials {
234+ creds := &Credentials{
235+ TokenProvider: opts.TokenProvider,
237+ projectID: opts.ProjectIDProvider,
238+ quotaProjectID: opts.QuotaProjectIDProvider,
239+ universeDomain: opts.UniverseDomainProvider,
245+// CachedTokenProviderOptions provides options for configuring a cached
247+type CachedTokenProviderOptions struct {
248+ // DisableAutoRefresh makes the TokenProvider always return the same token,
249+ // even if it is expired. The default is false. Optional.
250+ DisableAutoRefresh bool
251+ // ExpireEarly configures the amount of time before a token expires, that it
252+ // should be refreshed. If unset, the default value is 3 minutes and 45
253+ // seconds. Optional.
254+ ExpireEarly time.Duration
255+ // DisableAsyncRefresh configures a synchronous workflow that refreshes
256+ // tokens in a blocking manner. The default is false. Optional.
257+ DisableAsyncRefresh bool
260+func (ctpo *CachedTokenProviderOptions) autoRefresh() bool {
264+ return !ctpo.DisableAutoRefresh
267+func (ctpo *CachedTokenProviderOptions) expireEarly() time.Duration {
268+ if ctpo == nil || ctpo.ExpireEarly == 0 {
269+ return defaultExpiryDelta
271+ return ctpo.ExpireEarly
274+func (ctpo *CachedTokenProviderOptions) blockingRefresh() bool {
278+ return ctpo.DisableAsyncRefresh
281+// NewCachedTokenProvider wraps a [TokenProvider] to cache the tokens returned
282+// by the underlying provider. By default it will refresh tokens asynchronously
283+// a few minutes before they expire.
284+func NewCachedTokenProvider(tp TokenProvider, opts *CachedTokenProviderOptions) TokenProvider {
285+ if ctp, ok := tp.(*cachedTokenProvider); ok {
288+ return &cachedTokenProvider{
290+ autoRefresh: opts.autoRefresh(),
291+ expireEarly: opts.expireEarly(),
292+ blockingRefresh: opts.blockingRefresh(),
296+type cachedTokenProvider struct {
299+ expireEarly time.Duration
300+ blockingRefresh bool
304+ // isRefreshRunning ensures that the non-blocking refresh will only be
305+ // attempted once, even if multiple callers enter the Token method.
306+ isRefreshRunning bool
307+ // isRefreshErr ensures that the non-blocking refresh will only be attempted
308+ // once per refresh window if an error is encountered.
312+func (c *cachedTokenProvider) Token(ctx context.Context) (*Token, error) {
313+ if c.blockingRefresh {
314+ return c.tokenBlocking(ctx)
316+ return c.tokenNonBlocking(ctx)
319+func (c *cachedTokenProvider) tokenNonBlocking(ctx context.Context) (*Token, error) {
320+ switch c.tokenState() {
324+ return c.cachedToken, nil
326+ // Call tokenAsync with a new Context because the user-provided context
327+ // may have a short timeout incompatible with async token refresh.
328+ c.tokenAsync(context.Background())
329+ // Return the stale token immediately to not block customer requests to Cloud services.
332+ return c.cachedToken, nil
334+ return c.tokenBlocking(ctx)
338+// tokenState reports the token's validity.
339+func (c *cachedTokenProvider) tokenState() tokenState {
344+ if t == nil || t.Value == "" {
346+ } else if t.Expiry.IsZero() {
348+ } else if now.After(t.Expiry.Round(0)) {
350+ } else if now.After(t.Expiry.Round(0).Add(-c.expireEarly)) {
356+// tokenAsync uses a bool to ensure that only one non-blocking token refresh
357+// happens at a time, even if multiple callers have entered this function
358+// concurrently. This avoids creating an arbitrary number of concurrent
359+// goroutines. Retries should be attempted and managed within the Token method.
360+// If the refresh attempt fails, no further attempts are made until the refresh
361+// window expires and the token enters the invalid state, at which point the
362+// blocking call to Token should likely return the same error on the main goroutine.
363+func (c *cachedTokenProvider) tokenAsync(ctx context.Context) {
366+ c.isRefreshRunning = true
368+ t, err := c.tp.Token(ctx)
371+ c.isRefreshRunning = false
373+ // Discard errors from the non-blocking refresh, but prevent further
375+ c.isRefreshErr = true
382+ if !c.isRefreshRunning && !c.isRefreshErr {
387+func (c *cachedTokenProvider) tokenBlocking(ctx context.Context) (*Token, error) {
390+ c.isRefreshErr = false
391+ if c.cachedToken.IsValid() || (!c.autoRefresh && !c.cachedToken.isEmpty()) {
392+ return c.cachedToken, nil
394+ t, err := c.tp.Token(ctx)
402+// Error is a error associated with retrieving a [Token]. It can hold useful
403+// additional details for debugging.
405+ // Response is the HTTP response associated with error. The body will always
406+ // be already closed and consumed.
407+ Response *http.Response
408+ // Body is the HTTP response body.
410+ // Err is the underlying wrapped error.
413+ // code returned in the token response
415+ // description returned in the token response
417+ // uri returned in the token response
421+func (e *Error) Error() string {
423+ s := fmt.Sprintf("auth: %q", e.code)
424+ if e.description != "" {
425+ s += fmt.Sprintf(" %q", e.description)
428+ s += fmt.Sprintf(" %q", e.uri)
432+ return fmt.Sprintf("auth: cannot fetch token: %v\nResponse: %s", e.Response.StatusCode, e.Body)
435+// Temporary returns true if the error is considered temporary and may be able
437+func (e *Error) Temporary() bool {
438+ if e.Response == nil {
441+ sc := e.Response.StatusCode
442+ return sc == http.StatusInternalServerError || sc == http.StatusServiceUnavailable || sc == http.StatusRequestTimeout || sc == http.StatusTooManyRequests
445+func (e *Error) Unwrap() error {
449+// Style describes how the token endpoint wants to receive the ClientID and
454+ // StyleUnknown means the value has not been initiated. Sending this in
455+ // a request will cause the token exchange to fail.
456+ StyleUnknown Style = iota
457+ // StyleInParams sends client info in the body of a POST request.
459+ // StyleInHeader sends client info using Basic Authorization header.
463+// Options2LO is the configuration settings for doing a 2-legged JWT OAuth2 flow.
464+type Options2LO struct {
465+ // Email is the OAuth2 client ID. This value is set as the "iss" in the
468+ // PrivateKey contains the contents of an RSA private key or the
469+ // contents of a PEM file that contains a private key. It is used to sign
472+ // TokenURL is th URL the JWT is sent to. Required.
474+ // PrivateKeyID is the ID of the key used to sign the JWT. It is used as the
475+ // "kid" in the JWT header. Optional.
477+ // Subject is the used for to impersonate a user. It is used as the "sub" in
478+ // the JWT.m Optional.
480+ // Scopes specifies requested permissions for the token. Optional.
482+ // Expires specifies the lifetime of the token. Optional.
483+ Expires time.Duration
484+ // Audience specifies the "aud" in the JWT. Optional.
486+ // PrivateClaims allows specifying any custom claims for the JWT. Optional.
487+ PrivateClaims map[string]interface{}
489+ // Client is the client to be used to make the underlying token requests.
492+ // UseIDToken requests that the token returned be an ID token if one is
493+ // returned from the server. Optional.
495+ // Logger is used for debug logging. If provided, logging will be enabled
496+ // at the loggers configured level. By default logging is disabled unless
497+ // enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
498+ // logger will be used. Optional.
502+func (o *Options2LO) client() *http.Client {
503+ if o.Client != nil {
506+ return internal.DefaultClient()
509+func (o *Options2LO) validate() error {
511+ return errors.New("auth: options must be provided")
514+ return errors.New("auth: email must be provided")
516+ if len(o.PrivateKey) == 0 {
517+ return errors.New("auth: private key must be provided")
519+ if o.TokenURL == "" {
520+ return errors.New("auth: token URL must be provided")
525+// New2LOTokenProvider returns a [TokenProvider] from the provided options.
526+func New2LOTokenProvider(opts *Options2LO) (TokenProvider, error) {
527+ if err := opts.validate(); err != nil {
530+ return tokenProvider2LO{opts: opts, Client: opts.client(), logger: internallog.New(opts.Logger)}, nil
533+type tokenProvider2LO struct {
539+func (tp tokenProvider2LO) Token(ctx context.Context) (*Token, error) {
540+ pk, err := internal.ParseKey(tp.opts.PrivateKey)
544+ claimSet := &jwt.Claims{
546+ Scope: strings.Join(tp.opts.Scopes, " "),
547+ Aud: tp.opts.TokenURL,
548+ AdditionalClaims: tp.opts.PrivateClaims,
549+ Sub: tp.opts.Subject,
551+ if t := tp.opts.Expires; t > 0 {
552+ claimSet.Exp = time.Now().Add(t).Unix()
554+ if aud := tp.opts.Audience; aud != "" {
558+ h.KeyID = tp.opts.PrivateKeyID
559+ payload, err := jwt.EncodeJWS(&h, claimSet, pk)
564+ v.Set("grant_type", defaultGrantType)
565+ v.Set("assertion", payload)
566+ req, err := http.NewRequestWithContext(ctx, "POST", tp.opts.TokenURL, strings.NewReader(v.Encode()))
570+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
571+ tp.logger.DebugContext(ctx, "2LO token request", "request", internallog.HTTPRequest(req, []byte(v.Encode())))
572+ resp, body, err := internal.DoRequest(tp.Client, req)
574+ return nil, fmt.Errorf("auth: cannot fetch token: %w", err)
576+ tp.logger.DebugContext(ctx, "2LO token response", "response", internallog.HTTPResponse(resp, body))
577+ if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
583+ // tokenRes is the JSON response body.
584+ var tokenRes struct {
585+ AccessToken string `json:"access_token"`
586+ TokenType string `json:"token_type"`
587+ IDToken string `json:"id_token"`
588+ ExpiresIn int64 `json:"expires_in"`
590+ if err := json.Unmarshal(body, &tokenRes); err != nil {
591+ return nil, fmt.Errorf("auth: cannot fetch token: %w", err)
594+ Value: tokenRes.AccessToken,
595+ Type: tokenRes.TokenType,
597+ token.Metadata = make(map[string]interface{})
598+ json.Unmarshal(body, &token.Metadata) // no error checks for optional fields
600+ if secs := tokenRes.ExpiresIn; secs > 0 {
601+ token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
603+ if v := tokenRes.IDToken; v != "" {
604+ // decode returned id token to get expiry
605+ claimSet, err := jwt.DecodeJWS(v)
607+ return nil, fmt.Errorf("auth: error decoding JWT token: %w", err)
609+ token.Expiry = time.Unix(claimSet.Exp, 0)
611+ if tp.opts.UseIDToken {
612+ if tokenRes.IDToken == "" {
613+ return nil, fmt.Errorf("auth: response doesn't have JWT token")
615+ token.Value = tokenRes.IDToken
vendor/cloud.google.com/go/auth/credentials/compute.gonew file+90
@@ -0,0 +1,90 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
26+ "cloud.google.com/go/auth"
27+ "cloud.google.com/go/compute/metadata"
31+ computeTokenMetadata = map[string]interface{}{
32+ "auth.google.tokenSource": "compute-metadata",
33+ "auth.google.serviceAccount": "default",
35+ computeTokenURI = "instance/service-accounts/default/token"
38+// computeTokenProvider creates a [cloud.google.com/go/auth.TokenProvider] that
39+// uses the metadata service to retrieve tokens.
40+func computeTokenProvider(opts *DetectOptions, client *metadata.Client) auth.TokenProvider {
41+ return auth.NewCachedTokenProvider(&computeProvider{
44+ }, &auth.CachedTokenProviderOptions{
45+ ExpireEarly: opts.EarlyTokenRefresh,
46+ DisableAsyncRefresh: opts.DisableAsyncRefresh,
50+// computeProvider fetches tokens from the google cloud metadata service.
51+type computeProvider struct {
53+ client *metadata.Client
56+type metadataTokenResp struct {
57+ AccessToken string `json:"access_token"`
58+ ExpiresInSec int `json:"expires_in"`
59+ TokenType string `json:"token_type"`
62+func (cs *computeProvider) Token(ctx context.Context) (*auth.Token, error) {
63+ tokenURI, err := url.Parse(computeTokenURI)
67+ if len(cs.scopes) > 0 {
69+ v.Set("scopes", strings.Join(cs.scopes, ","))
70+ tokenURI.RawQuery = v.Encode()
72+ tokenJSON, err := cs.client.GetWithContext(ctx, tokenURI.String())
74+ return nil, fmt.Errorf("credentials: cannot fetch token: %w", err)
76+ var res metadataTokenResp
77+ if err := json.NewDecoder(strings.NewReader(tokenJSON)).Decode(&res); err != nil {
78+ return nil, fmt.Errorf("credentials: invalid token JSON from metadata: %w", err)
80+ if res.ExpiresInSec == 0 || res.AccessToken == "" {
81+ return nil, errors.New("credentials: incomplete token received from metadata")
84+ Value: res.AccessToken,
86+ Expiry: time.Now().Add(time.Duration(res.ExpiresInSec) * time.Second),
87+ Metadata: computeTokenMetadata,
vendor/cloud.google.com/go/auth/credentials/detect.gonew file+279
@@ -0,0 +1,279 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
27+ "cloud.google.com/go/auth"
28+ "cloud.google.com/go/auth/internal"
29+ "cloud.google.com/go/auth/internal/credsfile"
30+ "cloud.google.com/go/compute/metadata"
31+ "github.com/googleapis/gax-go/v2/internallog"
35+ // jwtTokenURL is Google's OAuth 2.0 token URL to use with the JWT(2LO) flow.
36+ jwtTokenURL = "https://oauth2.googleapis.com/token"
38+ // Google's OAuth 2.0 default endpoints.
39+ googleAuthURL = "https://accounts.google.com/o/oauth2/auth"
40+ googleTokenURL = "https://oauth2.googleapis.com/token"
42+ // GoogleMTLSTokenURL is Google's default OAuth2.0 mTLS endpoint.
43+ GoogleMTLSTokenURL = "https://oauth2.mtls.googleapis.com/token"
45+ // Help on default credentials
46+ adcSetupURL = "https://cloud.google.com/docs/authentication/external/set-up-adc"
51+ allowOnGCECheck = true
54+// OnGCE reports whether this process is running in Google Cloud.
56+ // TODO(codyoss): once all libs use this auth lib move metadata check here
57+ return allowOnGCECheck && metadata.OnGCE()
60+// DetectDefault searches for "Application Default Credentials" and returns
61+// a credential based on the [DetectOptions] provided.
63+// It looks for credentials in the following places, preferring the first
66+// - A JSON file whose path is specified by the GOOGLE_APPLICATION_CREDENTIALS
67+// environment variable. For workload identity federation, refer to
68+// https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation
69+// on how to generate the JSON configuration file for on-prem/non-Google
71+// - A JSON file in a location known to the gcloud command-line tool. On
72+// Windows, this is %APPDATA%/gcloud/application_default_credentials.json. On
73+// other systems, $HOME/.config/gcloud/application_default_credentials.json.
74+// - On Google Compute Engine, Google App Engine standard second generation
75+// runtimes, and Google App Engine flexible environment, it fetches
76+// credentials from the metadata server.
77+func DetectDefault(opts *DetectOptions) (*auth.Credentials, error) {
78+ if err := opts.validate(); err != nil {
81+ if len(opts.CredentialsJSON) > 0 {
82+ return readCredentialsFileJSON(opts.CredentialsJSON, opts)
84+ if opts.CredentialsFile != "" {
85+ return readCredentialsFile(opts.CredentialsFile, opts)
87+ if filename := os.Getenv(credsfile.GoogleAppCredsEnvVar); filename != "" {
88+ creds, err := readCredentialsFile(filename, opts)
95+ fileName := credsfile.GetWellKnownFileName()
96+ if b, err := os.ReadFile(fileName); err == nil {
97+ return readCredentialsFileJSON(b, opts)
101+ metadataClient := metadata.NewWithOptions(&metadata.Options{
102+ Logger: opts.logger(),
104+ return auth.NewCredentials(&auth.CredentialsOptions{
105+ TokenProvider: computeTokenProvider(opts, metadataClient),
106+ ProjectIDProvider: auth.CredentialsPropertyFunc(func(ctx context.Context) (string, error) {
107+ return metadataClient.ProjectIDWithContext(ctx)
109+ UniverseDomainProvider: &internal.ComputeUniverseDomainProvider{
110+ MetadataClient: metadataClient,
115+ return nil, fmt.Errorf("credentials: could not find default credentials. See %v for more information", adcSetupURL)
118+// DetectOptions provides configuration for [DetectDefault].
119+type DetectOptions struct {
120+ // Scopes that credentials tokens should have. Example:
121+ // https://www.googleapis.com/auth/cloud-platform. Required if Audience is
124+ // Audience that credentials tokens should have. Only applicable for 2LO
125+ // flows with service accounts. If specified, scopes should not be provided.
127+ // Subject is the user email used for [domain wide delegation](https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority).
130+ // EarlyTokenRefresh configures how early before a token expires that it
131+ // should be refreshed. Once the token’s time until expiration has entered
132+ // this refresh window the token is considered valid but stale. If unset,
133+ // the default value is 3 minutes and 45 seconds. Optional.
134+ EarlyTokenRefresh time.Duration
135+ // DisableAsyncRefresh configures a synchronous workflow that refreshes
136+ // stale tokens while blocking. The default is false. Optional.
137+ DisableAsyncRefresh bool
138+ // AuthHandlerOptions configures an authorization handler and other options
139+ // for 3LO flows. It is required, and only used, for client credential
141+ AuthHandlerOptions *auth.AuthorizationHandlerOptions
142+ // TokenURL allows to set the token endpoint for user credential flows. If
143+ // unset the default value is: https://oauth2.googleapis.com/token.
146+ // STSAudience is the audience sent to when retrieving an STS token.
147+ // Currently this only used for GDCH auth flow, for which it is required.
149+ // CredentialsFile overrides detection logic and sources a credential file
150+ // from the provided filepath. If provided, CredentialsJSON must not be.
152+ CredentialsFile string
153+ // CredentialsJSON overrides detection logic and uses the JSON bytes as the
154+ // source for the credential. If provided, CredentialsFile must not be.
156+ CredentialsJSON []byte
157+ // UseSelfSignedJWT directs service account based credentials to create a
158+ // self-signed JWT with the private key found in the file, skipping any
159+ // network requests that would normally be made. Optional.
160+ UseSelfSignedJWT bool
161+ // Client configures the underlying client used to make network requests
162+ // when fetching tokens. Optional.
164+ // UniverseDomain is the default service domain for a given Cloud universe.
165+ // The default value is "googleapis.com". This option is ignored for
166+ // authentication flows that do not support universe domain. Optional.
167+ UniverseDomain string
168+ // Logger is used for debug logging. If provided, logging will be enabled
169+ // at the loggers configured level. By default logging is disabled unless
170+ // enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
171+ // logger will be used. Optional.
175+func (o *DetectOptions) validate() error {
177+ return errors.New("credentials: options must be provided")
179+ if len(o.Scopes) > 0 && o.Audience != "" {
180+ return errors.New("credentials: both scopes and audience were provided")
182+ if len(o.CredentialsJSON) > 0 && o.CredentialsFile != "" {
183+ return errors.New("credentials: both credentials file and JSON were provided")
188+func (o *DetectOptions) tokenURL() string {
189+ if o.TokenURL != "" {
192+ return googleTokenURL
195+func (o *DetectOptions) scopes() []string {
196+ scopes := make([]string, len(o.Scopes))
197+ copy(scopes, o.Scopes)
201+func (o *DetectOptions) client() *http.Client {
202+ if o.Client != nil {
205+ return internal.DefaultClient()
208+func (o *DetectOptions) logger() *slog.Logger {
209+ return internallog.New(o.Logger)
212+func readCredentialsFile(filename string, opts *DetectOptions) (*auth.Credentials, error) {
213+ b, err := os.ReadFile(filename)
217+ return readCredentialsFileJSON(b, opts)
220+func readCredentialsFileJSON(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
221+ // attempt to parse jsonData as a Google Developers Console client_credentials.json.
222+ config := clientCredConfigFromJSON(b, opts)
224+ if config.AuthHandlerOpts == nil {
225+ return nil, errors.New("credentials: auth handler must be specified for this credential filetype")
227+ tp, err := auth.New3LOTokenProvider(config)
231+ return auth.NewCredentials(&auth.CredentialsOptions{
236+ return fileCredentials(b, opts)
239+func clientCredConfigFromJSON(b []byte, opts *DetectOptions) *auth.Options3LO {
240+ var creds credsfile.ClientCredentialsFile
241+ var c *credsfile.Config3LO
242+ if err := json.Unmarshal(b, &creds); err != nil {
246+ case creds.Web != nil:
248+ case creds.Installed != nil:
253+ if len(c.RedirectURIs) < 1 {
256+ var handleOpts *auth.AuthorizationHandlerOptions
257+ if opts.AuthHandlerOptions != nil {
258+ handleOpts = &auth.AuthorizationHandlerOptions{
259+ Handler: opts.AuthHandlerOptions.Handler,
260+ State: opts.AuthHandlerOptions.State,
261+ PKCEOpts: opts.AuthHandlerOptions.PKCEOpts,
264+ return &auth.Options3LO{
265+ ClientID: c.ClientID,
266+ ClientSecret: c.ClientSecret,
267+ RedirectURL: c.RedirectURIs[0],
268+ Scopes: opts.scopes(),
270+ TokenURL: c.TokenURI,
271+ Client: opts.client(),
272+ Logger: opts.logger(),
273+ EarlyTokenExpiry: opts.EarlyTokenRefresh,
274+ AuthHandlerOpts: handleOpts,
275+ // TODO(codyoss): refactor this out. We need to add in auto-detection
276+ // for this use case.
277+ AuthStyle: auth.StyleInParams,
vendor/cloud.google.com/go/auth/credentials/doc.gonew file+45
@@ -0,0 +1,45 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
15+// Package credentials provides support for making OAuth2 authorized and
16+// authenticated HTTP requests to Google APIs. It supports the Web server flow,
17+// client-side credentials, service accounts, Google Compute Engine service
18+// accounts, Google App Engine service accounts and workload identity federation
19+// from non-Google cloud platforms.
21+// A brief overview of the package follows. For more information, please read
22+// https://developers.google.com/accounts/docs/OAuth2
24+// https://developers.google.com/accounts/docs/application-default-credentials.
25+// For more information on using workload identity federation, refer to
26+// https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation.
30+// The [cloud.google.com/go/auth.Credentials] type represents Google
31+// credentials, including Application Default Credentials.
33+// Use [DetectDefault] to obtain Application Default Credentials.
35+// Application Default Credentials support workload identity federation to
36+// access Google Cloud resources from non-Google Cloud platforms including Amazon
37+// Web Services (AWS), Microsoft Azure or any identity provider that supports
38+// OpenID Connect (OIDC). Workload identity federation is recommended for
39+// non-Google Cloud environments as it avoids the need to download, manage, and
40+// store service account private keys locally.
42+// # Workforce Identity Federation
44+// For more information on this feature see [cloud.google.com/go/auth/credentials/externalaccount].
vendor/cloud.google.com/go/auth/credentials/filetypes.gonew file+231
@@ -0,0 +1,231 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
21+ "cloud.google.com/go/auth"
22+ "cloud.google.com/go/auth/credentials/internal/externalaccount"
23+ "cloud.google.com/go/auth/credentials/internal/externalaccountuser"
24+ "cloud.google.com/go/auth/credentials/internal/gdch"
25+ "cloud.google.com/go/auth/credentials/internal/impersonate"
26+ internalauth "cloud.google.com/go/auth/internal"
27+ "cloud.google.com/go/auth/internal/credsfile"
30+func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
31+ fileType, err := credsfile.ParseFileType(b)
36+ var projectID, universeDomain string
37+ var tp auth.TokenProvider
39+ case credsfile.ServiceAccountKey:
40+ f, err := credsfile.ParseServiceAccount(b)
44+ tp, err = handleServiceAccount(f, opts)
48+ projectID = f.ProjectID
49+ universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
50+ case credsfile.UserCredentialsKey:
51+ f, err := credsfile.ParseUserCredentials(b)
55+ tp, err = handleUserCredential(f, opts)
59+ universeDomain = f.UniverseDomain
60+ case credsfile.ExternalAccountKey:
61+ f, err := credsfile.ParseExternalAccount(b)
65+ tp, err = handleExternalAccount(f, opts)
69+ universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
70+ case credsfile.ExternalAccountAuthorizedUserKey:
71+ f, err := credsfile.ParseExternalAccountAuthorizedUser(b)
75+ tp, err = handleExternalAccountAuthorizedUser(f, opts)
79+ universeDomain = f.UniverseDomain
80+ case credsfile.ImpersonatedServiceAccountKey:
81+ f, err := credsfile.ParseImpersonatedServiceAccount(b)
85+ tp, err = handleImpersonatedServiceAccount(f, opts)
89+ universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
90+ case credsfile.GDCHServiceAccountKey:
91+ f, err := credsfile.ParseGDCHServiceAccount(b)
95+ tp, err = handleGDCHServiceAccount(f, opts)
99+ projectID = f.Project
100+ universeDomain = f.UniverseDomain
102+ return nil, fmt.Errorf("credentials: unsupported filetype %q", fileType)
104+ return auth.NewCredentials(&auth.CredentialsOptions{
105+ TokenProvider: auth.NewCachedTokenProvider(tp, &auth.CachedTokenProviderOptions{
106+ ExpireEarly: opts.EarlyTokenRefresh,
109+ ProjectIDProvider: internalauth.StaticCredentialsProperty(projectID),
110+ // TODO(codyoss): only set quota project here if there was a user override
111+ UniverseDomainProvider: internalauth.StaticCredentialsProperty(universeDomain),
115+// resolveUniverseDomain returns optsUniverseDomain if non-empty, in order to
116+// support configuring universe-specific credentials in code. Auth flows
117+// unsupported for universe domain should not use this func, but should instead
118+// simply set the file universe domain on the credentials.
119+func resolveUniverseDomain(optsUniverseDomain, fileUniverseDomain string) string {
120+ if optsUniverseDomain != "" {
121+ return optsUniverseDomain
123+ return fileUniverseDomain
126+func handleServiceAccount(f *credsfile.ServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
127+ ud := resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
128+ if opts.UseSelfSignedJWT {
129+ return configureSelfSignedJWT(f, opts)
130+ } else if ud != "" && ud != internalauth.DefaultUniverseDomain {
131+ // For non-GDU universe domains, token exchange is impossible and services
132+ // must support self-signed JWTs.
133+ opts.UseSelfSignedJWT = true
134+ return configureSelfSignedJWT(f, opts)
136+ opts2LO := &auth.Options2LO{
137+ Email: f.ClientEmail,
138+ PrivateKey: []byte(f.PrivateKey),
139+ PrivateKeyID: f.PrivateKeyID,
140+ Scopes: opts.scopes(),
141+ TokenURL: f.TokenURL,
142+ Subject: opts.Subject,
143+ Client: opts.client(),
144+ Logger: opts.logger(),
146+ if opts2LO.TokenURL == "" {
147+ opts2LO.TokenURL = jwtTokenURL
149+ return auth.New2LOTokenProvider(opts2LO)
152+func handleUserCredential(f *credsfile.UserCredentialsFile, opts *DetectOptions) (auth.TokenProvider, error) {
153+ opts3LO := &auth.Options3LO{
154+ ClientID: f.ClientID,
155+ ClientSecret: f.ClientSecret,
156+ Scopes: opts.scopes(),
157+ AuthURL: googleAuthURL,
158+ TokenURL: opts.tokenURL(),
159+ AuthStyle: auth.StyleInParams,
160+ EarlyTokenExpiry: opts.EarlyTokenRefresh,
161+ RefreshToken: f.RefreshToken,
162+ Client: opts.client(),
163+ Logger: opts.logger(),
165+ return auth.New3LOTokenProvider(opts3LO)
168+func handleExternalAccount(f *credsfile.ExternalAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
169+ externalOpts := &externalaccount.Options{
170+ Audience: f.Audience,
171+ SubjectTokenType: f.SubjectTokenType,
172+ TokenURL: f.TokenURL,
173+ TokenInfoURL: f.TokenInfoURL,
174+ ServiceAccountImpersonationURL: f.ServiceAccountImpersonationURL,
175+ ClientSecret: f.ClientSecret,
176+ ClientID: f.ClientID,
177+ CredentialSource: f.CredentialSource,
178+ QuotaProjectID: f.QuotaProjectID,
179+ Scopes: opts.scopes(),
180+ WorkforcePoolUserProject: f.WorkforcePoolUserProject,
181+ Client: opts.client(),
182+ Logger: opts.logger(),
183+ IsDefaultClient: opts.Client == nil,
185+ if f.ServiceAccountImpersonation != nil {
186+ externalOpts.ServiceAccountImpersonationLifetimeSeconds = f.ServiceAccountImpersonation.TokenLifetimeSeconds
188+ return externalaccount.NewTokenProvider(externalOpts)
191+func handleExternalAccountAuthorizedUser(f *credsfile.ExternalAccountAuthorizedUserFile, opts *DetectOptions) (auth.TokenProvider, error) {
192+ externalOpts := &externalaccountuser.Options{
193+ Audience: f.Audience,
194+ RefreshToken: f.RefreshToken,
195+ TokenURL: f.TokenURL,
196+ TokenInfoURL: f.TokenInfoURL,
197+ ClientID: f.ClientID,
198+ ClientSecret: f.ClientSecret,
199+ Scopes: opts.scopes(),
200+ Client: opts.client(),
201+ Logger: opts.logger(),
203+ return externalaccountuser.NewTokenProvider(externalOpts)
206+func handleImpersonatedServiceAccount(f *credsfile.ImpersonatedServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
207+ if f.ServiceAccountImpersonationURL == "" || f.CredSource == nil {
208+ return nil, errors.New("missing 'source_credentials' field or 'service_account_impersonation_url' in credentials")
211+ tp, err := fileCredentials(f.CredSource, opts)
215+ return impersonate.NewTokenProvider(&impersonate.Options{
216+ URL: f.ServiceAccountImpersonationURL,
217+ Scopes: opts.scopes(),
219+ Delegates: f.Delegates,
220+ Client: opts.client(),
221+ Logger: opts.logger(),
225+func handleGDCHServiceAccount(f *credsfile.GDCHServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
226+ return gdch.NewTokenProvider(f, &gdch.Options{
227+ STSAudience: opts.STSAudience,
228+ Client: opts.client(),
229+ Logger: opts.logger(),
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/aws_provider.gonew file+531
@@ -0,0 +1,531 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
15+package externalaccount
35+ "cloud.google.com/go/auth/internal"
36+ "github.com/googleapis/gax-go/v2/internallog"
40+ // getenv aliases os.Getenv for testing
45+ // AWS Signature Version 4 signing algorithm identifier.
46+ awsAlgorithm = "AWS4-HMAC-SHA256"
48+ // The termination string for the AWS credential scope value as defined in
49+ // https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
50+ awsRequestType = "aws4_request"
52+ // The AWS authorization header name for the security session token if available.
53+ awsSecurityTokenHeader = "x-amz-security-token"
55+ // The name of the header containing the session token for metadata endpoint calls
56+ awsIMDSv2SessionTokenHeader = "X-aws-ec2-metadata-token"
58+ awsIMDSv2SessionTTLHeader = "X-aws-ec2-metadata-token-ttl-seconds"
60+ awsIMDSv2SessionTTL = "300"
62+ // The AWS authorization header name for the auto-generated date.
63+ awsDateHeader = "x-amz-date"
65+ defaultRegionalCredentialVerificationURL = "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
67+ // Supported AWS configuration environment variables.
68+ awsAccessKeyIDEnvVar = "AWS_ACCESS_KEY_ID"
69+ awsDefaultRegionEnvVar = "AWS_DEFAULT_REGION"
70+ awsRegionEnvVar = "AWS_REGION"
71+ awsSecretAccessKeyEnvVar = "AWS_SECRET_ACCESS_KEY"
72+ awsSessionTokenEnvVar = "AWS_SESSION_TOKEN"
74+ awsTimeFormatLong = "20060102T150405Z"
75+ awsTimeFormatShort = "20060102"
76+ awsProviderType = "aws"
79+type awsSubjectProvider struct {
82+ RegionalCredVerificationURL string
83+ CredVerificationURL string
84+ IMDSv2SessionTokenURL string
85+ TargetResource string
86+ requestSigner *awsRequestSigner
88+ securityCredentialsProvider AwsSecurityCredentialsProvider
89+ reqOpts *RequestOptions
95+func (sp *awsSubjectProvider) subjectToken(ctx context.Context) (string, error) {
97+ if sp.RegionalCredVerificationURL == "" {
98+ sp.RegionalCredVerificationURL = defaultRegionalCredentialVerificationURL
100+ headers := make(map[string]string)
101+ if sp.shouldUseMetadataServer() {
102+ awsSessionToken, err := sp.getAWSSessionToken(ctx)
107+ if awsSessionToken != "" {
108+ headers[awsIMDSv2SessionTokenHeader] = awsSessionToken
112+ awsSecurityCredentials, err := sp.getSecurityCredentials(ctx, headers)
116+ if sp.region, err = sp.getRegion(ctx, headers); err != nil {
119+ sp.requestSigner = &awsRequestSigner{
120+ RegionName: sp.region,
121+ AwsSecurityCredentials: awsSecurityCredentials,
124+ // Generate the signed request to AWS STS GetCallerIdentity API.
125+ // Use the required regional endpoint. Otherwise, the request will fail.
126+ req, err := http.NewRequestWithContext(ctx, "POST", strings.Replace(sp.RegionalCredVerificationURL, "{region}", sp.region, 1), nil)
130+ // The full, canonical resource name of the workload identity pool
131+ // provider, with or without the HTTPS prefix.
132+ // Including this header as part of the signature is recommended to
133+ // ensure data integrity.
134+ if sp.TargetResource != "" {
135+ req.Header.Set("x-goog-cloud-target-resource", sp.TargetResource)
137+ sp.requestSigner.signRequest(req)
140+ The GCP STS endpoint expects the headers to be formatted as:
142+ # {key: 'x-amz-date', value: '...'},
143+ # {key: 'Authorization', value: '...'},
146+ # And then serialized as:
147+ # quote(json.dumps({
150+ # headers: [{key: 'x-amz-date', value: '...'}, ...]
154+ awsSignedReq := awsRequest{
155+ URL: req.URL.String(),
158+ for headerKey, headerList := range req.Header {
159+ for _, headerValue := range headerList {
160+ awsSignedReq.Headers = append(awsSignedReq.Headers, awsRequestHeader{
166+ sort.Slice(awsSignedReq.Headers, func(i, j int) bool {
167+ headerCompare := strings.Compare(awsSignedReq.Headers[i].Key, awsSignedReq.Headers[j].Key)
168+ if headerCompare == 0 {
169+ return strings.Compare(awsSignedReq.Headers[i].Value, awsSignedReq.Headers[j].Value) < 0
171+ return headerCompare < 0
174+ result, err := json.Marshal(awsSignedReq)
178+ return url.QueryEscape(string(result)), nil
181+func (sp *awsSubjectProvider) providerType() string {
182+ if sp.securityCredentialsProvider != nil {
183+ return programmaticProviderType
185+ return awsProviderType
188+func (sp *awsSubjectProvider) getAWSSessionToken(ctx context.Context) (string, error) {
189+ if sp.IMDSv2SessionTokenURL == "" {
192+ req, err := http.NewRequestWithContext(ctx, "PUT", sp.IMDSv2SessionTokenURL, nil)
196+ req.Header.Set(awsIMDSv2SessionTTLHeader, awsIMDSv2SessionTTL)
198+ sp.logger.DebugContext(ctx, "aws session token request", "request", internallog.HTTPRequest(req, nil))
199+ resp, body, err := internal.DoRequest(sp.Client, req)
203+ sp.logger.DebugContext(ctx, "aws session token response", "response", internallog.HTTPResponse(resp, body))
204+ if resp.StatusCode != http.StatusOK {
205+ return "", fmt.Errorf("credentials: unable to retrieve AWS session token: %s", body)
207+ return string(body), nil
210+func (sp *awsSubjectProvider) getRegion(ctx context.Context, headers map[string]string) (string, error) {
211+ if sp.securityCredentialsProvider != nil {
212+ return sp.securityCredentialsProvider.AwsRegion(ctx, sp.reqOpts)
214+ if canRetrieveRegionFromEnvironment() {
215+ if envAwsRegion := getenv(awsRegionEnvVar); envAwsRegion != "" {
216+ return envAwsRegion, nil
218+ return getenv(awsDefaultRegionEnvVar), nil
221+ if sp.RegionURL == "" {
222+ return "", errors.New("credentials: unable to determine AWS region")
225+ req, err := http.NewRequestWithContext(ctx, "GET", sp.RegionURL, nil)
230+ for name, value := range headers {
231+ req.Header.Add(name, value)
233+ sp.logger.DebugContext(ctx, "aws region request", "request", internallog.HTTPRequest(req, nil))
234+ resp, body, err := internal.DoRequest(sp.Client, req)
238+ sp.logger.DebugContext(ctx, "aws region response", "response", internallog.HTTPResponse(resp, body))
239+ if resp.StatusCode != http.StatusOK {
240+ return "", fmt.Errorf("credentials: unable to retrieve AWS region - %s", body)
243+ // This endpoint will return the region in format: us-east-2b.
244+ // Only the us-east-2 part should be used.
245+ bodyLen := len(body)
249+ return string(body[:bodyLen-1]), nil
252+func (sp *awsSubjectProvider) getSecurityCredentials(ctx context.Context, headers map[string]string) (result *AwsSecurityCredentials, err error) {
253+ if sp.securityCredentialsProvider != nil {
254+ return sp.securityCredentialsProvider.AwsSecurityCredentials(ctx, sp.reqOpts)
256+ if canRetrieveSecurityCredentialFromEnvironment() {
257+ return &AwsSecurityCredentials{
258+ AccessKeyID: getenv(awsAccessKeyIDEnvVar),
259+ SecretAccessKey: getenv(awsSecretAccessKeyEnvVar),
260+ SessionToken: getenv(awsSessionTokenEnvVar),
264+ roleName, err := sp.getMetadataRoleName(ctx, headers)
268+ credentials, err := sp.getMetadataSecurityCredentials(ctx, roleName, headers)
273+ if credentials.AccessKeyID == "" {
274+ return result, errors.New("credentials: missing AccessKeyId credential")
276+ if credentials.SecretAccessKey == "" {
277+ return result, errors.New("credentials: missing SecretAccessKey credential")
280+ return credentials, nil
283+func (sp *awsSubjectProvider) getMetadataSecurityCredentials(ctx context.Context, roleName string, headers map[string]string) (*AwsSecurityCredentials, error) {
284+ var result *AwsSecurityCredentials
286+ req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s", sp.CredVerificationURL, roleName), nil)
290+ for name, value := range headers {
291+ req.Header.Add(name, value)
293+ sp.logger.DebugContext(ctx, "aws security credential request", "request", internallog.HTTPRequest(req, nil))
294+ resp, body, err := internal.DoRequest(sp.Client, req)
298+ sp.logger.DebugContext(ctx, "aws security credential response", "response", internallog.HTTPResponse(resp, body))
299+ if resp.StatusCode != http.StatusOK {
300+ return result, fmt.Errorf("credentials: unable to retrieve AWS security credentials - %s", body)
302+ if err := json.Unmarshal(body, &result); err != nil {
308+func (sp *awsSubjectProvider) getMetadataRoleName(ctx context.Context, headers map[string]string) (string, error) {
309+ if sp.CredVerificationURL == "" {
310+ return "", errors.New("credentials: unable to determine the AWS metadata server security credentials endpoint")
312+ req, err := http.NewRequestWithContext(ctx, "GET", sp.CredVerificationURL, nil)
316+ for name, value := range headers {
317+ req.Header.Add(name, value)
320+ sp.logger.DebugContext(ctx, "aws metadata role request", "request", internallog.HTTPRequest(req, nil))
321+ resp, body, err := internal.DoRequest(sp.Client, req)
325+ sp.logger.DebugContext(ctx, "aws metadata role response", "response", internallog.HTTPResponse(resp, body))
326+ if resp.StatusCode != http.StatusOK {
327+ return "", fmt.Errorf("credentials: unable to retrieve AWS role name - %s", body)
329+ return string(body), nil
332+// awsRequestSigner is a utility class to sign http requests using a AWS V4 signature.
333+type awsRequestSigner struct {
335+ AwsSecurityCredentials *AwsSecurityCredentials
338+// signRequest adds the appropriate headers to an http.Request
339+// or returns an error if something prevented this.
340+func (rs *awsRequestSigner) signRequest(req *http.Request) error {
341+ // req is assumed non-nil
342+ signedRequest := cloneRequest(req)
344+ signedRequest.Header.Set("host", requestHost(req))
345+ if rs.AwsSecurityCredentials.SessionToken != "" {
346+ signedRequest.Header.Set(awsSecurityTokenHeader, rs.AwsSecurityCredentials.SessionToken)
348+ if signedRequest.Header.Get("date") == "" {
349+ signedRequest.Header.Set(awsDateHeader, timestamp.Format(awsTimeFormatLong))
351+ authorizationCode, err := rs.generateAuthentication(signedRequest, timestamp)
355+ signedRequest.Header.Set("Authorization", authorizationCode)
356+ req.Header = signedRequest.Header
360+func (rs *awsRequestSigner) generateAuthentication(req *http.Request, timestamp time.Time) (string, error) {
361+ canonicalHeaderColumns, canonicalHeaderData := canonicalHeaders(req)
362+ dateStamp := timestamp.Format(awsTimeFormatShort)
365+ if splitHost := strings.Split(requestHost(req), "."); len(splitHost) > 0 {
366+ serviceName = splitHost[0]
368+ credentialScope := strings.Join([]string{dateStamp, rs.RegionName, serviceName, awsRequestType}, "/")
369+ requestString, err := canonicalRequest(req, canonicalHeaderColumns, canonicalHeaderData)
373+ requestHash, err := getSha256([]byte(requestString))
378+ stringToSign := strings.Join([]string{awsAlgorithm, timestamp.Format(awsTimeFormatLong), credentialScope, requestHash}, "\n")
379+ signingKey := []byte("AWS4" + rs.AwsSecurityCredentials.SecretAccessKey)
380+ for _, signingInput := range []string{
381+ dateStamp, rs.RegionName, serviceName, awsRequestType, stringToSign,
383+ signingKey, err = getHmacSha256(signingKey, []byte(signingInput))
389+ return fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", awsAlgorithm, rs.AwsSecurityCredentials.AccessKeyID, credentialScope, canonicalHeaderColumns, hex.EncodeToString(signingKey)), nil
392+func getSha256(input []byte) (string, error) {
393+ hash := sha256.New()
394+ if _, err := hash.Write(input); err != nil {
397+ return hex.EncodeToString(hash.Sum(nil)), nil
400+func getHmacSha256(key, input []byte) ([]byte, error) {
401+ hash := hmac.New(sha256.New, key)
402+ if _, err := hash.Write(input); err != nil {
405+ return hash.Sum(nil), nil
408+func cloneRequest(r *http.Request) *http.Request {
409+ r2 := new(http.Request)
411+ if r.Header != nil {
412+ r2.Header = make(http.Header, len(r.Header))
414+ // Find total number of values.
416+ for _, headerValues := range r.Header {
417+ headerCount += len(headerValues)
419+ copiedHeaders := make([]string, headerCount) // shared backing array for headers' values
421+ for headerKey, headerValues := range r.Header {
422+ headerCount = copy(copiedHeaders, headerValues)
423+ r2.Header[headerKey] = copiedHeaders[:headerCount:headerCount]
424+ copiedHeaders = copiedHeaders[headerCount:]
430+func canonicalPath(req *http.Request) string {
431+ result := req.URL.EscapedPath()
435+ return path.Clean(result)
438+func canonicalQuery(req *http.Request) string {
439+ queryValues := req.URL.Query()
440+ for queryKey := range queryValues {
441+ sort.Strings(queryValues[queryKey])
443+ return queryValues.Encode()
446+func canonicalHeaders(req *http.Request) (string, string) {
447+ // Header keys need to be sorted alphabetically.
448+ var headers []string
449+ lowerCaseHeaders := make(http.Header)
450+ for k, v := range req.Header {
451+ k := strings.ToLower(k)
452+ if _, ok := lowerCaseHeaders[k]; ok {
453+ // include additional values
454+ lowerCaseHeaders[k] = append(lowerCaseHeaders[k], v...)
456+ headers = append(headers, k)
457+ lowerCaseHeaders[k] = v
460+ sort.Strings(headers)
462+ var fullHeaders bytes.Buffer
463+ for _, header := range headers {
464+ headerValue := strings.Join(lowerCaseHeaders[header], ",")
465+ fullHeaders.WriteString(header)
466+ fullHeaders.WriteRune(':')
467+ fullHeaders.WriteString(headerValue)
468+ fullHeaders.WriteRune('\n')
471+ return strings.Join(headers, ";"), fullHeaders.String()
474+func requestDataHash(req *http.Request) (string, error) {
475+ var requestData []byte
476+ if req.Body != nil {
477+ requestBody, err := req.GetBody()
481+ defer requestBody.Close()
483+ requestData, err = internal.ReadAll(requestBody)
489+ return getSha256(requestData)
492+func requestHost(req *http.Request) string {
499+func canonicalRequest(req *http.Request, canonicalHeaderColumns, canonicalHeaderData string) (string, error) {
500+ dataHash, err := requestDataHash(req)
504+ return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", req.Method, canonicalPath(req), canonicalQuery(req), canonicalHeaderData, canonicalHeaderColumns, dataHash), nil
507+type awsRequestHeader struct {
508+ Key string `json:"key"`
509+ Value string `json:"value"`
512+type awsRequest struct {
513+ URL string `json:"url"`
514+ Method string `json:"method"`
515+ Headers []awsRequestHeader `json:"headers"`
518+// The AWS region can be provided through AWS_REGION or AWS_DEFAULT_REGION. Only one is
520+func canRetrieveRegionFromEnvironment() bool {
521+ return getenv(awsRegionEnvVar) != "" || getenv(awsDefaultRegionEnvVar) != ""
524+// Check if both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are available.
525+func canRetrieveSecurityCredentialFromEnvironment() bool {
526+ return getenv(awsAccessKeyIDEnvVar) != "" && getenv(awsSecretAccessKeyEnvVar) != ""
529+func (sp *awsSubjectProvider) shouldUseMetadataServer() bool {
530+ return sp.securityCredentialsProvider == nil && (!canRetrieveRegionFromEnvironment() || !canRetrieveSecurityCredentialFromEnvironment())
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/executable_provider.gonew file+284
@@ -0,0 +1,284 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
15+package externalaccount
30+ "cloud.google.com/go/auth/internal"
34+ executableSupportedMaxVersion = 1
35+ executableDefaultTimeout = 30 * time.Second
36+ executableSource = "response"
37+ executableProviderType = "executable"
38+ outputFileSource = "output file"
40+ allowExecutablesEnvVar = "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES"
42+ jwtTokenType = "urn:ietf:params:oauth:token-type:jwt"
43+ idTokenType = "urn:ietf:params:oauth:token-type:id_token"
44+ saml2TokenType = "urn:ietf:params:oauth:token-type:saml2"
48+ serviceAccountImpersonationRE = regexp.MustCompile(`https://iamcredentials..+/v1/projects/-/serviceAccounts/(.*@.*):generateAccessToken`)
51+type nonCacheableError struct {
55+func (nce nonCacheableError) Error() string {
59+// environment is a contract for testing
60+type environment interface {
61+ existingEnv() []string
62+ getenv(string) string
63+ run(ctx context.Context, command string, env []string) ([]byte, error)
67+type runtimeEnvironment struct{}
69+func (r runtimeEnvironment) existingEnv() []string {
72+func (r runtimeEnvironment) getenv(key string) string {
73+ return os.Getenv(key)
75+func (r runtimeEnvironment) now() time.Time {
76+ return time.Now().UTC()
79+func (r runtimeEnvironment) run(ctx context.Context, command string, env []string) ([]byte, error) {
80+ splitCommand := strings.Fields(command)
81+ cmd := exec.CommandContext(ctx, splitCommand[0], splitCommand[1:]...)
84+ var stdout, stderr bytes.Buffer
88+ if err := cmd.Run(); err != nil {
89+ if ctx.Err() == context.DeadlineExceeded {
90+ return nil, context.DeadlineExceeded
92+ if exitError, ok := err.(*exec.ExitError); ok {
93+ return nil, exitCodeError(exitError)
95+ return nil, executableError(err)
98+ bytesStdout := bytes.TrimSpace(stdout.Bytes())
99+ if len(bytesStdout) > 0 {
100+ return bytesStdout, nil
102+ return bytes.TrimSpace(stderr.Bytes()), nil
105+type executableSubjectProvider struct {
107+ Timeout time.Duration
114+type executableResponse struct {
115+ Version int `json:"version,omitempty"`
116+ Success *bool `json:"success,omitempty"`
117+ TokenType string `json:"token_type,omitempty"`
118+ ExpirationTime int64 `json:"expiration_time,omitempty"`
119+ IDToken string `json:"id_token,omitempty"`
120+ SamlResponse string `json:"saml_response,omitempty"`
121+ Code string `json:"code,omitempty"`
122+ Message string `json:"message,omitempty"`
125+func (sp *executableSubjectProvider) parseSubjectTokenFromSource(response []byte, source string, now int64) (string, error) {
126+ var result executableResponse
127+ if err := json.Unmarshal(response, &result); err != nil {
128+ return "", jsonParsingError(source, string(response))
131+ if result.Version == 0 {
132+ return "", missingFieldError(source, "version")
134+ if result.Success == nil {
135+ return "", missingFieldError(source, "success")
137+ if !*result.Success {
138+ if result.Code == "" || result.Message == "" {
139+ return "", malformedFailureError()
141+ return "", userDefinedError(result.Code, result.Message)
143+ if result.Version > executableSupportedMaxVersion || result.Version < 0 {
144+ return "", unsupportedVersionError(source, result.Version)
146+ if result.ExpirationTime == 0 && sp.OutputFile != "" {
147+ return "", missingFieldError(source, "expiration_time")
149+ if result.TokenType == "" {
150+ return "", missingFieldError(source, "token_type")
152+ if result.ExpirationTime != 0 && result.ExpirationTime < now {
153+ return "", tokenExpiredError()
156+ switch result.TokenType {
157+ case jwtTokenType, idTokenType:
158+ if result.IDToken == "" {
159+ return "", missingFieldError(source, "id_token")
161+ return result.IDToken, nil
162+ case saml2TokenType:
163+ if result.SamlResponse == "" {
164+ return "", missingFieldError(source, "saml_response")
166+ return result.SamlResponse, nil
168+ return "", tokenTypeError(source)
172+func (sp *executableSubjectProvider) subjectToken(ctx context.Context) (string, error) {
173+ if token, err := sp.getTokenFromOutputFile(); token != "" || err != nil {
176+ return sp.getTokenFromExecutableCommand(ctx)
179+func (sp *executableSubjectProvider) providerType() string {
180+ return executableProviderType
183+func (sp *executableSubjectProvider) getTokenFromOutputFile() (token string, err error) {
184+ if sp.OutputFile == "" {
185+ // This ExecutableCredentialSource doesn't use an OutputFile.
189+ file, err := os.Open(sp.OutputFile)
191+ // No OutputFile found. Hasn't been created yet, so skip it.
196+ data, err := internal.ReadAll(file)
197+ if err != nil || len(data) == 0 {
198+ // Cachefile exists, but no data found. Get new credential.
202+ token, err = sp.parseSubjectTokenFromSource(data, outputFileSource, sp.env.now().Unix())
204+ if _, ok := err.(nonCacheableError); ok {
205+ // If the cached token is expired we need a new token,
206+ // and if the cache contains a failure, we need to try again.
210+ // There was an error in the cached token, and the developer should be aware of it.
213+ // Token parsing succeeded. Use found token.
217+func (sp *executableSubjectProvider) executableEnvironment() []string {
218+ result := sp.env.existingEnv()
219+ result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE=%v", sp.opts.Audience))
220+ result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE=%v", sp.opts.SubjectTokenType))
221+ result = append(result, "GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE=0")
222+ if sp.opts.ServiceAccountImpersonationURL != "" {
223+ matches := serviceAccountImpersonationRE.FindStringSubmatch(sp.opts.ServiceAccountImpersonationURL)
225+ result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL=%v", matches[1]))
228+ if sp.OutputFile != "" {
229+ result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE=%v", sp.OutputFile))
234+func (sp *executableSubjectProvider) getTokenFromExecutableCommand(ctx context.Context) (string, error) {
235+ // For security reasons, we need our consumers to set this environment variable to allow executables to be run.
236+ if sp.env.getenv(allowExecutablesEnvVar) != "1" {
237+ return "", errors.New("credentials: executables need to be explicitly allowed (set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES to '1') to run")
240+ ctx, cancel := context.WithDeadline(ctx, sp.env.now().Add(sp.Timeout))
243+ output, err := sp.env.run(ctx, sp.Command, sp.executableEnvironment())
247+ return sp.parseSubjectTokenFromSource(output, executableSource, sp.env.now().Unix())
250+func missingFieldError(source, field string) error {
251+ return fmt.Errorf("credentials: %q missing %q field", source, field)
254+func jsonParsingError(source, data string) error {
255+ return fmt.Errorf("credentials: unable to parse %q: %v", source, data)
258+func malformedFailureError() error {
259+ return nonCacheableError{"credentials: response must include `error` and `message` fields when unsuccessful"}
262+func userDefinedError(code, message string) error {
263+ return nonCacheableError{fmt.Sprintf("credentials: response contains unsuccessful response: (%v) %v", code, message)}
266+func unsupportedVersionError(source string, version int) error {
267+ return fmt.Errorf("credentials: %v contains unsupported version: %v", source, version)
270+func tokenExpiredError() error {
271+ return nonCacheableError{"credentials: the token returned by the executable is expired"}
274+func tokenTypeError(source string) error {
275+ return fmt.Errorf("credentials: %v contains unsupported token type", source)
278+func exitCodeError(err *exec.ExitError) error {
279+ return fmt.Errorf("credentials: executable command failed with exit code %v: %w", err.ExitCode(), err)
282+func executableError(err error) error {
283+ return fmt.Errorf("credentials: executable command failed: %w", err)
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/externalaccount.gonew file+428
@@ -0,0 +1,428 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
15+package externalaccount
28+ "cloud.google.com/go/auth"
29+ "cloud.google.com/go/auth/credentials/internal/impersonate"
30+ "cloud.google.com/go/auth/credentials/internal/stsexchange"
31+ "cloud.google.com/go/auth/internal/credsfile"
32+ "github.com/googleapis/gax-go/v2/internallog"
36+ timeoutMinimum = 5 * time.Second
37+ timeoutMaximum = 120 * time.Second
39+ universeDomainPlaceholder = "UNIVERSE_DOMAIN"
40+ defaultTokenURL = "https://sts.UNIVERSE_DOMAIN/v1/token"
41+ defaultUniverseDomain = "googleapis.com"
45+ // Now aliases time.Now for testing
46+ Now = func() time.Time {
47+ return time.Now().UTC()
49+ validWorkforceAudiencePattern *regexp.Regexp = regexp.MustCompile(`//iam\.googleapis\.com/locations/[^/]+/workforcePools/`)
52+// Options stores the configuration for fetching tokens with external credentials.
54+ // Audience is the Secure Token Service (STS) audience which contains the resource name for the workload
55+ // identity pool or the workforce pool and the provider identifier in that pool.
57+ // SubjectTokenType is the STS token type based on the Oauth2.0 token exchange spec
58+ // e.g. `urn:ietf:params:oauth:token-type:jwt`.
59+ SubjectTokenType string
60+ // TokenURL is the STS token exchange endpoint.
62+ // TokenInfoURL is the token_info endpoint used to retrieve the account related information (
63+ // user attributes like account identifier, eg. email, username, uid, etc). This is
64+ // needed for gCloud session account identification.
66+ // ServiceAccountImpersonationURL is the URL for the service account impersonation request. This is only
67+ // required for workload identity pools when APIs to be accessed have not integrated with UberMint.
68+ ServiceAccountImpersonationURL string
69+ // ServiceAccountImpersonationLifetimeSeconds is the number of seconds the service account impersonation
70+ // token will be valid for.
71+ ServiceAccountImpersonationLifetimeSeconds int
72+ // ClientSecret is currently only required if token_info endpoint also
73+ // needs to be called with the generated GCP access token. When provided, STS will be
74+ // called with additional basic authentication using client_id as username and client_secret as password.
76+ // ClientID is only required in conjunction with ClientSecret, as described above.
78+ // CredentialSource contains the necessary information to retrieve the token itself, as well
79+ // as some environmental information.
80+ CredentialSource *credsfile.CredentialSource
81+ // QuotaProjectID is injected by gCloud. If the value is non-empty, the Auth libraries
82+ // will set the x-goog-user-project which overrides the project associated with the credentials.
83+ QuotaProjectID string
84+ // Scopes contains the desired scopes for the returned access token.
86+ // WorkforcePoolUserProject should be set when it is a workforce pool and
87+ // not a workload identity pool. The underlying principal must still have
88+ // serviceusage.services.use IAM permission to use the project for
89+ // billing/quota. Optional.
90+ WorkforcePoolUserProject string
91+ // UniverseDomain is the default service domain for a given Cloud universe.
92+ // This value will be used in the default STS token URL. The default value
93+ // is "googleapis.com". It will not be used if TokenURL is set. Optional.
94+ UniverseDomain string
95+ // SubjectTokenProvider is an optional token provider for OIDC/SAML
96+ // credentials. One of SubjectTokenProvider, AWSSecurityCredentialProvider
97+ // or CredentialSource must be provided. Optional.
98+ SubjectTokenProvider SubjectTokenProvider
99+ // AwsSecurityCredentialsProvider is an AWS Security Credential provider
100+ // for AWS credentials. One of SubjectTokenProvider,
101+ // AWSSecurityCredentialProvider or CredentialSource must be provided. Optional.
102+ AwsSecurityCredentialsProvider AwsSecurityCredentialsProvider
103+ // Client for token request.
105+ // IsDefaultClient marks whether the client passed in is a default client that can be overriden.
106+ // This is important for X509 credentials which should create a new client if the default was used
107+ // but should respect a client explicitly passed in by the user.
108+ IsDefaultClient bool
109+ // Logger is used for debug logging. If provided, logging will be enabled
110+ // at the loggers configured level. By default logging is disabled unless
111+ // enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
112+ // logger will be used. Optional.
116+// SubjectTokenProvider can be used to supply a subject token to exchange for a
118+type SubjectTokenProvider interface {
119+ // SubjectToken should return a valid subject token or an error.
120+ // The external account token provider does not cache the returned subject
121+ // token, so caching logic should be implemented in the provider to prevent
122+ // multiple requests for the same subject token.
123+ SubjectToken(ctx context.Context, opts *RequestOptions) (string, error)
126+// RequestOptions contains information about the requested subject token or AWS
127+// security credentials from the Google external account credential.
128+type RequestOptions struct {
129+ // Audience is the requested audience for the external account credential.
131+ // Subject token type is the requested subject token type for the external
132+ // account credential. Expected values include:
133+ // “urn:ietf:params:oauth:token-type:jwt”
134+ // “urn:ietf:params:oauth:token-type:id-token”
135+ // “urn:ietf:params:oauth:token-type:saml2”
136+ // “urn:ietf:params:aws:token-type:aws4_request”
137+ SubjectTokenType string
140+// AwsSecurityCredentialsProvider can be used to supply AwsSecurityCredentials
141+// and an AWS Region to exchange for a GCP access token.
142+type AwsSecurityCredentialsProvider interface {
143+ // AwsRegion should return the AWS region or an error.
144+ AwsRegion(ctx context.Context, opts *RequestOptions) (string, error)
145+ // GetAwsSecurityCredentials should return a valid set of
146+ // AwsSecurityCredentials or an error. The external account token provider
147+ // does not cache the returned security credentials, so caching logic should
148+ // be implemented in the provider to prevent multiple requests for the
149+ // same security credentials.
150+ AwsSecurityCredentials(ctx context.Context, opts *RequestOptions) (*AwsSecurityCredentials, error)
153+// AwsSecurityCredentials models AWS security credentials.
154+type AwsSecurityCredentials struct {
155+ // AccessKeyId is the AWS Access Key ID - Required.
156+ AccessKeyID string `json:"AccessKeyID"`
157+ // SecretAccessKey is the AWS Secret Access Key - Required.
158+ SecretAccessKey string `json:"SecretAccessKey"`
159+ // SessionToken is the AWS Session token. This should be provided for
160+ // temporary AWS security credentials - Optional.
161+ SessionToken string `json:"Token"`
164+func (o *Options) validate() error {
165+ if o.Audience == "" {
166+ return fmt.Errorf("externalaccount: Audience must be set")
168+ if o.SubjectTokenType == "" {
169+ return fmt.Errorf("externalaccount: Subject token type must be set")
171+ if o.WorkforcePoolUserProject != "" {
172+ if valid := validWorkforceAudiencePattern.MatchString(o.Audience); !valid {
173+ return fmt.Errorf("externalaccount: workforce_pool_user_project should not be set for non-workforce pool credentials")
177+ if o.CredentialSource != nil {
180+ if o.SubjectTokenProvider != nil {
183+ if o.AwsSecurityCredentialsProvider != nil {
187+ return fmt.Errorf("externalaccount: one of CredentialSource, SubjectTokenProvider, or AwsSecurityCredentialsProvider must be set")
190+ return fmt.Errorf("externalaccount: only one of CredentialSource, SubjectTokenProvider, or AwsSecurityCredentialsProvider must be set")
195+// client returns the http client that should be used for the token exchange. If a non-default client
196+// is provided, then the client configured in the options will always be returned. If a default client
197+// is provided and the options are configured for X509 credentials, a new client will be created.
198+func (o *Options) client() (*http.Client, error) {
199+ // If a client was provided and no override certificate config location was provided, use the provided client.
200+ if o.CredentialSource == nil || o.CredentialSource.Certificate == nil || (!o.IsDefaultClient && o.CredentialSource.Certificate.CertificateConfigLocation == "") {
201+ return o.Client, nil
204+ // If a new client should be created, validate and use the certificate source to create a new mTLS client.
205+ cert := o.CredentialSource.Certificate
206+ if !cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation == "" {
207+ return nil, errors.New("credentials: \"certificate\" object must either specify a certificate_config_location or use_default_certificate_config should be true")
209+ if cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation != "" {
210+ return nil, errors.New("credentials: \"certificate\" object cannot specify both a certificate_config_location and use_default_certificate_config=true")
212+ return createX509Client(cert.CertificateConfigLocation)
215+// resolveTokenURL sets the default STS token endpoint with the configured
217+func (o *Options) resolveTokenURL() {
218+ if o.TokenURL != "" {
220+ } else if o.UniverseDomain != "" {
221+ o.TokenURL = strings.Replace(defaultTokenURL, universeDomainPlaceholder, o.UniverseDomain, 1)
223+ o.TokenURL = strings.Replace(defaultTokenURL, universeDomainPlaceholder, defaultUniverseDomain, 1)
227+// NewTokenProvider returns a [cloud.google.com/go/auth.TokenProvider]
228+// configured with the provided options.
229+func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
230+ if err := opts.validate(); err != nil {
233+ opts.resolveTokenURL()
234+ logger := internallog.New(opts.Logger)
235+ stp, err := newSubjectTokenProvider(opts)
240+ client, err := opts.client()
245+ tp := &tokenProvider{
252+ if opts.ServiceAccountImpersonationURL == "" {
253+ return auth.NewCachedTokenProvider(tp, nil), nil
256+ scopes := make([]string, len(opts.Scopes))
257+ copy(scopes, opts.Scopes)
258+ // needed for impersonation
259+ tp.opts.Scopes = []string{"https://www.googleapis.com/auth/cloud-platform"}
260+ imp, err := impersonate.NewTokenProvider(&impersonate.Options{
262+ URL: opts.ServiceAccountImpersonationURL,
264+ Tp: auth.NewCachedTokenProvider(tp, nil),
265+ TokenLifetimeSeconds: opts.ServiceAccountImpersonationLifetimeSeconds,
271+ return auth.NewCachedTokenProvider(imp, nil), nil
274+type subjectTokenProvider interface {
275+ subjectToken(ctx context.Context) (string, error)
276+ providerType() string
279+// tokenProvider is the provider that handles external credentials. It is used to retrieve Tokens.
280+type tokenProvider struct {
284+ stp subjectTokenProvider
287+func (tp *tokenProvider) Token(ctx context.Context) (*auth.Token, error) {
288+ subjectToken, err := tp.stp.subjectToken(ctx)
293+ stsRequest := &stsexchange.TokenRequest{
294+ GrantType: stsexchange.GrantType,
295+ Audience: tp.opts.Audience,
296+ Scope: tp.opts.Scopes,
297+ RequestedTokenType: stsexchange.TokenType,
298+ SubjectToken: subjectToken,
299+ SubjectTokenType: tp.opts.SubjectTokenType,
301+ header := make(http.Header)
302+ header.Set("Content-Type", "application/x-www-form-urlencoded")
303+ header.Add("x-goog-api-client", getGoogHeaderValue(tp.opts, tp.stp))
304+ clientAuth := stsexchange.ClientAuthentication{
305+ AuthStyle: auth.StyleInHeader,
306+ ClientID: tp.opts.ClientID,
307+ ClientSecret: tp.opts.ClientSecret,
309+ var options map[string]interface{}
310+ // Do not pass workforce_pool_user_project when client authentication is used.
311+ // The client ID is sufficient for determining the user project.
312+ if tp.opts.WorkforcePoolUserProject != "" && tp.opts.ClientID == "" {
313+ options = map[string]interface{}{
314+ "userProject": tp.opts.WorkforcePoolUserProject,
317+ stsResp, err := stsexchange.ExchangeToken(ctx, &stsexchange.Options{
319+ Endpoint: tp.opts.TokenURL,
320+ Request: stsRequest,
321+ Authentication: clientAuth,
331+ Value: stsResp.AccessToken,
332+ Type: stsResp.TokenType,
334+ // The RFC8693 doesn't define the explicit 0 of "expires_in" field behavior.
335+ if stsResp.ExpiresIn <= 0 {
336+ return nil, fmt.Errorf("credentials: got invalid expiry from security token service")
338+ tok.Expiry = Now().Add(time.Duration(stsResp.ExpiresIn) * time.Second)
342+// newSubjectTokenProvider determines the type of credsfile.CredentialSource needed to create a
343+// subjectTokenProvider
344+func newSubjectTokenProvider(o *Options) (subjectTokenProvider, error) {
345+ logger := internallog.New(o.Logger)
346+ reqOpts := &RequestOptions{Audience: o.Audience, SubjectTokenType: o.SubjectTokenType}
347+ if o.AwsSecurityCredentialsProvider != nil {
348+ return &awsSubjectProvider{
349+ securityCredentialsProvider: o.AwsSecurityCredentialsProvider,
350+ TargetResource: o.Audience,
354+ } else if o.SubjectTokenProvider != nil {
355+ return &programmaticProvider{stp: o.SubjectTokenProvider, opts: reqOpts}, nil
356+ } else if len(o.CredentialSource.EnvironmentID) > 3 && o.CredentialSource.EnvironmentID[:3] == "aws" {
357+ if awsVersion, err := strconv.Atoi(o.CredentialSource.EnvironmentID[3:]); err == nil {
358+ if awsVersion != 1 {
359+ return nil, fmt.Errorf("credentials: aws version '%d' is not supported in the current build", awsVersion)
362+ awsProvider := &awsSubjectProvider{
363+ EnvironmentID: o.CredentialSource.EnvironmentID,
364+ RegionURL: o.CredentialSource.RegionURL,
365+ RegionalCredVerificationURL: o.CredentialSource.RegionalCredVerificationURL,
366+ CredVerificationURL: o.CredentialSource.URL,
367+ TargetResource: o.Audience,
371+ if o.CredentialSource.IMDSv2SessionTokenURL != "" {
372+ awsProvider.IMDSv2SessionTokenURL = o.CredentialSource.IMDSv2SessionTokenURL
375+ return awsProvider, nil
377+ } else if o.CredentialSource.File != "" {
378+ return &fileSubjectProvider{File: o.CredentialSource.File, Format: o.CredentialSource.Format}, nil
379+ } else if o.CredentialSource.URL != "" {
380+ return &urlSubjectProvider{
381+ URL: o.CredentialSource.URL,
382+ Headers: o.CredentialSource.Headers,
383+ Format: o.CredentialSource.Format,
387+ } else if o.CredentialSource.Executable != nil {
388+ ec := o.CredentialSource.Executable
389+ if ec.Command == "" {
390+ return nil, errors.New("credentials: missing `command` field — executable command must be provided")
393+ execProvider := &executableSubjectProvider{}
394+ execProvider.Command = ec.Command
395+ if ec.TimeoutMillis == 0 {
396+ execProvider.Timeout = executableDefaultTimeout
398+ execProvider.Timeout = time.Duration(ec.TimeoutMillis) * time.Millisecond
399+ if execProvider.Timeout < timeoutMinimum || execProvider.Timeout > timeoutMaximum {
400+ return nil, fmt.Errorf("credentials: invalid `timeout_millis` field — executable timeout must be between %v and %v seconds", timeoutMinimum.Seconds(), timeoutMaximum.Seconds())
403+ execProvider.OutputFile = ec.OutputFile
404+ execProvider.client = o.Client
405+ execProvider.opts = o
406+ execProvider.env = runtimeEnvironment{}
407+ return execProvider, nil
408+ } else if o.CredentialSource.Certificate != nil {
409+ cert := o.CredentialSource.Certificate
410+ if !cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation == "" {
411+ return nil, errors.New("credentials: \"certificate\" object must either specify a certificate_config_location or use_default_certificate_config should be true")
413+ if cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation != "" {
414+ return nil, errors.New("credentials: \"certificate\" object cannot specify both a certificate_config_location and use_default_certificate_config=true")
416+ return &x509Provider{}, nil
418+ return nil, errors.New("credentials: unable to parse credential source")
421+func getGoogHeaderValue(conf *Options, p subjectTokenProvider) string {
422+ return fmt.Sprintf("gl-go/%s auth/%s google-byoid-sdk source/%s sa-impersonation/%t config-lifetime/%t",
426+ conf.ServiceAccountImpersonationURL != "",
427+ conf.ServiceAccountImpersonationLifetimeSeconds != 0)
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/file_provider.gonew file+78
@@ -0,0 +1,78 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
15+package externalaccount
25+ "cloud.google.com/go/auth/internal"
26+ "cloud.google.com/go/auth/internal/credsfile"
30+ fileProviderType = "file"
33+type fileSubjectProvider struct {
35+ Format *credsfile.Format
38+func (sp *fileSubjectProvider) subjectToken(context.Context) (string, error) {
39+ tokenFile, err := os.Open(sp.File)
41+ return "", fmt.Errorf("credentials: failed to open credential file %q: %w", sp.File, err)
43+ defer tokenFile.Close()
44+ tokenBytes, err := internal.ReadAll(tokenFile)
46+ return "", fmt.Errorf("credentials: failed to read credential file: %w", err)
48+ tokenBytes = bytes.TrimSpace(tokenBytes)
50+ if sp.Format == nil {
51+ return string(tokenBytes), nil
53+ switch sp.Format.Type {
55+ jsonData := make(map[string]interface{})
56+ err = json.Unmarshal(tokenBytes, &jsonData)
58+ return "", fmt.Errorf("credentials: failed to unmarshal subject token file: %w", err)
60+ val, ok := jsonData[sp.Format.SubjectTokenFieldName]
62+ return "", errors.New("credentials: provided subject_token_field_name not found in credentials")
64+ token, ok := val.(string)
66+ return "", errors.New("credentials: improperly formatted subject token")
70+ return string(tokenBytes), nil
72+ return "", errors.New("credentials: invalid credential_source file format type: " + sp.Format.Type)
76+func (sp *fileSubjectProvider) providerType() string {
77+ return fileProviderType
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/info.gonew file+74
@@ -0,0 +1,74 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
15+package externalaccount
24+ // version is a package internal global variable for testing purposes.
25+ version = runtime.Version
28+// versionUnknown is only used when the runtime version cannot be determined.
29+const versionUnknown = "UNKNOWN"
31+// goVersion returns a Go runtime version derived from the runtime environment
32+// that is modified to be suitable for reporting in a header, meaning it has no
33+// whitespace. If it is unable to determine the Go runtime version, it returns
35+func goVersion() string {
36+ const develPrefix = "devel +"
39+ if strings.HasPrefix(s, develPrefix) {
40+ s = s[len(develPrefix):]
41+ if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
45+ } else if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
49+ notSemverRune := func(r rune) bool {
50+ return !strings.ContainsRune("0123456789.", r)
53+ if strings.HasPrefix(s, "go1") {
55+ var prerelease string
56+ if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
57+ s, prerelease = s[:p], s[p:]
59+ if strings.HasSuffix(s, ".") {
61+ } else if strings.Count(s, ".") < 2 {
64+ if prerelease != "" {
65+ // Some release candidates already have a dash in them.
66+ if !strings.HasPrefix(prerelease, "-") {
67+ prerelease = "-" + prerelease
73+ return versionUnknown
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/programmatic_provider.gonew file+30
@@ -0,0 +1,30 @@
1+// Copyright 2024 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
15+package externalaccount
19+type programmaticProvider struct {
21+ stp SubjectTokenProvider
24+func (pp *programmaticProvider) providerType() string {
25+ return programmaticProviderType
28+func (pp *programmaticProvider) subjectToken(ctx context.Context) (string, error) {
29+ return pp.stp.SubjectToken(ctx, pp.opts)
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/url_provider.gonew file+93
@@ -0,0 +1,93 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
15+package externalaccount
25+ "cloud.google.com/go/auth/internal"
26+ "cloud.google.com/go/auth/internal/credsfile"
27+ "github.com/googleapis/gax-go/v2/internallog"
31+ fileTypeText = "text"
32+ fileTypeJSON = "json"
33+ urlProviderType = "url"
34+ programmaticProviderType = "programmatic"
35+ x509ProviderType = "x509"
38+type urlSubjectProvider struct {
40+ Headers map[string]string
41+ Format *credsfile.Format
46+func (sp *urlSubjectProvider) subjectToken(ctx context.Context) (string, error) {
47+ req, err := http.NewRequestWithContext(ctx, "GET", sp.URL, nil)
49+ return "", fmt.Errorf("credentials: HTTP request for URL-sourced credential failed: %w", err)
52+ for key, val := range sp.Headers {
53+ req.Header.Add(key, val)
55+ sp.Logger.DebugContext(ctx, "url subject token request", "request", internallog.HTTPRequest(req, nil))
56+ resp, body, err := internal.DoRequest(sp.Client, req)
58+ return "", fmt.Errorf("credentials: invalid response when retrieving subject token: %w", err)
60+ sp.Logger.DebugContext(ctx, "url subject token response", "response", internallog.HTTPResponse(resp, body))
61+ if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
62+ return "", fmt.Errorf("credentials: status code %d: %s", c, body)
65+ if sp.Format == nil {
66+ return string(body), nil
68+ switch sp.Format.Type {
70+ jsonData := make(map[string]interface{})
71+ err = json.Unmarshal(body, &jsonData)
73+ return "", fmt.Errorf("credentials: failed to unmarshal subject token file: %w", err)
75+ val, ok := jsonData[sp.Format.SubjectTokenFieldName]
77+ return "", errors.New("credentials: provided subject_token_field_name not found in credentials")
79+ token, ok := val.(string)
81+ return "", errors.New("credentials: improperly formatted subject token")
85+ return string(body), nil
87+ return "", errors.New("credentials: invalid credential_source file format type: " + sp.Format.Type)
91+func (sp *urlSubjectProvider) providerType() string {
92+ return urlProviderType
vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/x509_provider.gonew file+63
@@ -0,0 +1,63 @@
1+// Copyright 2024 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
15+package externalaccount
23+ "cloud.google.com/go/auth/internal/transport/cert"
26+// x509Provider implements the subjectTokenProvider type for
27+// x509 workload identity credentials. Because x509 credentials
28+// rely on an mTLS connection to represent the 3rd party identity
29+// rather than a subject token, this provider will always return
30+// an empty string when a subject token is requested by the external account
32+type x509Provider struct {
35+func (xp *x509Provider) providerType() string {
36+ return x509ProviderType
39+func (xp *x509Provider) subjectToken(ctx context.Context) (string, error) {
43+// createX509Client creates a new client that is configured with mTLS, using the
44+// certificate configuration specified in the credential source.
45+func createX509Client(certificateConfigLocation string) (*http.Client, error) {
46+ certProvider, err := cert.NewWorkloadX509CertProvider(certificateConfigLocation)
50+ trans := http.DefaultTransport.(*http.Transport).Clone()
52+ trans.TLSClientConfig = &tls.Config{
53+ GetClientCertificate: certProvider,
56+ // Create a client with default settings plus the X509 workload cert and key.
57+ client := &http.Client{
59+ Timeout: 30 * time.Second,
vendor/cloud.google.com/go/auth/credentials/internal/externalaccountuser/externalaccountuser.gonew file+115
@@ -0,0 +1,115 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
15+package externalaccountuser
24+ "cloud.google.com/go/auth"
25+ "cloud.google.com/go/auth/credentials/internal/stsexchange"
26+ "cloud.google.com/go/auth/internal"
27+ "github.com/googleapis/gax-go/v2/internallog"
30+// Options stores the configuration for fetching tokens with external authorized
33+ // Audience is the Secure Token Service (STS) audience which contains the
34+ // resource name for the workforce pool and the provider identifier in that
37+ // RefreshToken is the OAuth 2.0 refresh token.
39+ // TokenURL is the STS token exchange endpoint for refresh.
41+ // TokenInfoURL is the STS endpoint URL for token introspection. Optional.
43+ // ClientID is only required in conjunction with ClientSecret, as described
46+ // ClientSecret is currently only required if token_info endpoint also needs
47+ // to be called with the generated a cloud access token. When provided, STS
48+ // will be called with additional basic authentication using client_id as
49+ // username and client_secret as password.
51+ // Scopes contains the desired scopes for the returned access token.
54+ // Client for token request.
56+ // Logger for logging.
60+func (c *Options) validate() bool {
61+ return c.ClientID != "" && c.ClientSecret != "" && c.RefreshToken != "" && c.TokenURL != ""
64+// NewTokenProvider returns a [cloud.google.com/go/auth.TokenProvider]
65+// configured with the provided options.
66+func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
67+ if !opts.validate() {
68+ return nil, errors.New("credentials: invalid external_account_authorized_user configuration")
71+ tp := &tokenProvider{
74+ return auth.NewCachedTokenProvider(tp, nil), nil
77+type tokenProvider struct {
81+func (tp *tokenProvider) Token(ctx context.Context) (*auth.Token, error) {
84+ clientAuth := stsexchange.ClientAuthentication{
85+ AuthStyle: auth.StyleInHeader,
86+ ClientID: opts.ClientID,
87+ ClientSecret: opts.ClientSecret,
89+ headers := make(http.Header)
90+ headers.Set("Content-Type", "application/x-www-form-urlencoded")
91+ stsResponse, err := stsexchange.RefreshAccessToken(ctx, &stsexchange.Options{
93+ Endpoint: opts.TokenURL,
94+ RefreshToken: opts.RefreshToken,
95+ Authentication: clientAuth,
97+ Logger: internallog.New(tp.o.Logger),
102+ if stsResponse.ExpiresIn < 0 {
103+ return nil, errors.New("credentials: invalid expiry from security token service")
106+ // guarded by the wrapping with CachedTokenProvider
107+ if stsResponse.RefreshToken != "" {
108+ opts.RefreshToken = stsResponse.RefreshToken
111+ Value: stsResponse.AccessToken,
112+ Expiry: time.Now().UTC().Add(time.Duration(stsResponse.ExpiresIn) * time.Second),
113+ Type: internal.TokenTypeBearer,
vendor/cloud.google.com/go/auth/credentials/internal/gdch/gdch.gonew file+191
@@ -0,0 +1,191 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
32+ "cloud.google.com/go/auth"
33+ "cloud.google.com/go/auth/internal"
34+ "cloud.google.com/go/auth/internal/credsfile"
35+ "cloud.google.com/go/auth/internal/jwt"
36+ "github.com/googleapis/gax-go/v2/internallog"
40+ // GrantType is the grant type for the token request.
41+ GrantType = "urn:ietf:params:oauth:token-type:token-exchange"
42+ requestTokenType = "urn:ietf:params:oauth:token-type:access_token"
43+ subjectTokenType = "urn:k8s:params:oauth:token-type:serviceaccount"
47+ gdchSupportFormatVersions map[string]bool = map[string]bool{
52+// Options for [NewTokenProvider].
59+// NewTokenProvider returns a [cloud.google.com/go/auth.TokenProvider] from a
61+func NewTokenProvider(f *credsfile.GDCHServiceAccountFile, o *Options) (auth.TokenProvider, error) {
62+ if !gdchSupportFormatVersions[f.FormatVersion] {
63+ return nil, fmt.Errorf("credentials: unsupported gdch_service_account format %q", f.FormatVersion)
65+ if o.STSAudience == "" {
66+ return nil, errors.New("credentials: STSAudience must be set for the GDCH auth flows")
68+ signer, err := internal.ParseKey([]byte(f.PrivateKey))
72+ certPool, err := loadCertPool(f.CertPath)
78+ serviceIdentity: fmt.Sprintf("system:serviceaccount:%s:%s", f.Project, f.Name),
79+ tokenURL: f.TokenURL,
82+ pkID: f.PrivateKeyID,
85+ logger: internallog.New(o.Logger),
90+func loadCertPool(path string) (*x509.CertPool, error) {
91+ pool := x509.NewCertPool()
92+ pem, err := os.ReadFile(path)
94+ return nil, fmt.Errorf("credentials: failed to read certificate: %w", err)
96+ pool.AppendCertsFromPEM(pem)
100+type gdchProvider struct {
101+ serviceIdentity string
104+ signer crypto.Signer
106+ certPool *x509.CertPool
112+func (g gdchProvider) Token(ctx context.Context) (*auth.Token, error) {
113+ addCertToTransport(g.client, g.certPool)
115+ exp := iat.Add(time.Hour)
116+ claims := jwt.Claims{
117+ Iss: g.serviceIdentity,
118+ Sub: g.serviceIdentity,
124+ Algorithm: jwt.HeaderAlgRSA256,
125+ Type: jwt.HeaderType,
126+ KeyID: string(g.pkID),
128+ payload, err := jwt.EncodeJWS(&h, &claims, g.signer)
133+ v.Set("grant_type", GrantType)
134+ v.Set("audience", g.aud)
135+ v.Set("requested_token_type", requestTokenType)
136+ v.Set("subject_token", payload)
137+ v.Set("subject_token_type", subjectTokenType)
139+ req, err := http.NewRequestWithContext(ctx, "POST", g.tokenURL, strings.NewReader(v.Encode()))
143+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
144+ g.logger.DebugContext(ctx, "gdch token request", "request", internallog.HTTPRequest(req, []byte(v.Encode())))
145+ resp, body, err := internal.DoRequest(g.client, req)
147+ return nil, fmt.Errorf("credentials: cannot fetch token: %w", err)
149+ g.logger.DebugContext(ctx, "gdch token response", "response", internallog.HTTPResponse(resp, body))
150+ if c := resp.StatusCode; c < http.StatusOK || c > http.StatusMultipleChoices {
151+ return nil, &auth.Error{
157+ var tokenRes struct {
158+ AccessToken string `json:"access_token"`
159+ TokenType string `json:"token_type"`
160+ ExpiresIn int64 `json:"expires_in"` // relative seconds from now
162+ if err := json.Unmarshal(body, &tokenRes); err != nil {
163+ return nil, fmt.Errorf("credentials: cannot fetch token: %w", err)
165+ token := &auth.Token{
166+ Value: tokenRes.AccessToken,
167+ Type: tokenRes.TokenType,
169+ raw := make(map[string]interface{})
170+ json.Unmarshal(body, &raw) // no error checks for optional fields
171+ token.Metadata = raw
173+ if secs := tokenRes.ExpiresIn; secs > 0 {
174+ token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
179+// addCertToTransport makes a best effort attempt at adding in the cert info to
180+// the client. It tries to keep all configured transport settings if the
181+// underlying transport is an http.Transport. Or else it overwrites the
182+// transport with defaults adding in the certs.
183+func addCertToTransport(hc *http.Client, certPool *x509.CertPool) {
184+ trans, ok := hc.Transport.(*http.Transport)
186+ trans = http.DefaultTransport.(*http.Transport).Clone()
188+ trans.TLSClientConfig = &tls.Config{
vendor/cloud.google.com/go/auth/credentials/internal/impersonate/impersonate.gonew file+156
@@ -0,0 +1,156 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
27+ "cloud.google.com/go/auth"
28+ "cloud.google.com/go/auth/internal"
29+ "github.com/googleapis/gax-go/v2/internallog"
33+ defaultTokenLifetime = "3600s"
34+ authHeaderKey = "Authorization"
37+// generateAccesstokenReq is used for service account impersonation
38+type generateAccessTokenReq struct {
39+ Delegates []string `json:"delegates,omitempty"`
40+ Lifetime string `json:"lifetime,omitempty"`
41+ Scope []string `json:"scope,omitempty"`
44+type impersonateTokenResponse struct {
45+ AccessToken string `json:"accessToken"`
46+ ExpireTime string `json:"expireTime"`
49+// NewTokenProvider uses a source credential, stored in Ts, to request an access token to the provided URL.
50+// Scopes can be defined when the access token is requested.
51+func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
52+ if err := opts.validate(); err != nil {
58+// Options for [NewTokenProvider].
60+ // Tp is the source credential used to generate a token on the
61+ // impersonated service account. Required.
62+ Tp auth.TokenProvider
64+ // URL is the endpoint to call to generate a token
65+ // on behalf of the service account. Required.
67+ // Scopes that the impersonated credential should have. Required.
69+ // Delegates are the service account email addresses in a delegation chain.
70+ // Each service account must be granted roles/iam.serviceAccountTokenCreator
71+ // on the next service account in the chain. Optional.
73+ // TokenLifetimeSeconds is the number of seconds the impersonation token will
74+ // be valid for. Defaults to 1 hour if unset. Optional.
75+ TokenLifetimeSeconds int
76+ // Client configures the underlying client used to make network requests
77+ // when fetching tokens. Required.
79+ // Logger is used for debug logging. If provided, logging will be enabled
80+ // at the loggers configured level. By default logging is disabled unless
81+ // enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
82+ // logger will be used. Optional.
86+func (o *Options) validate() error {
88+ return errors.New("credentials: missing required 'source_credentials' field in impersonated credentials")
91+ return errors.New("credentials: missing required 'service_account_impersonation_url' field in impersonated credentials")
96+// Token performs the exchange to get a temporary service account token to allow access to GCP.
97+func (o *Options) Token(ctx context.Context) (*auth.Token, error) {
98+ logger := internallog.New(o.Logger)
99+ lifetime := defaultTokenLifetime
100+ if o.TokenLifetimeSeconds != 0 {
101+ lifetime = fmt.Sprintf("%ds", o.TokenLifetimeSeconds)
103+ reqBody := generateAccessTokenReq{
106+ Delegates: o.Delegates,
108+ b, err := json.Marshal(reqBody)
110+ return nil, fmt.Errorf("credentials: unable to marshal request: %w", err)
112+ req, err := http.NewRequestWithContext(ctx, "POST", o.URL, bytes.NewReader(b))
114+ return nil, fmt.Errorf("credentials: unable to create impersonation request: %w", err)
116+ req.Header.Set("Content-Type", "application/json")
117+ if err := setAuthHeader(ctx, o.Tp, req); err != nil {
120+ logger.DebugContext(ctx, "impersonated token request", "request", internallog.HTTPRequest(req, b))
121+ resp, body, err := internal.DoRequest(o.Client, req)
123+ return nil, fmt.Errorf("credentials: unable to generate access token: %w", err)
125+ logger.DebugContext(ctx, "impersonated token response", "response", internallog.HTTPResponse(resp, body))
126+ if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
127+ return nil, fmt.Errorf("credentials: status code %d: %s", c, body)
130+ var accessTokenResp impersonateTokenResponse
131+ if err := json.Unmarshal(body, &accessTokenResp); err != nil {
132+ return nil, fmt.Errorf("credentials: unable to parse response: %w", err)
134+ expiry, err := time.Parse(time.RFC3339, accessTokenResp.ExpireTime)
136+ return nil, fmt.Errorf("credentials: unable to parse expiry: %w", err)
139+ Value: accessTokenResp.AccessToken,
141+ Type: internal.TokenTypeBearer,
145+func setAuthHeader(ctx context.Context, tp auth.TokenProvider, r *http.Request) error {
146+ t, err := tp.Token(ctx)
152+ typ = internal.TokenTypeBearer
154+ r.Header.Set(authHeaderKey, typ+" "+t.Value)
vendor/cloud.google.com/go/auth/credentials/internal/stsexchange/sts_exchange.gonew file+167
@@ -0,0 +1,167 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
28+ "cloud.google.com/go/auth"
29+ "cloud.google.com/go/auth/internal"
30+ "github.com/googleapis/gax-go/v2/internallog"
34+ // GrantType for a sts exchange.
35+ GrantType = "urn:ietf:params:oauth:grant-type:token-exchange"
36+ // TokenType for a sts exchange.
37+ TokenType = "urn:ietf:params:oauth:token-type:access_token"
39+ jwtTokenType = "urn:ietf:params:oauth:token-type:jwt"
42+// Options stores the configuration for making an sts exchange request.
47+ Request *TokenRequest
48+ Authentication ClientAuthentication
50+ // ExtraOpts are optional fields marshalled into the `options` field of the
52+ ExtraOpts map[string]interface{}
56+// RefreshAccessToken performs the token exchange using a refresh token flow.
57+func RefreshAccessToken(ctx context.Context, opts *Options) (*TokenResponse, error) {
59+ data.Set("grant_type", "refresh_token")
60+ data.Set("refresh_token", opts.RefreshToken)
61+ return doRequest(ctx, opts, data)
64+// ExchangeToken performs an oauth2 token exchange with the provided endpoint.
65+func ExchangeToken(ctx context.Context, opts *Options) (*TokenResponse, error) {
67+ data.Set("audience", opts.Request.Audience)
68+ data.Set("grant_type", GrantType)
69+ data.Set("requested_token_type", TokenType)
70+ data.Set("subject_token_type", opts.Request.SubjectTokenType)
71+ data.Set("subject_token", opts.Request.SubjectToken)
72+ data.Set("scope", strings.Join(opts.Request.Scope, " "))
73+ if opts.ExtraOpts != nil {
74+ opts, err := json.Marshal(opts.ExtraOpts)
76+ return nil, fmt.Errorf("credentials: failed to marshal additional options: %w", err)
78+ data.Set("options", string(opts))
80+ return doRequest(ctx, opts, data)
83+func doRequest(ctx context.Context, opts *Options, data url.Values) (*TokenResponse, error) {
84+ opts.Authentication.InjectAuthentication(data, opts.Headers)
85+ encodedData := data.Encode()
86+ logger := internallog.New(opts.Logger)
88+ req, err := http.NewRequestWithContext(ctx, "POST", opts.Endpoint, strings.NewReader(encodedData))
90+ return nil, fmt.Errorf("credentials: failed to properly build http request: %w", err)
93+ for key, list := range opts.Headers {
94+ for _, val := range list {
95+ req.Header.Add(key, val)
98+ req.Header.Set("Content-Length", strconv.Itoa(len(encodedData)))
100+ logger.DebugContext(ctx, "sts token request", "request", internallog.HTTPRequest(req, []byte(encodedData)))
101+ resp, body, err := internal.DoRequest(opts.Client, req)
103+ return nil, fmt.Errorf("credentials: invalid response from Secure Token Server: %w", err)
105+ logger.DebugContext(ctx, "sts token response", "response", internallog.HTTPResponse(resp, body))
106+ if c := resp.StatusCode; c < http.StatusOK || c > http.StatusMultipleChoices {
107+ return nil, fmt.Errorf("credentials: status code %d: %s", c, body)
109+ var stsResp TokenResponse
110+ if err := json.Unmarshal(body, &stsResp); err != nil {
111+ return nil, fmt.Errorf("credentials: failed to unmarshal response body from Secure Token Server: %w", err)
114+ return &stsResp, nil
117+// TokenRequest contains fields necessary to make an oauth2 token
119+type TokenRequest struct {
120+ ActingParty struct {
122+ ActorTokenType string
128+ RequestedTokenType string
130+ SubjectTokenType string
133+// TokenResponse is used to decode the remote server response during
134+// an oauth2 token exchange.
135+type TokenResponse struct {
136+ AccessToken string `json:"access_token"`
137+ IssuedTokenType string `json:"issued_token_type"`
138+ TokenType string `json:"token_type"`
139+ ExpiresIn int `json:"expires_in"`
140+ Scope string `json:"scope"`
141+ RefreshToken string `json:"refresh_token"`
144+// ClientAuthentication represents an OAuth client ID and secret and the
145+// mechanism for passing these credentials as stated in rfc6749#2.3.1.
146+type ClientAuthentication struct {
147+ AuthStyle auth.Style
152+// InjectAuthentication is used to add authentication to a Secure Token Service
153+// exchange request. It modifies either the passed url.Values or http.Header
154+// depending on the desired authentication format.
155+func (c *ClientAuthentication) InjectAuthentication(values url.Values, headers http.Header) {
156+ if c.ClientID == "" || c.ClientSecret == "" || values == nil || headers == nil {
159+ switch c.AuthStyle {
160+ case auth.StyleInHeader:
161+ plainHeader := c.ClientID + ":" + c.ClientSecret
162+ headers.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(plainHeader)))
164+ values.Set("client_id", c.ClientID)
165+ values.Set("client_secret", c.ClientSecret)
vendor/cloud.google.com/go/auth/credentials/selfsignedjwt.gonew file+89
@@ -0,0 +1,89 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
26+ "cloud.google.com/go/auth"
27+ "cloud.google.com/go/auth/internal"
28+ "cloud.google.com/go/auth/internal/credsfile"
29+ "cloud.google.com/go/auth/internal/jwt"
34+ now func() time.Time = time.Now
37+// configureSelfSignedJWT uses the private key in the service account to create
38+// a JWT without making a network call.
39+func configureSelfSignedJWT(f *credsfile.ServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
40+ if len(opts.scopes()) == 0 && opts.Audience == "" {
41+ return nil, errors.New("credentials: both scopes and audience are empty")
43+ signer, err := internal.ParseKey([]byte(f.PrivateKey))
45+ return nil, fmt.Errorf("credentials: could not parse key: %w", err)
47+ return &selfSignedTokenProvider{
48+ email: f.ClientEmail,
49+ audience: opts.Audience,
50+ scopes: opts.scopes(),
52+ pkID: f.PrivateKeyID,
53+ logger: opts.logger(),
57+type selfSignedTokenProvider struct {
66+func (tp *selfSignedTokenProvider) Token(context.Context) (*auth.Token, error) {
68+ exp := iat.Add(time.Hour)
69+ scope := strings.Join(tp.scopes, " ")
79+ Algorithm: jwt.HeaderAlgRSA256,
80+ Type: jwt.HeaderType,
81+ KeyID: string(tp.pkID),
83+ tok, err := jwt.EncodeJWS(h, c, tp.signer)
85+ return nil, fmt.Errorf("credentials: could not encode JWT: %w", err)
87+ tp.logger.Debug("created self-signed JWT", "token", tok)
88+ return &auth.Token{Value: tok, Type: internal.TokenTypeBearer, Expiry: exp}, nil
vendor/cloud.google.com/go/auth/httptransport/httptransport.gonew file+247
@@ -0,0 +1,247 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
15+// Package httptransport provides functionality for managing HTTP client
16+// connections to Google Cloud services.
26+ "cloud.google.com/go/auth"
27+ detect "cloud.google.com/go/auth/credentials"
28+ "cloud.google.com/go/auth/internal"
29+ "cloud.google.com/go/auth/internal/transport"
30+ "github.com/googleapis/gax-go/v2/internallog"
33+// ClientCertProvider is a function that returns a TLS client certificate to be
34+// used when opening TLS connections. It follows the same semantics as
35+// [crypto/tls.Config.GetClientCertificate].
36+type ClientCertProvider = func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
38+// Options used to configure a [net/http.Client] from [NewClient].
40+ // DisableTelemetry disables default telemetry (OpenTelemetry). An example
41+ // reason to do so would be to bind custom telemetry that overrides the
43+ DisableTelemetry bool
44+ // DisableAuthentication specifies that no authentication should be used. It
45+ // is suitable only for testing and for accessing public resources, like
46+ // public Google Cloud Storage buckets.
47+ DisableAuthentication bool
48+ // Headers are extra HTTP headers that will be appended to every outgoing
51+ // BaseRoundTripper overrides the base transport used for serving requests.
52+ // If specified ClientCertProvider is ignored.
53+ BaseRoundTripper http.RoundTripper
54+ // Endpoint overrides the default endpoint to be used for a service.
56+ // APIKey specifies an API key to be used as the basis for authentication.
57+ // If set DetectOpts are ignored.
59+ // Credentials used to add Authorization header to all requests. If set
60+ // DetectOpts are ignored.
61+ Credentials *auth.Credentials
62+ // ClientCertProvider is a function that returns a TLS client certificate to
63+ // be used when opening TLS connections. It follows the same semantics as
64+ // crypto/tls.Config.GetClientCertificate.
65+ ClientCertProvider ClientCertProvider
66+ // DetectOpts configures settings for detect Application Default
68+ DetectOpts *detect.DetectOptions
69+ // UniverseDomain is the default service domain for a given Cloud universe.
70+ // The default value is "googleapis.com". This is the universe domain
71+ // configured for the client, which will be compared to the universe domain
72+ // that is separately configured for the credentials.
73+ UniverseDomain string
74+ // Logger is used for debug logging. If provided, logging will be enabled
75+ // at the loggers configured level. By default logging is disabled unless
76+ // enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
77+ // logger will be used. Optional.
80+ // InternalOptions are NOT meant to be set directly by consumers of this
81+ // package, they should only be set by generated client code.
82+ InternalOptions *InternalOptions
85+func (o *Options) validate() error {
87+ return errors.New("httptransport: opts required to be non-nil")
89+ if o.InternalOptions != nil && o.InternalOptions.SkipValidation {
92+ hasCreds := o.APIKey != "" ||
93+ o.Credentials != nil ||
94+ (o.DetectOpts != nil && len(o.DetectOpts.CredentialsJSON) > 0) ||
95+ (o.DetectOpts != nil && o.DetectOpts.CredentialsFile != "")
96+ if o.DisableAuthentication && hasCreds {
97+ return errors.New("httptransport: DisableAuthentication is incompatible with options that set or detect credentials")
102+// client returns the client a user set for the detect options or nil if one was
104+func (o *Options) client() *http.Client {
105+ if o.DetectOpts != nil && o.DetectOpts.Client != nil {
106+ return o.DetectOpts.Client
111+func (o *Options) logger() *slog.Logger {
112+ return internallog.New(o.Logger)
115+func (o *Options) resolveDetectOptions() *detect.DetectOptions {
116+ io := o.InternalOptions
117+ // soft-clone these so we are not updating a ref the user holds and may reuse
118+ do := transport.CloneDetectOptions(o.DetectOpts)
120+ // If scoped JWTs are enabled user provided an aud, allow self-signed JWT.
121+ if (io != nil && io.EnableJWTWithScope) || do.Audience != "" {
122+ do.UseSelfSignedJWT = true
124+ // Only default scopes if user did not also set an audience.
125+ if len(do.Scopes) == 0 && do.Audience == "" && io != nil && len(io.DefaultScopes) > 0 {
126+ do.Scopes = make([]string, len(io.DefaultScopes))
127+ copy(do.Scopes, io.DefaultScopes)
129+ if len(do.Scopes) == 0 && do.Audience == "" && io != nil {
130+ do.Audience = o.InternalOptions.DefaultAudience
132+ if o.ClientCertProvider != nil {
133+ tlsConfig := &tls.Config{
134+ GetClientCertificate: o.ClientCertProvider,
136+ do.Client = transport.DefaultHTTPClientWithTLS(tlsConfig)
137+ do.TokenURL = detect.GoogleMTLSTokenURL
139+ if do.Logger == nil {
140+ do.Logger = o.logger()
145+// InternalOptions are only meant to be set by generated client code. These are
146+// not meant to be set directly by consumers of this package. Configuration in
147+// this type is considered EXPERIMENTAL and may be removed at any time in the
148+// future without warning.
149+type InternalOptions struct {
150+ // EnableJWTWithScope specifies if scope can be used with self-signed JWT.
151+ EnableJWTWithScope bool
152+ // DefaultAudience specifies a default audience to be used as the audience
153+ // field ("aud") for the JWT token authentication.
154+ DefaultAudience string
155+ // DefaultEndpointTemplate combined with UniverseDomain specifies the
156+ // default endpoint.
157+ DefaultEndpointTemplate string
158+ // DefaultMTLSEndpoint specifies the default mTLS endpoint.
159+ DefaultMTLSEndpoint string
160+ // DefaultScopes specifies the default OAuth2 scopes to be used for a
162+ DefaultScopes []string
163+ // SkipValidation bypasses validation on Options. It should only be used
164+ // internally for clients that need more control over their transport.
166+ // SkipUniverseDomainValidation skips the verification that the universe
167+ // domain configured for the client matches the universe domain configured
168+ // for the credentials. It should only be used internally for clients that
169+ // need more control over their transport. The default is false.
170+ SkipUniverseDomainValidation bool
173+// AddAuthorizationMiddleware adds a middleware to the provided client's
174+// transport that sets the Authorization header with the value produced by the
175+// provided [cloud.google.com/go/auth.Credentials]. An error is returned only
176+// if client or creds is nil.
178+// This function does not support setting a universe domain value on the client.
179+func AddAuthorizationMiddleware(client *http.Client, creds *auth.Credentials) error {
180+ if client == nil || creds == nil {
181+ return fmt.Errorf("httptransport: client and tp must not be nil")
183+ base := client.Transport
185+ if dt, ok := http.DefaultTransport.(*http.Transport); ok {
188+ // Directly reuse the DefaultTransport if the application has
189+ // replaced it with an implementation of RoundTripper other than
191+ base = http.DefaultTransport
194+ client.Transport = &authTransport{
201+// NewClient returns a [net/http.Client] that can be used to communicate with a
202+// Google cloud service, configured with the provided [Options]. It
203+// automatically appends Authorization headers to all outgoing requests.
204+func NewClient(opts *Options) (*http.Client, error) {
205+ if err := opts.validate(); err != nil {
209+ tOpts := &transport.Options{
210+ Endpoint: opts.Endpoint,
211+ ClientCertProvider: opts.ClientCertProvider,
212+ Client: opts.client(),
213+ UniverseDomain: opts.UniverseDomain,
214+ Logger: opts.logger(),
216+ if io := opts.InternalOptions; io != nil {
217+ tOpts.DefaultEndpointTemplate = io.DefaultEndpointTemplate
218+ tOpts.DefaultMTLSEndpoint = io.DefaultMTLSEndpoint
220+ clientCertProvider, dialTLSContext, err := transport.GetHTTPTransportConfig(tOpts)
224+ baseRoundTripper := opts.BaseRoundTripper
225+ if baseRoundTripper == nil {
226+ baseRoundTripper = defaultBaseTransport(clientCertProvider, dialTLSContext)
228+ // Ensure the token exchange transport uses the same ClientCertProvider as the API transport.
229+ opts.ClientCertProvider = clientCertProvider
230+ trans, err := newTransport(baseRoundTripper, opts)
234+ return &http.Client{
239+// SetAuthHeader uses the provided token to set the Authorization header on a
240+// request. If the token.Type is empty, the type is assumed to be Bearer.
241+func SetAuthHeader(token *auth.Token, req *http.Request) {
244+ typ = internal.TokenTypeBearer
246+ req.Header.Set("Authorization", typ+" "+token.Value)
vendor/cloud.google.com/go/auth/httptransport/transport.gonew file+234
@@ -0,0 +1,234 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
25+ "cloud.google.com/go/auth"
26+ "cloud.google.com/go/auth/credentials"
27+ "cloud.google.com/go/auth/internal"
28+ "cloud.google.com/go/auth/internal/transport"
29+ "cloud.google.com/go/auth/internal/transport/cert"
30+ "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
31+ "golang.org/x/net/http2"
35+ quotaProjectHeaderKey = "X-goog-user-project"
38+func newTransport(base http.RoundTripper, opts *Options) (http.RoundTripper, error) {
39+ var headers = opts.Headers
40+ ht := &headerTransport{
44+ var trans http.RoundTripper = ht
45+ trans = addOpenTelemetryTransport(trans, opts)
47+ case opts.DisableAuthentication:
49+ case opts.APIKey != "":
50+ qp := internal.GetQuotaProject(nil, opts.Headers.Get(quotaProjectHeaderKey))
53+ headers = make(map[string][]string, 1)
55+ headers.Set(quotaProjectHeaderKey, qp)
57+ trans = &apiKeyTransport{
62+ var creds *auth.Credentials
63+ if opts.Credentials != nil {
64+ creds = opts.Credentials
67+ creds, err = credentials.DetectDefault(opts.resolveDetectOptions())
72+ qp, err := creds.QuotaProjectID(context.Background())
78+ headers = make(map[string][]string, 1)
80+ // Don't overwrite user specified quota
81+ if v := headers.Get(quotaProjectHeaderKey); v == "" {
82+ headers.Set(quotaProjectHeaderKey, qp)
86+ if iOpts := opts.InternalOptions; iOpts != nil {
87+ skipUD = iOpts.SkipUniverseDomainValidation
89+ creds.TokenProvider = auth.NewCachedTokenProvider(creds.TokenProvider, nil)
90+ trans = &authTransport{
93+ clientUniverseDomain: opts.UniverseDomain,
94+ skipUniverseDomainValidation: skipUD,
100+// defaultBaseTransport returns the base HTTP transport.
101+// On App Engine, this is urlfetch.Transport.
102+// Otherwise, use a default transport, taking most defaults from
103+// http.DefaultTransport.
104+// If TLSCertificate is available, set TLSClientConfig as well.
105+func defaultBaseTransport(clientCertSource cert.Provider, dialTLSContext func(context.Context, string, string) (net.Conn, error)) http.RoundTripper {
106+ defaultTransport, ok := http.DefaultTransport.(*http.Transport)
108+ defaultTransport = transport.BaseTransport()
110+ trans := defaultTransport.Clone()
111+ trans.MaxIdleConnsPerHost = 100
113+ if clientCertSource != nil {
114+ trans.TLSClientConfig = &tls.Config{
115+ GetClientCertificate: clientCertSource,
118+ if dialTLSContext != nil {
119+ // If DialTLSContext is set, TLSClientConfig wil be ignored
120+ trans.DialTLSContext = dialTLSContext
123+ // Configures the ReadIdleTimeout HTTP/2 option for the
124+ // transport. This allows broken idle connections to be pruned more quickly,
125+ // preventing the client from attempting to re-use connections that will no
127+ http2Trans, err := http2.ConfigureTransports(trans)
129+ http2Trans.ReadIdleTimeout = time.Second * 31
135+type apiKeyTransport struct {
136+ // Key is the API Key to set on requests.
138+ // Transport is the underlying HTTP transport.
139+ // If nil, http.DefaultTransport is used.
140+ Transport http.RoundTripper
143+func (t *apiKeyTransport) RoundTrip(req *http.Request) (*http.Response, error) {
145+ args := newReq.URL.Query()
146+ args.Set("key", t.Key)
147+ newReq.URL.RawQuery = args.Encode()
148+ return t.Transport.RoundTrip(&newReq)
151+type headerTransport struct {
153+ base http.RoundTripper
156+func (t *headerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
159+ newReq.Header = make(http.Header)
160+ for k, vv := range req.Header {
161+ newReq.Header[k] = vv
164+ for k, v := range t.headers {
165+ newReq.Header[k] = v
168+ return rt.RoundTrip(&newReq)
171+func addOpenTelemetryTransport(trans http.RoundTripper, opts *Options) http.RoundTripper {
172+ if opts.DisableTelemetry {
175+ return otelhttp.NewTransport(trans)
178+type authTransport struct {
179+ creds *auth.Credentials
180+ base http.RoundTripper
181+ clientUniverseDomain string
182+ skipUniverseDomainValidation bool
185+// getClientUniverseDomain returns the default service domain for a given Cloud
186+// universe, with the following precedence:
188+// 1. A non-empty option.WithUniverseDomain or similar client option.
189+// 2. A non-empty environment variable GOOGLE_CLOUD_UNIVERSE_DOMAIN.
190+// 3. The default value "googleapis.com".
192+// This is the universe domain configured for the client, which will be compared
193+// to the universe domain that is separately configured for the credentials.
194+func (t *authTransport) getClientUniverseDomain() string {
195+ if t.clientUniverseDomain != "" {
196+ return t.clientUniverseDomain
198+ if envUD := os.Getenv(internal.UniverseDomainEnvVar); envUD != "" {
201+ return internal.DefaultUniverseDomain
204+// RoundTrip authorizes and authenticates the request with an
205+// access token from Transport's Source. Per the RoundTripper contract we must
206+// not modify the initial request, so we clone it, and we must close the body
207+// on any errors that happens during our token logic.
208+func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) {
209+ reqBodyClosed := false
210+ if req.Body != nil {
217+ token, err := t.creds.Token(req.Context())
221+ if !t.skipUniverseDomainValidation && token.MetadataString("auth.google.tokenSource") != "compute-metadata" {
222+ credentialsUniverseDomain, err := t.creds.UniverseDomain(req.Context())
226+ if err := transport.ValidateUniverseDomain(t.getClientUniverseDomain(), credentialsUniverseDomain); err != nil {
230+ req2 := req.Clone(req.Context())
231+ SetAuthHeader(token, req2)
232+ reqBodyClosed = true
233+ return t.base.RoundTrip(req2)
vendor/cloud.google.com/go/auth/internal/credsfile/credsfile.gonew file+107
@@ -0,0 +1,107 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
15+// Package credsfile is meant to hide implementation details from the pubic
16+// surface of the detect package. It should not import any other packages in
17+// this module. It is located under the main internal package so other
18+// sub-packages can use these parsed types as well.
29+ // GoogleAppCredsEnvVar is the environment variable for setting the
30+ // application default credentials.
31+ GoogleAppCredsEnvVar = "GOOGLE_APPLICATION_CREDENTIALS"
32+ userCredsFilename = "application_default_credentials.json"
35+// CredentialType represents different credential filetypes Google credentials
37+type CredentialType int
40+ // UnknownCredType is an unidentified file type.
41+ UnknownCredType CredentialType = iota
42+ // UserCredentialsKey represents a user creds file type.
44+ // ServiceAccountKey represents a service account file type.
46+ // ImpersonatedServiceAccountKey represents a impersonated service account
48+ ImpersonatedServiceAccountKey
49+ // ExternalAccountKey represents a external account file type.
51+ // GDCHServiceAccountKey represents a GDCH file type.
52+ GDCHServiceAccountKey
53+ // ExternalAccountAuthorizedUserKey represents a external account authorized
55+ ExternalAccountAuthorizedUserKey
58+// parseCredentialType returns the associated filetype based on the parsed
59+// typeString provided.
60+func parseCredentialType(typeString string) CredentialType {
62+ case "service_account":
63+ return ServiceAccountKey
64+ case "authorized_user":
65+ return UserCredentialsKey
66+ case "impersonated_service_account":
67+ return ImpersonatedServiceAccountKey
68+ case "external_account":
69+ return ExternalAccountKey
70+ case "external_account_authorized_user":
71+ return ExternalAccountAuthorizedUserKey
72+ case "gdch_service_account":
73+ return GDCHServiceAccountKey
75+ return UnknownCredType
79+// GetFileNameFromEnv returns the override if provided or detects a filename
80+// from the environment.
81+func GetFileNameFromEnv(override string) string {
85+ return os.Getenv(GoogleAppCredsEnvVar)
88+// GetWellKnownFileName tries to locate the filepath for the user credential
89+// file based on the environment.
90+func GetWellKnownFileName() string {
91+ if runtime.GOOS == "windows" {
92+ return filepath.Join(os.Getenv("APPDATA"), "gcloud", userCredsFilename)
94+ return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", userCredsFilename)
97+// guessUnixHomeDir default to checking for HOME, but not all unix systems have
98+// this set, do have a fallback.
99+func guessUnixHomeDir() string {
100+ if v := os.Getenv("HOME"); v != "" {
103+ if u, err := user.Current(); err == nil {
vendor/cloud.google.com/go/auth/internal/credsfile/filetype.gonew file+157
@@ -0,0 +1,157 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
21+// Config3LO is the internals of a client creds file.
22+type Config3LO struct {
23+ ClientID string `json:"client_id"`
24+ ClientSecret string `json:"client_secret"`
25+ RedirectURIs []string `json:"redirect_uris"`
26+ AuthURI string `json:"auth_uri"`
27+ TokenURI string `json:"token_uri"`
30+// ClientCredentialsFile representation.
31+type ClientCredentialsFile struct {
32+ Web *Config3LO `json:"web"`
33+ Installed *Config3LO `json:"installed"`
34+ UniverseDomain string `json:"universe_domain"`
37+// ServiceAccountFile representation.
38+type ServiceAccountFile struct {
39+ Type string `json:"type"`
40+ ProjectID string `json:"project_id"`
41+ PrivateKeyID string `json:"private_key_id"`
42+ PrivateKey string `json:"private_key"`
43+ ClientEmail string `json:"client_email"`
44+ ClientID string `json:"client_id"`
45+ AuthURL string `json:"auth_uri"`
46+ TokenURL string `json:"token_uri"`
47+ UniverseDomain string `json:"universe_domain"`
50+// UserCredentialsFile representation.
51+type UserCredentialsFile struct {
52+ Type string `json:"type"`
53+ ClientID string `json:"client_id"`
54+ ClientSecret string `json:"client_secret"`
55+ QuotaProjectID string `json:"quota_project_id"`
56+ RefreshToken string `json:"refresh_token"`
57+ UniverseDomain string `json:"universe_domain"`
60+// ExternalAccountFile representation.
61+type ExternalAccountFile struct {
62+ Type string `json:"type"`
63+ ClientID string `json:"client_id"`
64+ ClientSecret string `json:"client_secret"`
65+ Audience string `json:"audience"`
66+ SubjectTokenType string `json:"subject_token_type"`
67+ ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"`
68+ TokenURL string `json:"token_url"`
69+ CredentialSource *CredentialSource `json:"credential_source,omitempty"`
70+ TokenInfoURL string `json:"token_info_url"`
71+ ServiceAccountImpersonation *ServiceAccountImpersonationInfo `json:"service_account_impersonation,omitempty"`
72+ QuotaProjectID string `json:"quota_project_id"`
73+ WorkforcePoolUserProject string `json:"workforce_pool_user_project"`
74+ UniverseDomain string `json:"universe_domain"`
77+// ExternalAccountAuthorizedUserFile representation.
78+type ExternalAccountAuthorizedUserFile struct {
79+ Type string `json:"type"`
80+ Audience string `json:"audience"`
81+ ClientID string `json:"client_id"`
82+ ClientSecret string `json:"client_secret"`
83+ RefreshToken string `json:"refresh_token"`
84+ TokenURL string `json:"token_url"`
85+ TokenInfoURL string `json:"token_info_url"`
86+ RevokeURL string `json:"revoke_url"`
87+ QuotaProjectID string `json:"quota_project_id"`
88+ UniverseDomain string `json:"universe_domain"`
91+// CredentialSource stores the information necessary to retrieve the credentials for the STS exchange.
93+// One field amongst File, URL, Certificate, and Executable should be filled, depending on the kind of credential in question.
94+// The EnvironmentID should start with AWS if being used for an AWS credential.
95+type CredentialSource struct {
96+ File string `json:"file"`
97+ URL string `json:"url"`
98+ Headers map[string]string `json:"headers"`
99+ Executable *ExecutableConfig `json:"executable,omitempty"`
100+ Certificate *CertificateConfig `json:"certificate"`
101+ EnvironmentID string `json:"environment_id"` // TODO: Make type for this
102+ RegionURL string `json:"region_url"`
103+ RegionalCredVerificationURL string `json:"regional_cred_verification_url"`
104+ CredVerificationURL string `json:"cred_verification_url"`
105+ IMDSv2SessionTokenURL string `json:"imdsv2_session_token_url"`
106+ Format *Format `json:"format,omitempty"`
109+// Format describes the format of a [CredentialSource].
111+ // Type is either "text" or "json". When not provided "text" type is assumed.
112+ Type string `json:"type"`
113+ // SubjectTokenFieldName is only required for JSON format. This would be "access_token" for azure.
114+ SubjectTokenFieldName string `json:"subject_token_field_name"`
117+// ExecutableConfig represents the command to run for an executable
118+// [CredentialSource].
119+type ExecutableConfig struct {
120+ Command string `json:"command"`
121+ TimeoutMillis int `json:"timeout_millis"`
122+ OutputFile string `json:"output_file"`
125+// CertificateConfig represents the options used to set up X509 based workload
126+// [CredentialSource]
127+type CertificateConfig struct {
128+ UseDefaultCertificateConfig bool `json:"use_default_certificate_config"`
129+ CertificateConfigLocation string `json:"certificate_config_location"`
132+// ServiceAccountImpersonationInfo has impersonation configuration.
133+type ServiceAccountImpersonationInfo struct {
134+ TokenLifetimeSeconds int `json:"token_lifetime_seconds"`
137+// ImpersonatedServiceAccountFile representation.
138+type ImpersonatedServiceAccountFile struct {
139+ Type string `json:"type"`
140+ ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"`
141+ Delegates []string `json:"delegates"`
142+ CredSource json.RawMessage `json:"source_credentials"`
143+ UniverseDomain string `json:"universe_domain"`
146+// GDCHServiceAccountFile represents the Google Distributed Cloud Hosted (GDCH) service identity file.
147+type GDCHServiceAccountFile struct {
148+ Type string `json:"type"`
149+ FormatVersion string `json:"format_version"`
150+ Project string `json:"project"`
151+ Name string `json:"name"`
152+ CertPath string `json:"ca_cert_path"`
153+ PrivateKeyID string `json:"private_key_id"`
154+ PrivateKey string `json:"private_key"`
155+ TokenURL string `json:"token_uri"`
156+ UniverseDomain string `json:"universe_domain"`
vendor/cloud.google.com/go/auth/internal/credsfile/parse.gonew file+98
@@ -0,0 +1,98 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
21+// ParseServiceAccount parses bytes into a [ServiceAccountFile].
22+func ParseServiceAccount(b []byte) (*ServiceAccountFile, error) {
23+ var f *ServiceAccountFile
24+ if err := json.Unmarshal(b, &f); err != nil {
30+// ParseClientCredentials parses bytes into a
31+// [credsfile.ClientCredentialsFile].
32+func ParseClientCredentials(b []byte) (*ClientCredentialsFile, error) {
33+ var f *ClientCredentialsFile
34+ if err := json.Unmarshal(b, &f); err != nil {
40+// ParseUserCredentials parses bytes into a [UserCredentialsFile].
41+func ParseUserCredentials(b []byte) (*UserCredentialsFile, error) {
42+ var f *UserCredentialsFile
43+ if err := json.Unmarshal(b, &f); err != nil {
49+// ParseExternalAccount parses bytes into a [ExternalAccountFile].
50+func ParseExternalAccount(b []byte) (*ExternalAccountFile, error) {
51+ var f *ExternalAccountFile
52+ if err := json.Unmarshal(b, &f); err != nil {
58+// ParseExternalAccountAuthorizedUser parses bytes into a
59+// [ExternalAccountAuthorizedUserFile].
60+func ParseExternalAccountAuthorizedUser(b []byte) (*ExternalAccountAuthorizedUserFile, error) {
61+ var f *ExternalAccountAuthorizedUserFile
62+ if err := json.Unmarshal(b, &f); err != nil {
68+// ParseImpersonatedServiceAccount parses bytes into a
69+// [ImpersonatedServiceAccountFile].
70+func ParseImpersonatedServiceAccount(b []byte) (*ImpersonatedServiceAccountFile, error) {
71+ var f *ImpersonatedServiceAccountFile
72+ if err := json.Unmarshal(b, &f); err != nil {
78+// ParseGDCHServiceAccount parses bytes into a [GDCHServiceAccountFile].
79+func ParseGDCHServiceAccount(b []byte) (*GDCHServiceAccountFile, error) {
80+ var f *GDCHServiceAccountFile
81+ if err := json.Unmarshal(b, &f); err != nil {
87+type fileTypeChecker struct {
88+ Type string `json:"type"`
91+// ParseFileType determines the [CredentialType] based on bytes provided.
92+func ParseFileType(b []byte) (CredentialType, error) {
93+ var f fileTypeChecker
94+ if err := json.Unmarshal(b, &f); err != nil {
97+ return parseCredentialType(f.Type), nil
vendor/cloud.google.com/go/auth/internal/internal.gonew file+219
@@ -0,0 +1,219 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
31+ "cloud.google.com/go/compute/metadata"
35+ // TokenTypeBearer is the auth header prefix for bearer tokens.
36+ TokenTypeBearer = "Bearer"
38+ // QuotaProjectEnvVar is the environment variable for setting the quota
40+ QuotaProjectEnvVar = "GOOGLE_CLOUD_QUOTA_PROJECT"
41+ // UniverseDomainEnvVar is the environment variable for setting the default
42+ // service domain for a given Cloud universe.
43+ UniverseDomainEnvVar = "GOOGLE_CLOUD_UNIVERSE_DOMAIN"
44+ projectEnvVar = "GOOGLE_CLOUD_PROJECT"
45+ maxBodySize = 1 << 20
47+ // DefaultUniverseDomain is the default value for universe domain.
48+ // Universe domain is the default service domain for a given Cloud universe.
49+ DefaultUniverseDomain = "googleapis.com"
52+type clonableTransport interface {
53+ Clone() *http.Transport
56+// DefaultClient returns an [http.Client] with some defaults set. If
57+// the current [http.DefaultTransport] is a [clonableTransport], as
58+// is the case for an [*http.Transport], the clone will be used.
59+// Otherwise the [http.DefaultTransport] is used directly.
60+func DefaultClient() *http.Client {
61+ if transport, ok := http.DefaultTransport.(clonableTransport); ok {
63+ Transport: transport.Clone(),
64+ Timeout: 30 * time.Second,
69+ Transport: http.DefaultTransport,
70+ Timeout: 30 * time.Second,
74+// ParseKey converts the binary contents of a private key file
75+// to an crypto.Signer. It detects whether the private key is in a
76+// PEM container or not. If so, it extracts the the private key
77+// from PEM container before conversion. It only supports PEM
78+// containers with no passphrase.
79+func ParseKey(key []byte) (crypto.Signer, error) {
80+ block, _ := pem.Decode(key)
84+ var parsedKey crypto.PrivateKey
86+ parsedKey, err = x509.ParsePKCS8PrivateKey(key)
88+ parsedKey, err = x509.ParsePKCS1PrivateKey(key)
90+ return nil, fmt.Errorf("private key should be a PEM or plain PKCS1 or PKCS8: %w", err)
93+ parsed, ok := parsedKey.(crypto.Signer)
95+ return nil, errors.New("private key is not a signer")
100+// GetQuotaProject retrieves quota project with precedence being: override,
101+// environment variable, creds json file.
102+func GetQuotaProject(b []byte, override string) string {
106+ if env := os.Getenv(QuotaProjectEnvVar); env != "" {
113+ QuotaProject string `json:"quota_project_id"`
115+ if err := json.Unmarshal(b, &v); err != nil {
118+ return v.QuotaProject
121+// GetProjectID retrieves project with precedence being: override,
122+// environment variable, creds json file.
123+func GetProjectID(b []byte, override string) string {
127+ if env := os.Getenv(projectEnvVar); env != "" {
134+ ProjectID string `json:"project_id"` // standard service account key
135+ Project string `json:"project"` // gdch key
137+ if err := json.Unmarshal(b, &v); err != nil {
140+ if v.ProjectID != "" {
146+// DoRequest executes the provided req with the client. It reads the response
147+// body, closes it, and returns it.
148+func DoRequest(client *http.Client, req *http.Request) (*http.Response, []byte, error) {
149+ resp, err := client.Do(req)
151+ return nil, nil, err
153+ defer resp.Body.Close()
154+ body, err := ReadAll(io.LimitReader(resp.Body, maxBodySize))
156+ return nil, nil, err
158+ return resp, body, nil
161+// ReadAll consumes the whole reader and safely reads the content of its body
162+// with some overflow protection.
163+func ReadAll(r io.Reader) ([]byte, error) {
164+ return io.ReadAll(io.LimitReader(r, maxBodySize))
167+// StaticCredentialsProperty is a helper for creating static credentials
169+func StaticCredentialsProperty(s string) StaticProperty {
170+ return StaticProperty(s)
173+// StaticProperty always returns that value of the underlying string.
174+type StaticProperty string
176+// GetProperty loads the properly value provided the given context.
177+func (p StaticProperty) GetProperty(context.Context) (string, error) {
178+ return string(p), nil
181+// ComputeUniverseDomainProvider fetches the credentials universe domain from
182+// the google cloud metadata service.
183+type ComputeUniverseDomainProvider struct {
184+ MetadataClient *metadata.Client
185+ universeDomainOnce sync.Once
186+ universeDomain string
187+ universeDomainErr error
190+// GetProperty fetches the credentials universe domain from the google cloud
192+func (c *ComputeUniverseDomainProvider) GetProperty(ctx context.Context) (string, error) {
193+ c.universeDomainOnce.Do(func() {
194+ c.universeDomain, c.universeDomainErr = getMetadataUniverseDomain(ctx, c.MetadataClient)
196+ if c.universeDomainErr != nil {
197+ return "", c.universeDomainErr
199+ return c.universeDomain, nil
202+// httpGetMetadataUniverseDomain is a package var for unit test substitution.
203+var httpGetMetadataUniverseDomain = func(ctx context.Context, client *metadata.Client) (string, error) {
204+ ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
206+ return client.GetWithContext(ctx, "universe/universe-domain")
209+func getMetadataUniverseDomain(ctx context.Context, client *metadata.Client) (string, error) {
210+ universeDomain, err := httpGetMetadataUniverseDomain(ctx, client)
212+ return universeDomain, nil
214+ if _, ok := err.(metadata.NotDefinedError); ok {
215+ // http.StatusNotFound (404)
216+ return DefaultUniverseDomain, nil
vendor/cloud.google.com/go/auth/internal/jwt/jwt.gonew file+171
@@ -0,0 +1,171 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
32+ // HeaderAlgRSA256 is the RS256 [Header.Algorithm].
33+ HeaderAlgRSA256 = "RS256"
34+ // HeaderAlgES256 is the ES256 [Header.Algorithm].
35+ HeaderAlgES256 = "ES256"
36+ // HeaderType is the standard [Header.Type].
40+// Header represents a JWT header.
42+ Algorithm string `json:"alg"`
43+ Type string `json:"typ"`
44+ KeyID string `json:"kid"`
47+func (h *Header) encode() (string, error) {
48+ b, err := json.Marshal(h)
52+ return base64.RawURLEncoding.EncodeToString(b), nil
55+// Claims represents the claims set of a JWT.
57+ // Iss is the issuer JWT claim.
58+ Iss string `json:"iss"`
59+ // Scope is the scope JWT claim.
60+ Scope string `json:"scope,omitempty"`
61+ // Exp is the expiry JWT claim. If unset, default is in one hour from now.
62+ Exp int64 `json:"exp"`
63+ // Iat is the subject issued at claim. If unset, default is now.
64+ Iat int64 `json:"iat"`
65+ // Aud is the audience JWT claim. Optional.
66+ Aud string `json:"aud"`
67+ // Sub is the subject JWT claim. Optional.
68+ Sub string `json:"sub,omitempty"`
69+ // AdditionalClaims contains any additional non-standard JWT claims. Optional.
70+ AdditionalClaims map[string]interface{} `json:"-"`
73+func (c *Claims) encode() (string, error) {
74+ // Compensate for skew
75+ now := time.Now().Add(-10 * time.Second)
80+ c.Exp = now.Add(time.Hour).Unix()
83+ return "", fmt.Errorf("jwt: invalid Exp = %d; must be later than Iat = %d", c.Exp, c.Iat)
86+ b, err := json.Marshal(c)
91+ if len(c.AdditionalClaims) == 0 {
92+ return base64.RawURLEncoding.EncodeToString(b), nil
95+ // Marshal private claim set and then append it to b.
96+ prv, err := json.Marshal(c.AdditionalClaims)
98+ return "", fmt.Errorf("invalid map of additional claims %v: %w", c.AdditionalClaims, err)
101+ // Concatenate public and private claim JSON objects.
102+ if !bytes.HasSuffix(b, []byte{'}'}) {
103+ return "", fmt.Errorf("invalid JSON %s", b)
105+ if !bytes.HasPrefix(prv, []byte{'{'}) {
106+ return "", fmt.Errorf("invalid JSON %s", prv)
108+ b[len(b)-1] = ',' // Replace closing curly brace with a comma.
109+ b = append(b, prv[1:]...) // Append private claims.
110+ return base64.RawURLEncoding.EncodeToString(b), nil
113+// EncodeJWS encodes the data using the provided key as a JSON web signature.
114+func EncodeJWS(header *Header, c *Claims, signer crypto.Signer) (string, error) {
115+ head, err := header.encode()
119+ claims, err := c.encode()
123+ ss := fmt.Sprintf("%s.%s", head, claims)
126+ sig, err := signer.Sign(rand.Reader, h.Sum(nil), crypto.SHA256)
130+ return fmt.Sprintf("%s.%s", ss, base64.RawURLEncoding.EncodeToString(sig)), nil
133+// DecodeJWS decodes a claim set from a JWS payload.
134+func DecodeJWS(payload string) (*Claims, error) {
135+ // decode returned id token to get expiry
136+ s := strings.Split(payload, ".")
138+ return nil, errors.New("invalid token received")
140+ decoded, err := base64.RawURLEncoding.DecodeString(s[1])
145+ if err := json.NewDecoder(bytes.NewBuffer(decoded)).Decode(c); err != nil {
148+ if err := json.NewDecoder(bytes.NewBuffer(decoded)).Decode(&c.AdditionalClaims); err != nil {
154+// VerifyJWS tests whether the provided JWT token's signature was produced by
155+// the private key associated with the provided public key.
156+func VerifyJWS(token string, key *rsa.PublicKey) error {
157+ parts := strings.Split(token, ".")
158+ if len(parts) != 3 {
159+ return errors.New("jwt: invalid token received, token must have 3 parts")
162+ signedContent := parts[0] + "." + parts[1]
163+ signatureString, err := base64.RawURLEncoding.DecodeString(parts[2])
169+ h.Write([]byte(signedContent))
170+ return rsa.VerifyPKCS1v15(key, crypto.SHA256, h.Sum(nil), signatureString)
vendor/cloud.google.com/go/auth/internal/transport/cba.gonew file+368
@@ -0,0 +1,368 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
31+ "cloud.google.com/go/auth/internal"
32+ "cloud.google.com/go/auth/internal/transport/cert"
33+ "github.com/google/s2a-go"
34+ "github.com/google/s2a-go/fallback"
35+ "google.golang.org/grpc/credentials"
39+ mTLSModeAlways = "always"
40+ mTLSModeNever = "never"
41+ mTLSModeAuto = "auto"
43+ // Experimental: if true, the code will try MTLS with S2A as the default for transport security. Default value is false.
44+ googleAPIUseS2AEnv = "EXPERIMENTAL_GOOGLE_API_USE_S2A"
45+ googleAPIUseCertSource = "GOOGLE_API_USE_CLIENT_CERTIFICATE"
46+ googleAPIUseMTLS = "GOOGLE_API_USE_MTLS_ENDPOINT"
47+ googleAPIUseMTLSOld = "GOOGLE_API_USE_MTLS"
49+ universeDomainPlaceholder = "UNIVERSE_DOMAIN"
51+ mtlsMDSRoot = "/run/google-mds-mtls/root.crt"
52+ mtlsMDSKey = "/run/google-mds-mtls/client.key"
55+// Options is a struct that is duplicated information from the individual
56+// transport packages in order to avoid cyclic deps. It correlates 1:1 with
57+// fields on httptransport.Options and grpctransport.Options.
60+ DefaultEndpointTemplate string
61+ DefaultMTLSEndpoint string
62+ ClientCertProvider cert.Provider
64+ UniverseDomain string
65+ EnableDirectPath bool
66+ EnableDirectPathXds bool
70+// getUniverseDomain returns the default service domain for a given Cloud
72+func (o *Options) getUniverseDomain() string {
73+ if o.UniverseDomain == "" {
74+ return internal.DefaultUniverseDomain
76+ return o.UniverseDomain
79+// isUniverseDomainGDU returns true if the universe domain is the default Google
81+func (o *Options) isUniverseDomainGDU() bool {
82+ return o.getUniverseDomain() == internal.DefaultUniverseDomain
85+// defaultEndpoint returns the DefaultEndpointTemplate merged with the
86+// universe domain if the DefaultEndpointTemplate is set, otherwise returns an
88+func (o *Options) defaultEndpoint() string {
89+ if o.DefaultEndpointTemplate == "" {
92+ return strings.Replace(o.DefaultEndpointTemplate, universeDomainPlaceholder, o.getUniverseDomain(), 1)
95+// defaultMTLSEndpoint returns the DefaultMTLSEndpointTemplate merged with the
96+// universe domain if the DefaultMTLSEndpointTemplate is set, otherwise returns an
98+func (o *Options) defaultMTLSEndpoint() string {
99+ if o.DefaultMTLSEndpoint == "" {
102+ return strings.Replace(o.DefaultMTLSEndpoint, universeDomainPlaceholder, o.getUniverseDomain(), 1)
105+// mergedEndpoint merges a user-provided Endpoint of format host[:port] with the
107+func (o *Options) mergedEndpoint() (string, error) {
108+ defaultEndpoint := o.defaultEndpoint()
109+ u, err := url.Parse(fixScheme(defaultEndpoint))
113+ return strings.Replace(defaultEndpoint, u.Host, o.Endpoint, 1), nil
116+func fixScheme(baseURL string) string {
117+ if !strings.Contains(baseURL, "://") {
118+ baseURL = "https://" + baseURL
123+// GetGRPCTransportCredsAndEndpoint returns an instance of
124+// [google.golang.org/grpc/credentials.TransportCredentials], and the
125+// corresponding endpoint to use for GRPC client.
126+func GetGRPCTransportCredsAndEndpoint(opts *Options) (credentials.TransportCredentials, string, error) {
127+ config, err := getTransportConfig(opts)
132+ defaultTransportCreds := credentials.NewTLS(&tls.Config{
133+ GetClientCertificate: config.clientCertSource,
137+ var transportCredsForS2A credentials.TransportCredentials
139+ if config.mtlsS2AAddress != "" {
140+ s2aAddr = config.mtlsS2AAddress
141+ transportCredsForS2A, err = loadMTLSMDSTransportCreds(mtlsMDSRoot, mtlsMDSKey)
143+ log.Printf("Loading MTLS MDS credentials failed: %v", err)
144+ if config.s2aAddress != "" {
145+ s2aAddr = config.s2aAddress
147+ return defaultTransportCreds, config.endpoint, nil
150+ } else if config.s2aAddress != "" {
151+ s2aAddr = config.s2aAddress
153+ return defaultTransportCreds, config.endpoint, nil
156+ var fallbackOpts *s2a.FallbackOptions
157+ // In case of S2A failure, fall back to the endpoint that would've been used without S2A.
158+ if fallbackHandshake, err := fallback.DefaultFallbackClientHandshakeFunc(config.endpoint); err == nil {
159+ fallbackOpts = &s2a.FallbackOptions{
160+ FallbackClientHandshakeFunc: fallbackHandshake,
164+ s2aTransportCreds, err := s2a.NewClientCreds(&s2a.ClientOptions{
165+ S2AAddress: s2aAddr,
166+ TransportCreds: transportCredsForS2A,
167+ FallbackOpts: fallbackOpts,
170+ // Use default if we cannot initialize S2A client transport credentials.
171+ return defaultTransportCreds, config.endpoint, nil
173+ return s2aTransportCreds, config.s2aMTLSEndpoint, nil
176+// GetHTTPTransportConfig returns a client certificate source and a function for
177+// dialing MTLS with S2A.
178+func GetHTTPTransportConfig(opts *Options) (cert.Provider, func(context.Context, string, string) (net.Conn, error), error) {
179+ config, err := getTransportConfig(opts)
181+ return nil, nil, err
185+ var transportCredsForS2A credentials.TransportCredentials
187+ if config.mtlsS2AAddress != "" {
188+ s2aAddr = config.mtlsS2AAddress
189+ transportCredsForS2A, err = loadMTLSMDSTransportCreds(mtlsMDSRoot, mtlsMDSKey)
191+ log.Printf("Loading MTLS MDS credentials failed: %v", err)
192+ if config.s2aAddress != "" {
193+ s2aAddr = config.s2aAddress
195+ return config.clientCertSource, nil, nil
198+ } else if config.s2aAddress != "" {
199+ s2aAddr = config.s2aAddress
201+ return config.clientCertSource, nil, nil
204+ var fallbackOpts *s2a.FallbackOptions
205+ // In case of S2A failure, fall back to the endpoint that would've been used without S2A.
206+ if fallbackURL, err := url.Parse(config.endpoint); err == nil {
207+ if fallbackDialer, fallbackServerAddr, err := fallback.DefaultFallbackDialerAndAddress(fallbackURL.Hostname()); err == nil {
208+ fallbackOpts = &s2a.FallbackOptions{
209+ FallbackDialer: &s2a.FallbackDialer{
210+ Dialer: fallbackDialer,
211+ ServerAddr: fallbackServerAddr,
217+ dialTLSContextFunc := s2a.NewS2ADialTLSContextFunc(&s2a.ClientOptions{
218+ S2AAddress: s2aAddr,
219+ TransportCreds: transportCredsForS2A,
220+ FallbackOpts: fallbackOpts,
222+ return nil, dialTLSContextFunc, nil
225+func loadMTLSMDSTransportCreds(mtlsMDSRootFile, mtlsMDSKeyFile string) (credentials.TransportCredentials, error) {
226+ rootPEM, err := os.ReadFile(mtlsMDSRootFile)
230+ caCertPool := x509.NewCertPool()
231+ ok := caCertPool.AppendCertsFromPEM(rootPEM)
233+ return nil, errors.New("failed to load MTLS MDS root certificate")
235+ // The mTLS MDS credentials are formatted as the concatenation of a PEM-encoded certificate chain
236+ // followed by a PEM-encoded private key. For this reason, the concatenation is passed in to the
237+ // tls.X509KeyPair function as both the certificate chain and private key arguments.
238+ cert, err := tls.LoadX509KeyPair(mtlsMDSKeyFile, mtlsMDSKeyFile)
242+ tlsConfig := tls.Config{
243+ RootCAs: caCertPool,
244+ Certificates: []tls.Certificate{cert},
245+ MinVersion: tls.VersionTLS13,
247+ return credentials.NewTLS(&tlsConfig), nil
250+func getTransportConfig(opts *Options) (*transportConfig, error) {
251+ clientCertSource, err := GetClientCertificateProvider(opts)
255+ endpoint, err := getEndpoint(opts, clientCertSource)
259+ defaultTransportConfig := transportConfig{
260+ clientCertSource: clientCertSource,
264+ if !shouldUseS2A(clientCertSource, opts) {
265+ return &defaultTransportConfig, nil
268+ s2aAddress := GetS2AAddress(opts.Logger)
269+ mtlsS2AAddress := GetMTLSS2AAddress(opts.Logger)
270+ if s2aAddress == "" && mtlsS2AAddress == "" {
271+ return &defaultTransportConfig, nil
273+ return &transportConfig{
274+ clientCertSource: clientCertSource,
276+ s2aAddress: s2aAddress,
277+ mtlsS2AAddress: mtlsS2AAddress,
278+ s2aMTLSEndpoint: opts.defaultMTLSEndpoint(),
282+// GetClientCertificateProvider returns a default client certificate source, if
283+// not provided by the user.
285+// A nil default source can be returned if the source does not exist. Any exceptions
286+// encountered while initializing the default source will be reported as client
287+// error (ex. corrupt metadata file).
288+func GetClientCertificateProvider(opts *Options) (cert.Provider, error) {
289+ if !isClientCertificateEnabled(opts) {
291+ } else if opts.ClientCertProvider != nil {
292+ return opts.ClientCertProvider, nil
294+ return cert.DefaultProvider()
298+// isClientCertificateEnabled returns true by default for all GDU universe domain, unless explicitly overridden by env var
299+func isClientCertificateEnabled(opts *Options) bool {
300+ if value, ok := os.LookupEnv(googleAPIUseCertSource); ok {
301+ // error as false is OK
302+ b, _ := strconv.ParseBool(value)
305+ return opts.isUniverseDomainGDU()
308+type transportConfig struct {
309+ // The client certificate source.
310+ clientCertSource cert.Provider
311+ // The corresponding endpoint to use based on client certificate source.
313+ // The plaintext S2A address if it can be used, otherwise an empty string.
315+ // The MTLS S2A address if it can be used, otherwise an empty string.
316+ mtlsS2AAddress string
317+ // The MTLS endpoint to use with S2A.
318+ s2aMTLSEndpoint string
321+// getEndpoint returns the endpoint for the service, taking into account the
322+// user-provided endpoint override "settings.Endpoint".
324+// If no endpoint override is specified, we will either return the default
325+// endpoint or the default mTLS endpoint if a client certificate is available.
327+// You can override the default endpoint choice (mTLS vs. regular) by setting
328+// the GOOGLE_API_USE_MTLS_ENDPOINT environment variable.
330+// If the endpoint override is an address (host:port) rather than full base
331+// URL (ex. https://...), then the user-provided address will be merged into
332+// the default endpoint. For example, WithEndpoint("myhost:8000") and
333+// DefaultEndpointTemplate("https://UNIVERSE_DOMAIN/bar/baz") will return
334+// "https://myhost:8080/bar/baz". Note that this does not apply to the mTLS
336+func getEndpoint(opts *Options, clientCertSource cert.Provider) (string, error) {
337+ if opts.Endpoint == "" {
338+ mtlsMode := getMTLSMode()
339+ if mtlsMode == mTLSModeAlways || (clientCertSource != nil && mtlsMode == mTLSModeAuto) {
340+ return opts.defaultMTLSEndpoint(), nil
342+ return opts.defaultEndpoint(), nil
344+ if strings.Contains(opts.Endpoint, "://") {
345+ // User passed in a full URL path, use it verbatim.
346+ return opts.Endpoint, nil
348+ if opts.defaultEndpoint() == "" {
349+ // If DefaultEndpointTemplate is not configured,
350+ // use the user provided endpoint verbatim. This allows a naked
351+ // "host[:port]" URL to be used with GRPC Direct Path.
352+ return opts.Endpoint, nil
355+ // Assume user-provided endpoint is host[:port], merge it with the default endpoint.
356+ return opts.mergedEndpoint()
359+func getMTLSMode() string {
360+ mode := os.Getenv(googleAPIUseMTLS)
362+ mode = os.Getenv(googleAPIUseMTLSOld) // Deprecated.
367+ return strings.ToLower(mode)
vendor/cloud.google.com/go/auth/internal/transport/cert/default_cert.gonew file+65
@@ -0,0 +1,65 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
23+// defaultCertData holds all the variables pertaining to
24+// the default certificate provider created by [DefaultProvider].
26+// A singleton model is used to allow the provider to be reused
27+// by the transport layer. As mentioned in [DefaultProvider] (provider nil, nil)
28+// may be returned to indicate a default provider could not be found, which
29+// will skip extra tls config in the transport layer .
30+type defaultCertData struct {
37+ defaultCert defaultCertData
40+// Provider is a function that can be passed into crypto/tls.Config.GetClientCertificate.
41+type Provider func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
43+// errSourceUnavailable is a sentinel error to indicate certificate source is unavailable.
44+var errSourceUnavailable = errors.New("certificate source is unavailable")
46+// DefaultProvider returns a certificate source using the preferred EnterpriseCertificateProxySource.
47+// If EnterpriseCertificateProxySource is not available, fall back to the legacy SecureConnectSource.
49+// If neither source is available (due to missing configurations), a nil Source and a nil Error are
50+// returned to indicate that a default certificate source is unavailable.
51+func DefaultProvider() (Provider, error) {
52+ defaultCert.once.Do(func() {
53+ defaultCert.provider, defaultCert.err = NewWorkloadX509CertProvider("")
54+ if errors.Is(defaultCert.err, errSourceUnavailable) {
55+ defaultCert.provider, defaultCert.err = NewEnterpriseCertificateProxyProvider("")
56+ if errors.Is(defaultCert.err, errSourceUnavailable) {
57+ defaultCert.provider, defaultCert.err = NewSecureConnectProvider("")
58+ if errors.Is(defaultCert.err, errSourceUnavailable) {
59+ defaultCert.provider, defaultCert.err = nil, nil
64+ return defaultCert.provider, defaultCert.err
vendor/cloud.google.com/go/auth/internal/transport/cert/enterprise_cert.gonew file+54
@@ -0,0 +1,54 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
20+ "github.com/googleapis/enterprise-certificate-proxy/client"
23+type ecpSource struct {
27+// NewEnterpriseCertificateProxyProvider creates a certificate source
28+// using the Enterprise Certificate Proxy client, which delegates
29+// certifcate related operations to an OS-specific "signer binary"
30+// that communicates with the native keystore (ex. keychain on MacOS).
32+// The configFilePath points to a config file containing relevant parameters
33+// such as the certificate issuer and the location of the signer binary.
34+// If configFilePath is empty, the client will attempt to load the config from
35+// a well-known gcloud location.
36+func NewEnterpriseCertificateProxyProvider(configFilePath string) (Provider, error) {
37+ key, err := client.Cred(configFilePath)
39+ // TODO(codyoss): once this is fixed upstream can handle this error a
40+ // little better here. But be safe for now and assume unavailable.
41+ return nil, errSourceUnavailable
46+ }).getClientCertificate, nil
49+func (s *ecpSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
50+ var cert tls.Certificate
51+ cert.PrivateKey = s.key
52+ cert.Certificate = s.key.CertificateChain()
vendor/cloud.google.com/go/auth/internal/transport/cert/secureconnect_cert.gonew file+124
@@ -0,0 +1,124 @@
1+// Copyright 2023 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
32+ metadataPath = ".secureConnect"
33+ metadataFile = "context_aware_metadata.json"
36+type secureConnectSource struct {
37+ metadata secureConnectMetadata
39+ // Cache the cert to avoid executing helper command repeatedly.
40+ cachedCertMutex sync.Mutex
41+ cachedCert *tls.Certificate
44+type secureConnectMetadata struct {
45+ Cmd []string `json:"cert_provider_command"`
48+// NewSecureConnectProvider creates a certificate source using
49+// the Secure Connect Helper and its associated metadata file.
51+// The configFilePath points to the location of the context aware metadata file.
52+// If configFilePath is empty, use the default context aware metadata location.
53+func NewSecureConnectProvider(configFilePath string) (Provider, error) {
54+ if configFilePath == "" {
55+ user, err := user.Current()
57+ // Error locating the default config means Secure Connect is not supported.
58+ return nil, errSourceUnavailable
60+ configFilePath = filepath.Join(user.HomeDir, metadataPath, metadataFile)
63+ file, err := os.ReadFile(configFilePath)
65+ // Config file missing means Secure Connect is not supported.
66+ // There are non-os.ErrNotExist errors that may be returned.
67+ // (e.g. if the home directory is /dev/null, *nix systems will
68+ // return ENOTDIR instead of ENOENT)
69+ return nil, errSourceUnavailable
72+ var metadata secureConnectMetadata
73+ if err := json.Unmarshal(file, &metadata); err != nil {
74+ return nil, fmt.Errorf("cert: could not parse JSON in %q: %w", configFilePath, err)
76+ if err := validateMetadata(metadata); err != nil {
77+ return nil, fmt.Errorf("cert: invalid config in %q: %w", configFilePath, err)
79+ return (&secureConnectSource{
81+ }).getClientCertificate, nil
84+func validateMetadata(metadata secureConnectMetadata) error {
85+ if len(metadata.Cmd) == 0 {
86+ return errors.New("empty cert_provider_command")
91+func (s *secureConnectSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
92+ s.cachedCertMutex.Lock()
93+ defer s.cachedCertMutex.Unlock()
94+ if s.cachedCert != nil && !isCertificateExpired(s.cachedCert) {
95+ return s.cachedCert, nil
97+ // Expand OS environment variables in the cert provider command such as "$HOME".
98+ for i := 0; i < len(s.metadata.Cmd); i++ {
99+ s.metadata.Cmd[i] = os.ExpandEnv(s.metadata.Cmd[i])
101+ command := s.metadata.Cmd
102+ data, err := exec.Command(command[0], command[1:]...).Output()
106+ cert, err := tls.X509KeyPair(data, data)
110+ s.cachedCert = &cert
114+// isCertificateExpired returns true if the given cert is expired or invalid.
115+func isCertificateExpired(cert *tls.Certificate) bool {
116+ if len(cert.Certificate) == 0 {
119+ parsed, err := x509.ParseCertificate(cert.Certificate[0])
123+ return time.Now().After(parsed.NotAfter)
vendor/cloud.google.com/go/auth/internal/transport/cert/workload_cert.gonew file+114
@@ -0,0 +1,114 @@
1+// Copyright 2024 Google LLC
3+// Licensed under the Apache License, Version 2.0 (the "License");
4+// you may not use this file except in compliance with the License.
5+// You may obtain a copy of the License at
7+// http://www.apache.org/licenses/LICENSE-2.0
9+// Unless required by applicable law or agreed to in writing, software
10+// distributed under the License is distributed on an "AS IS" BASIS,
11+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+// See the License for the specific language governing permissions and
13+// limitations under the License.
24+ "github.com/googleapis/enterprise-certificate-proxy/client/util"
27+type certConfigs struct {
28+ Workload *workloadSource `json:"workload"`
31+type workloadSource struct {
32+ CertPath string `json:"cert_path"`
33+ KeyPath string `json:"key_path"`
36+type certificateConfig struct {
37+ CertConfigs certConfigs `json:"cert_configs"`
40+// NewWorkloadX509CertProvider creates a certificate source
41+// that reads a certificate and private key file from the local file system.
42+// This is intended to be used for workload identity federation.
44+// The configFilePath points to a config file containing relevant parameters
45+// such as the certificate and key file paths.
46+// If configFilePath is empty, the client will attempt to load the config from
47+// a well-known gcloud location.
48+func NewWorkloadX509CertProvider(configFilePath string) (Provider, error) {
49+ if configFilePath == "" {
50+ envFilePath := util.GetConfigFilePathFromEnv()
51+ if envFilePath != "" {
52+ configFilePath = envFilePath
54+ configFilePath = util.GetDefaultConfigFilePath()
58+ certFile, keyFile, err := getCertAndKeyFiles(configFilePath)
63+ source := &workloadSource{
67+ return source.getClientCertificate, nil
70+// getClientCertificate attempts to load the certificate and key from the files specified in the
71+// certificate config.
72+func (s *workloadSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
73+ cert, err := tls.LoadX509KeyPair(s.CertPath, s.KeyPath)
80+// getCertAndKeyFiles attempts to read the provided config file and return the certificate and private
82+func getCertAndKeyFiles(configFilePath string) (string, string, error) {
83+ jsonFile, err := os.Open(configFilePath)
85+ return "", "", errSourceUnavailable
88+ byteValue, err := io.ReadAll(jsonFile)
93+ var config certificateConfig
94+ if err := json.Unmarshal(byteValue, &config); err != nil {
98+ if config.CertConfigs.Workload == nil {
99+ return "", "", errSourceUnavailable
102+ certFile := config.CertConfigs.Workload.CertPath
103+ keyFile := config.CertConfigs.Workload.KeyPath
106+ return "", "", errors.New("certificate configuration is missing the certificate file location")
110+ return "", "", errors.New("certificate configuration is missing the key file location")
113+ return certFile, keyFile, nil
vendor/cloud.google.com/go/auth/internal/transport/s2a.gonew file+138
This diff is too big to show here.
vendor/cloud.google.com/go/auth/internal/transport/transport.gonew file+106
This diff is too big to show here.
vendor/cloud.google.com/go/auth/threelegged.gonew file+382
This diff is too big to show here.
vendor/cloud.google.com/go/civil/civil.gonew file+350
This diff is too big to show here.
vendor/cloud.google.com/go/compute/metadata/CHANGES.mdnew file+66
This diff is too big to show here.
vendor/cloud.google.com/go/compute/metadata/LICENSEnew file+202
This diff is too big to show here.
vendor/cloud.google.com/go/compute/metadata/README.mdnew file+27
This diff is too big to show here.
vendor/cloud.google.com/go/compute/metadata/log.gonew file+149
This diff is too big to show here.
vendor/cloud.google.com/go/compute/metadata/metadata.gonew file+872
This diff is too big to show here.
vendor/cloud.google.com/go/compute/metadata/retry.gonew file+114
This diff is too big to show here.
vendor/cloud.google.com/go/compute/metadata/retry_linux.gonew file+31
This diff is too big to show here.
vendor/cloud.google.com/go/compute/metadata/syscheck.gonew file+26
This diff is too big to show here.
vendor/cloud.google.com/go/compute/metadata/syscheck_linux.gonew file+28
This diff is too big to show here.
vendor/cloud.google.com/go/compute/metadata/syscheck_windows.gonew file+38
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/CHANGELOG.mdnew file+849
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/LICENSE.txtnew file+21
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/README.mdnew file+39
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/resource/resource_identifier.gonew file+239
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/resource/resource_type.gonew file+114
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/policy/policy.gonew file+108
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/runtime/pipeline.gonew file+70
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/runtime/policy_bearer_token.gonew file+102
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/runtime/policy_register_rp.gonew file+322
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/runtime/policy_trace_namespace.gonew file+30
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/runtime/runtime.gonew file+24
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/ci.ymlnew file+29
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud/cloud.gonew file+44
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud/doc.gonew file+53
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/core.gonew file+173
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/doc.gonew file+264
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/errors.gonew file+17
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/etag.gonew file+57
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/exported.gonew file+175
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/pipeline.gonew file+77
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/request.gonew file+260
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/response_error.gonew file+201
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/log/log.gonew file+50
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/async/async.gonew file+159
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/body/body.gonew file+135
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/fake/fake.gonew file+133
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/loc/loc.gonew file+123
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/op/op.gonew file+148
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/poller.gonew file+24
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers/util.gonew file+212
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared/constants.gonew file+44
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared/shared.gonew file+149
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/log/doc.gonew file+10
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/log/log.gonew file+55
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/policy/doc.gonew file+10
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/policy/policy.gonew file+198
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/doc.gonew file+10
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/errors.gonew file+27
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/pager.gonew file+138
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/pipeline.gonew file+94
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_api_version.gonew file+75
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_bearer_token.gonew file+236
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_body_download.gonew file+72
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_http_header.gonew file+40
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_http_trace.gonew file+154
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_include_response.gonew file+35
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_key_credential.gonew file+64
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_logging.gonew file+264
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_request_id.gonew file+34
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_retry.gonew file+276
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_sas_credential.gonew file+55
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_telemetry.gonew file+83
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/poller.gonew file+396
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/request.gonew file+281
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/response.gonew file+109
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_dialer_other.gonew file+15
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_dialer_wasm.gonew file+15
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/transport_default_http_client.gonew file+48
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming/doc.gonew file+9
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming/progress.gonew file+89
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing/constants.gonew file+41
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing/tracing.gonew file+191
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/.gitignorenew file+4
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/CHANGELOG.mdnew file+575
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/LICENSE.txtnew file+21
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/MIGRATION.mdnew file+307
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/README.mdnew file+258
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/TOKEN_CACHING.MDnew file+71
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/TROUBLESHOOTING.mdnew file+241
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/assets.jsonnew file+6
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/authentication_record.gonew file+95
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/azidentity.gonew file+190
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/azure_cli_credential.gonew file+190
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/azure_developer_cli_credential.gonew file+169
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/azure_pipelines_credential.gonew file+140
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/chained_token_credential.gonew file+138
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/ci.ymlnew file+46
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/client_assertion_credential.gonew file+85
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/client_certificate_credential.gonew file+174
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/client_secret_credential.gonew file+75
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/confidential_client.gonew file+184
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/default_azure_credential.gonew file+165
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/developer_credential_util.gonew file+38
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/device_code_credential.gonew file+138
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/environment_credential.gonew file+167
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/errors.gonew file+170
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/go.work.sumnew file+60
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/interactive_browser_credential.gonew file+118
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/internal/exported.gonew file+18
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/internal/internal.gonew file+31
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/logging.gonew file+14
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed-identity-matrix.jsonnew file+17
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_client.gonew file+501
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_credential.gonew file+128
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/on_behalf_of_credential.gonew file+113
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/public_client.gonew file+273
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/test-resources-post.ps1new file+112
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/test-resources-pre.ps1new file+44
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/test-resources.bicepnew file+219
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/username_password_credential.gonew file+90
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/version.gonew file+18
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/workload_identity.gonew file+131
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/LICENSE.txtnew file+21
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/diag/diag.gonew file+51
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/diag/doc.gonew file+7
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo/doc.gonew file+7
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo/errorinfo.gonew file+46
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/exported/exported.gonew file+129
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/log/doc.gonew file+7
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/log/log.gonew file+104
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/poller/util.gonew file+155
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/temporal/resource.gonew file+123
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/uuid/doc.gonew file+7
This diff is too big to show here.
vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/uuid/uuid.gonew file+76
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/LICENSEnew file+21
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache/cache.gonew file+54
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential/confidential.gonew file+719
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors/error_design.mdnew file+111
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors/errors.gonew file+89
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/base.gonew file+477
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage/items.gonew file+213
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage/partitioned_storage.gonew file+442
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/internal/storage/storage.gonew file+583
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/exported/exported.gonew file+34
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/design.mdnew file+140
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/json.gonew file+184
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/mapslice.gonew file+333
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/marshal.gonew file+346
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/struct.gonew file+290
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/json/types/time/time.gonew file+70
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/local/server.gonew file+177
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/oauth.gonew file+354
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/accesstokens.gonew file+457
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/apptype_string.gonew file+25
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens/tokens.gonew file+339
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authority.gonew file+589
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority/authorizetype_string.gonew file+30
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/comm/comm.gonew file+320
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/comm/compress.gonew file+33
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/internal/grant/grant.gonew file+17
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/ops.gonew file+56
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/endpointtype_string.gonew file+25
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/mex_document_definitions.gonew file+394
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/saml_assertion_definitions.gonew file+230
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/version_string.gonew file+25
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/wstrust_endpoint.gonew file+199
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/defs/wstrust_mex_document.gonew file+159
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/wstrust/wstrust.gonew file+136
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/resolvers.gonew file+149
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/options/options.gonew file+52
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared/shared.gonew file+72
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/version/version.gonew file+8
This diff is too big to show here.
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/public/public.gonew file+756
This diff is too big to show here.
vendor/github.com/JohannesKaufmann/html-to-markdown/.gitignorenew file+14
This diff is too big to show here.
vendor/github.com/JohannesKaufmann/html-to-markdown/CONTRIBUTING.mdnew file
No changes to the content.
vendor/github.com/JohannesKaufmann/html-to-markdown/LICENSEnew file+21
This diff is too big to show here.
vendor/github.com/JohannesKaufmann/html-to-markdown/README.mdnew file+242
This diff is too big to show here.
vendor/github.com/JohannesKaufmann/html-to-markdown/SECURITY.mdnew file+6
This diff is too big to show here.
vendor/github.com/JohannesKaufmann/html-to-markdown/commonmark.gonew file+393
This diff is too big to show here.
vendor/github.com/JohannesKaufmann/html-to-markdown/escape/escape.gonew file+65
This diff is too big to show here.
vendor/github.com/JohannesKaufmann/html-to-markdown/from.gonew file+464
This diff is too big to show here.
vendor/github.com/JohannesKaufmann/html-to-markdown/logo_five_years.pngnew file
Binary file not shown.
vendor/github.com/JohannesKaufmann/html-to-markdown/markdown.gonew file+212
This diff is too big to show here.
vendor/github.com/JohannesKaufmann/html-to-markdown/utils.gonew file+533
This diff is too big to show here.
vendor/github.com/MakeNowJust/heredoc/LICENSEnew file+21
This diff is too big to show here.
vendor/github.com/MakeNowJust/heredoc/README.mdnew file+52
This diff is too big to show here.
vendor/github.com/MakeNowJust/heredoc/heredoc.gonew file+105
This diff is too big to show here.
vendor/github.com/PuerkitoBio/goquery/.gitattributesnew file+1
This diff is too big to show here.
vendor/github.com/PuerkitoBio/goquery/.gitignorenew file+16
This diff is too big to show here.
vendor/github.com/PuerkitoBio/goquery/LICENSEnew file+12
This diff is too big to show here.
vendor/github.com/PuerkitoBio/goquery/README.mdnew file+202
This diff is too big to show here.
vendor/github.com/PuerkitoBio/goquery/array.gonew file+124
This diff is too big to show here.
vendor/github.com/PuerkitoBio/goquery/doc.gonew file+123
This diff is too big to show here.
vendor/github.com/PuerkitoBio/goquery/expand.gonew file+70
This diff is too big to show here.
vendor/github.com/PuerkitoBio/goquery/filter.gonew file+163
This diff is too big to show here.
vendor/github.com/PuerkitoBio/goquery/iteration.gonew file+47
This diff is too big to show here.
vendor/github.com/PuerkitoBio/goquery/manipulation.gonew file+679
This diff is too big to show here.
vendor/github.com/PuerkitoBio/goquery/property.gonew file+275
This diff is too big to show here.
vendor/github.com/PuerkitoBio/goquery/query.gonew file+49
This diff is too big to show here.
vendor/github.com/PuerkitoBio/goquery/traversal.gonew file+704
This diff is too big to show here.
vendor/github.com/PuerkitoBio/goquery/type.gonew file+203
This diff is too big to show here.
vendor/github.com/PuerkitoBio/goquery/utilities.gonew file+178
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/.editorconfignew file+17
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/.gitignorenew file+25
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/.golangci.ymlnew file+95
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/.goreleaser.ymlnew file+37
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/Bitfilenew file+24
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/COPYINGnew file+19
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/Makefilenew file+23
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/README.mdnew file+297
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/coalesce.gonew file+35
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/colour.gonew file+192
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/delegate.gonew file+152
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/doc.gonew file+7
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/emitters.gonew file+218
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/formatter.gonew file+43
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/formatters/api.gonew file+57
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/formatters/html/html.gonew file+623
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/formatters/json.gonew file+39
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/formatters/svg/font_liberation_mono.gonew file+51
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/formatters/svg/svg.gonew file+222
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/formatters/tokens.gonew file+18
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/formatters/tty_indexed.gonew file+284
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/formatters/tty_truecolour.gonew file+76
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/iterator.gonew file+76
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexer.gonew file+162
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/README.mdnew file+46
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/caddyfile.gonew file+275
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/cl.gonew file+243
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/dns.gonew file+17
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/emacs.gonew file+533
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/abap.xmlnew file+154
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/abnf.xmlnew file+66
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/actionscript.xmlnew file+68
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/actionscript_3.xmlnew file+163
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ada.xmlnew file+321
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/agda.xmlnew file+66
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/al.xmlnew file+75
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/alloy.xmlnew file+58
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/angular2.xmlnew file+108
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/antlr.xmlnew file+317
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/apacheconf.xmlnew file+74
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/apl.xmlnew file+59
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/applescript.xmlnew file+130
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arangodb_aql.xmlnew file+174
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/arduino.xmlnew file+309
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/armasm.xmlnew file+126
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/atl.xmlnew file+165
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/autohotkey.xmlnew file+78
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/autoit.xmlnew file+70
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/awk.xmlnew file+95
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ballerina.xmlnew file+97
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bash.xmlnew file+220
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bash_session.xmlnew file+25
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/batchfile.xmlnew file+660
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/beef.xmlnew file+120
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bibtex.xmlnew file+152
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bicep.xmlnew file+84
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/blitzbasic.xmlnew file+141
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bnf.xmlnew file+28
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/bqn.xmlnew file+83
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/brainfuck.xmlnew file+51
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c#.xmlnew file+121
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c++.xmlnew file+331
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/c.xmlnew file+260
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cap_n_proto.xmlnew file+122
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cassandra_cql.xmlnew file+137
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ceylon.xmlnew file+151
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cfengine3.xmlnew file+197
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cfstatement.xmlnew file+92
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/chaiscript.xmlnew file+134
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/chapel.xmlnew file+143
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cheetah.xmlnew file+55
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/clojure.xmlnew file+71
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cmake.xmlnew file+90
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cobol.xmlnew file+90
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/coffeescript.xmlnew file+210
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/common_lisp.xmlnew file+184
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/coq.xmlnew file+136
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/crystal.xmlnew file+762
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/css.xmlnew file+323
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/csv.xmlnew file+53
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cue.xmlnew file+85
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/cython.xmlnew file+372
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/d.xmlnew file+133
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dart.xmlnew file+213
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dax.xmlnew file+39
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/desktop_entry.xmlnew file+17
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/diff.xmlnew file+52
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/django_jinja.xmlnew file+153
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dns.xmlnew file+44
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/docker.xmlnew file+57
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dtd.xmlnew file+168
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/dylan.xmlnew file+176
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ebnf.xmlnew file+90
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/elixir.xmlnew file+744
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/elm.xmlnew file+119
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/emacslisp.xmlnew file+132
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/erlang.xmlnew file+166
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/factor.xmlnew file+412
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fennel.xmlnew file+68
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fish.xmlnew file+159
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/forth.xmlnew file+78
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fortran.xmlnew file+102
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fortranfixed.xmlnew file+71
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/fsharp.xmlnew file+245
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gas.xmlnew file+150
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gdscript.xmlnew file+259
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gdscript3.xmlnew file+270
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gherkin.xmlnew file+263
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gleam.xmlnew file+117
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/glsl.xmlnew file+65
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/gnuplot.xmlnew file+289
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/go_template.xmlnew file+114
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/graphql.xmlnew file+88
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/groff.xmlnew file+90
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/groovy.xmlnew file+135
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/handlebars.xmlnew file+147
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hare.xmlnew file+98
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/haskell.xmlnew file+275
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hcl.xmlnew file+143
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hexdump.xmlnew file+189
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hlb.xmlnew file+149
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hlsl.xmlnew file+110
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/holyc.xmlnew file+252
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/html.xmlnew file+159
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/hy.xmlnew file+104
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/idris.xmlnew file+216
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/igor.xmlnew file+47
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ini.xmlnew file+45
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/io.xmlnew file+71
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/iscdhcpd.xmlnew file+96
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/j.xmlnew file+157
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/java.xmlnew file+193
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/javascript.xmlnew file+160
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/json.xmlnew file+112
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jsonata.xmlnew file+83
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jsonnet.xmlnew file+138
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/julia.xmlnew file+400
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/jungle.xmlnew file+98
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/kotlin.xmlnew file+223
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lighttpd_configuration_file.xmlnew file+42
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/llvm.xmlnew file+73
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/lua.xmlnew file+158
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/makefile.xmlnew file+131
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mako.xmlnew file+120
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mason.xmlnew file+89
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/materialize_sql_dialect.xmlnew file+155
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mathematica.xmlnew file+60
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/matlab.xmlnew file+114
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mcfunction.xmlnew file+138
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/meson.xmlnew file+85
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/metal.xmlnew file+270
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/minizinc.xmlnew file+82
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mlir.xmlnew file+73
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/modula-2.xmlnew file+245
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/monkeyc.xmlnew file+153
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/morrowindscript.xmlnew file+90
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/myghty.xmlnew file+77
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/mysql.xmlnew file+121
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nasm.xmlnew file+126
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/natural.xmlnew file+143
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ndisasm.xmlnew file+123
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/newspeak.xmlnew file+121
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nginx_configuration_file.xmlnew file+98
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nim.xmlnew file+211
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nix.xmlnew file+258
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/nsis.xmlnew file+59
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/objective-c.xmlnew file+510
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/objectpascal.xmlnew file+145
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ocaml.xmlnew file+145
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/octave.xmlnew file+101
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/odin.xmlnew file+113
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/onesenterprise.xmlnew file+92
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/openedge_abl.xmlnew file+101
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/openscad.xmlnew file+96
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/org_mode.xmlnew file+329
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pacmanconf.xmlnew file+37
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/perl.xmlnew file+400
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/php.xmlnew file+212
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pig.xmlnew file+105
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pkgconfig.xmlnew file+73
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pl_pgsql.xmlnew file+119
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/plaintext.xmlnew file+21
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/plutus_core.xmlnew file+105
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/pony.xmlnew file+135
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/postgresql_sql_dialect.xmlnew file+155
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/postscript.xmlnew file+89
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/povray.xmlnew file+58
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/powerquery.xmlnew file+51
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/powershell.xmlnew file+230
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/prolog.xmlnew file+115
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/promela.xmlnew file+119
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/promql.xmlnew file+123
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/properties.xmlnew file+45
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/protocol_buffer.xmlnew file+118
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/prql.xmlnew file+161
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/psl.xmlnew file+213
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/puppet.xmlnew file+100
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/python.xmlnew file+593
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/python_2.xmlnew file+356
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/qbasic.xmlnew file+173
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/qml.xmlnew file+113
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/r.xmlnew file+128
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/racket.xmlnew file+260
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ragel.xmlnew file+149
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/react.xmlnew file+236
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/reasonml.xmlnew file+147
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/reg.xmlnew file+68
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rego.xmlnew file+94
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rexx.xmlnew file+127
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rpm_spec.xmlnew file+58
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ruby.xmlnew file+724
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/rust.xmlnew file+375
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sas.xmlnew file+191
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sass.xmlnew file+362
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scala.xmlnew file+274
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scheme.xmlnew file+106
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scilab.xmlnew file+98
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/scss.xmlnew file+373
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sed.xmlnew file+28
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sieve.xmlnew file+61
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smali.xmlnew file+73
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smalltalk.xmlnew file+294
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/smarty.xmlnew file+79
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/snbt.xmlnew file+58
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/snobol.xmlnew file+95
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/solidity.xmlnew file+279
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sourcepawn.xmlnew file+59
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sparql.xmlnew file+160
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/sql.xmlnew file+90
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/squidconf.xmlnew file+63
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/standard_ml.xmlnew file+548
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/stas.xmlnew file+85
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/stylus.xmlnew file+132
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/swift.xmlnew file+207
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/systemd.xmlnew file+63
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/systemverilog.xmlnew file+181
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tablegen.xmlnew file+69
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tal.xmlnew file+43
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tasm.xmlnew file+135
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tcl.xmlnew file+272
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tcsh.xmlnew file+121
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/termcap.xmlnew file+75
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/terminfo.xmlnew file+84
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/terraform.xmlnew file+140
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tex.xmlnew file+113
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/thrift.xmlnew file+154
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/toml.xmlnew file+44
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/tradingview.xmlnew file+81
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/transact-sql.xmlnew file+137
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/turing.xmlnew file+82
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/turtle.xmlnew file+170
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/twig.xmlnew file+155
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typescript.xmlnew file+295
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscript.xmlnew file+178
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscriptcssdata.xmlnew file+52
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typoscripthtmldata.xmlnew file+52
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/typst.xmlnew file+108
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/ucode.xmlnew file+147
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/v.xmlnew file+355
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/v_shell.xmlnew file+365
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vala.xmlnew file+72
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vb_net.xmlnew file+162
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/verilog.xmlnew file+158
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vhdl.xmlnew file+171
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vhs.xmlnew file+48
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/viml.xmlnew file+85
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/vue.xmlnew file+307
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/wdte.xmlnew file+43
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/webgpu_shading_language.xmlnew file+142
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/webvtt.xmlnew file+283
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/whiley.xmlnew file+57
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/xml.xmlnew file+95
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/xorg.xmlnew file+35
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/yaml.xmlnew file+122
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/yang.xmlnew file+99
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/z80_assembly.xmlnew file+74
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/zed.xmlnew file+51
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/embedded/zig.xmlnew file+112
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/genshi.gonew file+118
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/go.gonew file+81
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/haxe.gonew file+647
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/html.gonew file+8
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/http.gonew file+131
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/lexers.gonew file+79
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/markdown.gonew file+46
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/mysql.gonew file+33
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/php.gonew file+37
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/raku.gonew file+1721
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/rst.gonew file+89
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/svelte.gonew file+70
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/typoscript.gonew file+85
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/lexers/zed.gonew file+24
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/mutators.gonew file+201
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/pygments-lexers.txtnew file+322
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/quick/quick.gonew file+44
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/regexp.gonew file+489
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/registry.gonew file+210
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/remap.gonew file+94
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/renovate.json5new file+18
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/serialise.gonew file+479
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/style.gonew file+481
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/abap.xmlnew file+11
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/algol.xmlnew file+18
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/algol_nu.xmlnew file+18
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/api.gonew file+65
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/arduino.xmlnew file+18
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/autumn.xmlnew file+36
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/average.xmlnew file+74
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/base16-snazzy.xmlnew file+74
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/borland.xmlnew file+26
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/bw.xmlnew file+23
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-frappe.xmlnew file+83
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-latte.xmlnew file+83
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-macchiato.xmlnew file+83
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/catppuccin-mocha.xmlnew file+83
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/colorful.xmlnew file+52
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/compat.gonew file+66
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/doom-one.xmlnew file+51
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/doom-one2.xmlnew file+64
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/dracula.xmlnew file+74
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/emacs.xmlnew file+44
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/evergarden.xmlnew file+33
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/friendly.xmlnew file+44
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/fruity.xmlnew file+19
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/github-dark.xmlnew file+45
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/github.xmlnew file+39
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/gruvbox-light.xmlnew file+33
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/gruvbox.xmlnew file+33
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/hr_high_contrast.xmlnew file+12
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/hrdark.xmlnew file+10
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/igor.xmlnew file+9
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/lovelace.xmlnew file+53
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/manni.xmlnew file+44
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/modus-operandi.xmlnew file+13
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/modus-vivendi.xmlnew file+13
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/monokai.xmlnew file+29
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/monokailight.xmlnew file+26
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/murphy.xmlnew file+52
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/native.xmlnew file+35
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/nord.xmlnew file+46
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/nordic.xmlnew file+46
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/onedark.xmlnew file+25
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/onesenterprise.xmlnew file+10
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/paraiso-dark.xmlnew file+37
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/paraiso-light.xmlnew file+37
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/pastie.xmlnew file+45
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/perldoc.xmlnew file+37
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/pygments.xmlnew file+42
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/rainbow_dash.xmlnew file+40
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/rose-pine-dawn.xmlnew file+29
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/rose-pine-moon.xmlnew file+29
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/rose-pine.xmlnew file+29
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/rrt.xmlnew file+13
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/solarized-dark.xmlnew file+39
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/solarized-dark256.xmlnew file+41
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/solarized-light.xmlnew file+17
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/swapoff.xmlnew file+18
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/tango.xmlnew file+72
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-day.xmlnew file+83
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-moon.xmlnew file+83
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-night.xmlnew file+83
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/tokyonight-storm.xmlnew file+83
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/trac.xmlnew file+35
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/vim.xmlnew file+29
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/vs.xmlnew file+16
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/vulcan.xmlnew file+74
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/witchhazel.xmlnew file+31
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/xcode-dark.xmlnew file+31
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/styles/xcode.xmlnew file+22
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/table.pynew file+31
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/tokentype_enumer.gonew file+583
This diff is too big to show here.
vendor/github.com/alecthomas/chroma/v2/types.gonew file+343
This diff is too big to show here.
vendor/github.com/andybalholm/cascadia/.travis.ymlnew file+14
This diff is too big to show here.
vendor/github.com/andybalholm/cascadia/LICENSEnew file+24
This diff is too big to show here.
vendor/github.com/andybalholm/cascadia/README.mdnew file+144
This diff is too big to show here.
vendor/github.com/andybalholm/cascadia/parser.gonew file+889
This diff is too big to show here.
vendor/github.com/andybalholm/cascadia/pseudo_classes.gonew file+458
This diff is too big to show here.
vendor/github.com/andybalholm/cascadia/selector.gonew file+586
This diff is too big to show here.
vendor/github.com/andybalholm/cascadia/serialize.gonew file+176
This diff is too big to show here.
vendor/github.com/andybalholm/cascadia/specificity.gonew file+26
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/.gitignorenew file+4
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/.release-please-manifest.jsonnew file+3
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/.stats.ymlnew file+4
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/Brewfilenew file+1
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/CHANGELOG.mdnew file+240
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/CONTRIBUTING.mdnew file+66
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/LICENSEnew file+8
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/README.mdnew file+876
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/SECURITY.mdnew file+27
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/aliases.gonew file+50
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/api.mdnew file+329
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/bedrock/bedrock.gonew file+245
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/beta.gonew file+364
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/betafile.gonew file+270
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/betamessage.gonew file+5823
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/betamessagebatch.gonew file+879
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/betamodel.gonew file+149
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/client.gonew file+126
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/completion.gonew file+194
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/field.gonew file+45
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apierror/apierror.gonew file+50
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apiform/encoder.gonew file+465
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apiform/form.gonew file+5
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apiform/richparam.gonew file+20
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apiform/tag.gonew file+51
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apijson/decoder.gonew file+691
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apijson/encoder.gonew file+392
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apijson/enum.gonew file+145
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apijson/field.gonew file+23
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apijson/port.gonew file+120
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apijson/registry.gonew file+51
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apijson/subfield.gonew file+67
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apijson/tag.gonew file+47
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apijson/union.gonew file+202
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apiquery/encoder.gonew file+415
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apiquery/query.gonew file+55
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apiquery/richparam.gonew file+19
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/apiquery/tag.gonew file+44
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/encoding/json/decode.gonew file+1324
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/encoding/json/encode.gonew file+1398
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/encoding/json/fold.gonew file+48
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/encoding/json/indent.gonew file+182
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/encoding/json/scanner.gonew file+610
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/encoding/json/sentinel/null.gonew file+57
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/encoding/json/shims/shims.gonew file+111
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/encoding/json/stream.gonew file+512
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/encoding/json/tables.gonew file+218
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/encoding/json/tags.gonew file+38
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/encoding/json/time.gonew file+61
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/paramutil/field.gonew file+30
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/paramutil/sentinel.gonew file+31
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/paramutil/union.gonew file+48
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/requestconfig/requestconfig.gonew file+629
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/internal/version.gonew file+5
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/message.gonew file+4562
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/messagebatch.gonew file+825
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/model.gonew file+149
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/option/requestoption.gonew file+284
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/packages/jsonl/jsonl.gonew file+57
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/packages/pagination/pagination.gonew file+134
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/packages/param/encoder.gonew file+101
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/packages/param/option.gonew file+121
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/packages/param/param.gonew file+158
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/packages/respjson/respjson.gonew file+88
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/packages/ssestream/ssestream.gonew file+198
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/release-please-config.jsonnew file+67
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/shared/constant/constants.gonew file+304
This diff is too big to show here.
vendor/github.com/anthropics/anthropic-sdk-go/shared/shared.gonew file+334
This diff is too big to show here.
vendor/github.com/atotto/clipboard/.travis.ymlnew file+22
This diff is too big to show here.
vendor/github.com/atotto/clipboard/LICENSEnew file+27
This diff is too big to show here.
vendor/github.com/atotto/clipboard/README.mdnew file+48
This diff is too big to show here.
vendor/github.com/atotto/clipboard/clipboard.gonew file+20
This diff is too big to show here.
vendor/github.com/atotto/clipboard/clipboard_darwin.gonew file+52
This diff is too big to show here.
vendor/github.com/atotto/clipboard/clipboard_plan9.gonew file+42
This diff is too big to show here.
vendor/github.com/atotto/clipboard/clipboard_unix.gonew file+149
This diff is too big to show here.
vendor/github.com/atotto/clipboard/clipboard_windows.gonew file+157
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/LICENSE.txtnew file+202
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/NOTICE.txtnew file+3
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/accountid_endpoint_mode.gonew file+18
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/config.gonew file+211
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/context.gonew file+22
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/credential_cache.gonew file+224
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/credentials.gonew file+173
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/auto.gonew file+38
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/configuration.gonew file+43
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/defaults.gonew file+50
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/defaults/doc.gonew file+2
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/defaultsmode.gonew file+95
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/doc.gonew file+62
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/endpoints.gonew file+247
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/errors.gonew file+9
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/from_ptr.gonew file+365
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/go_module_metadata.gonew file+6
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/logging.gonew file+119
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/logging_generate.gonew file+95
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/metadata.gonew file+213
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/middleware.gonew file+168
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/osname.gonew file+24
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/osname_go115.gonew file+24
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/private/metrics/metrics.gonew file+320
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/recursion_detection.gonew file+94
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/request_id.gonew file+27
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/request_id_retriever.gonew file+53
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/middleware/user_agent.gonew file+305
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/CHANGELOG.mdnew file+114
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/LICENSE.txtnew file+202
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/debug.gonew file+144
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/decode.gonew file+218
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/encode.gonew file+167
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/error.gonew file+23
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/eventstreamapi/headers.gonew file+24
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/eventstreamapi/middleware.gonew file+71
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/eventstreamapi/transport.gonew file+13
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/eventstreamapi/transport_go117.gonew file+12
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/go_module_metadata.gonew file+6
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/header.gonew file+175
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/header_value.gonew file+521
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream/message.gonew file+117
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/array.gonew file+72
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/encoder.gonew file+80
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/map.gonew file+78
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/middleware.gonew file+62
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/object.gonew file+69
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/query/value.gonew file+115
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/restjson/decoder_util.gonew file+85
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/protocol/xml/error_utils.gonew file+48
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/ratelimit/none.gonew file+20
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/ratelimit/token_bucket.gonew file+96
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/ratelimit/token_rate_limit.gonew file+83
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/request.gonew file+25
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/retry/adaptive.gonew file+156
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/retry/adaptive_ratelimit.gonew file+158
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/retry/adaptive_token_bucket.gonew file+83
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/retry/doc.gonew file+80
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/retry/errors.gonew file+20
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/retry/jitter_backoff.gonew file+49
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/retry/metadata.gonew file+52
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/retry/middleware.gonew file+383
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/retry/retry.gonew file+90
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/retry/retryable_error.gonew file+222
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/retry/standard.gonew file+269
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/retry/throttle_error.gonew file+60
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/retry/timeout_error.gonew file+52
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/retryer.gonew file+127
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/runtime.gonew file+14
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/cache.gonew file+115
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/const.gonew file+40
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/header_rules.gonew file+82
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/headers.gonew file+70
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/hmac.gonew file+13
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/host.gonew file+75
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/scope.gonew file+13
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/time.gonew file+36
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4/util.gonew file+80
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/v4/middleware.gonew file+414
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/v4/presign_middleware.gonew file+127
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/v4/stream.gonew file+86
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/signer/v4/v4.gonew file+559
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/to_ptr.gonew file+297
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/client.gonew file+310
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/content_type.gonew file+42
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/response_error.gonew file+33
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/response_error_middleware.gonew file+56
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/transport/http/timeout_read_closer.gonew file+104
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/types.gonew file+42
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/aws/version.gonew file+8
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/config/CHANGELOG.mdnew file+678
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/config/LICENSE.txtnew file+202
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/config/config.gonew file+222
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/config/defaultsmode.gonew file+47
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/config/doc.gonew file+20
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/config/env_config.gonew file+856
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/config/generate.gonew file+4
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/config/go_module_metadata.gonew file+6
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/config/load_options.gonew file+1141
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/config/local.gonew file+51
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/config/provider.gonew file+721
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/config/resolve.gonew file+383
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/config/resolve_bearer_token.gonew file+122
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/config/resolve_credentials.gonew file+566
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/config/shared_config.gonew file+1618
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/CHANGELOG.mdnew file+591
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/LICENSE.txtnew file+202
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/doc.gonew file+4
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/ec2rolecreds/doc.gonew file+58
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/ec2rolecreds/provider.gonew file+229
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/internal/client/auth.gonew file+48
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/internal/client/client.gonew file+165
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/internal/client/endpoints.gonew file+20
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/internal/client/middleware.gonew file+164
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/provider.gonew file+193
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/go_module_metadata.gonew file+6
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/processcreds/doc.gonew file+92
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/processcreds/provider.gonew file+285
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/ssocreds/doc.gonew file+81
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/ssocreds/sso_cached_token.gonew file+233
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/ssocreds/sso_credentials_provider.gonew file+153
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/ssocreds/sso_token_provider.gonew file+147
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/static_provider.gonew file+53
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/stscreds/assume_role_provider.gonew file+326
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/credentials/stscreds/web_identity_provider.gonew file+169
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/CHANGELOG.mdnew file+355
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/LICENSE.txtnew file+202
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_client.gonew file+352
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetDynamicData.gonew file+77
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetIAMInfo.gonew file+103
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetInstanceIdentityDocument.gonew file+110
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetMetadata.gonew file+77
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetRegion.gonew file+73
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetToken.gonew file+119
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_op_GetUserData.gonew file+61
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/auth.gonew file+48
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/doc.gonew file+12
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/endpoints.gonew file+20
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/go_module_metadata.gonew file+6
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/internal/config/resolvers.gonew file+114
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/request_middleware.gonew file+313
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/token_provider.gonew file+261
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/auth/auth.gonew file+45
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/auth/scheme.gonew file+191
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/auth/smithy/bearer_token_adapter.gonew file+43
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/auth/smithy/bearer_token_signer_adapter.gonew file+35
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/auth/smithy/credentials_adapter.gonew file+46
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/auth/smithy/smithy.gonew file+2
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/auth/smithy/v4signer_adapter.gonew file+57
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/CHANGELOG.mdnew file+320
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/LICENSE.txtnew file+202
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/config.gonew file+65
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/endpoints.gonew file+57
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/configsources/go_module_metadata.gonew file+6
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/context/context.gonew file+52
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/arn.gonew file+94
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/doc.gonew file+3
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/generate.gonew file+7
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/host.gonew file+51
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/partition.gonew file+76
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/partitions.gonew file+403
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/awsrulesfn/partitions.jsonnew file+220
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/endpoints.gonew file+201
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/CHANGELOG.mdnew file+294
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/LICENSE.txtnew file+202
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/endpoints.gonew file+302
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/endpoints/v2/go_module_metadata.gonew file+6
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/ini/CHANGELOG.mdnew file+271
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/ini/LICENSE.txtnew file+202
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/ini/errors.gonew file+22
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/ini/go_module_metadata.gonew file+6
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/ini/ini.gonew file+56
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/ini/parse.gonew file+109
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/ini/sections.gonew file+157
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/ini/strings.gonew file+89
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/ini/token.gonew file+32
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/ini/tokenize.gonew file+92
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/ini/value.gonew file+93
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/middleware/middleware.gonew file+42
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/rand/rand.gonew file+33
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/sdk/interfaces.gonew file+9
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/sdk/time.gonew file+74
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/sdkio/byte.gonew file+12
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/shareddefaults/shared_config.gonew file+47
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/strings/strings.gonew file+11
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight/LICENSEnew file+28
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight/docs.gonew file+7
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/sync/singleflight/singleflight.gonew file+210
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/internal/timeconv/duration.gonew file+13
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/CHANGELOG.mdnew file+140
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/LICENSE.txtnew file+202
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/accept_encoding_gzip.gonew file+176
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/doc.gonew file+22
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding/go_module_metadata.gonew file+6
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/CHANGELOG.mdnew file+346
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/LICENSE.txtnew file+202
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/context.gonew file+56
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/doc.gonew file+3
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/go_module_metadata.gonew file+6
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/internal/presigned-url/middleware.gonew file+110
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/CHANGELOG.mdnew file+475
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/LICENSE.txtnew file+202
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/api_client.gonew file+627
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/api_op_GetRoleCredentials.gonew file+153
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/api_op_ListAccountRoles.gonew file+251
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/api_op_ListAccounts.gonew file+249
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/api_op_Logout.gonew file+152
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/auth.gonew file+308
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/deserializers.gonew file+1161
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/doc.gonew file+27
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/endpoints.gonew file+550
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/generated.jsonnew file+35
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/go_module_metadata.gonew file+6
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/internal/endpoints/endpoints.gonew file+566
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/options.gonew file+227
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/serializers.gonew file+284
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/types/errors.gonew file+115
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/types/types.gonew file+63
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sso/validators.gonew file+175
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/CHANGELOG.mdnew file+469
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/LICENSE.txtnew file+202
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/api_client.gonew file+627
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/api_op_CreateToken.gonew file+225
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/api_op_CreateTokenWithIAM.gonew file+256
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/api_op_RegisterClient.gonew file+186
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/api_op_StartDeviceAuthorization.gonew file+176
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/auth.gonew file+302
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/deserializers.gonew file+2167
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/doc.gonew file+46
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/endpoints.gonew file+550
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/generated.jsonnew file+35
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/go_module_metadata.gonew file+6
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/internal/endpoints/endpoints.gonew file+566
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/options.gonew file+227
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/serializers.gonew file+487
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/types/errors.gonew file+428
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/types/types.gonew file+9
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/ssooidc/validators.gonew file+184
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/CHANGELOG.mdnew file+493
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/LICENSE.txtnew file+202
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_client.gonew file+779
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_AssumeRole.gonew file+520
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_AssumeRoleWithSAML.gonew file+436
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_AssumeRoleWithWebIdentity.gonew file+447
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_DecodeAuthorizationMessage.gonew file+177
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_GetAccessKeyInfo.gonew file+168
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_GetCallerIdentity.gonew file+180
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_GetFederationToken.gonew file+381
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/api_op_GetSessionToken.gonew file+227
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/auth.gonew file+296
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/deserializers.gonew file+2516
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/doc.gonew file+13
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/endpoints.gonew file+1130
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/generated.jsonnew file+41
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/go_module_metadata.gonew file+6
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/internal/endpoints/endpoints.gonew file+512
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/options.gonew file+227
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/serializers.gonew file+862
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/types/errors.gonew file+248
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/types/types.gonew file+144
This diff is too big to show here.
vendor/github.com/aws/aws-sdk-go-v2/service/sts/validators.gonew file+305
This diff is too big to show here.
vendor/github.com/aws/smithy-go/.gitignorenew file+29
This diff is too big to show here.
vendor/github.com/aws/smithy-go/.travis.ymlnew file+28
This diff is too big to show here.
vendor/github.com/aws/smithy-go/CHANGELOG.mdnew file+239
This diff is too big to show here.
vendor/github.com/aws/smithy-go/CODE_OF_CONDUCT.mdnew file+4
This diff is too big to show here.
vendor/github.com/aws/smithy-go/CONTRIBUTING.mdnew file+59
This diff is too big to show here.
vendor/github.com/aws/smithy-go/LICENSEnew file+175
This diff is too big to show here.
vendor/github.com/aws/smithy-go/Makefilenew file+102
This diff is too big to show here.
vendor/github.com/aws/smithy-go/NOTICEnew file+1
This diff is too big to show here.
vendor/github.com/aws/smithy-go/README.mdnew file+27
This diff is too big to show here.
vendor/github.com/aws/smithy-go/auth/auth.gonew file+3
This diff is too big to show here.
vendor/github.com/aws/smithy-go/auth/bearer/docs.gonew file+3
This diff is too big to show here.
vendor/github.com/aws/smithy-go/auth/bearer/middleware.gonew file+104
This diff is too big to show here.
vendor/github.com/aws/smithy-go/auth/bearer/token.gonew file+50
This diff is too big to show here.
vendor/github.com/aws/smithy-go/auth/bearer/token_cache.gonew file+208
This diff is too big to show here.
vendor/github.com/aws/smithy-go/auth/identity.gonew file+47
This diff is too big to show here.
vendor/github.com/aws/smithy-go/auth/option.gonew file+25
This diff is too big to show here.
vendor/github.com/aws/smithy-go/auth/scheme_id.gonew file+20
This diff is too big to show here.
vendor/github.com/aws/smithy-go/context/suppress_expired.gonew file+81
This diff is too big to show here.
vendor/github.com/aws/smithy-go/doc.gonew file+2
This diff is too big to show here.
vendor/github.com/aws/smithy-go/document.gonew file+10
This diff is too big to show here.
vendor/github.com/aws/smithy-go/document/doc.gonew file+12
This diff is too big to show here.
vendor/github.com/aws/smithy-go/document/document.gonew file+153
This diff is too big to show here.
vendor/github.com/aws/smithy-go/document/errors.gonew file+75
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/doc.gonew file+4
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/encoding.gonew file+40
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/httpbinding/encode.gonew file+123
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/httpbinding/header.gonew file+122
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/httpbinding/path_replace.gonew file+108
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/httpbinding/query.gonew file+107
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/httpbinding/uri.gonew file+111
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/json/array.gonew file+35
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/json/constants.gonew file+15
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/json/decoder_util.gonew file+139
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/json/encoder.gonew file+30
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/json/escape.gonew file+198
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/json/object.gonew file+40
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/json/value.gonew file+149
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/xml/array.gonew file+49
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/xml/constants.gonew file+10
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/xml/doc.gonew file+49
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/xml/element.gonew file+91
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/xml/encoder.gonew file+51
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/xml/error_utils.gonew file+51
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/xml/escape.gonew file+137
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/xml/map.gonew file+53
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/xml/value.gonew file+302
This diff is too big to show here.
vendor/github.com/aws/smithy-go/encoding/xml/xml_decoder.gonew file+154
This diff is too big to show here.
vendor/github.com/aws/smithy-go/endpoints/endpoint.gonew file+23
This diff is too big to show here.
vendor/github.com/aws/smithy-go/errors.gonew file+137
This diff is too big to show here.
vendor/github.com/aws/smithy-go/go_module_metadata.gonew file+6
This diff is too big to show here.
vendor/github.com/aws/smithy-go/internal/sync/singleflight/LICENSEnew file+28
This diff is too big to show here.
vendor/github.com/aws/smithy-go/internal/sync/singleflight/docs.gonew file+8
This diff is too big to show here.
vendor/github.com/aws/smithy-go/internal/sync/singleflight/singleflight.gonew file+210
This diff is too big to show here.
vendor/github.com/aws/smithy-go/io/byte.gonew file+12
This diff is too big to show here.
vendor/github.com/aws/smithy-go/io/doc.gonew file+2
This diff is too big to show here.
vendor/github.com/aws/smithy-go/io/reader.gonew file+16
This diff is too big to show here.
vendor/github.com/aws/smithy-go/io/ringbuffer.gonew file+94
This diff is too big to show here.
vendor/github.com/aws/smithy-go/local-mod-replace.shnew file+39
This diff is too big to show here.
vendor/github.com/aws/smithy-go/logging/logger.gonew file+82
This diff is too big to show here.
vendor/github.com/aws/smithy-go/middleware/doc.gonew file+67
This diff is too big to show here.
vendor/github.com/aws/smithy-go/middleware/logging.gonew file+46
This diff is too big to show here.
vendor/github.com/aws/smithy-go/middleware/metadata.gonew file+65
This diff is too big to show here.
vendor/github.com/aws/smithy-go/middleware/middleware.gonew file+71
This diff is too big to show here.
vendor/github.com/aws/smithy-go/middleware/ordered_group.gonew file+268
This diff is too big to show here.
vendor/github.com/aws/smithy-go/middleware/stack.gonew file+209
This diff is too big to show here.
vendor/github.com/aws/smithy-go/middleware/stack_values.gonew file+100
This diff is too big to show here.
vendor/github.com/aws/smithy-go/middleware/step_build.gonew file+211
This diff is too big to show here.
vendor/github.com/aws/smithy-go/middleware/step_deserialize.gonew file+217
This diff is too big to show here.
vendor/github.com/aws/smithy-go/middleware/step_finalize.gonew file+211
This diff is too big to show here.
vendor/github.com/aws/smithy-go/middleware/step_initialize.gonew file+211
This diff is too big to show here.
vendor/github.com/aws/smithy-go/middleware/step_serialize.gonew file+219
This diff is too big to show here.
vendor/github.com/aws/smithy-go/modman.tomlnew file+10
This diff is too big to show here.
vendor/github.com/aws/smithy-go/private/requestcompression/gzip.gonew file+30
This diff is too big to show here.
vendor/github.com/aws/smithy-go/private/requestcompression/middleware_capture_request_compression.gonew file+52
This diff is too big to show here.
vendor/github.com/aws/smithy-go/private/requestcompression/request_compression.gonew file+103
This diff is too big to show here.
vendor/github.com/aws/smithy-go/properties.gonew file+62
This diff is too big to show here.
vendor/github.com/aws/smithy-go/ptr/doc.gonew file+5
This diff is too big to show here.
vendor/github.com/aws/smithy-go/ptr/from_ptr.gonew file+601
This diff is too big to show here.
vendor/github.com/aws/smithy-go/ptr/gen_scalars.gonew file+83
This diff is too big to show here.
vendor/github.com/aws/smithy-go/ptr/to_ptr.gonew file+499
This diff is too big to show here.
vendor/github.com/aws/smithy-go/rand/doc.gonew file+3
This diff is too big to show here.
vendor/github.com/aws/smithy-go/rand/rand.gonew file+31
This diff is too big to show here.
vendor/github.com/aws/smithy-go/rand/uuid.gonew file+87
This diff is too big to show here.
vendor/github.com/aws/smithy-go/time/time.gonew file+134
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/auth.gonew file+21
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/auth_schemes.gonew file+45
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/checksum_middleware.gonew file+70
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/client.gonew file+120
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/doc.gonew file+5
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/headerlist.gonew file+163
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/host.gonew file+89
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/internal/io/safe.gonew file+75
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/md5_checksum.gonew file+25
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/middleware_close_response_body.gonew file+79
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/middleware_content_length.gonew file+84
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/middleware_header_comment.gonew file+81
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/middleware_headers.gonew file+167
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/middleware_http_logging.gonew file+75
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/middleware_metadata.gonew file+51
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/middleware_min_proto.gonew file+79
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/properties.gonew file+80
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/request.gonew file+189
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/response.gonew file+34
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/time.gonew file+13
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/url.gonew file+44
This diff is too big to show here.
vendor/github.com/aws/smithy-go/transport/http/user_agent.gonew file+37
This diff is too big to show here.
vendor/github.com/aws/smithy-go/validation.gonew file+140
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-osc52/v2/LICENSEnew file+21
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-osc52/v2/README.mdnew file+83
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-osc52/v2/osc52.gonew file+305
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-udiff/LICENSE-BSDnew file+27
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-udiff/LICENSE-MITnew file+21
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-udiff/README.mdnew file+142
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-udiff/diff.gonew file+177
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-udiff/export.gonew file+16
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-udiff/lcs/common.gonew file+179
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-udiff/lcs/doc.gonew file+156
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-udiff/lcs/git.shnew file+33
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-udiff/lcs/labels.gonew file+55
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-udiff/lcs/old.gonew file+478
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-udiff/lcs/sequence.gonew file+113
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-udiff/merge.gonew file+81
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-udiff/myers/diff.gonew file+246
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-udiff/ndiff.gonew file+99
This diff is too big to show here.
vendor/github.com/aymanbagabas/go-udiff/unified.gonew file+251
This diff is too big to show here.
vendor/github.com/aymerick/douceur/LICENSEnew file+22
This diff is too big to show here.
vendor/github.com/aymerick/douceur/css/declaration.gonew file+60
This diff is too big to show here.
vendor/github.com/aymerick/douceur/css/rule.gonew file+230
This diff is too big to show here.
vendor/github.com/aymerick/douceur/css/stylesheet.gonew file+25
This diff is too big to show here.
vendor/github.com/aymerick/douceur/parser/parser.gonew file+409
This diff is too big to show here.
vendor/github.com/bmatcuk/doublestar/v4/.codecov.ymlnew file+10
This diff is too big to show here.
vendor/github.com/bmatcuk/doublestar/v4/.gitignorenew file+32
This diff is too big to show here.
vendor/github.com/bmatcuk/doublestar/v4/LICENSEnew file+22
This diff is too big to show here.
vendor/github.com/bmatcuk/doublestar/v4/README.mdnew file+433
This diff is too big to show here.
vendor/github.com/bmatcuk/doublestar/v4/UPGRADING.mdnew file+63
This diff is too big to show here.
vendor/github.com/bmatcuk/doublestar/v4/doublestar.gonew file+13
This diff is too big to show here.
vendor/github.com/bmatcuk/doublestar/v4/glob.gonew file+472
This diff is too big to show here.
vendor/github.com/bmatcuk/doublestar/v4/globoptions.gonew file+144
This diff is too big to show here.
vendor/github.com/bmatcuk/doublestar/v4/globwalk.gonew file+414
This diff is too big to show here.
vendor/github.com/bmatcuk/doublestar/v4/match.gonew file+403
This diff is too big to show here.
vendor/github.com/bmatcuk/doublestar/v4/utils.gonew file+160
This diff is too big to show here.
vendor/github.com/bmatcuk/doublestar/v4/validate.gonew file+82
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/.gitignorenew file+2
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/LICENSEnew file+21
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/Makefilenew file+101
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/README.mdnew file+193
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/adapters.gonew file+102
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/dirent.gonew file+73
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/dirent_portable.gonew file+134
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/dirent_unix.gonew file+146
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/entry_filter_portable.gonew file+37
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/entry_filter_unix.gonew file+59
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/entry_filter_windows.gonew file+156
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/fastwalk.gonew file+697
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/fastwalk_darwin.gonew file+122
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/fastwalk_portable.gonew file+65
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/fastwalk_unix.gonew file+131
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/go.LICENSEnew file+27
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent.gonew file+109
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_aix.gonew file+29
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_dragonfly.gonew file+54
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_freebsd.gonew file+46
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_js.gonew file+27
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_linux.gonew file+50
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_netbsd.gonew file+46
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_openbsd.gonew file+46
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/internal/dirent/dirent_solaris.gonew file+29
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/internal/dirent/doc.gonew file+2
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/internal/dirent/endian_big.gonew file+9
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/internal/dirent/endian_little.gonew file+9
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/internal/fmtdirent/fmtdirent_go120.gonew file+31
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/internal/fmtdirent/fmtdirent_go121.gonew file+15
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/zsyscall_darwin.gonew file+93
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/zsyscall_darwin_amd64.1_13.snew file+22
This diff is too big to show here.
vendor/github.com/charlievieth/fastwalk/zsyscall_darwin_arm64.1_13.snew file+22
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbles/v2/LICENSEnew file+21
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbles/v2/cursor/cursor.gonew file+241
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbles/v2/filepicker/filepicker.gonew file+526
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbles/v2/filepicker/hidden_unix.gonew file+11
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbles/v2/filepicker/hidden_windows.gonew file+21
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbles/v2/help/help.gonew file+243
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbles/v2/internal/memoization/memoization.gonew file+125
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbles/v2/internal/runeutil/runeutil.gonew file+102
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbles/v2/key/key.gonew file+140
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbles/v2/spinner/spinner.gonew file+211
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbles/v2/textarea/textarea.gonew file+1682
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbles/v2/textinput/styles.gonew file+96
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbles/v2/textinput/textinput.gonew file+968
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbles/v2/viewport/highlight.gonew file+141
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbles/v2/viewport/keymap.gonew file+58
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbles/v2/viewport/viewport.gonew file+754
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/.gitattributesnew file+1
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/.gitignorenew file+23
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/.golangci.ymlnew file+41
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/.goreleaser.ymlnew file+5
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/LICENSEnew file+21
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/README.mdnew file+412
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/Taskfile.yamlnew file+14
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/clipboard.gonew file+69
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/color.gonew file+121
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/commands.gonew file+186
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/cursed_renderer.gonew file+450
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/cursor.gonew file+26
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/environ.gonew file+34
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/exec.gonew file+129
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/focus.gonew file+9
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/input.gonew file+67
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/key.gonew file+371
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/keyboard.gonew file+160
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/logging.gonew file+53
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/mod.gonew file+27
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/mode.gonew file+8
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/mouse.gonew file+144
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/nil_renderer.gonew file+70
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/options.gonew file+302
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/paste.gonew file+13
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/profile.gonew file+15
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/raw.gonew file+37
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/renderer.gonew file+124
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/screen.gonew file+154
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/signals_unix.gonew file+33
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/signals_windows.gonew file+10
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/standard_renderer.gonew file+384
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/tea.gonew file+1474
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/termcap.gonew file+41
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/termios_bsd.gonew file+13
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/termios_other.gonew file+8
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/termios_unix.gonew file+14
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/tty.gonew file+179
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/tty_unix.gonew file+54
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/tty_windows.gonew file+74
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/util.gonew file+8
This diff is too big to show here.
vendor/github.com/charmbracelet/bubbletea/v2/xterm.gonew file+14
This diff is too big to show here.
vendor/github.com/charmbracelet/colorprofile/.golangci.ymlnew file+41
This diff is too big to show here.
vendor/github.com/charmbracelet/colorprofile/.goreleaser.ymlnew file+6
This diff is too big to show here.
vendor/github.com/charmbracelet/colorprofile/LICENSEnew file+21
This diff is too big to show here.
vendor/github.com/charmbracelet/colorprofile/README.mdnew file+103
This diff is too big to show here.
vendor/github.com/charmbracelet/colorprofile/doc.gonew file+4
This diff is too big to show here.
vendor/github.com/charmbracelet/colorprofile/env.gonew file+293
This diff is too big to show here.
vendor/github.com/charmbracelet/colorprofile/env_other.gonew file+8
This diff is too big to show here.
vendor/github.com/charmbracelet/colorprofile/env_windows.gonew file+45
This diff is too big to show here.
vendor/github.com/charmbracelet/colorprofile/profile.go+399
This diff is too big to show here.
vendor/github.com/charmbracelet/colorprofile/writer.go+169
This diff is too big to show here.
vendor/github.com/charmbracelet/fang/.gitignore+2
This diff is too big to show here.
vendor/github.com/charmbracelet/fang/.golangci.yml+40
This diff is too big to show here.
vendor/github.com/charmbracelet/fang/.goreleaser.yml+5
This diff is too big to show here.
vendor/github.com/charmbracelet/fang/LICENSE.md+21
This diff is too big to show here.
vendor/github.com/charmbracelet/fang/README.md+91
This diff is too big to show here.
vendor/github.com/charmbracelet/fang/Taskfile.yml+7
This diff is too big to show here.
vendor/github.com/charmbracelet/fang/fang.go+149
This diff is too big to show here.
vendor/github.com/charmbracelet/fang/help.go+340
This diff is too big to show here.
vendor/github.com/charmbracelet/fang/theme.go+182
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/.editorconfig+18
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/.gitattributes+2
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/.gitignore+2
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/.golangci.yml+49
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/.goreleaser.yml+6
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/CONTRIBUTING.md+23
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/LICENSE+21
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/README.md+96
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/Taskfile.yml+25
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/ansi.go+3
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/baseelement.go+157
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/blockelement.go+65
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/blockstack.go+95
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/codeblock.go+150
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/codespan.go+15
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/context.go+38
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/elements.go+479
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/emphasis.go+47
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/heading.go+79
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/image.go+57
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/link.go+112
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/listitem.go+29
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/margin.go+189
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/paragraph.go+62
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/renderer.go+166
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/style.go+252
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/table.go+205
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/table_links.go+234
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/task.go+27
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/ansi/templatehelper.go+86
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/examples.sh+19
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/gallery.sh+23
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/glamour.go+294
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/internal/autolink/autolink.go+55
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/styles/README.md+565
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/styles/ascii.json+83
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/styles/dark.json+191
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/styles/dracula.go+216
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/styles/dracula.json+188
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/styles/light.json+190
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/styles/notty.json+83
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/styles/pink.json+90
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/styles/styles.go+680
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/styles/tokyo-night.go+209
This diff is too big to show here.
vendor/github.com/charmbracelet/glamour/v2/styles/tokyo_night.json+182
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/.editorconfig+18
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/.gitattributes+1
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/.gitignore+2
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/.golangci.yml+49
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/.goreleaser.yml+5
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/LICENSE+21
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/README.md+792
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/Taskfile.yaml+19
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/align.go+82
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/ansi_unix.go+9
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/ansi_windows.go+31
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/borders.go+489
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/canvas.go+267
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/color.go+227
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/get.go+571
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/join.go+175
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/lipgloss.go+3
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/position.go+134
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/query.go+89
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/ranges.go+48
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/runes.go+43
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/set.go+795
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/size.go+40
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/style.go+571
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/table/resizing.go+481
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/table/rows.go+130
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/table/table.go+561
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/table/util.go+44
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/terminal.go+114
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/tree/children.go+100
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/tree/enumerator.go+74
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/tree/renderer.go+142
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/tree/tree.go+369
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/unset.go+331
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/whitespace.go+69
This diff is too big to show here.
vendor/github.com/charmbracelet/lipgloss/v2/writer.go+160
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/.gitignore+17
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/.golangci-soft.yml+40
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/.golangci.yml+28
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/.goreleaser.yml+3
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/LICENSE+21
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/README.md+367
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/context.go+23
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/formatter.go+27
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/json.go+151
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/level.go+65
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/logfmt.go+32
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/logger.go+411
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/logger_121.go+71
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/logger_no121.go+68
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/options.go+61
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/pkg.go+248
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/stdlog.go+67
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/styles.go+83
This diff is too big to show here.
vendor/github.com/charmbracelet/log/v2/text.go+272
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/.gitattributes+1
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/.golangci.yml+40
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/.goreleaser.yml+5
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/LICENSE+5
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/README.md+25
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/TUTORIAL.md+207
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/border.go+231
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/buffer.go+612
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/cancelreader_other.go+14
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/cancelreader_windows.go+135
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/cell.go+519
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/doc.go+6
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/environ.go+32
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/event.go+443
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/input.go+61
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/key.go+766
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/key_table.go+664
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/mouse.go+86
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/parse.go+1766
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/styled.go+292
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/tabstop.go+142
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/terminal.go+1090
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/terminal_other.go+17
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/terminal_reader.go+357
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/terminal_reader_other.go+18
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/terminal_reader_windows.go+624
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/terminal_renderer.go+1584
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/terminal_renderer_hardscroll.go+275
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/terminal_renderer_hashmap.go+294
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/terminal_unix.go+72
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/terminal_windows.go+93
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/tty.go+18
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/tty_other.go+14
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/tty_unix.go+28
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/tty_windows.go+27
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/utils.go+15
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/uv.go+52
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/winch.go+29
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/winch_other.go+15
This diff is too big to show here.
vendor/github.com/charmbracelet/ultraviolet/winch_unix.go+86
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/LICENSE+21
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/ansi.go+11
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/ascii.go+8
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/background.go+178
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/c0.go+79
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/c1.go+72
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/charset.go+55
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/clipboard.go+75
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/color.go+784
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/ctrl.go+156
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/cursor.go+635
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/cwd.go+26
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/doc.go+7
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/finalterm.go+67
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/focus.go+9
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/graphics.go+237
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/hyperlink.go+28
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/iterm2.go+18
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/keypad.go+28
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/kitty.go+90
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/kitty/decoder.go+85
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/kitty/encoder.go+64
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/kitty/graphics.go+414
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/kitty/options.go+367
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/method.go+172
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/mode.go+778
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/modes.go+65
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/mouse.go+172
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/notification.go+13
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/parser.go+417
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/parser/const.go+78
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/parser/seq.go+136
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/parser/transition_table.go+273
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/parser_decode.go+524
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/parser_handler.go+60
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/parser_sync.go+29
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/passthrough.go+60
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/paste.go+7
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/reset.go+11
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/screen.go+410
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/sgr.go+79
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/status.go+144
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/style.go+673
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/termcap.go+41
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/title.go+48
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/truncate.go+299
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/util.go+106
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/width.go+113
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/winop.go+53
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/wrap.go+474
This diff is too big to show here.
vendor/github.com/charmbracelet/x/ansi/xterm.go+138
This diff is too big to show here.
vendor/github.com/charmbracelet/x/cellbuf/LICENSE+21
This diff is too big to show here.
vendor/github.com/charmbracelet/x/cellbuf/buffer.go+473
This diff is too big to show here.
vendor/github.com/charmbracelet/x/cellbuf/cell.go+508
This diff is too big to show here.
vendor/github.com/charmbracelet/x/cellbuf/errors.go+6
This diff is too big to show here.
vendor/github.com/charmbracelet/x/cellbuf/geom.go+21
This diff is too big to show here.
vendor/github.com/charmbracelet/x/cellbuf/hardscroll.go+273
This diff is too big to show here.
vendor/github.com/charmbracelet/x/cellbuf/hashmap.go+301
This diff is too big to show here.
vendor/github.com/charmbracelet/x/cellbuf/link.go+14
This diff is too big to show here.
vendor/github.com/charmbracelet/x/cellbuf/pen.go+88
This diff is too big to show here.
vendor/github.com/charmbracelet/x/cellbuf/screen.go+1506
This diff is too big to show here.
vendor/github.com/charmbracelet/x/cellbuf/sequence.go+131
This diff is too big to show here.
vendor/github.com/charmbracelet/x/cellbuf/style.go+31
This diff is too big to show here.
vendor/github.com/charmbracelet/x/cellbuf/tabstop.go+137
This diff is too big to show here.
vendor/github.com/charmbracelet/x/cellbuf/utils.go+38
This diff is too big to show here.
vendor/github.com/charmbracelet/x/cellbuf/wrap.go+194
This diff is too big to show here.
vendor/github.com/charmbracelet/x/cellbuf/writer.go+339
This diff is too big to show here.
vendor/github.com/charmbracelet/x/exp/charmtone/LICENSE+21
This diff is too big to show here.
vendor/github.com/charmbracelet/x/exp/charmtone/charmtone.go+362
This diff is too big to show here.
vendor/github.com/charmbracelet/x/exp/golden/LICENSE+21
This diff is too big to show here.
vendor/github.com/charmbracelet/x/exp/golden/golden.go+82
This diff is too big to show here.
vendor/github.com/charmbracelet/x/exp/slice/LICENSE+21
This diff is too big to show here.
vendor/github.com/charmbracelet/x/exp/slice/slice.go+120
This diff is too big to show here.
vendor/github.com/charmbracelet/x/term/LICENSE+21
This diff is too big to show here.
vendor/github.com/charmbracelet/x/term/term.go+49
This diff is too big to show here.
vendor/github.com/charmbracelet/x/term/term_other.go+39
This diff is too big to show here.
vendor/github.com/charmbracelet/x/term/term_unix.go+96
This diff is too big to show here.
vendor/github.com/charmbracelet/x/term/term_unix_bsd.go+11
This diff is too big to show here.
vendor/github.com/charmbracelet/x/term/term_unix_other.go+11
This diff is too big to show here.
vendor/github.com/charmbracelet/x/term/term_windows.go+87
This diff is too big to show here.
vendor/github.com/charmbracelet/x/term/terminal.go+12
This diff is too big to show here.
vendor/github.com/charmbracelet/x/term/util.go+47
This diff is too big to show here.
vendor/github.com/charmbracelet/x/termios/LICENSE+21
This diff is too big to show here.
vendor/github.com/charmbracelet/x/termios/bit_bsd.go+7
This diff is too big to show here.
vendor/github.com/charmbracelet/x/termios/bit_darwin.go+7
This diff is too big to show here.
vendor/github.com/charmbracelet/x/termios/bit_other.go+7
This diff is too big to show here.
vendor/github.com/charmbracelet/x/termios/syscalls_bsd.go+11
This diff is too big to show here.
vendor/github.com/charmbracelet/x/termios/syscalls_darwin.go+10
This diff is too big to show here.
vendor/github.com/charmbracelet/x/termios/syscalls_linux.go+14
This diff is too big to show here.
vendor/github.com/charmbracelet/x/termios/termios.go+249
This diff is too big to show here.
vendor/github.com/charmbracelet/x/termios/termios_bsd.go+13
This diff is too big to show here.
vendor/github.com/charmbracelet/x/termios/termios_linux.go+13
This diff is too big to show here.
vendor/github.com/charmbracelet/x/termios/termios_other.go+15
This diff is too big to show here.
vendor/github.com/charmbracelet/x/termios/termios_solaris.go+26
This diff is too big to show here.
vendor/github.com/charmbracelet/x/windows/LICENSE+21
This diff is too big to show here.
vendor/github.com/charmbracelet/x/windows/doc.go+3
This diff is too big to show here.
vendor/github.com/charmbracelet/x/windows/syscall_windows.go+12
This diff is too big to show here.
vendor/github.com/charmbracelet/x/windows/types_windows.go+351
This diff is too big to show here.
vendor/github.com/charmbracelet/x/windows/zsyscall_windows.go+77
This diff is too big to show here.
vendor/github.com/davecgh/go-spew/LICENSE+15
This diff is too big to show here.
vendor/github.com/davecgh/go-spew/spew/bypass.go+145
This diff is too big to show here.
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go+38
This diff is too big to show here.
vendor/github.com/davecgh/go-spew/spew/common.go+341
This diff is too big to show here.
vendor/github.com/davecgh/go-spew/spew/config.go+306
This diff is too big to show here.
vendor/github.com/davecgh/go-spew/spew/doc.go+211
This diff is too big to show here.
vendor/github.com/davecgh/go-spew/spew/dump.go+509
This diff is too big to show here.
vendor/github.com/davecgh/go-spew/spew/format.go+419
This diff is too big to show here.
vendor/github.com/davecgh/go-spew/spew/spew.go+148
This diff is too big to show here.
vendor/github.com/disintegration/gift/.travis.yml+17
This diff is too big to show here.
vendor/github.com/disintegration/gift/LICENSE+21
This diff is too big to show here.
vendor/github.com/disintegration/gift/README.md+251
This diff is too big to show here.
vendor/github.com/disintegration/gift/colors.go+511
This diff is too big to show here.
vendor/github.com/disintegration/gift/convolution.go+578
This diff is too big to show here.
vendor/github.com/disintegration/gift/effects.go+87
This diff is too big to show here.
vendor/github.com/disintegration/gift/gift.go+219
This diff is too big to show here.
vendor/github.com/disintegration/gift/pixels.go+493
This diff is too big to show here.
vendor/github.com/disintegration/gift/rank.go+223
This diff is too big to show here.
vendor/github.com/disintegration/gift/resize.go+462
This diff is too big to show here.
vendor/github.com/disintegration/gift/transform.go+498
This diff is too big to show here.
vendor/github.com/disintegration/gift/utils.go+242
This diff is too big to show here.
vendor/github.com/disintegration/imageorient/LICENSE+21
This diff is too big to show here.
vendor/github.com/disintegration/imageorient/README.md+61
This diff is too big to show here.
vendor/github.com/disintegration/imageorient/imageorient.go+209
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/.gitignore+27
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/.travis.yml+7
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/ATTRIB+133
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/LICENSE+21
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/README.md+174
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/fastclock.go+129
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/match.go+349
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/regexp.go+395
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/replace.go+177
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/runner.go+1613
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/syntax/charclass.go+865
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/syntax/code.go+274
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/syntax/escape.go+94
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/syntax/fuzz.go+20
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/syntax/parser.go+2262
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/syntax/prefix.go+896
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/syntax/replacerdata.go+87
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/syntax/tree.go+654
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/syntax/writer.go+500
This diff is too big to show here.
vendor/github.com/dlclark/regexp2/testoutput1+7061
This diff is too big to show here.
vendor/github.com/dustin/go-humanize/.travis.yml+21
This diff is too big to show here.
vendor/github.com/dustin/go-humanize/LICENSE+21
This diff is too big to show here.
vendor/github.com/dustin/go-humanize/README.markdown+124
This diff is too big to show here.
vendor/github.com/dustin/go-humanize/big.go+31
This diff is too big to show here.
vendor/github.com/dustin/go-humanize/bigbytes.go+189
This diff is too big to show here.
vendor/github.com/dustin/go-humanize/bytes.go+143
This diff is too big to show here.
vendor/github.com/dustin/go-humanize/comma.go+116
This diff is too big to show here.
vendor/github.com/dustin/go-humanize/commaf.go+41
This diff is too big to show here.
vendor/github.com/dustin/go-humanize/ftoa.go+49
This diff is too big to show here.
vendor/github.com/dustin/go-humanize/humanize.go+8
This diff is too big to show here.
vendor/github.com/dustin/go-humanize/number.go+192
This diff is too big to show here.
vendor/github.com/dustin/go-humanize/ordinals.go+25
This diff is too big to show here.
vendor/github.com/dustin/go-humanize/si.go+127
This diff is too big to show here.
vendor/github.com/dustin/go-humanize/times.go+117
This diff is too big to show here.
vendor/github.com/felixge/httpsnoop/.gitignore
This diff is too big to show here.
vendor/github.com/felixge/httpsnoop/LICENSE.txt+19
This diff is too big to show here.
vendor/github.com/felixge/httpsnoop/Makefile+10
This diff is too big to show here.
vendor/github.com/felixge/httpsnoop/README.md+95
This diff is too big to show here.
vendor/github.com/felixge/httpsnoop/capture_metrics.go+86
This diff is too big to show here.
vendor/github.com/felixge/httpsnoop/docs.go+10
This diff is too big to show here.
vendor/github.com/felixge/httpsnoop/wrap_generated_gteq_1.8.go+436
This diff is too big to show here.
vendor/github.com/felixge/httpsnoop/wrap_generated_lt_1.8.go+278
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/.cirrus.yml+14
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/.gitignore+10
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/.mailmap+2
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md+569
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md+144
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/LICENSE+25
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/README.md+184
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/backend_fen.go+484
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/backend_inotify.go+658
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/backend_kqueue.go+733
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/backend_other.go+23
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/backend_windows.go+682
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/fsnotify.go+494
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/internal/darwin.go+39
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/internal/debug_darwin.go+57
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/internal/debug_dragonfly.go+33
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/internal/debug_freebsd.go+42
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/internal/debug_kqueue.go+32
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/internal/debug_linux.go+56
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/internal/debug_netbsd.go+25
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/internal/debug_openbsd.go+28
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/internal/debug_solaris.go+45
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/internal/debug_windows.go+40
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/internal/freebsd.go+31
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/internal/internal.go+2
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/internal/unix.go+31
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/internal/unix2.go+7
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/internal/windows.go+41
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/system_bsd.go+7
This diff is too big to show here.
vendor/github.com/fsnotify/fsnotify/system_darwin.go+8
This diff is too big to show here.
vendor/github.com/go-logfmt/logfmt/.gitignore+1
This diff is too big to show here.
vendor/github.com/go-logfmt/logfmt/CHANGELOG.md+82
This diff is too big to show here.
vendor/github.com/go-logfmt/logfmt/LICENSE+22
This diff is too big to show here.
vendor/github.com/go-logfmt/logfmt/README.md+41
This diff is too big to show here.
vendor/github.com/go-logfmt/logfmt/decode.go+254
This diff is too big to show here.
vendor/github.com/go-logfmt/logfmt/doc.go+6
This diff is too big to show here.
vendor/github.com/go-logfmt/logfmt/encode.go+322
This diff is too big to show here.
vendor/github.com/go-logfmt/logfmt/jsonstring.go+277
This diff is too big to show here.
vendor/github.com/go-logr/logr/.golangci.yaml+26
This diff is too big to show here.
vendor/github.com/go-logr/logr/CHANGELOG.md+6
This diff is too big to show here.
vendor/github.com/go-logr/logr/CONTRIBUTING.md+17
This diff is too big to show here.
vendor/github.com/go-logr/logr/LICENSE+201
This diff is too big to show here.
vendor/github.com/go-logr/logr/README.md+407
This diff is too big to show here.
vendor/github.com/go-logr/logr/SECURITY.md+18
This diff is too big to show here.
vendor/github.com/go-logr/logr/context.go+33
This diff is too big to show here.
vendor/github.com/go-logr/logr/context_noslog.go+49
This diff is too big to show here.
vendor/github.com/go-logr/logr/context_slog.go+83
This diff is too big to show here.
vendor/github.com/go-logr/logr/discard.go+24
This diff is too big to show here.
vendor/github.com/go-logr/logr/funcr/funcr.go+914
This diff is too big to show here.
vendor/github.com/go-logr/logr/funcr/slogsink.go+105
This diff is too big to show here.
vendor/github.com/go-logr/logr/logr.go+520
This diff is too big to show here.
vendor/github.com/go-logr/logr/sloghandler.go+192
This diff is too big to show here.
vendor/github.com/go-logr/logr/slogr.go+100
This diff is too big to show here.
vendor/github.com/go-logr/logr/slogsink.go+120
This diff is too big to show here.
vendor/github.com/go-logr/stdr/LICENSE+201
This diff is too big to show here.
vendor/github.com/go-logr/stdr/README.md+6
This diff is too big to show here.
vendor/github.com/go-logr/stdr/stdr.go+170
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/.gitignore+4
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/LICENSE+9
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/MIGRATION_GUIDE.md+195
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/README.md+167
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/SECURITY.md+19
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/VERSION_HISTORY.md+137
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/claims.go+16
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/doc.go+4
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/ecdsa.go+134
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/ecdsa_utils.go+69
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/ed25519.go+79
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/ed25519_utils.go+64
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/errors.go+49
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/errors_go1_20.go+47
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/errors_go_other.go+78
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/hmac.go+104
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/map_claims.go+109
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/none.go+50
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/parser.go+268
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/parser_option.go+128
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/registered_claims.go+63
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/rsa.go+93
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/rsa_pss.go+135
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/rsa_utils.go+107
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/signing_method.go+49
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/staticcheck.conf+1
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/token.go+100
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/token_option.go+5
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/types.go+149
This diff is too big to show here.
vendor/github.com/golang-jwt/jwt/v5/validator.go+316
This diff is too big to show here.
vendor/github.com/google/go-cmp/LICENSE+27
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/compare.go+671
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/export.go+31
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go+18
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go+123
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go+402
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go+9
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/internal/function/func.go+106
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/internal/value/name.go+164
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/internal/value/pointer.go+34
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/internal/value/sort.go+106
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/options.go+562
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/path.go+390
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/report.go+54
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/report_compare.go+433
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/report_references.go+264
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/report_reflect.go+414
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/report_slices.go+614
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/report_text.go+432
This diff is too big to show here.
vendor/github.com/google/go-cmp/cmp/report_value.go+121
This diff is too big to show here.
vendor/github.com/google/s2a-go/.gitignore+6
This diff is too big to show here.
vendor/github.com/google/s2a-go/CODE_OF_CONDUCT.md+93
This diff is too big to show here.
vendor/github.com/google/s2a-go/CONTRIBUTING.md+29
This diff is too big to show here.
vendor/github.com/google/s2a-go/LICENSE.md+202
This diff is too big to show here.
vendor/github.com/google/s2a-go/README.md+14
This diff is too big to show here.
vendor/github.com/google/s2a-go/fallback/s2a_fallback.go+167
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/authinfo/authinfo.go+119
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/handshaker/handshaker.go+438
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/handshaker/service/service.go+66
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/proto/common_go_proto/common.pb.go+388
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/proto/s2a_context_go_proto/s2a_context.pb.go+267
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/proto/s2a_go_proto/s2a.pb.go+1377
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/proto/s2a_go_proto/s2a_grpc.pb.go+174
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/proto/v2/common_go_proto/common.pb.go+549
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/proto/v2/s2a_context_go_proto/s2a_context.pb.go+249
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/proto/v2/s2a_go_proto/s2a.pb.go+2512
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/proto/v2/s2a_go_proto/s2a_grpc.pb.go+160
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/record/internal/aeadcrypter/aeadcrypter.go+34
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/record/internal/aeadcrypter/aesgcm.go+70
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/record/internal/aeadcrypter/chachapoly.go+67
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/record/internal/aeadcrypter/common.go+92
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/record/internal/halfconn/ciphersuite.go+98
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/record/internal/halfconn/counter.go+60
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/record/internal/halfconn/expander.go+59
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/record/internal/halfconn/halfconn.go+193
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/record/record.go+729
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/record/ticketsender.go+178
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/tokenmanager/tokenmanager.go+79
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/v2/README.md+1
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/v2/certverifier/certverifier.go+122
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/v2/remotesigner/remotesigner.go+186
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/v2/s2av2.go+379
This diff is too big to show here.
vendor/github.com/google/s2a-go/internal/v2/tlsconfigstore/tlsconfigstore.go+403
This diff is too big to show here.
vendor/github.com/google/s2a-go/retry/retry.go+144
This diff is too big to show here.
vendor/github.com/google/s2a-go/s2a.go+442
This diff is too big to show here.
vendor/github.com/google/s2a-go/s2a_options.go+235
This diff is too big to show here.
vendor/github.com/google/s2a-go/s2a_utils.go+79
This diff is too big to show here.
vendor/github.com/google/s2a-go/stream/s2a_stream.go+34
This diff is too big to show here.
vendor/github.com/google/uuid/CHANGELOG.md+41
This diff is too big to show here.
vendor/github.com/google/uuid/CONTRIBUTING.md+26
This diff is too big to show here.
vendor/github.com/google/uuid/CONTRIBUTORS+9
This diff is too big to show here.
vendor/github.com/google/uuid/LICENSE+27
This diff is too big to show here.
vendor/github.com/google/uuid/README.md+21
This diff is too big to show here.
vendor/github.com/google/uuid/dce.go+80
This diff is too big to show here.
vendor/github.com/google/uuid/doc.go+12
This diff is too big to show here.
vendor/github.com/google/uuid/hash.go+59
This diff is too big to show here.
vendor/github.com/google/uuid/marshal.go+38
This diff is too big to show here.
vendor/github.com/google/uuid/node.go+90
This diff is too big to show here.
vendor/github.com/google/uuid/node_js.go+12
This diff is too big to show here.
vendor/github.com/google/uuid/node_net.go+33
This diff is too big to show here.
vendor/github.com/google/uuid/null.go+118
This diff is too big to show here.
vendor/github.com/google/uuid/sql.go+59
This diff is too big to show here.
vendor/github.com/google/uuid/time.go+134
This diff is too big to show here.
vendor/github.com/google/uuid/util.go+43
This diff is too big to show here.
vendor/github.com/google/uuid/uuid.go+365
This diff is too big to show here.
vendor/github.com/google/uuid/version1.go+44
This diff is too big to show here.
vendor/github.com/google/uuid/version4.go+76
This diff is too big to show here.
vendor/github.com/google/uuid/version6.go+56
This diff is too big to show here.
vendor/github.com/google/uuid/version7.go+104
This diff is too big to show here.
vendor/github.com/googleapis/enterprise-certificate-proxy/LICENSE+202
This diff is too big to show here.
vendor/github.com/googleapis/enterprise-certificate-proxy/client/client.go+219
This diff is too big to show here.
vendor/github.com/googleapis/enterprise-certificate-proxy/client/util/util.go+100
This diff is too big to show here.
vendor/github.com/googleapis/gax-go/v2/LICENSE+27
This diff is too big to show here.
vendor/github.com/googleapis/gax-go/v2/internallog/internal/internal.go+134
This diff is too big to show here.
vendor/github.com/googleapis/gax-go/v2/internallog/internallog.go+154
This diff is too big to show here.
vendor/github.com/gorilla/css/LICENSE+28
This diff is too big to show here.
vendor/github.com/gorilla/css/scanner/doc.go+33
This diff is too big to show here.
vendor/github.com/gorilla/css/scanner/scanner.go+360
This diff is too big to show here.
vendor/github.com/gorilla/websocket/.gitignore+25
This diff is too big to show here.
vendor/github.com/gorilla/websocket/AUTHORS+9
This diff is too big to show here.
vendor/github.com/gorilla/websocket/LICENSE+22
This diff is too big to show here.
vendor/github.com/gorilla/websocket/README.md+33
This diff is too big to show here.
vendor/github.com/gorilla/websocket/client.go+434
This diff is too big to show here.
vendor/github.com/gorilla/websocket/compression.go+148
This diff is too big to show here.
vendor/github.com/gorilla/websocket/conn.go+1238
This diff is too big to show here.
vendor/github.com/gorilla/websocket/doc.go+227
This diff is too big to show here.
vendor/github.com/gorilla/websocket/join.go+42
This diff is too big to show here.
vendor/github.com/gorilla/websocket/json.go+60
This diff is too big to show here.
vendor/github.com/gorilla/websocket/mask.go+55
This diff is too big to show here.
vendor/github.com/gorilla/websocket/mask_safe.go+16
This diff is too big to show here.
vendor/github.com/gorilla/websocket/prepared.go+102
This diff is too big to show here.
vendor/github.com/gorilla/websocket/proxy.go+77
This diff is too big to show here.
vendor/github.com/gorilla/websocket/server.go+365
This diff is too big to show here.
vendor/github.com/gorilla/websocket/tls_handshake.go+21
This diff is too big to show here.
vendor/github.com/gorilla/websocket/tls_handshake_116.go+21
This diff is too big to show here.
vendor/github.com/gorilla/websocket/util.go+298
This diff is too big to show here.
vendor/github.com/gorilla/websocket/x_net_proxy.go+473
This diff is too big to show here.
vendor/github.com/inconshreveable/mousetrap/LICENSE+201
This diff is too big to show here.
vendor/github.com/inconshreveable/mousetrap/README.md+23
This diff is too big to show here.
vendor/github.com/inconshreveable/mousetrap/trap_others.go+16
This diff is too big to show here.
vendor/github.com/inconshreveable/mousetrap/trap_windows.go+42
This diff is too big to show here.
vendor/github.com/joho/godotenv/.gitignore+1
This diff is too big to show here.
vendor/github.com/joho/godotenv/LICENCE+23
This diff is too big to show here.
vendor/github.com/joho/godotenv/README.md+202
This diff is too big to show here.
vendor/github.com/joho/godotenv/autoload/autoload.go+15
This diff is too big to show here.
vendor/github.com/joho/godotenv/godotenv.go+228
This diff is too big to show here.
vendor/github.com/joho/godotenv/parser.go+271
This diff is too big to show here.
vendor/github.com/kylelemons/godebug/LICENSE+202
This diff is too big to show here.
vendor/github.com/kylelemons/godebug/diff/diff.go+186
This diff is too big to show here.
vendor/github.com/kylelemons/godebug/pretty/.gitignore+5
This diff is too big to show here.
vendor/github.com/kylelemons/godebug/pretty/doc.go+25
This diff is too big to show here.
vendor/github.com/kylelemons/godebug/pretty/public.go+188
This diff is too big to show here.
vendor/github.com/kylelemons/godebug/pretty/reflect.go+241
This diff is too big to show here.
vendor/github.com/kylelemons/godebug/pretty/structure.go+223
This diff is too big to show here.
vendor/github.com/lucasb-eyer/go-colorful/.gitignore+101
This diff is too big to show here.
vendor/github.com/lucasb-eyer/go-colorful/CHANGELOG.md+42
This diff is too big to show here.
vendor/github.com/lucasb-eyer/go-colorful/LICENSE+7
This diff is too big to show here.
vendor/github.com/lucasb-eyer/go-colorful/README.md+482
This diff is too big to show here.
vendor/github.com/lucasb-eyer/go-colorful/colorgens.go+55
This diff is too big to show here.
vendor/github.com/lucasb-eyer/go-colorful/colors.go+979
This diff is too big to show here.
vendor/github.com/lucasb-eyer/go-colorful/happy_palettegen.go+25
This diff is too big to show here.
vendor/github.com/lucasb-eyer/go-colorful/hexcolor.go+67
This diff is too big to show here.
vendor/github.com/lucasb-eyer/go-colorful/hsluv-snapshot-rev4.json+1
This diff is too big to show here.
vendor/github.com/lucasb-eyer/go-colorful/hsluv.go+207
This diff is too big to show here.
vendor/github.com/lucasb-eyer/go-colorful/soft_palettegen.go+185
This diff is too big to show here.
vendor/github.com/lucasb-eyer/go-colorful/warm_palettegen.go+25
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/LICENSE+21
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/client/client.go+434
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/client/http.go+17
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/client/inprocess.go+12
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/client/interface.go+109
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/client/oauth.go+76
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/client/sse.go+42
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/client/stdio.go+43
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/client/transport/inprocess.go+70
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/client/transport/interface.go+50
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/client/transport/oauth.go+650
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/client/transport/oauth_utils.go+68
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/client/transport/sse.go+522
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/client/transport/stdio.go+288
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/client/transport/streamable_http.go+515
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/mcp/prompts.go+170
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/mcp/resources.go+99
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/mcp/tools.go+974
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/mcp/typed_tools.go+20
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/mcp/types.go+1044
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/mcp/utils.go+798
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/server/errors.go+34
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/server/hooks.go+532
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/server/http_transport_options.go+11
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/server/request_handler.go+320
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/server/server.go+1079
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/server/session.go+380
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/server/sse.go+736
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/server/stdio.go+314
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/server/streamable_http.go+653
This diff is too big to show here.
vendor/github.com/mark3labs/mcp-go/util/logger.go+33
This diff is too big to show here.
vendor/github.com/mattn/go-isatty/LICENSE+9
This diff is too big to show here.
vendor/github.com/mattn/go-isatty/README.md+50
This diff is too big to show here.
vendor/github.com/mattn/go-isatty/doc.go+2
This diff is too big to show here.
vendor/github.com/mattn/go-isatty/go.test.sh+12
This diff is too big to show here.
vendor/github.com/mattn/go-isatty/isatty_bsd.go+20
This diff is too big to show here.
vendor/github.com/mattn/go-isatty/isatty_others.go+17
This diff is too big to show here.
vendor/github.com/mattn/go-isatty/isatty_plan9.go+23
This diff is too big to show here.
vendor/github.com/mattn/go-isatty/isatty_solaris.go+21
This diff is too big to show here.
vendor/github.com/mattn/go-isatty/isatty_tcgets.go+20
This diff is too big to show here.
vendor/github.com/mattn/go-isatty/isatty_windows.go+125
This diff is too big to show here.
vendor/github.com/mattn/go-runewidth/LICENSE+21
This diff is too big to show here.
vendor/github.com/mattn/go-runewidth/README.md+27
This diff is too big to show here.
vendor/github.com/mattn/go-runewidth/runewidth.go+358
This diff is too big to show here.
vendor/github.com/mattn/go-runewidth/runewidth_appengine.go+9
This diff is too big to show here.
vendor/github.com/mattn/go-runewidth/runewidth_js.go+9
This diff is too big to show here.
vendor/github.com/mattn/go-runewidth/runewidth_posix.go+81
This diff is too big to show here.
vendor/github.com/mattn/go-runewidth/runewidth_table.go+450
This diff is too big to show here.
vendor/github.com/mattn/go-runewidth/runewidth_windows.go+28
This diff is too big to show here.
vendor/github.com/mfridman/interpolate/LICENSE.txt+24
This diff is too big to show here.
vendor/github.com/mfridman/interpolate/README.md+85
This diff is too big to show here.
vendor/github.com/mfridman/interpolate/env.go+53
This diff is too big to show here.
vendor/github.com/mfridman/interpolate/interpolate.go+213
This diff is too big to show here.
vendor/github.com/mfridman/interpolate/parser.go+281
This diff is too big to show here.
vendor/github.com/microcosm-cc/bluemonday/CONTRIBUTING.md+50
This diff is too big to show here.
vendor/github.com/microcosm-cc/bluemonday/CREDITS.md+8
This diff is too big to show here.
vendor/github.com/microcosm-cc/bluemonday/LICENSE.md+28
This diff is too big to show here.
vendor/github.com/microcosm-cc/bluemonday/README.md+386
This diff is too big to show here.
vendor/github.com/microcosm-cc/bluemonday/SECURITY.md+13
This diff is too big to show here.
vendor/github.com/microcosm-cc/bluemonday/css/handlers.go+2016
This diff is too big to show here.
vendor/github.com/microcosm-cc/bluemonday/doc.go+104
This diff is too big to show here.
vendor/github.com/microcosm-cc/bluemonday/helpers.go+300
This diff is too big to show here.
vendor/github.com/microcosm-cc/bluemonday/policies.go+253
This diff is too big to show here.
vendor/github.com/microcosm-cc/bluemonday/policy.go+990
This diff is too big to show here.
vendor/github.com/microcosm-cc/bluemonday/sanitize.go+1096
This diff is too big to show here.
vendor/github.com/muesli/cancelreader/.gitignore+15
This diff is too big to show here.
vendor/github.com/muesli/cancelreader/.golangci-soft.yml+47
This diff is too big to show here.
vendor/github.com/muesli/cancelreader/.golangci.yml+29
This diff is too big to show here.
vendor/github.com/muesli/cancelreader/LICENSE+21
This diff is too big to show here.
vendor/github.com/muesli/cancelreader/README.md+64
This diff is too big to show here.
vendor/github.com/muesli/cancelreader/cancelreader.go+93
This diff is too big to show here.
vendor/github.com/muesli/cancelreader/cancelreader_bsd.go+146
This diff is too big to show here.
vendor/github.com/muesli/cancelreader/cancelreader_default.go+12
This diff is too big to show here.
vendor/github.com/muesli/cancelreader/cancelreader_linux.go+154
This diff is too big to show here.
vendor/github.com/muesli/cancelreader/cancelreader_select.go+136
This diff is too big to show here.
vendor/github.com/muesli/cancelreader/cancelreader_unix.go+18
This diff is too big to show here.
vendor/github.com/muesli/cancelreader/cancelreader_windows.go+244
This diff is too big to show here.
vendor/github.com/muesli/mango-cobra/.gitignore+17
This diff is too big to show here.
vendor/github.com/muesli/mango-cobra/.golangci-soft.yml+47
This diff is too big to show here.
vendor/github.com/muesli/mango-cobra/.golangci.yml+29
This diff is too big to show here.
vendor/github.com/muesli/mango-cobra/LICENSE+21
This diff is too big to show here.
vendor/github.com/muesli/mango-cobra/README.md+39
This diff is too big to show here.
vendor/github.com/muesli/mango-cobra/mcobra.go+61
This diff is too big to show here.
vendor/github.com/muesli/mango-pflag/.gitignore+17
This diff is too big to show here.
vendor/github.com/muesli/mango-pflag/.golangci-soft.yml+47
This diff is too big to show here.
vendor/github.com/muesli/mango-pflag/.golangci.yml+29
This diff is too big to show here.
vendor/github.com/muesli/mango-pflag/LICENSE+21
This diff is too big to show here.
vendor/github.com/muesli/mango-pflag/README.md+33
This diff is too big to show here.
vendor/github.com/muesli/mango-pflag/mpflag.go+30
This diff is too big to show here.
vendor/github.com/muesli/mango/.gitignore+19
This diff is too big to show here.
vendor/github.com/muesli/mango/.golangci-soft.yml+47
This diff is too big to show here.
vendor/github.com/muesli/mango/.golangci.yml+29
This diff is too big to show here.
vendor/github.com/muesli/mango/LICENSE+21
This diff is too big to show here.
vendor/github.com/muesli/mango/README.md+150
This diff is too big to show here.
vendor/github.com/muesli/mango/builder.go+21
This diff is too big to show here.
vendor/github.com/muesli/mango/mango.go+221
This diff is too big to show here.
vendor/github.com/muesli/mango/mango.png
This diff is too big to show here.
vendor/github.com/muesli/roff/.gitignore+15
This diff is too big to show here.
vendor/github.com/muesli/roff/.golangci-soft.yml+47
This diff is too big to show here.
vendor/github.com/muesli/roff/.golangci.yml+29
This diff is too big to show here.
vendor/github.com/muesli/roff/LICENSE+21
This diff is too big to show here.
vendor/github.com/muesli/roff/README.md+65
This diff is too big to show here.
vendor/github.com/muesli/roff/roff.go+163
This diff is too big to show here.
vendor/github.com/muesli/termenv/.gitignore+15
This diff is too big to show here.
vendor/github.com/muesli/termenv/.golangci-soft.yml+43
This diff is too big to show here.
vendor/github.com/muesli/termenv/.golangci.yml+28
This diff is too big to show here.
vendor/github.com/muesli/termenv/LICENSE+21
This diff is too big to show here.
vendor/github.com/muesli/termenv/README.md+431
This diff is too big to show here.
vendor/github.com/muesli/termenv/ansi_compat.md+65
This diff is too big to show here.
vendor/github.com/muesli/termenv/ansicolors.go+281
This diff is too big to show here.
vendor/github.com/muesli/termenv/color.go+205
This diff is too big to show here.
vendor/github.com/muesli/termenv/constants_linux.go+8
This diff is too big to show here.
vendor/github.com/muesli/termenv/constants_solaris.go+8
This diff is too big to show here.
vendor/github.com/muesli/termenv/constants_unix.go+13
This diff is too big to show here.
vendor/github.com/muesli/termenv/constants_zos.go+8
This diff is too big to show here.
vendor/github.com/muesli/termenv/copy.go+37
This diff is too big to show here.
vendor/github.com/muesli/termenv/hyperlink.go+11
This diff is too big to show here.
vendor/github.com/muesli/termenv/notification.go+11
This diff is too big to show here.
vendor/github.com/muesli/termenv/output.go+205
This diff is too big to show here.
vendor/github.com/muesli/termenv/profile.go+112
This diff is too big to show here.
vendor/github.com/muesli/termenv/screen.go+590
This diff is too big to show here.
vendor/github.com/muesli/termenv/style.go+126
This diff is too big to show here.
vendor/github.com/muesli/termenv/templatehelper.go+88
This diff is too big to show here.
vendor/github.com/muesli/termenv/termenv.go+115
This diff is too big to show here.
vendor/github.com/muesli/termenv/termenv_other.go+30
This diff is too big to show here.
vendor/github.com/muesli/termenv/termenv_posix.go+17
This diff is too big to show here.
vendor/github.com/muesli/termenv/termenv_solaris.go+22
This diff is too big to show here.
vendor/github.com/muesli/termenv/termenv_unix.go+301
This diff is too big to show here.
vendor/github.com/muesli/termenv/termenv_windows.go+140
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/.gitignore+23
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/LICENSE+21
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/README.md+121
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/backup.go+134
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/blob.go+266
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/config.go+397
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/conn.go+496
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/const.go+425
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/context.go+234
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/driver/driver.go+821
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/driver/savepoint.go+40
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/driver/time.go+32
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/driver/util.go+74
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/embed/README.md+41
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/embed/build.sh+33
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/embed/exports.txt+141
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/embed/init.go+21
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/embed/sqlite3.wasm
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/error.go+164
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/func.go+319
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/alloc/alloc_other.go+24
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/alloc/alloc_unix.go+67
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/alloc/alloc_windows.go+74
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/dotlk/dotlk.go+29
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/dotlk/dotlk_other.go+13
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/dotlk/dotlk_unix.go+50
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/util/const.go+117
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/util/error.go+116
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/util/func.go+202
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/util/handle.go+70
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/util/json.go+50
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/util/math.go+29
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/util/mem.go+153
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/util/mmap_other.go+5
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/util/mmap_unix.go+87
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/util/mmap_windows.go+51
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/util/module.go+25
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/util/pointer.go+11
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/util/reflect.go+10
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/internal/util/set.go+12
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/json.go+12
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/pointer.go+13
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/quote.go+140
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/registry.go+30
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/sqlite.go+344
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/stmt.go+756
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/time.go+357
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/txn.go+301
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/util/sql3util/README.md+9
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/util/sql3util/arg.go+65
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/util/sql3util/const.go+61
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/util/sql3util/parse.go+210
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/util/sql3util/sql3util.go+9
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/util/sql3util/wasm/sql3parse_table.wasm
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/value.go+241
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/README.md+124
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/api.go+216
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/cksm.go+168
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/const.go+251
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/file.go+247
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/filename.go+179
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/lock.go+128
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/lock_other.go+23
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/os_bsd.go+110
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/os_darwin.go+107
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/os_dotlk.go+143
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/os_f2fs_linux.go+37
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go+86
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/os_ofd.go+59
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/os_std.go+50
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/os_std_alloc.go+19
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/os_std_atomic.go+9
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/os_std_rw.go+13
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/os_std_sync.go+9
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/os_unix.go+103
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/os_windows.go+179
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/registry.go+48
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/shm.go+26
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/shm_bsd.go+249
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/shm_copy.go+87
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/shm_dotlk.go+178
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/shm_memlk.go+53
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/shm_ofd.go+176
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/shm_other.go+21
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/shm_windows.go+175
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vfs/vfs.go+462
This diff is too big to show here.
vendor/github.com/ncruces/go-sqlite3/vtab.go+689
This diff is too big to show here.
vendor/github.com/ncruces/julianday/.gitignore+15
This diff is too big to show here.
vendor/github.com/ncruces/julianday/LICENSE+21
This diff is too big to show here.
vendor/github.com/ncruces/julianday/README.md+9
This diff is too big to show here.
vendor/github.com/ncruces/julianday/julianday.go+124
This diff is too big to show here.
vendor/github.com/nfnt/resize/.travis.yml+7
This diff is too big to show here.
vendor/github.com/nfnt/resize/LICENSE+13
This diff is too big to show here.
vendor/github.com/nfnt/resize/README.md+151
This diff is too big to show here.
vendor/github.com/nfnt/resize/converter.go+438
This diff is too big to show here.
vendor/github.com/nfnt/resize/filters.go+143
This diff is too big to show here.
vendor/github.com/nfnt/resize/nearest.go+318
This diff is too big to show here.
vendor/github.com/nfnt/resize/resize.go+620
This diff is too big to show here.
vendor/github.com/nfnt/resize/thumbnail.go+55
This diff is too big to show here.
vendor/github.com/nfnt/resize/ycc.go+387
This diff is too big to show here.
vendor/github.com/nxadm/tail/.cirrus.yml+8
This diff is too big to show here.
vendor/github.com/nxadm/tail/.gitignore+2
This diff is too big to show here.
vendor/github.com/nxadm/tail/CHANGES.md+64
This diff is too big to show here.
vendor/github.com/nxadm/tail/CONTRIBUTING.md+5
This diff is too big to show here.
vendor/github.com/nxadm/tail/Dockerfile+19
This diff is too big to show here.
vendor/github.com/nxadm/tail/LICENSE+21
This diff is too big to show here.
vendor/github.com/nxadm/tail/README.md+45
This diff is too big to show here.
vendor/github.com/nxadm/tail/ratelimiter/Licence+7
This diff is too big to show here.
vendor/github.com/nxadm/tail/ratelimiter/leakybucket.go+97
This diff is too big to show here.
vendor/github.com/nxadm/tail/ratelimiter/memory.go+60
This diff is too big to show here.
vendor/github.com/nxadm/tail/ratelimiter/storage.go+6
This diff is too big to show here.
vendor/github.com/nxadm/tail/tail.go+481
This diff is too big to show here.
vendor/github.com/nxadm/tail/tail_posix.go+17
This diff is too big to show here.
vendor/github.com/nxadm/tail/tail_windows.go+19
This diff is too big to show here.
vendor/github.com/nxadm/tail/util/util.go+49
This diff is too big to show here.
vendor/github.com/nxadm/tail/watch/filechanges.go+37
This diff is too big to show here.
vendor/github.com/nxadm/tail/watch/inotify.go+136
This diff is too big to show here.
vendor/github.com/nxadm/tail/watch/inotify_tracker.go+249
This diff is too big to show here.
vendor/github.com/nxadm/tail/watch/polling.go+119
This diff is too big to show here.
vendor/github.com/nxadm/tail/watch/watch.go+21
This diff is too big to show here.
vendor/github.com/nxadm/tail/winfile/winfile.go+93
This diff is too big to show here.
vendor/github.com/openai/openai-go/.gitignore+4
This diff is too big to show here.
vendor/github.com/openai/openai-go/.release-please-manifest.json+3
This diff is too big to show here.
vendor/github.com/openai/openai-go/.stats.yml+4
This diff is too big to show here.
vendor/github.com/openai/openai-go/Brewfile+1
This diff is too big to show here.
vendor/github.com/openai/openai-go/CHANGELOG.md+392
This diff is too big to show here.
vendor/github.com/openai/openai-go/CONTRIBUTING.md+66
This diff is too big to show here.
vendor/github.com/openai/openai-go/LICENSE+201
This diff is too big to show here.
vendor/github.com/openai/openai-go/MIGRATION.md+284
This diff is too big to show here.
vendor/github.com/openai/openai-go/README.md+948
This diff is too big to show here.
vendor/github.com/openai/openai-go/SECURITY.md+29
This diff is too big to show here.
vendor/github.com/openai/openai-go/aliases.go+440
This diff is too big to show here.
vendor/github.com/openai/openai-go/api.md+785
This diff is too big to show here.
vendor/github.com/openai/openai-go/audio.go+53
This diff is too big to show here.
vendor/github.com/openai/openai-go/audiospeech.go+129
This diff is too big to show here.
vendor/github.com/openai/openai-go/audiotranscription.go+654
This diff is too big to show here.
vendor/github.com/openai/openai-go/audiotranslation.go+117
This diff is too big to show here.
vendor/github.com/openai/openai-go/azure/azure.go+237
This diff is too big to show here.
vendor/github.com/openai/openai-go/batch.go+343
This diff is too big to show here.
vendor/github.com/openai/openai-go/beta.go+31
This diff is too big to show here.
vendor/github.com/openai/openai-go/betaassistant.go+2246
This diff is too big to show here.
vendor/github.com/openai/openai-go/betathread.go+1564
This diff is too big to show here.
vendor/github.com/openai/openai-go/betathreadmessage.go+1712
This diff is too big to show here.
vendor/github.com/openai/openai-go/betathreadrun.go+960
This diff is too big to show here.
vendor/github.com/openai/openai-go/betathreadrunstep.go+1393
This diff is too big to show here.
vendor/github.com/openai/openai-go/chat.go+28
This diff is too big to show here.
vendor/github.com/openai/openai-go/chatcompletion.go+2592
This diff is too big to show here.
vendor/github.com/openai/openai-go/chatcompletionmessage.go+96
This diff is too big to show here.
vendor/github.com/openai/openai-go/client.go+161
This diff is too big to show here.
vendor/github.com/openai/openai-go/completion.go+426
This diff is too big to show here.
vendor/github.com/openai/openai-go/container.go+352
This diff is too big to show here.
vendor/github.com/openai/openai-go/containerfile.go+286
This diff is too big to show here.
vendor/github.com/openai/openai-go/containerfilecontent.go+49
This diff is too big to show here.
vendor/github.com/openai/openai-go/embedding.go+203
This diff is too big to show here.
vendor/github.com/openai/openai-go/field.go+45
This diff is too big to show here.
vendor/github.com/openai/openai-go/file.go+313
This diff is too big to show here.
vendor/github.com/openai/openai-go/finetuning.go+34
This diff is too big to show here.
vendor/github.com/openai/openai-go/finetuningalpha.go+28
This diff is too big to show here.
vendor/github.com/openai/openai-go/finetuningalphagrader.go+672
This diff is too big to show here.
vendor/github.com/openai/openai-go/finetuningcheckpoint.go+28
This diff is too big to show here.
vendor/github.com/openai/openai-go/finetuningcheckpointpermission.go+254
This diff is too big to show here.
vendor/github.com/openai/openai-go/finetuningjob.go+880
This diff is too big to show here.
vendor/github.com/openai/openai-go/finetuningjobcheckpoint.go+149
This diff is too big to show here.
vendor/github.com/openai/openai-go/finetuningmethod.go+1487
This diff is too big to show here.
vendor/github.com/openai/openai-go/grader.go+28
This diff is too big to show here.
vendor/github.com/openai/openai-go/gradergradermodel.go+1179
This diff is too big to show here.
vendor/github.com/openai/openai-go/image.go+645
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apierror/apierror.go+58
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apiform/encoder.go+465
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apiform/form.go+5
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apiform/richparam.go+20
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apiform/tag.go+51
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apijson/decoder.go+691
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apijson/encoder.go+392
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apijson/enum.go+145
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apijson/field.go+23
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apijson/port.go+120
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apijson/registry.go+51
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apijson/subfield.go+67
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apijson/tag.go+47
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apijson/union.go+202
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apiquery/encoder.go+415
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apiquery/query.go+55
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apiquery/richparam.go+20
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/apiquery/tag.go+44
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/encoding/json/decode.go+1324
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/encoding/json/encode.go+1391
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/encoding/json/fold.go+48
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/encoding/json/indent.go+182
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/encoding/json/scanner.go+610
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/encoding/json/sentinel/null.go+46
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/encoding/json/shims/shims.go+111
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/encoding/json/stream.go+512
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/encoding/json/tables.go+218
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/encoding/json/tags.go+38
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/encoding/json/time.go+61
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/paramutil/field.go+30
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/paramutil/union.go+48
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/requestconfig/requestconfig.go+635
This diff is too big to show here.
vendor/github.com/openai/openai-go/internal/version.go+5
This diff is too big to show here.
vendor/github.com/openai/openai-go/model.go+134
This diff is too big to show here.
vendor/github.com/openai/openai-go/moderation.go+485
This diff is too big to show here.
vendor/github.com/openai/openai-go/option/middleware.go+38
This diff is too big to show here.
vendor/github.com/openai/openai-go/option/requestoption.go+298
This diff is too big to show here.
vendor/github.com/openai/openai-go/packages/pagination/pagination.go+223
This diff is too big to show here.
vendor/github.com/openai/openai-go/packages/param/encoder.go+101
This diff is too big to show here.
vendor/github.com/openai/openai-go/packages/param/null.go+19
This diff is too big to show here.
vendor/github.com/openai/openai-go/packages/param/option.go+121
This diff is too big to show here.
vendor/github.com/openai/openai-go/packages/param/param.go+168
This diff is too big to show here.
vendor/github.com/openai/openai-go/packages/respjson/respjson.go+88
This diff is too big to show here.
vendor/github.com/openai/openai-go/packages/ssestream/ssestream.go+216
This diff is too big to show here.
vendor/github.com/openai/openai-go/polling.go+95
This diff is too big to show here.
vendor/github.com/openai/openai-go/release-please-config.json+67
This diff is too big to show here.
vendor/github.com/openai/openai-go/responses/aliases.go+440
This diff is too big to show here.
vendor/github.com/openai/openai-go/responses/inputitem.go+136
This diff is too big to show here.
vendor/github.com/openai/openai-go/responses/response.go+12897
This diff is too big to show here.
vendor/github.com/openai/openai-go/shared/constant/constants.go+721
This diff is too big to show here.
vendor/github.com/openai/openai-go/shared/shared.go+796
This diff is too big to show here.
vendor/github.com/openai/openai-go/streamaccumulator.go+195
This diff is too big to show here.
vendor/github.com/openai/openai-go/upload.go+201
This diff is too big to show here.
vendor/github.com/openai/openai-go/uploadpart.go+112
This diff is too big to show here.
vendor/github.com/openai/openai-go/vectorstore.go+905
This diff is too big to show here.
vendor/github.com/openai/openai-go/vectorstorefile.go+534
This diff is too big to show here.
vendor/github.com/openai/openai-go/vectorstorefilebatch.go+364
This diff is too big to show here.
vendor/github.com/openai/openai-go/webhooks/aliases.go+440
This diff is too big to show here.
vendor/github.com/openai/openai-go/webhooks/webhook.go+1208
This diff is too big to show here.
vendor/github.com/pkg/browser/LICENSE+23
This diff is too big to show here.
vendor/github.com/pkg/browser/README.md+55
This diff is too big to show here.
vendor/github.com/pkg/browser/browser.go+57
This diff is too big to show here.
vendor/github.com/pkg/browser/browser_darwin.go+5
This diff is too big to show here.
vendor/github.com/pkg/browser/browser_freebsd.go+14
This diff is too big to show here.
vendor/github.com/pkg/browser/browser_linux.go+21
This diff is too big to show here.
vendor/github.com/pkg/browser/browser_netbsd.go+14
This diff is too big to show here.
vendor/github.com/pkg/browser/browser_openbsd.go+14
This diff is too big to show here.
vendor/github.com/pkg/browser/browser_unsupported.go+12
This diff is too big to show here.
vendor/github.com/pkg/browser/browser_windows.go+7
This diff is too big to show here.
vendor/github.com/pmezard/go-difflib/LICENSE+27
This diff is too big to show here.
vendor/github.com/pmezard/go-difflib/difflib/difflib.go+772
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/.gitignore+18
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/.golangci.yaml+31
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/.goreleaser.yaml+36
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/CHANGELOG.md+291
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/LICENSE+22
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/Makefile+144
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/README.md+493
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/create.go+120
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/database/dialect.go+172
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/database/doc.go+14
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/database/sql_extended.go+23
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/database/store.go+66
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/database/store_extended.go+33
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/db.go+46
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/dialect.go+64
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/down.go+107
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/fix.go+54
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/globals.go+104
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/goose.go+141
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/helpers.go+84
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/install.sh+37
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/controller/controller.go+37
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/dialect/dialectquery/clickhouse.go+44
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/dialect/dialectquery/dialectquery.go+59
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/dialect/dialectquery/mysql.go+53
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/dialect/dialectquery/postgres.go+54
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/dialect/dialectquery/redshift.go+43
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/dialect/dialectquery/sqlite3.go+42
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/dialect/dialectquery/sqlserver.go+42
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/dialect/dialectquery/starrocks.go+45
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/dialect/dialectquery/tidb.go+43
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/dialect/dialectquery/turso.go+7
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/dialect/dialectquery/vertica.go+43
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/dialect/dialectquery/ydb.go+53
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/dialect/dialects.go+18
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/dialect/store.go+169
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/gooseutil/resolve.go+124
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/sqlparser/parse.go+59
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/internal/sqlparser/parser.go+394
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/lock/postgres.go+118
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/lock/session_locker.go+23
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/lock/session_locker_options.go+98
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/log.go+36
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/migrate.go+378
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/migration.go+398
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/migration_sql.go+115
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/osfs.go+28
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/provider.go+650
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/provider_collect.go+196
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/provider_errors.go+48
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/provider_options.go+203
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/provider_run.go+453
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/provider_types.go+91
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/redo.go+51
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/register.go+133
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/reset.go+64
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/status.go+64
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/up.go+221
This diff is too big to show here.
vendor/github.com/pressly/goose/v3/version.go+52
This diff is too big to show here.
vendor/github.com/qjebbs/go-jsons/.gitignore+86
This diff is too big to show here.
vendor/github.com/qjebbs/go-jsons/LICENSE+21
This diff is too big to show here.
vendor/github.com/qjebbs/go-jsons/Makefile+9
This diff is too big to show here.
vendor/github.com/qjebbs/go-jsons/common.go+19
This diff is too big to show here.
vendor/github.com/qjebbs/go-jsons/formats.go+10
This diff is too big to show here.
vendor/github.com/qjebbs/go-jsons/jsons.go+32
This diff is too big to show here.
vendor/github.com/qjebbs/go-jsons/loader.go+101
This diff is too big to show here.
vendor/github.com/qjebbs/go-jsons/merge/map.go+55
This diff is too big to show here.
vendor/github.com/qjebbs/go-jsons/merger.go+170
This diff is too big to show here.
vendor/github.com/qjebbs/go-jsons/merger_extensions.go+27
This diff is too big to show here.
vendor/github.com/qjebbs/go-jsons/merger_loader.go+28
This diff is too big to show here.
vendor/github.com/qjebbs/go-jsons/readme.md+152
This diff is too big to show here.
vendor/github.com/qjebbs/go-jsons/rule/field.go+57
This diff is too big to show here.
vendor/github.com/qjebbs/go-jsons/rule/merge.go+75
This diff is too big to show here.
vendor/github.com/qjebbs/go-jsons/rule/rule.go+93
This diff is too big to show here.
vendor/github.com/qjebbs/go-jsons/rule/sort.go+95
This diff is too big to show here.
vendor/github.com/rivo/uniseg/LICENSE.txt+21
This diff is too big to show here.
vendor/github.com/rivo/uniseg/README.md+137
This diff is too big to show here.
vendor/github.com/rivo/uniseg/doc.go+108
This diff is too big to show here.
vendor/github.com/rivo/uniseg/eastasianwidth.go+2588
This diff is too big to show here.
vendor/github.com/rivo/uniseg/emojipresentation.go+295
This diff is too big to show here.
vendor/github.com/rivo/uniseg/gen_breaktest.go+215
This diff is too big to show here.
vendor/github.com/rivo/uniseg/gen_properties.go+261
This diff is too big to show here.
vendor/github.com/rivo/uniseg/grapheme.go+331
This diff is too big to show here.
vendor/github.com/rivo/uniseg/graphemeproperties.go+1915
This diff is too big to show here.
vendor/github.com/rivo/uniseg/graphemerules.go+176
This diff is too big to show here.
vendor/github.com/rivo/uniseg/line.go+134
This diff is too big to show here.
vendor/github.com/rivo/uniseg/lineproperties.go+3554
This diff is too big to show here.
vendor/github.com/rivo/uniseg/linerules.go+626
This diff is too big to show here.
vendor/github.com/rivo/uniseg/properties.go+208
This diff is too big to show here.
vendor/github.com/rivo/uniseg/sentence.go+90
This diff is too big to show here.
vendor/github.com/rivo/uniseg/sentenceproperties.go+2845
This diff is too big to show here.
vendor/github.com/rivo/uniseg/sentencerules.go+276
This diff is too big to show here.
vendor/github.com/rivo/uniseg/step.go+242
This diff is too big to show here.
vendor/github.com/rivo/uniseg/width.go+61
This diff is too big to show here.
vendor/github.com/rivo/uniseg/word.go+89
This diff is too big to show here.
vendor/github.com/rivo/uniseg/wordproperties.go+1883
This diff is too big to show here.
vendor/github.com/rivo/uniseg/wordrules.go+282
This diff is too big to show here.
vendor/github.com/sabhiram/go-gitignore/.gitignore+28
This diff is too big to show here.
vendor/github.com/sabhiram/go-gitignore/LICENSE+22
This diff is too big to show here.
vendor/github.com/sabhiram/go-gitignore/README.md+15
This diff is too big to show here.
vendor/github.com/sabhiram/go-gitignore/ignore.go+239
This diff is too big to show here.
vendor/github.com/sabhiram/go-gitignore/version_gen.go+12
This diff is too big to show here.
vendor/github.com/sahilm/fuzzy/.editorconfig+18
This diff is too big to show here.
vendor/github.com/sahilm/fuzzy/.gitignore+2
This diff is too big to show here.
vendor/github.com/sahilm/fuzzy/.travis.yml+8
This diff is too big to show here.
vendor/github.com/sahilm/fuzzy/CONTRIBUTING.md+1
This diff is too big to show here.
vendor/github.com/sahilm/fuzzy/Gopkg.lock+20
This diff is too big to show here.
vendor/github.com/sahilm/fuzzy/Gopkg.toml+4
This diff is too big to show here.
vendor/github.com/sahilm/fuzzy/LICENSE+21
This diff is too big to show here.
vendor/github.com/sahilm/fuzzy/Makefile+57
This diff is too big to show here.
vendor/github.com/sahilm/fuzzy/README.md+186
This diff is too big to show here.
vendor/github.com/sahilm/fuzzy/fuzzy.go+254
This diff is too big to show here.
vendor/github.com/sethvargo/go-retry/LICENSE+202
This diff is too big to show here.
vendor/github.com/sethvargo/go-retry/README.md+185
This diff is too big to show here.
vendor/github.com/sethvargo/go-retry/backoff.go+134
This diff is too big to show here.
vendor/github.com/sethvargo/go-retry/backoff_constant.go+25
This diff is too big to show here.
vendor/github.com/sethvargo/go-retry/backoff_exponential.go+47
This diff is too big to show here.
vendor/github.com/sethvargo/go-retry/backoff_fibonacci.go+56
This diff is too big to show here.
vendor/github.com/sethvargo/go-retry/rand.go+54
This diff is too big to show here.
vendor/github.com/sethvargo/go-retry/retry.go+104
This diff is too big to show here.
vendor/github.com/spf13/cast/.gitignore+25
This diff is too big to show here.
vendor/github.com/spf13/cast/LICENSE+21
This diff is too big to show here.
vendor/github.com/spf13/cast/Makefile+40
This diff is too big to show here.
vendor/github.com/spf13/cast/README.md+75
This diff is too big to show here.
vendor/github.com/spf13/cast/cast.go+176
This diff is too big to show here.
vendor/github.com/spf13/cast/caste.go+1510
This diff is too big to show here.
vendor/github.com/spf13/cast/timeformattype_string.go+27
This diff is too big to show here.
vendor/github.com/spf13/cobra/.gitignore+39
This diff is too big to show here.
vendor/github.com/spf13/cobra/.golangci.yml+57
This diff is too big to show here.
vendor/github.com/spf13/cobra/.mailmap+3
This diff is too big to show here.
vendor/github.com/spf13/cobra/CONDUCT.md+37
This diff is too big to show here.
vendor/github.com/spf13/cobra/CONTRIBUTING.md+50
This diff is too big to show here.
vendor/github.com/spf13/cobra/LICENSE.txt+174
This diff is too big to show here.
vendor/github.com/spf13/cobra/MAINTAINERS+13
This diff is too big to show here.
vendor/github.com/spf13/cobra/Makefile+35
This diff is too big to show here.
vendor/github.com/spf13/cobra/README.md+113
This diff is too big to show here.
vendor/github.com/spf13/cobra/active_help.go+60
This diff is too big to show here.
vendor/github.com/spf13/cobra/args.go+131
This diff is too big to show here.
vendor/github.com/spf13/cobra/bash_completions.go+709
This diff is too big to show here.
vendor/github.com/spf13/cobra/bash_completionsV2.go+484
This diff is too big to show here.
vendor/github.com/spf13/cobra/cobra.go+246
This diff is too big to show here.
vendor/github.com/spf13/cobra/command.go+2067
This diff is too big to show here.
vendor/github.com/spf13/cobra/command_notwin.go+20
This diff is too big to show here.
vendor/github.com/spf13/cobra/command_win.go+41
This diff is too big to show here.
vendor/github.com/spf13/cobra/completions.go+1005
This diff is too big to show here.
vendor/github.com/spf13/cobra/fish_completions.go+292
This diff is too big to show here.
vendor/github.com/spf13/cobra/flag_groups.go+290
This diff is too big to show here.
vendor/github.com/spf13/cobra/powershell_completions.go+350
This diff is too big to show here.
vendor/github.com/spf13/cobra/shell_completions.go+98
This diff is too big to show here.
vendor/github.com/spf13/cobra/zsh_completions.go+308
This diff is too big to show here.
vendor/github.com/spf13/pflag/.editorconfig+12
This diff is too big to show here.
vendor/github.com/spf13/pflag/.gitignore+2
This diff is too big to show here.
vendor/github.com/spf13/pflag/.golangci.yaml+4
This diff is too big to show here.
vendor/github.com/spf13/pflag/.travis.yml+22
This diff is too big to show here.
vendor/github.com/spf13/pflag/LICENSE+28
This diff is too big to show here.
vendor/github.com/spf13/pflag/README.md+296
This diff is too big to show here.
vendor/github.com/spf13/pflag/bool.go+94
This diff is too big to show here.
vendor/github.com/spf13/pflag/bool_slice.go+185
This diff is too big to show here.
vendor/github.com/spf13/pflag/bytes.go+209
This diff is too big to show here.
vendor/github.com/spf13/pflag/count.go+96
This diff is too big to show here.
vendor/github.com/spf13/pflag/duration.go+86
This diff is too big to show here.
vendor/github.com/spf13/pflag/duration_slice.go+166
This diff is too big to show here.
vendor/github.com/spf13/pflag/flag.go+1246
This diff is too big to show here.
vendor/github.com/spf13/pflag/float32.go+88
This diff is too big to show here.
vendor/github.com/spf13/pflag/float32_slice.go+174
This diff is too big to show here.
vendor/github.com/spf13/pflag/float64.go+84
This diff is too big to show here.
vendor/github.com/spf13/pflag/float64_slice.go+166
This diff is too big to show here.
vendor/github.com/spf13/pflag/golangflag.go+105
This diff is too big to show here.
vendor/github.com/spf13/pflag/int.go+84
This diff is too big to show here.
vendor/github.com/spf13/pflag/int16.go+88
This diff is too big to show here.
vendor/github.com/spf13/pflag/int32.go+88
This diff is too big to show here.
vendor/github.com/spf13/pflag/int32_slice.go+174
This diff is too big to show here.
vendor/github.com/spf13/pflag/int64.go+84
This diff is too big to show here.
vendor/github.com/spf13/pflag/int64_slice.go+166
This diff is too big to show here.
vendor/github.com/spf13/pflag/int8.go+88
This diff is too big to show here.
vendor/github.com/spf13/pflag/int_slice.go+158
This diff is too big to show here.
vendor/github.com/spf13/pflag/ip.go+97
This diff is too big to show here.
vendor/github.com/spf13/pflag/ip_slice.go+186
This diff is too big to show here.
vendor/github.com/spf13/pflag/ipmask.go+122
This diff is too big to show here.
vendor/github.com/spf13/pflag/ipnet.go+98
This diff is too big to show here.
vendor/github.com/spf13/pflag/ipnet_slice.go+147
This diff is too big to show here.
vendor/github.com/spf13/pflag/string.go+80
This diff is too big to show here.
vendor/github.com/spf13/pflag/string_array.go+125
This diff is too big to show here.
vendor/github.com/spf13/pflag/string_slice.go+163
This diff is too big to show here.
vendor/github.com/spf13/pflag/string_to_int.go+149
This diff is too big to show here.
vendor/github.com/spf13/pflag/string_to_int64.go+149
This diff is too big to show here.
vendor/github.com/spf13/pflag/string_to_string.go+160
This diff is too big to show here.
vendor/github.com/spf13/pflag/uint.go+88
This diff is too big to show here.
vendor/github.com/spf13/pflag/uint16.go+88
This diff is too big to show here.
vendor/github.com/spf13/pflag/uint32.go+88
This diff is too big to show here.
vendor/github.com/spf13/pflag/uint64.go+88
This diff is too big to show here.
vendor/github.com/spf13/pflag/uint8.go+88
This diff is too big to show here.
vendor/github.com/spf13/pflag/uint_slice.go+168
This diff is too big to show here.
vendor/github.com/srwiley/oksvg/.gitignore+2
This diff is too big to show here.
vendor/github.com/srwiley/oksvg/LICENSE+29
This diff is too big to show here.
vendor/github.com/srwiley/oksvg/README.md+49
This diff is too big to show here.
vendor/github.com/srwiley/oksvg/definitions.go+16
This diff is too big to show here.
vendor/github.com/srwiley/oksvg/draw.go+392
This diff is too big to show here.
vendor/github.com/srwiley/oksvg/icon_cursor.go+391
This diff is too big to show here.
vendor/github.com/srwiley/oksvg/path_cursor.go+397
This diff is too big to show here.
vendor/github.com/srwiley/oksvg/path_style.go+35
This diff is too big to show here.
vendor/github.com/srwiley/oksvg/public.go+287
This diff is too big to show here.
vendor/github.com/srwiley/oksvg/svg_icon.go+37
This diff is too big to show here.
vendor/github.com/srwiley/oksvg/svg_path.go+109
This diff is too big to show here.
vendor/github.com/srwiley/oksvg/utils.go+172
This diff is too big to show here.
vendor/github.com/srwiley/rasterx/LICENSE+29
This diff is too big to show here.
vendor/github.com/srwiley/rasterx/README.md+84
This diff is too big to show here.
vendor/github.com/srwiley/rasterx/dash.go+202
This diff is too big to show here.
vendor/github.com/srwiley/rasterx/fill.go+241
This diff is too big to show here.
vendor/github.com/srwiley/rasterx/geomx.go+306
This diff is too big to show here.
vendor/github.com/srwiley/rasterx/gradient.go+310
This diff is too big to show here.
vendor/github.com/srwiley/rasterx/matrix.go+191
This diff is too big to show here.
vendor/github.com/srwiley/rasterx/scan.go+181
This diff is too big to show here.
vendor/github.com/srwiley/rasterx/shapes.go+221
This diff is too big to show here.
vendor/github.com/srwiley/rasterx/stroke.go+677
This diff is too big to show here.
vendor/github.com/stretchr/testify/LICENSE+21
This diff is too big to show here.
vendor/github.com/stretchr/testify/assert/assertion_compare.go+489
This diff is too big to show here.
vendor/github.com/stretchr/testify/assert/assertion_format.go+841
This diff is too big to show here.
vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl+5
This diff is too big to show here.
vendor/github.com/stretchr/testify/assert/assertion_forward.go+1673
This diff is too big to show here.
vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl+5
This diff is too big to show here.
vendor/github.com/stretchr/testify/assert/assertion_order.go+81
This diff is too big to show here.
vendor/github.com/stretchr/testify/assert/assertions.go+2184
This diff is too big to show here.
vendor/github.com/stretchr/testify/assert/doc.go+46
This diff is too big to show here.
vendor/github.com/stretchr/testify/assert/errors.go+10
This diff is too big to show here.
vendor/github.com/stretchr/testify/assert/forward_assertions.go+16
This diff is too big to show here.
vendor/github.com/stretchr/testify/assert/http_assertions.go+165
This diff is too big to show here.
vendor/github.com/stretchr/testify/assert/yaml/yaml_custom.go+25
This diff is too big to show here.
vendor/github.com/stretchr/testify/assert/yaml/yaml_default.go+37
This diff is too big to show here.
vendor/github.com/stretchr/testify/assert/yaml/yaml_fail.go+18
This diff is too big to show here.
vendor/github.com/stretchr/testify/require/doc.go+29
This diff is too big to show here.
vendor/github.com/stretchr/testify/require/forward_requirements.go+16
This diff is too big to show here.
vendor/github.com/stretchr/testify/require/require.go+2124
This diff is too big to show here.
vendor/github.com/stretchr/testify/require/require.go.tmpl+6
This diff is too big to show here.
vendor/github.com/stretchr/testify/require/require_forward.go+1674
This diff is too big to show here.
vendor/github.com/stretchr/testify/require/require_forward.go.tmpl+5
This diff is too big to show here.
vendor/github.com/stretchr/testify/require/requirements.go+29
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/.editorconfig+7
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/.gitattributes+2
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/.gitignore+46
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/.gitmodules+3
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/CONTRIBUTING.md+75
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/LICENSE+201
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/Makefile+364
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/NOTICE+2
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/RATIONALE.md+1587
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/README.md+139
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/api/features.go+214
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/api/wasm.go+766
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/builder.go+367
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/cache.go+123
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/codecov.yml+9
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/config.go+899
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/checkpoint.go+35
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/close.go+63
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/experimental.go+41
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/features.go+15
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/importresolver.go+19
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/listener.go+324
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/memory.go+52
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/sys/dir.go+92
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/sys/errno.go+98
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/sys/error.go+45
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/sys/file.go+316
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/sys/fs.go+292
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/sys/oflag.go+70
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/sys/syscall_errno.go+106
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/sys/syscall_errno_notwindows.go+13
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/sys/syscall_errno_unsupported.go+7
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/sys/syscall_errno_windows.go+66
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/sys/time.go+10
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/experimental/sys/unimplemented.go+160
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/fsconfig.go+213
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/descriptor/table.go+149
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/interpreter/compiler.go+3632
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/interpreter/format.go+22
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/interpreter/interpreter.go+4596
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/interpreter/operations.go+2812
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/interpreter/signature.go+767
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/abi.go+170
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/backend.go+3
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/compiler.go+399
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/compiler_lower.go+226
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/go_call.go+33
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi.go+186
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.go+9
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi_entry_amd64.s+29
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi_entry_preamble.go+248
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/abi_go_call.go+440
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/cond.go+168
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/ext.go+35
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/instr.go+2447
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go+1683
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/lower_constant.go+71
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/lower_mem.go+187
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine.go+3733
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine_pro_epi_logue.go+303
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine_regalloc.go+352
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/machine_vec.go+992
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/operands.go+336
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/reg.go+181
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64/stack.go+130
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi.go+333
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi_entry_arm64.go+9
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi_entry_arm64.s+29
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi_entry_preamble.go+233
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/abi_go_call.go+430
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/cond.go+215
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/instr.go+2534
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/instr_encoding.go+2351
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/lower_constant.go+301
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/lower_instr.go+2224
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/lower_instr_operands.go+340
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/lower_mem.go+451
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine.go+631
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine_pro_epi_logue.go+467
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine_regalloc.go+351
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/machine_relocation.go+122
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/reg.go+397
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64/unwind_stack.go+84
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/machine.go+119
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc/api.go+124
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc/reg.go+123
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc/regalloc.go+1189
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc/regset.go+96
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/backend/vdef.go+19
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/call_engine.go+708
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/engine.go+843
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/engine_cache.go+303
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/entrypoint_amd64.go+15
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/entrypoint_arm64.go+15
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/entrypoint_others.go+15
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/frontend.go+567
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/lower.go+4261
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/misc.go+10
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/frontend/sort_id.go+13
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/hostmodule.go+88
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/isa_amd64.go+30
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/isa_arm64.go+32
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/isa_other.go+29
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/memmove.go+11
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/module_engine.go+331
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/basic_block.go+379
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/basic_block_sort.go+32
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/builder.go+790
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/cmp.go+107
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/funcref.go+12
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/instructions.go+2976
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass.go+393
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass_blk_layouts.go+334
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/pass_cfg.go+313
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/signature.go+49
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/ssa.go+14
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/type.go+115
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/ssa/vs.go+114
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/debug_options.go+196
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/exitcode.go+109
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/offsetdata.go+216
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/perfmap.go+96
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/perfmap_disabled.go+5
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/perfmap_enabled.go+5
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/pool.go+215
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/ptr.go+15
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/queue.go+26
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi/resetmap.go+11
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/expctxkeys/checkpoint.go+10
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/expctxkeys/close.go+5
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/expctxkeys/expctxkeys.go+2
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/expctxkeys/importresolver.go+6
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/expctxkeys/listener.go+7
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/expctxkeys/memory.go+4
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/filecache/compilationcache.go+42
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/filecache/file_cache.go+76
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/fsapi/file.go+69
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/fsapi/poll.go+20
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/fsapi/unimplemented.go+27
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/ieee754/ieee754.go+29
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/internalapi/internal.go+9
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/leb128/leb128.go+285
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/moremath/moremath.go+271
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/cpuid.go+35
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_amd64.go+80
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_amd64.s+16
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_arm64.go+71
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_arm64.s+21
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/cpuid_unsupported.go+17
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/crypto.go+17
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/mmap_linux.go+76
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/mmap_other.go+18
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/mmap_unix.go+32
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/mmap_unsupported.go+26
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/mmap_windows.go+95
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/mprotect_bsd.go+22
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/mprotect_syscall.go+10
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/mprotect_unsupported.go+9
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/path.go+6
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/path_windows.go+17
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/platform.go+56
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/time.go+76
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/time_cgo.go+11
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/time_notcgo.go+7
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/platform/time_windows.go+40
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/sock/sock.go+89
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/sock/sock_supported.go+11
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/sock/sock_unsupported.go+10
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/sys/fs.go+457
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/sys/lazy.go+151
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/sys/stdio.go+128
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/sys/sys.go+228
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/sysfs/adapter.go+105
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/sysfs/datasync_linux.go+14
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/sysfs/datasync_tinygo.go+13
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/sysfs/datasync_unsupported.go+14
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/sysfs/dir.go+24
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/sysfs/dirfs.go+99
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/sysfs/dirfs_supported.go+48
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/sysfs/dirfs_unsupported.go+34
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/sysfs/file.go+523
This diff is too big to show here.
vendor/github.com/tetratelabs/wazero/internal/sysfs/file_unix.go+39
This diff is too big to show here.
1435 more files changed are not listed.