haru-cli 0.1.4__tar.gz → 0.1.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {haru_cli-0.1.4 → haru_cli-0.1.6}/CHANGELOG.md +48 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/PKG-INFO +36 -3
- {haru_cli-0.1.4 → haru_cli-0.1.6}/README.md +35 -2
- {haru_cli-0.1.4 → haru_cli-0.1.6}/docs/configuration.md +9 -0
- haru_cli-0.1.6/docs/troubleshooting.md +136 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/pyproject.toml +16 -3
- haru_cli-0.1.6/src/haru/agents/__init__.py +1 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/agents/factory.py +14 -8
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/auth/identity.py +102 -25
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/auth/session.py +81 -42
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/auth/sso.py +6 -3
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/cli.py +10 -1
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/commands/chat.py +9 -1
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/commands/config.py +8 -0
- haru_cli-0.1.6/src/haru/commands/doctor.py +161 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/commands/login.py +8 -2
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/commands/run.py +8 -2
- haru_cli-0.1.6/src/haru/commands/streaming.py +83 -0
- haru_cli-0.1.6/src/haru/diagnostics/__init__.py +6 -0
- haru_cli-0.1.6/src/haru/diagnostics/checks.py +336 -0
- haru_cli-0.1.6/src/haru/diagnostics/matrix.py +143 -0
- haru_cli-0.1.6/src/haru/logging_setup.py +118 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/models/bedrock.py +8 -3
- haru_cli-0.1.6/src/haru/models/errors.py +134 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/observability/telemetry.py +2 -2
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/sessions/manager.py +11 -4
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/tools/mcp.py +12 -6
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/unit/test_agent_factory.py +9 -9
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/unit/test_bedrock.py +6 -6
- haru_cli-0.1.6/tests/unit/test_diagnostics.py +338 -0
- haru_cli-0.1.6/tests/unit/test_doctor.py +172 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/unit/test_guardrails.py +2 -2
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/unit/test_identity.py +87 -4
- haru_cli-0.1.6/tests/unit/test_import_time.py +25 -0
- haru_cli-0.1.6/tests/unit/test_logging_setup.py +123 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/unit/test_mcp.py +7 -7
- haru_cli-0.1.6/tests/unit/test_model_errors.py +84 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/unit/test_session.py +26 -3
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/unit/test_telemetry.py +5 -5
- {haru_cli-0.1.4 → haru_cli-0.1.6}/uv.lock +1 -1
- haru_cli-0.1.4/src/haru/agents/__init__.py +0 -18
- haru_cli-0.1.4/src/haru/commands/streaming.py +0 -34
- {haru_cli-0.1.4 → haru_cli-0.1.6}/.github/workflows/ci.yml +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/.github/workflows/release.yml +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/.gitignore +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/.kiro/steering/coding-standards.md +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/.kiro/steering/git-workflow.md +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/.kiro/steering/product.md +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/.kiro/steering/security.md +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/.kiro/steering/structure.md +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/.kiro/steering/tech.md +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/.kiro/steering/testing.md +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/.pre-commit-config.yaml +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/.python-version +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/CLAUDE.md +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/LICENSE +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/config/agents.yaml +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/config/guardrails.yaml +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/config/haru.yaml +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/config/logging.yaml +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/config/mcp.yaml +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/config/models.yaml +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/config/prompts/researcher.md +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/config/prompts/supervisor.md +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/config/prompts/writer.md +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/renovate.json +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/__init__.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/__main__.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/agents/orchestration.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/auth/__init__.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/auth/cache.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/auth/pkce.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/commands/__init__.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/commands/agents.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/commands/session.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/config/__init__.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/config/loader.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/config/schema.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/config/templates/__init__.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/config/templates/agents.yaml +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/config/templates/haru.yaml +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/config/templates/logging.yaml +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/config/templates/mcp.yaml +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/config/templates/models.yaml +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/config/templates/prompts/researcher.md +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/config/templates/prompts/supervisor.md +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/config/templates/prompts/writer.md +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/errors.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/models/__init__.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/observability/__init__.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/observability/guardrails.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/sessions/__init__.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/steering/__init__.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/steering/prompts.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/tools/__init__.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/src/haru/tools/registry.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/conftest.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/integration/test_packaging.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/unit/test_cache.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/unit/test_cli.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/unit/test_config_command.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/unit/test_config_loader.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/unit/test_main.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/unit/test_orchestration.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/unit/test_pkce.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/unit/test_prompts.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/unit/test_sessions.py +0 -0
- {haru_cli-0.1.4 → haru_cli-0.1.6}/tests/unit/test_sso.py +0 -0
|
@@ -9,6 +9,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
+
- `haru doctor`: diagnoses configuration, SSO token, identity provenance,
|
|
13
|
+
role credentials, caller identity, region divergence, Bedrock reachability,
|
|
14
|
+
inference-profile availability, and guardrail sanity, with per-check
|
|
15
|
+
remediation and `--json` output. `--invoke` makes one real (billable)
|
|
16
|
+
Bedrock call for a definitive answer.
|
|
17
|
+
- `haru doctor --all-roles`: probes every account and role you are assigned
|
|
18
|
+
and prints a matrix of which combinations can actually reach Bedrock -
|
|
19
|
+
answering "which IAM role do I need?" empirically.
|
|
20
|
+
- Global `--debug` flag and real logging: `logging.yaml` (level/format/file)
|
|
21
|
+
is now honoured instead of being dead config. AWS SDK logging rises to INFO
|
|
22
|
+
under `--debug` (operations and error codes, never bodies or headers), and a
|
|
23
|
+
redaction filter masks credential-shaped values.
|
|
24
|
+
- `docs/troubleshooting.md`: the IAM policy to hand an AWS administrator,
|
|
25
|
+
including the easily-missed foundation-model ARNs that cross-region
|
|
26
|
+
inference profiles require, and why a Kiro/Amazon Q role will not work.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- Bedrock failures no longer surface as raw botocore tracebacks. AccessDenied,
|
|
31
|
+
ResourceNotFound, Validation, Throttling, and expired-credential errors are
|
|
32
|
+
translated into actionable messages naming the model, region, role, and the
|
|
33
|
+
exact IAM action required. A denial mid-chat now returns to the prompt
|
|
34
|
+
instead of ending the session.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- Stale configuration pins no longer produce a broken sign-in: `haru login`
|
|
39
|
+
validates a pinned `role_name`/`account_id` against your actual Identity
|
|
40
|
+
Center assignments, warns, and falls back to the chooser when they do not
|
|
41
|
+
match (previously a leftover `role_name` from an older `config init` was
|
|
42
|
+
persisted unchecked and every later command failed).
|
|
43
|
+
- Role rejections are reported honestly: `AWS rejected role 'X' in account
|
|
44
|
+
'Y' … the role may not be assigned to you` instead of the misleading
|
|
45
|
+
"SSO credentials rejected; run 'haru login'", which is now reserved for
|
|
46
|
+
genuinely invalid tokens.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- CLI startup is roughly 6x faster (~0.6s to ~0.1s for `haru --version`):
|
|
51
|
+
strands, mcp, boto3, botocore, and opentelemetry are imported lazily at
|
|
52
|
+
their call sites, so `--help`, `--version`, `config`, `agents`, and
|
|
53
|
+
`session list` never load the agent/AWS stack. A guard test fails the
|
|
54
|
+
build if an eager import creeps back in.
|
|
55
|
+
- `haru login` and `haru config show` report where the account and role
|
|
56
|
+
came from (login selection, pinned in config, or an environment variable).
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
|
|
12
60
|
- The login callback now serves a proper local result page (dark themed,
|
|
13
61
|
approval/denial card): "Request approved" on success, "Request denied"
|
|
14
62
|
with the reason on error, missing parameters, or a state mismatch —
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: haru-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: Secure, scriptable CLI for Amazon Bedrock Claude models via the AWS Strands Agents SDK.
|
|
5
5
|
Author: haru-cli maintainers
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -56,6 +56,8 @@ uv sync
|
|
|
56
56
|
|
|
57
57
|
| Command | Purpose |
|
|
58
58
|
| -------------------------------- | ------------------------------------------------------------- |
|
|
59
|
+
| `haru doctor` | Diagnose config, sign-in, and Bedrock permissions |
|
|
60
|
+
| `haru doctor --all-roles` | Find which account+role can actually reach Bedrock |
|
|
59
61
|
| `haru config init` | Create a starter configuration (interactive) |
|
|
60
62
|
| `haru config show` | Show the resolved configuration (no secrets) |
|
|
61
63
|
| `haru login` | Browser sign-in to IAM Identity Center (OAuth 2.0 + PKCE) |
|
|
@@ -68,7 +70,9 @@ uv sync
|
|
|
68
70
|
| `haru --version` | Print the installed version |
|
|
69
71
|
|
|
70
72
|
Configuration is resolved from `--config`, then `$HARU_CONFIG`, then
|
|
71
|
-
`./config/haru.yaml`, then `~/.config/haru/haru.yaml`.
|
|
73
|
+
`./config/haru.yaml`, then `~/.config/haru/haru.yaml`. `haru --debug <command>`
|
|
74
|
+
turns on verbose logging, including the AWS API operations and error codes
|
|
75
|
+
behind a failure (never request bodies or credentials).
|
|
72
76
|
|
|
73
77
|
Inside `haru chat`, slash commands switch targets mid-session:
|
|
74
78
|
|
|
@@ -93,7 +97,15 @@ haru chat --agent supervisor --session-id demo
|
|
|
93
97
|
AWS accounts and permission-set roles are discovered automatically (like
|
|
94
98
|
`aws configure sso`), auto-selected when there is exactly one, prompted
|
|
95
99
|
otherwise, and remembered for later sessions. Pin `auth.sso.account_id` /
|
|
96
|
-
`role_name` in configuration only if you want to skip the discovery
|
|
100
|
+
`role_name` in configuration only if you want to skip the discovery — pins
|
|
101
|
+
are validated against your actual assignments, and a stale pin falls back to
|
|
102
|
+
the chooser with a warning rather than failing later.
|
|
103
|
+
|
|
104
|
+
The consent screen you see in the browser ("Allow haru-cli to access your
|
|
105
|
+
data?") is rendered by AWS IAM Identity Center, and its wording reflects the
|
|
106
|
+
scopes the client requests. haru requests only `sso:account:access`, so AWS
|
|
107
|
+
describes exactly that: the ability to assume roles you have been assigned.
|
|
108
|
+
Once you approve, haru serves a local result page confirming the outcome.
|
|
97
109
|
|
|
98
110
|
Authentication uses the same PKCE authorization-code flow as `aws sso login`,
|
|
99
111
|
caching tokens in the botocore-compatible schema under `~/.aws/sso/cache`
|
|
@@ -139,6 +151,27 @@ Declarative YAML under `config/` — models, agents, orchestration
|
|
|
139
151
|
logging, and OpenTelemetry. No secrets in YAML: values reference environment
|
|
140
152
|
variables with `${env:VAR}`. See [docs/configuration.md](docs/configuration.md).
|
|
141
153
|
|
|
154
|
+
## Troubleshooting
|
|
155
|
+
|
|
156
|
+
If `haru login` succeeds but `haru chat` fails, the role you signed in with
|
|
157
|
+
almost certainly lacks Bedrock permissions. Signing in and being allowed to
|
|
158
|
+
call Bedrock are separate things: haru assumes a role in **your** AWS account
|
|
159
|
+
and calls Bedrock there, so that role needs `bedrock:InvokeModel` and
|
|
160
|
+
`bedrock:InvokeModelWithResponseStream`.
|
|
161
|
+
|
|
162
|
+
Kiro and Amazon Q working is not evidence that haru will — they send your SSO
|
|
163
|
+
token to the managed Amazon Q service and never call Bedrock in your account,
|
|
164
|
+
so there is no Kiro role to copy.
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
haru doctor # what is configured, signed in, and permitted
|
|
168
|
+
haru doctor --all-roles # which of your assigned roles can reach Bedrock
|
|
169
|
+
haru doctor --invoke # definitive check (makes one billable call)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
See [docs/troubleshooting.md](docs/troubleshooting.md) for the IAM policy to
|
|
173
|
+
hand your AWS administrator.
|
|
174
|
+
|
|
142
175
|
## Development
|
|
143
176
|
|
|
144
177
|
All quality gates must pass before a change is considered done:
|
|
@@ -28,6 +28,8 @@ uv sync
|
|
|
28
28
|
|
|
29
29
|
| Command | Purpose |
|
|
30
30
|
| -------------------------------- | ------------------------------------------------------------- |
|
|
31
|
+
| `haru doctor` | Diagnose config, sign-in, and Bedrock permissions |
|
|
32
|
+
| `haru doctor --all-roles` | Find which account+role can actually reach Bedrock |
|
|
31
33
|
| `haru config init` | Create a starter configuration (interactive) |
|
|
32
34
|
| `haru config show` | Show the resolved configuration (no secrets) |
|
|
33
35
|
| `haru login` | Browser sign-in to IAM Identity Center (OAuth 2.0 + PKCE) |
|
|
@@ -40,7 +42,9 @@ uv sync
|
|
|
40
42
|
| `haru --version` | Print the installed version |
|
|
41
43
|
|
|
42
44
|
Configuration is resolved from `--config`, then `$HARU_CONFIG`, then
|
|
43
|
-
`./config/haru.yaml`, then `~/.config/haru/haru.yaml`.
|
|
45
|
+
`./config/haru.yaml`, then `~/.config/haru/haru.yaml`. `haru --debug <command>`
|
|
46
|
+
turns on verbose logging, including the AWS API operations and error codes
|
|
47
|
+
behind a failure (never request bodies or credentials).
|
|
44
48
|
|
|
45
49
|
Inside `haru chat`, slash commands switch targets mid-session:
|
|
46
50
|
|
|
@@ -65,7 +69,15 @@ haru chat --agent supervisor --session-id demo
|
|
|
65
69
|
AWS accounts and permission-set roles are discovered automatically (like
|
|
66
70
|
`aws configure sso`), auto-selected when there is exactly one, prompted
|
|
67
71
|
otherwise, and remembered for later sessions. Pin `auth.sso.account_id` /
|
|
68
|
-
`role_name` in configuration only if you want to skip the discovery
|
|
72
|
+
`role_name` in configuration only if you want to skip the discovery — pins
|
|
73
|
+
are validated against your actual assignments, and a stale pin falls back to
|
|
74
|
+
the chooser with a warning rather than failing later.
|
|
75
|
+
|
|
76
|
+
The consent screen you see in the browser ("Allow haru-cli to access your
|
|
77
|
+
data?") is rendered by AWS IAM Identity Center, and its wording reflects the
|
|
78
|
+
scopes the client requests. haru requests only `sso:account:access`, so AWS
|
|
79
|
+
describes exactly that: the ability to assume roles you have been assigned.
|
|
80
|
+
Once you approve, haru serves a local result page confirming the outcome.
|
|
69
81
|
|
|
70
82
|
Authentication uses the same PKCE authorization-code flow as `aws sso login`,
|
|
71
83
|
caching tokens in the botocore-compatible schema under `~/.aws/sso/cache`
|
|
@@ -111,6 +123,27 @@ Declarative YAML under `config/` — models, agents, orchestration
|
|
|
111
123
|
logging, and OpenTelemetry. No secrets in YAML: values reference environment
|
|
112
124
|
variables with `${env:VAR}`. See [docs/configuration.md](docs/configuration.md).
|
|
113
125
|
|
|
126
|
+
## Troubleshooting
|
|
127
|
+
|
|
128
|
+
If `haru login` succeeds but `haru chat` fails, the role you signed in with
|
|
129
|
+
almost certainly lacks Bedrock permissions. Signing in and being allowed to
|
|
130
|
+
call Bedrock are separate things: haru assumes a role in **your** AWS account
|
|
131
|
+
and calls Bedrock there, so that role needs `bedrock:InvokeModel` and
|
|
132
|
+
`bedrock:InvokeModelWithResponseStream`.
|
|
133
|
+
|
|
134
|
+
Kiro and Amazon Q working is not evidence that haru will — they send your SSO
|
|
135
|
+
token to the managed Amazon Q service and never call Bedrock in your account,
|
|
136
|
+
so there is no Kiro role to copy.
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
haru doctor # what is configured, signed in, and permitted
|
|
140
|
+
haru doctor --all-roles # which of your assigned roles can reach Bedrock
|
|
141
|
+
haru doctor --invoke # definitive check (makes one billable call)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
See [docs/troubleshooting.md](docs/troubleshooting.md) for the IAM policy to
|
|
145
|
+
hand your AWS administrator.
|
|
146
|
+
|
|
114
147
|
## Development
|
|
115
148
|
|
|
116
149
|
All quality gates must pass before a change is considered done:
|
|
@@ -50,6 +50,15 @@ configuration; `top_k` and `seed` travel via
|
|
|
50
50
|
non-default sampling values (HTTP 400), and no Claude model honours `seed` —
|
|
51
51
|
use these fields with models that accept them (for example Haiku 4.5).
|
|
52
52
|
|
|
53
|
+
## Logging
|
|
54
|
+
|
|
55
|
+
`logging.yaml` is honoured: `level` (DEBUG/INFO/WARNING/ERROR), `format`
|
|
56
|
+
(`json` or `text`), and `file` (a path also writes a 0600 log file). The
|
|
57
|
+
global `--debug` flag overrides the level and additionally raises AWS SDK
|
|
58
|
+
logging to INFO - operation names, endpoints, retries, and error codes, never
|
|
59
|
+
request bodies or headers. A redaction filter masks credential-shaped values
|
|
60
|
+
in all log output.
|
|
61
|
+
|
|
53
62
|
## Sessions
|
|
54
63
|
|
|
55
64
|
Conversation persistence is configured in the base file:
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
Start with `haru doctor`. It checks configuration, sign-in, and Bedrock
|
|
4
|
+
permissions, and prints what to fix.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
haru doctor # configuration, token, identity, Bedrock reachability
|
|
8
|
+
haru doctor --invoke # definitive: makes one real (billable) Bedrock call
|
|
9
|
+
haru doctor --all-roles # probe every account and role you are assigned
|
|
10
|
+
haru doctor --json # machine-readable, for a support ticket
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## "`haru login` works but `haru chat` fails"
|
|
14
|
+
|
|
15
|
+
This is the common case, and it is usually not a bug. **Signing in and being
|
|
16
|
+
allowed to call Bedrock are two different permissions.**
|
|
17
|
+
|
|
18
|
+
haru's path is: browser SSO → `sso:GetRoleCredentials` → an assumed role in
|
|
19
|
+
**your own AWS account** → `bedrock-runtime` Converse. Signing in proves only
|
|
20
|
+
that IAM Identity Center assigned you a role; calling Bedrock additionally
|
|
21
|
+
requires that role to hold `bedrock:InvokeModel` and
|
|
22
|
+
`bedrock:InvokeModelWithResponseStream`, and that Bedrock model access is
|
|
23
|
+
enabled for the Claude models in that account and region.
|
|
24
|
+
|
|
25
|
+
### Kiro or Amazon Q working tells you nothing about haru
|
|
26
|
+
|
|
27
|
+
Kiro CLI and Amazon Q authenticate through the same Identity Center, then send
|
|
28
|
+
the SSO token as a bearer credential to the **managed Amazon Q service**. They
|
|
29
|
+
never call Bedrock in your account, so they need no `bedrock:*` IAM
|
|
30
|
+
permissions at all — [Kiro's IAM documentation](https://kiro.dev/docs/cli/enterprise/iam/)
|
|
31
|
+
lists `sso:*`, `user-subscriptions:*`, and `q:*` actions and no
|
|
32
|
+
`bedrock:InvokeModel`.
|
|
33
|
+
|
|
34
|
+
Consequently an Amazon Q permission set (names like `AmazonQUsers`) is
|
|
35
|
+
**expected to fail** with haru, and there is no Kiro role to copy. Run
|
|
36
|
+
`haru doctor --all-roles` to find which of your assigned roles can actually
|
|
37
|
+
reach Bedrock:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
ACCOUNT NAME ROLE CREDS BEDROCK INVOKE
|
|
41
|
+
881490127383 amazonq-prod AmazonQUsers ok denied denied
|
|
42
|
+
111122223333 ml-sandbox BedrockDeveloper ok ok ok
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Then re-run `haru login` and choose the working combination, or pin it with
|
|
46
|
+
`auth.sso.account_id` and `auth.sso.role_name`.
|
|
47
|
+
|
|
48
|
+
## Policy to give your AWS administrator
|
|
49
|
+
|
|
50
|
+
Attach to the permission set you sign in with. **Both invoke statements are
|
|
51
|
+
required.** haru defaults to `us.` cross-region inference profiles, so Bedrock
|
|
52
|
+
authorizes against the underlying foundation-model ARN in *every region the
|
|
53
|
+
profile routes to* — a policy granting only the inference-profile ARN produces
|
|
54
|
+
an `AccessDeniedException` naming a foundation-model ARN you never configured.
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"Version": "2012-10-17",
|
|
59
|
+
"Statement": [
|
|
60
|
+
{
|
|
61
|
+
"Sid": "InvokeViaInferenceProfile",
|
|
62
|
+
"Effect": "Allow",
|
|
63
|
+
"Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
|
|
64
|
+
"Resource": "arn:aws:bedrock:us-east-1:ACCOUNT_ID:inference-profile/us.anthropic.claude-*"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"Sid": "InvokeUnderlyingFoundationModelsInEveryRoutedRegion",
|
|
68
|
+
"Effect": "Allow",
|
|
69
|
+
"Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
|
|
70
|
+
"Resource": [
|
|
71
|
+
"arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-*",
|
|
72
|
+
"arn:aws:bedrock:us-east-2::foundation-model/anthropic.claude-*",
|
|
73
|
+
"arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-*"
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"Sid": "GuardrailsWhenConfigured",
|
|
78
|
+
"Effect": "Allow",
|
|
79
|
+
"Action": "bedrock:ApplyGuardrail",
|
|
80
|
+
"Resource": "arn:aws:bedrock:us-east-1:ACCOUNT_ID:guardrail/*"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"Sid": "Diagnostics",
|
|
84
|
+
"Effect": "Allow",
|
|
85
|
+
"Action": ["bedrock:ListFoundationModels", "bedrock:GetInferenceProfile"],
|
|
86
|
+
"Resource": "*"
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
There is **no `bedrock:Converse` IAM action** — Converse is authorized by
|
|
93
|
+
`bedrock:InvokeModel` and ConverseStream by
|
|
94
|
+
`bedrock:InvokeModelWithResponseStream`. A policy naming `bedrock:Converse`
|
|
95
|
+
grants nothing.
|
|
96
|
+
|
|
97
|
+
`sts:GetCallerIdentity` and the `sso:*` calls need no policy; they are
|
|
98
|
+
authorized by the service itself. That is why `haru doctor` can show a valid
|
|
99
|
+
assumed-role ARN while Bedrock is still denied.
|
|
100
|
+
|
|
101
|
+
## Model access is separate from IAM
|
|
102
|
+
|
|
103
|
+
Even with the policy above, Bedrock refuses models that are not enabled for
|
|
104
|
+
the account. In the AWS console: **Bedrock → Model access → Manage model
|
|
105
|
+
access**, enable the Anthropic models, and confirm the region matches
|
|
106
|
+
`auth.bedrock_region` (or the model entry's `region`, which wins).
|
|
107
|
+
|
|
108
|
+
## Debug logging
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
haru --debug chat
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Raises haru's own logging to DEBUG and AWS SDK logging to INFO, showing the
|
|
115
|
+
API operations, endpoints, retries, and error codes behind a failure. Request
|
|
116
|
+
and response bodies and headers are never logged, and a redaction filter masks
|
|
117
|
+
credential-shaped values as defence in depth.
|
|
118
|
+
|
|
119
|
+
Set persistent logging in `logging.yaml`:
|
|
120
|
+
|
|
121
|
+
```yaml
|
|
122
|
+
logging:
|
|
123
|
+
level: INFO # DEBUG, INFO, WARNING, ERROR
|
|
124
|
+
format: json # json or text
|
|
125
|
+
file: null # a path here writes a 0600 log file as well as stderr
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Other failures
|
|
129
|
+
|
|
130
|
+
| Symptom | Cause and fix |
|
|
131
|
+
| --- | --- |
|
|
132
|
+
| `No configuration found` | Run `haru config init`, or pass `--config`, or set `$HARU_CONFIG`. |
|
|
133
|
+
| `No cached SSO token` | Run `haru login`. |
|
|
134
|
+
| `AWS rejected role 'X' in account 'Y'` | The role is not assigned to you, or a stale pin is in your config. Run `haru doctor --all-roles`. |
|
|
135
|
+
| `Guardrails are enabled but no guardrail_id` | Set `guardrails.guardrail_id`, or `enabled: false`. Guardrails fail closed by design. |
|
|
136
|
+
| Bedrock rejects sampling parameters | Claude 5-series and Opus 4.7+ reject non-default `temperature`/`top_p`/`top_k`. Leave them unset for those models. |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "haru-cli"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.6"
|
|
4
4
|
description = "Secure, scriptable CLI for Amazon Bedrock Claude models via the AWS Strands Agents SDK."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -61,8 +61,21 @@ select = ["E", "F", "I", "N", "UP", "B", "SIM", "PL", "RUF", "S"]
|
|
|
61
61
|
|
|
62
62
|
[tool.ruff.lint.per-file-ignores]
|
|
63
63
|
# S101: pytest uses bare asserts. S105/S106/S107: tests exercise secret
|
|
64
|
-
# handling with deliberately fake credential strings.
|
|
65
|
-
|
|
64
|
+
# handling with deliberately fake credential strings. S603: the import-time
|
|
65
|
+
# guard runs the interpreter in a subprocess with a fixed argv.
|
|
66
|
+
"tests/**" = ["S101", "PLR2004", "S105", "S106", "S107", "S603"]
|
|
67
|
+
# PLC0415: heavy third-party imports (strands, mcp, boto3, opentelemetry)
|
|
68
|
+
# are deliberately deferred to call sites so `haru --help` stays fast; the
|
|
69
|
+
# guard test tests/unit/test_import_time.py enforces this.
|
|
70
|
+
"src/haru/agents/factory.py" = ["PLC0415"]
|
|
71
|
+
"src/haru/auth/*" = ["PLC0415"]
|
|
72
|
+
"src/haru/commands/doctor.py" = ["PLC0415"]
|
|
73
|
+
"src/haru/commands/streaming.py" = ["PLC0415"]
|
|
74
|
+
"src/haru/diagnostics/*" = ["PLC0415"]
|
|
75
|
+
"src/haru/models/bedrock.py" = ["PLC0415"]
|
|
76
|
+
"src/haru/observability/telemetry.py" = ["PLC0415"]
|
|
77
|
+
"src/haru/sessions/manager.py" = ["PLC0415"]
|
|
78
|
+
"src/haru/tools/mcp.py" = ["PLC0415"]
|
|
66
79
|
|
|
67
80
|
[tool.ruff.lint.isort]
|
|
68
81
|
known-first-party = ["haru"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Agent factories and multi-agent orchestration."""
|
|
@@ -5,15 +5,9 @@ tools, and MCP tools. Multi-agent orchestration composes these in
|
|
|
5
5
|
``haru.agents.orchestration``.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from typing import Any
|
|
8
|
+
from __future__ import annotations
|
|
11
9
|
|
|
12
|
-
import
|
|
13
|
-
from strands import Agent
|
|
14
|
-
from strands.models import BedrockModel
|
|
15
|
-
from strands.session.session_manager import SessionManager
|
|
16
|
-
from strands.tools.mcp import MCPClient
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
17
11
|
|
|
18
12
|
from haru.config.schema import HaruConfig, SamplingConfig
|
|
19
13
|
from haru.errors import ConfigError
|
|
@@ -21,6 +15,16 @@ from haru.models.bedrock import build_model, get_model_config, merge_sampling
|
|
|
21
15
|
from haru.steering.prompts import DEFAULT_PROMPTS_ROOT, load_prompts, resolve_prompt
|
|
22
16
|
from haru.tools.mcp import collect_tools
|
|
23
17
|
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from collections.abc import Mapping
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
import boto3
|
|
23
|
+
from strands import Agent
|
|
24
|
+
from strands.models import BedrockModel
|
|
25
|
+
from strands.session.session_manager import SessionManager
|
|
26
|
+
from strands.tools.mcp import MCPClient
|
|
27
|
+
|
|
24
28
|
|
|
25
29
|
def build_agent( # noqa: PLR0913 - keyword-only wiring points, all optional
|
|
26
30
|
config: HaruConfig,
|
|
@@ -41,6 +45,8 @@ def build_agent( # noqa: PLR0913 - keyword-only wiring points, all optional
|
|
|
41
45
|
model entry). Raises ConfigError for unknown agents, prompt references,
|
|
42
46
|
tools, or an invalid override.
|
|
43
47
|
"""
|
|
48
|
+
from strands import Agent
|
|
49
|
+
|
|
44
50
|
if agent_name is None:
|
|
45
51
|
model_cfg = get_model_config(config, model_name)
|
|
46
52
|
model = build_model(model_cfg, session, guardrails=config.guardrails, sampling=sampling)
|
|
@@ -4,8 +4,14 @@ Kiro-style browser sign-in needs no upfront AWS knowledge: after the token
|
|
|
4
4
|
exchange, the accessible accounts and permission-set roles are listed from
|
|
5
5
|
IAM Identity Center (as ``aws configure sso`` does), auto-selected when there
|
|
6
6
|
is exactly one, and persisted next to the token cache for later sessions.
|
|
7
|
+
|
|
8
|
+
Configuration pins (``account_id``/``role_name``) are validated against the
|
|
9
|
+
user's actual assignments; a stale pin produces a warning and falls back to
|
|
10
|
+
the chooser instead of persisting a broken identity.
|
|
7
11
|
"""
|
|
8
12
|
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
9
15
|
import json
|
|
10
16
|
import os
|
|
11
17
|
from collections.abc import Callable
|
|
@@ -13,9 +19,6 @@ from dataclasses import dataclass
|
|
|
13
19
|
from pathlib import Path
|
|
14
20
|
from typing import Any
|
|
15
21
|
|
|
16
|
-
import boto3
|
|
17
|
-
from botocore.exceptions import ClientError
|
|
18
|
-
|
|
19
22
|
from haru.auth.cache import cache_path
|
|
20
23
|
from haru.auth.sso import SsoToken
|
|
21
24
|
from haru.config.schema import SsoConfig
|
|
@@ -24,6 +27,12 @@ from haru.errors import AuthError
|
|
|
24
27
|
Chooser = Callable[[str, list[str]], int]
|
|
25
28
|
"""Callback picking one option: (question, labels) -> chosen index."""
|
|
26
29
|
|
|
30
|
+
Notify = Callable[[str], None]
|
|
31
|
+
"""Callback for user-facing warnings during selection."""
|
|
32
|
+
|
|
33
|
+
SOURCE_SELECTED = "login selection"
|
|
34
|
+
SOURCE_CONFIG = "pinned in config"
|
|
35
|
+
|
|
27
36
|
|
|
28
37
|
@dataclass(frozen=True)
|
|
29
38
|
class SelectedIdentity:
|
|
@@ -32,6 +41,8 @@ class SelectedIdentity:
|
|
|
32
41
|
account_id: str
|
|
33
42
|
role_name: str
|
|
34
43
|
account_name: str | None = None
|
|
44
|
+
account_source: str = SOURCE_SELECTED
|
|
45
|
+
role_source: str = SOURCE_SELECTED
|
|
35
46
|
|
|
36
47
|
|
|
37
48
|
def identity_path(start_url: str, cache_dir: Path | None = None) -> Path:
|
|
@@ -72,57 +83,121 @@ def read_identity(start_url: str, cache_dir: Path | None = None) -> SelectedIden
|
|
|
72
83
|
return None
|
|
73
84
|
|
|
74
85
|
|
|
75
|
-
def
|
|
86
|
+
def effective_identity(
|
|
87
|
+
sso_cfg: SsoConfig, cache_dir: Path | None = None
|
|
88
|
+
) -> tuple[str | None, str, str | None, str]:
|
|
89
|
+
"""Resolve the effective account and role with their provenance.
|
|
90
|
+
|
|
91
|
+
Returns ``(account_id, account_source, role_name, role_source)``;
|
|
92
|
+
values are None (source ``"unset"``) when nothing provides them.
|
|
93
|
+
Precedence per field: config pin, then ``account_id_env``, then the
|
|
94
|
+
identity persisted at login.
|
|
95
|
+
"""
|
|
96
|
+
stored = read_identity(sso_cfg.start_url, cache_dir)
|
|
97
|
+
|
|
98
|
+
if sso_cfg.account_id is not None:
|
|
99
|
+
account, account_source = sso_cfg.account_id, SOURCE_CONFIG
|
|
100
|
+
elif sso_cfg.account_id_env is not None and os.environ.get(sso_cfg.account_id_env):
|
|
101
|
+
account = os.environ[sso_cfg.account_id_env]
|
|
102
|
+
account_source = f"from ${sso_cfg.account_id_env}"
|
|
103
|
+
elif stored is not None:
|
|
104
|
+
account, account_source = stored.account_id, SOURCE_SELECTED
|
|
105
|
+
else:
|
|
106
|
+
account, account_source = None, "unset"
|
|
107
|
+
|
|
108
|
+
if sso_cfg.role_name is not None:
|
|
109
|
+
role, role_source = sso_cfg.role_name, SOURCE_CONFIG
|
|
110
|
+
elif stored is not None:
|
|
111
|
+
role, role_source = stored.role_name, SOURCE_SELECTED
|
|
112
|
+
else:
|
|
113
|
+
role, role_source = None, "unset"
|
|
114
|
+
|
|
115
|
+
return account, account_source, role, role_source
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def select_identity( # noqa: PLR0913 - keyword-only wiring points, all optional
|
|
76
119
|
sso_cfg: SsoConfig,
|
|
77
120
|
token: SsoToken,
|
|
78
121
|
*,
|
|
79
122
|
sso_client: Any | None = None,
|
|
80
123
|
cache_dir: Path | None = None,
|
|
81
124
|
chooser: Chooser | None = None,
|
|
125
|
+
notify: Notify | None = None,
|
|
82
126
|
) -> SelectedIdentity:
|
|
83
127
|
"""Resolve the account and role for this sign-in and persist the choice.
|
|
84
128
|
|
|
85
|
-
Configuration pins
|
|
86
|
-
|
|
87
|
-
|
|
129
|
+
Configuration pins are honoured only when they match the user's actual
|
|
130
|
+
assignments; a stale pin triggers a ``notify`` warning and falls back to
|
|
131
|
+
discovery. With multiple choices and no ``chooser``, raises AuthError.
|
|
88
132
|
"""
|
|
89
133
|
if sso_client is None:
|
|
90
|
-
|
|
134
|
+
import boto3
|
|
91
135
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
136
|
+
sso_client = boto3.Session().client("sso", region_name=sso_cfg.sso_region)
|
|
137
|
+
warn = notify if notify is not None else _ignore_warning
|
|
138
|
+
|
|
139
|
+
accounts = list_accounts(sso_client, token.access_token)
|
|
140
|
+
pinned_account, pin_source = _pinned_account(sso_cfg)
|
|
141
|
+
account_source = SOURCE_SELECTED
|
|
142
|
+
chosen = next((account for account in accounts if account["accountId"] == pinned_account), None)
|
|
143
|
+
if chosen is not None:
|
|
144
|
+
account_source = pin_source
|
|
145
|
+
else:
|
|
146
|
+
if pinned_account is not None:
|
|
147
|
+
warn(
|
|
148
|
+
f"Warning: configured account {pinned_account} is not accessible with"
|
|
149
|
+
" this sign-in; choose from your accessible accounts."
|
|
150
|
+
)
|
|
96
151
|
chosen = _choose(
|
|
97
152
|
"Choose the AWS account to use",
|
|
98
153
|
accounts,
|
|
99
154
|
lambda account: f"{account['accountId']} {account.get('accountName', '')}".strip(),
|
|
100
155
|
chooser,
|
|
101
156
|
)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
157
|
+
account_id: str = chosen["accountId"]
|
|
158
|
+
account_name: str | None = chosen.get("accountName")
|
|
159
|
+
|
|
160
|
+
roles = list_roles(sso_client, token.access_token, account_id)
|
|
161
|
+
role_source = SOURCE_SELECTED
|
|
162
|
+
if sso_cfg.role_name is not None and sso_cfg.role_name in roles:
|
|
163
|
+
role_name = sso_cfg.role_name
|
|
164
|
+
role_source = SOURCE_CONFIG
|
|
165
|
+
else:
|
|
166
|
+
if sso_cfg.role_name is not None:
|
|
167
|
+
warn(
|
|
168
|
+
f"Warning: configured role {sso_cfg.role_name!r} is not assigned to you"
|
|
169
|
+
f" in account {account_id}; choose from your assigned roles."
|
|
170
|
+
)
|
|
108
171
|
role_name = _choose(f"Choose the role for account {account_id}", roles, str, chooser)
|
|
109
172
|
|
|
110
173
|
identity = SelectedIdentity(
|
|
111
|
-
account_id=account_id,
|
|
174
|
+
account_id=account_id,
|
|
175
|
+
role_name=role_name,
|
|
176
|
+
account_name=account_name,
|
|
177
|
+
account_source=account_source,
|
|
178
|
+
role_source=role_source,
|
|
112
179
|
)
|
|
113
180
|
write_identity(identity, sso_cfg.start_url, cache_dir)
|
|
114
181
|
return identity
|
|
115
182
|
|
|
116
183
|
|
|
117
|
-
def
|
|
184
|
+
def _ignore_warning(message: str) -> None:
|
|
185
|
+
"""Default notify callback: drop the warning (non-interactive callers)."""
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _pinned_account(sso_cfg: SsoConfig) -> tuple[str | None, str]:
|
|
118
189
|
if sso_cfg.account_id is not None:
|
|
119
|
-
return sso_cfg.account_id
|
|
190
|
+
return sso_cfg.account_id, SOURCE_CONFIG
|
|
120
191
|
if sso_cfg.account_id_env is not None:
|
|
121
|
-
|
|
122
|
-
|
|
192
|
+
value = os.environ.get(sso_cfg.account_id_env)
|
|
193
|
+
if value:
|
|
194
|
+
return value, f"from ${sso_cfg.account_id_env}"
|
|
195
|
+
return None, ""
|
|
123
196
|
|
|
124
197
|
|
|
125
|
-
def
|
|
198
|
+
def list_accounts(sso_client: Any, access_token: str) -> list[dict[str, Any]]:
|
|
199
|
+
from botocore.exceptions import ClientError
|
|
200
|
+
|
|
126
201
|
try:
|
|
127
202
|
paginator = sso_client.get_paginator("list_accounts")
|
|
128
203
|
accounts = [
|
|
@@ -137,7 +212,9 @@ def _list_accounts(sso_client: Any, access_token: str) -> list[dict[str, Any]]:
|
|
|
137
212
|
return sorted(accounts, key=lambda account: str(account["accountId"]))
|
|
138
213
|
|
|
139
214
|
|
|
140
|
-
def
|
|
215
|
+
def list_roles(sso_client: Any, access_token: str, account_id: str) -> list[str]:
|
|
216
|
+
from botocore.exceptions import ClientError
|
|
217
|
+
|
|
141
218
|
try:
|
|
142
219
|
paginator = sso_client.get_paginator("list_account_roles")
|
|
143
220
|
roles = [
|