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.
- {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/workflows/ci.yml +23 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/AGENTS.md +1 -1
- {strands_shell-0.1.0 → strands_shell-0.2.0}/Cargo.lock +1 -1
- {strands_shell-0.1.0 → strands_shell-0.2.0}/Cargo.toml +1 -1
- {strands_shell-0.1.0 → strands_shell-0.2.0}/PKG-INFO +44 -3
- {strands_shell-0.1.0 → strands_shell-0.2.0}/README.md +43 -2
- {strands_shell-0.1.0 → strands_shell-0.2.0}/index.d.ts +60 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/index.js +46 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/package.json +1 -1
- {strands_shell-0.1.0 → strands_shell-0.2.0}/pyproject.toml +1 -1
- {strands_shell-0.1.0 → strands_shell-0.2.0}/python/strands_shell/__init__.py +135 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/js.rs +111 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/lib.rs +4 -1
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/python.rs +203 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/shell.rs +209 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/vfs_config.rs +25 -3
- strands_shell-0.2.0/tests/config.rs +168 -0
- strands_shell-0.2.0/tests/js/test_config.mjs +135 -0
- strands_shell-0.2.0/tests/python/test_config.py +134 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/ts/api.types.ts +18 -0
- strands_shell-0.1.0/COMMANDS.md +0 -133
- {strands_shell-0.1.0 → strands_shell-0.2.0}/.cargo/config.toml +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/actions/stamp-version/action.yml +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/dependabot.yml +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/workflows/pr-title.yml +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/.github/workflows/release.yml +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/.gitignore +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/CODE_OF_CONDUCT.md +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/CONTRIBUTING.md +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/LICENSE +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/NOTICE +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/SECURITY.md +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/build.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/scripts/build-wasm.sh +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/alias.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/cd.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/colon.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/echo.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/export.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/find.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/getopts.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/hash.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/local.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/lua.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/mod.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/printf.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/pwd.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/read.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/set.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/shift.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/test.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/trap.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/type_cmd.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/umask.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/unset.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/wait.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/builtins/xargs.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/cli.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/basename.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/cat.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/chmod.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/cp.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/curl.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/cut.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/date.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/dirname.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/echo.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/env.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/false.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/grep.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/head.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/jq.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/ln.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/ls.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/mkdir.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/mktemp.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/mod.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/mv.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/pwd.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/readlink.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/rm.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/rmdir.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/sed.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/sleep.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/sort.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/tail.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/tee.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/touch.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/tr.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/true.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/uniq.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/commands/wc.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/exec.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/io.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/main.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/mcp.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/mcp_client.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/os.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/parser.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/prelude.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/vfs.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/vfs_kernel.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/src/wasm_main.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/strands-shell-macros/Cargo.toml +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/strands-shell-macros/src/lib.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/curl_integration.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/js/test_bindings.mjs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/js/test_builder.mjs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/lua_integration.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/mcp_integration.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/python/test_bindings.py +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/python/test_builder.py +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/shell_integration.rs +0 -0
- {strands_shell-0.1.0 → strands_shell-0.2.0}/tests/vfs_unit.rs +0 -0
- {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
|
-
- [
|
|
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/)
|
|
@@ -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.
|
|
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.
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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) {
|
|
@@ -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.
|
|
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::{
|
|
167
|
+
pub use shell::{
|
|
168
|
+
BindInfo, CredInfo, FileInfo, FileOpErrorKind, LimitsInfo, Output, Shell, ShellBuilder,
|
|
169
|
+
ShellConfig,
|
|
170
|
+
};
|
|
168
171
|
pub use vfs_config::CredKind;
|