Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7059954
Add AI chat overlay with Bedrock Converse API (#123)
yimsk Jan 7, 2026
4778ecd
Enhance AI chat, add ECS task-definitions, improve filter UX
yimsk Jan 8, 2026
9eb6fbc
Fix AI chat code review issues
yimsk Jan 8, 2026
dd9c8e2
Use ConverseStream for real-time AI response + refactor
yimsk Jan 8, 2026
fd0ee08
Add extended thinking, search_aws_docs, fix Data field in 62 resources
yimsk Jan 8, 2026
9a57482
Fix AI chat multi-turn conversation with tool calls
yimsk Jan 8, 2026
7e0da39
Add AI chat list/diff context modes + multi-profile support
yimsk Jan 8, 2026
00edab5
Add collapsible tool call params in AI chat (default collapsed)
yimsk Jan 8, 2026
8250a35
AI chat: disable session persistence by default, increase max_session…
yimsk Jan 8, 2026
2013ebc
Add AI module tests and Bedrock IAM permissions docs
yimsk Jan 8, 2026
68249c4
Fix code review issues: remove duplicate DefaultModel, use parent ctx…
yimsk Jan 8, 2026
1106985
Fix review issues: slice copy, log.Warn, ctx shadow, use go-runewidth
yimsk Jan 8, 2026
5c1ac80
Fix race condition: remove duplicate streamMessages assignment in sta…
yimsk Jan 8, 2026
d9d3aba
Add chat session history UI (Ctrl+H) and fix nav key conflicts
yimsk Jan 8, 2026
2b7ebf5
fix TestSessionPruning: add message to trigger file save
yimsk Jan 8, 2026
725dcbd
Fix review: nil guard loadSession, log session errors, warn negative …
yimsk Jan 9, 2026
50f25d6
SecurityHub findings filter toggle + AI context expansion
yimsk Jan 9, 2026
716891e
ai: make max_tool_rounds configurable (default 15)
yimsk Jan 9, 2026
0ef2fb9
ai: use UUID for session ID, add debug log on load failure
yimsk Jan 9, 2026
e362fd0
Fix code review issues: stream cancellation, file permissions, docs
yimsk Jan 9, 2026
2e85dc1
Fix: save tool use/result to session for persistence
yimsk Jan 9, 2026
a8ddfbc
AI chat: fix profile ID handling, strengthen list mode instructions
yimsk Jan 9, 2026
11099fd
AI chat: region/profile context complete
yimsk Jan 9, 2026
325ec72
Refactor: share mergeResources across views
yimsk Jan 9, 2026
a1af562
AI chat: tool call limit, resource pagination, session pruning optimi…
yimsk Jan 9, 2026
efa05a7
Docs: add max_tool_calls_per_query config
yimsk Jan 9, 2026
e431d97
Fix: toolCallCount naming, reset in loadSession, profile ID handling
yimsk Jan 9, 2026
bb3a061
Docs: AI chat feature w/ screenshot, usage guide
yimsk Jan 9, 2026
fc84033
Fix: code review critical issues (imports, race, perms, tool limit)
yimsk Jan 9, 2026
3e407b7
Fix: session pruning by mtime, streamCancel race condition
yimsk Jan 9, 2026
81f1789
Docs: simplify ai-chat.md, remove tool details and examples
yimsk Jan 9, 2026
e19c82f
Docs: update IAM permissions for all Bedrock models
yimsk Jan 9, 2026
0dd7211
Fix: return error flag for invalid pagination offset
yimsk Jan 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ A terminal UI for AWS resource management
- **Cross-resource navigation** - Jump from VPC to subnets, Lambda to CloudWatch
- **Filtering & sorting** - Fuzzy search, tag filtering, column sorting
- **Resource comparison** - Side-by-side diff view
- **AI Chat** - AI assistant with AWS context (via Bedrock)
- **6 color themes** - dark, light, nord, dracula, gruvbox, catppuccin

## Screenshots
Expand All @@ -31,6 +32,12 @@ A terminal UI for AWS resource management

![multi-region](docs/images/multi-account-region.png)

### AI Chat (Bedrock)

![ai-chat](docs/images/ai-chat.png)

Press `A` in list/detail/diff views to open AI chat. The assistant analyzes resources, compares configurations, and identifies risks using AWS Bedrock.

## Installation

### Homebrew (macOS/Linux)
Expand Down Expand Up @@ -89,6 +96,7 @@ claws --read-only
| `:` | Command mode (e.g., `:ec2/instances`) |
| `/` | Filter mode (fuzzy search) |
| `a` | Open actions menu |
| `A` | AI Chat (in list/detail/diff views) |
| `R` | Select region(s) |
| `P` | Select profile(s) |
| `?` | Show help |
Expand All @@ -104,6 +112,7 @@ See [docs/keybindings.md](docs/keybindings.md) for complete reference.
| [Supported Services](docs/services.md) | All 69 services and 163 resources |
| [Configuration](docs/configuration.md) | Config file, themes, and options |
| [IAM Permissions](docs/iam-permissions.md) | Required AWS permissions |
| [AI Chat](docs/ai-chat.md) | AI assistant usage and features |
| [Architecture](docs/architecture.md) | Internal design and structure |
| [Adding Resources](docs/adding-resources.md) | Guide for contributors |

Expand Down
1 change: 1 addition & 0 deletions cmd/claws/imports_custom.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions custom/accessanalyzer/analyzers/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ type AnalyzerResource struct {
func NewAnalyzerResource(analyzer types.AnalyzerSummary) *AnalyzerResource {
return &AnalyzerResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(analyzer.Name),
ARN: appaws.Str(analyzer.Arn),
ID: appaws.Str(analyzer.Name),
ARN: appaws.Str(analyzer.Arn),
Data: analyzer,
},
Summary: &analyzer,
}
Expand All @@ -96,8 +97,9 @@ func NewAnalyzerResource(analyzer types.AnalyzerSummary) *AnalyzerResource {
func NewAnalyzerResourceFromDetail(analyzer types.AnalyzerSummary) *AnalyzerResource {
return &AnalyzerResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(analyzer.Name),
ARN: appaws.Str(analyzer.Arn),
ID: appaws.Str(analyzer.Name),
ARN: appaws.Str(analyzer.Arn),
Data: analyzer,
},
Detail: &analyzer,
}
Expand Down
6 changes: 4 additions & 2 deletions custom/accessanalyzer/findings/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ type FindingResource struct {
func NewFindingResource(finding types.FindingSummary, analyzerArn string) *FindingResource {
return &FindingResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(finding.Id),
ID: appaws.Str(finding.Id),
Data: finding,
},
Summary: &finding,
AnalyzerArn: analyzerArn,
Expand All @@ -104,7 +105,8 @@ func NewFindingResource(finding types.FindingSummary, analyzerArn string) *Findi
func NewFindingResourceFromDetail(finding types.Finding, analyzerArn string) *FindingResource {
return &FindingResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(finding.Id),
ID: appaws.Str(finding.Id),
Data: finding,
},
Detail: &finding,
AnalyzerArn: analyzerArn,
Expand Down
5 changes: 3 additions & 2 deletions custom/apprunner/operations/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,9 @@ type OperationResource struct {
func NewOperationResource(op types.OperationSummary) *OperationResource {
return &OperationResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(op.Id),
ARN: appaws.Str(op.TargetArn),
ID: appaws.Str(op.Id),
ARN: appaws.Str(op.TargetArn),
Data: op,
},
Item: op,
}
Expand Down
10 changes: 6 additions & 4 deletions custom/apprunner/services/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ type ServiceResource struct {
func NewServiceResource(svc types.ServiceSummary) *ServiceResource {
return &ServiceResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(svc.ServiceName),
ARN: appaws.Str(svc.ServiceArn),
ID: appaws.Str(svc.ServiceName),
ARN: appaws.Str(svc.ServiceArn),
Data: svc,
},
Summary: &svc,
}
Expand All @@ -96,8 +97,9 @@ func NewServiceResource(svc types.ServiceSummary) *ServiceResource {
func NewServiceResourceFromDetail(svc types.Service) *ServiceResource {
return &ServiceResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(svc.ServiceName),
ARN: appaws.Str(svc.ServiceArn),
ID: appaws.Str(svc.ServiceName),
ARN: appaws.Str(svc.ServiceArn),
Data: svc,
},
Detail: &svc,
}
Expand Down
5 changes: 3 additions & 2 deletions custom/appsync/data-sources/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,9 @@ type DataSourceResource struct {
func NewDataSourceResource(ds types.DataSource, apiId string) *DataSourceResource {
return &DataSourceResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(ds.Name),
ARN: appaws.Str(ds.DataSourceArn),
ID: appaws.Str(ds.Name),
ARN: appaws.Str(ds.DataSourceArn),
Data: ds,
},
DataSource: &ds,
apiId: apiId,
Expand Down
5 changes: 3 additions & 2 deletions custom/appsync/graphql-apis/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ type GraphQLApiResource struct {
func NewGraphQLApiResource(api types.GraphqlApi) *GraphQLApiResource {
return &GraphQLApiResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(api.ApiId),
ARN: appaws.Str(api.Arn),
ID: appaws.Str(api.ApiId),
ARN: appaws.Str(api.Arn),
Data: api,
},
Api: &api,
}
Expand Down
2 changes: 1 addition & 1 deletion custom/appsync/graphql-apis/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func (r *GraphQLApiRenderer) Navigations(resource dao.Resource) []render.Navigat
}
return []render.Navigation{
{
Key: "d",
Key: "D",
Label: "Data Sources",
Service: "appsync",
Resource: "data-sources",
Expand Down
5 changes: 3 additions & 2 deletions custom/athena/query-executions/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,9 @@ type QueryExecutionResource struct {
func NewQueryExecutionResource(qe types.QueryExecution) *QueryExecutionResource {
return &QueryExecutionResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(qe.QueryExecutionId),
ARN: "",
ID: appaws.Str(qe.QueryExecutionId),
ARN: "",
Data: qe,
},
Item: qe,
}
Expand Down
10 changes: 6 additions & 4 deletions custom/athena/workgroups/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ type WorkgroupResource struct {
func NewWorkgroupResource(wg types.WorkGroupSummary) *WorkgroupResource {
return &WorkgroupResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(wg.Name),
ARN: "",
ID: appaws.Str(wg.Name),
ARN: "",
Data: wg,
},
Summary: &wg,
}
Expand All @@ -96,8 +97,9 @@ func NewWorkgroupResource(wg types.WorkGroupSummary) *WorkgroupResource {
func NewWorkgroupResourceFromDetail(wg types.WorkGroup) *WorkgroupResource {
return &WorkgroupResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(wg.Name),
ARN: "",
ID: appaws.Str(wg.Name),
ARN: "",
Data: wg,
},
Detail: &wg,
}
Expand Down
5 changes: 3 additions & 2 deletions custom/batch/compute-environments/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,9 @@ type ComputeEnvironmentResource struct {
func NewComputeEnvironmentResource(env types.ComputeEnvironmentDetail) *ComputeEnvironmentResource {
return &ComputeEnvironmentResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(env.ComputeEnvironmentName),
ARN: appaws.Str(env.ComputeEnvironmentArn),
ID: appaws.Str(env.ComputeEnvironmentName),
ARN: appaws.Str(env.ComputeEnvironmentArn),
Data: env,
},
Env: &env,
}
Expand Down
5 changes: 3 additions & 2 deletions custom/batch/job-definitions/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ func NewJobDefinitionResource(def types.JobDefinition) *JobDefinitionResource {
}
return &JobDefinitionResource{
BaseResource: dao.BaseResource{
ID: name,
ARN: appaws.Str(def.JobDefinitionArn),
ID: name,
ARN: appaws.Str(def.JobDefinitionArn),
Data: def,
},
Def: &def,
}
Expand Down
5 changes: 3 additions & 2 deletions custom/batch/job-queues/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,9 @@ type JobQueueResource struct {
func NewJobQueueResource(queue types.JobQueueDetail) *JobQueueResource {
return &JobQueueResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(queue.JobQueueName),
ARN: appaws.Str(queue.JobQueueArn),
ID: appaws.Str(queue.JobQueueName),
ARN: appaws.Str(queue.JobQueueArn),
Data: queue,
},
Queue: &queue,
}
Expand Down
10 changes: 6 additions & 4 deletions custom/batch/jobs/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,9 @@ func (d *JobDAO) Get(ctx context.Context, id string) (dao.Resource, error) {

return &JobResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(job.JobId),
ARN: appaws.Str(job.JobArn),
ID: appaws.Str(job.JobId),
ARN: appaws.Str(job.JobArn),
Data: job,
},
Job: &types.JobSummary{
JobId: job.JobId,
Expand Down Expand Up @@ -154,8 +155,9 @@ type JobResource struct {
func NewJobResource(job types.JobSummary) *JobResource {
return &JobResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(job.JobId),
ARN: appaws.Str(job.JobArn),
ID: appaws.Str(job.JobId),
ARN: appaws.Str(job.JobArn),
Data: job,
},
Job: &job,
}
Expand Down
5 changes: 3 additions & 2 deletions custom/budgets/budgets/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,9 @@ type BudgetResource struct {
func NewBudgetResource(budget types.Budget, accountID string) *BudgetResource {
return &BudgetResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(budget.BudgetName),
ARN: fmt.Sprintf("arn:aws:budgets::%s:budget/%s", accountID, appaws.Str(budget.BudgetName)),
ID: appaws.Str(budget.BudgetName),
ARN: fmt.Sprintf("arn:aws:budgets::%s:budget/%s", accountID, appaws.Str(budget.BudgetName)),
Data: budget,
},
Item: budget,
AccountID: accountID,
Expand Down
5 changes: 3 additions & 2 deletions custom/budgets/notifications/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@ func NewNotificationResource(notif types.Notification, budgetName string, index
)
return &NotificationResource{
BaseResource: dao.BaseResource{
ID: id,
ARN: "",
ID: id,
ARN: "",
Data: notif,
},
Item: notif,
BudgetName: budgetName,
Expand Down
3 changes: 2 additions & 1 deletion custom/ce/costs/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ func NewCostResource(group types.Group, start, end string) *CostResource {
ID: serviceName,
// Pseudo-ARN: Cost Explorer aggregates don't have real ARNs.
// Format "ce::<service>" enables internal resource identification.
ARN: fmt.Sprintf("ce::%s", serviceName),
ARN: fmt.Sprintf("ce::%s", serviceName),
Data: serviceName,
},
ServiceName: serviceName,
Cost: cost,
Expand Down
5 changes: 3 additions & 2 deletions custom/cloudtrail/events/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,9 @@ type EventResource struct {
func NewEventResource(event types.Event) *EventResource {
return &EventResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(event.EventId),
ARN: appaws.Str(event.EventId),
ID: appaws.Str(event.EventId),
ARN: appaws.Str(event.EventId),
Data: event,
},
Item: event,
}
Expand Down
5 changes: 3 additions & 2 deletions custom/cloudtrail/trails/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ type TrailResource struct {
func NewTrailResource(trail types.Trail) *TrailResource {
return &TrailResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(trail.Name),
ARN: appaws.Str(trail.TrailARN),
ID: appaws.Str(trail.Name),
ARN: appaws.Str(trail.TrailARN),
Data: trail,
},
Item: trail,
}
Expand Down
5 changes: 3 additions & 2 deletions custom/configservice/rules/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,9 @@ type RuleResource struct {
func NewRuleResource(rule types.ConfigRule) *RuleResource {
return &RuleResource{
BaseResource: dao.BaseResource{
ID: appaws.Str(rule.ConfigRuleName),
ARN: appaws.Str(rule.ConfigRuleArn),
ID: appaws.Str(rule.ConfigRuleName),
ARN: appaws.Str(rule.ConfigRuleArn),
Data: rule,
},
Item: rule,
}
Expand Down
5 changes: 3 additions & 2 deletions custom/datasync/locations/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ func NewLocationResource(loc types.LocationListEntry) *LocationResource {
arn := appaws.Str(loc.LocationArn)
return &LocationResource{
BaseResource: dao.BaseResource{
ID: extractLocationID(arn),
ARN: arn,
ID: extractLocationID(arn),
ARN: arn,
Data: loc,
},
Location: &loc,
}
Expand Down
5 changes: 3 additions & 2 deletions custom/datasync/task-executions/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,9 @@ func NewTaskExecutionResource(exec types.TaskExecutionListEntry) *TaskExecutionR
arn := appaws.Str(exec.TaskExecutionArn)
return &TaskExecutionResource{
BaseResource: dao.BaseResource{
ID: extractExecutionID(arn),
ARN: arn,
ID: extractExecutionID(arn),
ARN: arn,
Data: exec,
},
Execution: &exec,
}
Expand Down
10 changes: 6 additions & 4 deletions custom/datasync/tasks/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ func (d *TaskDAO) Get(ctx context.Context, id string) (dao.Resource, error) {

return &TaskResource{
BaseResource: dao.BaseResource{
ID: extractTaskID(appaws.Str(output.TaskArn)),
ARN: appaws.Str(output.TaskArn),
ID: extractTaskID(appaws.Str(output.TaskArn)),
ARN: appaws.Str(output.TaskArn),
Data: output,
},
Task: &types.TaskListEntry{
TaskArn: output.TaskArn,
Expand Down Expand Up @@ -150,8 +151,9 @@ func NewTaskResource(task types.TaskListEntry) *TaskResource {
arn := appaws.Str(task.TaskArn)
return &TaskResource{
BaseResource: dao.BaseResource{
ID: extractTaskID(arn),
ARN: arn,
ID: extractTaskID(arn),
ARN: arn,
Data: task,
},
Task: &task,
}
Expand Down
5 changes: 3 additions & 2 deletions custom/detective/graphs/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,9 @@ func NewGraphResource(graph types.Graph) *GraphResource {

return &GraphResource{
BaseResource: dao.BaseResource{
ID: id,
ARN: arn,
ID: id,
ARN: arn,
Data: graph,
},
Graph: &graph,
}
Expand Down
5 changes: 3 additions & 2 deletions custom/detective/investigations/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,9 @@ func NewInvestigationResource(inv types.InvestigationDetail, graphArn string) *I
id := appaws.Str(inv.InvestigationId)
return &InvestigationResource{
BaseResource: dao.BaseResource{
ID: id,
ARN: graphArn + "/investigation/" + id,
ID: id,
ARN: graphArn + "/investigation/" + id,
Data: inv,
},
Investigation: &inv,
graphArn: graphArn,
Expand Down
Loading