shell-next 0.1.2__tar.gz → 0.1.3__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.
- {shell_next-0.1.2 → shell_next-0.1.3}/CHANGELOG.md +19 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/PKG-INFO +8 -1
- {shell_next-0.1.2 → shell_next-0.1.3}/README.md +7 -0
- shell_next-0.1.3/docs/assets/shell-next-logo.png +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/docs/assets/stylesheets/brand.css +96 -105
- {shell_next-0.1.2 → shell_next-0.1.3}/docs/backends.md +2 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/docs/development/architecture.md +5 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/docs/development/documentation.md +20 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/docs/development/specification.md +28 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/docs/getting-started.md +2 -1
- {shell_next-0.1.2 → shell_next-0.1.3}/docs/index.md +4 -1
- shell_next-0.1.3/docs/tutorials/01-commands.md +69 -0
- shell_next-0.1.3/docs/tutorials/02-navigation.md +69 -0
- shell_next-0.1.3/docs/tutorials/03-environment.md +74 -0
- shell_next-0.1.3/docs/tutorials/04-scripts.md +97 -0
- shell_next-0.1.3/docs/tutorials/05-capture.md +88 -0
- shell_next-0.1.3/docs/tutorials/06-input-and-events.md +115 -0
- shell_next-0.1.3/docs/tutorials/07-errors-and-lifecycle.md +108 -0
- shell_next-0.1.3/docs/tutorials/08-concurrency.md +74 -0
- shell_next-0.1.3/docs/tutorials/09-sudo.md +128 -0
- shell_next-0.1.3/docs/tutorials/10-mocking.md +115 -0
- shell_next-0.1.3/docs/tutorials/11-workflow.md +103 -0
- shell_next-0.1.3/docs/tutorials/index.md +49 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/docs/usage.md +66 -0
- shell_next-0.1.3/examples/01-commands.py +37 -0
- shell_next-0.1.3/examples/02-navigation.py +39 -0
- shell_next-0.1.3/examples/03-environment.py +41 -0
- shell_next-0.1.3/examples/04-scripts.py +56 -0
- shell_next-0.1.3/examples/05-capture.py +53 -0
- shell_next-0.1.3/examples/06-input-and-events.py +73 -0
- shell_next-0.1.3/examples/07-errors-and-lifecycle.py +65 -0
- shell_next-0.1.3/examples/08-concurrency.py +42 -0
- shell_next-0.1.3/examples/09-sudo.py +69 -0
- shell_next-0.1.3/examples/10-mocking.py +81 -0
- shell_next-0.1.3/examples/11-workflow.py +68 -0
- shell_next-0.1.3/examples/README.md +15 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/mkdocs.yml +13 -1
- {shell_next-0.1.2 → shell_next-0.1.3}/pyproject.toml +1 -1
- shell_next-0.1.3/shell-next-logo.png +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/cmd/syntax.py +2 -2
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/driver.py +218 -215
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/session.py +4 -1
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/config.py +7 -2
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/privilege.py +47 -1
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/bash/test_sudo_integration.py +23 -0
- shell_next-0.1.3/tests/backends/mock/test_session_password.py +136 -0
- shell_next-0.1.3/tests/backends/native/test_cmd_completion.py +18 -0
- shell_next-0.1.3/tests/contracts/test_tutorial_examples.py +27 -0
- shell_next-0.1.3/tests/docs/test_tutorials.py +120 -0
- shell_next-0.1.3/tests/models/test_session_password.py +27 -0
- shell_next-0.1.3/tests/support/tutorials.py +17 -0
- shell_next-0.1.2/docs/assets/shell-next-logo.png +0 -0
- shell_next-0.1.2/shell-next-logo.png +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/.gitattributes +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/.github/workflows/ci.yml +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/.github/workflows/docs.yml +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/.github/workflows/publish.yml +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/.gitignore +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/AGENTS.md +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/LICENSE +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/docs/development/release.md +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/docs/testing.md +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/docs/troubleshooting.md +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/scripts/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/scripts/docs/validate.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/scripts/quality/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/scripts/quality/check_source.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/scripts/quality/rhel8.sh +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/scripts/release/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/scripts/release/check_wheel.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/scripts/release/client.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/scripts/release/github.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/scripts/release/publish.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/scripts/release/verify.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/scripts/release/wheel_probe.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/shell-next Development Specification.md +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/bash/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/bash/authentication.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/bash/containment.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/bash/password_channel.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/bash/syntax.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/cmd/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/mock/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/mock/driver.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/mock/scenario.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/mock/session.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/mock/state.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/bridge.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/channels.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/containment.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/preparation.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/process.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/syntax.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/termination.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/powershell/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/powershell/driver.ps1 +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/powershell/syntax.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/protocol.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/windows/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/windows/containment.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/windows/limits.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/errors.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/capture.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/execution.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/finalization.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/handle.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/lease.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/observation.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/operations.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/output.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/capabilities.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/commands.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/input.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/representation.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/results.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/state.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/py.typed +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/bash/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/bash/test_authentication.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/bash/test_password_channel.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/mock/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/mock/test_input_edges.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/mock/test_privilege.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/mock/test_session.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/mock/test_state.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/mock/test_virtual_observation.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_bridge.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_channels.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_interrupt_integration.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_platform_containment.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_process_failures.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_resources_integration.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_state_integration.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_stdin_integration.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_syntax.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_termination.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/conftest.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/contracts/test_execution.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/contracts/test_lifecycle.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/contracts/test_queue_cancellation.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/docs/test_validation.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/test_capture.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/test_capture_failures.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/test_failures.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/test_lease_races.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/test_observation_edges.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/test_operations.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/test_representation.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/test_result_content.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/models/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/models/test_representation.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/models/test_results.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/models/test_values.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/support/__init__.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/support/native.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/tests/support/sessions.py +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/wiki/Backend-Support.md +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/wiki/Command-Lifecycle.md +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/wiki/Getting-Started.md +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/wiki/Home.md +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/wiki/README.md +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/wiki/Testing-with-Mocks.md +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/wiki/Troubleshooting.md +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/wiki/_Footer.md +0 -0
- {shell_next-0.1.2 → shell_next-0.1.3}/wiki/_Sidebar.md +0 -0
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.3
|
|
4
|
+
|
|
5
|
+
Released 2026-09-13.
|
|
6
|
+
|
|
7
|
+
- Fix intermittent cmd session loss by reporting completion after the private
|
|
8
|
+
batch wrapper returns, before removing its files.
|
|
9
|
+
|
|
10
|
+
- Accept an upfront text or bytes sudo password in `SessionConfig`, reusing it
|
|
11
|
+
for explicitly elevated Bash commands through the private authentication
|
|
12
|
+
channel. Preserve per-command provider precedence and secret-free diagnostics.
|
|
13
|
+
|
|
14
|
+
- Add an ordered tutorial with complete runnable examples for commands, navigation,
|
|
15
|
+
environment, native scripts, capture, input, lifecycle, concurrency, sudo,
|
|
16
|
+
deterministic mocks, and a composed workflow across Bash, PowerShell, and cmd.
|
|
17
|
+
- Verify tutorial discovery, example-file parity, and exact published source
|
|
18
|
+
execution with native platform and deterministic mock checks.
|
|
19
|
+
- Update the project logo and show it only in the home page content on the
|
|
20
|
+
documentation site, removing the sidebar logo.
|
|
21
|
+
|
|
3
22
|
## 0.1.2
|
|
4
23
|
|
|
5
24
|
Released 2026-09-11.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: shell-next
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Persistent asynchronous shell sessions with deterministic test doubles
|
|
5
5
|
Project-URL: Documentation, https://gokurakujoudo.github.io/shell-next/
|
|
6
6
|
Project-URL: Repository, https://github.com/gokurakujoudo/shell-next
|
|
@@ -41,6 +41,13 @@ Description-Content-Type: text/markdown
|
|
|
41
41
|
|
|
42
42
|
Persistent asynchronous Bash, PowerShell, and cmd sessions for Python 3.14+.
|
|
43
43
|
|
|
44
|
+
Bash sudo supports an upfront `SessionConfig(sudo_password=...)` reused by
|
|
45
|
+
explicitly elevated commands, or an asynchronous per-command password provider.
|
|
46
|
+
|
|
47
|
+
Follow the [step-by-step tutorial](docs/tutorials/index.md) for examples from basic
|
|
48
|
+
commands through a complete workflow on all three backends. Full standalone code
|
|
49
|
+
is in [examples/](examples/README.md).
|
|
50
|
+
|
|
44
51
|
[Documentation](https://gokurakujoudo.github.io/shell-next/) ·
|
|
45
52
|
[PyPI](https://pypi.org/project/shell-next/) · [Wiki drafts](wiki/README.md)
|
|
46
53
|
|
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
|
|
13
13
|
Persistent asynchronous Bash, PowerShell, and cmd sessions for Python 3.14+.
|
|
14
14
|
|
|
15
|
+
Bash sudo supports an upfront `SessionConfig(sudo_password=...)` reused by
|
|
16
|
+
explicitly elevated commands, or an asynchronous per-command password provider.
|
|
17
|
+
|
|
18
|
+
Follow the [step-by-step tutorial](docs/tutorials/index.md) for examples from basic
|
|
19
|
+
commands through a complete workflow on all three backends. Full standalone code
|
|
20
|
+
is in [examples/](examples/README.md).
|
|
21
|
+
|
|
15
22
|
[Documentation](https://gokurakujoudo.github.io/shell-next/) ·
|
|
16
23
|
[PyPI](https://pypi.org/project/shell-next/) · [Wiki drafts](wiki/README.md)
|
|
17
24
|
|
|
Binary file
|
|
@@ -1,105 +1,96 @@
|
|
|
1
|
-
:root {
|
|
2
|
-
--shell-ink: #20303a;
|
|
3
|
-
--shell-blue: #007caa;
|
|
4
|
-
--shell-accent: #16b8ef;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
.wy-side-nav-search,
|
|
8
|
-
.wy-nav-top {
|
|
9
|
-
background: var(--shell-ink);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
.wy-side-nav-search
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
.
|
|
31
|
-
.
|
|
32
|
-
color: var(--shell-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.hero img {
|
|
98
|
-
width: 170px;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.rst-content table.docutils {
|
|
102
|
-
display: block;
|
|
103
|
-
overflow-x: auto;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
1
|
+
:root {
|
|
2
|
+
--shell-ink: #20303a;
|
|
3
|
+
--shell-blue: #007caa;
|
|
4
|
+
--shell-accent: #16b8ef;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.wy-side-nav-search,
|
|
8
|
+
.wy-nav-top {
|
|
9
|
+
background: var(--shell-ink);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.wy-side-nav-search input[type="text"] {
|
|
13
|
+
border-color: var(--shell-accent);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.wy-nav-content {
|
|
17
|
+
max-width: 1040px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.wy-menu-vertical header,
|
|
21
|
+
.wy-menu-vertical p.caption {
|
|
22
|
+
color: var(--shell-accent);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.rst-content a {
|
|
26
|
+
color: var(--shell-blue);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.rst-content h1,
|
|
30
|
+
.rst-content h2,
|
|
31
|
+
.rst-content h3 {
|
|
32
|
+
color: var(--shell-ink);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.hero {
|
|
36
|
+
display: grid;
|
|
37
|
+
grid-template-columns: 220px 1fr;
|
|
38
|
+
align-items: center;
|
|
39
|
+
gap: 32px;
|
|
40
|
+
margin: 0 0 32px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.hero img {
|
|
44
|
+
width: 220px;
|
|
45
|
+
height: auto;
|
|
46
|
+
border-radius: 18px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.hero p {
|
|
50
|
+
font-size: 1.15rem;
|
|
51
|
+
line-height: 1.6;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.hero .version {
|
|
55
|
+
color: #52616b;
|
|
56
|
+
font-size: 0.85rem;
|
|
57
|
+
letter-spacing: 0.06em;
|
|
58
|
+
text-transform: uppercase;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.rst-content .button {
|
|
62
|
+
display: inline-block;
|
|
63
|
+
padding: 10px 16px;
|
|
64
|
+
margin: 4px 8px 4px 0;
|
|
65
|
+
border-radius: 6px;
|
|
66
|
+
color: white;
|
|
67
|
+
background: var(--shell-blue);
|
|
68
|
+
font-weight: 600;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.rst-content .button.secondary {
|
|
72
|
+
background: var(--shell-ink);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
a:focus-visible,
|
|
76
|
+
button:focus-visible,
|
|
77
|
+
input:focus-visible {
|
|
78
|
+
outline: 3px solid var(--shell-accent);
|
|
79
|
+
outline-offset: 3px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@media (max-width: 600px) {
|
|
83
|
+
.hero {
|
|
84
|
+
grid-template-columns: 1fr;
|
|
85
|
+
gap: 12px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.hero img {
|
|
89
|
+
width: 170px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.rst-content table.docutils {
|
|
93
|
+
display: block;
|
|
94
|
+
overflow-x: auto;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -25,6 +25,8 @@ shell scope. Bash descriptor 9 and the `sn_` namespace are reserved for the
|
|
|
25
25
|
control protocol.
|
|
26
26
|
|
|
27
27
|
Interactive sudo authentication uses private channels and a unique prompt.
|
|
28
|
+
`SessionConfig.sudo_password` supplies an upfront text/bytes secret for elevated
|
|
29
|
+
commands without an explicit provider; ordinary commands retain their identity.
|
|
28
30
|
Passwords are separate from business stdin and capture. Cached credentials do
|
|
29
31
|
not cause unnecessary provider calls. Elevated scripts run in a separate
|
|
30
32
|
elevated Bash process, so their state does not persist in the ordinary session.
|
|
@@ -41,6 +41,11 @@ POSIX uses private FIFOs and a new process group. Native shells are trusted:
|
|
|
41
41
|
programs that deliberately escape a process group are outside that containment
|
|
42
42
|
guarantee. Active Windows elevation is rejected.
|
|
43
43
|
|
|
44
|
+
The shared submission frontend resolves `SessionConfig.sudo_password` into a
|
|
45
|
+
captured asynchronous provider only for an elevated request without its own
|
|
46
|
+
provider. Both native and mock drivers use their existing authentication paths.
|
|
47
|
+
The configured secret stays out of child environment and configuration serialization.
|
|
48
|
+
|
|
44
49
|
Capture retains bounded tails and rolling match windows. Each file destination
|
|
45
50
|
has a dedicated single-worker executor; transport backpressure limits outstanding
|
|
46
51
|
writes. Subscriber queues are bounded and cannot backpressure primary capture.
|
|
@@ -4,6 +4,8 @@ The site is built from `docs/` with MkDocs. `mkdocs.yml` owns navigation and
|
|
|
4
4
|
theme configuration; `docs/assets/stylesheets/brand.css` owns the presentation.
|
|
5
5
|
The supplied `shell-next-logo.png` remains the canonical logo. Its byte-identical
|
|
6
6
|
copy in `docs/assets/` makes site builds independent of remote image hosts.
|
|
7
|
+
The site displays the logo only at the top of the home page content; the sidebar
|
|
8
|
+
uses the site name without a logo.
|
|
7
9
|
|
|
8
10
|
## Build and preview
|
|
9
11
|
|
|
@@ -19,6 +21,24 @@ The local preview is served at `http://127.0.0.1:8000`. Generated output lives i
|
|
|
19
21
|
navigation warnings; the validator also checks generated HTML links, fragments,
|
|
20
22
|
assets, and logo integrity.
|
|
21
23
|
|
|
24
|
+
## Tutorial examples
|
|
25
|
+
|
|
26
|
+
`docs/tutorials/index.md` owns the ordered chapter list. Each chapter publishes
|
|
27
|
+
the complete matching `examples/NN-topic.py` source. Update both together;
|
|
28
|
+
documentation tests reject differences and missing or unlisted chapters/files.
|
|
29
|
+
|
|
30
|
+
Tutorial blocks use `<!-- python-doc-exec native: NN-topic.py -->`, `mock`, or
|
|
31
|
+
`sudo` markers. Documentation tests execute mock blocks and inject deterministic
|
|
32
|
+
sudo scenarios into the exact published code. Integration tests in
|
|
33
|
+
`tests/contracts/test_tutorial_examples.py` execute native blocks on each supported
|
|
34
|
+
host/backend and report genuine platform skips. This does not make legacy,
|
|
35
|
+
unmarked examples executable. To check tutorial native execution separately, run
|
|
36
|
+
`python -m pytest tests/contracts/test_tutorial_examples.py`.
|
|
37
|
+
|
|
38
|
+
Run Ruff and `python -m mypy examples` after editing standalone examples in
|
|
39
|
+
addition to the normal project checks. Tutorial examples are application code,
|
|
40
|
+
not part of the production source branch-coverage target.
|
|
41
|
+
|
|
22
42
|
## GitHub Pages
|
|
23
43
|
|
|
24
44
|
The `documentation` workflow builds pull requests for review. Pushes to
|
|
@@ -84,6 +84,10 @@ A Bash `SessionScript` executes in the current Bash state space, a PowerShell `S
|
|
|
84
84
|
|
|
85
85
|
A `SessionScript` may modify persistent session state. Such modifications are not rolled back when the script later fails.
|
|
86
86
|
|
|
87
|
+
On cmd, command completion is reported only after the private batch wrapper
|
|
88
|
+
returns to its caller. Cleanup must not remove a batch file that cmd may still
|
|
89
|
+
read. Consecutive successful commands must preserve session usability.
|
|
90
|
+
|
|
87
91
|
The package does not convert Bash syntax to PowerShell syntax, PowerShell syntax to cmd syntax, or any other Shell language automatically.
|
|
88
92
|
|
|
89
93
|
Portable application logic should therefore prefer `ProcessCommand`, `shell.chdir()`, `shell.set_env()`, and `shell.unset_env()` whenever Shell-specific syntax is unnecessary.
|
|
@@ -290,6 +294,30 @@ The package must expose privilege capability through `shell.capabilities` before
|
|
|
290
294
|
|
|
291
295
|
### Bash supports interactive and noninteractive sudo in the first release.
|
|
292
296
|
|
|
297
|
+
`SessionConfig(sudo_password=...)` accepts an upfront password as UTF-8 text or
|
|
298
|
+
raw bytes for use by the session. `None` means no configured password; empty
|
|
299
|
+
text/bytes represent an empty password. Other types, unencodable text, and
|
|
300
|
+
passwords containing CR, LF, or NUL raise `ConfigurationError` without including
|
|
301
|
+
the secret. Text is normalized to UTF-8 bytes during configuration.
|
|
302
|
+
|
|
303
|
+
An explicitly elevated command with no password provider uses the configured
|
|
304
|
+
password through the existing private interactive authentication transport.
|
|
305
|
+
This enables password authentication even when the request's `interactive`
|
|
306
|
+
field has its default false value. An explicit provider takes precedence.
|
|
307
|
+
Without a session password, existing noninteractive/provider behavior is unchanged.
|
|
308
|
+
Setting the password never elevates an inherited-identity command or authenticates
|
|
309
|
+
at session startup. Windows active elevation and strict privileged cleanup remain
|
|
310
|
+
unsupported. Commands can inherit an elevated request from `SessionConfig.defaults`.
|
|
311
|
+
|
|
312
|
+
The password is reused for later authentication requests, subject to each
|
|
313
|
+
request's attempt limit; sudo cache hits send no password. No host prompt or
|
|
314
|
+
fallback provider is added. Passwords are excluded from configuration repr,
|
|
315
|
+
equality, `to_dict()`, child environment, command results, and call history.
|
|
316
|
+
The caller-owned configuration retains its password in memory; closing a session
|
|
317
|
+
does not erase it or promise secure memory zeroization. Each submission captures
|
|
318
|
+
its password value; a later change to `shell.config.sudo_password` affects future
|
|
319
|
+
submissions only. The mock copies the caller's configuration when it is created.
|
|
320
|
+
|
|
293
321
|
The Bash backend on RHEL 8 must support noninteractive sudo and interactive password-based sudo as first-release features.
|
|
294
322
|
|
|
295
323
|
Interactive sudo must not send a password through a normal business stdin plan.
|
|
@@ -62,5 +62,6 @@ result = await shell.run(SessionScript('printf "%s" "$answer"'), check=True)
|
|
|
62
62
|
assert result.stdout.tail == b"42"
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
Continue with the [
|
|
65
|
+
Continue with the [step-by-step tutorial](tutorials/index.md) for complete examples
|
|
66
|
+
across all three backends, the [usage guide](usage.md) for input and capture, or
|
|
66
67
|
[application testing](testing.md) to replace native execution with a mock.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<div class="hero">
|
|
4
4
|
<img src="assets/shell-next-logo.png" alt="shell-next logo" width="220" height="220">
|
|
5
5
|
<div>
|
|
6
|
-
<p class="version">shell-next 0.1.
|
|
6
|
+
<p class="version">shell-next 0.1.3 · Python 3.14+</p>
|
|
7
7
|
<p>Persistent asynchronous Bash, PowerShell, and cmd sessions. Own commands through completion, interact with prompts, and test the same application code with a deterministic mock.</p>
|
|
8
8
|
<a class="button" href="getting-started/">Get started</a>
|
|
9
9
|
<a class="button secondary" href="https://github.com/gokurakujoudo/shell-next">View on GitHub</a>
|
|
@@ -40,10 +40,13 @@ environment variables, and native shell state persist between normal commands.
|
|
|
40
40
|
configuration. Declare expected commands, input, output, failures, and virtual time.
|
|
41
41
|
- **Backend honesty.** Discover supported features before execution. Forced
|
|
42
42
|
interruption invalidates the session; there is no hidden restart.
|
|
43
|
+
- **Session sudo password.** Supply `SessionConfig(sudo_password=...)` once for
|
|
44
|
+
elevated Bash commands, or use a per-command provider for on-demand secrets.
|
|
43
45
|
|
|
44
46
|
## Find your next step
|
|
45
47
|
|
|
46
48
|
- [Getting started](getting-started.md): choose a backend and run your first command.
|
|
49
|
+
- [Step-by-step tutorial](tutorials/index.md): learn each feature with complete runnable examples.
|
|
47
50
|
- [Usage guide](usage.md): input, deadlines, results, capture, and sudo.
|
|
48
51
|
- [Backend support](backends.md): Linux and Windows guarantees and limitations.
|
|
49
52
|
- [Testing applications](testing.md): test production call paths without starting a shell.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Ordinary and multiple commands
|
|
2
|
+
|
|
3
|
+
Start with a normal executable, then add literal arguments, then an ordered
|
|
4
|
+
sequence. This chapter works on Bash, PowerShell, and cmd.
|
|
5
|
+
|
|
6
|
+
1. Run the selected Python interpreter with `--version`. `await shell.run()`
|
|
7
|
+
waits through capture and cleanup before returning the result.
|
|
8
|
+
2. Pass each argument separately in `ProcessCommand.args`. The spaces, ampersand,
|
|
9
|
+
and pipe in the second example are data, so the shell does not execute them.
|
|
10
|
+
3. Await commands in a loop. `check=True` stops the sequence by raising on the
|
|
11
|
+
first unsuccessful command; the three successful results remain in order.
|
|
12
|
+
|
|
13
|
+
`ProcessCommand` is for external executables. Shell built-ins such as `cd`,
|
|
14
|
+
PowerShell cmdlets, redirection, and pipes require the portable state helpers or
|
|
15
|
+
`SessionScript`, covered in later chapters. Replace `sys.executable` with an
|
|
16
|
+
installed executable such as `git` when adapting this example.
|
|
17
|
+
|
|
18
|
+
## Full example
|
|
19
|
+
|
|
20
|
+
File: [`examples/01-commands.py`](https://github.com/gokurakujoudo/shell-next/blob/main/examples/01-commands.py).
|
|
21
|
+
|
|
22
|
+
```console
|
|
23
|
+
python examples/01-commands.py bash
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
On Windows replace `bash` with `powershell` or `cmd`.
|
|
27
|
+
|
|
28
|
+
<!-- python-doc-exec native: 01-commands.py -->
|
|
29
|
+
```python
|
|
30
|
+
"""Run one command, preserve arguments, then run an ordered sequence."""
|
|
31
|
+
|
|
32
|
+
import asyncio
|
|
33
|
+
import os
|
|
34
|
+
import sys
|
|
35
|
+
|
|
36
|
+
from shell_next import Backend, ProcessCommand, SessionConfig, use_shell_session
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def main(backend: Backend) -> None:
|
|
40
|
+
config = SessionConfig(backend=backend, startup_timeout=30)
|
|
41
|
+
async with use_shell_session(config) as shell:
|
|
42
|
+
# 1. Run an ordinary external command through the selected shell.
|
|
43
|
+
result = await shell.run(ProcessCommand(sys.executable, ("--version",)), check=True)
|
|
44
|
+
assert "Python 3." in result.stdout_str()
|
|
45
|
+
print(result.stdout_str(), end="")
|
|
46
|
+
|
|
47
|
+
# 2. Arguments remain literal, including spaces and shell operators.
|
|
48
|
+
value = "two words & literal | text"
|
|
49
|
+
command = ProcessCommand(sys.executable, ("-c", "import sys; print(sys.argv[1])", value))
|
|
50
|
+
result = await shell.run(command, check=True)
|
|
51
|
+
assert result.stdout_str().strip() == value
|
|
52
|
+
assert result.command is command
|
|
53
|
+
|
|
54
|
+
# 3. Await each command to preserve order and stop on the first failure.
|
|
55
|
+
outputs = []
|
|
56
|
+
for number in (1, 2, 3):
|
|
57
|
+
command = ProcessCommand(sys.executable, ("-c", f"print({number})"))
|
|
58
|
+
result = await shell.run(command, check=True, timeout=10)
|
|
59
|
+
outputs.append(result.stdout_str().strip())
|
|
60
|
+
assert outputs == ["1", "2", "3"]
|
|
61
|
+
print(outputs)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
selected = sys.argv[1] if len(sys.argv) > 1 else ("cmd" if os.name == "nt" else "bash")
|
|
66
|
+
asyncio.run(main(Backend(selected)))
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
[Tutorial contents](index.md)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Directory navigation
|
|
2
|
+
|
|
3
|
+
A shell session owns its working directory independently of the Python process.
|
|
4
|
+
These two examples work unchanged on all three backends.
|
|
5
|
+
|
|
6
|
+
1. Create an isolated workspace and select it with `SessionConfig.cwd`. Query
|
|
7
|
+
`get_cwd()` to observe the interpreter, then inspect the cached `snapshot()`.
|
|
8
|
+
2. Use `chdir()` with a relative path containing spaces. The next external
|
|
9
|
+
process inherits that directory. Navigate back using `..` and verify that
|
|
10
|
+
Python never moved. Absolute paths also work; the final workflow uses one.
|
|
11
|
+
|
|
12
|
+
The shell closes before `TemporaryDirectory` removes its workspace, which matters
|
|
13
|
+
on Windows where an active process can hold a directory open. After a native
|
|
14
|
+
script changes directories, call `get_cwd()` before relying on the cached snapshot.
|
|
15
|
+
|
|
16
|
+
## Full example
|
|
17
|
+
|
|
18
|
+
File: [`examples/02-navigation.py`](https://github.com/gokurakujoudo/shell-next/blob/main/examples/02-navigation.py).
|
|
19
|
+
|
|
20
|
+
```console
|
|
21
|
+
python examples/02-navigation.py bash
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
On Windows replace `bash` with `powershell` or `cmd`.
|
|
25
|
+
|
|
26
|
+
<!-- python-doc-exec native: 02-navigation.py -->
|
|
27
|
+
```python
|
|
28
|
+
"""Inspect and change the shell directory without changing Python's directory."""
|
|
29
|
+
|
|
30
|
+
import asyncio
|
|
31
|
+
import os
|
|
32
|
+
import sys
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from tempfile import TemporaryDirectory
|
|
35
|
+
|
|
36
|
+
from shell_next import Backend, ProcessCommand, SessionConfig, use_shell_session
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def main(backend: Backend) -> None:
|
|
40
|
+
parent_cwd = Path.cwd()
|
|
41
|
+
with TemporaryDirectory(prefix="shell-next-navigation-") as directory:
|
|
42
|
+
root = await asyncio.to_thread(Path(directory).resolve)
|
|
43
|
+
child = root / "project with spaces"
|
|
44
|
+
child.mkdir()
|
|
45
|
+
config = SessionConfig(backend=backend, cwd=str(root), startup_timeout=30)
|
|
46
|
+
async with use_shell_session(config) as shell:
|
|
47
|
+
# 1. Inspect the initial directory and the cached snapshot.
|
|
48
|
+
assert Path(await shell.get_cwd()) == root
|
|
49
|
+
assert shell.snapshot().cwd == str(root)
|
|
50
|
+
|
|
51
|
+
# 2. Navigate relatively, then let a child process inherit the cwd.
|
|
52
|
+
await shell.chdir("project with spaces")
|
|
53
|
+
assert Path(await shell.get_cwd()) == child
|
|
54
|
+
command = ProcessCommand(sys.executable, ("-c", "import os; print(os.getcwd())"))
|
|
55
|
+
result = await shell.run(command, check=True)
|
|
56
|
+
assert Path(result.stdout_str().strip()) == child
|
|
57
|
+
await shell.chdir("..")
|
|
58
|
+
assert Path(await shell.get_cwd()) == root
|
|
59
|
+
assert Path.cwd() == parent_cwd
|
|
60
|
+
print("Only the shell moved; Python stayed in", parent_cwd)
|
|
61
|
+
# Close the shell before removing directories it may still have open.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
selected = sys.argv[1] if len(sys.argv) > 1 else ("cmd" if os.name == "nt" else "bash")
|
|
66
|
+
asyncio.run(main(Backend(selected)))
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
[Tutorial contents](index.md)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Environment variables
|
|
2
|
+
|
|
3
|
+
Exported environment variables are inherited by later external commands on
|
|
4
|
+
Bash, PowerShell, and cmd. The Python parent remains unchanged.
|
|
5
|
+
|
|
6
|
+
1. Supply an initial override through `SessionConfig.env` and query it by name.
|
|
7
|
+
2. Update it with `set_env()` and read it from an external Python process.
|
|
8
|
+
`get_env()` without a name returns the observed environment dictionary and
|
|
9
|
+
refreshes the cached snapshot. Avoid printing the whole environment because
|
|
10
|
+
it may contain credentials.
|
|
11
|
+
3. Remove the variable with `unset_env()`; a subsequent lookup returns `None`.
|
|
12
|
+
|
|
13
|
+
Portable names are shell identifiers, for example `BUILD_MODE`. Native shell
|
|
14
|
+
variables are different: Bash locals and PowerShell variables are not exported;
|
|
15
|
+
cmd `set` variables are environment variables. Use the next chapter for native
|
|
16
|
+
syntax. Environment queries need enough output retention for their JSON reply;
|
|
17
|
+
a very small `tail_bytes` can cause `SessionProtocolError`.
|
|
18
|
+
|
|
19
|
+
## Full example
|
|
20
|
+
|
|
21
|
+
File: [`examples/03-environment.py`](https://github.com/gokurakujoudo/shell-next/blob/main/examples/03-environment.py).
|
|
22
|
+
|
|
23
|
+
```console
|
|
24
|
+
python examples/03-environment.py bash
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
On Windows replace `bash` with `powershell` or `cmd`.
|
|
28
|
+
|
|
29
|
+
<!-- python-doc-exec native: 03-environment.py -->
|
|
30
|
+
```python
|
|
31
|
+
"""Configure, update, inspect, and remove an exported session variable."""
|
|
32
|
+
|
|
33
|
+
import asyncio
|
|
34
|
+
import os
|
|
35
|
+
import sys
|
|
36
|
+
|
|
37
|
+
from shell_next import Backend, ProcessCommand, SessionConfig, use_shell_session
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def main(backend: Backend) -> None:
|
|
41
|
+
parent_value = os.environ.get("SHELL_NEXT_DEMO_MODE")
|
|
42
|
+
config = SessionConfig(
|
|
43
|
+
backend=backend, env={"SHELL_NEXT_DEMO_MODE": "preview"}, startup_timeout=30
|
|
44
|
+
)
|
|
45
|
+
async with use_shell_session(config) as shell:
|
|
46
|
+
# 1. Initial overrides belong to this session, not the Python parent.
|
|
47
|
+
assert await shell.get_env("SHELL_NEXT_DEMO_MODE") == "preview"
|
|
48
|
+
assert os.environ.get("SHELL_NEXT_DEMO_MODE") == parent_value
|
|
49
|
+
|
|
50
|
+
# 2. A later process inherits updates made through the portable helper.
|
|
51
|
+
await shell.set_env("SHELL_NEXT_DEMO_MODE", "release candidate")
|
|
52
|
+
command = ProcessCommand(
|
|
53
|
+
sys.executable, ("-c", "import os; print(os.environ['SHELL_NEXT_DEMO_MODE'])")
|
|
54
|
+
)
|
|
55
|
+
result = await shell.run(command, check=True)
|
|
56
|
+
assert result.stdout_str().strip() == "release candidate"
|
|
57
|
+
environment = await shell.get_env()
|
|
58
|
+
assert isinstance(environment, dict)
|
|
59
|
+
assert environment["SHELL_NEXT_DEMO_MODE"] == "release candidate"
|
|
60
|
+
assert dict(shell.snapshot().environment)["SHELL_NEXT_DEMO_MODE"] == "release candidate"
|
|
61
|
+
|
|
62
|
+
# 3. Remove the variable explicitly; None means it is absent.
|
|
63
|
+
await shell.unset_env("SHELL_NEXT_DEMO_MODE")
|
|
64
|
+
assert await shell.get_env("SHELL_NEXT_DEMO_MODE") is None
|
|
65
|
+
assert os.environ.get("SHELL_NEXT_DEMO_MODE") == parent_value
|
|
66
|
+
print("Session environment updated and removed")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
selected = sys.argv[1] if len(sys.argv) > 1 else ("cmd" if os.name == "nt" else "bash")
|
|
71
|
+
asyncio.run(main(Backend(selected)))
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
[Tutorial contents](index.md)
|