shell-next 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. shell_next-0.1.0/.gitattributes +7 -0
  2. shell_next-0.1.0/.github/workflows/ci.yml +86 -0
  3. shell_next-0.1.0/.github/workflows/publish.yml +42 -0
  4. shell_next-0.1.0/.gitignore +18 -0
  5. shell_next-0.1.0/CHANGELOG.md +18 -0
  6. shell_next-0.1.0/LICENSE +21 -0
  7. shell_next-0.1.0/PKG-INFO +124 -0
  8. shell_next-0.1.0/README.md +98 -0
  9. shell_next-0.1.0/docs/development/architecture.md +60 -0
  10. shell_next-0.1.0/docs/development/release.md +32 -0
  11. shell_next-0.1.0/docs/development/specification.md +498 -0
  12. shell_next-0.1.0/docs/usage.md +148 -0
  13. shell_next-0.1.0/pyproject.toml +61 -0
  14. shell_next-0.1.0/scripts/__init__.py +0 -0
  15. shell_next-0.1.0/scripts/quality/__init__.py +0 -0
  16. shell_next-0.1.0/scripts/quality/check_source.py +66 -0
  17. shell_next-0.1.0/scripts/quality/rhel8.sh +14 -0
  18. shell_next-0.1.0/scripts/release/__init__.py +0 -0
  19. shell_next-0.1.0/scripts/release/check_wheel.py +40 -0
  20. shell_next-0.1.0/scripts/release/client.py +60 -0
  21. shell_next-0.1.0/scripts/release/github.py +76 -0
  22. shell_next-0.1.0/scripts/release/publish.py +105 -0
  23. shell_next-0.1.0/scripts/release/verify.py +35 -0
  24. shell_next-0.1.0/scripts/release/wheel_probe.py +32 -0
  25. shell_next-0.1.0/shell-next Development Specification.md +498 -0
  26. shell_next-0.1.0/shell-next-logo.png +0 -0
  27. shell_next-0.1.0/src/shell_next/__init__.py +71 -0
  28. shell_next-0.1.0/src/shell_next/backends/__init__.py +1 -0
  29. shell_next-0.1.0/src/shell_next/backends/bash/__init__.py +1 -0
  30. shell_next-0.1.0/src/shell_next/backends/bash/authentication.py +64 -0
  31. shell_next-0.1.0/src/shell_next/backends/bash/containment.py +34 -0
  32. shell_next-0.1.0/src/shell_next/backends/bash/password_channel.py +43 -0
  33. shell_next-0.1.0/src/shell_next/backends/bash/syntax.py +30 -0
  34. shell_next-0.1.0/src/shell_next/backends/cmd/__init__.py +1 -0
  35. shell_next-0.1.0/src/shell_next/backends/cmd/syntax.py +32 -0
  36. shell_next-0.1.0/src/shell_next/backends/mock/__init__.py +1 -0
  37. shell_next-0.1.0/src/shell_next/backends/mock/driver.py +198 -0
  38. shell_next-0.1.0/src/shell_next/backends/mock/scenario.py +131 -0
  39. shell_next-0.1.0/src/shell_next/backends/mock/session.py +151 -0
  40. shell_next-0.1.0/src/shell_next/backends/mock/state.py +25 -0
  41. shell_next-0.1.0/src/shell_next/backends/native/__init__.py +1 -0
  42. shell_next-0.1.0/src/shell_next/backends/native/bridge.py +65 -0
  43. shell_next-0.1.0/src/shell_next/backends/native/channels.py +116 -0
  44. shell_next-0.1.0/src/shell_next/backends/native/containment.py +17 -0
  45. shell_next-0.1.0/src/shell_next/backends/native/driver.py +215 -0
  46. shell_next-0.1.0/src/shell_next/backends/native/preparation.py +85 -0
  47. shell_next-0.1.0/src/shell_next/backends/native/process.py +184 -0
  48. shell_next-0.1.0/src/shell_next/backends/native/syntax.py +66 -0
  49. shell_next-0.1.0/src/shell_next/backends/native/termination.py +42 -0
  50. shell_next-0.1.0/src/shell_next/backends/powershell/__init__.py +1 -0
  51. shell_next-0.1.0/src/shell_next/backends/powershell/driver.ps1 +22 -0
  52. shell_next-0.1.0/src/shell_next/backends/powershell/syntax.py +86 -0
  53. shell_next-0.1.0/src/shell_next/backends/protocol.py +69 -0
  54. shell_next-0.1.0/src/shell_next/backends/windows/__init__.py +1 -0
  55. shell_next-0.1.0/src/shell_next/backends/windows/containment.py +83 -0
  56. shell_next-0.1.0/src/shell_next/backends/windows/limits.py +41 -0
  57. shell_next-0.1.0/src/shell_next/errors.py +142 -0
  58. shell_next-0.1.0/src/shell_next/frontend/__init__.py +1 -0
  59. shell_next-0.1.0/src/shell_next/frontend/capture.py +115 -0
  60. shell_next-0.1.0/src/shell_next/frontend/execution.py +199 -0
  61. shell_next-0.1.0/src/shell_next/frontend/finalization.py +51 -0
  62. shell_next-0.1.0/src/shell_next/frontend/handle.py +228 -0
  63. shell_next-0.1.0/src/shell_next/frontend/lease.py +40 -0
  64. shell_next-0.1.0/src/shell_next/frontend/observation.py +35 -0
  65. shell_next-0.1.0/src/shell_next/frontend/operations.py +78 -0
  66. shell_next-0.1.0/src/shell_next/frontend/output.py +123 -0
  67. shell_next-0.1.0/src/shell_next/frontend/session.py +313 -0
  68. shell_next-0.1.0/src/shell_next/models/__init__.py +1 -0
  69. shell_next-0.1.0/src/shell_next/models/capabilities.py +74 -0
  70. shell_next-0.1.0/src/shell_next/models/commands.py +48 -0
  71. shell_next-0.1.0/src/shell_next/models/config.py +174 -0
  72. shell_next-0.1.0/src/shell_next/models/input.py +91 -0
  73. shell_next-0.1.0/src/shell_next/models/privilege.py +80 -0
  74. shell_next-0.1.0/src/shell_next/models/results.py +167 -0
  75. shell_next-0.1.0/src/shell_next/models/state.py +49 -0
  76. shell_next-0.1.0/src/shell_next/py.typed +0 -0
  77. shell_next-0.1.0/tests/__init__.py +0 -0
  78. shell_next-0.1.0/tests/backends/__init__.py +0 -0
  79. shell_next-0.1.0/tests/backends/bash/__init__.py +0 -0
  80. shell_next-0.1.0/tests/backends/bash/test_authentication.py +63 -0
  81. shell_next-0.1.0/tests/backends/bash/test_password_channel.py +78 -0
  82. shell_next-0.1.0/tests/backends/bash/test_sudo_integration.py +134 -0
  83. shell_next-0.1.0/tests/backends/mock/__init__.py +0 -0
  84. shell_next-0.1.0/tests/backends/mock/test_input_edges.py +53 -0
  85. shell_next-0.1.0/tests/backends/mock/test_privilege.py +83 -0
  86. shell_next-0.1.0/tests/backends/mock/test_session.py +175 -0
  87. shell_next-0.1.0/tests/backends/mock/test_state.py +52 -0
  88. shell_next-0.1.0/tests/backends/mock/test_virtual_observation.py +80 -0
  89. shell_next-0.1.0/tests/backends/native/__init__.py +0 -0
  90. shell_next-0.1.0/tests/backends/native/test_bridge.py +90 -0
  91. shell_next-0.1.0/tests/backends/native/test_channels.py +18 -0
  92. shell_next-0.1.0/tests/backends/native/test_interrupt_integration.py +51 -0
  93. shell_next-0.1.0/tests/backends/native/test_platform_containment.py +56 -0
  94. shell_next-0.1.0/tests/backends/native/test_process_failures.py +102 -0
  95. shell_next-0.1.0/tests/backends/native/test_resources_integration.py +80 -0
  96. shell_next-0.1.0/tests/backends/native/test_state_integration.py +68 -0
  97. shell_next-0.1.0/tests/backends/native/test_stdin_integration.py +25 -0
  98. shell_next-0.1.0/tests/backends/native/test_syntax.py +10 -0
  99. shell_next-0.1.0/tests/backends/native/test_termination.py +42 -0
  100. shell_next-0.1.0/tests/conftest.py +11 -0
  101. shell_next-0.1.0/tests/contracts/test_execution.py +147 -0
  102. shell_next-0.1.0/tests/contracts/test_lifecycle.py +103 -0
  103. shell_next-0.1.0/tests/contracts/test_queue_cancellation.py +47 -0
  104. shell_next-0.1.0/tests/frontend/__init__.py +0 -0
  105. shell_next-0.1.0/tests/frontend/test_capture.py +102 -0
  106. shell_next-0.1.0/tests/frontend/test_capture_failures.py +76 -0
  107. shell_next-0.1.0/tests/frontend/test_failures.py +192 -0
  108. shell_next-0.1.0/tests/frontend/test_lease_races.py +23 -0
  109. shell_next-0.1.0/tests/frontend/test_observation_edges.py +42 -0
  110. shell_next-0.1.0/tests/frontend/test_operations.py +39 -0
  111. shell_next-0.1.0/tests/models/__init__.py +0 -0
  112. shell_next-0.1.0/tests/models/test_values.py +120 -0
  113. shell_next-0.1.0/tests/support/__init__.py +0 -0
  114. shell_next-0.1.0/tests/support/native.py +80 -0
  115. shell_next-0.1.0/tests/support/sessions.py +29 -0
@@ -0,0 +1,7 @@
1
+ * text=auto
2
+ *.py text eol=lf
3
+ *.sh text eol=lf
4
+ *.yml text eol=lf
5
+ *.md text eol=lf
6
+ *.png binary
7
+ *.ps1 text eol=lf
@@ -0,0 +1,86 @@
1
+ name: quality
2
+ on:
3
+ push:
4
+ pull_request:
5
+ workflow_dispatch:
6
+ permissions:
7
+ contents: read
8
+ jobs:
9
+ contracts:
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ os: [ubuntu-latest, windows-latest]
14
+ runs-on: ${{ matrix.os }}
15
+ timeout-minutes: 20
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: '3.14'
21
+ - run: python -m pip install -e ".[dev]"
22
+ - run: python -m ruff check .
23
+ - run: python -m ruff format --check .
24
+ - run: python -m mypy
25
+ - run: python scripts/quality/check_source.py
26
+ - name: Run platform contracts and collect partial coverage
27
+ run: python -m pytest --cov --cov-branch --cov-report=xml --cov-fail-under=0
28
+ - name: Verify installed wheel outside the source checkout
29
+ run: python -m scripts.release.check_wheel
30
+ - uses: actions/upload-artifact@v4
31
+ if: always()
32
+ with:
33
+ name: coverage-${{ matrix.os }}
34
+ path: .coverage
35
+ include-hidden-files: true
36
+ release-gates:
37
+ needs: [contracts, rhel8-reference]
38
+ runs-on: ubuntu-latest
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+ - uses: actions/setup-python@v5
42
+ with:
43
+ python-version: '3.14'
44
+ - run: python -m pip install -e ".[dev]"
45
+ - uses: actions/download-artifact@v4
46
+ with:
47
+ pattern: coverage-*
48
+ path: reports/coverage
49
+ - run: python -m coverage combine reports/coverage/coverage-ubuntu-latest/.coverage reports/coverage/coverage-windows-latest/.coverage reports/coverage/coverage-rhel8/.coverage
50
+ - name: Enforce full combined branch coverage
51
+ run: python -m coverage report --fail-under=100
52
+ - run: python -m build
53
+ - run: python -m twine check dist/*
54
+ - uses: actions/upload-artifact@v4
55
+ with:
56
+ name: distributions
57
+ path: dist/*
58
+ rhel8-reference:
59
+ runs-on: ubuntu-latest
60
+ container: registry.access.redhat.com/ubi8/ubi:8.10
61
+ timeout-minutes: 40
62
+ steps:
63
+ - uses: actions/checkout@v4
64
+ - uses: actions/cache@v4
65
+ with:
66
+ path: /opt/python
67
+ key: rhel8-python-3.14.2-v1
68
+ - run: bash scripts/quality/rhel8.sh
69
+ - run: /opt/python/bin/python3.14 -m pip install -e ".[dev]"
70
+ - name: Configure disposable sudo contract account
71
+ run: |
72
+ useradd --create-home shellnext
73
+ echo 'shellnext:shell-next-disposable-ci-password' | chpasswd
74
+ printf 'Defaults:shellnext timestamp_type=global\nDefaults:shellnext passwd_tries=2\nshellnext ALL=(ALL) ALL\n' > /etc/sudoers.d/shell-next-contract
75
+ chmod 0440 /etc/sudoers.d/shell-next-contract
76
+ chown -R shellnext:shellnext "$GITHUB_WORKSPACE"
77
+ - name: Reference contracts and interactive sudo
78
+ env:
79
+ SHELL_NEXT_SUDO_PASSWORD: shell-next-disposable-ci-password
80
+ run: su -m -s /bin/bash shellnext -c '/opt/python/bin/python3.14 -m pytest --cov --cov-branch --cov-report=xml --cov-fail-under=0'
81
+ - uses: actions/upload-artifact@v4
82
+ if: always()
83
+ with:
84
+ name: coverage-rhel8
85
+ path: .coverage
86
+ include-hidden-files: true
@@ -0,0 +1,42 @@
1
+ name: publish
2
+ on:
3
+ workflow_dispatch:
4
+ inputs:
5
+ version:
6
+ description: Exact package version to publish after all release gates pass
7
+ required: true
8
+ permissions:
9
+ contents: read
10
+ actions: read
11
+ jobs:
12
+ verify:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: '3.14'
19
+ - run: python -m pip install -e ".[dev]"
20
+ - name: Require completed release evidence for this commit
21
+ env:
22
+ GH_TOKEN: ${{ github.token }}
23
+ VERSION: ${{ inputs.version }}
24
+ run: python -m scripts.release.verify
25
+ - run: python -m build
26
+ - run: python -m twine check dist/*
27
+ - uses: actions/upload-artifact@v4
28
+ with:
29
+ name: verified-distributions
30
+ path: dist/*
31
+ pypi:
32
+ needs: verify
33
+ runs-on: ubuntu-latest
34
+ environment: pypi
35
+ permissions:
36
+ id-token: write
37
+ steps:
38
+ - uses: actions/download-artifact@v4
39
+ with:
40
+ name: verified-distributions
41
+ path: dist
42
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,18 @@
1
+ /.coverage
2
+ /.mypy_cache/
3
+ /.pytest_cache/
4
+ /pytest-cache-files-*/
5
+ /.ruff_cache/
6
+ /build/
7
+ /dist/
8
+ /reports/
9
+ /*.egg-info/
10
+ **/__pycache__/
11
+ *.py[cod]
12
+ /.codex_tmp/
13
+
14
+ /venv/
15
+ /.venv/
16
+ /.coverage.*
17
+ /coverage.xml
18
+
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Persistent Bash, PowerShell 7, and cmd sessions for Python 3.14+.
6
+ - Structural process arguments and native scripts with persistent shell state.
7
+ - Managed commands, scoped handles, FIFO queuing, independent observation deadlines,
8
+ and bounded interruption and cleanup.
9
+ - Manual and planned stdin, literal prompt matching, bounded byte capture,
10
+ optional durable files, and independent output subscribers.
11
+ - Interactive/noninteractive Bash sudo with separate authentication channels.
12
+ - Strict deterministic mock scenarios through `SessionConfig._session_cls`.
13
+ - RHEL 8, Linux, and Windows contracts, installed-wheel checks, static analysis,
14
+ and 100% combined branch coverage as publication gates.
15
+
16
+ Active Windows elevation, terminal emulation, and strict cleanup of privileged
17
+ descendants are unsupported. Forced interruption invalidates the persistent
18
+ session; callers create a new session explicitly.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 shell-next contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.5
2
+ Name: shell-next
3
+ Version: 0.1.0
4
+ Summary: Persistent asynchronous shell sessions with deterministic test doubles
5
+ Project-URL: Repository, https://github.com/gokurakujoudo/shell-next
6
+ Project-URL: Issues, https://github.com/gokurakujoudo/shell-next/issues
7
+ Author: shell-next contributors
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Framework :: AsyncIO
12
+ Classifier: Operating System :: Microsoft :: Windows
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: >=3.14
17
+ Provides-Extra: dev
18
+ Requires-Dist: build>=1.4; extra == 'dev'
19
+ Requires-Dist: mypy>=1.19; extra == 'dev'
20
+ Requires-Dist: pytest-asyncio>=1; extra == 'dev'
21
+ Requires-Dist: pytest-cov>=7; extra == 'dev'
22
+ Requires-Dist: pytest>=9; extra == 'dev'
23
+ Requires-Dist: ruff>=0.15; extra == 'dev'
24
+ Requires-Dist: twine>=6; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # shell-next
28
+
29
+ Persistent asynchronous Bash, PowerShell, and cmd sessions for Python 3.14+.
30
+
31
+ ```python
32
+ from shell_next import Backend, ProcessCommand, SessionConfig, use_shell_session
33
+
34
+
35
+ async def inspect_repository(config: SessionConfig):
36
+ async with use_shell_session(config) as shell:
37
+ await shell.chdir("/srv/project")
38
+ result = await shell.run(ProcessCommand("git", ("status", "--short")), check=True)
39
+ return result.stdout.tail
40
+ ```
41
+
42
+ Use `ProcessCommand` for executable arguments that must remain structural. Use
43
+ `SessionScript` for backend-native scripts and persistent shell variables,
44
+ functions, and aliases. Shell syntax is never translated between languages.
45
+
46
+ Install with `python -m pip install shell-next`. Supply
47
+ `SessionConfig(backend=Backend.BASH)` on Linux, or select `Backend.POWERSHELL`
48
+ (PowerShell 7 installed) or `Backend.CMD` on Windows. Python 3.14+ is required.
49
+
50
+ ## Interactive commands
51
+
52
+ ```python
53
+ from shell_next import CommandOptions, StdinMode
54
+
55
+
56
+ async def answer_prompt(shell, program):
57
+ options = CommandOptions(stdin=StdinMode.MANUAL)
58
+ async with shell.command(program, options=options, timeout=30) as command:
59
+ await command.expect(b"Name: ")
60
+ await command.sendline(b"Ada")
61
+ await command.close_stdin()
62
+ return await command.wait()
63
+ ```
64
+
65
+ `submit()` immediately registers a command and returns its handle. One session
66
+ executes one command at a time; the default policy rejects concurrent submissions.
67
+ `ConcurrencyPolicy.QUEUE` enables FIFO queuing. Different sessions run independently.
68
+
69
+ Cancelling `wait()` cancels observation only. Cancelling `run()` or escaping a
70
+ command context requests termination. A force-stopped shell becomes unusable;
71
+ create another session explicitly when required.
72
+
73
+ ## Deterministic application tests
74
+
75
+ ```python
76
+ from shell_next import Emit, MockExpectation, MockScenario, MockShellSession
77
+
78
+ scenario = MockScenario(
79
+ [
80
+ MockExpectation(ProcessCommand("git", ("status", "--short")), (Emit(b" M README.md\n"),)),
81
+ ]
82
+ )
83
+ config = SessionConfig()
84
+ config._session_cls = MockShellSession.configured(scenario)
85
+ ```
86
+
87
+ Pass this same configuration to application code using `use_shell_session`.
88
+ The mock shares production lifecycle code while keeping execution, state, input,
89
+ and output in memory. Unmatched commands and unconsumed strict expectations fail.
90
+ `Advance(60)` advances mock time without sleeping. Plain
91
+ `config._session_cls = MockShellSession` creates an empty strict scenario.
92
+
93
+ ## Capture and capabilities
94
+
95
+ Output is bytes. Each stream retains a bounded tail (64 KiB by default), and
96
+ literal prompt matching uses a separate bounded window. Slow event subscribers
97
+ receive an overflow error without blocking primary capture. Opt into full capture
98
+ with `CaptureConfig(directory=existing_directory)`. Results distinguish received
99
+ bytes, durable file bytes, truncation, sealing, and possible unread output.
100
+
101
+ Inspect `shell.capabilities` before requesting backend-specific behavior.
102
+ Windows active elevation and terminal/PowerShell Host prompt automation are
103
+ unsupported. The library is a process-management API, **not a security sandbox**
104
+ for untrusted scripts.
105
+
106
+ See the [development specification](docs/development/specification.md),
107
+ [usage guide](docs/usage.md),
108
+ [architecture](docs/development/architecture.md), and
109
+ [release checklist](docs/development/release.md).
110
+
111
+ ## Development
112
+
113
+ ```console
114
+ python -m pip install -e ".[dev]"
115
+ python -m ruff check .
116
+ python -m mypy
117
+ python -m pytest --cov --cov-branch
118
+ python -m build
119
+ python -m twine check dist/*
120
+ ```
121
+
122
+ The package uses only the Python standard library at runtime. Tests that start
123
+ real shells are marked `integration`. Test files mirror production responsibilities;
124
+ reusable test infrastructure lives in `tests/support`.
@@ -0,0 +1,98 @@
1
+ # shell-next
2
+
3
+ Persistent asynchronous Bash, PowerShell, and cmd sessions for Python 3.14+.
4
+
5
+ ```python
6
+ from shell_next import Backend, ProcessCommand, SessionConfig, use_shell_session
7
+
8
+
9
+ async def inspect_repository(config: SessionConfig):
10
+ async with use_shell_session(config) as shell:
11
+ await shell.chdir("/srv/project")
12
+ result = await shell.run(ProcessCommand("git", ("status", "--short")), check=True)
13
+ return result.stdout.tail
14
+ ```
15
+
16
+ Use `ProcessCommand` for executable arguments that must remain structural. Use
17
+ `SessionScript` for backend-native scripts and persistent shell variables,
18
+ functions, and aliases. Shell syntax is never translated between languages.
19
+
20
+ Install with `python -m pip install shell-next`. Supply
21
+ `SessionConfig(backend=Backend.BASH)` on Linux, or select `Backend.POWERSHELL`
22
+ (PowerShell 7 installed) or `Backend.CMD` on Windows. Python 3.14+ is required.
23
+
24
+ ## Interactive commands
25
+
26
+ ```python
27
+ from shell_next import CommandOptions, StdinMode
28
+
29
+
30
+ async def answer_prompt(shell, program):
31
+ options = CommandOptions(stdin=StdinMode.MANUAL)
32
+ async with shell.command(program, options=options, timeout=30) as command:
33
+ await command.expect(b"Name: ")
34
+ await command.sendline(b"Ada")
35
+ await command.close_stdin()
36
+ return await command.wait()
37
+ ```
38
+
39
+ `submit()` immediately registers a command and returns its handle. One session
40
+ executes one command at a time; the default policy rejects concurrent submissions.
41
+ `ConcurrencyPolicy.QUEUE` enables FIFO queuing. Different sessions run independently.
42
+
43
+ Cancelling `wait()` cancels observation only. Cancelling `run()` or escaping a
44
+ command context requests termination. A force-stopped shell becomes unusable;
45
+ create another session explicitly when required.
46
+
47
+ ## Deterministic application tests
48
+
49
+ ```python
50
+ from shell_next import Emit, MockExpectation, MockScenario, MockShellSession
51
+
52
+ scenario = MockScenario(
53
+ [
54
+ MockExpectation(ProcessCommand("git", ("status", "--short")), (Emit(b" M README.md\n"),)),
55
+ ]
56
+ )
57
+ config = SessionConfig()
58
+ config._session_cls = MockShellSession.configured(scenario)
59
+ ```
60
+
61
+ Pass this same configuration to application code using `use_shell_session`.
62
+ The mock shares production lifecycle code while keeping execution, state, input,
63
+ and output in memory. Unmatched commands and unconsumed strict expectations fail.
64
+ `Advance(60)` advances mock time without sleeping. Plain
65
+ `config._session_cls = MockShellSession` creates an empty strict scenario.
66
+
67
+ ## Capture and capabilities
68
+
69
+ Output is bytes. Each stream retains a bounded tail (64 KiB by default), and
70
+ literal prompt matching uses a separate bounded window. Slow event subscribers
71
+ receive an overflow error without blocking primary capture. Opt into full capture
72
+ with `CaptureConfig(directory=existing_directory)`. Results distinguish received
73
+ bytes, durable file bytes, truncation, sealing, and possible unread output.
74
+
75
+ Inspect `shell.capabilities` before requesting backend-specific behavior.
76
+ Windows active elevation and terminal/PowerShell Host prompt automation are
77
+ unsupported. The library is a process-management API, **not a security sandbox**
78
+ for untrusted scripts.
79
+
80
+ See the [development specification](docs/development/specification.md),
81
+ [usage guide](docs/usage.md),
82
+ [architecture](docs/development/architecture.md), and
83
+ [release checklist](docs/development/release.md).
84
+
85
+ ## Development
86
+
87
+ ```console
88
+ python -m pip install -e ".[dev]"
89
+ python -m ruff check .
90
+ python -m mypy
91
+ python -m pytest --cov --cov-branch
92
+ python -m build
93
+ python -m twine check dist/*
94
+ ```
95
+
96
+ The package uses only the Python standard library at runtime. Tests that start
97
+ real shells are marked `integration`. Test files mirror production responsibilities;
98
+ reusable test infrastructure lives in `tests/support`.
@@ -0,0 +1,60 @@
1
+ # Architecture
2
+
3
+ The public entry point is `use_shell_session(SessionConfig(...))`. The factory
4
+ selects the implementation before entering the session, including the official
5
+ `config._session_cls` injection point. Application code should depend on this
6
+ interface instead of constructing native adapters.
7
+
8
+ ## Dependency boundaries
9
+
10
+ | Responsibility | Modules | Tests |
11
+ | --- | --- | --- |
12
+ | Value validation and immutable contracts | `models/{commands,config,capabilities,state,results,input,privilege}`; `errors` | `tests/models` |
13
+ | Session ownership and command lifecycle | `frontend/{session,execution,lease,handle,observation,operations}` | `tests/frontend`, `tests/contracts` |
14
+ | Bounded output and subscriptions | `frontend/{capture,output,finalization}` | `tests/frontend` |
15
+ | Native process and byte transports | `backends/native/{process,channels,containment,termination}` | `tests/backends/native` |
16
+ | Native command protocol | `backends/native/{driver,preparation,syntax,bridge}` | `tests/backends/native` |
17
+ | Bash syntax and sudo | `backends/bash/{syntax,containment,authentication,password_channel}` | `tests/backends/bash` |
18
+ | PowerShell and cmd syntax | `backends/powershell/{syntax,driver.ps1}`; `backends/cmd/syntax` | native and common contract tests |
19
+ | Windows process containment | `backends/windows/{containment,limits}` | `tests/backends/native/test_platform_containment.py` |
20
+ | Deterministic application double | `backends/mock/{session,driver,scenario}` | `tests/backends/mock`, `tests/contracts` |
21
+
22
+ Quality tools live in `scripts/quality`; packaging and release operations live in
23
+ `scripts/release`. Shared test fixtures belong in `tests/support`. Keep shell
24
+ language details in their backend instead of branching throughout the frontend.
25
+
26
+ Value models do not start processes or perform I/O. The common frontend owns
27
+ submission, queuing, cancellation, and final results. Drivers own transport
28
+ resources. Both drivers implement the same protocol and feed the same bounded
29
+ capture and input interfaces. Mock code cannot call native driver methods.
30
+
31
+ Each session owns one idle interpreter, one execution lease, and all its submitted
32
+ commands. Command-specific pipes isolate business input and output from shell
33
+ control messages. A bridge invokes structural processes with argv while inheriting
34
+ the shell's current directory and exported environment. Native scripts run in the
35
+ existing interpreter scope. Wrapper state uses the reserved `sn_`/`$sn_` namespace;
36
+ Bash reserves file descriptor 9 for status messages. Scripts that corrupt the
37
+ protocol invalidate the session rather than causing transparent restart.
38
+
39
+ Windows uses IOCP named pipes and a Job Object assigned before user commands.
40
+ POSIX uses private FIFOs and a new process group. Native shells are trusted:
41
+ programs that deliberately escape a process group are outside that containment
42
+ guarantee. Active Windows elevation is rejected.
43
+
44
+ Capture retains bounded tails and rolling match windows. Each file destination
45
+ has a dedicated single-worker executor; transport backpressure limits outstanding
46
+ writes. Subscriber queues are bounded and cannot backpressure primary capture.
47
+ File bytes are called durable only after flush and fsync complete.
48
+
49
+ Tests must mock external connectivity. Filesystem tests own a TemporaryDirectory.
50
+ Contract tests assert behavior through the public factory; unit tests exercise
51
+ failure boundaries with narrowly scoped fakes. Integration tests assert native
52
+ semantics and process cleanup instead of merely checking an echo command.
53
+
54
+ Production modules are limited to 200 code-bearing physical lines. Production
55
+ classes and functions use descriptive names without underscore prefixes, except
56
+ Python protocol dunders. Every function and value class uses English rST
57
+ documentation with parameter, return, and intentional-exception documentation.
58
+ Ruff, strict mypy, structural policy checks, and 100% combined branch coverage are
59
+ release gates. Platform-specific tests contribute coverage across the OS matrix;
60
+ coverage must not be lowered or platform implementations excluded to pass release.
@@ -0,0 +1,32 @@
1
+ # First release gates
2
+
3
+ No stable tag or PyPI publication is permitted until these gates are verified.
4
+ Candidate source metadata may carry the intended version while publication
5
+ remains blocked until all evidence passes for that exact commit.
6
+
7
+ - Common behavioral contracts pass for Mock, Bash, PowerShell 7, and cmd.
8
+ - Bash reference testing includes RHEL 8, persistent state, large simultaneous
9
+ streams, descendant cleanup, cancellation races, and ignored soft termination.
10
+ - Interactive sudo, cached credentials, wrong passwords, authentication deadlines,
11
+ separate business stdin, and strict privileged-cleanup rejection pass.
12
+ - PowerShell and cmd pass native script, structural process, persistent state,
13
+ timeout, cancellation, containment, and unsupported-elevation tests.
14
+ - Mock tests prohibit real subprocesses, filesystem writes, network access,
15
+ environment/directory mutation, and real sleeps.
16
+ - Ruff, strict mypy, production policy checks, and **100% branch coverage** pass.
17
+ - Repeated lifecycle stress tests detect no process, handle, task, or thread leaks.
18
+ - Wheel and sdist build, metadata checks, and installed-wheel smoke tests pass.
19
+ - GitHub release artifacts are built from the tested commit.
20
+ - PyPI publication uses a configured trusted publisher or the maintainer's existing
21
+ local PyPI credential configuration.
22
+
23
+ The optional GitHub publishing workflow uses an environment named `pypi` and
24
+ the PyPA trusted publisher action. Local publication uses Twine after the same
25
+ exact-commit verification. Credentials are never committed to the repository or
26
+ printed by release tooling. A draft release does not establish that gates passed.
27
+
28
+ For an authorized local release, start with a clean checkout of the tested commit
29
+ and run `python -m scripts.release.publish`. This verifies the complete successful
30
+ quality run, builds both distributions, checks metadata, stages GitHub assets,
31
+ publishes through the existing Twine configuration, and publishes the GitHub
32
+ release. An existing version or asset with different bytes is never overwritten.