sshscript 4.0.2__tar.gz → 6.0.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.
- {sshscript-4.0.2 → sshscript-6.0.0}/.gitignore +2 -0
- sshscript-6.0.0/AI_GUIDE.md +112 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/API_GUIDE.md +165 -11
- {sshscript-4.0.2 → sshscript-6.0.0}/CHANGELOG.md +41 -0
- sshscript-6.0.0/CONSOLE.md +208 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/MANIFEST.in +6 -0
- {sshscript-4.0.2/src/sshscript.egg-info → sshscript-6.0.0}/PKG-INFO +78 -8
- {sshscript-4.0.2 → sshscript-6.0.0}/README.md +77 -7
- {sshscript-4.0.2 → sshscript-6.0.0}/SECURITY.md +11 -2
- {sshscript-4.0.2 → sshscript-6.0.0}/VERSIONING.md +53 -3
- sshscript-6.0.0/skills/sshscript/SKILL.md +89 -0
- sshscript-6.0.0/skills/sshscript/references/agent-guide.md +432 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/__init__.py +2 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/__init__.pyi +2 -1
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/_version.py +1 -1
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/channelgeneric.py +7 -2
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/dollarparser.py +3 -1
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/errorutils.py +0 -7
- sshscript-6.0.0/src/sshscript/interactive.py +606 -0
- sshscript-6.0.0/src/sshscript/scriptcontrol.py +34 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/session.py +124 -27
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/session.pyi +18 -3
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/sessionsettings.py +17 -1
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/sessionwrapper.py +3 -4
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/sessionwrapper.pyi +1 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/spyimporter.py +10 -1
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/sshscript.py +25 -22
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_command_job.py +5 -1
- sshscript-6.0.0/src/sshscript/unittest/test_interactive_console.py +631 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_logger_integration.py +16 -3
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_openssh_integration.py +30 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_recommended_examples.py +22 -1
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_session_close_reporting.py +6 -6
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_ssh_config.py +7 -1
- sshscript-6.0.0/src/sshscript/unittest/test_ssh_security.py +531 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_sshscript_module.py +4 -4
- {sshscript-4.0.2 → sshscript-6.0.0/src/sshscript.egg-info}/PKG-INFO +78 -8
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript.egg-info/SOURCES.txt +8 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/tools/check_public_types.py +14 -5
- {sshscript-4.0.2 → sshscript-6.0.0}/tools/prepare_release.py +5 -1
- sshscript-6.0.0/tools/sync_ai_docs.py +101 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/tools/sync_api_guide.py +1 -0
- sshscript-4.0.2/src/sshscript/unittest/test_ssh_security.py +0 -295
- {sshscript-4.0.2 → sshscript-6.0.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/.github/dependabot.yml +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/.github/pull_request_template.md +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/.github/workflows/ci.yml +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/.github/workflows/codeql.yml +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/.github/workflows/release.yml +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/CODE_OF_CONDUCT.md +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/CONTRIBUTING.md +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/EXCEPTIONS.md +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/LICENSE.txt +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/RELEASING.md +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/SUPPORT.md +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/pyproject.toml +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/setup.cfg +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/_sshscript_cli.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/_runtime.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/channelssh.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/channelsubprocess.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/channelutils.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/commandjob.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/commandjob.pyi +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/commandresult.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/commandresult.pyi +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/consolejob.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/dollar.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/dollarchanger.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/patching.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/py.typed +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/sshconfig.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/stdio.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/tokenparser.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/README.console-authentication.md +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/README.console-results.md +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/README.dollar-syntax-tests.md +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/README.module-tests.md +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/check_package_asserts.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/dollar_syntax.spy +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/dollar_syntax_fixture.spy +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/language_fixture.spy +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_channelgeneric_expect.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_check_file.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_command_api.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_console_authentication.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_console_job.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_console_results.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_dollar_command_escapes.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_file_transfer.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_logger_api.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_native_auth_runner.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_native_console_authentication.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_production_contract.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_public_types.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_python_version.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_release_inputs.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_session_proxy_cleanup.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_session_settings.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_spy_source_mapping.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_spy_thread_session.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_sshscript_dollar_syntax.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_stdin_contract.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_stdio_dynamic_string.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_syntax_error.spy +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_update_check.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript/unittest/test_version_policy.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript.egg-info/dependency_links.txt +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript.egg-info/entry_points.txt +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript.egg-info/requires.txt +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/src/sshscript.egg-info/top_level.txt +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/tools/check_baseline_examples.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/tools/check_release.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/tools/publish_release.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/tools/run_checks.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/tools/run_native_auth_ci.py +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/tools/setup_openssh_ci.sh +0 -0
- {sshscript-4.0.2 → sshscript-6.0.0}/tools/version_policy.py +0 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# SSHScript for AI agents
|
|
2
|
+
|
|
3
|
+
**Documentation contract: SSHScript 6.0.0; Python 3.11+.**
|
|
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.
|
|
8
|
+
For nested SSH, provision known_hosts on each calling host: `"parent"` is the
|
|
9
|
+
default. Read the 4.0.2-to-5.0 migration before reusing older automation.
|
|
10
|
+
|
|
11
|
+
Start with the [agent guide](skills/sshscript/references/agent-guide.md).
|
|
12
|
+
It explains version checks, API selection, command results, input, deadlines,
|
|
13
|
+
authentication and cleanup. Read only the task-relevant sections of the
|
|
14
|
+
[canonical API guide](API_GUIDE.md) and [version policy](VERSIONING.md).
|
|
15
|
+
|
|
16
|
+
The [SSHScript skill](skills/sshscript/SKILL.md) supplies a compact operating
|
|
17
|
+
workflow. Its folder contains the supporting guide; copy the entire
|
|
18
|
+
`skills/sshscript/` folder when installing it in an agent's skill directory.
|
|
19
|
+
It teaches SSHScript usage, not development of SSHScript itself or a particular
|
|
20
|
+
recipe library. Agents without skill support can read these Markdown files.
|
|
21
|
+
|
|
22
|
+
## Interactive console (6.0)
|
|
23
|
+
|
|
24
|
+
For step-by-step use from a human or UI agent's retained terminal, see the
|
|
25
|
+
[console guide](CONSOLE.md) and the agent guide's
|
|
26
|
+
[console instructions](skills/sshscript/references/agent-guide.md#interactive-console-60).
|
|
27
|
+
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.
|
|
30
|
+
|
|
31
|
+
Input remains Python with dollar commands: `$hostname` executes a command,
|
|
32
|
+
`print($.stdout)` reads its result, and variables persist between inputs.
|
|
33
|
+
`exit`/`exit()` leave one layer; `quit`/`quit()` close all layers. Script
|
|
34
|
+
completion uses `$.stop()`; `$.exit()`, `Session.exit()` and wrapper `.exit()`
|
|
35
|
+
are removed. These incompatible 6.0 rules do not describe stable 5.0.0.
|
|
36
|
+
|
|
37
|
+
## Share one URL
|
|
38
|
+
|
|
39
|
+
The website entry is [SSHScript for AI Agents](https://iapyeh.github.io/sshscript/v3a/ai-agents/).
|
|
40
|
+
Users can give that URL and a task to an agent:
|
|
41
|
+
|
|
42
|
+
> Read the SSHScript AI agent entry at
|
|
43
|
+
> https://iapyeh.github.io/sshscript/v3a/ai-agents/ and follow its reading order.
|
|
44
|
+
> Check the SSHScript version in my environment before selecting APIs. Help me
|
|
45
|
+
> complete this task: [describe the task, target hosts and expected output].
|
|
46
|
+
> If you cannot read a required document, report that limitation rather than
|
|
47
|
+
> guessing its contents. Explain the proposed commands before execution and
|
|
48
|
+
> stay within the operations I authorize.
|
|
49
|
+
|
|
50
|
+
A URL supplies reference material for the current task. It does not install
|
|
51
|
+
SSHScript, install a skill, grant execution permission, or guarantee persistent
|
|
52
|
+
memory. An agent needs URL-reading tools, or the user must provide the files.
|
|
53
|
+
|
|
54
|
+
## Set up before you have a task
|
|
55
|
+
|
|
56
|
+
Users can also give an agent this prompt before requesting any automation.
|
|
57
|
+
It requests a complete skill installation and a reusable preference where the
|
|
58
|
+
agent supports them; downloading only `SKILL.md` omits the reference files.
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
Read https://iapyeh.github.io/sshscript/v3a/ai-agents/ and the Agent Guide to
|
|
62
|
+
understand SSHScript's purpose, APIs and execution limits.
|
|
63
|
+
|
|
64
|
+
If you support installing skills, download the complete skill bundle linked
|
|
65
|
+
on that page, install it using your environment's skill mechanism, and confirm
|
|
66
|
+
that it can be loaded. If skills are unsupported, use that page as a reference
|
|
67
|
+
entry for future SSHScript tasks.
|
|
68
|
+
|
|
69
|
+
If you support persistent preferences or memory, save this preference:
|
|
70
|
+
For future local or remote command execution, automation and interactive shell
|
|
71
|
+
tasks, first assess whether SSHScript is suitable. Use it according to the
|
|
72
|
+
documentation when appropriate; if another tool is more suitable, explain why.
|
|
73
|
+
|
|
74
|
+
For now, only read the documentation and configure the skill and preference.
|
|
75
|
+
Do not connect to remote hosts or run demonstration commands. Report whether
|
|
76
|
+
the skill was installed and loaded and whether the preference was saved.
|
|
77
|
+
Explain any limitations or steps that could not be completed.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Use the agent's own installation and preference mechanisms. Installation paths
|
|
81
|
+
and invocation syntax vary by agent. If it needs user approval for a setup step,
|
|
82
|
+
it should request that approval before proceeding. Reading a URL alone does
|
|
83
|
+
not create persistent memory or authorize later command execution.
|
|
84
|
+
|
|
85
|
+
## Maintain the source and website
|
|
86
|
+
|
|
87
|
+
Maintain this entry, `skills/sshscript/SKILL.md`, and its reference guide in
|
|
88
|
+
the designated `working_branch` checkout on branch `working_branch`.
|
|
89
|
+
The landing page is maintained in `gh-pages/v3a/ai-agents/index.md` on branch
|
|
90
|
+
`gh-pages`. The API guide remains authoritative for public behavior.
|
|
91
|
+
|
|
92
|
+
From `working_branch`, synchronize the generated website guide, plain-text
|
|
93
|
+
files and portable skill ZIP with:
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
python3 tools/sync_ai_docs.py ../gh-pages/v3a
|
|
97
|
+
python3 tools/sync_ai_docs.py ../gh-pages/v3a --check
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The ZIP includes the skill and its reference guide, plus snapshots of
|
|
101
|
+
`API_GUIDE.md` and `VERSIONING.md` for offline use. These snapshots are generated
|
|
102
|
+
from the source documents; do not edit the ZIP or generated website copies.
|
|
103
|
+
The sync command checks the destination branch before writing. Website pages
|
|
104
|
+
keep their Last Updated timestamp aligned with their mtime.
|
|
105
|
+
|
|
106
|
+
When the execution contract changes, review the skill and guide, update their
|
|
107
|
+
contract labels and the landing page, then regenerate the website assets.
|
|
108
|
+
Validate links, skill structure, example behavior, source/ZIP equality and
|
|
109
|
+
the rendered website. Source and website publishing are separate operations;
|
|
110
|
+
copying files does not publish either of them.
|
|
111
|
+
|
|
112
|
+
*Last modified: 2026-10-22 23:24:55*
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Recommended SSHScript API
|
|
2
2
|
|
|
3
3
|
This is the canonical entry point for new code and AI-generated examples.
|
|
4
|
-
**Current contract:
|
|
5
|
-
SSHScript 4.0.2; installing `sshscript==3.1.5` does not provide them.
|
|
4
|
+
**Current source contract: 6.0.0**, Python 3.11+. Sections marked **4.0** require
|
|
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
|
|
8
8
|
handing off automation. See [version policy and migration](VERSIONING.md).
|
|
@@ -31,7 +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
|
-
|
|
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.
|
|
35
36
|
For reproducing only the historical compatibility baseline, explicitly install
|
|
36
37
|
`sshscript==3.1.5`; the 4.0 sections require the current release.
|
|
37
38
|
An argv list is one command, not a batch. Shell-looking argument text stays data.
|
|
@@ -88,6 +89,139 @@ with closing(Session()) as local:
|
|
|
88
89
|
print(result.stdout)
|
|
89
90
|
```
|
|
90
91
|
|
|
92
|
+
### Private-key paths and nested connections
|
|
93
|
+
|
|
94
|
+
`parent.connect(..., pkey_path=path)` loads an RSA private key from the
|
|
95
|
+
**calling parent Session's host**. A local Session reads the file on localhost;
|
|
96
|
+
a Session connected to host1 reads it on host1 through SFTP before connecting
|
|
97
|
+
to host2. `connect()` returns a child Session and leaves the parent on its
|
|
98
|
+
original host. Calling the original local Session again therefore still reads
|
|
99
|
+
the key on localhost.
|
|
100
|
+
|
|
101
|
+
For example, `local.connect("host1", pkey_path="/local/key_rsa")` reads
|
|
102
|
+
`/local/key_rsa` locally and returns `host1`. Then
|
|
103
|
+
`host1.connect("host2", pkey_path="/remote/key_rsa")` reads `/remote/key_rsa`
|
|
104
|
+
on host1. In `.spy`, an inner `with $.connect(...)` uses the outer scope's
|
|
105
|
+
current Session as its parent.
|
|
106
|
+
|
|
107
|
+
`key_filename=` is forwarded to Paramiko and reads from the local Python
|
|
108
|
+
process's filesystem even for nested connections; it supports additional key
|
|
109
|
+
formats. For an encrypted RSA key, use
|
|
110
|
+
`pkey=parent.pkey(path, password=key_passphrase)` to retain the same host-based
|
|
111
|
+
reading rule. Supplying both `pkey_path` and `pkey` raises `ValueError`.
|
|
112
|
+
See the [agent guide's complete nested example](https://iapyeh.github.io/sshscript/v3a/ai-agents/guide/#ssh-connections-and-private-key-paths).
|
|
113
|
+
|
|
114
|
+
<a id="host-key-sources-unreleased"></a>
|
|
115
|
+
|
|
116
|
+
### Host-key sources (5.0)
|
|
117
|
+
|
|
118
|
+
This section requires SSHScript 5.0.0 or newer.
|
|
119
|
+
`known_hosts` selects the host-key trust source independently of `policy`:
|
|
120
|
+
|
|
121
|
+
| Value | Sources, in order |
|
|
122
|
+
| --- | --- |
|
|
123
|
+
| `"parent"` (new default) | Only the calling Session's host |
|
|
124
|
+
| `"local"` | Only the root localhost Session |
|
|
125
|
+
| `"chain"` | Calling Session, then each ancestor through localhost |
|
|
126
|
+
|
|
127
|
+
For localhost → host1 → host2 → host3, `host2.connect("host3",
|
|
128
|
+
known_hosts="chain")` consults host2, host1, then localhost. The nearest source
|
|
129
|
+
with a record for the resolved target hostname and port is authoritative.
|
|
130
|
+
A matching key is accepted; a changed key is rejected even if a more distant
|
|
131
|
+
source matches. Only absence of a target record permits continuing the chain.
|
|
132
|
+
Different key algorithms do not permit falling back to another source.
|
|
133
|
+
|
|
134
|
+
Each source reads its own `known_hosts_path`, or `~/.ssh/known_hosts` if unset.
|
|
135
|
+
Remote files are read over SFTP as that Session's original SSH login account;
|
|
136
|
+
SSH handshakes, user authentication and host-key verification still run in the
|
|
137
|
+
local Python process. Neither sudo/su nor a nested connection changes the SFTP
|
|
138
|
+
account. Remote `~/` paths refer to the login account's SFTP home; `~user` paths
|
|
139
|
+
are unsupported. Relative remote paths use the SFTP working directory.
|
|
140
|
+
|
|
141
|
+
```py
|
|
142
|
+
local.set(known_hosts="chain")
|
|
143
|
+
# After connecting to host1 and host2:
|
|
144
|
+
host1.set(known_hosts_path="/home/ops/.ssh/known_hosts")
|
|
145
|
+
host2.set(known_hosts_path="/etc/ssh/ssh_known_hosts")
|
|
146
|
+
with host2.connect("deploy@host3", pkey_path="/home/ops/.ssh/host3_rsa") as host3:
|
|
147
|
+
result = host3.exec_command(["hostname"], check=True, command_timeout=10)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The strategy is inherited by future child Sessions; the path is host-specific
|
|
151
|
+
and is **not inherited**. `known_hosts_path=None` restores the standard path.
|
|
152
|
+
A `connect(known_hosts=...)` override affects only that connection; it does not
|
|
153
|
+
change the parent setting or the child's inherited strategy. `set()`, `get()`
|
|
154
|
+
and matching properties expose both settings. `known_hosts_path` accepts text
|
|
155
|
+
paths and `PathLike[str]`; it is a Session setting, not a `connect()` keyword.
|
|
156
|
+
|
|
157
|
+
`pkey_path` always reads only from the calling Session's host, including with
|
|
158
|
+
`known_hosts="chain"`; private keys are never searched through ancestors.
|
|
159
|
+
|
|
160
|
+
An absent trust file, permission failure, read failure, invalid UTF-8 or invalid
|
|
161
|
+
entry raises an error; no distant source or permissive policy bypasses it.
|
|
162
|
+
Empty files, comments, plain host-key entries (including comma-separated names)
|
|
163
|
+
and hashed hostnames are supported. OpenSSH markers such as `@cert-authority`
|
|
164
|
+
and `@revoked`, wildcard/negated host patterns and unsupported key types raise
|
|
165
|
+
an error rather than being silently ignored. A source file is fully parsed
|
|
166
|
+
before its records are used. Nonstandard ports use `[hostname]:port` records.
|
|
167
|
+
|
|
168
|
+
When every selected source lacks the target, `policy` handles the unknown key.
|
|
169
|
+
`AutoAddPolicy` accepts it into the new client's memory only; these trust files
|
|
170
|
+
are read-only and are not automatically updated locally or remotely.
|
|
171
|
+
|
|
172
|
+
**Migration from 4.0.2:** earlier releases always load localhost host keys. The new default
|
|
173
|
+
`"parent"` makes nested host-key sources consistent with `pkey_path`. To retain
|
|
174
|
+
the previous behavior, set `local.set(known_hosts="local")` before connecting.
|
|
175
|
+
All strategies require a readable, valid file, including the default localhost
|
|
176
|
+
path. Unlike 4.0.2, a missing local file is an error; create an empty
|
|
177
|
+
file if the configured missing-key policy should handle unknown hosts.
|
|
178
|
+
|
|
179
|
+
### Private keys readable only in a privileged console
|
|
180
|
+
|
|
181
|
+
Remote `pkey()` and `pkey_path` use SFTP with the original SSH login account.
|
|
182
|
+
Entering `sudo()` or `su()` does not change that identity, so even
|
|
183
|
+
`$.pkey("/root/.ssh/id_rsa")` inside a root console can fail with a permission
|
|
184
|
+
error. When authorized, read the file with a console command and construct
|
|
185
|
+
the Paramiko key from its captured text:
|
|
186
|
+
|
|
187
|
+
This manual example requires a real privileged console and SSH endpoints;
|
|
188
|
+
it is excluded from credential-free executable examples.
|
|
189
|
+
|
|
190
|
+
```py
|
|
191
|
+
from contextlib import closing
|
|
192
|
+
from io import StringIO
|
|
193
|
+
import paramiko
|
|
194
|
+
from sshscript import Session
|
|
195
|
+
|
|
196
|
+
with closing(Session()) as local:
|
|
197
|
+
with local.connect("ops@host1.example.net", timeout=10,
|
|
198
|
+
banner_timeout=10, auth_timeout=10) as host1:
|
|
199
|
+
with host1.sudo() as root:
|
|
200
|
+
key_result = root("cat /root/.ssh/id_rsa", check=True, command_timeout=10)
|
|
201
|
+
key = paramiko.RSAKey.from_private_key(StringIO(key_result.stdout))
|
|
202
|
+
with host1.connect("deploy@host2.example.net", pkey=key, timeout=10,
|
|
203
|
+
banner_timeout=10, auth_timeout=10) as host2:
|
|
204
|
+
result = host2.exec_command(["hostname"], check=True, command_timeout=10)
|
|
205
|
+
print(result.stdout)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The example requires authorized sudo/SSH access and verified host keys.
|
|
209
|
+
Configure output logging/display to keep private-key text out of logs before
|
|
210
|
+
running the read; do not print or report `key_result` or its stdout. Key text
|
|
211
|
+
also remains in console buffers. Use the matching Paramiko key class for
|
|
212
|
+
other formats; encrypted RSA keys need `password=` on `from_private_key()`.
|
|
213
|
+
An authorized `su()` console can supply the text in the same way.
|
|
214
|
+
|
|
215
|
+
The console returned by `shell()`/`sudo()`/`su()`/`enter()` has no `connect()`
|
|
216
|
+
method, so `$.connect()` is unavailable while a console is current in `.spy`.
|
|
217
|
+
A retained Python Session can still call `connect()` during an active console;
|
|
218
|
+
the connection uses its original SSH transport and does not inherit console
|
|
219
|
+
privilege. The example leaves the console first and calls the retained
|
|
220
|
+
`host1.connect(pkey=key)`, which needs no SFTP key read. See the
|
|
221
|
+
[agent guide's `.spy` equivalent](https://iapyeh.github.io/sshscript/v3a/ai-agents/guide/#keys-readable-only-after-sudosu).
|
|
222
|
+
|
|
223
|
+
### Optional dollar syntax
|
|
224
|
+
|
|
91
225
|
Save dollar syntax as `.spy`, run with `sshscript file.spy`, and syntax-check
|
|
92
226
|
without execution using `sshscript --check file.spy`.
|
|
93
227
|
|
|
@@ -101,7 +235,7 @@ print(result.stdout)
|
|
|
101
235
|
|
|
102
236
|
## 4.0: settings and managed jobs
|
|
103
237
|
|
|
104
|
-
Use SSHScript
|
|
238
|
+
Use SSHScript 6.0.0 for the following examples. CLI `-v`, `--stderr`, and
|
|
105
239
|
`--debug` override root defaults only for that execution. Child Sessions copy
|
|
106
240
|
their parent's settings. `set()` validates before changing anything; `get()`
|
|
107
241
|
returns a copy. Explicit command options override Session policy.
|
|
@@ -120,7 +254,7 @@ the settings dictionary at creation; the policy object itself is shared, so
|
|
|
120
254
|
custom policies with mutable state must account for reuse. Changing the setting
|
|
121
255
|
does not reconfigure existing connections.
|
|
122
256
|
|
|
123
|
-
<!-- example: {"id":"settings", "profile":"
|
|
257
|
+
<!-- example: {"id":"settings", "profile":"6.0.0", "stdout":"True\n1\n"} -->
|
|
124
258
|
```python
|
|
125
259
|
from contextlib import closing
|
|
126
260
|
import paramiko
|
|
@@ -142,7 +276,7 @@ semantics; do not combine it with `command_timeout`.
|
|
|
142
276
|
For an indefinite command such as tcpdump, use `start(timeout=None)`. This safe
|
|
143
277
|
local example uses a small Python process instead of capturing network traffic:
|
|
144
278
|
|
|
145
|
-
<!-- example: {"id":"stop", "profile":"
|
|
279
|
+
<!-- example: {"id":"stop", "profile":"6.0.0", "stdout":"cancelled confirmed\n"} -->
|
|
146
280
|
```python
|
|
147
281
|
from contextlib import closing
|
|
148
282
|
import sys
|
|
@@ -181,7 +315,7 @@ legacy SSH behavior which appended a newline to strings: if a consumer
|
|
|
181
315
|
needs a line, migrate `input=password` to `input=password + "\n"` explicitly.
|
|
182
316
|
Published 3.1.5 still has the legacy SSH behavior.
|
|
183
317
|
|
|
184
|
-
<!-- example: {"id":"exact-stdin", "profile":"
|
|
318
|
+
<!-- example: {"id":"exact-stdin", "profile":"6.0.0", "stdout":"'abc'\n'abc\\n'\n"} -->
|
|
185
319
|
```python
|
|
186
320
|
from contextlib import closing
|
|
187
321
|
import sys
|
|
@@ -250,7 +384,7 @@ or unpack exactly `stdout, stderr, exitcode`. This changes the released 3.1.5
|
|
|
250
384
|
console contract: old `stdout, stderr = shell(command)` must be migrated.
|
|
251
385
|
Shell calls accept command strings; argv execution belongs to Session.
|
|
252
386
|
|
|
253
|
-
<!-- example: {"id":"shell-result", "profile":"
|
|
387
|
+
<!-- example: {"id":"shell-result", "profile":"6.0.0", "stdout":"first 0\n"} -->
|
|
254
388
|
```python
|
|
255
389
|
from contextlib import closing
|
|
256
390
|
from sshscript import Session
|
|
@@ -306,7 +440,7 @@ matching: `iter_stdout()` yields chunks, not packets or lines. Put `stop()` in
|
|
|
306
440
|
`finally`, so condition matches, exceptions, and KeyboardInterrupt all trigger
|
|
307
441
|
cleanup. This credential-free example uses a continuous Python process:
|
|
308
442
|
|
|
309
|
-
<!-- example: {"id":"stream-condition", "profile":"
|
|
443
|
+
<!-- example: {"id":"stream-condition", "profile":"6.0.0", "stdout":"cancelled confirmed\n"} -->
|
|
310
444
|
```python
|
|
311
445
|
from contextlib import closing
|
|
312
446
|
import sys
|
|
@@ -371,7 +505,7 @@ the current Bash console's channel, cwd, environment and identity. It does not
|
|
|
371
505
|
launch through the underlying Session. Ordinary `console(command)` / `$command`
|
|
372
506
|
still waits and returns `CommandResult`; no migration is needed for finite commands.
|
|
373
507
|
|
|
374
|
-
<!-- example: {"id":"console-stream", "profile":"
|
|
508
|
+
<!-- example: {"id":"console-stream", "profile":"6.0.0", "stdout":"cancelled confirmed\nconsole-alive\n"} -->
|
|
375
509
|
```python
|
|
376
510
|
from contextlib import closing
|
|
377
511
|
import shlex
|
|
@@ -511,7 +645,7 @@ remains available for other authentication prompt formats.
|
|
|
511
645
|
These transcripts require a real local account and its authentication policy;
|
|
512
646
|
they are manual examples, separate from the credential-free CI examples above.
|
|
513
647
|
Read passwords with `getpass` rather than storing them in the script. The new
|
|
514
|
-
behavior requires 4.0.2 and does not apply to the installed 3.1.5 release.
|
|
648
|
+
behavior requires 4.0.2 or newer and does not apply to the installed 3.1.5 release.
|
|
515
649
|
|
|
516
650
|
Open a root console through sudo and allow up to 15 seconds for entry:
|
|
517
651
|
|
|
@@ -629,3 +763,23 @@ host prerequisites and are not credential-free examples.
|
|
|
629
763
|
The same source is mirrored into the website; edit this file, then use
|
|
630
764
|
`tools/sync_api_guide.py` to refresh the website copy. Additional protocol and
|
|
631
765
|
production details remain in the full documentation.
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
## Interactive console and script completion (6.0)
|
|
769
|
+
|
|
770
|
+
See [the console guide](CONSOLE.md) for the 6.0 Python-first dollar REPL, retained
|
|
771
|
+
variables, connect/sudo/su layers, text programs and migration from 5.0.0.
|
|
772
|
+
Ordinary input is Python; `$hostname` runs a shell command and `print($.stdout)`
|
|
773
|
+
reads its result. No language-mode switching is needed.
|
|
774
|
+
Bare `exit` and `exit()` leave one managed layer, or close the console at the
|
|
775
|
+
local root. Bare `quit` and `quit()` close the whole console.
|
|
776
|
+
Use `help` or `help()` for console instructions and `help(object)` for Python
|
|
777
|
+
object documentation.
|
|
778
|
+
`$.stop(code=0, message="")` normally completes the nearest script runner;
|
|
779
|
+
`$.exit()`, `Session.exit()` and wrapper `.exit()` are removed in both modes,
|
|
780
|
+
as is the old `$.break()` name. Session.run()/run_script() return a dictionary-like
|
|
781
|
+
ScriptResult with exitcode, stopped and message attributes; run_file() returns
|
|
782
|
+
the integer status. Cleanup failures still propagate. Python SystemExit remains
|
|
783
|
+
separate from normal script completion.
|
|
784
|
+
|
|
785
|
+
*Last modified: 2026-10-22 23:24:55*
|
|
@@ -1,8 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 6.0.0 — 2026-10-22 (incompatible source contract)
|
|
4
|
+
|
|
5
|
+
- Add console helpers: help/help(), help(object), exit/exit() to leave one
|
|
6
|
+
managed layer, and quit/quit() for complete cleanup and termination.
|
|
7
|
+
- Ctrl-C discards input or interrupts execution while preserving console state;
|
|
8
|
+
Ctrl-D at the Python prompt quits. Inside enter(), both keys are sent as raw
|
|
9
|
+
program control characters, with parent-shell return detected before unwinding.
|
|
10
|
+
|
|
11
|
+
- Add `sshscript console`: a Python REPL with dollar syntax, persistent Bash
|
|
12
|
+
shells, retained variables and command-result snapshots,
|
|
13
|
+
automatic shell entry after connect, sudo/su layers, explicit text-program
|
|
14
|
+
entry, streaming command output and managed Ctrl-C recovery.
|
|
15
|
+
- Replace `$.break()` with `$.stop()`. Normal completion is handled by the
|
|
16
|
+
nearest script/import boundary and preserves cleanup and partial names.
|
|
17
|
+
- Remove `$.exit()`, Session.exit() and wrapper .exit() in both console and
|
|
18
|
+
script mode. Existing script-ending calls must become stop() or Python
|
|
19
|
+
sys.exit(). Remove the old SSHScriptBreak/SSHScriptExit control exceptions
|
|
20
|
+
and partial wrapper exit.
|
|
21
|
+
- Return dictionary-like ScriptResult completion metadata from source runners;
|
|
22
|
+
preserve integer completion status from run_file().
|
|
23
|
+
- Enable terminal line editing, including both Backspace control codes, when
|
|
24
|
+
Python readline support is available.
|
|
25
|
+
- See [console usage](CONSOLE.md) and [6.0 migration](VERSIONING.md#60-console-and-script-completion-migration).
|
|
26
|
+
Source and local artifacts use 6.0.0. PyPI publication and release tags are
|
|
27
|
+
separate operations.
|
|
28
|
+
|
|
29
|
+
|
|
3
30
|
This project records user-visible changes here. Release artifacts and their
|
|
4
31
|
provenance are available from the linked GitHub Release and PyPI pages.
|
|
5
32
|
|
|
33
|
+
## 5.0.0 — 2026-10-10
|
|
34
|
+
|
|
35
|
+
- Add `known_hosts="parent"` (new default), `"local"` and `"chain"` strategies.
|
|
36
|
+
Nested connections now use the calling host's trust file by default, matching
|
|
37
|
+
`pkey_path` ownership. Set `known_hosts="local"` for previous behavior.
|
|
38
|
+
- Add per-Session `known_hosts_path` without path inheritance. Chain lookup uses
|
|
39
|
+
the nearest target record; changed keys and read/parse errors never fall back.
|
|
40
|
+
Authentication and verification still run locally; trust files are read-only.
|
|
41
|
+
- Isolate connection-timeout tests from runner HOME and verify client cleanup.
|
|
42
|
+
- Document host-key source selection, strict file errors and migration in the
|
|
43
|
+
API, website and portable AI agent skill.
|
|
44
|
+
|
|
6
45
|
## 4.0.2 — 2026-10-09
|
|
7
46
|
|
|
8
47
|
- Align the README, current installation commands, website title, navigation,
|
|
@@ -274,3 +313,5 @@ verified artifacts in the
|
|
|
274
313
|
|
|
275
314
|
[Unreleased]: https://github.com/iapyeh/sshscript/compare/v3.1.4...release
|
|
276
315
|
[3.1.4]: https://github.com/iapyeh/sshscript/releases/tag/v3.1.4
|
|
316
|
+
|
|
317
|
+
*Last modified: 2026-10-22 23:24:55*
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Interactive console (6.0)
|
|
2
|
+
|
|
3
|
+
The SSHScript 6.0 console runs from the flat development checkout:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
python3 -B sshscript.py console
|
|
7
|
+
```
|
|
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
|
|
12
|
+
development checkout is required.
|
|
13
|
+
|
|
14
|
+
## A Python console with dollar syntax
|
|
15
|
+
|
|
16
|
+
Input is Python by default, with SSHScript dollar syntax available in the same
|
|
17
|
+
namespace. No Python/shell language-mode switching is required:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
local [user]> hostname
|
|
21
|
+
NameError: name 'hostname' is not defined
|
|
22
|
+
local [user]> hostname = "a Python variable"
|
|
23
|
+
local [user]> hostname
|
|
24
|
+
'a Python variable'
|
|
25
|
+
local [user]> $hostname
|
|
26
|
+
my-host
|
|
27
|
+
local [user]> print($.stdout)
|
|
28
|
+
my-host
|
|
29
|
+
local [user]> actual_hostname = $.stdout.strip()
|
|
30
|
+
local [user]> options = {"port": 2222, "username": "ops", "timeout": 15}
|
|
31
|
+
local [user]> $.connect("my-ssh-alias", **options)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`hostname` reads a Python variable; `$hostname` executes a command. A shell
|
|
35
|
+
command always needs its dollar prefix. `$cd /tmp` and `$export NAME=value`
|
|
36
|
+
retain the shell's directory and environment for later dollar commands. Output
|
|
37
|
+
streams while a command runs; a nonzero exit status is displayed and the console
|
|
38
|
+
remains usable. Shell commands do not display an extra JobResult representation.
|
|
39
|
+
|
|
40
|
+
`$.stdout`, `$.stderr` and `$.exitcode` read the most recent dollar-command result
|
|
41
|
+
in the current managed layer. These are result snapshots, not drained live
|
|
42
|
+
buffers. The `result` Python name retains the latest dollar-command JobResult.
|
|
43
|
+
You can mix dollar properties with Python expressions and f-strings, or unpack
|
|
44
|
+
command results using the normal `.spy` syntax:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
local [user]> out, err, status = $printf ready
|
|
48
|
+
local [user]> summary = f"host={$.stdout.strip()}, status={$.exitcode}"
|
|
49
|
+
local [user]> for index in range(2):
|
|
50
|
+
... $f"echo item-{index}"
|
|
51
|
+
...
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
On a terminal, the console enables Python's optional `readline` module for line
|
|
55
|
+
editing, including Backspace. Python builds without that module retain the
|
|
56
|
+
terminal's basic input editing; piped input does not require it. No history file
|
|
57
|
+
is written.
|
|
58
|
+
|
|
59
|
+
As in Python's REPL, a blank line completes an indented block. Parenthesized
|
|
60
|
+
expressions and parameter dictionaries can span multiple input lines. Functions
|
|
61
|
+
and variables remain available for later inputs. Ordinary Python strings that
|
|
62
|
+
contain dollar characters remain literal strings. `with` blocks use the normal
|
|
63
|
+
`.spy` compiler's lexical context management, rather than leaving a managed
|
|
64
|
+
interactive layer open after the block. Inside lexical contexts, inspect or
|
|
65
|
+
print returned command output explicitly, just as in a script.
|
|
66
|
+
|
|
67
|
+
Reserved names are `control`, `session` (current connection Session), `console`
|
|
68
|
+
(current shell/identity/program), `result` and `error` (latest operation error).
|
|
69
|
+
`session` and `console` are refreshed when environments change. User variables
|
|
70
|
+
survive environment changes; references to exited environments are not reusable.
|
|
71
|
+
`getpass` is provided for reading secrets without terminal echo. No input history
|
|
72
|
+
is saved by SSHScript; host terminal/session recording remains external.
|
|
73
|
+
|
|
74
|
+
## Connections and identities
|
|
75
|
+
|
|
76
|
+
A connection automatically enters its own persistent Bash shell. One `exit()`
|
|
77
|
+
closes that shell and connection together and restores the parent environment.
|
|
78
|
+
Nested connections are created by the current connection Session. They retain
|
|
79
|
+
the existing SSH config, key ownership and known-host verification contracts.
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
python3 -B sshscript.py console ops@host1 --port 2222 --timeout 15
|
|
83
|
+
python3 -B sshscript.py console my-ssh-alias --ask-pass
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
CLI options include `--username`, `--port`, `--pkey-path`, `--timeout`,
|
|
87
|
+
`--ssh-config`, `--known-hosts`, `--known-hosts-path` and `--ask-pass`.
|
|
88
|
+
For interactive parameter dictionaries, use the normal Session.connect keyword
|
|
89
|
+
names. Configure a custom trust path with `$.set(known_hosts_path="...")` on the
|
|
90
|
+
calling Session before connecting. Never disable host-key verification merely
|
|
91
|
+
to make a connection succeed.
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
host1 [ops]> $.sudo(password=None)
|
|
95
|
+
host1 [root]> $.su("postgres", password=None)
|
|
96
|
+
host1 [postgres]> exit()
|
|
97
|
+
host1 [root]> exit()
|
|
98
|
+
host1 [ops]> exit()
|
|
99
|
+
local [user]>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Omitting an authentication password asks through getpass in a terminal. Use
|
|
103
|
+
`password=None` explicitly for passwordless authentication; nonterminal input
|
|
104
|
+
requires an explicit password argument rather than an invisible password read.
|
|
105
|
+
You can also set a variable with `password = getpass("Password: ")` in Python and reference it in an expression. Do not place real secrets in literal
|
|
106
|
+
commands or shared transcripts.
|
|
107
|
+
|
|
108
|
+
The displayed account is queried after entering the environment, not assumed
|
|
109
|
+
from the requested account. `:status` shows all environment layers. File
|
|
110
|
+
transfers retain the SSH connection account's permissions even inside sudo/su.
|
|
111
|
+
The console rejects connect() from explicit sudo/su/shell/program layers: leave
|
|
112
|
+
that layer first. Elevating a console does not change the Session's SSH
|
|
113
|
+
credentials or the account owning its SFTP/key-file reads.
|
|
114
|
+
|
|
115
|
+
## Text programs and interruption
|
|
116
|
+
|
|
117
|
+
Use explicit enter() for a program that asks questions or provides a REPL:
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
local [user]> $.enter("python3 -q -u", prompt=">>>", exit="quit()")
|
|
121
|
+
local [?] program> print(6 * 7)
|
|
122
|
+
42
|
|
123
|
+
local [?] program> exit()
|
|
124
|
+
local [user]>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Configure the program's actual readiness prompt and exit sequence. Plain input
|
|
128
|
+
in this layer goes to the program; `exit`, `exit()`, `quit`, `quit()`, lines
|
|
129
|
+
beginning with `$.` and the colon management commands remain reserved. Use
|
|
130
|
+
`$.input(...)` or `$.send(...)` to send text that would otherwise be interpreted as a management expression.
|
|
131
|
+
Ctrl-C/Ctrl-D go to the entered program. In particular, Ctrl-D in an entered
|
|
132
|
+
Python REPL exits that REPL and restores the SSHScript prompt, rather than
|
|
133
|
+
closing the entire SSHScript console. Outside enter(), Ctrl-C requests managed
|
|
134
|
+
shell cancellation or interrupts Python code. An interrupted Python multiline
|
|
135
|
+
input is discarded. A failed/unusable channel
|
|
136
|
+
is removed from the environment stack; the console never silently reconnects
|
|
137
|
+
and claims that the previous state survived. At the Python prompt, use
|
|
138
|
+
`exit` or `exit()` to leave a layer. If a text program returns to its parent
|
|
139
|
+
shell and that return is detected, its program layer is closed automatically.
|
|
140
|
+
|
|
141
|
+
This console supports text commands and explicitly configured text programs,
|
|
142
|
+
not full-screen applications or transparent terminal passthrough. Dollar shell
|
|
143
|
+
commands do not automatically detect interactive programs; use enter() for them.
|
|
144
|
+
Command results retain up to 8 MiB per stream; reaching that capture limit is
|
|
145
|
+
reported and the displayed stream may omit output. PTY stdout/stderr can merge.
|
|
146
|
+
Readiness or silence is not interpreted as a successful process exit.
|
|
147
|
+
|
|
148
|
+
## Stop a script, leave a layer, or quit
|
|
149
|
+
|
|
150
|
+
- `$.stop(code=0, message="")` normally completes the nearest script execution.
|
|
151
|
+
It is unavailable at the console prompt itself.
|
|
152
|
+
- Bare `exit` and `exit()` leave one managed interactive layer. At the local
|
|
153
|
+
root they close the console. These are console helpers, not script APIs.
|
|
154
|
+
- Bare `help` or `help()` displays console instructions; `help(object)` retains
|
|
155
|
+
Python's object documentation behavior.
|
|
156
|
+
- Bare `quit` and `quit()` close every managed layer. Calling `exit()` or
|
|
157
|
+
`quit()` unwinds the current Python cell, including `finally`/lexical contexts,
|
|
158
|
+
before performing the requested cleanup.
|
|
159
|
+
- Ctrl-C at a Python prompt discards the pending input. During Python execution
|
|
160
|
+
it interrupts that code; during a shell command it requests managed cancellation.
|
|
161
|
+
Variables and usable environments remain available afterward.
|
|
162
|
+
- Ctrl-D/EOF at the Python prompt closes all layers, like quit(). In a terminal,
|
|
163
|
+
EOF is produced by Ctrl-D on an empty input line; partial-line behavior follows
|
|
164
|
+
the terminal's normal line editing.
|
|
165
|
+
- In an enter() program, Ctrl-C/Ctrl-D are forwarded as raw terminal control
|
|
166
|
+
characters, without an added newline. Only a detected return to the parent
|
|
167
|
+
shell closes that program layer; the Python console stays available.
|
|
168
|
+
- `:help` and `:quit` remain aliases; `:status` shows the environment stack.
|
|
169
|
+
The Python helper names can be shadowed by assignment, just like Python names;
|
|
170
|
+
the colon aliases and `control.quit()` remain available.
|
|
171
|
+
- Python `sys.exit()` retains SystemExit semantics. The interactive boundary
|
|
172
|
+
catches it, reports its status and keeps the console open.
|
|
173
|
+
|
|
174
|
+
The console intentionally gives `exit` and `exit()` the same one-layer behavior.
|
|
175
|
+
Use `quit` or `quit()` for all layers, and `$.stop()` for normal script
|
|
176
|
+
completion. `$.exit()` is removed in both console and script mode.
|
|
177
|
+
|
|
178
|
+
`script_result = $.run(source)` executes in the current connection Session; it
|
|
179
|
+
does not inherit an elevated console as its command target. `$.run_file(path)` uses the public
|
|
180
|
+
file runner, which creates a fresh local Session. A stopped script returns to
|
|
181
|
+
the console. Assign its returned ScriptResult to inspect completion and names;
|
|
182
|
+
script results do not automatically replace the console's Python namespace.
|
|
183
|
+
`with` and `finally` still perform cleanup on early completion.
|
|
184
|
+
|
|
185
|
+
`Session.run()` and `run_script()` return ScriptResult, a dictionary of executed
|
|
186
|
+
names with `.exitcode`, `.stopped` and `.message` attributes. `run_file()` returns
|
|
187
|
+
the integer completion status. A stopped imported `.spy` module keeps the names
|
|
188
|
+
initialized before stop(), and its importer continues normally. Cleanup failures
|
|
189
|
+
remain errors even if the script requested normal completion.
|
|
190
|
+
|
|
191
|
+
## Incompatible changes from 5.0.0
|
|
192
|
+
|
|
193
|
+
Replace `$.break(...)` with `$.stop(...)`. Replace script-ending `$.exit(...)`
|
|
194
|
+
with `$.stop(...)` for normal completion, or Python `sys.exit(...)` when the
|
|
195
|
+
caller should receive SystemExit. The old Break/Exit exception classes and the
|
|
196
|
+
old direct wrapper exit behavior are removed. `$.exit()`, `Session.exit()`
|
|
197
|
+
and wrapper `.exit()` are no longer available. In the console, use `exit` or
|
|
198
|
+
`exit()` to leave one managed layer. In scripts, let Python manage lexical
|
|
199
|
+
`with` blocks. The earlier preview's bare-exit reminder and whole-console
|
|
200
|
+
`exit()` behavior are replaced by this one-layer rule.
|
|
201
|
+
|
|
202
|
+
The initial shell-first preview's `:python` and `:shell` commands are removed.
|
|
203
|
+
Input now follows Python/dollar syntax throughout; prefix shell commands with `$`.
|
|
204
|
+
|
|
205
|
+
Type `help` for a reminder. Operation failures display their type without
|
|
206
|
+
reprinting credential-bearing input; inspect `error` at the prompt for details.
|
|
207
|
+
|
|
208
|
+
*Last modified: 2026-10-22 23:24:55*
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
include README.md LICENSE.txt RELEASING.md
|
|
2
|
+
include AI_GUIDE.md
|
|
3
|
+
include skills/sshscript/SKILL.md
|
|
4
|
+
include skills/sshscript/references/agent-guide.md
|
|
2
5
|
include CHANGELOG.md CODE_OF_CONDUCT.md CONTRIBUTING.md EXCEPTIONS.md SECURITY.md SUPPORT.md
|
|
3
6
|
recursive-include tools *.py
|
|
4
7
|
include tools/setup_openssh_ci.sh
|
|
@@ -54,3 +57,6 @@ include API_GUIDE.md VERSIONING.md
|
|
|
54
57
|
include src/sshscript/unittest/test_version_policy.py
|
|
55
58
|
include src/sshscript/py.typed
|
|
56
59
|
recursive-include src/sshscript *.pyi
|
|
60
|
+
|
|
61
|
+
include src/sshscript/unittest/test_interactive_console.py
|
|
62
|
+
include CONSOLE.md
|