diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 93009e71..e2b17321 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -19,6 +19,9 @@ jobs: AWS_SECRET_ACCESS_KEY: test AWS_DEFAULT_REGION: us-east-1 AWS_EC2_METADATA_DISABLED: "true" + AWS_CONFIG_FILE: scripts/demo-aws-config/config + AWS_SHARED_CREDENTIALS_FILE: scripts/demo-aws-config/credentials + LOCALSTACK_IMAGE: localstack/localstack:4.12.0 steps: - uses: actions/checkout@v6 @@ -27,12 +30,14 @@ jobs: go-version-file: 'go.mod' cache: true + - uses: cachix/install-nix-action@6b2916c41eac5735ac995c58c9e8561a78c42319 # v31 + - name: Build run: go build -o claws ./cmd/claws - name: Start LocalStack run: | - docker run -d --name localstack -p 4566:4566 localstack/localstack:4.12.0 + docker run -d --name localstack -p 4566:4566 "${LOCALSTACK_IMAGE}" echo "Waiting for LocalStack..." for i in $(seq 1 30); do if curl -s http://localhost:4566/_localstack/health | grep -qE '"s3": "(available|running)"'; then @@ -48,12 +53,6 @@ jobs: timeout-minutes: 5 run: ./scripts/localstack-demo-setup.sh - - name: Create AWS config for demo - run: | - mkdir -p ~/.aws - cp scripts/demo-aws-config/config ~/.aws/config - cp scripts/demo-aws-config/credentials ~/.aws/credentials - - name: Run VHS tapes run: | set -e @@ -61,12 +60,7 @@ jobs: echo "==========================================" echo "Running: $tape" echo "==========================================" - docker run --rm --network host \ - -v "$(pwd)":/vhs \ - -v ~/.aws:/root/.aws:ro \ - -e AWS_ENDPOINT_URL=http://localhost:4566 \ - -e AWS_EC2_METADATA_DISABLED=true \ - ghcr.io/charmbracelet/vhs "$tape" + nix develop --command vhs "$tape" done - name: Upload screenshots on failure diff --git a/Taskfile.yml b/Taskfile.yml index 1e53fd7f..2af217ce 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -2,6 +2,7 @@ version: '3' vars: LOCALSTACK_CONTAINER: claws-localstack + LOCALSTACK_IMAGE: localstack/localstack:4.12.0 tasks: build: @@ -33,9 +34,9 @@ tasks: # LocalStack tasks localstack:start: - desc: Start LocalStack container + desc: Start pinned LocalStack container cmds: - - docker start {{.LOCALSTACK_CONTAINER}} 2>/dev/null || docker run -d --name {{.LOCALSTACK_CONTAINER}} -p 4566:4566 localstack/localstack:4.12.0 + - docker start {{.LOCALSTACK_CONTAINER}} 2>/dev/null || docker run -d --name {{.LOCALSTACK_CONTAINER}} -p 4566:4566 {{.LOCALSTACK_IMAGE}} - | echo "Waiting for LocalStack to be ready..." for i in $(seq 1 30); do @@ -99,11 +100,11 @@ tasks: - ./claws demo:record: - desc: Record all demos (gif + screenshots) using VHS + LocalStack + desc: Record all demos (gif + screenshots) using host VHS + LocalStack deps: [build, localstack:start, localstack:demo-setup] preconditions: - - sh: '[ "$(uname -s)" = "Linux" ]' - msg: "demo:record requires Linux (--network host not supported on macOS/Windows)" + - sh: command -v vhs >/dev/null 2>&1 + msg: "vhs is required. Install it locally before running demo recording tasks." cmds: - task: demo:record:gif - task: demo:record:themes @@ -115,83 +116,101 @@ tasks: desc: Record demo.gif only deps: [build, localstack:start, localstack:demo-setup] preconditions: - - sh: '[ "$(uname -s)" = "Linux" ]' - msg: "demo:record requires Linux (--network host not supported on macOS/Windows)" + - sh: command -v vhs >/dev/null 2>&1 + msg: "vhs is required. Install it locally before running demo recording tasks." + env: + AWS_ENDPOINT_URL: http://localhost:4566 + AWS_ACCESS_KEY_ID: test + AWS_SECRET_ACCESS_KEY: test + AWS_DEFAULT_REGION: us-east-1 + AWS_EC2_METADATA_DISABLED: "true" + AWS_CONFIG_FILE: scripts/demo-aws-config/config + AWS_SHARED_CREDENTIALS_FILE: scripts/demo-aws-config/credentials cmds: - - | - docker run --rm --network host \ - -v "$(pwd)":/vhs \ - -v "$(pwd)/scripts/demo-aws-config:/root/.aws:ro" \ - -e AWS_ENDPOINT_URL=http://localhost:4566 \ - -e AWS_EC2_METADATA_DISABLED=true \ - ghcr.io/charmbracelet/vhs docs/tapes/demo.tape + - vhs docs/tapes/demo.tape demo:record:themes: desc: Record theme screenshots only deps: [build, localstack:start, localstack:demo-setup] preconditions: - - sh: '[ "$(uname -s)" = "Linux" ]' - msg: "demo:record requires Linux (--network host not supported on macOS/Windows)" + - sh: command -v vhs >/dev/null 2>&1 + msg: "vhs is required. Install it locally before running demo recording tasks." + env: + AWS_ENDPOINT_URL: http://localhost:4566 + AWS_ACCESS_KEY_ID: test + AWS_SECRET_ACCESS_KEY: test + AWS_DEFAULT_REGION: us-east-1 + AWS_EC2_METADATA_DISABLED: "true" + AWS_CONFIG_FILE: scripts/demo-aws-config/config + AWS_SHARED_CREDENTIALS_FILE: scripts/demo-aws-config/credentials cmds: - - | - docker run --rm --network host \ - -v "$(pwd)":/vhs \ - -v "$(pwd)/scripts/demo-aws-config:/root/.aws:ro" \ - -e AWS_ENDPOINT_URL=http://localhost:4566 \ - -e AWS_EC2_METADATA_DISABLED=true \ - ghcr.io/charmbracelet/vhs docs/tapes/themes.tape + - vhs docs/tapes/themes.tape demo:record:features: desc: Record feature screenshots only deps: [build, localstack:start, localstack:demo-setup] preconditions: - - sh: '[ "$(uname -s)" = "Linux" ]' - msg: "demo:record requires Linux (--network host not supported on macOS/Windows)" + - sh: command -v vhs >/dev/null 2>&1 + msg: "vhs is required. Install it locally before running demo recording tasks." + env: + AWS_ENDPOINT_URL: http://localhost:4566 + AWS_ACCESS_KEY_ID: test + AWS_SECRET_ACCESS_KEY: test + AWS_DEFAULT_REGION: us-east-1 + AWS_EC2_METADATA_DISABLED: "true" + AWS_CONFIG_FILE: scripts/demo-aws-config/config + AWS_SHARED_CREDENTIALS_FILE: scripts/demo-aws-config/credentials cmds: - - | - docker run --rm --network host \ - -v "$(pwd)":/vhs \ - -v "$(pwd)/scripts/demo-aws-config:/root/.aws:ro" \ - -e AWS_ENDPOINT_URL=http://localhost:4566 \ - -e AWS_EC2_METADATA_DISABLED=true \ - ghcr.io/charmbracelet/vhs docs/tapes/features.tape + - vhs docs/tapes/features.tape demo:record:theme-light: desc: Record light theme screenshot (requires white terminal background) deps: [build, localstack:start, localstack:demo-setup] preconditions: - - sh: '[ "$(uname -s)" = "Linux" ]' - msg: "demo:record requires Linux (--network host not supported on macOS/Windows)" + - sh: command -v vhs >/dev/null 2>&1 + msg: "vhs is required. Install it locally before running demo recording tasks." + env: + AWS_ENDPOINT_URL: http://localhost:4566 + AWS_ACCESS_KEY_ID: test + AWS_SECRET_ACCESS_KEY: test + AWS_DEFAULT_REGION: us-east-1 + AWS_EC2_METADATA_DISABLED: "true" + AWS_CONFIG_FILE: scripts/demo-aws-config/config + AWS_SHARED_CREDENTIALS_FILE: scripts/demo-aws-config/credentials cmds: - - | - docker run --rm --network host \ - -v "$(pwd)":/vhs \ - -v "$(pwd)/scripts/demo-aws-config:/root/.aws:ro" \ - -e AWS_ENDPOINT_URL=http://localhost:4566 \ - -e AWS_EC2_METADATA_DISABLED=true \ - ghcr.io/charmbracelet/vhs docs/tapes/theme-light.tape + - vhs docs/tapes/theme-light.tape demo:record:command-mode: desc: Record command mode suggestion/completion test deps: [build, localstack:start, localstack:demo-setup] preconditions: - - sh: '[ "$(uname -s)" = "Linux" ]' - msg: "demo:record requires Linux (--network host not supported on macOS/Windows)" + - sh: command -v vhs >/dev/null 2>&1 + msg: "vhs is required. Install it locally before running demo recording tasks." + env: + AWS_ENDPOINT_URL: http://localhost:4566 + AWS_ACCESS_KEY_ID: test + AWS_SECRET_ACCESS_KEY: test + AWS_DEFAULT_REGION: us-east-1 + AWS_EC2_METADATA_DISABLED: "true" + AWS_CONFIG_FILE: scripts/demo-aws-config/config + AWS_SHARED_CREDENTIALS_FILE: scripts/demo-aws-config/credentials cmds: - - | - docker run --rm --network host \ - -v "$(pwd)":/vhs \ - -v "$(pwd)/scripts/demo-aws-config:/root/.aws:ro" \ - -e AWS_ENDPOINT_URL=http://localhost:4566 \ - -e AWS_EC2_METADATA_DISABLED=true \ - ghcr.io/charmbracelet/vhs docs/tapes/command-mode.tape + - vhs docs/tapes/command-mode.tape test:vhs: - desc: Run all VHS tapes as integration tests + desc: Run all VHS tapes as integration tests using host VHS deps: [build, localstack:start, localstack:demo-setup] preconditions: - - sh: '[ "$(uname -s)" = "Linux" ]' - msg: "test:vhs requires Linux (--network host not supported on macOS/Windows)" + - sh: command -v vhs >/dev/null 2>&1 + msg: "vhs is required. Install it locally before running VHS integration tests." + env: + AWS_ENDPOINT_URL: http://localhost:4566 + AWS_ACCESS_KEY_ID: test + AWS_SECRET_ACCESS_KEY: test + AWS_DEFAULT_REGION: us-east-1 + AWS_EC2_METADATA_DISABLED: "true" + AWS_CONFIG_FILE: scripts/demo-aws-config/config + AWS_SHARED_CREDENTIALS_FILE: scripts/demo-aws-config/credentials cmds: - | set -e @@ -199,12 +218,7 @@ tasks: echo "==========================================" echo "Running: $tape" echo "==========================================" - docker run --rm --network host \ - -v "$(pwd)":/vhs \ - -v "$(pwd)/scripts/demo-aws-config:/root/.aws:ro" \ - -e AWS_ENDPOINT_URL=http://localhost:4566 \ - -e AWS_EC2_METADATA_DISABLED=true \ - ghcr.io/charmbracelet/vhs "$tape" + vhs "$tape" done test-localstack: diff --git a/custom/events/rules/dao.go b/custom/events/rules/dao.go index c827c3b3..c30cd8cd 100644 --- a/custom/events/rules/dao.go +++ b/custom/events/rules/dao.go @@ -2,6 +2,7 @@ package rules import ( "context" + "strings" "github.com/aws/aws-sdk-go-v2/service/eventbridge" "github.com/aws/aws-sdk-go-v2/service/eventbridge/types" @@ -61,8 +62,12 @@ func (d *RuleDAO) List(ctx context.Context) ([]dao.Resource, error) { } func (d *RuleDAO) Get(ctx context.Context, id string) (dao.Resource, error) { + ruleName, eventBusName := parseRuleID(id) input := &eventbridge.DescribeRuleInput{ - Name: &id, + Name: &ruleName, + } + if eventBusName != "" { + input.EventBusName = &eventBusName } output, err := d.client.DescribeRule(ctx, input) @@ -90,7 +95,7 @@ func (d *RuleDAO) Get(ctx context.Context, id string) (dao.Resource, error) { // Fetch targets targetsInput := &eventbridge.ListTargetsByRuleInput{ - Rule: &id, + Rule: &ruleName, EventBusName: output.EventBusName, } if targetsOutput, err := d.client.ListTargetsByRule(ctx, targetsInput); err == nil { @@ -101,9 +106,13 @@ func (d *RuleDAO) Get(ctx context.Context, id string) (dao.Resource, error) { } func (d *RuleDAO) Delete(ctx context.Context, id string) error { + ruleName, eventBusName := parseRuleID(id) // First, need to remove all targets targetsInput := &eventbridge.ListTargetsByRuleInput{ - Rule: &id, + Rule: &ruleName, + } + if eventBusName != "" { + targetsInput.EventBusName = &eventBusName } targetsOutput, err := d.client.ListTargetsByRule(ctx, targetsInput) if err == nil && len(targetsOutput.Targets) > 0 { @@ -114,10 +123,14 @@ func (d *RuleDAO) Delete(ctx context.Context, id string) error { } } if len(targetIds) > 0 { - _, err = d.client.RemoveTargets(ctx, &eventbridge.RemoveTargetsInput{ - Rule: &id, + removeInput := &eventbridge.RemoveTargetsInput{ + Rule: &ruleName, Ids: targetIds, - }) + } + if eventBusName != "" { + removeInput.EventBusName = &eventBusName + } + _, err = d.client.RemoveTargets(ctx, removeInput) if err != nil { return apperrors.Wrapf(err, "remove targets for rule %s", id) } @@ -125,7 +138,10 @@ func (d *RuleDAO) Delete(ctx context.Context, id string) error { } input := &eventbridge.DeleteRuleInput{ - Name: &id, + Name: &ruleName, + } + if eventBusName != "" { + input.EventBusName = &eventBusName } _, err = d.client.DeleteRule(ctx, input) @@ -147,10 +163,12 @@ type RuleResource struct { // NewRuleResource creates a new RuleResource func NewRuleResource(rule types.Rule) *RuleResource { name := appaws.Str(rule.Name) + eventBusName := appaws.Str(rule.EventBusName) + id := ruleID(name, eventBusName) return &RuleResource{ BaseResource: dao.BaseResource{ - ID: name, + ID: id, Name: name, ARN: appaws.Str(rule.Arn), Tags: nil, @@ -160,6 +178,21 @@ func NewRuleResource(rule types.Rule) *RuleResource { } } +func ruleID(name, eventBusName string) string { + if eventBusName == "" { + return name + } + return eventBusName + "/" + name +} + +func parseRuleID(id string) (name, eventBusName string) { + idx := strings.LastIndex(id, "/") + if idx < 0 { + return id, "" + } + return id[idx+1:], id[:idx] +} + // ARN returns the rule ARN func (r *RuleResource) ARN() string { if r.Item.Arn != nil { diff --git a/custom/events/rules/resource_test.go b/custom/events/rules/resource_test.go new file mode 100644 index 00000000..366b860d --- /dev/null +++ b/custom/events/rules/resource_test.go @@ -0,0 +1,45 @@ +package rules + +import ( + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/eventbridge/types" +) + +func TestNewRuleResourceUsesBusQualifiedID(t *testing.T) { + rule := types.Rule{ + Name: aws.String("nightly"), + EventBusName: aws.String("custom-bus"), + } + + resource := NewRuleResource(rule) + + if resource.GetID() != "custom-bus/nightly" { + t.Fatalf("GetID() = %q, want %q", resource.GetID(), "custom-bus/nightly") + } + if resource.GetName() != "nightly" { + t.Fatalf("GetName() = %q, want %q", resource.GetName(), "nightly") + } +} + +func TestParseRuleID(t *testing.T) { + tests := []struct { + id string + name string + eventBus string + }{ + {id: "nightly", name: "nightly", eventBus: ""}, + {id: "custom-bus/nightly", name: "nightly", eventBus: "custom-bus"}, + {id: "aws.partner/example.com/account/nightly", name: "nightly", eventBus: "aws.partner/example.com/account"}, + } + + for _, tt := range tests { + t.Run(tt.id, func(t *testing.T) { + name, eventBus := parseRuleID(tt.id) + if name != tt.name || eventBus != tt.eventBus { + t.Fatalf("parseRuleID(%q) = (%q, %q), want (%q, %q)", tt.id, name, eventBus, tt.name, tt.eventBus) + } + }) + } +} diff --git a/custom/iam/policies/dao.go b/custom/iam/policies/dao.go index 669265dc..b4326fae 100644 --- a/custom/iam/policies/dao.go +++ b/custom/iam/policies/dao.go @@ -8,6 +8,7 @@ import ( appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" + "github.com/clawscli/claws/internal/enrichment" apperrors "github.com/clawscli/claws/internal/errors" ) @@ -88,6 +89,11 @@ func (d *PolicyDAO) Get(ctx context.Context, id string) (dao.Resource, error) { }) if err == nil && versionOutput.PolicyVersion != nil && versionOutput.PolicyVersion.Document != nil { res.PolicyDocument = *versionOutput.PolicyVersion.Document + res.PolicyDocumentStatus = enrichment.Fetched + } else if err != nil { + res.PolicyDocumentStatus = enrichment.FailureStatus(err) + } else { + res.PolicyDocumentStatus = enrichment.Fetched } } @@ -96,6 +102,9 @@ func (d *PolicyDAO) Get(ctx context.Context, id string) (dao.Resource, error) { res.AttachedUsers = entities.PolicyUsers res.AttachedRoles = entities.PolicyRoles res.AttachedGroups = entities.PolicyGroups + res.AttachedEntitiesStatus = enrichment.Fetched + } else { + res.AttachedEntitiesStatus = enrichment.FailureStatus(err) } return res, nil @@ -120,11 +129,13 @@ func (d *PolicyDAO) Delete(ctx context.Context, id string) error { // PolicyResource wraps an IAM Policy type PolicyResource struct { dao.BaseResource - Item types.Policy - PolicyDocument string - AttachedUsers []types.PolicyUser - AttachedRoles []types.PolicyRole - AttachedGroups []types.PolicyGroup + Item types.Policy + PolicyDocument string + PolicyDocumentStatus enrichment.Status + AttachedUsers []types.PolicyUser + AttachedRoles []types.PolicyRole + AttachedGroups []types.PolicyGroup + AttachedEntitiesStatus enrichment.Status } // NewPolicyResource creates a new PolicyResource diff --git a/custom/iam/policies/render.go b/custom/iam/policies/render.go index 1cc3c4b0..b9385fd1 100644 --- a/custom/iam/policies/render.go +++ b/custom/iam/policies/render.go @@ -8,6 +8,7 @@ import ( appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" + "github.com/clawscli/claws/internal/enrichment" "github.com/clawscli/claws/internal/render" ) @@ -131,7 +132,10 @@ func (r *PolicyRenderer) RenderDetail(resource dao.Resource) string { } // Attached Entities - if len(pr.AttachedUsers) > 0 || len(pr.AttachedRoles) > 0 || len(pr.AttachedGroups) > 0 { + if enrichment.IsFailure(pr.AttachedEntitiesStatus) { + d.Section("Attached To") + d.Field("Entities", enrichment.Display(pr.AttachedEntitiesStatus)) + } else if len(pr.AttachedUsers) > 0 || len(pr.AttachedRoles) > 0 || len(pr.AttachedGroups) > 0 { d.Section("Attached To") if len(pr.AttachedUsers) > 0 { d.Field("Users", fmt.Sprintf("%d", len(pr.AttachedUsers))) @@ -154,7 +158,10 @@ func (r *PolicyRenderer) RenderDetail(resource dao.Resource) string { } // Policy Document - if pr.PolicyDocument != "" { + if enrichment.IsFailure(pr.PolicyDocumentStatus) { + d.Section("Policy Document") + d.Field("Document", enrichment.Display(pr.PolicyDocumentStatus)) + } else if pr.PolicyDocument != "" { d.Section("Policy Document") d.Line(formatPolicyDoc(pr.PolicyDocument)) } diff --git a/custom/iam/roles/dao.go b/custom/iam/roles/dao.go index 8f3f14c0..1cdfb73b 100644 --- a/custom/iam/roles/dao.go +++ b/custom/iam/roles/dao.go @@ -8,6 +8,7 @@ import ( appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" + "github.com/clawscli/claws/internal/enrichment" apperrors "github.com/clawscli/claws/internal/errors" ) @@ -96,11 +97,17 @@ func (d *RoleDAO) Get(ctx context.Context, id string) (dao.Resource, error) { // Fetch attached policies if policies, err := d.client.ListAttachedRolePolicies(ctx, &iam.ListAttachedRolePoliciesInput{RoleName: &id}); err == nil { res.AttachedPolicies = policies.AttachedPolicies + res.AttachedPoliciesStatus = enrichment.Fetched + } else { + res.AttachedPoliciesStatus = enrichment.FailureStatus(err) } // Fetch inline policy names if inline, err := d.client.ListRolePolicies(ctx, &iam.ListRolePoliciesInput{RoleName: &id}); err == nil { res.InlinePolicies = inline.PolicyNames + res.InlinePoliciesStatus = enrichment.Fetched + } else { + res.InlinePoliciesStatus = enrichment.FailureStatus(err) } return res, nil @@ -125,9 +132,11 @@ func (d *RoleDAO) Delete(ctx context.Context, id string) error { // RoleResource wraps an IAM Role type RoleResource struct { dao.BaseResource - Item types.Role - AttachedPolicies []types.AttachedPolicy - InlinePolicies []string + Item types.Role + AttachedPolicies []types.AttachedPolicy + InlinePolicies []string + AttachedPoliciesStatus enrichment.Status + InlinePoliciesStatus enrichment.Status } // NewRoleResource creates a new RoleResource diff --git a/custom/iam/roles/render.go b/custom/iam/roles/render.go index 696746e4..471a9588 100644 --- a/custom/iam/roles/render.go +++ b/custom/iam/roles/render.go @@ -8,6 +8,7 @@ import ( appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" + "github.com/clawscli/claws/internal/enrichment" "github.com/clawscli/claws/internal/render" ) @@ -124,7 +125,16 @@ func (r *RoleRenderer) RenderDetail(resource dao.Resource) string { // Attached Policies d.Section("Attached Policies") - if len(rr.AttachedPolicies) == 0 && len(rr.InlinePolicies) == 0 { + managedFailed := enrichment.IsFailure(rr.AttachedPoliciesStatus) + inlineFailed := enrichment.IsFailure(rr.InlinePoliciesStatus) + if managedFailed || inlineFailed { + if managedFailed { + d.Field("Managed Policies", enrichment.Display(rr.AttachedPoliciesStatus)) + } + if inlineFailed { + d.Field("Inline Policies", enrichment.Display(rr.InlinePoliciesStatus)) + } + } else if len(rr.AttachedPolicies) == 0 && len(rr.InlinePolicies) == 0 { d.Field("Policies", render.Empty) } else { if len(rr.AttachedPolicies) > 0 { diff --git a/custom/iam/roles/resource_test.go b/custom/iam/roles/resource_test.go index 94cabfc9..7a644b40 100644 --- a/custom/iam/roles/resource_test.go +++ b/custom/iam/roles/resource_test.go @@ -1,11 +1,14 @@ package roles import ( + "strings" "testing" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/iam/types" + + "github.com/clawscli/claws/internal/enrichment" ) func TestNewRoleResource(t *testing.T) { @@ -53,6 +56,24 @@ func TestNewRoleResource(t *testing.T) { } } +func TestRoleRendererShowsUnknownForFailedPolicyEnrichment(t *testing.T) { + role := types.Role{RoleName: aws.String("my-role")} + resource := NewRoleResource(role) + resource.AttachedPoliciesStatus = enrichment.FetchFailed + resource.InlinePoliciesStatus = enrichment.AccessDenied + + detail := (&RoleRenderer{}).RenderDetail(resource) + + for _, want := range []string{"Unknown (fetch failed)", "Unknown (access denied)"} { + if !strings.Contains(detail, want) { + t.Fatalf("expected %q in detail, got %q", want, detail) + } + } + if strings.Contains(detail, "Policies: Empty") { + t.Fatalf("fetch failures must not render as empty policies: %q", detail) + } +} + func TestRoleResource_MinimalRole(t *testing.T) { role := types.Role{ RoleName: aws.String("minimal-role"), diff --git a/custom/iam/users/dao.go b/custom/iam/users/dao.go index 6ee3bc9d..172c5b03 100644 --- a/custom/iam/users/dao.go +++ b/custom/iam/users/dao.go @@ -8,17 +8,23 @@ import ( appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" + "github.com/clawscli/claws/internal/enrichment" apperrors "github.com/clawscli/claws/internal/errors" ) // UserDetail contains extended user information from multiple API calls type UserDetail struct { - User types.User - AccessKeys []types.AccessKeyMetadata - MFADevices []types.MFADevice - Groups []types.Group - AttachedPolicies []types.AttachedPolicy - InlinePolicies []string + User types.User + AccessKeys []types.AccessKeyMetadata + MFADevices []types.MFADevice + Groups []types.Group + AttachedPolicies []types.AttachedPolicy + InlinePolicies []string + AccessKeysStatus enrichment.Status + MFADevicesStatus enrichment.Status + GroupsStatus enrichment.Status + AttachedPoliciesStatus enrichment.Status + InlinePoliciesStatus enrichment.Status } // UserDAO provides data access for IAM Users @@ -84,26 +90,41 @@ func (d *UserDAO) Get(ctx context.Context, id string) (dao.Resource, error) { // Fetch access keys if keys, err := d.client.ListAccessKeys(ctx, &iam.ListAccessKeysInput{UserName: &id}); err == nil { detail.AccessKeys = keys.AccessKeyMetadata + detail.AccessKeysStatus = enrichment.Fetched + } else { + detail.AccessKeysStatus = enrichment.FailureStatus(err) } // Fetch MFA devices if mfa, err := d.client.ListMFADevices(ctx, &iam.ListMFADevicesInput{UserName: &id}); err == nil { detail.MFADevices = mfa.MFADevices + detail.MFADevicesStatus = enrichment.Fetched + } else { + detail.MFADevicesStatus = enrichment.FailureStatus(err) } // Fetch groups if groups, err := d.client.ListGroupsForUser(ctx, &iam.ListGroupsForUserInput{UserName: &id}); err == nil { detail.Groups = groups.Groups + detail.GroupsStatus = enrichment.Fetched + } else { + detail.GroupsStatus = enrichment.FailureStatus(err) } // Fetch attached policies if policies, err := d.client.ListAttachedUserPolicies(ctx, &iam.ListAttachedUserPoliciesInput{UserName: &id}); err == nil { detail.AttachedPolicies = policies.AttachedPolicies + detail.AttachedPoliciesStatus = enrichment.Fetched + } else { + detail.AttachedPoliciesStatus = enrichment.FailureStatus(err) } // Fetch inline policy names if inline, err := d.client.ListUserPolicies(ctx, &iam.ListUserPoliciesInput{UserName: &id}); err == nil { detail.InlinePolicies = inline.PolicyNames + detail.InlinePoliciesStatus = enrichment.Fetched + } else { + detail.InlinePoliciesStatus = enrichment.FailureStatus(err) } return NewUserResourceWithDetail(detail), nil @@ -122,12 +143,17 @@ func (d *UserDAO) Delete(ctx context.Context, id string) error { // UserResource wraps an IAM User type UserResource struct { dao.BaseResource - Item types.User - AccessKeys []types.AccessKeyMetadata - MFADevices []types.MFADevice - Groups []types.Group - AttachedPolicies []types.AttachedPolicy - InlinePolicies []string + Item types.User + AccessKeys []types.AccessKeyMetadata + MFADevices []types.MFADevice + Groups []types.Group + AttachedPolicies []types.AttachedPolicy + InlinePolicies []string + AccessKeysStatus enrichment.Status + MFADevicesStatus enrichment.Status + GroupsStatus enrichment.Status + AttachedPoliciesStatus enrichment.Status + InlinePoliciesStatus enrichment.Status } // NewUserResource creates a new UserResource @@ -158,12 +184,17 @@ func NewUserResourceWithDetail(detail UserDetail) *UserResource { Tags: appaws.TagsToMap(detail.User.Tags), Data: detail.User, }, - Item: detail.User, - AccessKeys: detail.AccessKeys, - MFADevices: detail.MFADevices, - Groups: detail.Groups, - AttachedPolicies: detail.AttachedPolicies, - InlinePolicies: detail.InlinePolicies, + Item: detail.User, + AccessKeys: detail.AccessKeys, + MFADevices: detail.MFADevices, + Groups: detail.Groups, + AttachedPolicies: detail.AttachedPolicies, + InlinePolicies: detail.InlinePolicies, + AccessKeysStatus: detail.AccessKeysStatus, + MFADevicesStatus: detail.MFADevicesStatus, + GroupsStatus: detail.GroupsStatus, + AttachedPoliciesStatus: detail.AttachedPoliciesStatus, + InlinePoliciesStatus: detail.InlinePoliciesStatus, } } diff --git a/custom/iam/users/render.go b/custom/iam/users/render.go index 04e7e1fd..155cb8f4 100644 --- a/custom/iam/users/render.go +++ b/custom/iam/users/render.go @@ -6,6 +6,7 @@ import ( appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" + "github.com/clawscli/claws/internal/enrichment" "github.com/clawscli/claws/internal/render" ) @@ -115,7 +116,9 @@ func (r *UserRenderer) RenderDetail(resource dao.Resource) string { // Access Keys d.Section("Access Keys") - if len(ur.AccessKeys) == 0 { + if enrichment.IsFailure(ur.AccessKeysStatus) { + d.Field("Access Keys", enrichment.Display(ur.AccessKeysStatus)) + } else if len(ur.AccessKeys) == 0 { d.Field("Access Keys", render.Empty) } else { d.Field("Access Key Count", fmt.Sprintf("%d", len(ur.AccessKeys))) @@ -130,7 +133,9 @@ func (r *UserRenderer) RenderDetail(resource dao.Resource) string { // MFA Devices d.Section("MFA") - if len(ur.MFADevices) == 0 { + if enrichment.IsFailure(ur.MFADevicesStatus) { + d.Field("MFA Status", enrichment.Display(ur.MFADevicesStatus)) + } else if len(ur.MFADevices) == 0 { d.Field("MFA Status", "Not enabled") } else { d.Field("MFA Status", "Enabled") @@ -147,7 +152,9 @@ func (r *UserRenderer) RenderDetail(resource dao.Resource) string { // Groups d.Section("Groups") - if len(ur.Groups) == 0 { + if enrichment.IsFailure(ur.GroupsStatus) { + d.Field("Groups", enrichment.Display(ur.GroupsStatus)) + } else if len(ur.Groups) == 0 { d.Field("Groups", render.Empty) } else { d.Field("Group Count", fmt.Sprintf("%d", len(ur.Groups))) @@ -158,7 +165,16 @@ func (r *UserRenderer) RenderDetail(resource dao.Resource) string { // Attached Policies d.Section("Attached Policies") - if len(ur.AttachedPolicies) == 0 && len(ur.InlinePolicies) == 0 { + managedFailed := enrichment.IsFailure(ur.AttachedPoliciesStatus) + inlineFailed := enrichment.IsFailure(ur.InlinePoliciesStatus) + if managedFailed || inlineFailed { + if managedFailed { + d.Field("Managed Policies", enrichment.Display(ur.AttachedPoliciesStatus)) + } + if inlineFailed { + d.Field("Inline Policies", enrichment.Display(ur.InlinePoliciesStatus)) + } + } else if len(ur.AttachedPolicies) == 0 && len(ur.InlinePolicies) == 0 { d.Field("Policies", render.Empty) } else { if len(ur.AttachedPolicies) > 0 { diff --git a/custom/s3/buckets/dao.go b/custom/s3/buckets/dao.go index 2063f001..2303c5cf 100644 --- a/custom/s3/buckets/dao.go +++ b/custom/s3/buckets/dao.go @@ -2,14 +2,17 @@ package buckets import ( "context" + "errors" "fmt" "time" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/aws/smithy-go" appaws "github.com/clawscli/claws/internal/aws" "github.com/clawscli/claws/internal/dao" + "github.com/clawscli/claws/internal/enrichment" apperrors "github.com/clawscli/claws/internal/errors" ) @@ -102,6 +105,29 @@ func (d *BucketDAO) Get(ctx context.Context, id string) (dao.Resource, error) { return resource, nil } +func enrichmentFailureStatus(err error) enrichment.Status { + if apperrors.IsAccessDenied(err) { + return enrichment.AccessDenied + } + if isNotConfiguredError(err) { + return enrichment.NotConfigured + } + return enrichment.FetchFailed +} + +func isNotConfiguredError(err error) bool { + var apiErr smithy.APIError + if !errors.As(err, &apiErr) { + return false + } + switch apiErr.ErrorCode() { + case "ServerSideEncryptionConfigurationNotFoundError", "NoSuchPublicAccessBlockConfiguration": + return true + default: + return false + } +} + // getRegionClient creates an S3 client for the specified region func (d *BucketDAO) getRegionClient(ctx context.Context, region string) (*s3.Client, error) { cfg, err := appaws.NewConfigWithRegion(ctx, region) @@ -117,12 +143,15 @@ func (d *BucketDAO) fetchVersioning(ctx context.Context, client *s3.Client, buck Bucket: &bucket, }) if err != nil { + r.VersioningStatus = enrichmentFailureStatus(err) return } if output.Status != "" { r.Versioning = string(output.Status) + r.VersioningStatus = enrichment.Configured } else { r.Versioning = "Disabled" + r.VersioningStatus = enrichment.NotConfigured } if output.MFADelete != "" { r.MFADelete = string(output.MFADelete) @@ -135,10 +164,12 @@ func (d *BucketDAO) fetchEncryption(ctx context.Context, client *s3.Client, buck Bucket: &bucket, }) if err != nil { + r.EncryptionStatus = enrichmentFailureStatus(err) return } if output.ServerSideEncryptionConfiguration != nil && len(output.ServerSideEncryptionConfiguration.Rules) > 0 { r.EncryptionEnabled = true + r.EncryptionStatus = enrichment.Configured rule := output.ServerSideEncryptionConfiguration.Rules[0] if rule.ApplyServerSideEncryptionByDefault != nil { r.EncryptionAlgorithm = string(rule.ApplyServerSideEncryptionByDefault.SSEAlgorithm) @@ -149,6 +180,8 @@ func (d *BucketDAO) fetchEncryption(ctx context.Context, client *s3.Client, buck if rule.BucketKeyEnabled != nil { r.BucketKeyEnabled = *rule.BucketKeyEnabled } + } else { + r.EncryptionStatus = enrichment.NotConfigured } } @@ -158,9 +191,11 @@ func (d *BucketDAO) fetchPublicAccessBlock(ctx context.Context, client *s3.Clien Bucket: &bucket, }) if err != nil { + r.PublicAccessBlockStatus = enrichmentFailureStatus(err) return } if output.PublicAccessBlockConfiguration != nil { + r.PublicAccessBlockStatus = enrichment.Configured cfg := output.PublicAccessBlockConfiguration r.PublicAccessBlock = &PublicAccessBlockInfo{ BlockPublicAcls: cfg.BlockPublicAcls != nil && *cfg.BlockPublicAcls, @@ -168,6 +203,8 @@ func (d *BucketDAO) fetchPublicAccessBlock(ctx context.Context, client *s3.Clien BlockPublicPolicy: cfg.BlockPublicPolicy != nil && *cfg.BlockPublicPolicy, RestrictPublicBuckets: cfg.RestrictPublicBuckets != nil && *cfg.RestrictPublicBuckets, } + } else { + r.PublicAccessBlockStatus = enrichment.NotConfigured } } @@ -245,17 +282,20 @@ type BucketResource struct { CreationDate time.Time // Extended info (fetched in Get() only) - Versioning string - MFADelete string - EncryptionEnabled bool - EncryptionAlgorithm string - EncryptionKMSKeyID string - BucketKeyEnabled bool - PublicAccessBlock *PublicAccessBlockInfo - LifecycleRulesCount int - ObjectLockEnabled bool - ObjectLockMode string - ObjectLockRetention string + Versioning string + VersioningStatus enrichment.Status + MFADelete string + EncryptionEnabled bool + EncryptionStatus enrichment.Status + EncryptionAlgorithm string + EncryptionKMSKeyID string + BucketKeyEnabled bool + PublicAccessBlock *PublicAccessBlockInfo + PublicAccessBlockStatus enrichment.Status + LifecycleRulesCount int + ObjectLockEnabled bool + ObjectLockMode string + ObjectLockRetention string } // PublicAccessBlockInfo holds public access block settings diff --git a/custom/s3/buckets/render.go b/custom/s3/buckets/render.go index e88d0d42..c9ef81a4 100644 --- a/custom/s3/buckets/render.go +++ b/custom/s3/buckets/render.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/clawscli/claws/internal/dao" + "github.com/clawscli/claws/internal/enrichment" "github.com/clawscli/claws/internal/render" ) @@ -97,10 +98,21 @@ func (r *BucketRenderer) RenderDetail(resource dao.Resource) string { // Versioning d.Section("Versioning") - if b.Versioning != "" { + switch b.VersioningStatus { + case enrichment.Configured: d.Field("Status", b.Versioning) - } else { + case enrichment.NotConfigured: d.Field("Status", render.NotConfigured) + case enrichment.AccessDenied, enrichment.FetchFailed: + d.Field("Status", enrichment.Display(b.VersioningStatus)) + default: + // List() resources do not fetch enrichment status, but older callers may + // still populate Versioning directly. + if b.Versioning != "" { + d.Field("Status", b.Versioning) + } else { + d.Field("Status", enrichment.Display(enrichment.Unknown)) + } } if b.MFADelete != "" { d.Field("MFA Delete", b.MFADelete) @@ -108,7 +120,9 @@ func (r *BucketRenderer) RenderDetail(resource dao.Resource) string { // Encryption d.Section("Server-Side Encryption") - if b.EncryptionEnabled { + if b.EncryptionStatus == enrichment.AccessDenied || b.EncryptionStatus == enrichment.FetchFailed || b.EncryptionStatus == enrichment.Unknown { + d.Field("Status", enrichment.Display(b.EncryptionStatus)) + } else if b.EncryptionEnabled { d.Field("Status", "Enabled") d.Field("Algorithm", b.EncryptionAlgorithm) if b.EncryptionKMSKeyID != "" { @@ -123,7 +137,9 @@ func (r *BucketRenderer) RenderDetail(resource dao.Resource) string { // Public Access Block d.Section("Block Public Access") - if b.PublicAccessBlock != nil { + if b.PublicAccessBlockStatus == enrichment.AccessDenied || b.PublicAccessBlockStatus == enrichment.FetchFailed || b.PublicAccessBlockStatus == enrichment.Unknown { + d.Field("Status", enrichment.Display(b.PublicAccessBlockStatus)) + } else if b.PublicAccessBlock != nil { pab := b.PublicAccessBlock allBlocked := pab.BlockPublicAcls && pab.IgnorePublicAcls && pab.BlockPublicPolicy && pab.RestrictPublicBuckets if allBlocked { @@ -199,13 +215,17 @@ func (r *BucketRenderer) RenderSummary(resource dao.Resource) []render.SummaryFi } // Versioning (if fetched) - if b.Versioning != "" { + if b.VersioningStatus == enrichment.Configured && b.Versioning != "" { fields = append(fields, render.SummaryField{Label: "Versioning", Value: b.Versioning}) + } else if b.VersioningStatus == enrichment.AccessDenied || b.VersioningStatus == enrichment.FetchFailed { + fields = append(fields, render.SummaryField{Label: "Versioning", Value: enrichment.Display(b.VersioningStatus)}) } // Encryption (if fetched) if b.EncryptionEnabled { fields = append(fields, render.SummaryField{Label: "Encryption", Value: b.EncryptionAlgorithm}) + } else if b.EncryptionStatus == enrichment.AccessDenied || b.EncryptionStatus == enrichment.FetchFailed { + fields = append(fields, render.SummaryField{Label: "Encryption", Value: enrichment.Display(b.EncryptionStatus)}) } // Public Access Block (if fetched) @@ -217,6 +237,8 @@ func (r *BucketRenderer) RenderSummary(resource dao.Resource) []render.SummaryFi } else { fields = append(fields, render.SummaryField{Label: "Public Access", Value: "Partial"}) } + } else if b.PublicAccessBlockStatus == enrichment.AccessDenied || b.PublicAccessBlockStatus == enrichment.FetchFailed { + fields = append(fields, render.SummaryField{Label: "Public Access", Value: enrichment.Display(b.PublicAccessBlockStatus)}) } // Object Lock (if enabled) diff --git a/custom/s3/buckets/resource_test.go b/custom/s3/buckets/resource_test.go index 4fdd49d2..621ff825 100644 --- a/custom/s3/buckets/resource_test.go +++ b/custom/s3/buckets/resource_test.go @@ -1,11 +1,15 @@ package buckets import ( + "strings" "testing" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/aws/smithy-go" + + "github.com/clawscli/claws/internal/enrichment" ) func TestNewBucketResource(t *testing.T) { @@ -76,8 +80,10 @@ func TestBucketResource_ExtendedInfo(t *testing.T) { // Set extended info (normally done by DAO.Get) resource.Region = "us-west-2" resource.Versioning = "Enabled" + resource.VersioningStatus = enrichment.Configured resource.MFADelete = "Disabled" resource.EncryptionEnabled = true + resource.EncryptionStatus = enrichment.Configured resource.EncryptionAlgorithm = "aws:kms" resource.EncryptionKMSKeyID = "arn:aws:kms:us-west-2:123456789012:key/abc123" resource.BucketKeyEnabled = true @@ -91,6 +97,7 @@ func TestBucketResource_ExtendedInfo(t *testing.T) { BlockPublicPolicy: true, RestrictPublicBuckets: true, } + resource.PublicAccessBlockStatus = enrichment.Configured // Verify extended info if resource.Region != "us-west-2" { @@ -116,6 +123,52 @@ func TestBucketResource_ExtendedInfo(t *testing.T) { } } +func TestBucketRendererShowsUnknownForFailedSecurityEnrichment(t *testing.T) { + resource := &BucketResource{ + BucketName: "test-bucket", + Region: "us-east-1", + VersioningStatus: enrichment.FetchFailed, + EncryptionStatus: enrichment.AccessDenied, + PublicAccessBlockStatus: enrichment.FetchFailed, + } + + detail := (&BucketRenderer{}).RenderDetail(resource) + + for _, want := range []string{ + "Unknown (fetch failed)", + "Unknown (access denied)", + } { + if !strings.Contains(detail, want) { + t.Fatalf("expected %q in detail, got %q", want, detail) + } + } + if strings.Contains(detail, "Not configured") { + t.Fatalf("fetch failures must not render as not configured: %q", detail) + } +} + +func TestS3EnrichmentFailureStatusClassifiesNotConfiguredErrors(t *testing.T) { + tests := []struct { + name string + code string + want enrichment.Status + }{ + {name: "encryption not configured", code: "ServerSideEncryptionConfigurationNotFoundError", want: enrichment.NotConfigured}, + {name: "public access block not configured", code: "NoSuchPublicAccessBlockConfiguration", want: enrichment.NotConfigured}, + {name: "access denied", code: "AccessDeniedException", want: enrichment.AccessDenied}, + {name: "other failure", code: "InternalError", want: enrichment.FetchFailed}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := &smithy.GenericAPIError{Code: tt.code, Message: tt.name} + if got := enrichmentFailureStatus(err); got != tt.want { + t.Fatalf("enrichmentFailureStatus(%q) = %q, want %q", tt.code, got, tt.want) + } + }) + } +} + func TestBucketResource_NilName(t *testing.T) { bucket := types.Bucket{ Name: nil, diff --git a/custom/sagemaker/training-jobs/render.go b/custom/sagemaker/training-jobs/render.go index 9522bc6c..df215a53 100644 --- a/custom/sagemaker/training-jobs/render.go +++ b/custom/sagemaker/training-jobs/render.go @@ -121,7 +121,7 @@ func (r *TrainingJobRenderer) RenderDetail(resource dao.Resource) string { if len(job.GetInputDataConfig()) > 0 || job.GetOutputS3Path() != "" || job.GetModelArtifactsS3() != "" { d.Section("Data Configuration") for _, ch := range job.GetInputDataConfig() { - if ch.ChannelName != nil && ch.DataSource != nil && ch.DataSource.S3DataSource != nil { + if ch.ChannelName != nil && ch.DataSource != nil && ch.DataSource.S3DataSource != nil && ch.DataSource.S3DataSource.S3Uri != nil { d.Field("Input: "+*ch.ChannelName, *ch.DataSource.S3DataSource.S3Uri) } } diff --git a/docs/ai-chat.ja.md b/docs/ai-chat.ja.md index 183f4840..bfb9da52 100644 --- a/docs/ai-chat.ja.md +++ b/docs/ai-chat.ja.md @@ -144,4 +144,4 @@ ai: save_sessions: true # デフォルト: false ``` -セッションは`~/.config/claws/sessions/`に保存されます。 +セッションは`~/.config/claws/chat/sessions/`に保存されます。 diff --git a/docs/ai-chat.ko.md b/docs/ai-chat.ko.md index e18f3935..52ff6a53 100644 --- a/docs/ai-chat.ko.md +++ b/docs/ai-chat.ko.md @@ -144,4 +144,4 @@ ai: save_sessions: true # 기본값: false ``` -세션은 `~/.config/claws/sessions/`에 저장됩니다. +세션은 `~/.config/claws/chat/sessions/`에 저장됩니다. diff --git a/docs/ai-chat.md b/docs/ai-chat.md index 80080914..4fd3fe4e 100644 --- a/docs/ai-chat.md +++ b/docs/ai-chat.md @@ -144,4 +144,4 @@ ai: save_sessions: true # Default: false ``` -Sessions are stored in `~/.config/claws/sessions/`. +Sessions are stored in `~/.config/claws/chat/sessions/`. diff --git a/docs/ai-chat.zh-CN.md b/docs/ai-chat.zh-CN.md index 30cf7b8b..c77c2ec3 100644 --- a/docs/ai-chat.zh-CN.md +++ b/docs/ai-chat.zh-CN.md @@ -144,4 +144,4 @@ ai: save_sessions: true # 默认:false ``` -会话存储在 `~/.config/claws/sessions/` 中。 +会话存储在 `~/.config/claws/chat/sessions/` 中。 diff --git a/docs/tapes/README.md b/docs/tapes/README.md index 5c203204..dd426134 100644 --- a/docs/tapes/README.md +++ b/docs/tapes/README.md @@ -14,7 +14,7 @@ This directory contains [VHS](https://github.com/charmbracelet/vhs) tape files f ## Usage (Recommended) -Use task commands from project root (requires Docker + Linux): +Use task commands from project root. These commands expect `vhs` to be installed on the host and use Docker only for the pinned LocalStack emulator: ```bash # Record everything (GIF + all screenshots) @@ -34,7 +34,7 @@ task test:vhs This automatically: - Builds the `claws` binary - Starts LocalStack with demo data -- Runs VHS in Docker with proper environment +- Runs host-installed VHS with the demo AWS config ## Manual Usage @@ -49,11 +49,13 @@ task build AWS_ENDPOINT_URL=http://localhost:4566 \ AWS_ACCESS_KEY_ID=test \ AWS_SECRET_ACCESS_KEY=test \ +AWS_CONFIG_FILE=scripts/demo-aws-config/config \ +AWS_SHARED_CREDENTIALS_FILE=scripts/demo-aws-config/credentials \ vhs docs/tapes/demo.tape ``` ## Notes -- `task demo:record` requires Linux (`--network host` for LocalStack access) -- Tapes use LocalStack for demo data (no real AWS credentials needed) +- `task demo:record` and `task test:vhs` run VHS on the host, so they work anywhere `vhs` and Docker are available +- Tapes use `localstack/localstack:4.12.0` for demo data (no real AWS credentials needed). Current LocalStack releases use account-based Hobby/commercial plans, so keep this pin unless you intentionally move to a token-backed LocalStack plan or another AWS emulator. - Adjust `Sleep` durations if rendering is slow diff --git a/docs/tapes/command-mode.tape b/docs/tapes/command-mode.tape index e4119228..23c20494 100644 --- a/docs/tapes/command-mode.tape +++ b/docs/tapes/command-mode.tape @@ -6,6 +6,12 @@ Set Width 1200 Set Height 600 Set TypingSpeed 0.08 +Hide +Type "export PS1='$ '; clear" +Enter +Sleep 200ms +Show + Type "./claws" Enter Sleep 2s diff --git a/docs/tapes/demo.tape b/docs/tapes/demo.tape index 46efb170..df438deb 100644 --- a/docs/tapes/demo.tape +++ b/docs/tapes/demo.tape @@ -8,6 +8,12 @@ Set Width 1920 Set Height 1080 Set TypingSpeed 0.08 +Hide +Type "export PS1='$ '; clear" +Enter +Sleep 200ms +Show + Sleep 0.5s Type "./claws -p prod,dev -r us-east-1,us-west-2" Sleep 1.5s diff --git a/docs/tapes/features.tape b/docs/tapes/features.tape index 83f591c7..50034433 100644 --- a/docs/tapes/features.tape +++ b/docs/tapes/features.tape @@ -6,6 +6,12 @@ Set Width 1920 Set Height 1080 Set TypingSpeed 0.05 +Hide +Type "export PS1='$ '; clear" +Enter +Sleep 200ms +Show + Type "./claws" Enter Sleep 2s diff --git a/docs/tapes/theme-light.tape b/docs/tapes/theme-light.tape index 8bbfb0d8..294e645c 100644 --- a/docs/tapes/theme-light.tape +++ b/docs/tapes/theme-light.tape @@ -4,6 +4,12 @@ Set Width 1920 Set Height 1080 Set TypingSpeed 0.05s +Hide +Type "export PS1='$ '; clear" +Enter +Sleep 200ms +Show + Type "./claws -t light" Enter Sleep 2s diff --git a/docs/tapes/themes.tape b/docs/tapes/themes.tape index 1682a01d..7d81b477 100644 --- a/docs/tapes/themes.tape +++ b/docs/tapes/themes.tape @@ -6,6 +6,12 @@ Set Width 1920 Set Height 1080 Set TypingSpeed 0.05 +Hide +Type "export PS1='$ '; clear" +Enter +Sleep 200ms +Show + # Theme: dark (default) Type "./claws -t dark" Enter diff --git a/flake.nix b/flake.nix index 9f0c5520..330375c4 100644 --- a/flake.nix +++ b/flake.nix @@ -14,7 +14,9 @@ packages = with pkgs; [ go_1_25 go-task + gopls golangci-lint + vhs ]; env.GOROOT = "${pkgs.go_1_25}/share/go"; diff --git a/go.mod b/go.mod index e04480f2..235ed703 100644 --- a/go.mod +++ b/go.mod @@ -3,126 +3,123 @@ module github.com/clawscli/claws go 1.25.4 require ( - charm.land/bubbles/v2 v2.0.0-rc.1 - charm.land/bubbletea/v2 v2.0.0-rc.2 - charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106192539-4b304240aab7 + charm.land/bubbles/v2 v2.1.0 + charm.land/bubbletea/v2 v2.0.6 + charm.land/lipgloss/v2 v2.0.3 github.com/atotto/clipboard v0.1.4 - github.com/aws/aws-sdk-go-v2 v1.41.1 - github.com/aws/aws-sdk-go-v2/config v1.32.5 - github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.45.7 - github.com/aws/aws-sdk-go-v2/service/acm v1.37.18 - github.com/aws/aws-sdk-go-v2/service/apigateway v1.38.3 - github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.33.4 - github.com/aws/aws-sdk-go-v2/service/apprunner v1.39.9 - github.com/aws/aws-sdk-go-v2/service/appsync v1.53.0 - github.com/aws/aws-sdk-go-v2/service/athena v1.56.4 - github.com/aws/aws-sdk-go-v2/service/autoscaling v1.62.4 - github.com/aws/aws-sdk-go-v2/service/backup v1.54.5 - github.com/aws/aws-sdk-go-v2/service/batch v1.58.11 - github.com/aws/aws-sdk-go-v2/service/bedrock v1.53.0 - github.com/aws/aws-sdk-go-v2/service/bedrockagent v1.52.2 - github.com/aws/aws-sdk-go-v2/service/bedrockagentcorecontrol v1.15.1 - github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.47.1 - github.com/aws/aws-sdk-go-v2/service/budgets v1.42.3 - github.com/aws/aws-sdk-go-v2/service/cloudformation v1.71.4 - github.com/aws/aws-sdk-go-v2/service/cloudfront v1.58.3 - github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.55.4 - github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.53.0 - github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.62.2 - github.com/aws/aws-sdk-go-v2/service/codebuild v1.68.8 - github.com/aws/aws-sdk-go-v2/service/codepipeline v1.46.16 - github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.57.17 - github.com/aws/aws-sdk-go-v2/service/computeoptimizer v1.49.3 - github.com/aws/aws-sdk-go-v2/service/configservice v1.59.9 - github.com/aws/aws-sdk-go-v2/service/costexplorer v1.62.0 - github.com/aws/aws-sdk-go-v2/service/datasync v1.57.0 - github.com/aws/aws-sdk-go-v2/service/detective v1.38.8 - github.com/aws/aws-sdk-go-v2/service/directconnect v1.38.10 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.5 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.276.1 - github.com/aws/aws-sdk-go-v2/service/ecr v1.54.4 - github.com/aws/aws-sdk-go-v2/service/ecs v1.69.5 - github.com/aws/aws-sdk-go-v2/service/eks v1.76.3 - github.com/aws/aws-sdk-go-v2/service/elasticache v1.51.8 - github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.54.5 - github.com/aws/aws-sdk-go-v2/service/emr v1.57.4 - github.com/aws/aws-sdk-go-v2/service/eventbridge v1.45.17 - github.com/aws/aws-sdk-go-v2/service/fms v1.44.16 - github.com/aws/aws-sdk-go-v2/service/gamelift v1.50.0 - github.com/aws/aws-sdk-go-v2/service/glue v1.135.3 - github.com/aws/aws-sdk-go-v2/service/guardduty v1.70.1 - github.com/aws/aws-sdk-go-v2/service/health v1.35.5 - github.com/aws/aws-sdk-go-v2/service/iam v1.53.1 - github.com/aws/aws-sdk-go-v2/service/inspector2 v1.46.1 - github.com/aws/aws-sdk-go-v2/service/kinesis v1.42.9 - github.com/aws/aws-sdk-go-v2/service/kms v1.49.4 - github.com/aws/aws-sdk-go-v2/service/lambda v1.87.0 - github.com/aws/aws-sdk-go-v2/service/licensemanager v1.37.4 - github.com/aws/aws-sdk-go-v2/service/macie2 v1.50.8 - github.com/aws/aws-sdk-go-v2/service/networkfirewall v1.59.2 - github.com/aws/aws-sdk-go-v2/service/opensearch v1.56.0 - github.com/aws/aws-sdk-go-v2/service/organizations v1.50.0 - github.com/aws/aws-sdk-go-v2/service/rds v1.113.1 - github.com/aws/aws-sdk-go-v2/service/redshift v1.61.4 - github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.31.5 - github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0 - github.com/aws/aws-sdk-go-v2/service/s3 v1.93.2 - github.com/aws/aws-sdk-go-v2/service/s3vectors v1.6.1 - github.com/aws/aws-sdk-go-v2/service/sagemaker v1.228.2 - github.com/aws/aws-sdk-go-v2/service/savingsplans v1.31.1 - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.0 - github.com/aws/aws-sdk-go-v2/service/securityhub v1.67.2 - github.com/aws/aws-sdk-go-v2/service/servicequotas v1.33.12 - github.com/aws/aws-sdk-go-v2/service/sfn v1.40.5 - github.com/aws/aws-sdk-go-v2/service/sns v1.39.10 - github.com/aws/aws-sdk-go-v2/service/sqs v1.42.20 - github.com/aws/aws-sdk-go-v2/service/ssm v1.67.7 - github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 - github.com/aws/aws-sdk-go-v2/service/transcribe v1.53.10 - github.com/aws/aws-sdk-go-v2/service/transfer v1.68.4 - github.com/aws/aws-sdk-go-v2/service/trustedadvisor v1.13.17 - github.com/aws/aws-sdk-go-v2/service/wafv2 v1.70.4 - github.com/aws/aws-sdk-go-v2/service/xray v1.36.16 - github.com/aws/smithy-go v1.24.0 - github.com/charmbracelet/x/ansi v0.11.3 + github.com/aws/aws-sdk-go-v2 v1.41.7 + github.com/aws/aws-sdk-go-v2/config v1.32.17 + github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.47.2 + github.com/aws/aws-sdk-go-v2/service/acm v1.38.3 + github.com/aws/aws-sdk-go-v2/service/apigateway v1.39.3 + github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.34.3 + github.com/aws/aws-sdk-go-v2/service/apprunner v1.39.16 + github.com/aws/aws-sdk-go-v2/service/appsync v1.53.7 + github.com/aws/aws-sdk-go-v2/service/athena v1.57.6 + github.com/aws/aws-sdk-go-v2/service/autoscaling v1.66.2 + github.com/aws/aws-sdk-go-v2/service/backup v1.55.2 + github.com/aws/aws-sdk-go-v2/service/batch v1.64.1 + github.com/aws/aws-sdk-go-v2/service/bedrock v1.59.2 + github.com/aws/aws-sdk-go-v2/service/bedrockagent v1.53.2 + github.com/aws/aws-sdk-go-v2/service/bedrockagentcorecontrol v1.34.0 + github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.6 + github.com/aws/aws-sdk-go-v2/service/budgets v1.43.6 + github.com/aws/aws-sdk-go-v2/service/cloudformation v1.71.11 + github.com/aws/aws-sdk-go-v2/service/cloudfront v1.62.0 + github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.55.11 + github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.56.3 + github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.71.1 + github.com/aws/aws-sdk-go-v2/service/codebuild v1.68.15 + github.com/aws/aws-sdk-go-v2/service/codepipeline v1.46.23 + github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.60.2 + github.com/aws/aws-sdk-go-v2/service/computeoptimizer v1.50.1 + github.com/aws/aws-sdk-go-v2/service/configservice v1.62.3 + github.com/aws/aws-sdk-go-v2/service/costexplorer v1.63.8 + github.com/aws/aws-sdk-go-v2/service/datasync v1.58.4 + github.com/aws/aws-sdk-go-v2/service/detective v1.38.15 + github.com/aws/aws-sdk-go-v2/service/directconnect v1.38.17 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.57.3 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.299.1 + github.com/aws/aws-sdk-go-v2/service/ecr v1.57.2 + github.com/aws/aws-sdk-go-v2/service/ecs v1.79.1 + github.com/aws/aws-sdk-go-v2/service/eks v1.83.0 + github.com/aws/aws-sdk-go-v2/service/elasticache v1.52.2 + github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.54.12 + github.com/aws/aws-sdk-go-v2/service/emr v1.59.2 + github.com/aws/aws-sdk-go-v2/service/eventbridge v1.45.25 + github.com/aws/aws-sdk-go-v2/service/fms v1.44.24 + github.com/aws/aws-sdk-go-v2/service/gamelift v1.54.0 + github.com/aws/aws-sdk-go-v2/service/glue v1.140.1 + github.com/aws/aws-sdk-go-v2/service/guardduty v1.75.3 + github.com/aws/aws-sdk-go-v2/service/health v1.37.6 + github.com/aws/aws-sdk-go-v2/service/iam v1.53.9 + github.com/aws/aws-sdk-go-v2/service/inspector2 v1.47.6 + github.com/aws/aws-sdk-go-v2/service/kinesis v1.43.7 + github.com/aws/aws-sdk-go-v2/service/kms v1.51.1 + github.com/aws/aws-sdk-go-v2/service/lambda v1.90.1 + github.com/aws/aws-sdk-go-v2/service/licensemanager v1.37.12 + github.com/aws/aws-sdk-go-v2/service/macie2 v1.51.2 + github.com/aws/aws-sdk-go-v2/service/networkfirewall v1.60.1 + github.com/aws/aws-sdk-go-v2/service/opensearch v1.67.1 + github.com/aws/aws-sdk-go-v2/service/organizations v1.51.3 + github.com/aws/aws-sdk-go-v2/service/rds v1.118.2 + github.com/aws/aws-sdk-go-v2/service/redshift v1.62.7 + github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.31.12 + github.com/aws/aws-sdk-go-v2/service/route53 v1.62.7 + github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1 + github.com/aws/aws-sdk-go-v2/service/s3vectors v1.6.8 + github.com/aws/aws-sdk-go-v2/service/sagemaker v1.244.0 + github.com/aws/aws-sdk-go-v2/service/savingsplans v1.32.4 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.7 + github.com/aws/aws-sdk-go-v2/service/securityhub v1.69.2 + github.com/aws/aws-sdk-go-v2/service/servicequotas v1.34.7 + github.com/aws/aws-sdk-go-v2/service/sfn v1.40.12 + github.com/aws/aws-sdk-go-v2/service/sns v1.39.17 + github.com/aws/aws-sdk-go-v2/service/sqs v1.42.27 + github.com/aws/aws-sdk-go-v2/service/ssm v1.68.6 + github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 + github.com/aws/aws-sdk-go-v2/service/transcribe v1.54.6 + github.com/aws/aws-sdk-go-v2/service/transfer v1.72.0 + github.com/aws/aws-sdk-go-v2/service/trustedadvisor v1.14.6 + github.com/aws/aws-sdk-go-v2/service/wafv2 v1.71.5 + github.com/aws/aws-sdk-go-v2/service/xray v1.36.23 + github.com/aws/smithy-go v1.25.1 + github.com/charmbracelet/x/ansi v0.11.7 github.com/creack/pty v1.1.24 github.com/google/uuid v1.6.0 - github.com/mattn/go-runewidth v0.0.19 - golang.org/x/sync v0.19.0 - golang.org/x/term v0.38.0 - gopkg.in/ini.v1 v1.67.0 + github.com/mattn/go-runewidth v0.0.23 + golang.org/x/sync v0.20.0 + golang.org/x/term v0.42.0 + gopkg.in/ini.v1 v1.67.1 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.19.5 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.16 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect - github.com/charmbracelet/colorprofile v0.4.1 // indirect - github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.16 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.23 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 // indirect + github.com/charmbracelet/colorprofile v0.4.3 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20260428153724-66037269d7be // indirect github.com/charmbracelet/x/term v0.2.2 // indirect github.com/charmbracelet/x/termios v0.1.1 // indirect github.com/charmbracelet/x/windows v0.2.2 // indirect - github.com/clipperhouse/displaywidth v0.6.1 // indirect - github.com/clipperhouse/stringish v0.1.1 // indirect - github.com/clipperhouse/uax29/v2 v2.3.0 // indirect - github.com/lucasb-eyer/go-colorful v1.3.0 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect + github.com/lucasb-eyer/go-colorful v1.4.0 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/stretchr/testify v1.11.1 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect - golang.org/x/sys v0.39.0 // indirect + golang.org/x/sys v0.43.0 // indirect ) diff --git a/go.sum b/go.sum index d38d8bc3..d75a79b7 100644 --- a/go.sum +++ b/go.sum @@ -1,203 +1,201 @@ -charm.land/bubbles/v2 v2.0.0-rc.1 h1:EiIFVAc3Zi/yY86td+79mPhHR7AqZ1OxF+6ztpOCRaM= -charm.land/bubbles/v2 v2.0.0-rc.1/go.mod h1:5AbN6cEd/47gkEf8TgiQ2O3RZ5QxMS14l9W+7F9fPC4= -charm.land/bubbletea/v2 v2.0.0-rc.2 h1:TdTbUOFzbufDJmSz/3gomL6q+fR6HwfY+P13hXQzD7k= -charm.land/bubbletea/v2 v2.0.0-rc.2/go.mod h1:IXFmnCnMLTWw/KQ9rEatSYqbAPAYi8kA3Yqwa1SFnLk= -charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106192539-4b304240aab7 h1:059k1h5vvZ4ASinki9nmBguxu9Rq0UDDSa6q8LOUphk= -charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106192539-4b304240aab7/go.mod h1:1qZyvvVCenJO2M1ac2mX0yyiIZJoZmDM4DG4s0udJkU= +charm.land/bubbles/v2 v2.1.0 h1:YSnNh5cPYlYjPxRrzs5VEn3vwhtEn3jVGRBT3M7/I0g= +charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY= +charm.land/bubbletea/v2 v2.0.6 h1:UHN/91OyuhaOFGSrBXQ/hMZD8IO1Uc4BvHlgHXL2WJo= +charm.land/bubbletea/v2 v2.0.6/go.mod h1:MH/D8ZLlN3op37vQvijKuU29g3rqTp+aQapURFonF9g= +charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU= +charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU= -github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4= -github.com/aws/aws-sdk-go-v2/config v1.32.5 h1:pz3duhAfUgnxbtVhIK39PGF/AHYyrzGEyRD9Og0QrE8= -github.com/aws/aws-sdk-go-v2/config v1.32.5/go.mod h1:xmDjzSUs/d0BB7ClzYPAZMmgQdrodNjPPhd6bGASwoE= -github.com/aws/aws-sdk-go-v2/credentials v1.19.5 h1:xMo63RlqP3ZZydpJDMBsH9uJ10hgHYfQFIk1cHDXrR4= -github.com/aws/aws-sdk-go-v2/credentials v1.19.5/go.mod h1:hhbH6oRcou+LpXfA/0vPElh/e0M3aFeOblE1sssAAEk= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16 h1:CjMzUs78RDDv4ROu3JnJn/Ig1r6ZD7/T2DXLLRpejic= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.16/go.mod h1:uVW4OLBqbJXSHJYA9svT9BluSvvwbzLQ2Crf6UPzR3c= -github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.45.7 h1:Wk+iUYnUOd4SQiRrYW6pN6//pXlzKq58oxY7bgCbbME= -github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.45.7/go.mod h1:GXWkNLt5Pwh0vlSnzoPsI/95tbJuSc2vKbyKqFUZ9pA= -github.com/aws/aws-sdk-go-v2/service/acm v1.37.18 h1:3rTIYf8RlwM3XjF6pLi08IEXKTOXumInlWQX73tcVsU= -github.com/aws/aws-sdk-go-v2/service/acm v1.37.18/go.mod h1:GzbPzpSxdxuZW3cs+3XKt8B46/mbktp2y69dfQWYJXo= -github.com/aws/aws-sdk-go-v2/service/apigateway v1.38.3 h1:nnhGwOSJAnWSwcOINuRUql8/C/l0pCGedsNgv6FSZHs= -github.com/aws/aws-sdk-go-v2/service/apigateway v1.38.3/go.mod h1:U3xTNpFRAV7yduECTfDBDJVFmY5FLrL5HsTSigwOeHs= -github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.33.4 h1:FcarAOOdK+8gIYD8/90x7JTOAno+U6IrzMdowePmyBA= -github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.33.4/go.mod h1:pCcxm44Iqac20ss6LXtMfg9eAqrP0HHmovnX5PZuHcE= -github.com/aws/aws-sdk-go-v2/service/apprunner v1.39.9 h1:3MgcobMoBK3IqP2TbuySbdjc79EYCmN+ZRCKQD6d0GU= -github.com/aws/aws-sdk-go-v2/service/apprunner v1.39.9/go.mod h1:n6b+O7QJ6E37dXZYPdLnC4S7Cc5HUYOQPZijLeDKIGY= -github.com/aws/aws-sdk-go-v2/service/appsync v1.53.0 h1:8I7CLKciARX91L7cKj1horWon1/Z1eGG9E1ZvjW7HwA= -github.com/aws/aws-sdk-go-v2/service/appsync v1.53.0/go.mod h1:iLJM8Rf5z8Td/Zvz5qv/XxP4h4E9aoBNXDUVoryy110= -github.com/aws/aws-sdk-go-v2/service/athena v1.56.4 h1:kCHAYlcGKCKKDGMRUfnY6pI992vC3UdA3+oPdfDBSHE= -github.com/aws/aws-sdk-go-v2/service/athena v1.56.4/go.mod h1:Whob/cKMykIKxGMQVhPhGEC1+D+bgEDOL7brjSB4ju8= -github.com/aws/aws-sdk-go-v2/service/autoscaling v1.62.4 h1:zCXye5ezlTkRlxDTwQ+ijc3BtYKrjCWu67Dmf3LGcEk= -github.com/aws/aws-sdk-go-v2/service/autoscaling v1.62.4/go.mod h1:CATFGdm+7wEDojXHd8AVSxbFRK+q6b0FL/6hqPtWZ5k= -github.com/aws/aws-sdk-go-v2/service/backup v1.54.5 h1:1ohWtO/jcqLqX1lh0sFcAKXCChhf7inCemQZMTqNfF0= -github.com/aws/aws-sdk-go-v2/service/backup v1.54.5/go.mod h1:mFaiE+PG/HYqwomFCUPLbqkQSwztsPZNIu30rBkRohc= -github.com/aws/aws-sdk-go-v2/service/batch v1.58.11 h1:A3s5XrpKnhe84eWf8FnwtbDFD81mtCAvTLDAJe67vOo= -github.com/aws/aws-sdk-go-v2/service/batch v1.58.11/go.mod h1:wcqihqx5FqtYtykgE5ZMCVgkLaBFrr/0JqOZp8xowaw= -github.com/aws/aws-sdk-go-v2/service/bedrock v1.53.0 h1:cmQBS5qaRe1yV7eL7shROYjBv/O3TJf9tJEDSiWndIA= -github.com/aws/aws-sdk-go-v2/service/bedrock v1.53.0/go.mod h1:LV2LELzMlToA6tauFUTYr0iy20Gp4TKz2vMQYaKq0Pw= -github.com/aws/aws-sdk-go-v2/service/bedrockagent v1.52.2 h1:jrOALh0fIx8kUfesQS4jMkXGPDQ2xKt5bbREgsoHcmw= -github.com/aws/aws-sdk-go-v2/service/bedrockagent v1.52.2/go.mod h1:hRzcNxU8BOG5ijgeMDLyw0sx4fBOxrjPDB/DnDK6X1M= -github.com/aws/aws-sdk-go-v2/service/bedrockagentcorecontrol v1.15.1 h1:BJmfQWd/3kjWCw3zkS3lSZ9uVwo9jsDGfW8g4EG2xbY= -github.com/aws/aws-sdk-go-v2/service/bedrockagentcorecontrol v1.15.1/go.mod h1:3zWDBnJEUh72XdC7iEqdCSwPwDuveVsKTmtThuGwC2s= -github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.47.1 h1:xryaVPvLLcCf7Y/4beWjOcWxiftorB/KDjtiYORVSNo= -github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.47.1/go.mod h1:ckSglleOJ2avj81L6vBb70nK51cnhTwvVK1SkLgFtj4= -github.com/aws/aws-sdk-go-v2/service/budgets v1.42.3 h1:SWmlAqhAeh9ByGn56CLqJEEFwd1tsDM1t9ojTcxpnvo= -github.com/aws/aws-sdk-go-v2/service/budgets v1.42.3/go.mod h1:MBllv8Mjt8gp2rBU+iA5L6QabvS5L00LSru/ICHld7M= -github.com/aws/aws-sdk-go-v2/service/cloudformation v1.71.4 h1:9dwMueqbHIp0KTw2Zt0rhVobiPMlAI8UgyxiaBzM+1E= -github.com/aws/aws-sdk-go-v2/service/cloudformation v1.71.4/go.mod h1:R4SVh77rxRZut8uzbNhnXcwA5m99OT4hqhHkZjh5NAk= -github.com/aws/aws-sdk-go-v2/service/cloudfront v1.58.3 h1:/nyo0QD97D5VQQL/UE+rKGNKz+BesiqJgjdmp0qtTOQ= -github.com/aws/aws-sdk-go-v2/service/cloudfront v1.58.3/go.mod h1:Jp0zmzn87l3dKarpDT/qbHNyISst5OnmzMACKuiyMvY= -github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.55.4 h1:paDKcKBWPFh/uaTEMPMXyVj5Qsz2dlHaJCi+6yg1C84= -github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.55.4/go.mod h1:06x0N2mdQ+l0uv/fjo8p96812Ex8sxq24LmC8JPajmg= -github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.53.0 h1:XY6wKzfriEF+V8bFYFi1S3i8ly+Zetq/RuPyaGdMMzE= -github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.53.0/go.mod h1:zUms+kt0awoSYh/MwI9d3AV5xMHIDRf7I736b1Drw/k= -github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.62.2 h1:U7ATBzpyD+A3IxzwKUL+meioIs3HO+/eyxghGTy6bkY= -github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.62.2/go.mod h1:ESQxVIp7hs1MdsdEF4KITf65SfM3fh/EEiYi+s0S/pE= -github.com/aws/aws-sdk-go-v2/service/codebuild v1.68.8 h1:uzot4kkdHaFpj1cjsHilL6B4wjC47pKoGzRBu6Ru/vo= -github.com/aws/aws-sdk-go-v2/service/codebuild v1.68.8/go.mod h1:br0rKgL6SJI6tuipFqqCTwbi8YgQ0zTYi1HHAq0uaBQ= -github.com/aws/aws-sdk-go-v2/service/codepipeline v1.46.16 h1:d3xDjD1paX0rHG+CVdspZN/LGoznmLLphz2HSsStZIk= -github.com/aws/aws-sdk-go-v2/service/codepipeline v1.46.16/go.mod h1:p461ewWfgWNHSHnpSphvvUYAVjq/XaL+2DsXJjza2F4= -github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.57.17 h1:kYAxFlyBhmhdjel6MNFf5lYQlTcMUOXPC33mor8rFz0= -github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.57.17/go.mod h1:NSRHRisUPKx5y8RD+HpeCjIn8SYz5m6HhNGkd0GLB1o= -github.com/aws/aws-sdk-go-v2/service/computeoptimizer v1.49.3 h1:FF2Z+l7R0L1XamxF1UfLYZ/B+gI+L/bKQWA6k+L94T0= -github.com/aws/aws-sdk-go-v2/service/computeoptimizer v1.49.3/go.mod h1:ihDczy1E6oCNDolEL8peazafiSnqf2Usw7X9jn40m9w= -github.com/aws/aws-sdk-go-v2/service/configservice v1.59.9 h1:mfrlCO6GCwSiVV+riXWQnfQxJMXeTe9xZ4k0HCDYFZ4= -github.com/aws/aws-sdk-go-v2/service/configservice v1.59.9/go.mod h1:nkku7pEfQLBI9XGX0fTdDylOiXF8T54Wrff6CHBMeXY= -github.com/aws/aws-sdk-go-v2/service/costexplorer v1.62.0 h1:YD2xJ3wFL8svkw7cEpt/1rUq1NeMnz+TRXgMooMFoqo= -github.com/aws/aws-sdk-go-v2/service/costexplorer v1.62.0/go.mod h1:SCRS6FhD8HFqq9ISjLdNO4X6uCZ/ESRL2JlIKSI75RQ= -github.com/aws/aws-sdk-go-v2/service/datasync v1.57.0 h1:c86IDU9xeMkzzgGICKh6UIgVjCDEMjh3RSB6ET5bzwA= -github.com/aws/aws-sdk-go-v2/service/datasync v1.57.0/go.mod h1:1edw09z6gZp6OY1O5hyS6FNa5elwegmnNlsULbt2Ixw= -github.com/aws/aws-sdk-go-v2/service/detective v1.38.8 h1:aV2RW2nJTNDHAYZMMEk1mBGGzCw76YjsElxaCOz/+Q0= -github.com/aws/aws-sdk-go-v2/service/detective v1.38.8/go.mod h1:wNn3bdVqMNImj4GyhdRpSpH005AY/5whiODMTh4Eamo= -github.com/aws/aws-sdk-go-v2/service/directconnect v1.38.10 h1:Fm5d5e7Iy73zmS0su/bSyinfwyNqlIOQmxCD4N0HKEQ= -github.com/aws/aws-sdk-go-v2/service/directconnect v1.38.10/go.mod h1:y3QZUun1UX9K2bPjXe4im5jc2Jwy2TI56DXLprrH6IU= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.5 h1:mSBrQCXMjEvLHsYyJVbN8QQlcITXwHEuu+8mX9e2bSo= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.5/go.mod h1:eEuD0vTf9mIzsSjGBFWIaNQwtH5/mzViJOVQfnMY5DE= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.276.1 h1:P7db/Z55pXvwnueLuHUuVlxnqjbAtiadm01+QIC42OA= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.276.1/go.mod h1:Wg68QRgy2gEGGdmTPU/UbVpdv8sM14bUZmF64KFwAsY= -github.com/aws/aws-sdk-go-v2/service/ecr v1.54.4 h1:4THfkydiKvFeOFlfY1ABHe4Nsj+Jy6S6tHBqUAojY0M= -github.com/aws/aws-sdk-go-v2/service/ecr v1.54.4/go.mod h1:8n8vVvu7LzveA0or4iWQwNndJStpKOX4HiVHM5jax2U= -github.com/aws/aws-sdk-go-v2/service/ecs v1.69.5 h1:5nkhwt0d/gjuT3AQ2LUK0aFRNB3MGlzB2elqy/ZsKP4= -github.com/aws/aws-sdk-go-v2/service/ecs v1.69.5/go.mod h1:LQMlcWBoiFVD3vUVEz42ST0yTiaDujv2dRE6sXt1yPE= -github.com/aws/aws-sdk-go-v2/service/eks v1.76.3 h1:840uwcJTIwrMPLuEUQVFKZbPgwnYzc5WDyXMiMYm5Ts= -github.com/aws/aws-sdk-go-v2/service/eks v1.76.3/go.mod h1:7IU8o/Snul26xioEWN5tgoOas1ISPGsiq5gME5rPh3o= -github.com/aws/aws-sdk-go-v2/service/elasticache v1.51.8 h1:LiAvvvkFFhvL0AKbsDwEFLC6w4jLOd6r/eNk/b7ZvL4= -github.com/aws/aws-sdk-go-v2/service/elasticache v1.51.8/go.mod h1:QMDpBJOUoPTE4u4IJjbbmrY9ky+yFe6rU1FdKQtvc30= -github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.54.5 h1:JjKuK9zbAVv6X44ia/OZrRS8ngOx3QfvtQTN0poJdPw= -github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.54.5/go.mod h1:qZnMTI+Q9S/C2dNbIMhIH8XMMR3UpO1dgpM4FnH8ZOY= -github.com/aws/aws-sdk-go-v2/service/emr v1.57.4 h1:6gpOrv5HebiRILDlq6quIr4UtmhyxdE0v+tVKdpu0wo= -github.com/aws/aws-sdk-go-v2/service/emr v1.57.4/go.mod h1:qHrbyloGbgvGIYYWn51aHx7HK9gVQKHTWZPLmhlfgtQ= -github.com/aws/aws-sdk-go-v2/service/eventbridge v1.45.17 h1:ltbEzdlO5qKYK1FuwTt2LibddWFmH/QY6usxvPOQP08= -github.com/aws/aws-sdk-go-v2/service/eventbridge v1.45.17/go.mod h1:KXFNdzl+mZpQlLYm378Ml18wBHybbMpyBwNXuYjbDT4= -github.com/aws/aws-sdk-go-v2/service/fms v1.44.16 h1:IoO9da/CYmn+WlJdEimFLj+n1Cv5vKQSd9gwZlNY1PY= -github.com/aws/aws-sdk-go-v2/service/fms v1.44.16/go.mod h1:ps2AgucjzvCIdeuAOoXBRZUeVAqWgJ1+fGChfWRq3FM= -github.com/aws/aws-sdk-go-v2/service/gamelift v1.50.0 h1:knUB4jZTiIYcMQpdK4J6nk6zNQbHyTqEZL3KKaPavZs= -github.com/aws/aws-sdk-go-v2/service/gamelift v1.50.0/go.mod h1:JPSMCIr4USXQl0z5PXj7m9JFbb74k+U1L/QHzovpMMY= -github.com/aws/aws-sdk-go-v2/service/glue v1.135.3 h1:Y3AJG3faZeMLkERgg+vdqhLDtBIx+8uc14BvWlxFcCY= -github.com/aws/aws-sdk-go-v2/service/glue v1.135.3/go.mod h1:t3GxMA7CEzEXN6zmI6Br0gSLy+9x4ndsXTk1prQuP7s= -github.com/aws/aws-sdk-go-v2/service/guardduty v1.70.1 h1:i6rDonvayDvW/AGQV3AjcQAZeC/oKclwhh2ozGNRRj8= -github.com/aws/aws-sdk-go-v2/service/guardduty v1.70.1/go.mod h1:JYjdl7T2irE+UVsbalQMvdS9Ecx4gc3o93w5/wSHIKo= -github.com/aws/aws-sdk-go-v2/service/health v1.35.5 h1:Sw/o9AmsW7HmEQokFUQU/Y9Z+GcFSe4Eh9l2n/aFVtA= -github.com/aws/aws-sdk-go-v2/service/health v1.35.5/go.mod h1:AObcHeSFMWL3yRqt1rtaxN8t2DbuxO/aQYy3XxO8mKo= -github.com/aws/aws-sdk-go-v2/service/iam v1.53.1 h1:xNCUk9XN6Pa9PyzbEfzgRpvEIVlqtth402yjaWvNMu4= -github.com/aws/aws-sdk-go-v2/service/iam v1.53.1/go.mod h1:GNQZL4JRSGH6L0/SNGOtffaB1vmlToYp3KtcUIB0NhI= -github.com/aws/aws-sdk-go-v2/service/inspector2 v1.46.1 h1:LFa1WYHZQ5+mC3r33QWxEO0z3V580ktQhVesRmIkX14= -github.com/aws/aws-sdk-go-v2/service/inspector2 v1.46.1/go.mod h1:/7lMsX5Krrhhfcs3gzjSthmQSJOaM92iHzZ2PI4lA3k= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7 h1:DIBqIrJ7hv+e4CmIk2z3pyKT+3B6qVMgRsawHiR3qso= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.7/go.mod h1:vLm00xmBke75UmpNvOcZQ/Q30ZFjbczeLFqGx5urmGo= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.16 h1:8g4OLy3zfNzLV20wXmZgx+QumI9WhWHnd4GCdvETxs4= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.16/go.mod h1:5a78jwLMs7BaesU0UIhLfVy2ZmOEgOy6ewYQXKTD37Q= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 h1:NSbvS17MlI2lurYgXnCOLvCFX38sBW4eiVER7+kkgsU= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A= -github.com/aws/aws-sdk-go-v2/service/kinesis v1.42.9 h1:9Dme/lCNr7GT+n3+AsJV95g5akEhSYeJKoQOcrL8xZ4= -github.com/aws/aws-sdk-go-v2/service/kinesis v1.42.9/go.mod h1:77+d3nX1hnx0CMC+FG3N34e86SOaEKGpSP+8bQYkX90= -github.com/aws/aws-sdk-go-v2/service/kms v1.49.4 h1:2gom8MohxN0SnhHZBYAC4S8jHG+ENEnXjyJ5xKe3vLc= -github.com/aws/aws-sdk-go-v2/service/kms v1.49.4/go.mod h1:HO31s0qt0lso/ADvZQyzKs8js/ku0fMHsfyXW8OPVYc= -github.com/aws/aws-sdk-go-v2/service/lambda v1.87.0 h1:E5UXxF3vK3JuViwKCHfTJBIiFjvE4aytSucZjI2UAlQ= -github.com/aws/aws-sdk-go-v2/service/lambda v1.87.0/go.mod h1:6f64Y1BEf6e1uCI+LtGbcZSKDK1GvgJ+iI4vP/bbE8s= -github.com/aws/aws-sdk-go-v2/service/licensemanager v1.37.4 h1:9wWpaVEAfS6oSblVpTcpYbOY1t13K0OaSw5wNfDTPZM= -github.com/aws/aws-sdk-go-v2/service/licensemanager v1.37.4/go.mod h1:Zrc5dFCvWTGlQA8hlhldaQ5llKwSONV46rUUXyl/KOA= -github.com/aws/aws-sdk-go-v2/service/macie2 v1.50.8 h1:wBz04NRh0P+QdXEDUg9ZxPg7rnMAJwx8FPuDlsywK8g= -github.com/aws/aws-sdk-go-v2/service/macie2 v1.50.8/go.mod h1:V01kM0gQi/X7cAgQq8oYxJZK5SI0ix1X30dsEPdnkG0= -github.com/aws/aws-sdk-go-v2/service/networkfirewall v1.59.2 h1:UQqzswR55GJGyliE/cnDHSWvAi5medG2PN5zdQKZWwY= -github.com/aws/aws-sdk-go-v2/service/networkfirewall v1.59.2/go.mod h1:eQQOkwMgV4/kW5q8A8M0JitBIppaWHEY2ST9tSZlLng= -github.com/aws/aws-sdk-go-v2/service/opensearch v1.56.0 h1:5fEX9xgasulnog9z/s2tfA1HzxoOcTzjFNZxbRvURYw= -github.com/aws/aws-sdk-go-v2/service/opensearch v1.56.0/go.mod h1:0VgDf/vMiSyGBTP1OrqqdWLpbAJQd9wKfFpLtWffrFQ= -github.com/aws/aws-sdk-go-v2/service/organizations v1.50.0 h1:HGC9bFaqjHWWD8cnNYVbQIrkzZwRJs2UxqdrGnaeSvE= -github.com/aws/aws-sdk-go-v2/service/organizations v1.50.0/go.mod h1:tTgixGOX/GSKJg6/ktn/dc49IYJDxeV+LNxiYE33riU= -github.com/aws/aws-sdk-go-v2/service/rds v1.113.1 h1:/vV0g/Su8rCTqT57UUYiFU/aRrPXz//fGDn1dkXblG4= -github.com/aws/aws-sdk-go-v2/service/rds v1.113.1/go.mod h1:q02df+DL73LN+jDXzj86tMsI6kKf1kfv61nB684H+o8= -github.com/aws/aws-sdk-go-v2/service/redshift v1.61.4 h1:nufUF8qOf5sSKOBJsTu5sYJnA+sgKGA6712pdIpCSoA= -github.com/aws/aws-sdk-go-v2/service/redshift v1.61.4/go.mod h1:QYBdUiwwcvJ6/RomRedCV4hEKkvI1GtJ35d9Qv2r2Zs= -github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.31.5 h1:0jwTqyyPsbn4UysC6ltj/AuntNBWBeU++kNJQtShtg0= -github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.31.5/go.mod h1:ydy76wx7I+HsqhlEo0vhVTl785TDNbpgtEXhd3i4ZTc= -github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0 h1:80pDB3Tpmb2RCSZORrK9/3iQxsd+w6vSzVqpT1FGiwE= -github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0/go.mod h1:6EZUGGNLPLh5Unt30uEoA+KQcByERfXIkax9qrc80nA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.93.2 h1:U3ygWUhCpiSPYSHOrRhb3gOl9T5Y3kB8k5Vjs//57bE= -github.com/aws/aws-sdk-go-v2/service/s3 v1.93.2/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8= -github.com/aws/aws-sdk-go-v2/service/s3vectors v1.6.1 h1:rDzOqlygRph8qCsnVfvCxi6HPJx4Ui7XDZmlQY0MXYs= -github.com/aws/aws-sdk-go-v2/service/s3vectors v1.6.1/go.mod h1:oyW5/VgQ6XPfMYtu6cSwfAEnhaFQdzJ67L5MwEfFoiw= -github.com/aws/aws-sdk-go-v2/service/sagemaker v1.228.2 h1:96uJoMTjZ6WdXD0+bCjQib+U42++cYrf4fXbiu7VpEY= -github.com/aws/aws-sdk-go-v2/service/sagemaker v1.228.2/go.mod h1:6TLogKvr0gKvi3GDJd6rZQ9uVl/fkXgCkWUuVD4EdLI= -github.com/aws/aws-sdk-go-v2/service/savingsplans v1.31.1 h1:Zqz+yK0iuS84I6cQExTXewD2/XjH/m+RsCYbhQukbp0= -github.com/aws/aws-sdk-go-v2/service/savingsplans v1.31.1/go.mod h1:A/FYlteWmWYAAUgFEPEd+zMhZPeusOpFyBxxlUesmuU= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.0 h1:vL6rQXcGtFv9q/9eRPdI+lL+dvTm7xKGZYSHEvmrpDk= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.0/go.mod h1:QwEDLD+7EukuEUnbWtiNE8LhgvvmhjZoi4XAppYPtyc= -github.com/aws/aws-sdk-go-v2/service/securityhub v1.67.2 h1:mFwn+Z/A7cs8lgawN2ASJ/u60Ay4fPYg0lGL1GgpnT0= -github.com/aws/aws-sdk-go-v2/service/securityhub v1.67.2/go.mod h1:+1I3OMggwxrBeWT1LTtwS7DKtUizbLL3dozMaR33KV0= -github.com/aws/aws-sdk-go-v2/service/servicequotas v1.33.12 h1:7/Bys3vN+LgCtSMSETBRNRTuVkIC2WTEtu9MZyQ2zwc= -github.com/aws/aws-sdk-go-v2/service/servicequotas v1.33.12/go.mod h1:zfrr8eV7yr3nakr+K+22q+wA3t5ApjqTiNSCbEzK7fM= -github.com/aws/aws-sdk-go-v2/service/sfn v1.40.5 h1:nhPlRp9oCZOh1M/4zVn4pqguzEJ3Q3emnyS9k8sW8u8= -github.com/aws/aws-sdk-go-v2/service/sfn v1.40.5/go.mod h1:dfVRuB5XudlLMY6PVMu4T2lmfXYMARapmdc2/cUN2Mw= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU= -github.com/aws/aws-sdk-go-v2/service/sns v1.39.10 h1:wqErrLzV3iERQ7dbZbKQS0gOM6ngxZtmPwKyRGn+Krc= -github.com/aws/aws-sdk-go-v2/service/sns v1.39.10/go.mod h1:OiwBtRz6QlQyt69WLBMvSiyfgI7cOd6xSJ9ThTMjI5M= -github.com/aws/aws-sdk-go-v2/service/sqs v1.42.20 h1:qa+1W+Kon3WDwO+8ugco4D9KvO0Pf0KBTn1hN7opIFw= -github.com/aws/aws-sdk-go-v2/service/sqs v1.42.20/go.mod h1:OG0Y3TgC+IeM++ngh+IcEkN24ruGsmRiAP8GUsOhMW8= -github.com/aws/aws-sdk-go-v2/service/ssm v1.67.7 h1:0q42w8/mywPCzQD1IoWIBUCYfBJc5+fLwtZNpHffBSM= -github.com/aws/aws-sdk-go-v2/service/ssm v1.67.7/go.mod h1:urlU9nfKJEfi0+8T9luB3f3Y0UnomH/yxI7tTrfH9es= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 h1:eYnlt6QxnFINKzwxP5/Ucs1vkG7VT3Iezmvfgc2waUw= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.7/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk= -github.com/aws/aws-sdk-go-v2/service/transcribe v1.53.10 h1:in3CSrCjUYvz3z6I/flh2328lYRcochyHgWwi9ocRHw= -github.com/aws/aws-sdk-go-v2/service/transcribe v1.53.10/go.mod h1:ELpS1JjDBqdWL3vGuJJThEUZM7jxD+tzw9cHaCm7DEs= -github.com/aws/aws-sdk-go-v2/service/transfer v1.68.4 h1:btvrYbX0GK9orCMyQYZWMcTGnr/6vyTgrRoxlxn0zoo= -github.com/aws/aws-sdk-go-v2/service/transfer v1.68.4/go.mod h1:ipUzOV/iivYDtfMRTYXeat4B8+lKX1X9MMLnTGcprpQ= -github.com/aws/aws-sdk-go-v2/service/trustedadvisor v1.13.17 h1:JmmxkbTdh4T/YVBCDsjAmIqiFgZaN0J1diHq7/fCnk4= -github.com/aws/aws-sdk-go-v2/service/trustedadvisor v1.13.17/go.mod h1:LoA+TP4mpM7Szx9mjMSevYMroSZGXIbmtjqI4sBcA1w= -github.com/aws/aws-sdk-go-v2/service/wafv2 v1.70.4 h1:nzu+shQb7bVbXFWEnFB/R2LuiM4p8QuyN3P9vS/KJBw= -github.com/aws/aws-sdk-go-v2/service/wafv2 v1.70.4/go.mod h1:UU4OZ1UXQ8O2vx6dj6czjDKv+8WbmtVYBFoFS+4buQ8= -github.com/aws/aws-sdk-go-v2/service/xray v1.36.16 h1:QmiDhZi76gIQXhZttJvkrJQBEiMQtnvD1SykHVWRD7A= -github.com/aws/aws-sdk-go-v2/service/xray v1.36.16/go.mod h1:KOlafD/fk22WyDqDQIhCav1UFffNk1KcUyUNXqEMYBw= -github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= -github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= -github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= -github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= -github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk= -github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk= -github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38 h1:7Rs87fbKJoIIxsQS8YKJYGYa0tlsDwwb0twQjV1KB+g= -github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38/go.mod h1:6lfcr3MNP+kZR25sF1nQwJFuQnNYBlFy3PGX5rvslXc= -github.com/charmbracelet/x/ansi v0.11.3 h1:6DcVaqWI82BBVM/atTyq6yBoRLZFBsnoDoX9GCu2YOI= -github.com/charmbracelet/x/ansi v0.11.3/go.mod h1:yI7Zslym9tCJcedxz5+WBq+eUGMJT0bM06Fqy1/Y4dI= +github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8= +github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 h1:gx1AwW1Iyk9Z9dD9F4akX5gnN3QZwUB20GGKH/I+Rho= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10/go.mod h1:qqY157uZoqm5OXq/amuaBJyC9hgBCBQnsaWnPe905GY= +github.com/aws/aws-sdk-go-v2/config v1.32.17 h1:FpL4/758/diKwqbytU0prpuiu60fgXKUWCpDJtApclU= +github.com/aws/aws-sdk-go-v2/config v1.32.17/go.mod h1:OXqUMzgXytfoF9JaKkhrOYsyh72t9G+MJH8mMRaexOE= +github.com/aws/aws-sdk-go-v2/credentials v1.19.16 h1:r3RJBuU7X9ibt8RHbMjWE6y60QbKBiII6wSrXnapxSU= +github.com/aws/aws-sdk-go-v2/credentials v1.19.16/go.mod h1:6cx7zqDENJDbBIIWX6P8s0h6hqHC8Avbjh9Dseo27ug= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 h1:UuSfcORqNSz/ey3VPRS8TcVH2Ikf0/sC+Hdj400QI6U= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23/go.mod h1:+G/OSGiOFnSOkYloKj/9M35s74LgVAdJBSD5lsFfqKg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE= +github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.47.2 h1:YGLFRzpQ/+XEJdnaB219COjJr/XsIjV/+AylgQK2dNM= +github.com/aws/aws-sdk-go-v2/service/accessanalyzer v1.47.2/go.mod h1:CP5pWLCGRZJDXLkeUvxTulAvFkPnfK+TqJNtJtH/Jmo= +github.com/aws/aws-sdk-go-v2/service/acm v1.38.3 h1:Fzab84hCu3rw9R9Y3mH7SHfr/cSEHnCB0Mq1JCdr9t0= +github.com/aws/aws-sdk-go-v2/service/acm v1.38.3/go.mod h1:yCteizCNPaHt0SnNusoGGHvy0JDB0tvGDTVhEt5anZM= +github.com/aws/aws-sdk-go-v2/service/apigateway v1.39.3 h1:7MJlB7KGFd+KNKtnPgoFWYf52PGO1pd+1VHp10lNKhI= +github.com/aws/aws-sdk-go-v2/service/apigateway v1.39.3/go.mod h1:MwilTAruv11x8EFjsk1R0VfjMdCxB6JHVtanCqsTR5o= +github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.34.3 h1:JqFmDekar5Ije9SKKBUAmvuqardAXgCoJV78dt2BQSI= +github.com/aws/aws-sdk-go-v2/service/apigatewayv2 v1.34.3/go.mod h1:+ONaMzn4bVIQwVsamP28xDWWZuYO+6cWupJpZOSlUNE= +github.com/aws/aws-sdk-go-v2/service/apprunner v1.39.16 h1:fqfppotMvz3HCWi7vdL84uEk29lYeMqNwLH025r1k6A= +github.com/aws/aws-sdk-go-v2/service/apprunner v1.39.16/go.mod h1:QA5CUOg5mkS73RhLYCDPeFuCFxoeW2TPA6XRo4JL2Ek= +github.com/aws/aws-sdk-go-v2/service/appsync v1.53.7 h1:SjkXYNc15FoovS9k6ML7dO7pkXE3ns66RXwL+rfQVl8= +github.com/aws/aws-sdk-go-v2/service/appsync v1.53.7/go.mod h1:RcoBov7Pw38ViMxlthKRr1GLgHPPseA+kY2bupWVm10= +github.com/aws/aws-sdk-go-v2/service/athena v1.57.6 h1:QPDxHlfHmozXUj9rmUyxQiOLVEJL3qX4x56CrwAtytM= +github.com/aws/aws-sdk-go-v2/service/athena v1.57.6/go.mod h1:3QLTFWWkK+tGp4LD0ClX5Ib75MWE5H66vBat1N9f8Os= +github.com/aws/aws-sdk-go-v2/service/autoscaling v1.66.2 h1:pPd+/Ujqf2+DmPOdB47EN7ox1iC21lu2zlOccUlfHeo= +github.com/aws/aws-sdk-go-v2/service/autoscaling v1.66.2/go.mod h1:b3XHAIEe5I9cmeZ9MLvUqj5DRWcBuh1/hpKDPb7T6KE= +github.com/aws/aws-sdk-go-v2/service/backup v1.55.2 h1:WhdT1PwOjTjKLGuD9lJDyNMgYmVaZgTHK06ioJKMBYE= +github.com/aws/aws-sdk-go-v2/service/backup v1.55.2/go.mod h1:uMh3ZDoLKhXldYL1qcBYy1HsNQfKgL/w8iF2onOyoKY= +github.com/aws/aws-sdk-go-v2/service/batch v1.64.1 h1:+Nm5tPlRgOVQB/uMvphSNyJI9WKyIdE2FlTCMXEqqns= +github.com/aws/aws-sdk-go-v2/service/batch v1.64.1/go.mod h1:EHhfaNTxntvYHmSdHGLLSxuerqkMIlXY9lWjl+CJJxg= +github.com/aws/aws-sdk-go-v2/service/bedrock v1.59.2 h1:TdYgGWLuNqQGNNVHzMwBEkg1fsyoaOfd+aP3xKfIeDM= +github.com/aws/aws-sdk-go-v2/service/bedrock v1.59.2/go.mod h1:v/+a2bHvfo04yQC/p2HOK1iJF6V59OwCfCSf4iYgob4= +github.com/aws/aws-sdk-go-v2/service/bedrockagent v1.53.2 h1:5WfuuVf0Mie5g3ibps8fgYDrlTVMc7PQpdycZmI1R+k= +github.com/aws/aws-sdk-go-v2/service/bedrockagent v1.53.2/go.mod h1:zue4MN4ji6nlKYQYwVLmaPXJ66wB9JnIePX1e1yg5MU= +github.com/aws/aws-sdk-go-v2/service/bedrockagentcorecontrol v1.34.0 h1:CINQhADU+ikQ4A9dCJdM7L8IrYIcDFPHrm3yOzKLPI0= +github.com/aws/aws-sdk-go-v2/service/bedrockagentcorecontrol v1.34.0/go.mod h1:WXGqnCrWtpgiig7ymwkNHdGvWF7FJtGvx8ldK/yy5BQ= +github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.6 h1:Wbo1WlWyGaAXlr6C7OGXq9avbdJhIV9cQ4M6E34b5x8= +github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.6/go.mod h1:uY1fJe6m3I3w/m8UAkQ89Cm/ZAt/um6LW+AOZU33LDI= +github.com/aws/aws-sdk-go-v2/service/budgets v1.43.6 h1:2C1Fx5PAPB+8Se7nwnxAElSMW5KxuDVwzBu6t2qtlfg= +github.com/aws/aws-sdk-go-v2/service/budgets v1.43.6/go.mod h1:W/ACxHO2V65T11t9KLLG+pQytcYk206Ff5b5lbGeMF8= +github.com/aws/aws-sdk-go-v2/service/cloudformation v1.71.11 h1:gIRdzLv98ugE0nvMkub5yp4uziPFHF66ERrQ9JN+D54= +github.com/aws/aws-sdk-go-v2/service/cloudformation v1.71.11/go.mod h1:BMpnKVWK+343lUuI2ZM5bm282z+p61ZK9kwRg6/wBm4= +github.com/aws/aws-sdk-go-v2/service/cloudfront v1.62.0 h1:Vd4U87ecTyeQwOTezwqAYW9qcWdZpwicC96MlqXd67M= +github.com/aws/aws-sdk-go-v2/service/cloudfront v1.62.0/go.mod h1:brhMG/gR2xEB5lezxL2Cx+hqsEzGUn4LhNUtu7+ePFE= +github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.55.11 h1:3IDx7ybn7pyrLgVShEfGmEXec1xsqgoD1ADI1SxqKT0= +github.com/aws/aws-sdk-go-v2/service/cloudtrail v1.55.11/go.mod h1:iSArc5uhvz1S3EICNNvRzPksb6HPAUhltzMvoCrGyfM= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.56.3 h1:EP1ULqh0t8szWtLlQFd7pvIvfyuwX09ALLQkJ4AZ9KA= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.56.3/go.mod h1:7900IH3EvTrwNGLNx3QDKnQwPF/Cw+pD9cuvBDQ4org= +github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.71.1 h1:p0A8HO2B++3LfOTRxQScOPc3QhFWgyAXQQ6W92RT7Yk= +github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.71.1/go.mod h1:MLJu3PUd8fp5Qvj4CiLvyY5H8y7kxHKlTp060Wsd+Vc= +github.com/aws/aws-sdk-go-v2/service/codebuild v1.68.15 h1:ZrDV293SvcUFF/2QQ+oBeIPj/0vn7OC7q5CtuBQx6ow= +github.com/aws/aws-sdk-go-v2/service/codebuild v1.68.15/go.mod h1:9WntqQzPVMdtY8e9rM22XT1ykcrmxdr/l/A2w17+l+8= +github.com/aws/aws-sdk-go-v2/service/codepipeline v1.46.23 h1:T1CJQxFSFH/74qHb6h02V7oJBCcNlWoBZj+ZM1C/+0I= +github.com/aws/aws-sdk-go-v2/service/codepipeline v1.46.23/go.mod h1:YRBQdlmNr8T+a+cvU7p0s7kYNT9UrZ4RPfLSHthtMOc= +github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.60.2 h1:pZxE29WAHgnk0jGj1P4UOOJnNPHXfltkfnK4F1Tg8jU= +github.com/aws/aws-sdk-go-v2/service/cognitoidentityprovider v1.60.2/go.mod h1:nbe4Nf/HOY+e54Dl+yjv04scYTGTC+4ZthbfOuPTXQs= +github.com/aws/aws-sdk-go-v2/service/computeoptimizer v1.50.1 h1:FyZGfF3X2uzNSZjIxXgiW/5va1HcJKueMIXEbEhVa78= +github.com/aws/aws-sdk-go-v2/service/computeoptimizer v1.50.1/go.mod h1:ypZFNO3viD/a8tii9vlzKCOPxRuDMidRNIadJf7eJ1Y= +github.com/aws/aws-sdk-go-v2/service/configservice v1.62.3 h1:8rcAUMhCUuQe7kVBTBLPqMsJWFSVDPSFdpFexVQEEdU= +github.com/aws/aws-sdk-go-v2/service/configservice v1.62.3/go.mod h1:PvCpYWzh0/jwa8v3jYJOaDobKiKQzFm1hI7+4Ms3J90= +github.com/aws/aws-sdk-go-v2/service/costexplorer v1.63.8 h1:rUg81vwoGgCmnQxsVl+B65QF8Qh+zYCluhyAcxsk79o= +github.com/aws/aws-sdk-go-v2/service/costexplorer v1.63.8/go.mod h1:V+bKJ05kIC6K0LH8TB3D5boy7F8BhrZeecoXLz1NlB4= +github.com/aws/aws-sdk-go-v2/service/datasync v1.58.4 h1:8dunTSKwZYHGBpCKtiSLXPHZph5uRGnlaMY/f4UeQEA= +github.com/aws/aws-sdk-go-v2/service/datasync v1.58.4/go.mod h1:5H2TlnEkHntq2f9qVEZ9Fc0uO0vf+30KtECvmnms2m8= +github.com/aws/aws-sdk-go-v2/service/detective v1.38.15 h1:H5v+Sro9ZL5OQGOoxuZz8RTzvO23VkOJ1LjKqXbank8= +github.com/aws/aws-sdk-go-v2/service/detective v1.38.15/go.mod h1:/DN1zJ/OloIC8JwOVgOjbSDpl0S56cnu5Qnf9D/sXc8= +github.com/aws/aws-sdk-go-v2/service/directconnect v1.38.17 h1:fkeDjhbAy9ddanOVlxP2vnY2dbTxA8HL+DdV9HezVSs= +github.com/aws/aws-sdk-go-v2/service/directconnect v1.38.17/go.mod h1:kzj2OFWYl3uGXBkincAArVPtSG8QwXJRfCL8+Ztsw9o= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.57.3 h1:XgjzLEE8CrNYnr4Xmi1W5PfKsKMjp4Pu1rWkJNO43JI= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.57.3/go.mod h1:r7sfLXEN8RUA89tAHy1E7lCtVOOWIkqVy/FbnUdxW1E= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.299.1 h1:gQ9fSyFk3Y9Vm2fVbphBeJfXJlkJvEvC35TszBVjprg= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.299.1/go.mod h1:Y95W0Hm6FYLPa6o0hbnJ+sWgmdc4ifcLFjGkdobWVhY= +github.com/aws/aws-sdk-go-v2/service/ecr v1.57.2 h1:rHEW02JFJUV2/ttjzyPIvbD0YraqpyU2w6m6DfQUmdg= +github.com/aws/aws-sdk-go-v2/service/ecr v1.57.2/go.mod h1:gNS8pNht4VMzPd4UtQUL3NTUQbjEPLLmb9MqmqrqsCM= +github.com/aws/aws-sdk-go-v2/service/ecs v1.79.1 h1:tQNU4tC4cMoZo1e+7J8j3/GWM7PJFdXCN0VzEFwFqUE= +github.com/aws/aws-sdk-go-v2/service/ecs v1.79.1/go.mod h1:TIKZ9zIFS6W2k9FeW+r5sGVnlxp+aUt9oQ/St3Suj1o= +github.com/aws/aws-sdk-go-v2/service/eks v1.83.0 h1:mS5rkyFt+NYryy0p4n8o80tJjBmXiQrRCQjP8jZcSLY= +github.com/aws/aws-sdk-go-v2/service/eks v1.83.0/go.mod h1:JQcyECIV9iZHm+GMrWn1pTPTJYRavOVsqPvlCbjt+Fg= +github.com/aws/aws-sdk-go-v2/service/elasticache v1.52.2 h1:5wbCUfyxXcjIqesyVfJBBJs0bDMyejthtHyy48mfZCI= +github.com/aws/aws-sdk-go-v2/service/elasticache v1.52.2/go.mod h1:o4vQxDt6oteknUjkXIEskp0ccy+93NRTPKXw3HlVMFE= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.54.12 h1:TJXv7kZjdXA2maPDaJFFEQPBrPmvPtMybN3qYDOpJ4Y= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.54.12/go.mod h1:lwjtb9DHOAmNt7EUW68Zd1Qd+cPyFxacXHN5c9JZ2VY= +github.com/aws/aws-sdk-go-v2/service/emr v1.59.2 h1:BR7dPfnYULtw1QMLTb4p67A5O7cOhwsRh6arvYQFIQg= +github.com/aws/aws-sdk-go-v2/service/emr v1.59.2/go.mod h1:cWxbjdU0/WUkpKZ+k03XbZiC5b8OVjCRzQ4wJlnO0Q4= +github.com/aws/aws-sdk-go-v2/service/eventbridge v1.45.25 h1:9PZbyFSCN/E0TnqXqnvYJRhu7yQJv31vHG/vyuirbCY= +github.com/aws/aws-sdk-go-v2/service/eventbridge v1.45.25/go.mod h1:ZJ1LBykgykfLqmsP2pBUesSd24sL6SebSEeXzzJ2hhE= +github.com/aws/aws-sdk-go-v2/service/fms v1.44.24 h1:9HVojSt6nLVX/Ao55d7PGVg3zzg6XbNzT6RIiLnKEmM= +github.com/aws/aws-sdk-go-v2/service/fms v1.44.24/go.mod h1:cIz6sTAJzcJceQM5chsHkKq5agNHWtLCm5r20teGur8= +github.com/aws/aws-sdk-go-v2/service/gamelift v1.54.0 h1:TnDHcCjQbeyxNgWV67Gz8DigkCDcz6t4y+UWBItR1zc= +github.com/aws/aws-sdk-go-v2/service/gamelift v1.54.0/go.mod h1:krO76k8XMEa+hBF4M+zuyU2XXct6JQIdH5LKOrltHew= +github.com/aws/aws-sdk-go-v2/service/glue v1.140.1 h1:svc9ZslCPD5rQ0ZF3yYLbZPxnz/Bsu9zH52dHOWIzFY= +github.com/aws/aws-sdk-go-v2/service/glue v1.140.1/go.mod h1:FZCvt95CcJWRkZNRcmMW1uQkpDHmyUSkZfz3awyZgqg= +github.com/aws/aws-sdk-go-v2/service/guardduty v1.75.3 h1:SCPA/MVlgzHGmWKRFhtwAtWadETz3Yk6BeYF2LXrpGY= +github.com/aws/aws-sdk-go-v2/service/guardduty v1.75.3/go.mod h1:kCpMuo4vLtzUaugNCZGCkVtdehl2YvoSd73Jmiuh1M0= +github.com/aws/aws-sdk-go-v2/service/health v1.37.6 h1:m97jNgQk8XQrMqBxxVUWC709yuzhERApLPNDwjJdlU8= +github.com/aws/aws-sdk-go-v2/service/health v1.37.6/go.mod h1:tAAxr8sOfZmUsRJEQawUO/eij8XjjD9365NEfhvTrbk= +github.com/aws/aws-sdk-go-v2/service/iam v1.53.9 h1:slEs4iUvSt/YOiQQajtXkYBZTMrsEeplSnaB928p4l0= +github.com/aws/aws-sdk-go-v2/service/iam v1.53.9/go.mod h1:1vkJzjCYC3byO0kIrBqLPzvZpuvYhPXkuyARs6E7tM4= +github.com/aws/aws-sdk-go-v2/service/inspector2 v1.47.6 h1:Z8mkRPVl24t+m5FTtHJFBfh5mMKv+mUpubZgNwR1aWY= +github.com/aws/aws-sdk-go-v2/service/inspector2 v1.47.6/go.mod h1:LuywkZEbCgj1aB3Ij62TrWfPThhx6GHJqZSO1ZDj2cM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 h1:ieLCO1JxUWuxTZ1cRd0GAaeX7O6cIxnwk7tc1LsQhC4= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15/go.mod h1:e3IzZvQ3kAWNykvE0Tr0RDZCMFInMvhku3qNpcIQXhM= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.23 h1:3Eo/PBBnjFi1+gYfaL286dpmFSW3mTfodBIybq36Qv4= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.23/go.mod h1:3oh+5xGSd1iuxonVb3Qbm+WJYlbhczT9kbzr6doJLzY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 h1:03xatSQO4+AM1lTAbnRg5OK528EUg744nW7F73U8DKw= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23/go.mod h1:M8l3mwgx5ToK7wot2sBBce/ojzgnPzZXUV445gTSyE8= +github.com/aws/aws-sdk-go-v2/service/kinesis v1.43.7 h1:9FvrpWzkSPbm995UGQ4jOdRDuhQLmwgh/5t8UoosTdY= +github.com/aws/aws-sdk-go-v2/service/kinesis v1.43.7/go.mod h1:A7b/tv2nIcdfLY6EfH9fklY+L/wpVo6PtDJ6KA43PKg= +github.com/aws/aws-sdk-go-v2/service/kms v1.51.1 h1:zuSf4olLKZW8cF/W9Y5wvGT+/0raY/3kVp49KsGs0QY= +github.com/aws/aws-sdk-go-v2/service/kms v1.51.1/go.mod h1:Y0+uxvxz6ib4KktRdK0V4X45Vcs/JyYoz8H71pO8xeI= +github.com/aws/aws-sdk-go-v2/service/lambda v1.90.1 h1:odCeJgHXfQoXEWQUIzPkKvsJTWcLMsaOWowNpovPFFw= +github.com/aws/aws-sdk-go-v2/service/lambda v1.90.1/go.mod h1:NbtJVztitG7JkuoI4GSrDUlsB32zeXqKBvXj6bUxcMo= +github.com/aws/aws-sdk-go-v2/service/licensemanager v1.37.12 h1:CRnlzGMTYDTHpmFSGUe+CjEHzNQzL+fRZS5weQLvRfc= +github.com/aws/aws-sdk-go-v2/service/licensemanager v1.37.12/go.mod h1:1Y6IDySTexX5E3Jl95awEToOP1pW/rwEfcUwNElaF5A= +github.com/aws/aws-sdk-go-v2/service/macie2 v1.51.2 h1:JPIqNyD7JAaQddkmXRFVI7My2vtJ40uqTpEYIlnTEVw= +github.com/aws/aws-sdk-go-v2/service/macie2 v1.51.2/go.mod h1:Y1uuzDg6j6jVdm/xMgIgEaCJ2fMLPAgoKzKYuMBhJUQ= +github.com/aws/aws-sdk-go-v2/service/networkfirewall v1.60.1 h1:acbBwzoZSM3oet/FcUNddED5V7zBauXiRxsD2NJcD70= +github.com/aws/aws-sdk-go-v2/service/networkfirewall v1.60.1/go.mod h1:oWCet/AjsuKhMkvcXOGEeS2QmssLJX1UmX2SiKCEsFM= +github.com/aws/aws-sdk-go-v2/service/opensearch v1.67.1 h1:wzMn2wCOOzpDndCTeRYOpS/P9CYla83JKMDMSwZtdeE= +github.com/aws/aws-sdk-go-v2/service/opensearch v1.67.1/go.mod h1:m6jcW6ksKQtM4f/AsUbYdfbyM9xv4jjrBnRWWh1q0VQ= +github.com/aws/aws-sdk-go-v2/service/organizations v1.51.3 h1:LWSmXWwYzR9yRcszxyqaKuPCO4E6g/iknZv1kQIkD7I= +github.com/aws/aws-sdk-go-v2/service/organizations v1.51.3/go.mod h1:DGpC4BVQ1zS8X/nFYfHGiHyAhrsb8gZ8pPxn+Jf0iPY= +github.com/aws/aws-sdk-go-v2/service/rds v1.118.2 h1:pkEeQneYFpTAnGhyqSbyp/DlCPPJTGt0GkWahlLYzMA= +github.com/aws/aws-sdk-go-v2/service/rds v1.118.2/go.mod h1:7gS+cGrKF0mH253QHFlStmx79ws+DlNk+04ZRfmw3U0= +github.com/aws/aws-sdk-go-v2/service/redshift v1.62.7 h1:6NCQBp9IIEs51YI9/jT5/ckSd6Ka9956gAGlw0a0yFI= +github.com/aws/aws-sdk-go-v2/service/redshift v1.62.7/go.mod h1:uLWlNO4q8278lSx2iKIJZ09zSXNJ6uQTFM1jvZIZRf4= +github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.31.12 h1:kOX5fCUb0BSMNHbRm7icw/dEyTjiYCLczIYglbYFJnI= +github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.31.12/go.mod h1:n8ixkV2383DfuJhsCMVdfeSfYWqJhO2uadau9wrta9U= +github.com/aws/aws-sdk-go-v2/service/route53 v1.62.7 h1:twRRMmtSITnt/rrp+D7UDLzE5pKMZe759aalkUdN+OY= +github.com/aws/aws-sdk-go-v2/service/route53 v1.62.7/go.mod h1:ztM1lr+sRoCAI8336ZUvlRPbToue0d3gE/wd6jomSJ8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1 h1:mxuT1xE+dI54NW3RkNjP8DUT5HXqbkiAFvfdyDFwE5c= +github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4= +github.com/aws/aws-sdk-go-v2/service/s3vectors v1.6.8 h1:UfJBdSWO3yWDYEr/VEG4auXWStFQsgkiNsRax39ubLs= +github.com/aws/aws-sdk-go-v2/service/s3vectors v1.6.8/go.mod h1:loahjH/vYffb8QjGNCG7lNTvjot1Hy0eikvRuyMAymg= +github.com/aws/aws-sdk-go-v2/service/sagemaker v1.244.0 h1:yuCr5PhSk3kkvO8KDa0BoM0h/+fL+5agWRmsgJJlS5c= +github.com/aws/aws-sdk-go-v2/service/sagemaker v1.244.0/go.mod h1:CWbiQe1BkvmBlSfdI8DOoNaqoehc2+JWnylOfmR7aKU= +github.com/aws/aws-sdk-go-v2/service/savingsplans v1.32.4 h1:BWmdjmP8sWqe0eICMM+2X2jic2S41bJ7xDhz3Wfd8/0= +github.com/aws/aws-sdk-go-v2/service/savingsplans v1.32.4/go.mod h1:r/jBjd7QFGv9fsk0EFMjHKMPV6El60e4pa+BU4oyjBI= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.7 h1:JUGKqUnJHbXpS8uyuICP/zpQ+vXUIXW2zTEqjMLCqrY= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.7/go.mod h1:l/cqI7ujYqBuTR6Ll13d9/gG/uUdlVzJ1UDltEEBTOo= +github.com/aws/aws-sdk-go-v2/service/securityhub v1.69.2 h1:Ilq5NInONmelIDfs1dWp7j1lKOciY7sANZm5wzsIgH0= +github.com/aws/aws-sdk-go-v2/service/securityhub v1.69.2/go.mod h1:zaWfTggHBDZYQS7YIk6Atd2SsxXehBvSQjyN65+KCtk= +github.com/aws/aws-sdk-go-v2/service/servicequotas v1.34.7 h1:LRcX5C4jwSmkbmkamPVLU3/VALAo0Fuy77a2TgMKYx0= +github.com/aws/aws-sdk-go-v2/service/servicequotas v1.34.7/go.mod h1:52QJsp2N27Em8o5H/cgkBwjTY4I/TYpTBHMlqhuCHMQ= +github.com/aws/aws-sdk-go-v2/service/sfn v1.40.12 h1:jAnv9iYjIfFchpTYQ77jOix/yd5TkjLqk70g+qQ4js8= +github.com/aws/aws-sdk-go-v2/service/sfn v1.40.12/go.mod h1:4FYhQ/fQeVDlW53MUOevhxGhJ9RYOYV8HSwyVmFCer0= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 h1:TdJ+HdzOBhU8+iVAOGUTU63VXopcumCOF1paFulHWZc= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.11/go.mod h1:R82ZRExE/nheo0N+T8zHPcLRTcH8MGsnR3BiVGX0TwI= +github.com/aws/aws-sdk-go-v2/service/sns v1.39.17 h1:synXIPC/L4Cc489P0XDcrVJzHSLj7krKRpFLalbGM2k= +github.com/aws/aws-sdk-go-v2/service/sns v1.39.17/go.mod h1:4ABZnI23uNK37waIjGwkubnCwGhepIt9x1GvASfljJA= +github.com/aws/aws-sdk-go-v2/service/sqs v1.42.27 h1:QgaWXVmNDxv/U/3UIHfGb7ohvtFgerf/bYcYylj4i8E= +github.com/aws/aws-sdk-go-v2/service/sqs v1.42.27/go.mod h1:8S6ExnLprS0oIeA8ZlHkJUJ0BMpKqnRPws/S0jegTqQ= +github.com/aws/aws-sdk-go-v2/service/ssm v1.68.6 h1:0LPJjbSNEDHidGOXa0LfvSVbdn9/GdlJUQTgE0kFpso= +github.com/aws/aws-sdk-go-v2/service/ssm v1.68.6/go.mod h1:SrZAopBP5/lyQ6NBVXKlRp8wPIXhzBCZU98sEozmv8Y= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 h1:7byT8HUWrgoRp6sXjxtZwgOKfhss5fW6SkLBtqzgRoE= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.17/go.mod h1:xNWknVi4Ezm1vg1QsB/5EWpAJURq22uqd38U8qKvOJc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 h1:+1Kl1zx6bWi4X7cKi3VYh29h8BvsCoHQEQ6ST9X8w7w= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21/go.mod h1:4vIRDq+CJB2xFAXZ+YgGUTiEft7oAQlhIs71xcSeuVg= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 h1:F/M5Y9I3nwr2IEpshZgh1GeHpOItExNM9L1euNuh/fk= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.1/go.mod h1:mTNxImtovCOEEuD65mKW7DCsL+2gjEH+RPEAexAzAio= +github.com/aws/aws-sdk-go-v2/service/transcribe v1.54.6 h1:orXy3mYg4nOuACxT7dK07me2A2AaS/FcFBwq+WUA258= +github.com/aws/aws-sdk-go-v2/service/transcribe v1.54.6/go.mod h1:z29t64Pva4oeaYboSZrJhhYJDZu80VCV/AnMnqhNvwE= +github.com/aws/aws-sdk-go-v2/service/transfer v1.72.0 h1:RbzzKZwU/fPSDpO731YSRDQqbp/h6IycNV5pgVCbJVk= +github.com/aws/aws-sdk-go-v2/service/transfer v1.72.0/go.mod h1:NuALErcQ4g5s7KJef/5aoMwFmWn6deuczObKXYnuVRg= +github.com/aws/aws-sdk-go-v2/service/trustedadvisor v1.14.6 h1:kpQGIvOLoQbmSBJScW9JuJTJ95cKpoL5oA+R2W/kQcQ= +github.com/aws/aws-sdk-go-v2/service/trustedadvisor v1.14.6/go.mod h1:XSD4wwFqBwJIpghykR6eSp1geECVt1vG32BwjWZcXy8= +github.com/aws/aws-sdk-go-v2/service/wafv2 v1.71.5 h1:IMqwkBHrf3RjjNl+Xw0hkWz4WjB0ypcyNnRqOGiT7J0= +github.com/aws/aws-sdk-go-v2/service/wafv2 v1.71.5/go.mod h1:4PHOazTWE3wBq/xYohRMnO7vv3H0WgBYAvR8hyeZCfI= +github.com/aws/aws-sdk-go-v2/service/xray v1.36.23 h1:23UXC4h6nNZqOmk+xaXcfyTH/IIvqWycQk154GaSiE8= +github.com/aws/aws-sdk-go-v2/service/xray v1.36.23/go.mod h1:XEYnz2YYwBDEDHrfBWjv31kK8vCTrvCcnS7K7vyrDA0= +github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI= +github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o= +github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w= +github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= +github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= +github.com/charmbracelet/ultraviolet v0.0.0-20260428153724-66037269d7be h1:j7w8VP/D4lu5+/4GamMmFy8nrtadcl82/fjvDgSHwLo= +github.com/charmbracelet/ultraviolet v0.0.0-20260428153724-66037269d7be/go.mod h1:3YdTxlnV/L0bQ3VN8WOSw8doF7LZV/xawUQ4MuAPDvo= +github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI= +github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ= github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA= github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= @@ -206,43 +204,50 @@ github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8 github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= -github.com/clipperhouse/displaywidth v0.6.1 h1:/zMlAezfDzT2xy6acHBzwIfyu2ic0hgkT83UX5EY2gY= -github.com/clipperhouse/displaywidth v0.6.1/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= -github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= -github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= -github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= -github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= -github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= -github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= +github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= +github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= -golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k= +gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/ai/session_test.go b/internal/ai/session_test.go index d9ec4e72..adcd03fd 100644 --- a/internal/ai/session_test.go +++ b/internal/ai/session_test.go @@ -3,6 +3,7 @@ package ai import ( "os" "path/filepath" + "strings" "testing" "time" ) @@ -157,6 +158,53 @@ func TestSessionManagerWithPersistence(t *testing.T) { } } +func TestSessionPersistenceDoesNotContainRedactedSecrets(t *testing.T) { + tmpDir := t.TempDir() + t.Setenv("HOME", tmpDir) + + resource := &mockResource{ + id: "func-1", + name: "my-function", + raw: map[string]any{ + "Environment": map[string]any{ + "Variables": map[string]any{"TOKEN": "persist-me-not"}, + }, + "Outputs": []map[string]any{ + { + "OutputKey": "DB_PASSWORD", + "OutputValue": "persist-output-secret", + }, + }, + }, + } + detail := formatResourceDetail(resource) + if strings.Contains(detail, "persist-me-not") || strings.Contains(detail, "persist-output-secret") { + t.Fatalf("detail output still contains secret: %q", detail) + } + + sm := NewSessionManager(10, true) + session, err := sm.NewSession(&Context{Service: "lambda", ResourceType: "functions"}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if err := sm.AddMessage(session, NewUserMessage(detail)); err != nil { + t.Fatalf("failed to add message: %v", err) + } + + sessionFile := filepath.Join(tmpDir, ".config", "claws", "chat", "sessions", session.ID+".json") + data, err := os.ReadFile(sessionFile) + if err != nil { + t.Fatalf("failed to read session file: %v", err) + } + if strings.Contains(string(data), "persist-me-not") || strings.Contains(string(data), "TOKEN") || + strings.Contains(string(data), "persist-output-secret") || strings.Contains(string(data), "DB_PASSWORD") { + t.Fatalf("session file contains secret data: %s", string(data)) + } + if !strings.Contains(string(data), "[REDACTED]") { + t.Fatalf("session file should contain redaction marker: %s", string(data)) + } +} + func TestContext(t *testing.T) { t.Run("single mode", func(t *testing.T) { ctx := &Context{ diff --git a/internal/ai/tools.go b/internal/ai/tools.go index 19ac28a1..a3c9bd9c 100644 --- a/internal/ai/tools.go +++ b/internal/ai/tools.go @@ -8,6 +8,7 @@ import ( "net/http" "strings" "time" + "unicode" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" @@ -759,7 +760,7 @@ func formatResourceDetail(r dao.Resource) string { } if raw := r.Raw(); raw != nil { - data, err := json.MarshalIndent(raw, "", " ") + data, err := json.MarshalIndent(redactSensitiveRaw(raw), "", " ") if err == nil { result += fmt.Sprintf("\nRaw Data:\n%s\n", string(data)) } @@ -767,3 +768,154 @@ func formatResourceDetail(r dao.Resource) string { return result } + +func redactSensitiveRaw(raw any) any { + data, err := json.Marshal(raw) + if err != nil { + return raw + } + + var decoded any + if err := json.Unmarshal(data, &decoded); err != nil { + return raw + } + return redactSensitiveValue(decoded) +} + +func redactSensitiveValue(v any) any { + switch value := v.(type) { + case map[string]any: + redacted := make(map[string]any, len(value)) + sensitiveRecord := hasSensitiveLabelField(value) + for key, nested := range value { + normalizedKey := normalizeRawKey(key) + if sensitiveRecord && (isSensitiveLabelField(normalizedKey) || isSensitiveValueField(normalizedKey)) { + redacted[key] = "[REDACTED]" + continue + } + if isSensitiveRawKey(key) { + redacted[key] = "[REDACTED]" + continue + } + redacted[key] = redactSensitiveValue(nested) + } + return redacted + case []any: + redacted := make([]any, len(value)) + for i, nested := range value { + redacted[i] = redactSensitiveValue(nested) + } + return redacted + default: + return value + } +} + +func hasSensitiveLabelField(value map[string]any) bool { + for key, nested := range value { + if !isSensitiveLabelField(normalizeRawKey(key)) { + continue + } + label, ok := nested.(string) + if ok && isSensitiveRawKey(label) { + return true + } + } + return false +} + +func isSensitiveLabelField(normalizedKey string) bool { + switch normalizedKey { + case "name", "key", "parameterkey", "outputkey": + return true + default: + return false + } +} + +func isSensitiveValueField(normalizedKey string) bool { + switch normalizedKey { + case "value", "parametervalue", "outputvalue", "resolvedvalue": + return true + default: + return false + } +} + +func isSensitiveRawKey(key string) bool { + normalized := normalizeRawKey(key) + if exactSensitiveRawKeys[normalized] { + return true + } + for _, segment := range rawKeySegments(key) { + if sensitiveRawKeySegments[segment] { + return true + } + } + return false +} + +var exactSensitiveRawKeys = map[string]bool{ + "authorization": true, + "clientsecret": true, + "credential": true, + "credentials": true, + "environment": true, + "environmentvariables": true, + "privatekey": true, + "variables": true, + "secret": true, + "secrets": true, + "secretstring": true, + "secretbinary": true, + "password": true, + "token": true, + "apikey": true, + "accesskey": true, + "accesskeyid": true, + "secretaccesskey": true, + "sessiontoken": true, +} + +var sensitiveRawKeySegments = map[string]bool{ + "authorization": true, + "credential": true, + "credentials": true, + "environment": true, + "password": true, + "private": true, + "secret": true, + "token": true, +} + +func rawKeySegments(key string) []string { + var segments []string + var current []rune + var previous rune + for _, r := range key { + if r == '_' || r == '-' || r == ' ' || r == '.' || r == '/' { + segments = appendNormalizedSegment(segments, current) + current = nil + previous = 0 + continue + } + if len(current) > 0 && unicode.IsUpper(r) && (unicode.IsLower(previous) || unicode.IsDigit(previous)) { + segments = appendNormalizedSegment(segments, current) + current = nil + } + current = append(current, unicode.ToLower(r)) + previous = r + } + return appendNormalizedSegment(segments, current) +} + +func appendNormalizedSegment(segments []string, segment []rune) []string { + if len(segment) == 0 { + return segments + } + return append(segments, string(segment)) +} + +func normalizeRawKey(key string) string { + return strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(key, "_", ""), "-", "")) +} diff --git a/internal/ai/tools_test.go b/internal/ai/tools_test.go index 3cf0c34e..2778bdd5 100644 --- a/internal/ai/tools_test.go +++ b/internal/ai/tools_test.go @@ -311,6 +311,87 @@ func TestFormatResourceDetail(t *testing.T) { } } +func TestFormatResourceDetailRedactsSensitiveRawData(t *testing.T) { + resource := &mockResource{ + id: "func-1", + name: "my-function", + raw: map[string]any{ + "FunctionName": "my-function", + "Environment": map[string]any{ + "Variables": map[string]any{ + "API_KEY": "super-secret-value", + }, + }, + "VpcConfig": map[string]any{ + "VpcId": "vpc-123", + }, + }, + } + + result := formatResourceDetail(resource) + + if strings.Contains(result, "super-secret-value") || strings.Contains(result, "API_KEY") { + t.Fatalf("expected sensitive environment values to be redacted, got %q", result) + } + if !strings.Contains(result, "[REDACTED]") { + t.Fatalf("expected redaction marker, got %q", result) + } + if !strings.Contains(result, "vpc-123") { + t.Fatalf("expected non-sensitive raw fields to remain, got %q", result) + } +} + +func TestFormatResourceDetailRedactsSensitiveLabelValueRecords(t *testing.T) { + resource := &mockResource{ + id: "stack-1", + name: "my-stack", + raw: map[string]any{ + "Outputs": []map[string]any{ + { + "OutputKey": "DB_PASSWORD", + "OutputValue": "plain-secret-output", + }, + { + "OutputKey": "Endpoint", + "OutputValue": "https://example.com", + }, + }, + "Parameters": []map[string]any{ + { + "ParameterKey": "ApiToken", + "ParameterValue": "plain-secret-parameter", + "ResolvedValue": "plain-secret-resolved", + }, + }, + }, + } + + result := formatResourceDetail(resource) + + for _, secret := range []string{"DB_PASSWORD", "plain-secret-output", "ApiToken", "plain-secret-parameter", "plain-secret-resolved"} { + if strings.Contains(result, secret) { + t.Fatalf("expected sensitive label/value record data to be redacted, found %q in %q", secret, result) + } + } + if !strings.Contains(result, "https://example.com") { + t.Fatalf("expected non-sensitive output value to remain, got %q", result) + } +} + +func TestIsSensitiveRawKeyAvoidsSubstringFalsePositives(t *testing.T) { + for _, key := range []string{"tokenization", "tokencount", "accessTokens_issued", "secretsmanager_arn", "credentialsexpiry"} { + if isSensitiveRawKey(key) { + t.Fatalf("isSensitiveRawKey(%q) = true, want false", key) + } + } + + for _, key := range []string{"DB_PASSWORD", "ApiToken", "clientSecret", "SecretAccessKey", "EnvironmentVariables"} { + if !isSensitiveRawKey(key) { + t.Fatalf("isSensitiveRawKey(%q) = false, want true", key) + } + } +} + type mockResource struct { id string name string diff --git a/internal/enrichment/status.go b/internal/enrichment/status.go new file mode 100644 index 00000000..05af7845 --- /dev/null +++ b/internal/enrichment/status.go @@ -0,0 +1,40 @@ +package enrichment + +import apperrors "github.com/clawscli/claws/internal/errors" + +// Status describes whether optional resource details were fetched and why they +// may be unavailable. +type Status string + +const ( + Unknown Status = "" + Fetched Status = "fetched" + Configured Status = "configured" + NotConfigured Status = "not_configured" + AccessDenied Status = "access_denied" + FetchFailed Status = "fetch_failed" +) + +func FailureStatus(err error) Status { + if apperrors.IsAccessDenied(err) { + return AccessDenied + } + return FetchFailed +} + +func IsFailure(status Status) bool { + return status == AccessDenied || status == FetchFailed +} + +func Display(status Status) string { + switch status { + case AccessDenied: + return "Unknown (access denied)" + case FetchFailed: + return "Unknown (fetch failed)" + case NotConfigured: + return "Not configured" + default: + return "Unknown" + } +} diff --git a/internal/genimports/genimports_test.go b/internal/genimports/genimports_test.go index 8e914739..e8f7092c 100644 --- a/internal/genimports/genimports_test.go +++ b/internal/genimports/genimports_test.go @@ -276,9 +276,7 @@ func TestGetProjectRoot(t *testing.T) { t.Fatalf("GetProjectRoot() error = %v", err) } - if root != tmpDir { - t.Errorf("GetProjectRoot() = %q, want %q", root, tmpDir) - } + assertSamePath(t, root, tmpDir) }) t.Run("goes up two levels from cmd/claws", func(t *testing.T) { @@ -303,12 +301,29 @@ func TestGetProjectRoot(t *testing.T) { } expected := filepath.Join(cmdClawsDir, "..", "..") - if root != expected { - t.Errorf("GetProjectRoot() = %q, want %q", root, expected) - } + assertSamePath(t, root, expected) }) } +func assertSamePath(t *testing.T, got, want string) { + t.Helper() + + normalizedGot := normalizePath(got) + normalizedWant := normalizePath(want) + if normalizedGot != normalizedWant { + t.Errorf("path = %q (%q), want %q (%q)", got, normalizedGot, want, normalizedWant) + } +} + +func normalizePath(path string) string { + cleaned := filepath.Clean(path) + resolved, err := filepath.EvalSymlinks(cleaned) + if err == nil { + return resolved + } + return cleaned +} + func TestReadPackageName(t *testing.T) { t.Run("reads package name from file", func(t *testing.T) { tmpDir := t.TempDir() diff --git a/internal/view/resource_browser_fetch.go b/internal/view/resource_browser_fetch.go index b5295a5f..ef1442c0 100644 --- a/internal/view/resource_browser_fetch.go +++ b/internal/view/resource_browser_fetch.go @@ -130,7 +130,13 @@ func (r *ResourceBrowser) fetchMultiProfileResources(profiles []config.ProfileSe var keys []profileRegionKey for _, sel := range profiles { for _, region := range regions { - keys = append(keys, profileRegionKey{Profile: sel.ID(), Region: region}) + key := profileRegionKey{Profile: sel.ID(), Region: region} + if existingTokens != nil { + if _, ok := existingTokens[key]; !ok { + continue + } + } + keys = append(keys, key) } } diff --git a/internal/view/resource_browser_test.go b/internal/view/resource_browser_test.go index 92209827..7c5a08a5 100644 --- a/internal/view/resource_browser_test.go +++ b/internal/view/resource_browser_test.go @@ -3,10 +3,12 @@ package view import ( "context" "strings" + "sync" "testing" tea "charm.land/bubbletea/v2" + "github.com/clawscli/claws/internal/config" "github.com/clawscli/claws/internal/dao" "github.com/clawscli/claws/internal/registry" ) @@ -606,6 +608,73 @@ func TestFetchParallelAllErrors(t *testing.T) { } } +func TestFetchMultiProfileResourcesSkipsPairsWithoutNextToken(t *testing.T) { + recorder := &recordingPaginatedDAO{BaseDAO: dao.NewBaseDAO("svc", "items")} + reg := registry.New() + reg.RegisterCustom("svc", "items", registry.Entry{ + DAOFactory: func(context.Context) (dao.DAO, error) { + return recorder, nil + }, + }) + + profiles := []config.ProfileSelection{config.NamedProfile("p1"), config.NamedProfile("p2")} + regions := []string{"us-east-1", "us-west-2"} + config.Global().SetAccountIDs(map[string]string{"p1": "111111111111", "p2": "222222222222"}) + + browser := &ResourceBrowser{ + ctx: context.Background(), + registry: reg, + service: "svc", + resourceType: "items", + pageSize: 10, + } + + result := browser.fetchMultiProfileResources(profiles, regions, map[profileRegionKey]string{ + {Profile: "p1", Region: "us-east-1"}: "next-p1-r1", + }) + + if len(result.errors) != 0 { + t.Fatalf("unexpected errors: %v", result.errors) + } + if got := recorder.tokens(); len(got) != 1 || got[0] != "next-p1-r1" { + t.Fatalf("ListPage tokens = %v, want only [next-p1-r1]", got) + } + if len(result.resources) != 1 { + t.Fatalf("resources = %d, want 1", len(result.resources)) + } +} + +type recordingPaginatedDAO struct { + dao.BaseDAO + mu sync.Mutex + pageTokens []string +} + +func (d *recordingPaginatedDAO) List(context.Context) ([]dao.Resource, error) { + return nil, nil +} + +func (d *recordingPaginatedDAO) Get(context.Context, string) (dao.Resource, error) { + return nil, nil +} + +func (d *recordingPaginatedDAO) Delete(context.Context, string) error { + return nil +} + +func (d *recordingPaginatedDAO) ListPage(_ context.Context, _ int, pageToken string) ([]dao.Resource, string, error) { + d.mu.Lock() + d.pageTokens = append(d.pageTokens, pageToken) + d.mu.Unlock() + return []dao.Resource{&mockResource{id: pageToken, name: pageToken}}, "", nil +} + +func (d *recordingPaginatedDAO) tokens() []string { + d.mu.Lock() + defer d.mu.Unlock() + return append([]string(nil), d.pageTokens...) +} + func TestResourceBrowserCopyID(t *testing.T) { ctx := context.Background() reg := registry.New() diff --git a/internal/view/tag_search_view.go b/internal/view/tag_search_view.go index 2b0d35cf..63afa16d 100644 --- a/internal/view/tag_search_view.go +++ b/internal/view/tag_search_view.go @@ -152,6 +152,7 @@ func (v *TagSearchView) fetchTaggedResources(regions []string, existingTokens ma defer cancel() results := make(chan regionResult, len(regions)) + sem := make(chan struct{}, config.File().MaxConcurrentFetches()) var wg sync.WaitGroup tagFilters := v.parseTagFilters() @@ -161,6 +162,9 @@ func (v *TagSearchView) fetchTaggedResources(regions []string, existingTokens ma go func(region string) { defer wg.Done() + sem <- struct{}{} + defer func() { <-sem }() + regionCtx := aws.WithRegionOverride(ctx, region) cfg, err := aws.NewConfig(regionCtx) if err != nil {