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.
Files changed (174) hide show
  1. {shell_next-0.1.2 → shell_next-0.1.3}/CHANGELOG.md +19 -0
  2. {shell_next-0.1.2 → shell_next-0.1.3}/PKG-INFO +8 -1
  3. {shell_next-0.1.2 → shell_next-0.1.3}/README.md +7 -0
  4. shell_next-0.1.3/docs/assets/shell-next-logo.png +0 -0
  5. {shell_next-0.1.2 → shell_next-0.1.3}/docs/assets/stylesheets/brand.css +96 -105
  6. {shell_next-0.1.2 → shell_next-0.1.3}/docs/backends.md +2 -0
  7. {shell_next-0.1.2 → shell_next-0.1.3}/docs/development/architecture.md +5 -0
  8. {shell_next-0.1.2 → shell_next-0.1.3}/docs/development/documentation.md +20 -0
  9. {shell_next-0.1.2 → shell_next-0.1.3}/docs/development/specification.md +28 -0
  10. {shell_next-0.1.2 → shell_next-0.1.3}/docs/getting-started.md +2 -1
  11. {shell_next-0.1.2 → shell_next-0.1.3}/docs/index.md +4 -1
  12. shell_next-0.1.3/docs/tutorials/01-commands.md +69 -0
  13. shell_next-0.1.3/docs/tutorials/02-navigation.md +69 -0
  14. shell_next-0.1.3/docs/tutorials/03-environment.md +74 -0
  15. shell_next-0.1.3/docs/tutorials/04-scripts.md +97 -0
  16. shell_next-0.1.3/docs/tutorials/05-capture.md +88 -0
  17. shell_next-0.1.3/docs/tutorials/06-input-and-events.md +115 -0
  18. shell_next-0.1.3/docs/tutorials/07-errors-and-lifecycle.md +108 -0
  19. shell_next-0.1.3/docs/tutorials/08-concurrency.md +74 -0
  20. shell_next-0.1.3/docs/tutorials/09-sudo.md +128 -0
  21. shell_next-0.1.3/docs/tutorials/10-mocking.md +115 -0
  22. shell_next-0.1.3/docs/tutorials/11-workflow.md +103 -0
  23. shell_next-0.1.3/docs/tutorials/index.md +49 -0
  24. {shell_next-0.1.2 → shell_next-0.1.3}/docs/usage.md +66 -0
  25. shell_next-0.1.3/examples/01-commands.py +37 -0
  26. shell_next-0.1.3/examples/02-navigation.py +39 -0
  27. shell_next-0.1.3/examples/03-environment.py +41 -0
  28. shell_next-0.1.3/examples/04-scripts.py +56 -0
  29. shell_next-0.1.3/examples/05-capture.py +53 -0
  30. shell_next-0.1.3/examples/06-input-and-events.py +73 -0
  31. shell_next-0.1.3/examples/07-errors-and-lifecycle.py +65 -0
  32. shell_next-0.1.3/examples/08-concurrency.py +42 -0
  33. shell_next-0.1.3/examples/09-sudo.py +69 -0
  34. shell_next-0.1.3/examples/10-mocking.py +81 -0
  35. shell_next-0.1.3/examples/11-workflow.py +68 -0
  36. shell_next-0.1.3/examples/README.md +15 -0
  37. {shell_next-0.1.2 → shell_next-0.1.3}/mkdocs.yml +13 -1
  38. {shell_next-0.1.2 → shell_next-0.1.3}/pyproject.toml +1 -1
  39. shell_next-0.1.3/shell-next-logo.png +0 -0
  40. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/cmd/syntax.py +2 -2
  41. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/driver.py +218 -215
  42. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/session.py +4 -1
  43. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/config.py +7 -2
  44. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/privilege.py +47 -1
  45. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/bash/test_sudo_integration.py +23 -0
  46. shell_next-0.1.3/tests/backends/mock/test_session_password.py +136 -0
  47. shell_next-0.1.3/tests/backends/native/test_cmd_completion.py +18 -0
  48. shell_next-0.1.3/tests/contracts/test_tutorial_examples.py +27 -0
  49. shell_next-0.1.3/tests/docs/test_tutorials.py +120 -0
  50. shell_next-0.1.3/tests/models/test_session_password.py +27 -0
  51. shell_next-0.1.3/tests/support/tutorials.py +17 -0
  52. shell_next-0.1.2/docs/assets/shell-next-logo.png +0 -0
  53. shell_next-0.1.2/shell-next-logo.png +0 -0
  54. {shell_next-0.1.2 → shell_next-0.1.3}/.gitattributes +0 -0
  55. {shell_next-0.1.2 → shell_next-0.1.3}/.github/workflows/ci.yml +0 -0
  56. {shell_next-0.1.2 → shell_next-0.1.3}/.github/workflows/docs.yml +0 -0
  57. {shell_next-0.1.2 → shell_next-0.1.3}/.github/workflows/publish.yml +0 -0
  58. {shell_next-0.1.2 → shell_next-0.1.3}/.gitignore +0 -0
  59. {shell_next-0.1.2 → shell_next-0.1.3}/AGENTS.md +0 -0
  60. {shell_next-0.1.2 → shell_next-0.1.3}/LICENSE +0 -0
  61. {shell_next-0.1.2 → shell_next-0.1.3}/docs/development/release.md +0 -0
  62. {shell_next-0.1.2 → shell_next-0.1.3}/docs/testing.md +0 -0
  63. {shell_next-0.1.2 → shell_next-0.1.3}/docs/troubleshooting.md +0 -0
  64. {shell_next-0.1.2 → shell_next-0.1.3}/scripts/__init__.py +0 -0
  65. {shell_next-0.1.2 → shell_next-0.1.3}/scripts/docs/validate.py +0 -0
  66. {shell_next-0.1.2 → shell_next-0.1.3}/scripts/quality/__init__.py +0 -0
  67. {shell_next-0.1.2 → shell_next-0.1.3}/scripts/quality/check_source.py +0 -0
  68. {shell_next-0.1.2 → shell_next-0.1.3}/scripts/quality/rhel8.sh +0 -0
  69. {shell_next-0.1.2 → shell_next-0.1.3}/scripts/release/__init__.py +0 -0
  70. {shell_next-0.1.2 → shell_next-0.1.3}/scripts/release/check_wheel.py +0 -0
  71. {shell_next-0.1.2 → shell_next-0.1.3}/scripts/release/client.py +0 -0
  72. {shell_next-0.1.2 → shell_next-0.1.3}/scripts/release/github.py +0 -0
  73. {shell_next-0.1.2 → shell_next-0.1.3}/scripts/release/publish.py +0 -0
  74. {shell_next-0.1.2 → shell_next-0.1.3}/scripts/release/verify.py +0 -0
  75. {shell_next-0.1.2 → shell_next-0.1.3}/scripts/release/wheel_probe.py +0 -0
  76. {shell_next-0.1.2 → shell_next-0.1.3}/shell-next Development Specification.md +0 -0
  77. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/__init__.py +0 -0
  78. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/__init__.py +0 -0
  79. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/bash/__init__.py +0 -0
  80. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/bash/authentication.py +0 -0
  81. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/bash/containment.py +0 -0
  82. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/bash/password_channel.py +0 -0
  83. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/bash/syntax.py +0 -0
  84. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/cmd/__init__.py +0 -0
  85. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/mock/__init__.py +0 -0
  86. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/mock/driver.py +0 -0
  87. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/mock/scenario.py +0 -0
  88. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/mock/session.py +0 -0
  89. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/mock/state.py +0 -0
  90. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/__init__.py +0 -0
  91. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/bridge.py +0 -0
  92. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/channels.py +0 -0
  93. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/containment.py +0 -0
  94. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/preparation.py +0 -0
  95. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/process.py +0 -0
  96. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/syntax.py +0 -0
  97. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/native/termination.py +0 -0
  98. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/powershell/__init__.py +0 -0
  99. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/powershell/driver.ps1 +0 -0
  100. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/powershell/syntax.py +0 -0
  101. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/protocol.py +0 -0
  102. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/windows/__init__.py +0 -0
  103. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/windows/containment.py +0 -0
  104. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/backends/windows/limits.py +0 -0
  105. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/errors.py +0 -0
  106. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/__init__.py +0 -0
  107. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/capture.py +0 -0
  108. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/execution.py +0 -0
  109. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/finalization.py +0 -0
  110. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/handle.py +0 -0
  111. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/lease.py +0 -0
  112. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/observation.py +0 -0
  113. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/operations.py +0 -0
  114. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/frontend/output.py +0 -0
  115. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/__init__.py +0 -0
  116. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/capabilities.py +0 -0
  117. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/commands.py +0 -0
  118. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/input.py +0 -0
  119. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/representation.py +0 -0
  120. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/results.py +0 -0
  121. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/models/state.py +0 -0
  122. {shell_next-0.1.2 → shell_next-0.1.3}/src/shell_next/py.typed +0 -0
  123. {shell_next-0.1.2 → shell_next-0.1.3}/tests/__init__.py +0 -0
  124. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/__init__.py +0 -0
  125. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/bash/__init__.py +0 -0
  126. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/bash/test_authentication.py +0 -0
  127. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/bash/test_password_channel.py +0 -0
  128. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/mock/__init__.py +0 -0
  129. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/mock/test_input_edges.py +0 -0
  130. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/mock/test_privilege.py +0 -0
  131. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/mock/test_session.py +0 -0
  132. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/mock/test_state.py +0 -0
  133. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/mock/test_virtual_observation.py +0 -0
  134. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/__init__.py +0 -0
  135. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_bridge.py +0 -0
  136. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_channels.py +0 -0
  137. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_interrupt_integration.py +0 -0
  138. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_platform_containment.py +0 -0
  139. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_process_failures.py +0 -0
  140. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_resources_integration.py +0 -0
  141. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_state_integration.py +0 -0
  142. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_stdin_integration.py +0 -0
  143. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_syntax.py +0 -0
  144. {shell_next-0.1.2 → shell_next-0.1.3}/tests/backends/native/test_termination.py +0 -0
  145. {shell_next-0.1.2 → shell_next-0.1.3}/tests/conftest.py +0 -0
  146. {shell_next-0.1.2 → shell_next-0.1.3}/tests/contracts/test_execution.py +0 -0
  147. {shell_next-0.1.2 → shell_next-0.1.3}/tests/contracts/test_lifecycle.py +0 -0
  148. {shell_next-0.1.2 → shell_next-0.1.3}/tests/contracts/test_queue_cancellation.py +0 -0
  149. {shell_next-0.1.2 → shell_next-0.1.3}/tests/docs/test_validation.py +0 -0
  150. {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/__init__.py +0 -0
  151. {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/test_capture.py +0 -0
  152. {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/test_capture_failures.py +0 -0
  153. {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/test_failures.py +0 -0
  154. {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/test_lease_races.py +0 -0
  155. {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/test_observation_edges.py +0 -0
  156. {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/test_operations.py +0 -0
  157. {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/test_representation.py +0 -0
  158. {shell_next-0.1.2 → shell_next-0.1.3}/tests/frontend/test_result_content.py +0 -0
  159. {shell_next-0.1.2 → shell_next-0.1.3}/tests/models/__init__.py +0 -0
  160. {shell_next-0.1.2 → shell_next-0.1.3}/tests/models/test_representation.py +0 -0
  161. {shell_next-0.1.2 → shell_next-0.1.3}/tests/models/test_results.py +0 -0
  162. {shell_next-0.1.2 → shell_next-0.1.3}/tests/models/test_values.py +0 -0
  163. {shell_next-0.1.2 → shell_next-0.1.3}/tests/support/__init__.py +0 -0
  164. {shell_next-0.1.2 → shell_next-0.1.3}/tests/support/native.py +0 -0
  165. {shell_next-0.1.2 → shell_next-0.1.3}/tests/support/sessions.py +0 -0
  166. {shell_next-0.1.2 → shell_next-0.1.3}/wiki/Backend-Support.md +0 -0
  167. {shell_next-0.1.2 → shell_next-0.1.3}/wiki/Command-Lifecycle.md +0 -0
  168. {shell_next-0.1.2 → shell_next-0.1.3}/wiki/Getting-Started.md +0 -0
  169. {shell_next-0.1.2 → shell_next-0.1.3}/wiki/Home.md +0 -0
  170. {shell_next-0.1.2 → shell_next-0.1.3}/wiki/README.md +0 -0
  171. {shell_next-0.1.2 → shell_next-0.1.3}/wiki/Testing-with-Mocks.md +0 -0
  172. {shell_next-0.1.2 → shell_next-0.1.3}/wiki/Troubleshooting.md +0 -0
  173. {shell_next-0.1.2 → shell_next-0.1.3}/wiki/_Footer.md +0 -0
  174. {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.2
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
 
@@ -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 > a img.logo {
13
- width: 200px;
14
- height: 130px;
15
- max-width: 100%;
16
- object-fit: cover;
17
- border-radius: 12px;
18
- margin: 4px auto 12px;
19
- padding: 0;
20
- }
21
-
22
- .wy-side-nav-search input[type="text"] {
23
- border-color: var(--shell-accent);
24
- }
25
-
26
- .wy-nav-content {
27
- max-width: 1040px;
28
- }
29
-
30
- .wy-menu-vertical header,
31
- .wy-menu-vertical p.caption {
32
- color: var(--shell-accent);
33
- }
34
-
35
- .rst-content a {
36
- color: var(--shell-blue);
37
- }
38
-
39
- .rst-content h1,
40
- .rst-content h2,
41
- .rst-content h3 {
42
- color: var(--shell-ink);
43
- }
44
-
45
- .hero {
46
- display: grid;
47
- grid-template-columns: 220px 1fr;
48
- align-items: center;
49
- gap: 32px;
50
- margin: 0 0 32px;
51
- }
52
-
53
- .hero img {
54
- width: 220px;
55
- border-radius: 18px;
56
- }
57
-
58
- .hero p {
59
- font-size: 1.15rem;
60
- line-height: 1.6;
61
- }
62
-
63
- .hero .version {
64
- color: #52616b;
65
- font-size: 0.85rem;
66
- letter-spacing: 0.06em;
67
- text-transform: uppercase;
68
- }
69
-
70
- .rst-content .button {
71
- display: inline-block;
72
- padding: 10px 16px;
73
- margin: 4px 8px 4px 0;
74
- border-radius: 6px;
75
- color: white;
76
- background: var(--shell-blue);
77
- font-weight: 600;
78
- }
79
-
80
- .rst-content .button.secondary {
81
- background: var(--shell-ink);
82
- }
83
-
84
- a:focus-visible,
85
- button:focus-visible,
86
- input:focus-visible {
87
- outline: 3px solid var(--shell-accent);
88
- outline-offset: 3px;
89
- }
90
-
91
- @media (max-width: 600px) {
92
- .hero {
93
- grid-template-columns: 1fr;
94
- gap: 12px;
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 [usage guide](usage.md) for interactive input and capture, or
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.2 · Python 3.14+</p>
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)