strands-shell 0.1.0__tar.gz → 0.2.0__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.
Files changed (119) hide show
  1. {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/workflows/ci.yml +23 -0
  2. {strands_shell-0.1.0 → strands_shell-0.2.0}/AGENTS.md +1 -1
  3. {strands_shell-0.1.0 → strands_shell-0.2.0}/Cargo.lock +1 -1
  4. {strands_shell-0.1.0 → strands_shell-0.2.0}/Cargo.toml +1 -1
  5. {strands_shell-0.1.0 → strands_shell-0.2.0}/PKG-INFO +44 -3
  6. {strands_shell-0.1.0 → strands_shell-0.2.0}/README.md +43 -2
  7. {strands_shell-0.1.0 → strands_shell-0.2.0}/index.d.ts +60 -0
  8. {strands_shell-0.1.0 → strands_shell-0.2.0}/index.js +46 -0
  9. {strands_shell-0.1.0 → strands_shell-0.2.0}/package.json +1 -1
  10. {strands_shell-0.1.0 → strands_shell-0.2.0}/pyproject.toml +1 -1
  11. {strands_shell-0.1.0 → strands_shell-0.2.0}/python/strands_shell/__init__.py +135 -0
  12. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/js.rs +111 -0
  13. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/lib.rs +4 -1
  14. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/python.rs +203 -0
  15. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/shell.rs +209 -0
  16. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/vfs_config.rs +25 -3
  17. strands_shell-0.2.0/tests/config.rs +168 -0
  18. strands_shell-0.2.0/tests/js/test_config.mjs +135 -0
  19. strands_shell-0.2.0/tests/python/test_config.py +134 -0
  20. {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/ts/api.types.ts +18 -0
  21. strands_shell-0.1.0/COMMANDS.md +0 -133
  22. {strands_shell-0.1.0 → strands_shell-0.2.0}/.cargo/config.toml +0 -0
  23. {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  24. {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  25. {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  26. {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  27. {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/actions/stamp-version/action.yml +0 -0
  28. {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/dependabot.yml +0 -0
  29. {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/workflows/pr-title.yml +0 -0
  30. {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/workflows/release.yml +0 -0
  31. {strands_shell-0.1.0 → strands_shell-0.2.0}/.gitignore +0 -0
  32. {strands_shell-0.1.0 → strands_shell-0.2.0}/CODE_OF_CONDUCT.md +0 -0
  33. {strands_shell-0.1.0 → strands_shell-0.2.0}/CONTRIBUTING.md +0 -0
  34. {strands_shell-0.1.0 → strands_shell-0.2.0}/LICENSE +0 -0
  35. {strands_shell-0.1.0 → strands_shell-0.2.0}/NOTICE +0 -0
  36. {strands_shell-0.1.0 → strands_shell-0.2.0}/SECURITY.md +0 -0
  37. {strands_shell-0.1.0 → strands_shell-0.2.0}/build.rs +0 -0
  38. {strands_shell-0.1.0 → strands_shell-0.2.0}/scripts/build-wasm.sh +0 -0
  39. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/alias.rs +0 -0
  40. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/cd.rs +0 -0
  41. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/colon.rs +0 -0
  42. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/echo.rs +0 -0
  43. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/export.rs +0 -0
  44. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/find.rs +0 -0
  45. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/getopts.rs +0 -0
  46. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/hash.rs +0 -0
  47. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/local.rs +0 -0
  48. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/lua.rs +0 -0
  49. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/mod.rs +0 -0
  50. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/printf.rs +0 -0
  51. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/pwd.rs +0 -0
  52. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/read.rs +0 -0
  53. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/set.rs +0 -0
  54. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/shift.rs +0 -0
  55. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/test.rs +0 -0
  56. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/trap.rs +0 -0
  57. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/type_cmd.rs +0 -0
  58. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/umask.rs +0 -0
  59. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/unset.rs +0 -0
  60. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/wait.rs +0 -0
  61. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/xargs.rs +0 -0
  62. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/cli.rs +0 -0
  63. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/basename.rs +0 -0
  64. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/cat.rs +0 -0
  65. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/chmod.rs +0 -0
  66. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/cp.rs +0 -0
  67. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/curl.rs +0 -0
  68. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/cut.rs +0 -0
  69. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/date.rs +0 -0
  70. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/dirname.rs +0 -0
  71. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/echo.rs +0 -0
  72. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/env.rs +0 -0
  73. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/false.rs +0 -0
  74. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/grep.rs +0 -0
  75. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/head.rs +0 -0
  76. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/jq.rs +0 -0
  77. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/ln.rs +0 -0
  78. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/ls.rs +0 -0
  79. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/mkdir.rs +0 -0
  80. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/mktemp.rs +0 -0
  81. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/mod.rs +0 -0
  82. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/mv.rs +0 -0
  83. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/pwd.rs +0 -0
  84. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/readlink.rs +0 -0
  85. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/rm.rs +0 -0
  86. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/rmdir.rs +0 -0
  87. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/sed.rs +0 -0
  88. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/sleep.rs +0 -0
  89. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/sort.rs +0 -0
  90. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/tail.rs +0 -0
  91. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/tee.rs +0 -0
  92. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/touch.rs +0 -0
  93. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/tr.rs +0 -0
  94. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/true.rs +0 -0
  95. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/uniq.rs +0 -0
  96. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/wc.rs +0 -0
  97. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/exec.rs +0 -0
  98. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/io.rs +0 -0
  99. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/main.rs +0 -0
  100. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/mcp.rs +0 -0
  101. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/mcp_client.rs +0 -0
  102. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/os.rs +0 -0
  103. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/parser.rs +0 -0
  104. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/prelude.rs +0 -0
  105. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/vfs.rs +0 -0
  106. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/vfs_kernel.rs +0 -0
  107. {strands_shell-0.1.0 → strands_shell-0.2.0}/src/wasm_main.rs +0 -0
  108. {strands_shell-0.1.0 → strands_shell-0.2.0}/strands-shell-macros/Cargo.toml +0 -0
  109. {strands_shell-0.1.0 → strands_shell-0.2.0}/strands-shell-macros/src/lib.rs +0 -0
  110. {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/curl_integration.rs +0 -0
  111. {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/js/test_bindings.mjs +0 -0
  112. {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/js/test_builder.mjs +0 -0
  113. {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/lua_integration.rs +0 -0
  114. {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/mcp_integration.rs +0 -0
  115. {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/python/test_bindings.py +0 -0
  116. {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/python/test_builder.py +0 -0
  117. {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/shell_integration.rs +0 -0
  118. {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/vfs_unit.rs +0 -0
  119. {strands_shell-0.1.0 → strands_shell-0.2.0}/tsconfig.json +0 -0
@@ -146,3 +146,26 @@ jobs:
146
146
 
147
147
  - name: npm test
148
148
  run: npm test
149
+
150
+ # Single required status check. Branch protection can require just this one
151
+ # context ("CI Gate") instead of every matrix leg (Rust/Python/Node/audit),
152
+ # whose names change whenever the matrix changes. This job fails if any
153
+ # needed job failed or was cancelled, so it is a faithful aggregate gate.
154
+ gate:
155
+ name: CI Gate
156
+ if: always()
157
+ needs: [rust, python, audit, node]
158
+ runs-on: ubuntu-latest
159
+ steps:
160
+ - name: Verify all required jobs succeeded
161
+ env:
162
+ RESULTS: ${{ join(needs.*.result, ' ') }}
163
+ run: |
164
+ echo "Needed job results: $RESULTS"
165
+ for result in $RESULTS; do
166
+ if [ "$result" != "success" ]; then
167
+ echo "::error::A required CI job did not succeed (result: $result)."
168
+ exit 1
169
+ fi
170
+ done
171
+ echo "All required CI jobs succeeded."
@@ -163,5 +163,5 @@ PR titles must follow [Conventional Commits](https://www.conventionalcommits.org
163
163
  - [CONTRIBUTING.md](./CONTRIBUTING.md) — human contributor guidelines, full development environment setup
164
164
  - [SECURITY.md](./SECURITY.md) — vulnerability reporting
165
165
  - [README.md](./README.md) — product overview, configuration, supported commands
166
- - [COMMANDS.md](./COMMANDS.md) — per-command status and known gaps
166
+ - [Command Reference](https://strandsagents.com/docs/user-guide/shell/commands/) — per-command status and known gaps
167
167
  - [Strands Agents Documentation](https://strandsagents.com/)
@@ -1606,7 +1606,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
1606
1606
 
1607
1607
  [[package]]
1608
1608
  name = "strands-shell"
1609
- version = "0.1.0"
1609
+ version = "0.2.0"
1610
1610
  dependencies = [
1611
1611
  "async-trait",
1612
1612
  "axum",
@@ -2,7 +2,7 @@
2
2
  name = "strands-shell"
3
3
  # Placeholder — the real version comes from the release git tag, stamped at
4
4
  # build time by .github/actions/stamp-version. Do not bump this by hand.
5
- version = "0.1.0"
5
+ version = "0.2.0"
6
6
  edition = "2024"
7
7
  description = "A virtual shell sandbox for AI agents"
8
8
  license = "Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: strands-shell
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  License-File: LICENSE
@@ -37,7 +37,7 @@ Project-URL: Repository, https://github.com/strands-agents/shell
37
37
  </div>
38
38
 
39
39
  <p>
40
- <a href="https://strandsagents.com/">Documentation</a>
40
+ <a href="https://strandsagents.com/docs/user-guide/shell/">Documentation</a>
41
41
  ◆ <a href="#mcp-server">MCP Server</a>
42
42
  ◆ <a href="#python">Python</a>
43
43
  ◆ <a href="#nodejs">Node.js</a>
@@ -157,6 +157,47 @@ shell = strands_shell.Shell(
157
157
 
158
158
  > ⚠️ **`mode: "direct"` mounts are live.** The agent can read and modify host files in real time. Use only for designated output directories. Never direct-bind directories containing secrets, credentials, or configuration you don't want the agent to modify.
159
159
 
160
+ ### Inspecting configuration
161
+
162
+ A constructed shell exposes a read-only snapshot of how it was configured. This
163
+ is useful when you embed Strands Shell as a sandbox in a larger framework and
164
+ need to build tool descriptions, surface the network allowlist, or report the
165
+ active resource caps from a shell object you were handed.
166
+
167
+ ```python
168
+ shell = strands_shell.Shell(
169
+ allowed_urls=["https://api.example.com/"],
170
+ credentials=[strands_shell.Cred("https://api.example.com/", env_var="API_TOKEN")],
171
+ timeout=30.0,
172
+ )
173
+
174
+ cfg = shell.config # a frozen ShellConfig snapshot
175
+ cfg.allowed_urls # ('https://api.example.com/',)
176
+ cfg.timeout # 30.0
177
+ cfg.credentials[0].url # 'https://api.example.com/'
178
+ cfg.credentials[0].env_var # 'API_TOKEN' (the secret value is never exposed)
179
+ ```
180
+
181
+ ```javascript
182
+ const shell = await Shell.create({
183
+ allowedUrls: ['https://api.example.com/'],
184
+ credentials: [{ url: 'https://api.example.com/', envVar: 'API_TOKEN' }],
185
+ timeout: 30,
186
+ })
187
+
188
+ const cfg = await shell.config() // a deep-frozen snapshot object
189
+ cfg.allowedUrls // ['https://api.example.com/']
190
+ cfg.timeout // 30
191
+ cfg.credentials[0].envVar // 'API_TOKEN' (the secret value is never exposed)
192
+ ```
193
+
194
+ The snapshot reports binds, credentials, the network allowlist, environment
195
+ variables, umask, timeout, and resource limits. Credential **secrets are never
196
+ included** — each entry reports its URL pattern, kind, and source (a literal
197
+ token was supplied, or the name of the environment variable it is read from),
198
+ so you can reason about credentials without the agent or your tooling ever
199
+ seeing the secret itself.
200
+
160
201
  ### TOML
161
202
 
162
203
  You can load all of this from a config file instead:
@@ -225,7 +266,7 @@ Out of the box, the shell is an empty sandbox — no files, no network, no crede
225
266
 
226
267
  The commands agents use constantly: `grep`, `find`, `cat`, `head`, `tail`, `jq` for reading and searching. `sed`, `sort`, `tr`, `cut` for transforming output. `cp`, `mv`, `rm`, `mkdir` for managing files. `curl` for HTTP (SSRF-guarded, credentials auto-injected). `lua` for scripting when shell gets awkward.
227
268
 
228
- [COMMANDS.md](COMMANDS.md) has the full inventory with implementation status, supported flags, and known gaps vs GNU coreutils.
269
+ The [full command reference](https://strandsagents.com/docs/user-guide/shell/commands/) has the inventory with implementation status, supported flags, and known gaps vs GNU coreutils.
229
270
 
230
271
  ## File Operations API
231
272
 
@@ -21,7 +21,7 @@
21
21
  </div>
22
22
 
23
23
  <p>
24
- <a href="https://strandsagents.com/">Documentation</a>
24
+ <a href="https://strandsagents.com/docs/user-guide/shell/">Documentation</a>
25
25
  ◆ <a href="#mcp-server">MCP Server</a>
26
26
  ◆ <a href="#python">Python</a>
27
27
  ◆ <a href="#nodejs">Node.js</a>
@@ -141,6 +141,47 @@ shell = strands_shell.Shell(
141
141
 
142
142
  > ⚠️ **`mode: "direct"` mounts are live.** The agent can read and modify host files in real time. Use only for designated output directories. Never direct-bind directories containing secrets, credentials, or configuration you don't want the agent to modify.
143
143
 
144
+ ### Inspecting configuration
145
+
146
+ A constructed shell exposes a read-only snapshot of how it was configured. This
147
+ is useful when you embed Strands Shell as a sandbox in a larger framework and
148
+ need to build tool descriptions, surface the network allowlist, or report the
149
+ active resource caps from a shell object you were handed.
150
+
151
+ ```python
152
+ shell = strands_shell.Shell(
153
+ allowed_urls=["https://api.example.com/"],
154
+ credentials=[strands_shell.Cred("https://api.example.com/", env_var="API_TOKEN")],
155
+ timeout=30.0,
156
+ )
157
+
158
+ cfg = shell.config # a frozen ShellConfig snapshot
159
+ cfg.allowed_urls # ('https://api.example.com/',)
160
+ cfg.timeout # 30.0
161
+ cfg.credentials[0].url # 'https://api.example.com/'
162
+ cfg.credentials[0].env_var # 'API_TOKEN' (the secret value is never exposed)
163
+ ```
164
+
165
+ ```javascript
166
+ const shell = await Shell.create({
167
+ allowedUrls: ['https://api.example.com/'],
168
+ credentials: [{ url: 'https://api.example.com/', envVar: 'API_TOKEN' }],
169
+ timeout: 30,
170
+ })
171
+
172
+ const cfg = await shell.config() // a deep-frozen snapshot object
173
+ cfg.allowedUrls // ['https://api.example.com/']
174
+ cfg.timeout // 30
175
+ cfg.credentials[0].envVar // 'API_TOKEN' (the secret value is never exposed)
176
+ ```
177
+
178
+ The snapshot reports binds, credentials, the network allowlist, environment
179
+ variables, umask, timeout, and resource limits. Credential **secrets are never
180
+ included** — each entry reports its URL pattern, kind, and source (a literal
181
+ token was supplied, or the name of the environment variable it is read from),
182
+ so you can reason about credentials without the agent or your tooling ever
183
+ seeing the secret itself.
184
+
144
185
  ### TOML
145
186
 
146
187
  You can load all of this from a config file instead:
@@ -209,7 +250,7 @@ Out of the box, the shell is an empty sandbox — no files, no network, no crede
209
250
 
210
251
  The commands agents use constantly: `grep`, `find`, `cat`, `head`, `tail`, `jq` for reading and searching. `sed`, `sort`, `tr`, `cut` for transforming output. `cp`, `mv`, `rm`, `mkdir` for managing files. `curl` for HTTP (SSRF-guarded, credentials auto-injected). `lua` for scripting when shell gets awkward.
211
252
 
212
- [COMMANDS.md](COMMANDS.md) has the full inventory with implementation status, supported flags, and known gaps vs GNU coreutils.
253
+ The [full command reference](https://strandsagents.com/docs/user-guide/shell/commands/) has the inventory with implementation status, supported flags, and known gaps vs GNU coreutils.
213
254
 
214
255
  ## File Operations API
215
256
 
@@ -63,6 +63,61 @@ export interface ShellConfig {
63
63
  configFile?: string
64
64
  }
65
65
 
66
+ /** A bind mount in a {@link ShellConfigSnapshot}. */
67
+ export interface BindInfo {
68
+ readonly source: string
69
+ readonly destination: string
70
+ /** `'copy'` (build-time snapshot) or `'direct'` (host passthrough). */
71
+ readonly mode: 'copy' | 'direct'
72
+ readonly readonly: boolean
73
+ }
74
+
75
+ /**
76
+ * A credential rule in a {@link ShellConfigSnapshot}.
77
+ *
78
+ * The secret value is never exposed. `envVar` holds the environment-variable
79
+ * name when the credential was configured from the environment; `fromLiteral`
80
+ * is `true` when a literal token was supplied directly (its value is withheld).
81
+ */
82
+ export interface CredInfo {
83
+ readonly url: string
84
+ readonly kind: 'bearer' | 'query'
85
+ readonly methods: readonly string[]
86
+ readonly param: string | null
87
+ readonly envVar: string | null
88
+ readonly fromLiteral: boolean
89
+ }
90
+
91
+ /** Resource caps in a {@link ShellConfigSnapshot}. Every cap is present. */
92
+ export interface LimitsInfo {
93
+ readonly maxDepth: number
94
+ readonly maxOutput: number
95
+ readonly maxFds: number
96
+ readonly maxBgJobs: number
97
+ readonly maxPipeline: number
98
+ readonly maxInput: number
99
+ readonly maxFileSize: number
100
+ readonly maxInodes: number
101
+ }
102
+
103
+ /**
104
+ * A read-only snapshot of how a {@link Shell} was configured, returned by
105
+ * {@link Shell.config}. Lets an embedder introspect a constructed shell — to
106
+ * build tool descriptions, surface the network allowlist, or report active
107
+ * limits — without having held onto the {@link ShellConfig} it was built from.
108
+ * Secret values are never included.
109
+ */
110
+ export interface ShellConfigSnapshot {
111
+ readonly binds: readonly BindInfo[]
112
+ readonly credentials: readonly CredInfo[]
113
+ readonly allowedUrls: readonly string[]
114
+ readonly env: Readonly<Record<string, string>>
115
+ readonly umask: number
116
+ /** Per-command timeout in seconds, or `null` for no timeout. */
117
+ readonly timeout: number | null
118
+ readonly limits: LimitsInfo
119
+ }
120
+
66
121
  /** errno-style discriminator carried on every {@link ShellError}. */
67
122
  export type ShellErrorCode = 'ENOENT' | 'EACCES' | 'EFBIG' | 'EOTHER'
68
123
 
@@ -89,6 +144,11 @@ export declare class Shell {
89
144
  setEnv(key: string, value: string): Promise<void>
90
145
  /** Get an environment variable. */
91
146
  getEnv(key: string): Promise<string | null>
147
+ /**
148
+ * A read-only snapshot of how this shell was configured. Secret values are
149
+ * never included — see {@link CredInfo}. The returned object is deep-frozen.
150
+ */
151
+ config(): Promise<ShellConfigSnapshot>
92
152
  /** Read a file as raw bytes. Rejects with {@link NotFoundError} if missing. */
93
153
  readFile(path: string): Promise<Uint8Array>
94
154
  /** Write raw bytes; creates parent dirs (mkdir -p) and truncates. */
@@ -190,6 +190,52 @@ class Shell {
190
190
  return this._inner.getEnv(key)
191
191
  }
192
192
 
193
+ // ---- Configuration introspection ----
194
+
195
+ /**
196
+ * A read-only snapshot of how this shell was configured. Resolves to an
197
+ * object reporting binds, credentials, allowedUrls, env, umask, timeout,
198
+ * and limits.
199
+ *
200
+ * Secret values are never included: each credential reports its `url`
201
+ * pattern, `kind`, and source (`envVar` name, or `fromLiteral: true`), but
202
+ * never the token itself. The returned object and its nested objects/arrays
203
+ * are deep-frozen so the snapshot cannot be mutated.
204
+ */
205
+ async config() {
206
+ const c = await this._inner.config()
207
+ const snapshot = {
208
+ binds: c.binds.map((b) =>
209
+ Object.freeze({
210
+ source: b.source,
211
+ destination: b.destination,
212
+ mode: b.mode,
213
+ readonly: b.readonly,
214
+ }),
215
+ ),
216
+ credentials: c.credentials.map((cr) =>
217
+ Object.freeze({
218
+ url: cr.url,
219
+ kind: cr.kind,
220
+ methods: Object.freeze([...cr.methods]),
221
+ param: cr.param ?? null,
222
+ envVar: cr.envVar ?? null,
223
+ fromLiteral: cr.fromLiteral,
224
+ }),
225
+ ),
226
+ allowedUrls: c.allowedUrls,
227
+ env: c.env,
228
+ umask: c.umask,
229
+ timeout: c.timeout ?? null,
230
+ limits: Object.freeze({ ...c.limits }),
231
+ }
232
+ Object.freeze(snapshot.binds)
233
+ Object.freeze(snapshot.credentials)
234
+ Object.freeze(snapshot.allowedUrls)
235
+ Object.freeze(snapshot.env)
236
+ return Object.freeze(snapshot)
237
+ }
238
+
193
239
  // ---- VFS file operations ----
194
240
 
195
241
  readFile(path) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strands-agents/shell",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Strands Shell — a virtual shell sandbox for AI agents (Node.js bindings)",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -6,7 +6,7 @@ build-backend = "maturin"
6
6
  name = "strands-shell"
7
7
  # Placeholder — the real version comes from the release git tag, stamped at
8
8
  # build time by .github/actions/stamp-version. Do not bump this by hand.
9
- version = "0.1.0"
9
+ version = "0.2.0"
10
10
  description = "A virtual shell for AI agents"
11
11
  readme = "README.md"
12
12
  requires-python = ">=3.10"
@@ -27,6 +27,10 @@ __all__ = [
27
27
  "Limits",
28
28
  "Output",
29
29
  "FileInfo",
30
+ "ShellConfig",
31
+ "ConfigBind",
32
+ "ConfigCred",
33
+ "ConfigLimits",
30
34
  "ShellError",
31
35
  "FileNotFoundError",
32
36
  "PermissionDeniedError",
@@ -99,6 +103,120 @@ class Limits:
99
103
  max_depth: int = 64
100
104
 
101
105
 
106
+ # --------------------------------------------------------------------------- #
107
+ # Read-only config snapshot (returned by ``Shell.config``)
108
+ # --------------------------------------------------------------------------- #
109
+
110
+
111
+ @dataclass(frozen=True)
112
+ class ConfigBind:
113
+ """A bind mount as reported by :attr:`Shell.config`.
114
+
115
+ Read-only view; mirrors the :class:`Bind` you pass in, with ``mode``
116
+ normalized to ``"copy"`` / ``"direct"``.
117
+ """
118
+
119
+ source: str
120
+ destination: str
121
+ mode: Literal["direct", "copy"]
122
+ readonly: bool
123
+
124
+
125
+ @dataclass(frozen=True)
126
+ class ConfigCred:
127
+ """A credential rule as reported by :attr:`Shell.config`.
128
+
129
+ The secret value is **never** exposed. ``env_var`` holds the environment
130
+ variable name when the credential was configured from the environment;
131
+ ``from_literal`` is ``True`` when a literal token was supplied directly
132
+ (its value is still withheld).
133
+ """
134
+
135
+ url: str
136
+ kind: str
137
+ methods: tuple[str, ...]
138
+ param: str | None
139
+ env_var: str | None
140
+ from_literal: bool
141
+
142
+
143
+ @dataclass(frozen=True)
144
+ class ConfigLimits:
145
+ """Resource caps as reported by :attr:`Shell.config`.
146
+
147
+ Unlike :class:`Limits` (the input bundle), this view always carries every
148
+ cap with its concrete active value.
149
+ """
150
+
151
+ max_depth: int
152
+ max_output: int
153
+ max_fds: int
154
+ max_bg_jobs: int
155
+ max_pipeline: int
156
+ max_input: int
157
+ max_file_size: int
158
+ max_inodes: int
159
+
160
+
161
+ @dataclass(frozen=True)
162
+ class ShellConfig:
163
+ """A read-only snapshot of how a :class:`Shell` was configured.
164
+
165
+ Returned by :attr:`Shell.config`. Lets an embedder introspect a constructed
166
+ shell after the fact (to build tool descriptions, surface the network
167
+ allowlist, or report active limits) without having held onto the
168
+ construction arguments. Secret values are never included.
169
+ """
170
+
171
+ binds: tuple[ConfigBind, ...]
172
+ credentials: tuple[ConfigCred, ...]
173
+ allowed_urls: tuple[str, ...]
174
+ env: dict[str, str]
175
+ umask: int
176
+ timeout: float | None
177
+ limits: ConfigLimits
178
+
179
+
180
+ def _snapshot_from_native(native_config: object) -> ShellConfig:
181
+ """Convert a ``_native.ShellConfig`` into the frozen public dataclass."""
182
+ return ShellConfig(
183
+ binds=tuple(
184
+ ConfigBind(
185
+ source=b.source,
186
+ destination=b.destination,
187
+ mode=b.mode, # type: ignore[arg-type]
188
+ readonly=b.readonly,
189
+ )
190
+ for b in native_config.binds
191
+ ),
192
+ credentials=tuple(
193
+ ConfigCred(
194
+ url=c.url,
195
+ kind=c.kind,
196
+ methods=tuple(c.methods),
197
+ param=c.param,
198
+ env_var=c.env_var,
199
+ from_literal=c.from_literal,
200
+ )
201
+ for c in native_config.credentials
202
+ ),
203
+ allowed_urls=tuple(native_config.allowed_urls),
204
+ env=dict(native_config.env),
205
+ umask=native_config.umask,
206
+ timeout=native_config.timeout,
207
+ limits=ConfigLimits(
208
+ max_depth=native_config.limits.max_depth,
209
+ max_output=native_config.limits.max_output,
210
+ max_fds=native_config.limits.max_fds,
211
+ max_bg_jobs=native_config.limits.max_bg_jobs,
212
+ max_pipeline=native_config.limits.max_pipeline,
213
+ max_input=native_config.limits.max_input,
214
+ max_file_size=native_config.limits.max_file_size,
215
+ max_inodes=native_config.limits.max_inodes,
216
+ ),
217
+ )
218
+
219
+
102
220
  # --------------------------------------------------------------------------- #
103
221
  # Exception hierarchy
104
222
  # --------------------------------------------------------------------------- #
@@ -251,6 +369,23 @@ class Shell:
251
369
  def get_env(self, key: str) -> str | None:
252
370
  return self._shell.get_env(key)
253
371
 
372
+ # ---- Configuration introspection ----
373
+
374
+ @property
375
+ def config(self) -> ShellConfig:
376
+ """A read-only snapshot of how this shell was configured.
377
+
378
+ Reports bind mounts, credential rules, the network allowlist, seeded
379
+ environment variables, umask, timeout, and resource caps. Useful for
380
+ introspecting a constructed shell — e.g. to build tool descriptions or
381
+ surface the allowlist — without having held onto the constructor args.
382
+
383
+ Secret values are never included: each :class:`ConfigCred` reports its
384
+ URL pattern, kind, and source (literal vs environment variable name),
385
+ but never the token itself.
386
+ """
387
+ return _snapshot_from_native(self._shell.config())
388
+
254
389
  # ---- VFS file operations ----
255
390
  # Each accepts **kwargs and ignores unknown keys, matching the
256
391
  # kwargs-tolerant strands.sandbox.Sandbox contract the adapter passes
@@ -166,6 +166,66 @@ pub struct FileInfo {
166
166
  pub size: Option<u32>,
167
167
  }
168
168
 
169
+ // ---------------------------------------------------------------------------
170
+ // Read-only config snapshot — plain object shapes mirrored from the core
171
+ // `crate::shell::{ShellConfig, BindInfo, CredInfo, LimitsInfo}` view types.
172
+ // Returned by `Shell.config()`. Secret values are never carried — see
173
+ // `CredInfo`.
174
+ // ---------------------------------------------------------------------------
175
+
176
+ /// A single bind mount in a config snapshot.
177
+ #[napi(object)]
178
+ pub struct BindInfo {
179
+ pub source: String,
180
+ pub destination: String,
181
+ /// `"copy"` or `"direct"`.
182
+ pub mode: String,
183
+ pub readonly: bool,
184
+ }
185
+
186
+ /// A single credential rule in a config snapshot. Never carries the secret.
187
+ #[napi(object)]
188
+ pub struct CredInfo {
189
+ pub url: String,
190
+ /// `"bearer"` or `"query"`.
191
+ pub kind: String,
192
+ pub methods: Vec<String>,
193
+ pub param: Option<String>,
194
+ /// Name of the env var the secret is read from, or `null` for a literal.
195
+ pub env_var: Option<String>,
196
+ /// True when a literal token was supplied (value itself never exposed).
197
+ pub from_literal: bool,
198
+ }
199
+
200
+ /// Resource caps in a config snapshot.
201
+ #[napi(object)]
202
+ pub struct LimitsInfo {
203
+ // f64 because JS numbers are doubles; values fit comfortably (max_inodes
204
+ // default 10_000, max_file_size default 10 MiB — well within 2^53).
205
+ pub max_depth: f64,
206
+ pub max_output: f64,
207
+ pub max_fds: f64,
208
+ pub max_bg_jobs: f64,
209
+ pub max_pipeline: f64,
210
+ pub max_input: f64,
211
+ pub max_file_size: f64,
212
+ pub max_inodes: f64,
213
+ }
214
+
215
+ /// A read-only snapshot of how a `Shell` was configured.
216
+ #[napi(object)]
217
+ pub struct ShellConfig {
218
+ pub binds: Vec<BindInfo>,
219
+ pub credentials: Vec<CredInfo>,
220
+ pub allowed_urls: Vec<String>,
221
+ /// Seeded environment variables as a plain object.
222
+ pub env: std::collections::HashMap<String, String>,
223
+ pub umask: f64,
224
+ /// Per-command timeout in seconds, or `null` for no timeout.
225
+ pub timeout: Option<f64>,
226
+ pub limits: LimitsInfo,
227
+ }
228
+
169
229
  // ---------------------------------------------------------------------------
170
230
  // ShellBuilder
171
231
  // ---------------------------------------------------------------------------
@@ -400,6 +460,57 @@ impl Shell {
400
460
  .await
401
461
  }
402
462
 
463
+ /// Read-only snapshot of the configuration this shell was built with.
464
+ ///
465
+ /// Mirrors `Shell::config()` in the core. Never carries secret values —
466
+ /// each credential reports its source (literal vs env-var name) only.
467
+ #[napi]
468
+ pub async fn config(&self) -> Result<ShellConfig> {
469
+ self.worker
470
+ .run(move |shell, _rt| {
471
+ let c = shell.config();
472
+ ShellConfig {
473
+ binds: c
474
+ .binds
475
+ .iter()
476
+ .map(|b| BindInfo {
477
+ source: b.source.clone(),
478
+ destination: b.destination.clone(),
479
+ mode: b.mode.to_string(),
480
+ readonly: b.readonly,
481
+ })
482
+ .collect(),
483
+ credentials: c
484
+ .credentials
485
+ .iter()
486
+ .map(|cr| CredInfo {
487
+ url: cr.url.clone(),
488
+ kind: cr.kind.to_string(),
489
+ methods: cr.methods.clone(),
490
+ param: cr.param.clone(),
491
+ env_var: cr.env_var.clone(),
492
+ from_literal: cr.from_literal,
493
+ })
494
+ .collect(),
495
+ allowed_urls: c.allowed_urls.clone(),
496
+ env: c.env.iter().cloned().collect(),
497
+ umask: c.umask as f64,
498
+ timeout: c.timeout_secs,
499
+ limits: LimitsInfo {
500
+ max_depth: c.limits.max_depth as f64,
501
+ max_output: c.limits.max_output as f64,
502
+ max_fds: c.limits.max_fds as f64,
503
+ max_bg_jobs: c.limits.max_bg_jobs as f64,
504
+ max_pipeline: c.limits.max_pipeline as f64,
505
+ max_input: c.limits.max_input as f64,
506
+ max_file_size: c.limits.max_file_size as f64,
507
+ max_inodes: c.limits.max_inodes as f64,
508
+ },
509
+ }
510
+ })
511
+ .await
512
+ }
513
+
403
514
  /// Read a file from the virtual filesystem as raw bytes.
404
515
  #[napi]
405
516
  pub async fn read_file(&self, path: String) -> Result<Uint8Array> {
@@ -164,5 +164,8 @@ pub mod python;
164
164
  pub mod js;
165
165
 
166
166
  // Primary public API
167
- pub use shell::{FileInfo, FileOpErrorKind, Output, Shell, ShellBuilder};
167
+ pub use shell::{
168
+ BindInfo, CredInfo, FileInfo, FileOpErrorKind, LimitsInfo, Output, Shell, ShellBuilder,
169
+ ShellConfig,
170
+ };
168
171
  pub use vfs_config::CredKind;