claude-aws-mfa 1.3.0 → 1.4.0
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.
- package/CHANGELOG.md +9 -0
- package/README.md +32 -0
- package/bun.lock +31 -131
- package/package.json +5 -3
- package/src/flags.ts +48 -0
- package/src/index.ts +36 -9
- package/src/usage.ts +226 -0
- package/test/index.test.ts +6 -0
- package/test/usage.test.ts +115 -0
package/CHANGELOG.md
CHANGED
|
@@ -22,6 +22,15 @@ Releases are automated via CI. To create a new release:
|
|
|
22
22
|
Stable versions (e.g. `v1.2.0`) publish to npm `@latest`. Pre-release versions
|
|
23
23
|
(e.g. `v1.2.0-rc.1`) publish to npm `@next` and create a GitHub pre-release.
|
|
24
24
|
|
|
25
|
+
## [v1.4.0] - 2026-10-19
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
- `--help` / `-h` prints a summary of the CLI options.
|
|
29
|
+
- `--usage[=[<role>/]<session>]` prints Bedrock token usage from model invocation logs for a session name (default: the one in the config), optionally under one role, or itemized per role or session with `*` (`--usage='my-role/*'`, `--usage='*'`), with `--from=`/`--to=` for the time range (default: current month) and `--profile=` to query with a different AWS profile. Each model's estimated cost is shown, at current AWS list prices.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- Updated `@aws-sdk/client-sts` to 3.1152 (shared with the new Bedrock and CloudWatch Logs clients) and `@types/bun` to 1.4.3.
|
|
33
|
+
|
|
25
34
|
## [v1.3.0] - 2026-10-14
|
|
26
35
|
|
|
27
36
|
### Added
|
package/README.md
CHANGED
|
@@ -39,9 +39,13 @@ Add this (plus any addtitional config you prefer) to your Claude Code settings (
|
|
|
39
39
|
| Flag | Description |
|
|
40
40
|
|------|-------------|
|
|
41
41
|
| `--setup` | Launch the guided setup wizard |
|
|
42
|
+
| `-h`, `--help` | Show usage help |
|
|
42
43
|
| `--cache-session` | Cache STS session credentials and reuse them until they expire (default: on) |
|
|
43
44
|
| `--auto-mfa` | Automatically run the configured MFA command and obtain credentials without showing the dialog, requires MFA Command mode (default: on) |
|
|
44
45
|
| `--single-instance-lock` | Prevent multiple concurrent instances from showing overlapping dialogs; subsequent invocations wait for the first to finish and reuse its cached session (default: on) |
|
|
46
|
+
| `--usage[=[<role>/]<session>]` | Print Bedrock token usage for a session name (default: the one in the config), optionally under one role; `*` itemizes instead of credentials (see [Usage report](#usage-report)) |
|
|
47
|
+
| `--profile=<name>` | With `--usage`: query using this AWS profile instead of the tool's own credentials |
|
|
48
|
+
| `--from=<date>` / `--to=<date>` | With `--usage`: time range (default: start of the current month to now; `--to` is exclusive) |
|
|
45
49
|
| `--no-ui` | Never show any GUI dialog. Prints credentials JSON to stdout on success, or an error to stderr on failure — useful for scripting or letting an LLM drive the tool during setup/debugging |
|
|
46
50
|
|
|
47
51
|
## How it works
|
|
@@ -91,6 +95,34 @@ Claude Code does not surface a failing command's stderr to the user, so when MFA
|
|
|
91
95
|
|
|
92
96
|
With `--no-ui`, no GUI is ever shown — not even the error dialog above — and the tool never imports the GUI toolkit, so it also works in headless environments without a display. On success, credentials are printed to stdout as usual, keeping stdout cleanly parsable. On failure, an error message is printed to **stderr** instead (e.g. `Missing config: roleArn, mfaCommand` if the config is incomplete, or the underlying STS error if authentication itself failed) and the process exits non-zero. This is intended for scripting and for letting an LLM drive the tool directly while setting up or debugging a configuration — full non-interactive operation requires **MFA Command mode**, since a static "code" mode value can't be re-entered headlessly. The command doesn't need to be a real TOTP generator: for a one-off test or debugging run, `mfaCommand` can simply be `echo <code>` with a fresh, not-yet-used code from your authenticator — it only needs to produce a valid code once.
|
|
93
97
|
|
|
98
|
+
### Usage report
|
|
99
|
+
|
|
100
|
+
`claude-aws-mfa --usage` prints per-model request and token counts (input, cache read/write, output), with an estimated cost, for every Bedrock call made under the configured STS session name.
|
|
101
|
+
|
|
102
|
+
`--usage=` takes `[<role>/]<session>` to choose whose usage to report. Either part can be `*`, which gives one row per role or session (all models merged) instead of one row per model:
|
|
103
|
+
|
|
104
|
+
| Value | Reports |
|
|
105
|
+
|-------|---------|
|
|
106
|
+
| *(none)* | The session name from the config, any role, per model |
|
|
107
|
+
| `claude-aws-mfa` | That session name under any role, per model |
|
|
108
|
+
| `my-role/claude-aws-mfa` | That session of role `my-role`, per model |
|
|
109
|
+
| `my-role/*` | Each session of role `my-role` |
|
|
110
|
+
| `*` | Each role (its sessions merged) |
|
|
111
|
+
| `*/*` | Each role and session pair |
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
claude-aws-mfa --usage # this month, using the tool's own role session
|
|
115
|
+
claude-aws-mfa --usage --from=2026-09-01 --to=2026-10-01 # September
|
|
116
|
+
claude-aws-mfa --usage --profile=billing # query with a different AWS profile
|
|
117
|
+
claude-aws-mfa --usage='my-role/*' # quote * so the shell doesn't expand it
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Only callers that assumed a role are counted; calls made directly with IAM user keys never match.
|
|
121
|
+
|
|
122
|
+
The data comes from Bedrock [model invocation logs](https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html), so invocation logging to CloudWatch Logs must be enabled in the account and region. The querying credentials need `bedrock:GetModelInvocationLoggingConfiguration`, `logs:StartQuery` and `logs:GetQueryResults`. Without `--profile`, credentials are obtained exactly as for Claude Code (cached session, auto-MFA, or the dialog). Errors go to stderr.
|
|
123
|
+
|
|
124
|
+
Costs are estimated from AWS's public [Price List](https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/price-changes.html) for the region (no extra permissions needed), at current base on-demand rates — `us.`/`eu.` cross-region profiles at their geo price, `global.` at the global price. They exclude negotiated discounts and the long-context and 1-hour-cache-write surcharges, which the aggregated logs can't distinguish, so treat them as an estimate rather than your bill. If prices can't be fetched, token counts are still printed.
|
|
125
|
+
|
|
94
126
|
## System requirements
|
|
95
127
|
|
|
96
128
|
The GUI dialog uses [webview-bun](https://github.com/tr1ckydev/webview-bun), which requires:
|
package/bun.lock
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
"": {
|
|
6
6
|
"name": "claude-bedrock-mfa",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@aws-sdk/client-
|
|
8
|
+
"@aws-sdk/client-bedrock": "^3.1152.0",
|
|
9
|
+
"@aws-sdk/client-cloudwatch-logs": "^3.1152.0",
|
|
10
|
+
"@aws-sdk/client-sts": "^3.1152.0",
|
|
9
11
|
"webview-bun": "^2.4.0",
|
|
10
12
|
},
|
|
11
13
|
"devDependencies": {
|
|
12
|
-
"@types/bun": "
|
|
14
|
+
"@types/bun": "^1.4.3",
|
|
13
15
|
"node-screenshots": "^0.2.8",
|
|
14
16
|
},
|
|
15
17
|
"peerDependencies": {
|
|
@@ -18,151 +20,61 @@
|
|
|
18
20
|
},
|
|
19
21
|
},
|
|
20
22
|
"packages": {
|
|
21
|
-
"@aws-
|
|
23
|
+
"@aws-sdk/client-bedrock": ["@aws-sdk/client-bedrock@3.1152.0", "", { "dependencies": { "@aws-sdk/core": "^3.978.3", "@aws-sdk/credential-provider-node": "^3.972.86", "@aws-sdk/token-providers": "3.1152.0", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.2", "@smithy/fetch-http-handler": "^5.8.0", "@smithy/node-http-handler": "^4.12.1", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-mZQk6H/Gt34V5LWDJfSf4Ar/FMaUrGwu9TOvstKP7GNo81PJJOkgS+ICjhoV3YX/AZXjZD8mNCp+MYynRitRag=="],
|
|
22
24
|
|
|
23
|
-
"@aws-
|
|
25
|
+
"@aws-sdk/client-cloudwatch-logs": ["@aws-sdk/client-cloudwatch-logs@3.1152.0", "", { "dependencies": { "@aws-sdk/core": "^3.978.3", "@aws-sdk/credential-provider-node": "^3.972.86", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.2", "@smithy/fetch-http-handler": "^5.8.0", "@smithy/node-http-handler": "^4.12.1", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-626LS/9YffzAUH68bvnyNwXo/WYtShhs7+X6kEH5ARTom1sSk3hHJmR9F+1zc50mHSfJNwYEbUNDmgC5vW4/Xg=="],
|
|
24
26
|
|
|
25
|
-
"@aws-
|
|
27
|
+
"@aws-sdk/client-sts": ["@aws-sdk/client-sts@3.1152.0", "", { "dependencies": { "@aws-sdk/core": "^3.978.3", "@aws-sdk/credential-provider-node": "^3.972.86", "@aws-sdk/signature-v4-multi-region": "^3.996.48", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.2", "@smithy/fetch-http-handler": "^5.8.0", "@smithy/node-http-handler": "^4.12.1", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-dLvk02UzZPRTeLLh8C86OpJBRzsJZ+fSxGqqdzoWOXACwZOf3xH6d4rQn54eZNPKYWaQNlt4VmfA5mZsfItbgw=="],
|
|
26
28
|
|
|
27
|
-
"@aws-
|
|
29
|
+
"@aws-sdk/core": ["@aws-sdk/core@3.978.3", "", { "dependencies": { "@aws-sdk/types": "^3.974.6", "@aws-sdk/xml-builder": "^3.972.41", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.35.2", "@smithy/signature-v4": "^5.7.4", "@smithy/types": "^4.19.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-nslpzUCTI9b2IQMWSd6mSAXldBexPc8rr0d3dXUutXYFLFaTdsHa2GD7sJdsn3PhyjK0O5K8ypd6zi+RXXPVlg=="],
|
|
28
30
|
|
|
29
|
-
"@aws-sdk/
|
|
31
|
+
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.74", "", { "dependencies": { "@aws-sdk/core": "^3.978.3", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.2", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-rsRJcW2iFUhCgclyD6OSbMEaPl8Eu0qSeRZb2uuCK1ViZ66hl5wOe/p6Vr6MO3b+ydyKgFeqza2p6tZThvILlw=="],
|
|
30
32
|
|
|
31
|
-
"@aws-sdk/
|
|
33
|
+
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.76", "", { "dependencies": { "@aws-sdk/core": "^3.978.3", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.2", "@smithy/fetch-http-handler": "^5.8.0", "@smithy/node-http-handler": "^4.12.1", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-pnKdh0Br7OJPj+7qKjzgTfDTFW0RhEMKXNDR2jv9CAM4oHuG/4z3hRh8KtutLfpmItuRtDbKBmwWkDqHwwAWXw=="],
|
|
32
34
|
|
|
33
|
-
"@aws-sdk/credential-provider-
|
|
35
|
+
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.973.19", "", { "dependencies": { "@aws-sdk/core": "^3.978.3", "@aws-sdk/credential-provider-env": "^3.972.74", "@aws-sdk/credential-provider-http": "^3.972.76", "@aws-sdk/credential-provider-login": "^3.972.81", "@aws-sdk/credential-provider-process": "^3.972.74", "@aws-sdk/credential-provider-sso": "^3.973.18", "@aws-sdk/credential-provider-web-identity": "^3.972.80", "@aws-sdk/nested-clients": "^3.997.48", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.2", "@smithy/credential-provider-imds": "^4.5.2", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-kcjvAxqQEV8yuwkX58RelNn3ne65o7JgRWf3Z/gVvbi7Xuw1qSDLJlatrLD76+40cmRxmNkLxBoyp/vAQChQ5g=="],
|
|
34
36
|
|
|
35
|
-
"@aws-sdk/credential-provider-
|
|
37
|
+
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.81", "", { "dependencies": { "@aws-sdk/core": "^3.978.3", "@aws-sdk/nested-clients": "^3.997.48", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.2", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-CeQHSbE2CnVdaHIJ/NayRaCecIER+Pm9faUPkmRATO54rCoMTEPSbVieGHzEwvm1dZM2HIs6qQZCznhayNTWJQ=="],
|
|
36
38
|
|
|
37
|
-
"@aws-sdk/credential-provider-
|
|
39
|
+
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.86", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.74", "@aws-sdk/credential-provider-http": "^3.972.76", "@aws-sdk/credential-provider-ini": "^3.973.19", "@aws-sdk/credential-provider-process": "^3.972.74", "@aws-sdk/credential-provider-sso": "^3.973.18", "@aws-sdk/credential-provider-web-identity": "^3.972.80", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.2", "@smithy/credential-provider-imds": "^4.5.2", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-WNttSyr6e8innPtBSty//CdnqJcgZN6eAbtw3v/CR2IObz6MYZEo8eusdtuCHJ2ujwFqC2bSPHVyhXGHx8HruQ=="],
|
|
38
40
|
|
|
39
|
-
"@aws-sdk/credential-provider-
|
|
41
|
+
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.74", "", { "dependencies": { "@aws-sdk/core": "^3.978.3", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.2", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-Yj6kZFJQdbR+PygTkqdVLPHHpdDpOS9GCYVcDCuweCRRig9LF24ELQBMfyUw1vspvrZJUfnOGVyMjhaL094ahw=="],
|
|
40
42
|
|
|
41
|
-
"@aws-sdk/credential-provider-
|
|
43
|
+
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.973.18", "", { "dependencies": { "@aws-sdk/core": "^3.978.3", "@aws-sdk/nested-clients": "^3.997.48", "@aws-sdk/token-providers": "3.1150.0", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.2", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-FnpPhdY4qUGtX/8X6eKxh+uD+9Y19i9FYibgk4SBe0M935s9vocHkHzaxaMNk+YkHmvzgdu8cHA5SCoeU+kkew=="],
|
|
42
44
|
|
|
43
|
-
"@aws-sdk/credential-provider-
|
|
45
|
+
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.80", "", { "dependencies": { "@aws-sdk/core": "^3.978.3", "@aws-sdk/nested-clients": "^3.997.48", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.2", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-TJF25Ndwy2HxcVj6oxMgp8qVlJAawlExHYBXqhuC6Zt2m+A7WpdkjBD6QglNbiArlteYljZHEY3R7KKmno6ISA=="],
|
|
44
46
|
|
|
45
|
-
"@aws-sdk/
|
|
47
|
+
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.48", "", { "dependencies": { "@aws-sdk/core": "^3.978.3", "@aws-sdk/signature-v4-multi-region": "^3.996.48", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.2", "@smithy/fetch-http-handler": "^5.8.0", "@smithy/node-http-handler": "^4.12.1", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-B01jdXJtAW/XOiUzGdI2mz67WyEHbGEc9X6Euzw3bjLAa+txTqdZDER4X2FsIEcXzV2ABqudVZ5thali8tCCzQ=="],
|
|
46
48
|
|
|
47
|
-
"@aws-sdk/
|
|
49
|
+
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.48", "", { "dependencies": { "@aws-sdk/types": "^3.974.6", "@smithy/signature-v4": "^5.7.4", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-cFDXfEbXaGwbnpvR/DyqfxfjxpjXVRVthQLgkwvcfDS5u3+8G9WSj1X1Ag/pTRg/QOeOno1Tq3ycEdrKsCUcOA=="],
|
|
48
50
|
|
|
49
|
-
"@aws-sdk/
|
|
51
|
+
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1152.0", "", { "dependencies": { "@aws-sdk/core": "^3.978.3", "@aws-sdk/nested-clients": "^3.997.48", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.2", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-XcQlQFkIDOk4P1dF/ADQAjPK361mBChR/AJghM4my6SgDnsOX77Z7zCUtZfXXbQG4E+vpoWfbySzX/oKXcmV5w=="],
|
|
50
52
|
|
|
51
|
-
"@aws-sdk/
|
|
53
|
+
"@aws-sdk/types": ["@aws-sdk/types@3.974.6", "", { "dependencies": { "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-v/clNZzZnDxGyvpHMOGpJKVXFAExJzUNAAjaWGdcx8QAcXLGwTaOkw33p5SHAi0YAioK32xB3hWwOekRVfmfKg=="],
|
|
52
54
|
|
|
53
|
-
"@aws-sdk/
|
|
55
|
+
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.41", "", { "dependencies": { "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-ctjVSyCMegrWfXlx6VqzSBFI6UqmQ5ZlnfMhdLIiWmhoH8UAQxSCP5N3OpG7X3k4LnS7ou74C4mt20+bfTW2aQ=="],
|
|
54
56
|
|
|
55
|
-
"@aws
|
|
57
|
+
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
|
|
56
58
|
|
|
57
|
-
"@
|
|
59
|
+
"@smithy/core": ["@smithy/core@3.36.0", "", { "dependencies": { "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-3hJiwG1F5t1SoXqp/yOCmYXziyx7ZVqF63S5W6YU201e/qwVaHPex+E/3vvyY9a3ORq/b+BydeNZpP2saQ9X2g=="],
|
|
58
60
|
|
|
59
|
-
"@
|
|
61
|
+
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.5.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg=="],
|
|
60
62
|
|
|
61
|
-
"@
|
|
63
|
+
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.8.0", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA=="],
|
|
62
64
|
|
|
63
|
-
"@
|
|
65
|
+
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.12.1", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw=="],
|
|
64
66
|
|
|
65
|
-
"@
|
|
67
|
+
"@smithy/signature-v4": ["@smithy/signature-v4@5.7.4", "", { "dependencies": { "@smithy/core": "^3.35.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-tHy0K0VtqNd5Y7Y41h0a0Lhh0L1GzC08dTWg0F7vRJWFtTENg7IZikf3wQkanYIRdb7ngoIPMTmqgUi401fEeQ=="],
|
|
66
68
|
|
|
67
|
-
"@
|
|
69
|
+
"@smithy/types": ["@smithy/types@4.19.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q=="],
|
|
68
70
|
|
|
69
|
-
"@
|
|
70
|
-
|
|
71
|
-
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.972.13", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.14", "@aws-sdk/types": "^3.973.3", "@smithy/node-config-provider": "^4.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-PHErmuu+v6iAST48zcsB2cYwDKW45gk6qCp49t1p0NGZ4EaFPr/tA5jl0X/ekDwvWbuT0LTj++fjjdVQAbuh0Q=="],
|
|
72
|
-
|
|
73
|
-
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.7", "", { "dependencies": { "@smithy/types": "^4.13.0", "fast-xml-parser": "5.3.6", "tslib": "^2.6.2" } }, "sha512-9GF86s6mHuc1TYCbuKatMDWl2PyK3KIkpRaI7ul2/gYZPfaLzKZ+ISHhxzVb9KVeakf75tUQe6CXW2gugSCXNw=="],
|
|
74
|
-
|
|
75
|
-
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.3", "", {}, "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw=="],
|
|
76
|
-
|
|
77
|
-
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-qocxM/X4XGATqQtUkbE9SPUB6wekBi+FyJOMbPj0AhvyvFGYEmOlz6VB22iMePCQsFmMIvFSeViDvA7mZJG47g=="],
|
|
78
|
-
|
|
79
|
-
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.9", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.10", "@smithy/types": "^4.13.0", "@smithy/util-config-provider": "^4.2.1", "@smithy/util-endpoints": "^3.3.1", "@smithy/util-middleware": "^4.2.10", "tslib": "^2.6.2" } }, "sha512-ejQvXqlcU30h7liR9fXtj7PIAau1t/sFbJpgWPfiYDs7zd16jpH0IsSXKcba2jF6ChTXvIjACs27kNMc5xxE2Q=="],
|
|
80
|
-
|
|
81
|
-
"@smithy/core": ["@smithy/core@3.23.6", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.11", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.1", "@smithy/util-body-length-browser": "^4.2.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-stream": "^4.5.15", "@smithy/util-utf8": "^4.2.1", "@smithy/uuid": "^1.1.1", "tslib": "^2.6.2" } }, "sha512-4xE+0L2NrsFKpEVFlFELkIHQddBvMbQ41LRIP74dGCXnY1zQ9DgksrBcRBDJT+iOzGy4VEJIeU3hkUK5mn06kg=="],
|
|
82
|
-
|
|
83
|
-
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.10", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.10", "@smithy/property-provider": "^4.2.10", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.10", "tslib": "^2.6.2" } }, "sha512-3bsMLJJLTZGZqVGGeBVFfLzuRulVsGTj12BzRKODTHqUABpIr0jMN1vN3+u6r2OfyhAQ2pXaMZWX/swBK5I6PQ=="],
|
|
84
|
-
|
|
85
|
-
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.11", "", { "dependencies": { "@smithy/protocol-http": "^5.3.10", "@smithy/querystring-builder": "^4.2.10", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-wbTRjOxdFuyEg0CpumjZO0hkUl+fetJFqxNROepuLIoijQh51aMBmzFLfoQdwRjxsuuS2jizzIUTjPWgd8pd7g=="],
|
|
86
|
-
|
|
87
|
-
"@smithy/hash-node": ["@smithy/hash-node@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-buffer-from": "^4.2.1", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-1VzIOI5CcsvMDvP3iv1vG/RfLJVVVc67dCRyLSB2Hn9SWCZrDO3zvcIzj3BfEtqRW5kcMg5KAeVf1K3dR6nD3w=="],
|
|
88
|
-
|
|
89
|
-
"@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-vy9KPNSFUU0ajFYk0sDZIYiUlAWGEAhRfehIr5ZkdFrRFTAuXEPUd41USuqHU6vvLX4r6Q9X7MKBco5+Il0Org=="],
|
|
90
|
-
|
|
91
|
-
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Yfu664Qbf1B4IYIsYgKoABt010daZjkaCRvdU/sPnZG6TtHOB0md0RjNdLGzxe5UIdn9js4ftPICzmkRa9RJ4Q=="],
|
|
92
|
-
|
|
93
|
-
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.10", "", { "dependencies": { "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-TQZ9kX5c6XbjhaEBpvhSvMEZ0klBs1CFtOdPFwATZSbC9UeQfKHPLPN9Y+I6wZGMOavlYTOlHEPDrt42PMSH9w=="],
|
|
94
|
-
|
|
95
|
-
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.20", "", { "dependencies": { "@smithy/core": "^3.23.6", "@smithy/middleware-serde": "^4.2.11", "@smithy/node-config-provider": "^4.3.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "@smithy/url-parser": "^4.2.10", "@smithy/util-middleware": "^4.2.10", "tslib": "^2.6.2" } }, "sha512-9W6Np4ceBP3XCYAGLoMCmn8t2RRVzuD1ndWPLBbv7H9CrwM9Bprf6Up6BM9ZA/3alodg0b7Kf6ftBK9R1N04vw=="],
|
|
96
|
-
|
|
97
|
-
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.37", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.10", "@smithy/protocol-http": "^5.3.10", "@smithy/service-error-classification": "^4.2.10", "@smithy/smithy-client": "^4.12.0", "@smithy/types": "^4.13.0", "@smithy/util-middleware": "^4.2.10", "@smithy/util-retry": "^4.2.10", "@smithy/uuid": "^1.1.1", "tslib": "^2.6.2" } }, "sha512-/1psZZllBBSQ7+qo5+hhLz7AEPGLx3Z0+e3ramMBEuPK2PfvLK4SrncDB9VegX5mBn+oP/UTDrM6IHrFjvX1ZA=="],
|
|
98
|
-
|
|
99
|
-
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.11", "", { "dependencies": { "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-STQdONGPwbbC7cusL60s7vOa6He6A9w2jWhoapL0mgVjmR19pr26slV+yoSP76SIssMTX/95e5nOZ6UQv6jolg=="],
|
|
100
|
-
|
|
101
|
-
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-pmts/WovNcE/tlyHa8z/groPeOtqtEpp61q3W0nW1nDJuMq/x+hWa/OVQBtgU0tBqupeXq0VBOLA4UZwE8I0YA=="],
|
|
102
|
-
|
|
103
|
-
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.10", "", { "dependencies": { "@smithy/property-provider": "^4.2.10", "@smithy/shared-ini-file-loader": "^4.4.5", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-UALRbJtVX34AdP2VECKVlnNgidLHA2A7YgcJzwSBg1hzmnO/bZBHl/LDQQyYifzUwp1UOODnl9JJ3KNawpUJ9w=="],
|
|
104
|
-
|
|
105
|
-
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.12", "", { "dependencies": { "@smithy/abort-controller": "^4.2.10", "@smithy/protocol-http": "^5.3.10", "@smithy/querystring-builder": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-zo1+WKJkR9x7ZtMeMDAAsq2PufwiLDmkhcjpWPRRkmeIuOm6nq1qjFICSZbnjBvD09ei8KMo26BWxsu2BUU+5w=="],
|
|
106
|
-
|
|
107
|
-
"@smithy/property-provider": ["@smithy/property-provider@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-5jm60P0CU7tom0eNrZ7YrkgBaoLFXzmqB0wVS+4uK8PPGmosSrLNf6rRd50UBvukztawZ7zyA8TxlrKpF5z9jw=="],
|
|
108
|
-
|
|
109
|
-
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-2NzVWpYY0tRdfeCJLsgrR89KE3NTWT2wGulhNUxYlRmtRmPwLQwKzhrfVaiNlA9ZpJvbW7cjTVChYKgnkqXj1A=="],
|
|
110
|
-
|
|
111
|
-
"@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "@smithy/util-uri-escape": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-HeN7kEvuzO2DmAzLukE9UryiUvejD3tMp9a1D1NJETerIfKobBUCLfviP6QEk500166eD2IATaXM59qgUI+YDA=="],
|
|
112
|
-
|
|
113
|
-
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-4Mh18J26+ao1oX5wXJfWlTT+Q1OpDR8ssiC9PDOuEgVBGloqg18Fw7h5Ct8DyT9NBYwJgtJ2nLjKKFU6RP1G1Q=="],
|
|
114
|
-
|
|
115
|
-
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0" } }, "sha512-0R/+/Il5y8nB/By90o8hy/bWVYptbIfvoTYad0igYQO5RefhNCDmNzqxaMx7K1t/QWo0d6UynqpqN5cCQt1MCg=="],
|
|
116
|
-
|
|
117
|
-
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-pHgASxl50rrtOztgQCPmOXFjRW+mCd7ALr/3uXNzRrRoGV5G2+78GOsQ3HlQuBVHCh9o6xqMNvlIKZjWn4Euug=="],
|
|
118
|
-
|
|
119
|
-
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.10", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.1", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "@smithy/util-hex-encoding": "^4.2.1", "@smithy/util-middleware": "^4.2.10", "@smithy/util-uri-escape": "^4.2.1", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-Wab3wW8468WqTKIxI+aZe3JYO52/RYT/8sDOdzkUhjnLakLe9qoQqIcfih/qxcF4qWEFoWBszY0mj5uxffaVXA=="],
|
|
120
|
-
|
|
121
|
-
"@smithy/smithy-client": ["@smithy/smithy-client@4.12.0", "", { "dependencies": { "@smithy/core": "^3.23.6", "@smithy/middleware-endpoint": "^4.4.20", "@smithy/middleware-stack": "^4.2.10", "@smithy/protocol-http": "^5.3.10", "@smithy/types": "^4.13.0", "@smithy/util-stream": "^4.5.15", "tslib": "^2.6.2" } }, "sha512-R8bQ9K3lCcXyZmBnQqUZJF4ChZmtWT5NLi6x5kgWx5D+/j0KorXcA0YcFg/X5TOgnTCy1tbKc6z2g2y4amFupQ=="],
|
|
122
|
-
|
|
123
|
-
"@smithy/types": ["@smithy/types@4.13.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw=="],
|
|
124
|
-
|
|
125
|
-
"@smithy/url-parser": ["@smithy/url-parser@4.2.10", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-uypjF7fCDsRk26u3qHmFI/ePL7bxxB9vKkE+2WKEciHhz+4QtbzWiHRVNRJwU3cKhrYDYQE3b0MRFtqfLYdA4A=="],
|
|
126
|
-
|
|
127
|
-
"@smithy/util-base64": ["@smithy/util-base64@4.3.1", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.1", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-BKGuawX4Doq/bI/uEmg+Zyc36rJKWuin3py89PquXBIBqmbnJwBBsmKhdHfNEp0+A4TDgLmT/3MSKZ1SxHcR6w=="],
|
|
128
|
-
|
|
129
|
-
"@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-SiJeLiozrAoCrgDBUgsVbmqHmMgg/2bA15AzcbcW+zan7SuyAVHN4xTSbq0GlebAIwlcaX32xacnrG488/J/6g=="],
|
|
130
|
-
|
|
131
|
-
"@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4rHqBvxtJEBvsZcFQSPQqXP2b/yy/YlB66KlcEgcH2WNoOKCKB03DSLzXmOsXjbl8dJ4OEYTn31knhdznwk7zw=="],
|
|
132
|
-
|
|
133
|
-
"@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.1", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-/swhmt1qTiVkaejlmMPPDgZhEaWb/HWMGRBheaxwuVkusp/z+ErJyQxO6kaXumOciZSWlmq6Z5mNylCd33X7Ig=="],
|
|
134
|
-
|
|
135
|
-
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-462id/00U8JWFw6qBuTSWfN5TxOHvDu4WliI97qOIOnuC/g+NDAknTU8eoGXEPlLkRVgWEr03jJBLV4o2FL8+A=="],
|
|
136
|
-
|
|
137
|
-
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.36", "", { "dependencies": { "@smithy/property-provider": "^4.2.10", "@smithy/smithy-client": "^4.12.0", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-R0smq7EHQXRVMxkAxtH5akJ/FvgAmNF6bUy/GwY/N20T4GrwjT633NFm0VuRpC+8Bbv8R9A0DoJ9OiZL/M3xew=="],
|
|
138
|
-
|
|
139
|
-
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.39", "", { "dependencies": { "@smithy/config-resolver": "^4.4.9", "@smithy/credential-provider-imds": "^4.2.10", "@smithy/node-config-provider": "^4.3.10", "@smithy/property-provider": "^4.2.10", "@smithy/smithy-client": "^4.12.0", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-otWuoDm35btJV1L8MyHrPl462B07QCdMTktKc7/yM+Psv6KbED/ziXiHnmr7yPHUjfIwE9S8Max0LO24Mo3ZVg=="],
|
|
140
|
-
|
|
141
|
-
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.1", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-xyctc4klmjmieQiF9I1wssBWleRV0RhJ2DpO8+8yzi2LO1Z+4IWOZNGZGNj4+hq9kdo+nyfrRLmQTzc16Op2Vg=="],
|
|
142
|
-
|
|
143
|
-
"@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-c1hHtkgAWmE35/50gmdKajgGAKV3ePJ7t6UtEmpfCWJmQE9BQAQPz0URUVI89eSkcDqCtzqllxzG28IQoZPvwA=="],
|
|
144
|
-
|
|
145
|
-
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.10", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-LxaQIWLp4y0r72eA8mwPNQ9va4h5KeLM0I3M/HV9klmFaY2kN766wf5vsTzmaOpNNb7GgXAd9a25P3h8T49PSA=="],
|
|
146
|
-
|
|
147
|
-
"@smithy/util-retry": ["@smithy/util-retry@4.2.10", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.10", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-HrBzistfpyE5uqTwiyLsFHscgnwB0kgv8vySp7q5kZ0Eltn/tjosaSGGDj/jJ9ys7pWzIP/icE2d+7vMKXLv7A=="],
|
|
148
|
-
|
|
149
|
-
"@smithy/util-stream": ["@smithy/util-stream@4.5.15", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.11", "@smithy/node-http-handler": "^4.4.12", "@smithy/types": "^4.13.0", "@smithy/util-base64": "^4.3.1", "@smithy/util-buffer-from": "^4.2.1", "@smithy/util-hex-encoding": "^4.2.1", "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-OlOKnaqnkU9X+6wEkd7mN+WB7orPbCVDauXOj22Q7VtiTkvy7ZdSsOg4QiNAZMgI4OkvNf+/VLUC3VXkxuWJZw=="],
|
|
150
|
-
|
|
151
|
-
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YmiUDn2eo2IOiWYYvGQkgX5ZkBSiTQu4FlDo5jNPpAxng2t6Sjb6WutnZV9l6VR4eJul1ABmCrnWBC9hKHQa6Q=="],
|
|
152
|
-
|
|
153
|
-
"@smithy/util-utf8": ["@smithy/util-utf8@4.2.1", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.1", "tslib": "^2.6.2" } }, "sha512-DSIwNaWtmzrNQHv8g7DBGR9mulSit65KSj5ymGEIAknmIN8IpbZefEep10LaMG/P/xquwbmJ1h9ectz8z6mV6g=="],
|
|
154
|
-
|
|
155
|
-
"@smithy/uuid": ["@smithy/uuid@1.1.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-dSfDCeihDmZlV2oyr0yWPTUfh07suS+R5OB+FZGiv/hHyK3hrFBW5rR1UYjfa57vBsrP9lciFkRPzebaV1Qujw=="],
|
|
156
|
-
|
|
157
|
-
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
|
|
71
|
+
"@types/bun": ["@types/bun@1.4.3", "", { "dependencies": { "bun-types": "1.4.3" } }, "sha512-N67Pqy8JXbAY/j0tNraS3V5mubGvc6rrrNDmDCtZLlFzccbUJPDb0tvgUBC5q4m8ybkP93Jcn1QB+RYQ6PvBhw=="],
|
|
158
72
|
|
|
159
73
|
"@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="],
|
|
160
74
|
|
|
161
75
|
"bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="],
|
|
162
76
|
|
|
163
|
-
"bun-types": ["bun-types@1.3
|
|
164
|
-
|
|
165
|
-
"fast-xml-parser": ["fast-xml-parser@5.3.6", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA=="],
|
|
77
|
+
"bun-types": ["bun-types@1.4.3", "", { "dependencies": { "@types/node": "*", "undici-types": "*" } }, "sha512-tinRJNGOmUN+tEAsn2y2e942n9JHRHR42iUYzDH75NmhJqVBokGSQkkfSFdNWwsANIdrK6ACH9t01M452YyTwg=="],
|
|
166
78
|
|
|
167
79
|
"node-screenshots": ["node-screenshots@0.2.8", "", { "optionalDependencies": { "node-screenshots-darwin-arm64": "0.2.8", "node-screenshots-darwin-x64": "0.2.8", "node-screenshots-linux-arm64-gnu": "0.2.8", "node-screenshots-linux-loong64-gnu": "0.2.8", "node-screenshots-linux-x64-gnu": "0.2.8", "node-screenshots-linux-x64-musl": "0.2.8", "node-screenshots-win32-arm64-msvc": "0.2.8", "node-screenshots-win32-ia32-msvc": "0.2.8", "node-screenshots-win32-x64-msvc": "0.2.8" } }, "sha512-7O2ab89hkUgiXkrYj9iYCbJjY1iFJqIyZt0vuVtHFKlnajeFoyW+hur4CPxwwrnCUK0ahr0WLWQ5A6rCzF8rWQ=="],
|
|
168
80
|
|
|
@@ -184,8 +96,6 @@
|
|
|
184
96
|
|
|
185
97
|
"node-screenshots-win32-x64-msvc": ["node-screenshots-win32-x64-msvc@0.2.8", "", { "os": "win32", "cpu": "x64" }, "sha512-DdFORB74FghM1L6UtE1TltIR2H0CCCmfQEa8gp2CTSdtBr2b0TKL7ZlbEV6x/wVKRFT59w7Kd6BULdeMmp0X2w=="],
|
|
186
98
|
|
|
187
|
-
"strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
|
|
188
|
-
|
|
189
99
|
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
|
190
100
|
|
|
191
101
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
@@ -194,16 +104,6 @@
|
|
|
194
104
|
|
|
195
105
|
"webview-bun": ["webview-bun@2.4.0", "", {}, "sha512-0+ugnQlcUHmuW+iLeb+Lzb8rGUJh7WEdXvNsuvaVEXT3EagK380XdD7heVJu0Ek/mNxMY3G2JM142YRQ1hDUGQ=="],
|
|
196
106
|
|
|
197
|
-
"@aws-
|
|
198
|
-
|
|
199
|
-
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
|
200
|
-
|
|
201
|
-
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
|
202
|
-
|
|
203
|
-
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
|
204
|
-
|
|
205
|
-
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
|
206
|
-
|
|
207
|
-
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
|
107
|
+
"@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1150.0", "", { "dependencies": { "@aws-sdk/core": "^3.978.3", "@aws-sdk/nested-clients": "^3.997.48", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.2", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-wDU36ofhabZYu+d2KSriyDbwzxNoaOp+4dG0p3qkabk7HeALR5nIWyQbjoyJZLn3iqWkv/xNdyUfbfevDVDzzg=="],
|
|
208
108
|
}
|
|
209
109
|
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-aws-mfa",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": "https://github.com/aaronmeriwether/claude-aws-mfa",
|
|
6
6
|
"bin": {
|
|
7
7
|
"claude-aws-mfa": "./src/index.ts"
|
|
8
8
|
},
|
|
9
9
|
"devDependencies": {
|
|
10
|
-
"@types/bun": "
|
|
10
|
+
"@types/bun": "^1.4.3",
|
|
11
11
|
"node-screenshots": "^0.2.8"
|
|
12
12
|
},
|
|
13
13
|
"peerDependencies": {
|
|
14
14
|
"typescript": "^5"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@aws-sdk/client-
|
|
17
|
+
"@aws-sdk/client-bedrock": "^3.1152.0",
|
|
18
|
+
"@aws-sdk/client-cloudwatch-logs": "^3.1152.0",
|
|
19
|
+
"@aws-sdk/client-sts": "^3.1152.0",
|
|
18
20
|
"webview-bun": "^2.4.0"
|
|
19
21
|
}
|
|
20
22
|
}
|
package/src/flags.ts
CHANGED
|
@@ -1,21 +1,66 @@
|
|
|
1
|
+
export const HELP = `Usage: claude-aws-mfa [options]
|
|
2
|
+
|
|
3
|
+
With no options, prints temporary AWS credentials as JSON for Claude Code's
|
|
4
|
+
awsCredentialExport, prompting for MFA when needed.
|
|
5
|
+
|
|
6
|
+
Options:
|
|
7
|
+
--setup Launch the guided setup wizard
|
|
8
|
+
--[no-]cache-session Reuse cached STS credentials until they expire (default: on)
|
|
9
|
+
--[no-]auto-mfa Run the configured MFA command without showing the dialog (default: on)
|
|
10
|
+
--[no-]single-instance-lock Let only one instance show a dialog at a time (default: on)
|
|
11
|
+
--no-ui Never show a GUI; errors go to stderr
|
|
12
|
+
--usage[=[<role>/]<session>]
|
|
13
|
+
Print Bedrock token usage and estimated cost instead of
|
|
14
|
+
credentials, for a session name (default: the one in the
|
|
15
|
+
config), optionally under one role. Use * to itemize:
|
|
16
|
+
--usage=my-role/* one row per session of role my-role
|
|
17
|
+
--usage=* one row per role
|
|
18
|
+
--profile=<name> With --usage: query using this AWS profile instead of the
|
|
19
|
+
tool's own credentials
|
|
20
|
+
--from=<date> With --usage: start of range (default: start of this month)
|
|
21
|
+
--to=<date> With --usage: end of range, exclusive (default: now)
|
|
22
|
+
-h, --help Show this help`;
|
|
23
|
+
|
|
1
24
|
export interface CliFlags {
|
|
25
|
+
help: boolean;
|
|
2
26
|
setup: boolean;
|
|
3
27
|
cacheSession: boolean | undefined; // undefined = use config default
|
|
4
28
|
autoMfa: boolean | undefined;
|
|
5
29
|
singleInstanceLock: boolean | undefined;
|
|
6
30
|
noUi: boolean;
|
|
31
|
+
usage: boolean;
|
|
32
|
+
usageSpec?: string; // --usage=[<role>/]<session>; undefined = session name from config
|
|
33
|
+
profile?: string;
|
|
34
|
+
from?: string;
|
|
35
|
+
to?: string;
|
|
7
36
|
}
|
|
8
37
|
|
|
9
38
|
export function parseFlags(argv: string[]): CliFlags {
|
|
10
39
|
const flags: CliFlags = {
|
|
40
|
+
help: false,
|
|
11
41
|
setup: false,
|
|
12
42
|
cacheSession: undefined,
|
|
13
43
|
autoMfa: undefined,
|
|
14
44
|
singleInstanceLock: undefined,
|
|
15
45
|
noUi: false,
|
|
46
|
+
usage: false,
|
|
16
47
|
};
|
|
17
48
|
for (const arg of argv.slice(2)) {
|
|
49
|
+
const [key, value] = arg.split(/=(.*)/s);
|
|
50
|
+
if (value !== undefined && (key === "--profile" || key === "--from" || key === "--to")) {
|
|
51
|
+
flags[key.slice(2) as "profile" | "from" | "to"] = value;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (key === "--usage" && value !== undefined) {
|
|
55
|
+
flags.usage = true;
|
|
56
|
+
flags.usageSpec = value || undefined;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
18
59
|
switch (arg) {
|
|
60
|
+
case "-h":
|
|
61
|
+
case "--help":
|
|
62
|
+
flags.help = true;
|
|
63
|
+
break;
|
|
19
64
|
case "setup":
|
|
20
65
|
case "--setup":
|
|
21
66
|
flags.setup = true;
|
|
@@ -41,6 +86,9 @@ export function parseFlags(argv: string[]): CliFlags {
|
|
|
41
86
|
case "--no-ui":
|
|
42
87
|
flags.noUi = true;
|
|
43
88
|
break;
|
|
89
|
+
case "--usage":
|
|
90
|
+
flags.usage = true;
|
|
91
|
+
break;
|
|
44
92
|
}
|
|
45
93
|
}
|
|
46
94
|
return flags;
|
package/src/index.ts
CHANGED
|
@@ -15,10 +15,15 @@ if (process.platform === "linux" && !process.env.WEBKIT_DISABLE_SANDBOX_THIS_IS_
|
|
|
15
15
|
process.exit(proc.exitCode);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
import { parseFlags } from "./flags";
|
|
18
|
+
import { parseFlags, HELP } from "./flags";
|
|
19
19
|
|
|
20
20
|
const flags = parseFlags(process.argv);
|
|
21
21
|
|
|
22
|
+
if (flags.help) {
|
|
23
|
+
console.log(HELP);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
if (flags.setup) {
|
|
23
28
|
const { runSetup } = await import("./setup");
|
|
24
29
|
process.exit(runSetup() ? 0 : 2);
|
|
@@ -44,9 +49,31 @@ function isSessionValid(session: CachedSession | undefined): session is CachedSe
|
|
|
44
49
|
return new Date(session.Expiration).getTime() > Date.now();
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
// --- Helper:
|
|
52
|
+
// --- Helper: query Bedrock usage (--usage); a CLI command, so errors go to stderr ---
|
|
53
|
+
|
|
54
|
+
async function runUsage(opts: Omit<Parameters<typeof import("./usage").printUsage>[0], "spec" | "from" | "to">): Promise<never> {
|
|
55
|
+
const { printUsage } = await import("./usage");
|
|
56
|
+
try {
|
|
57
|
+
await printUsage({ ...opts, spec: flags.usageSpec ?? defaults.sessionName, from: flags.from, to: flags.to });
|
|
58
|
+
process.exit(0);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
process.stderr.write(`Usage query failed: ${err instanceof Error ? err.message : err}\n`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
48
64
|
|
|
49
|
-
|
|
65
|
+
// With --profile, the query uses that AWS profile instead of this tool's own credentials.
|
|
66
|
+
if (flags.usage && flags.profile) await runUsage({ region: defaults.region, profile: flags.profile });
|
|
67
|
+
|
|
68
|
+
// --- Helper: output credentials (or, with --usage, use them for the usage query) ---
|
|
69
|
+
|
|
70
|
+
async function outputCredentials(session: CachedSession): Promise<void> {
|
|
71
|
+
if (flags.usage) {
|
|
72
|
+
await runUsage({
|
|
73
|
+
region: defaults.region,
|
|
74
|
+
credentials: { accessKeyId: session.AccessKeyId, secretAccessKey: session.SecretAccessKey, sessionToken: session.SessionToken },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
50
77
|
console.log(JSON.stringify({
|
|
51
78
|
Credentials: {
|
|
52
79
|
AccessKeyId: session.AccessKeyId,
|
|
@@ -59,7 +86,7 @@ function outputCredentials(session: CachedSession): void {
|
|
|
59
86
|
// --- Helper: report a failure, respecting --no-ui (dialog is never shown in that mode) ---
|
|
60
87
|
|
|
61
88
|
async function reportFailure(message: string): Promise<never> {
|
|
62
|
-
if (flags.noUi) {
|
|
89
|
+
if (flags.noUi || flags.usage) {
|
|
63
90
|
// stdout is reserved for cleanly-parsable credentials JSON on success.
|
|
64
91
|
process.stderr.write(`${message}\n`);
|
|
65
92
|
} else {
|
|
@@ -174,7 +201,7 @@ async function obtainViaDialog(cfg: Partial<Config>): Promise<void> {
|
|
|
174
201
|
...(useCache ? { cachedSession: session } : {}),
|
|
175
202
|
});
|
|
176
203
|
|
|
177
|
-
outputCredentials(session);
|
|
204
|
+
await outputCredentials(session);
|
|
178
205
|
}
|
|
179
206
|
|
|
180
207
|
// --- Main flow ---
|
|
@@ -184,7 +211,7 @@ try {
|
|
|
184
211
|
if (useCache && config?.cachedSession) {
|
|
185
212
|
if (isSessionValid(config.cachedSession)) {
|
|
186
213
|
// Return cached credentials without showing dialog
|
|
187
|
-
outputCredentials(config.cachedSession);
|
|
214
|
+
await outputCredentials(config.cachedSession);
|
|
188
215
|
process.exit(0);
|
|
189
216
|
}
|
|
190
217
|
// Expired — clear the cached session
|
|
@@ -196,7 +223,7 @@ try {
|
|
|
196
223
|
try {
|
|
197
224
|
const session = await tryAutoMfa(defaults);
|
|
198
225
|
if (session) {
|
|
199
|
-
outputCredentials(session);
|
|
226
|
+
await outputCredentials(session);
|
|
200
227
|
process.exit(0);
|
|
201
228
|
}
|
|
202
229
|
} catch (err) {
|
|
@@ -226,7 +253,7 @@ try {
|
|
|
226
253
|
if (useCache) {
|
|
227
254
|
const freshConfig = loadConfig();
|
|
228
255
|
if (freshConfig?.cachedSession && isSessionValid(freshConfig.cachedSession)) {
|
|
229
|
-
outputCredentials(freshConfig.cachedSession);
|
|
256
|
+
await outputCredentials(freshConfig.cachedSession);
|
|
230
257
|
process.exit(0);
|
|
231
258
|
}
|
|
232
259
|
}
|
|
@@ -238,7 +265,7 @@ try {
|
|
|
238
265
|
if (useCache) {
|
|
239
266
|
const freshConfig = loadConfig();
|
|
240
267
|
if (freshConfig?.cachedSession && isSessionValid(freshConfig.cachedSession)) {
|
|
241
|
-
outputCredentials(freshConfig.cachedSession);
|
|
268
|
+
await outputCredentials(freshConfig.cachedSession);
|
|
242
269
|
process.exit(0);
|
|
243
270
|
}
|
|
244
271
|
}
|
package/src/usage.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { BedrockClient, GetModelInvocationLoggingConfigurationCommand } from "@aws-sdk/client-bedrock";
|
|
2
|
+
import { CloudWatchLogsClient, StartQueryCommand, GetQueryResultsCommand } from "@aws-sdk/client-cloudwatch-logs";
|
|
3
|
+
import { DEFAULT_SESSION_NAME } from "./sts";
|
|
4
|
+
|
|
5
|
+
const COLUMNS = ["requests", "inputTokens", "cacheReadTokens", "cacheWriteTokens", "outputTokens"] as const;
|
|
6
|
+
const HEADERS = ["Model", "Requests", "Input", "Cache read", "Cache write", "Output"];
|
|
7
|
+
|
|
8
|
+
/** Start of the current month (local time) through now, unless overridden. */
|
|
9
|
+
export function usageRange(from?: string, to?: string): { start: Date; end: Date } {
|
|
10
|
+
const now = new Date();
|
|
11
|
+
const start = from ? new Date(from) : new Date(now.getFullYear(), now.getMonth(), 1);
|
|
12
|
+
const end = to ? new Date(to) : now;
|
|
13
|
+
if (isNaN(start.getTime())) throw new Error(`Invalid --from date: ${from}`);
|
|
14
|
+
if (isNaN(end.getTime())) throw new Error(`Invalid --to date: ${to}`);
|
|
15
|
+
if (start >= end) throw new Error("--from must be before --to");
|
|
16
|
+
return { start, end };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type GroupField = "roleName" | "sessionName";
|
|
20
|
+
const GROUP_HEADERS: Record<GroupField, string> = { roleName: "Role", sessionName: "Session" };
|
|
21
|
+
|
|
22
|
+
/** What `--usage=<spec>` selects; an undefined role/session matches any. */
|
|
23
|
+
export interface UsageSpec {
|
|
24
|
+
role?: string;
|
|
25
|
+
session?: string;
|
|
26
|
+
groupBy: GroupField[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* `--usage=[<role>/]<session>`. Either part may be `*`, meaning "itemize by
|
|
31
|
+
* it": `my-role/*` gives one row per session of role my-role. A bare `*` itemizes
|
|
32
|
+
* by role (sessions merged); a bare name matches that session under any role.
|
|
33
|
+
*/
|
|
34
|
+
export function parseUsageSpec(spec: string): UsageSpec {
|
|
35
|
+
if (spec === "*") return { groupBy: ["roleName"] };
|
|
36
|
+
const parts = spec.split("/");
|
|
37
|
+
if (parts.length > 2) throw new Error(`Invalid --usage value "${spec}": expected [<role>/]<session-name>`);
|
|
38
|
+
const [role, session] = parts.length === 2 ? parts : [undefined, parts[0]];
|
|
39
|
+
// IAM's charset for role and session names; also keeps them safe to embed as query literals.
|
|
40
|
+
for (const name of [role, session]) {
|
|
41
|
+
if (name !== undefined && name !== "*" && !/^[\w+=,.@-]+$/.test(name)) {
|
|
42
|
+
throw new Error(`Invalid role or session name "${name}" in --usage value "${spec}"`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
role: role === "*" ? undefined : role,
|
|
47
|
+
session: session === "*" ? undefined : session,
|
|
48
|
+
groupBy: [role === "*" && "roleName", session === "*" && "sessionName"].filter((g): g is GroupField => !!g),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Logs Insights query summing token counts per model (and per group) for the
|
|
54
|
+
* assumed-role callers a spec selects. The caller's ARN is
|
|
55
|
+
* `arn:aws:sts::<acct>:assumed-role/<role>/<session>`; other callers (IAM
|
|
56
|
+
* users, root) never match.
|
|
57
|
+
*/
|
|
58
|
+
export function usageQuery(spec: UsageSpec): string {
|
|
59
|
+
const filters = ["ispresent(sessionName)"];
|
|
60
|
+
if (spec.role) filters.push(`roleName = "${spec.role}"`);
|
|
61
|
+
if (spec.session) filters.push(`sessionName = "${spec.session}"`);
|
|
62
|
+
return [
|
|
63
|
+
"parse identity.arn /:assumed-role\\/(?<roleName>[^\\/]+)\\/(?<sessionName>[^\\/]+)$/",
|
|
64
|
+
`| filter ${filters.join(" and ")}`,
|
|
65
|
+
"| stats count(*) as requests, sum(input.inputTokenCount) as inputTokens,",
|
|
66
|
+
" sum(input.cacheReadInputTokenCount) as cacheReadTokens, sum(input.cacheWriteInputTokenCount) as cacheWriteTokens,",
|
|
67
|
+
` sum(output.outputTokenCount) as outputTokens by ${[...spec.groupBy, "modelId"].join(", ")}`,
|
|
68
|
+
].join("\n");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type Column = (typeof COLUMNS)[number];
|
|
72
|
+
/** Per-token USD prices, keyed by `<Price List model name>|<global?>`. */
|
|
73
|
+
export type PriceTable = Record<string, Partial<Record<Column, number>>>;
|
|
74
|
+
|
|
75
|
+
const PRICED_COLUMNS: Record<string, Column> = {
|
|
76
|
+
"Input tokens": "inputTokens",
|
|
77
|
+
"Output tokens": "outputTokens",
|
|
78
|
+
"Prompt cache read input tokens": "cacheReadTokens",
|
|
79
|
+
"Prompt cache write input tokens": "cacheWriteTokens",
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Standard on-demand token prices from an AWS Price List offer file
|
|
84
|
+
* (AmazonBedrockFoundationModels). Long-context, 1h-cache-write and
|
|
85
|
+
* latency-optimized variants are skipped: the aggregated logs can't tell those
|
|
86
|
+
* requests apart, so every token is priced at the base rate.
|
|
87
|
+
*/
|
|
88
|
+
export function parsePriceList(offer: any): PriceTable {
|
|
89
|
+
const table: PriceTable = {};
|
|
90
|
+
for (const [sku, product] of Object.entries<any>(offer.products ?? {})) {
|
|
91
|
+
const a = product.attributes ?? {};
|
|
92
|
+
const column = PRICED_COLUMNS[a.inferenceType];
|
|
93
|
+
if (a.feature !== "On-demand Inference" || !column || /long_ctx|1h|LatencyOptimized/i.test(a.usagetype)) continue;
|
|
94
|
+
const dim: any = Object.values<any>(Object.values<any>(offer.terms?.OnDemand?.[sku] ?? {})[0]?.priceDimensions ?? {})[0];
|
|
95
|
+
if (dim?.unit !== "1M tokens") continue;
|
|
96
|
+
(table[`${a.model}|${/global/i.test(a.usagetype)}`] ??= {})[column] = Number(dim.pricePerUnit.USD) / 1e6;
|
|
97
|
+
}
|
|
98
|
+
return table;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** `…/us.anthropic.claude-sonnet-4-5-20250929-v1:0` → `Claude Sonnet 4.5`, the Price List's model name. */
|
|
102
|
+
export function priceListModelName(modelId: string): string {
|
|
103
|
+
const words = modelId.split("/").pop()!.split(".").pop()!.replace(/-\d{8}.*$|-v\d+(:\d+)?$/, "").split("-");
|
|
104
|
+
return words
|
|
105
|
+
.map((w, i) => (i === 0 ? "" : /^\d+$/.test(w) && /^\d+$/.test(words[i - 1]) ? "." : " ") + w[0].toUpperCase() + w.slice(1))
|
|
106
|
+
.join("");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** USD cost of one row, or undefined if any token type it used has no known price. */
|
|
110
|
+
export function rowCost(row: Record<string, string>, prices: PriceTable): number | undefined {
|
|
111
|
+
const isGlobal = row.modelId.split("/").pop()!.startsWith("global.");
|
|
112
|
+
const price = prices[`${priceListModelName(row.modelId)}|${isGlobal}`] ?? {};
|
|
113
|
+
let cost = 0;
|
|
114
|
+
for (const c of COLUMNS.slice(1)) {
|
|
115
|
+
const tokens = Number(row[c] || 0);
|
|
116
|
+
if (!tokens) continue;
|
|
117
|
+
if (price[c] === undefined) return undefined;
|
|
118
|
+
cost += tokens * price[c]!;
|
|
119
|
+
}
|
|
120
|
+
return cost;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const usd = (n: number) => "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
124
|
+
|
|
125
|
+
interface Tally {
|
|
126
|
+
label: string;
|
|
127
|
+
counts: number[];
|
|
128
|
+
cost: number;
|
|
129
|
+
unpriced: boolean; // some tokens had no list price, so `cost` is a lower bound
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function tally(label: string, rows: Record<string, string>[], prices: PriceTable): Tally {
|
|
133
|
+
const t: Tally = { label, counts: COLUMNS.map(() => 0), cost: 0, unpriced: false };
|
|
134
|
+
for (const r of rows) {
|
|
135
|
+
COLUMNS.forEach((c, i) => (t.counts[i] += Number(r[c] || 0)));
|
|
136
|
+
const cost = rowCost(r, prices);
|
|
137
|
+
if (cost === undefined) t.unpriced = true;
|
|
138
|
+
else t.cost += cost;
|
|
139
|
+
}
|
|
140
|
+
return t;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** One line per model, or per group (role and/or session, models merged) when itemizing. */
|
|
144
|
+
export function formatUsageTable(rows: Record<string, string>[], prices: PriceTable = {}, groupBy: GroupField[] = []): string {
|
|
145
|
+
if (!rows.length) return "No Bedrock invocations found.";
|
|
146
|
+
const labelOf = (r: Record<string, string>) =>
|
|
147
|
+
groupBy.length ? groupBy.map((g) => r[g]).join("/") : (r.modelId ?? "").split("/").pop()!;
|
|
148
|
+
const groups = Map.groupBy(rows, labelOf);
|
|
149
|
+
const tallies = [...groups].map(([label, rs]) => tally(label, rs, prices)).sort((a, b) => b.cost - a.cost);
|
|
150
|
+
const total = tally("TOTAL", rows, prices);
|
|
151
|
+
|
|
152
|
+
const cost = (t: Tally) => (t.unpriced && !t.cost ? "?" : usd(t.cost) + (t.unpriced ? "+" : ""));
|
|
153
|
+
const cells = (t: Tally) => [t.label, ...t.counts.map((n) => n.toLocaleString("en-US")), cost(t)];
|
|
154
|
+
const headers = [groupBy.length ? groupBy.map((g) => GROUP_HEADERS[g]).join("/") : "Model", ...HEADERS.slice(1), "Cost (USD)"];
|
|
155
|
+
const table = [...tallies, total].map(cells);
|
|
156
|
+
const widths = headers.map((h, i) => Math.max(h.length, ...table.map((r) => r[i].length)));
|
|
157
|
+
const line = (cs: string[]) => cs.map((c, i) => (i ? c.padStart(widths[i]) : c.padEnd(widths[i]))).join(" ");
|
|
158
|
+
return [
|
|
159
|
+
line(headers),
|
|
160
|
+
...table.slice(0, -1).map(line),
|
|
161
|
+
widths.map((w) => "-".repeat(w)).join(" "),
|
|
162
|
+
line(table.at(-1)!),
|
|
163
|
+
"",
|
|
164
|
+
"Cost is an estimate at current AWS list prices (base on-demand rates; excludes discounts, long-context and 1h-cache surcharges).",
|
|
165
|
+
...(total.unpriced ? ["? / + = some models have no list price and are excluded from the cost."] : []),
|
|
166
|
+
].join("\n");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Bedrock prices for `region` from the public AWS Price List bulk API (no credentials needed). */
|
|
170
|
+
async function fetchPrices(region: string): Promise<PriceTable> {
|
|
171
|
+
const url = `https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/${region}/index.json`;
|
|
172
|
+
const res = await fetch(url);
|
|
173
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} from ${url}`);
|
|
174
|
+
return parsePriceList(await res.json());
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Query Bedrock model invocation logs (CloudWatch Logs destination) for usage
|
|
179
|
+
* by the role/session that `spec` (see parseUsageSpec) selects. Requires invocation logging to be enabled.
|
|
180
|
+
* `credentials` undefined + `profile` set → the SDK resolves that profile.
|
|
181
|
+
*/
|
|
182
|
+
export async function printUsage(opts: {
|
|
183
|
+
region?: string;
|
|
184
|
+
credentials?: { accessKeyId: string; secretAccessKey: string; sessionToken?: string };
|
|
185
|
+
profile?: string;
|
|
186
|
+
spec?: string;
|
|
187
|
+
from?: string;
|
|
188
|
+
to?: string;
|
|
189
|
+
}): Promise<void> {
|
|
190
|
+
const { start, end } = usageRange(opts.from, opts.to);
|
|
191
|
+
const specText = opts.spec || DEFAULT_SESSION_NAME;
|
|
192
|
+
const spec = parseUsageSpec(specText);
|
|
193
|
+
const clientConfig = { region: opts.region || undefined, credentials: opts.credentials, profile: opts.profile };
|
|
194
|
+
|
|
195
|
+
const bedrock = new BedrockClient(clientConfig);
|
|
196
|
+
// Prices are best-effort: token counts are still worth printing without them.
|
|
197
|
+
const prices = bedrock.config.region().then(fetchPrices).catch((err) => {
|
|
198
|
+
process.stderr.write(`Could not fetch Bedrock prices, costs omitted: ${err instanceof Error ? err.message : err}\n`);
|
|
199
|
+
return {};
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const { loggingConfig } = await bedrock.send(new GetModelInvocationLoggingConfigurationCommand({}));
|
|
203
|
+
const logGroupName = loggingConfig?.cloudWatchConfig?.logGroupName;
|
|
204
|
+
if (!logGroupName) {
|
|
205
|
+
throw new Error("Bedrock model invocation logging to CloudWatch Logs is not enabled in this account/region, so there is no per-session usage to query.");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const logs = new CloudWatchLogsClient(clientConfig);
|
|
209
|
+
const { queryId } = await logs.send(new StartQueryCommand({
|
|
210
|
+
logGroupName,
|
|
211
|
+
startTime: Math.floor(start.getTime() / 1000),
|
|
212
|
+
endTime: Math.floor(end.getTime() / 1000),
|
|
213
|
+
queryString: usageQuery(spec),
|
|
214
|
+
}));
|
|
215
|
+
|
|
216
|
+
let res;
|
|
217
|
+
do {
|
|
218
|
+
await Bun.sleep(1000);
|
|
219
|
+
res = await logs.send(new GetQueryResultsCommand({ queryId }));
|
|
220
|
+
} while (res.status === "Scheduled" || res.status === "Running");
|
|
221
|
+
if (res.status !== "Complete") throw new Error(`Logs Insights query ${res.status}`);
|
|
222
|
+
|
|
223
|
+
const rows = (res.results ?? []).map((r) => Object.fromEntries(r.map((f) => [f.field!, f.value ?? ""])));
|
|
224
|
+
console.log(`Bedrock usage for ${specText}, ${start.toISOString()} – ${end.toISOString()} (log group ${logGroupName})\n`);
|
|
225
|
+
console.log(formatUsageTable(rows, await prices, spec.groupBy));
|
|
226
|
+
}
|
package/test/index.test.ts
CHANGED
|
@@ -18,6 +18,12 @@ describe("parseFlags", () => {
|
|
|
18
18
|
expect(parseFlags([...base, "--no-ui"]).noUi).toBe(true);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
test("parses --help and -h", () => {
|
|
22
|
+
expect(parseFlags(base).help).toBe(false);
|
|
23
|
+
expect(parseFlags([...base, "--help"]).help).toBe(true);
|
|
24
|
+
expect(parseFlags([...base, "-h"]).help).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
21
27
|
test("parses --setup", () => {
|
|
22
28
|
expect(parseFlags([...base, "--setup"]).setup).toBe(true);
|
|
23
29
|
expect(parseFlags([...base, "setup"]).setup).toBe(true);
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { parseUsageSpec, usageQuery, usageRange, formatUsageTable, parsePriceList, priceListModelName, rowCost } from "../src/usage";
|
|
3
|
+
import { parseFlags } from "../src/flags";
|
|
4
|
+
|
|
5
|
+
describe("usage", () => {
|
|
6
|
+
test("parses --usage, --profile=, --from=, --to=", () => {
|
|
7
|
+
const f = parseFlags(["bun", "x", "--usage", "--profile=work", "--from=2026-09-01", "--to=2026-10-01"]);
|
|
8
|
+
expect(f).toMatchObject({ usage: true, profile: "work", from: "2026-09-01", to: "2026-10-01" });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("parses --usage=<spec>", () => {
|
|
12
|
+
expect(parseFlags(["bun", "x", "--usage=my-role/*"])).toMatchObject({ usage: true, usageSpec: "my-role/*" });
|
|
13
|
+
expect(parseFlags(["bun", "x", "--usage"]).usageSpec).toBeUndefined();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("parses usage specs", () => {
|
|
17
|
+
expect(parseUsageSpec("sess")).toEqual({ role: undefined, session: "sess", groupBy: [] });
|
|
18
|
+
expect(parseUsageSpec("my-role/sess")).toEqual({ role: "my-role", session: "sess", groupBy: [] });
|
|
19
|
+
expect(parseUsageSpec("my-role/*")).toEqual({ role: "my-role", session: undefined, groupBy: ["sessionName"] });
|
|
20
|
+
expect(parseUsageSpec("*")).toEqual({ groupBy: ["roleName"] });
|
|
21
|
+
expect(parseUsageSpec("*/*")).toEqual({ role: undefined, session: undefined, groupBy: ["roleName", "sessionName"] });
|
|
22
|
+
expect(parseUsageSpec("jane.doe@example.com").session).toBe("jane.doe@example.com");
|
|
23
|
+
expect(() => parseUsageSpec("a/b/c")).toThrow("expected");
|
|
24
|
+
expect(() => parseUsageSpec('x" or 1=1')).toThrow("Invalid role or session");
|
|
25
|
+
expect(() => parseUsageSpec("my-role/")).toThrow("Invalid role or session");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("defaults to the current month", () => {
|
|
29
|
+
const { start, end } = usageRange();
|
|
30
|
+
const now = new Date();
|
|
31
|
+
expect(start).toEqual(new Date(now.getFullYear(), now.getMonth(), 1));
|
|
32
|
+
expect(end.getTime()).toBeGreaterThanOrEqual(start.getTime());
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("rejects bad ranges", () => {
|
|
36
|
+
expect(() => usageRange("nope")).toThrow("Invalid --from");
|
|
37
|
+
expect(() => usageRange("2026-10-02", "2026-10-01")).toThrow("before");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("query parses role and session from the caller ARN and filters/groups by the spec", () => {
|
|
41
|
+
const q = usageQuery(parseUsageSpec("my-role/*"));
|
|
42
|
+
expect(q).toContain('filter ispresent(sessionName) and roleName = "my-role"\n');
|
|
43
|
+
expect(q).toContain("by sessionName, modelId");
|
|
44
|
+
const re = new RegExp(q.match(/^parse identity\.arn \/(.*)\/$/m)![1]);
|
|
45
|
+
expect("arn:aws:sts::1:assumed-role/my-role/jane.doe@example.com".match(re)?.groups).toEqual({ roleName: "my-role", sessionName: "jane.doe@example.com" });
|
|
46
|
+
expect(re.test("arn:aws:iam::1:user/bob")).toBe(false);
|
|
47
|
+
expect(usageQuery(parseUsageSpec("sess"))).toContain('and sessionName = "sess"\n');
|
|
48
|
+
expect(usageQuery(parseUsageSpec("sess"))).toContain("by modelId");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("formats a table with totals", () => {
|
|
52
|
+
const out = formatUsageTable([
|
|
53
|
+
{ modelId: "m1", requests: "2", inputTokens: "1000", outputTokens: "50" },
|
|
54
|
+
{ modelId: "m2", requests: "1", inputTokens: "10", cacheReadTokens: "5", outputTokens: "1" },
|
|
55
|
+
]);
|
|
56
|
+
expect(out).toContain("TOTAL");
|
|
57
|
+
expect(out.split("\n").find((l) => l.startsWith("TOTAL"))).toMatch(/^TOTAL\s+3\s+1,010\s+5\s+0\s+51\s+\?$/);
|
|
58
|
+
expect(formatUsageTable([])).toBe("No Bedrock invocations found.");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("maps log model IDs to Price List model names", () => {
|
|
62
|
+
expect(priceListModelName("arn:aws:bedrock:us-west-2:1:inference-profile/us.anthropic.claude-opus-5-5")).toBe("Claude Opus 5.5");
|
|
63
|
+
expect(priceListModelName("global.anthropic.claude-sonnet-5")).toBe("Claude Sonnet 5");
|
|
64
|
+
expect(priceListModelName("us.anthropic.claude-sonnet-4-5-20250929-v1:0")).toBe("Claude Sonnet 4.5");
|
|
65
|
+
expect(priceListModelName("anthropic.claude-3-5-haiku-20241022-v1:0")).toBe("Claude 3.5 Haiku");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const product = (model: string, inferenceType: string, usagetype: string) => ({
|
|
69
|
+
attributes: { feature: "On-demand Inference", model, inferenceType, usagetype },
|
|
70
|
+
});
|
|
71
|
+
const term = (usd: string) => ({ t: { priceDimensions: { d: { unit: "1M tokens", pricePerUnit: { USD: usd } } } } });
|
|
72
|
+
const offer = {
|
|
73
|
+
products: {
|
|
74
|
+
a: product("Claude Opus 5.5", "Input tokens", "USW2-MP:USW2_input_tokens_standard-Units"),
|
|
75
|
+
b: product("Claude Opus 5.5", "Input tokens", "USW2-MP:USW2_input_tokens_global_standard-Units"),
|
|
76
|
+
c: product("Claude Opus 5.5", "Input tokens", "USW2-MP:USW2_input_tokens_long_ctx_standard-Units"),
|
|
77
|
+
d: product("Claude Opus 5.5", "Prompt cache write input tokens", "USW2-MP:USW2_cache_write_tokens_1h_standard-Units"),
|
|
78
|
+
e: product("Claude Opus 5.5", "Prompt cache write input tokens", "USW2-MP:USW2_cache_write_tokens_standard-Units"),
|
|
79
|
+
f: product("Claude Opus 5.5", "Output tokens", "USW2-MP:USW2_output_tokens_standard-Units"),
|
|
80
|
+
},
|
|
81
|
+
terms: { OnDemand: { a: term("4.4"), b: term("4"), c: term("99"), d: term("99"), e: term("5.5"), f: term("22") } },
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
test("parses base on-demand prices, skipping long-context and 1h variants", () => {
|
|
85
|
+
expect(parsePriceList(offer)).toEqual({
|
|
86
|
+
"Claude Opus 5.5|false": { inputTokens: 4.4e-6, cacheWriteTokens: 5.5e-6, outputTokens: 22e-6 },
|
|
87
|
+
"Claude Opus 5.5|true": { inputTokens: 4e-6 },
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("costs a row, picking global vs geo prices; unknown prices give undefined", () => {
|
|
92
|
+
const prices = parsePriceList(offer);
|
|
93
|
+
expect(rowCost({ modelId: "us.anthropic.claude-opus-5-5", inputTokens: "1000000", outputTokens: "1000000" }, prices)).toBeCloseTo(26.4);
|
|
94
|
+
expect(rowCost({ modelId: "global.anthropic.claude-opus-5-5", inputTokens: "1000000" }, prices)).toBeCloseTo(4);
|
|
95
|
+
expect(rowCost({ modelId: "global.anthropic.claude-opus-5-5", outputTokens: "1" }, prices)).toBeUndefined();
|
|
96
|
+
expect(rowCost({ modelId: "us.anthropic.claude-unknown-9" , inputTokens: "1" }, prices)).toBeUndefined();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("itemized tables merge models per group and cost each model separately", () => {
|
|
100
|
+
const prices = parsePriceList(offer);
|
|
101
|
+
const out = formatUsageTable(
|
|
102
|
+
[
|
|
103
|
+
{ sessionName: "a", modelId: "us.anthropic.claude-opus-5-5", requests: "1", inputTokens: "1000000" },
|
|
104
|
+
{ sessionName: "a", modelId: "global.anthropic.claude-opus-5-5", requests: "1", inputTokens: "1000000" },
|
|
105
|
+
{ sessionName: "b", modelId: "us.anthropic.claude-unknown-9", requests: "1", inputTokens: "5" },
|
|
106
|
+
],
|
|
107
|
+
prices,
|
|
108
|
+
["sessionName"],
|
|
109
|
+
).split("\n");
|
|
110
|
+
expect(out[0]).toMatch(/^Session\s/);
|
|
111
|
+
expect(out[1]).toMatch(/^a\s+2\s+2,000,000\s.*\$8\.40$/);
|
|
112
|
+
expect(out[2]).toMatch(/^b\s+1\s+5\s.*\?$/);
|
|
113
|
+
expect(out[4]).toMatch(/^TOTAL\s+3\s.*\$8\.40\+$/);
|
|
114
|
+
});
|
|
115
|
+
});
|