sshscript 6.0.0__tar.gz → 6.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 (124) hide show
  1. {sshscript-6.0.0 → sshscript-6.1.0}/.github/workflows/ci.yml +2 -2
  2. {sshscript-6.0.0 → sshscript-6.1.0}/.github/workflows/release.yml +1 -1
  3. {sshscript-6.0.0 → sshscript-6.1.0}/.gitignore +2 -0
  4. {sshscript-6.0.0 → sshscript-6.1.0}/AI_GUIDE.md +16 -9
  5. {sshscript-6.0.0 → sshscript-6.1.0}/API_GUIDE.md +19 -11
  6. {sshscript-6.0.0 → sshscript-6.1.0}/CHANGELOG.md +24 -1
  7. {sshscript-6.0.0 → sshscript-6.1.0}/CONSOLE.md +17 -8
  8. sshscript-6.1.0/EMBEDDED_CONSOLE.md +200 -0
  9. {sshscript-6.0.0 → sshscript-6.1.0}/MANIFEST.in +5 -0
  10. {sshscript-6.0.0/src/sshscript.egg-info → sshscript-6.1.0}/PKG-INFO +27 -20
  11. {sshscript-6.0.0 → sshscript-6.1.0}/README.md +25 -18
  12. {sshscript-6.0.0 → sshscript-6.1.0}/RELEASING.md +3 -1
  13. {sshscript-6.0.0 → sshscript-6.1.0}/VERSIONING.md +46 -19
  14. sshscript-6.1.0/examples/embedded_agent.py +36 -0
  15. sshscript-6.1.0/examples/embedded_terminal.py +21 -0
  16. {sshscript-6.0.0 → sshscript-6.1.0}/pyproject.toml +1 -1
  17. {sshscript-6.0.0 → sshscript-6.1.0}/skills/sshscript/SKILL.md +23 -5
  18. {sshscript-6.0.0 → sshscript-6.1.0}/skills/sshscript/references/agent-guide.md +35 -11
  19. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/__init__.py +3 -0
  20. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/__init__.pyi +2 -0
  21. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/_version.py +1 -1
  22. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/interactive.py +565 -194
  23. sshscript-6.1.0/src/sshscript/interactive.pyi +71 -0
  24. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/sshscript.py +3 -1
  25. sshscript-6.1.0/src/sshscript/unittest/test_embedded_console.py +471 -0
  26. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_interactive_console.py +86 -86
  27. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_native_auth_runner.py +2 -2
  28. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_native_console_authentication.py +28 -1
  29. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_openssh_integration.py +10 -1
  30. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_public_types.py +2 -1
  31. {sshscript-6.0.0 → sshscript-6.1.0/src/sshscript.egg-info}/PKG-INFO +27 -20
  32. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript.egg-info/SOURCES.txt +5 -0
  33. {sshscript-6.0.0 → sshscript-6.1.0}/tools/check_public_types.py +6 -1
  34. {sshscript-6.0.0 → sshscript-6.1.0}/tools/prepare_release.py +3 -1
  35. {sshscript-6.0.0 → sshscript-6.1.0}/tools/sync_ai_docs.py +7 -0
  36. {sshscript-6.0.0 → sshscript-6.1.0}/tools/sync_api_guide.py +1 -0
  37. {sshscript-6.0.0 → sshscript-6.1.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  38. {sshscript-6.0.0 → sshscript-6.1.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  39. {sshscript-6.0.0 → sshscript-6.1.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  40. {sshscript-6.0.0 → sshscript-6.1.0}/.github/dependabot.yml +0 -0
  41. {sshscript-6.0.0 → sshscript-6.1.0}/.github/pull_request_template.md +0 -0
  42. {sshscript-6.0.0 → sshscript-6.1.0}/.github/workflows/codeql.yml +0 -0
  43. {sshscript-6.0.0 → sshscript-6.1.0}/CODE_OF_CONDUCT.md +0 -0
  44. {sshscript-6.0.0 → sshscript-6.1.0}/CONTRIBUTING.md +0 -0
  45. {sshscript-6.0.0 → sshscript-6.1.0}/EXCEPTIONS.md +0 -0
  46. {sshscript-6.0.0 → sshscript-6.1.0}/LICENSE.txt +0 -0
  47. {sshscript-6.0.0 → sshscript-6.1.0}/SECURITY.md +0 -0
  48. {sshscript-6.0.0 → sshscript-6.1.0}/SUPPORT.md +0 -0
  49. {sshscript-6.0.0 → sshscript-6.1.0}/setup.cfg +0 -0
  50. {sshscript-6.0.0 → sshscript-6.1.0}/src/_sshscript_cli.py +0 -0
  51. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/_runtime.py +0 -0
  52. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/channelgeneric.py +0 -0
  53. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/channelssh.py +0 -0
  54. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/channelsubprocess.py +0 -0
  55. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/channelutils.py +0 -0
  56. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/commandjob.py +0 -0
  57. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/commandjob.pyi +0 -0
  58. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/commandresult.py +0 -0
  59. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/commandresult.pyi +0 -0
  60. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/consolejob.py +0 -0
  61. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/dollar.py +0 -0
  62. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/dollarchanger.py +0 -0
  63. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/dollarparser.py +0 -0
  64. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/errorutils.py +0 -0
  65. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/patching.py +0 -0
  66. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/py.typed +0 -0
  67. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/scriptcontrol.py +0 -0
  68. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/session.py +0 -0
  69. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/session.pyi +0 -0
  70. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/sessionsettings.py +0 -0
  71. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/sessionwrapper.py +0 -0
  72. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/sessionwrapper.pyi +0 -0
  73. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/spyimporter.py +0 -0
  74. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/sshconfig.py +0 -0
  75. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/stdio.py +0 -0
  76. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/tokenparser.py +0 -0
  77. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/README.console-authentication.md +0 -0
  78. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/README.console-results.md +0 -0
  79. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/README.dollar-syntax-tests.md +0 -0
  80. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/README.module-tests.md +0 -0
  81. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/check_package_asserts.py +0 -0
  82. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/dollar_syntax.spy +0 -0
  83. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/dollar_syntax_fixture.spy +0 -0
  84. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/language_fixture.spy +0 -0
  85. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_channelgeneric_expect.py +0 -0
  86. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_check_file.py +0 -0
  87. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_command_api.py +0 -0
  88. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_command_job.py +0 -0
  89. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_console_authentication.py +0 -0
  90. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_console_job.py +0 -0
  91. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_console_results.py +0 -0
  92. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_dollar_command_escapes.py +0 -0
  93. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_file_transfer.py +0 -0
  94. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_logger_api.py +0 -0
  95. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_logger_integration.py +0 -0
  96. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_production_contract.py +0 -0
  97. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_python_version.py +0 -0
  98. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_recommended_examples.py +0 -0
  99. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_release_inputs.py +0 -0
  100. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_session_close_reporting.py +0 -0
  101. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_session_proxy_cleanup.py +0 -0
  102. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_session_settings.py +0 -0
  103. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_spy_source_mapping.py +0 -0
  104. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_spy_thread_session.py +0 -0
  105. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_ssh_config.py +0 -0
  106. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_ssh_security.py +0 -0
  107. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_sshscript_dollar_syntax.py +0 -0
  108. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_sshscript_module.py +0 -0
  109. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_stdin_contract.py +0 -0
  110. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_stdio_dynamic_string.py +0 -0
  111. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_syntax_error.spy +0 -0
  112. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_update_check.py +0 -0
  113. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript/unittest/test_version_policy.py +0 -0
  114. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript.egg-info/dependency_links.txt +0 -0
  115. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript.egg-info/entry_points.txt +0 -0
  116. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript.egg-info/requires.txt +0 -0
  117. {sshscript-6.0.0 → sshscript-6.1.0}/src/sshscript.egg-info/top_level.txt +0 -0
  118. {sshscript-6.0.0 → sshscript-6.1.0}/tools/check_baseline_examples.py +0 -0
  119. {sshscript-6.0.0 → sshscript-6.1.0}/tools/check_release.py +0 -0
  120. {sshscript-6.0.0 → sshscript-6.1.0}/tools/publish_release.py +0 -0
  121. {sshscript-6.0.0 → sshscript-6.1.0}/tools/run_checks.py +0 -0
  122. {sshscript-6.0.0 → sshscript-6.1.0}/tools/run_native_auth_ci.py +0 -0
  123. {sshscript-6.0.0 → sshscript-6.1.0}/tools/setup_openssh_ci.sh +0 -0
  124. {sshscript-6.0.0 → sshscript-6.1.0}/tools/version_policy.py +0 -0
@@ -4,7 +4,7 @@ permissions:
4
4
  contents: read
5
5
  jobs:
6
6
  verify:
7
- timeout-minutes: 20
7
+ timeout-minutes: 30
8
8
  strategy:
9
9
  fail-fast: false
10
10
  matrix:
@@ -48,7 +48,7 @@ jobs:
48
48
  openssh-integration:
49
49
  name: OpenSSH integration (Python ${{ matrix.python-version }}, ${{ matrix.dependency-label }})
50
50
  runs-on: ubuntu-latest
51
- timeout-minutes: 25
51
+ timeout-minutes: 35
52
52
  strategy:
53
53
  fail-fast: false
54
54
  matrix:
@@ -12,7 +12,7 @@ jobs:
12
12
  build:
13
13
  name: Verify and attest release artifacts
14
14
  runs-on: ubuntu-latest
15
- timeout-minutes: 20
15
+ timeout-minutes: 30
16
16
  permissions:
17
17
  contents: read
18
18
  id-token: write
@@ -121,3 +121,5 @@ htmlcov/
121
121
  !/unittest/test_update_check.py
122
122
 
123
123
  !/unittest/test_interactive_console.py
124
+
125
+ !/unittest/test_embedded_console.py
@@ -1,10 +1,9 @@
1
1
  # SSHScript for AI agents
2
2
 
3
- **Documentation contract: SSHScript 6.0.0; Python 3.11+.**
3
+ **Documentation contract: SSHScript 6.1.0; Python 3.11+.**
4
4
 
5
- This guide targets the 6.0.0 source contract and verified local wheels.
6
- [5.0.0 remains the latest published package](https://pypi.org/project/sshscript/5.0.0/);
7
- source publication and CI do not upload 6.0.0 to PyPI.
5
+ This guide targets SSHScript 6.1.0 and its public Console integration API.
6
+ Install `sshscript==6.1.0` and verify the imported package version.
8
7
  For nested SSH, provision known_hosts on each calling host: `"parent"` is the
9
8
  default. Read the 4.0.2-to-5.0 migration before reusing older automation.
10
9
 
@@ -19,14 +18,22 @@ workflow. Its folder contains the supporting guide; copy the entire
19
18
  It teaches SSHScript usage, not development of SSHScript itself or a particular
20
19
  recipe library. Agents without skill support can read these Markdown files.
21
20
 
22
- ## Interactive console (6.0)
21
+ ## Borrowed Session integration (unified Console API)
22
+
23
+ Use `from sshscript import Console` and `with Console(existing_session)` for an
24
+ existing authorized connection. Read [the full host contract](EMBEDDED_CONSOLE.md)
25
+ before driving it. Require structured completion/exit status and confirmed reuse;
26
+ keep host account policy and credentials in the resolver. A prompt/silence is not
27
+ a success signal. See the bundled skill for the matching agent guidance.
28
+
29
+ ## Interactive console (6.1)
23
30
 
24
31
  For step-by-step use from a human or UI agent's retained terminal, see the
25
32
  [console guide](CONSOLE.md) and the agent guide's
26
- [console instructions](skills/sshscript/references/agent-guide.md#interactive-console-60).
33
+ [console instructions](skills/sshscript/references/agent-guide.md#interactive-console-61).
27
34
  Run `python3 -B sshscript.py console` from the matching development checkout;
28
- an installed 6.0.0 wheel provides `sshscript console`. Published 5.0.0 does not
29
- include this console.
35
+ an installed 6.1.0 wheel provides `sshscript console`. Use public
36
+ Console(session) for host integration; older 6.0 console internals are removed.
30
37
 
31
38
  Input remains Python with dollar commands: `$hostname` executes a command,
32
39
  `print($.stdout)` reads its result, and variables persist between inputs.
@@ -109,4 +116,4 @@ Validate links, skill structure, example behavior, source/ZIP equality and
109
116
  the rendered website. Source and website publishing are separate operations;
110
117
  copying files does not publish either of them.
111
118
 
112
- *Last modified: 2026-10-22 23:24:55*
119
+ *Last modified: 2026-10-23 18:39:31*
@@ -1,7 +1,7 @@
1
1
  # Recommended SSHScript API
2
2
 
3
3
  This is the canonical entry point for new code and AI-generated examples.
4
- **Current source contract: 6.0.0**, Python 3.11+. Sections marked **4.0** require
4
+ **Current release contract: 6.1.0**, Python 3.11+. Sections marked **4.0** require
5
5
  SSHScript 4.0.2 or newer; installing `sshscript==3.1.5` does not provide them.
6
6
  Examples labelled 3.1.5 remain verified compatibility examples for that baseline.
7
7
  Pin the exact artifact version and record it with execution diagnostics when
@@ -31,8 +31,8 @@ identifiers and handoff reports are future work, not a current API guarantee.
31
31
 
32
32
  ## One command, one retained result (4.0; compatible with 3.1.5)
33
33
 
34
- For this source contract, install a verified 6.0.0 wheel. Once published, pin
35
- `sshscript==6.0.0`; the source push does not itself upload a PyPI package.
34
+ Install `sshscript==6.1.0` for this contract. Verify the installed version and
35
+ artifact before operational use; source publication alone does not install it.
36
36
  For reproducing only the historical compatibility baseline, explicitly install
37
37
  `sshscript==3.1.5`; the 4.0 sections require the current release.
38
38
  An argv list is one command, not a batch. Shell-looking argument text stays data.
@@ -235,7 +235,7 @@ print(result.stdout)
235
235
 
236
236
  ## 4.0: settings and managed jobs
237
237
 
238
- Use SSHScript 6.0.0 for the following examples. CLI `-v`, `--stderr`, and
238
+ Use SSHScript 6.1.0 for the following examples. CLI `-v`, `--stderr`, and
239
239
  `--debug` override root defaults only for that execution. Child Sessions copy
240
240
  their parent's settings. `set()` validates before changing anything; `get()`
241
241
  returns a copy. Explicit command options override Session policy.
@@ -254,7 +254,7 @@ the settings dictionary at creation; the policy object itself is shared, so
254
254
  custom policies with mutable state must account for reuse. Changing the setting
255
255
  does not reconfigure existing connections.
256
256
 
257
- <!-- example: {"id":"settings", "profile":"6.0.0", "stdout":"True\n1\n"} -->
257
+ <!-- example: {"id":"settings", "profile":"6.1.0", "stdout":"True\n1\n"} -->
258
258
  ```python
259
259
  from contextlib import closing
260
260
  import paramiko
@@ -276,7 +276,7 @@ semantics; do not combine it with `command_timeout`.
276
276
  For an indefinite command such as tcpdump, use `start(timeout=None)`. This safe
277
277
  local example uses a small Python process instead of capturing network traffic:
278
278
 
279
- <!-- example: {"id":"stop", "profile":"6.0.0", "stdout":"cancelled confirmed\n"} -->
279
+ <!-- example: {"id":"stop", "profile":"6.1.0", "stdout":"cancelled confirmed\n"} -->
280
280
  ```python
281
281
  from contextlib import closing
282
282
  import sys
@@ -315,7 +315,7 @@ legacy SSH behavior which appended a newline to strings: if a consumer
315
315
  needs a line, migrate `input=password` to `input=password + "\n"` explicitly.
316
316
  Published 3.1.5 still has the legacy SSH behavior.
317
317
 
318
- <!-- example: {"id":"exact-stdin", "profile":"6.0.0", "stdout":"'abc'\n'abc\\n'\n"} -->
318
+ <!-- example: {"id":"exact-stdin", "profile":"6.1.0", "stdout":"'abc'\n'abc\\n'\n"} -->
319
319
  ```python
320
320
  from contextlib import closing
321
321
  import sys
@@ -384,7 +384,7 @@ or unpack exactly `stdout, stderr, exitcode`. This changes the released 3.1.5
384
384
  console contract: old `stdout, stderr = shell(command)` must be migrated.
385
385
  Shell calls accept command strings; argv execution belongs to Session.
386
386
 
387
- <!-- example: {"id":"shell-result", "profile":"6.0.0", "stdout":"first 0\n"} -->
387
+ <!-- example: {"id":"shell-result", "profile":"6.1.0", "stdout":"first 0\n"} -->
388
388
  ```python
389
389
  from contextlib import closing
390
390
  from sshscript import Session
@@ -440,7 +440,7 @@ matching: `iter_stdout()` yields chunks, not packets or lines. Put `stop()` in
440
440
  `finally`, so condition matches, exceptions, and KeyboardInterrupt all trigger
441
441
  cleanup. This credential-free example uses a continuous Python process:
442
442
 
443
- <!-- example: {"id":"stream-condition", "profile":"6.0.0", "stdout":"cancelled confirmed\n"} -->
443
+ <!-- example: {"id":"stream-condition", "profile":"6.1.0", "stdout":"cancelled confirmed\n"} -->
444
444
  ```python
445
445
  from contextlib import closing
446
446
  import sys
@@ -505,7 +505,7 @@ the current Bash console's channel, cwd, environment and identity. It does not
505
505
  launch through the underlying Session. Ordinary `console(command)` / `$command`
506
506
  still waits and returns `CommandResult`; no migration is needed for finite commands.
507
507
 
508
- <!-- example: {"id":"console-stream", "profile":"6.0.0", "stdout":"cancelled confirmed\nconsole-alive\n"} -->
508
+ <!-- example: {"id":"console-stream", "profile":"6.1.0", "stdout":"cancelled confirmed\nconsole-alive\n"} -->
509
509
  ```python
510
510
  from contextlib import closing
511
511
  import shlex
@@ -765,6 +765,14 @@ The same source is mirrored into the website; edit this file, then use
765
765
  production details remain in the full documentation.
766
766
 
767
767
 
768
+ ## Embed an existing Session (unified Console API)
769
+
770
+ `from sshscript import Console` provides a supported integration surface without
771
+ editing reserved variables. `Console(session)` borrows the connection, while
772
+ `ownership="owned"` explicitly transfers ownership. Read the complete
773
+ [API and lifecycle contract](EMBEDDED_CONSOLE.md) for structured updates,
774
+ credentials, identity, PTY limits, exceptions and runnable examples.
775
+
768
776
  ## Interactive console and script completion (6.0)
769
777
 
770
778
  See [the console guide](CONSOLE.md) for the 6.0 Python-first dollar REPL, retained
@@ -782,4 +790,4 @@ ScriptResult with exitcode, stopped and message attributes; run_file() returns
782
790
  the integer status. Cleanup failures still propagate. Python SystemExit remains
783
791
  separate from normal script completion.
784
792
 
785
- *Last modified: 2026-10-22 23:24:55*
793
+ *Last modified: 2026-10-23 18:39:31*
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 6.1.0 — 2026-10-23
4
+
5
+ - Add public `Console(existing_session)` for terminal and host/AI-agent interaction
6
+ over an existing Session, including target Sessions reached through jumps,
7
+ without reconnecting or changing authentication/host-key sources.
8
+ - Retain Python variables, shell cwd/environment and interactive program state.
9
+ Expose structured submit/poll/input/interrupt/EOF and completion results.
10
+ - Add account credential/policy and switch-result hooks, separate connection login
11
+ identity from effective shell identity, and expose the environment stack.
12
+ - Borrow Sessions by default. Unwind console-owned layers and confirm Session
13
+ reuse; initialization/restoration/cleanup failures report unknown explicitly.
14
+ - Fix lost prompt readiness after program Ctrl-C. Keep waiting_input after a
15
+ protocol match, running for ignored/unfinished interrupts, and real exit status
16
+ for exiting programs; support delayed prompt recovery through poll.
17
+ - Use one Console implementation for CLI and embedding. Remove old internal
18
+ InteractiveConsole/open()/execute() entry points and :help/:quit aliases; use
19
+ Console(session), submit(), help/quit and structured status instead.
20
+ - Add complete terminal/agent examples and the matching AI skill. OpsFeast reports
21
+ local/simulated and browser UI revalidation passed. This is distinct from real
22
+ SSH/jump/PAM/su/sudo validation; release CI records its own disposable native tests.
23
+ - Development-stage releases do not guarantee backward compatibility unless
24
+ explicitly requested. Minor releases may include the differences listed above.
25
+
3
26
  ## 6.0.0 — 2026-10-22 (incompatible source contract)
4
27
 
5
28
  - Add console helpers: help/help(), help(object), exit/exit() to leave one
@@ -314,4 +337,4 @@ verified artifacts in the
314
337
  [Unreleased]: https://github.com/iapyeh/sshscript/compare/v3.1.4...release
315
338
  [3.1.4]: https://github.com/iapyeh/sshscript/releases/tag/v3.1.4
316
339
 
317
- *Last modified: 2026-10-22 23:24:55*
340
+ *Last modified: 2026-10-23 18:39:31*
@@ -1,16 +1,24 @@
1
- # Interactive console (6.0)
1
+ # Interactive console (6.1)
2
2
 
3
- The SSHScript 6.0 console runs from the flat development checkout:
3
+ The SSHScript 6.1 console runs from the flat development checkout:
4
4
 
5
5
  ```sh
6
6
  python3 -B sshscript.py console
7
7
  ```
8
8
 
9
- Use Python 3.11 or newer with the project's dependencies. An installed 6.0.0
10
- wheel provides `sshscript console`. Published 5.0.0 lacks this feature; this
11
- source version update does not upload a package. No installation into the
9
+ Use Python 3.11 or newer with the project's dependencies. Install
10
+ `sshscript==6.1.0` for `sshscript console`. No installation into the flat
12
11
  development checkout is required.
13
12
 
13
+ ## Embed an existing Session (new API)
14
+
15
+ Use the public `sshscript.Console` API for borrowed Sessions, host-driven updates,
16
+ credential hooks and explicit cleanup/reuse reporting. Read
17
+ [the embedded console contract](EMBEDDED_CONSOLE.md) and its executable examples.
18
+ The CLI uses the same Console core and ownership contract. Earlier 6.0 CLI
19
+ compatibility is not maintained; see the linked contract for removed entry points
20
+ and aliases. This guide describes the 6.1.0 release contract.
21
+
14
22
  ## A Python console with dollar syntax
15
23
 
16
24
  Input is Python by default, with SSHScript dollar syntax available in the same
@@ -18,7 +26,7 @@ namespace. No Python/shell language-mode switching is required:
18
26
 
19
27
  ```text
20
28
  local [user]> hostname
21
- NameError: name 'hostname' is not defined
29
+ NameError: operation failed; inspect error for details.
22
30
  local [user]> hostname = "a Python variable"
23
31
  local [user]> hostname
24
32
  'a Python variable'
@@ -165,7 +173,8 @@ Readiness or silence is not interpreted as a successful process exit.
165
173
  - In an enter() program, Ctrl-C/Ctrl-D are forwarded as raw terminal control
166
174
  characters, without an added newline. Only a detected return to the parent
167
175
  shell closes that program layer; the Python console stays available.
168
- - `:help` and `:quit` remain aliases; `:status` shows the environment stack.
176
+ - Use `help`/`help()` and `quit`/`quit()`. The former `:help` and `:quit` aliases
177
+ are removed; `:status` shows the environment stack.
169
178
  The Python helper names can be shadowed by assignment, just like Python names;
170
179
  the colon aliases and `control.quit()` remain available.
171
180
  - Python `sys.exit()` retains SystemExit semantics. The interactive boundary
@@ -205,4 +214,4 @@ Input now follows Python/dollar syntax throughout; prefix shell commands with `$
205
214
  Type `help` for a reminder. Operation failures display their type without
206
215
  reprinting credential-bearing input; inspect `error` at the prompt for details.
207
216
 
208
- *Last modified: 2026-10-22 23:24:55*
217
+ *Last modified: 2026-10-23 18:39:31*
@@ -0,0 +1,200 @@
1
+ # Unified Console API
2
+
3
+ **Contract: SSHScript 6.1.0; Python 3.11+.**
4
+
5
+ Design and lifecycle contract, written before implementation.
6
+
7
+ `from sshscript import Console` exposes the shared Python/dollar console
8
+ on an existing `Session`. `Console(session, ownership="borrowed")` opens only a
9
+ new Bash channel, without connect(), authentication or Session settings changes.
10
+ Jump connections and SSH/SFTP login identity remain owned by the caller.
11
+ `ownership="owned"` explicitly transfers the supplied Session (and its children,
12
+ not its ancestors) to the console. Ownership begins at construction, including
13
+ initialization failure. A local Session is also supported.
14
+
15
+ All calls run on the constructing thread. The caller must serialize console and
16
+ Session use until close. Python evaluation and lexical with blocks are synchronous;
17
+ arbitrary Python cannot be safely preempted. `submit(line)` retains the existing
18
+ compiler and namespace. `start_command(command, timeout=60)` starts the existing
19
+ persistent-shell foreground job; `poll()` reads incremental output without waiting.
20
+ `send_input(text, timeout=...)` replies to an enter() program, appending one newline.
21
+ `send_input(text, newline=False)` sends raw text to an enter() program.
22
+ Shell jobs reject stdin writes: a late write could reach the parent shell after
23
+ the process exits. Use enter() for interactive applications.
24
+ `interrupt()` stops a job with bounded recovery, or sends Ctrl-C to a program;
25
+ otherwise it discards unfinished Python input. After program Ctrl-C, a matched
26
+ program prompt returns `waiting_input` and remains ready across polls, even when
27
+ the protocol has consumed the prompt text. A control-read timeout stays `running`;
28
+ a later prompt can restore readiness through `poll()`. `error_type=KeyboardInterrupt`
29
+ records the interruption attempt, not program completion or failure. If the program
30
+ exits, its restored exit status determines `completed` or `failed`. `eof()` leaves program EOF behavior
31
+ unchanged; at the Python prompt it closes the console. `leave()` exits one layer,
32
+ including the root shell; `close()` unwinds everything. `quit` closes everything.
33
+ Python SystemExit is reported as failed input and leaves the console open.
34
+ A host exception in `with Console(...)` runs cleanup; cleanup failure is never
35
+ suppressed. Initialization failure cleans up the acquired channel.
36
+
37
+ Immutable `ConsoleUpdate` records carry state, incremental stdout/stderr, a command result,
38
+ safe error type, and Session reuse status. States are `ready`, `incomplete`,
39
+ `running`, `waiting_input`, `completed`, `failed`, `closed`. A nonzero exit status,
40
+ Python/syntax error, deadline expiry or unconfirmed recovery is failure. `completed`
41
+ requires a real job completion record or a restored program exit status, never silence. `waiting_input` means an
42
+ enter() prompt matched the configured program protocol; it is not proof of command
43
+ success. Silent or timed-out interactive reads remain `running`. A generic process
44
+ cannot reveal whether it is waiting for stdin: shell jobs remain `running` until
45
+ completion. Polling does not infer readiness from text.
46
+ For an ordinary Python cell, completed means evaluation returned without an
47
+ uncaught exception. It does not certify side effects or results of arbitrary
48
+ Python calls. The exit-status classification applies to managed dollar jobs,
49
+ start_command() and restored enter() program results; inspect other returned
50
+ Python API results explicitly. Configure enter() prompts
51
+ for text programs; full-screen terminal emulation is unsupported.
52
+
53
+ `connection` returns the current Session, without changing its login account.
54
+ `environments` returns immutable snapshots with kind, host, connection username,
55
+ effective username and identity verification. Effective names come from `id -un`
56
+ on entry; program layers inherit the last verified name with verification false.
57
+ They are observations, not authorization evidence for subsequent arbitrary Python.
58
+ Leaving a nested layer restores its parent channel; closing the root channel does
59
+ not attempt to preserve that shell's cwd/environment in the Session's later new
60
+ commands. A successful borrowed close additionally probes Session command execution
61
+ with a finite budget; `session_reuse="confirmed"` means cleanup and that probe
62
+ succeeded. `unknown` means the caller must reconcile or discard the connection;
63
+ `closed` means an owned Session was closed. No API can guarantee future network
64
+ availability or remote child-process termination from a channel close alone.
65
+
66
+ `credential_resolver(SwitchRequest)` runs before every managed su/sudo switch,
67
+ including explicit credentials. It may reject by raising or return
68
+ `SwitchCredentials(password=...)`; None keeps explicit credentials. Request metadata
69
+ contains no password. `switch_observer(SwitchOutcome)` receives success/failure,
70
+ request, resulting environment and safe error type, never credential/error text.
71
+ The resolver implements host configuration, candidate ordering and policy outside
72
+ SSHScript. It is called once per attempted switch; there is no automatic retry.
73
+ A missing password in non-terminal mode raises `CredentialsRequiredError`;
74
+ explicit None requests passwordless authentication. Observer failure propagates;
75
+ a successful switch remains active and is visible for cleanup. Explicit password
76
+ arguments and arbitrary Python error objects are sensitive application memory.
77
+ Do not log input source, namespace/error objects, JobResult.command or arbitrary
78
+ program output containing secrets. Use the resolver rather than literals in input.
79
+ Hooks cover managed Console.su/sudo and prompt-level dollar calls; arbitrary Python,
80
+ raw Session access and lexical script with blocks are trusted code, not a policy
81
+ sandbox. Hosts must restrict supplied code if they need enforced authorization.
82
+
83
+ `terminal=False` needs no terminal on the Python process. The new shell still uses
84
+ a PTY by default for su/sudo and reliable job interruption. `get_pty=False` supports
85
+ pipe jobs but cannot safely interrupt a running job; recovery can be unknown.
86
+ Programs/password readers may require a PTY. No hidden getpass prompt is used in
87
+ non-terminal mode. `terminal=True` enables the terminal frontend of the same Console core
88
+ and getpass fallback. Captured event streams and terminal streams are separate;
89
+ headless capture is consumed by each returned update. When a custom stdout or
90
+ stderr stream is supplied (or terminal=True selects terminal streams), that
91
+ stream receives the output and the corresponding update output field is empty. Only one console/redirected
92
+ Python execution may run in a process at a time because Python stdout redirection
93
+ is process-global. This API does not provide a Python sandbox or an async Python
94
+ executor.
95
+
96
+ ## One core for CLI and host integration
97
+
98
+ The CLI and embedding applications use the same public `Console` implementation
99
+ in `interactive.py`. There is no legacy InteractiveConsole class, separate embedded
100
+ subclass, or compatibility module. The CLI creates an explicit owned local Session;
101
+ embedding callers normally borrow the Session they already hold. Both frontends
102
+ use submit(), poll(), interrupt(), eof() and the same cleanup and failure policy.
103
+ The CLI drains foreground $.start() jobs through poll() before reading another line.
104
+
105
+ Earlier 6.0 CLI behavior is not a compatibility target. Internal open()/execute()
106
+ entry points are removed: construct Console(session) and call submit(line).
107
+ `:help` and `:quit` aliases are removed; use help/help() and quit/quit(). `:status`
108
+ remains the environment-stack command. Diagnostics always omit source and arbitrary
109
+ exception text, including NameError and SystemExit values. A failed channel remains
110
+ owned until cleanup and is reported as unknown; it is not silently popped to an
111
+ apparently healthy parent layer. SSH/SFTP authentication and Session command APIs
112
+ keep their existing responsibilities.
113
+
114
+ This public API is released in SSHScript 6.1.0. During development, minor releases
115
+ may include incompatible changes; compatibility is required only when explicitly
116
+ requested. Pin the released artifact or record the exact source revision/hash.
117
+
118
+ ## Executable examples
119
+
120
+ Run from the flat source checkout with `PYTHONPATH=. python3 -B` followed by
121
+ [examples/embedded_agent.py](examples/embedded_agent.py) or
122
+ [examples/embedded_terminal.py](examples/embedded_terminal.py). With the matching
123
+ installed wheel the PYTHONPATH setting is unnecessary. Both examples create only
124
+ local Sessions; `handover(existing_target)` and `drive(existing_target)` also
125
+ accept a host's already-connected target Session, including a jump target.
126
+
127
+ Terminal handover, followed by reuse:
128
+
129
+ ```python
130
+ from sshscript import Console
131
+
132
+ def handover(existing_target):
133
+ with Console(existing_target, terminal=True) as console:
134
+ console.interact()
135
+ assert console.session_reuse == "confirmed"
136
+ return existing_target.exec_command("hostname", check=True)
137
+ ```
138
+
139
+ Non-terminal Python/dollar cells and asynchronous shell output:
140
+
141
+ ```python
142
+ import time
143
+ from sshscript import Console
144
+
145
+ def drive(existing_target):
146
+ with Console(existing_target) as console:
147
+ assert console.submit("value = 42").state == "completed"
148
+ assert console.submit("$export TASK=value; cd /tmp").state == "completed"
149
+ update = console.start_command('printf "%s" "$TASK"', timeout=3)
150
+ while True:
151
+ consume(update.stdout, update.stderr) # host callback
152
+ if update.state != "running":
153
+ break
154
+ time.sleep(.03)
155
+ update = console.poll()
156
+ if update.state != "completed":
157
+ raise RuntimeError("command failed: " + str(update.error_type))
158
+ assert update.result.exitcode == 0
159
+ return existing_target.exec_command("printf reusable", check=True)
160
+ ```
161
+
162
+ For a text program, call `console.enter("python3 -q -u", prompt=">>>", exit="quit()")`,
163
+ then `console.send_input("print(42)", timeout=2)`. A prompt response produces
164
+ `waiting_input`; program exit restores the parent and supplies `result.exitcode`.
165
+ `poll()` can observe a program's later prompt/exit after a timed-out read or raw
166
+ send. Program results snapshot the final input buffers; retain incremental events
167
+ for the full conversation. Job results snapshot the bounded whole command output.
168
+ `poll()` may perform bounded shell recovery when an enter() program has exited;
169
+ it does not block on a running shell job. Program restoration uses the existing
170
+ console exit budget (up to 60 seconds), unlike immediate job polling.
171
+
172
+ Credential and policy integration (no host configuration parser in SSHScript):
173
+
174
+ ```python
175
+ from sshscript import Console, SwitchCredentials
176
+
177
+ def resolve(request):
178
+ host_policy.require_switch(request.environment.host, request.kind, request.username)
179
+ # Return None only when keeping an explicit password argument. Returning a
180
+ # SwitchCredentials object with password=None deliberately tries passwordless.
181
+ return SwitchCredentials(secret_store.password_for(request.username))
182
+
183
+ with Console(existing_target, credential_resolver=resolve,
184
+ switch_observer=audit_switch_metadata) as console:
185
+ console.su("operator")
186
+ print(console.environments[-1])
187
+ console.leave()
188
+ ```
189
+
190
+ The host supplies `consume`, `host_policy`, `secret_store` and
191
+ `audit_switch_metadata`; the linked executable examples need none of these.
192
+ Resolver/observer exceptions from direct method calls propagate. `submit()` reports
193
+ safe error types instead. Cleanup errors always raise `ConsoleCleanupError` with
194
+ `session_reuse="unknown"`; repeated close reports the same failure. Initialization
195
+ raises `ConsoleInitializationError` with unknown reuse, even if channel cleanup
196
+ appeared successful. If the host body and cleanup both fail, both errors are
197
+ preserved in a BaseExceptionGroup. Do not reuse an unknown Session on the strength
198
+ of a prompt or a successful command from an unrelated shell.
199
+
200
+ *Last modified: 2026-10-23 18:39:31*
@@ -60,3 +60,8 @@ recursive-include src/sshscript *.pyi
60
60
 
61
61
  include src/sshscript/unittest/test_interactive_console.py
62
62
  include CONSOLE.md
63
+
64
+ include src/sshscript/unittest/test_embedded_console.py
65
+ include EMBEDDED_CONSOLE.md
66
+ include examples/embedded_terminal.py
67
+ include examples/embedded_agent.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sshscript
3
- Version: 6.0.0
3
+ Version: 6.1.0
4
4
  Summary: Python automation for local processes, SSH, and SSHScript .spy files
5
5
  Author-email: "Yeh, Hsin-Yuan" <iapyeh@gmail.com>
6
6
  License-Expression: MIT
@@ -13,7 +13,7 @@ Project-URL: Issues, https://github.com/iapyeh/sshscript/issues
13
13
  Project-URL: Security, https://github.com/iapyeh/sshscript/security/policy
14
14
  Project-URL: Support, https://github.com/iapyeh/sshscript/discussions
15
15
  Keywords: automation,paramiko,remote-execution,sftp,ssh,subprocess,system-administration
16
- Classifier: Development Status :: 5 - Production/Stable
16
+ Classifier: Development Status :: 4 - Beta
17
17
  Classifier: Operating System :: MacOS
18
18
  Classifier: Operating System :: POSIX :: Linux
19
19
  Classifier: Programming Language :: Python :: 3
@@ -31,7 +31,12 @@ Dynamic: license-file
31
31
 
32
32
  # SSHScript
33
33
 
34
- SSHScript 6.0.0: [interactive console and script completion](CONSOLE.md).
34
+ **Development status:** SSHScript is still under active development. Backward
35
+ compatibility is not maintained at this stage. APIs, CLI behavior, configuration
36
+ and data formats may change; we provide only brief notes on major differences.
37
+ Compatibility work is done only when explicitly requested by the maintainer.
38
+
39
+ SSHScript 6.1.0: [borrow an existing Session for interactive operation](EMBEDDED_CONSOLE.md).
35
40
  Run `python3 -B sshscript.py console` from the development checkout.
36
41
 
37
42
 
@@ -54,10 +59,9 @@ output and status, distinguish failure from timeout, and make cleanup and
54
59
  unknown termination explicit. A successful command exit is evidence about
55
60
  that command; verify the deployment or other application outcome separately.
56
61
 
57
- **Source contract: 6.0.0.** The release branch and local artifacts target this
58
- version; this source update does not publish a package or create a release tag.
59
- **Latest published release:** [5.0.0](https://github.com/iapyeh/sshscript/releases/tag/v5.0.0)
60
- (Production/Stable) · **Python:** 3.11 or newer · **Tested:** Python
62
+ **Release contract: 6.1.0.** Install the matching distribution and verify its version.
63
+ [Release notes](https://github.com/iapyeh/sshscript/releases/tag/v6.1.0)
64
+ (development-stage project) · **Python:** 3.11 or newer · **Tested:** Python
61
65
  3.11–3.14 on Linux and macOS
62
66
 
63
67
  [Documentation](https://iapyeh.github.io/sshscript/v3a/) ·
@@ -66,13 +70,14 @@ version; this source update does not publish a package or create a release tag.
66
70
  [Security](https://github.com/iapyeh/sshscript/security/policy) ·
67
71
  [Support](https://github.com/iapyeh/sshscript/blob/release/SUPPORT.md)
68
72
 
69
- **Version 6.0.0** adds a Python-first interactive console and normal script
70
- completion through `$.stop()`. It removes `$.exit()`, `$.break()` and their old
71
- control exceptions; review the [6.0 migration](VERSIONING.md#60-console-and-script-completion-migration).
72
- It retains the 5.0 calling-host default for host-key sources. Provision its known_hosts file or
73
- select `known_hosts="local"` when migrating existing nested connections.
74
- Upgrade 3.1.5 automation using the migration guide and application regressions.
75
- See [version policy and migration](VERSIONING.md) before upgrading automation.
73
+ **Version 6.1.0** adds public `Console(existing_session)` integration for terminals,
74
+ host applications and AI agents. Python variables, shell state and text programs
75
+ persist; structured updates distinguish readiness, execution, failure and cleanup.
76
+ Borrowed Sessions remain owned by the host. Credential/policy hooks and verified
77
+ Session reuse keep integration explicit. Read the [integration contract](EMBEDDED_CONSOLE.md).
78
+ Earlier 6.0 console internals, open()/execute() entry points and :help/:quit aliases
79
+ are removed; use Console(session), submit(), help/quit and structured updates.
80
+ The 5.0 host-key source and SSH/SFTP login identity contracts remain unchanged.
76
81
 
77
82
  ## AI agent ready
78
83
 
@@ -161,7 +166,7 @@ pin SSHScript and all transitive dependencies in your application's lock file.
161
166
  To install this release explicitly:
162
167
 
163
168
  ```sh
164
- python3 -m pip install /path/to/sshscript-6.0.0-py3-none-any.whl
169
+ python3 -m pip install "sshscript==6.1.0"
165
170
  ```
166
171
 
167
172
  Use `python3 -m pip install --upgrade sshscript` to upgrade. The optional
@@ -170,11 +175,9 @@ it never installs an update by itself.
170
175
 
171
176
  ## 60-second local quickstart
172
177
 
173
- For 6.0.0, install a verified local wheel as shown above until package publication.
174
- After publication, pin `sshscript==6.0.0` instead. The latest published 5.0.0
175
- package does not contain the new console.
178
+ Install `sshscript==6.1.0` for the Console integration documented here.
176
179
 
177
- This example runs on 6.0.0 and retains compatibility with the 3.1.5 command API.
180
+ This example runs on 6.1.0 and retains compatibility with the 3.1.5 command API.
178
181
  The metadata below records that older compatibility baseline.
179
182
 
180
183
  <!-- example: {"id":"readme-quickstart", "profile":"3.1.5", "stdout":"sshscript is ready\nexit code: 0\n"} -->
@@ -620,4 +623,8 @@ for maintainer-only release steps.
620
623
  SSHScript is released under the
621
624
  [MIT License](https://github.com/iapyeh/sshscript/blob/release/LICENSE.txt).
622
625
 
623
- *Last modified: 2026-10-22 23:24:55*
626
+ For embedding an existing connected Session, see the new public
627
+ [Console integration API](EMBEDDED_CONSOLE.md). CLI and embedding applications
628
+ share one implementation; earlier 6.0 CLI compatibility is not maintained.
629
+
630
+ *Last modified: 2026-10-23 18:39:31*