Metadata-Version: 2.4
Name: aether-conpty
Version: 0.2.0
Summary: A Windows Python console runtime with an independent terminal GUI
License-Expression: MIT
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Dynamic: license-file

# Aether

Aether 是 Windows Python 程序的控制台替代运行时：真实程序在 ConPTY 中运行，独立 Tk GUI 承载输入和屏幕，同时提供可嵌入的 TerminalSession API 和诊断 CLI。原生层直接使用标准库 ctypes，不依赖 Windows Terminal、pywinpty 或 pywin32。

当前版本为 **0.2.0，结构化渲染功能已实现**。

完整的包级 Python API、参数语义、错误类型、模板表达式、CLI 和兼容边界见源码发行包中的 `API.md`。[发行文件](https://pypi.org/project/aether-conpty/0.2.0/#files)中的 `.tar.gz` 源码包包含该文档、MIT 许可证、`examples/` 示例和测试；下载并解压后即可离线阅读。

## 环境与安装

目标为 Windows 10 1809+ x64、Python 3.10+。本机 Windows 10 19045 下，Python 3.10.21/3.11.15/3.12.12/3.13.9 的单元与集成分组已全部通过。GUI 需要当前 Python 安装中可用的 Tkinter；运行时不安装第三方包。pytest 为开发依赖，IPython/prompt_toolkit 仅用于可选兼容性验证。

在目标 Python 环境中安装：

```powershell
$py = (Get-Command python.exe).Source
& $py -m pip install aether-conpty==0.2.0
& $py -m aether --help
& $py -m aether probe --json
```

发行名是 `aether-conpty`，导入名和 CLI 名均为 `aether`。若当前 Python 的 Scripts 目录不在 PATH，使用 `& $py -m aether ...`。MIT 许可证全文随发行包提供，许可证条款也可参阅 [SPDX MIT](https://spdx.org/licenses/MIT.html)。示例路径均相对于解压后的源码包目录。

在源码包目录中进行开发安装时，使用 `& $py -m pip install -e '.[dev]'`。

## 脚本顶部自动激活

将 import 放在脚本顶部：

```python
import aether

name = input('你的名字：')
print(f'你好，{name}！')
```

正常运行此 `.py` / `.pyw` 文件会打开独立 GUI。GUI 宿主脱离原 console，在 ConPTY 中用同一安装的 Python 重启脚本一次，保留参数、cwd 和环境；内部标记避免重复启动。目标退出后窗口保留输出供查看和复制，用户关闭窗口后宿主返回缓存的目标退出码。

**导入前的代码可能执行两次，内存状态不会迁移。** 首版自动激活只承诺普通脚本，`-m`、`-c`、stdin、交互 REPL 和 frozen 入口不在自动激活验收范围。对于这些入口使用显式 CLI 或库 API。

从进程启动第一刻都不创建 console，请通过同安装目录的 pythonw.exe 或 `.pyw` 入口启动。import 无法阻止操作系统在执行 import 前创建窗口。Aether 不关闭调用者已有的共享 PowerShell/cmd 窗口。pythonw 宿主的真实目标使用同目录 python.exe，缺少该文件时报错，不切换其他环境。

源码包中的自动 GUI 示例 `examples/run_script.py`：

```powershell
Remove-Item Env:AETHER_MODE -ErrorAction SilentlyContinue
& $py examples\run_script.py
# 从第一刻不创建 console：
& (Join-Path (Split-Path $py) 'pythonw.exe') examples\run_script.py
```

## GUI 输入与快捷键

| 操作 | 快捷键/入口 |
|---|---|
| 复制选区 | Ctrl+C，或“会话 → 复制”；无选区时不发送中断 |
| 粘贴 | Ctrl+V，或“会话 → 粘贴”；尊重目标括号粘贴模式 |
| 中断目标 | Ctrl+Break，或“会话 → 中断程序” |
| 关闭窗口与会话 | Ctrl+Shift+Q、Alt+F4、关闭按钮或“会话 → 关闭” |
| 选择/历史 | 鼠标拖动选择、滚轮查看历史 |

其他按键交给真实目标处理。Tab 不被 Aether 用来生成补全；编辑、历史和补全由 Python/IPython/prompt_toolkit/shell 自己处理。中断 API 发送 0x03，新 Python input/sleep 流程已验证 KeyboardInterrupt；任意目标可以自行处理或忽略信号。关闭提供超时强制终止兜底。

## 显式 CLI

裸命令、--help、--version 只报告信息，probe 只探测环境，不启动会话。run/shell/repl 默认使用独立 GUI：

```powershell
& $py -m aether run examples\run_script.py
& $py -m aether run -- $py -u my_script.py '带空格的参数'
& $py -m aether shell --shell cmd.exe
& $py -m aether repl
& $py -m aether repl --python $py
```

默认 Python 来自运行 Aether 的当前安装。shell 按显式 --shell → AETHER_SHELL → pwsh.exe → powershell.exe → COMSPEC → cmd.exe 解析。run -- 后的首个参数必须是可执行文件；`.bat` 等通过显式 shell 运行。

自动化管道必须显式传 `--pipe`，并放在脚本/目标参数之前，不根据 stdin 是否 TTY 自动选择：

```powershell
& $py -m aether run --pipe -- cmd.exe /d /c 'echo AETHER_OK & exit 7'
& $py -m aether run --pipe my_script.py
```

该模式转发 stdin 与 ConPTY 原始 UTF-8/VT 输出，stdin EOF 后停止输入转发并保持 ConPTY 连接，并返回目标退出码。stdout/stderr 在 ConPTY 内合并。调用方消费的是终端字节，其中可能包含 ANSI 控制序列。初始化错误返回非零并提供诊断。

## 嵌入库 API

在导入前设置库模式，避免当前脚本自动启动 GUI：

```python
import os
import sys
os.environ['AETHER_MODE'] = 'library'
from aether import TerminalSession

with TerminalSession.spawn(
    [sys.executable, '-u', '-c', "name=input('NAME:'); print('HELLO='+name)"],
    cols=100, rows=28,
) as session:
    if not session.wait_for_text('NAME:', timeout=5):
        raise RuntimeError('没有到达输入提示')
    session.write('Alice\r')
    while session.read(timeout=5):
        pass
    code = session.wait(timeout=5)
    print(session.screen.snapshot().text)
    print('Exit:', code)
```

也可在启动宿主前设置 `$env:AETHER_MODE='library'`。库模式的导入不创建窗口或子进程；显式 spawn 才创建目标及内部 broker。command 必填，字符串只表示可执行路径；带参数用序列。cwd 显式传递，env 在继承环境上增量覆盖。

启动指定 conda Python 不等于完整激活 conda 环境。Aether 不伪造 CONDA_PREFIX，不自动改 PATH 或执行 conda activate。需要完整环境时调用方显式提供 env，或在 shell 内激活。

### 读取、快照与生命周期

| API | 语义 |
|---|---|
| write(str/bytes) | UTF-8 文本或原始字节，返回字节数 |
| read(timeout, max_bytes) | 单消费者读取原始字节；超时抛 TimeoutError；自然 EOF 返回空 bytes；显式关闭抛 SessionClosedError |
| screen.snapshot() | 不可变单元、样式、光标、标题及模式；include_scrollback=True 包含历史 |
| resize(cols, rows) | 同步终端与屏幕；已有内容裁剪/补空，不 reflow |
| wait(timeout) | 返回退出码，超时返回 None；不代表全部输出已消费 |
| wait_for_text(text, timeout) | 查询可见屏幕，不消费原始输出 |
| interrupt() | 向目标发送 0x03，不等于 GUI Ctrl+C |
| close(force=True, timeout=2) | 幂等；非强制先尝试中断，再超时强制；取消等待并清理 |

默认原始输出捕获约 16 MiB（chunk 粒度）后施加背压。调用方应持续 read 到 EOF，防止大量输出阻塞目标。只需要屏幕时设置 capture_output=False，此时 read 抛 OutputCaptureDisabledError。GUI 使用该模式；backlog_bytes 可观察捕获队列大小。上下文管理器始终强制清理。

公共类型为 TerminalSession、Screen、ScreenSnapshot、Cell、Style、Cursor；错误为 AetherError 及 UnsupportedPlatformError、ConPtyUnavailableError、ProcessSpawnError（winerror）、SessionClosedError、OutputCaptureDisabledError。

可运行示例：

```powershell
& $py examples\embed.py
& $py examples\stream.py
```

源码包中的 `examples/embed.py` 展示输入、排空输出、快照、退出码与清理；`examples/stream.py` 展示原始字节消费。

## 首版边界与排查

## 0.2.0 结构化控制台渲染

0.2.0 在同一个 Aether GUI 窗口中提供目标 Python 进程可调用的结构化渲染 API：

```python
from aether import console, Tree

console.print("准备开始")
tree = Tree("项目")
tree.add("src").add("aether")
console.print(tree)

with console.progress() as progress:
    task = progress.add_task("处理", total=100)
    progress.update(task, completed=100)
    progress.finish(task)
```

富文本使用 `RichText`、`Span` 和 `RenderStyle` 组合颜色、粗体、斜体、下划线和删除线。活动进度条按创建顺序排列在同一窗口顶部；普通 Python `sys.stdout`/`sys.stderr`、富文本和树在活动进度期间进入有界延迟队列，最后一个活动组件结束后按序回放。输入提示和读取也等待活动组件结束；Ctrl+Break 与窗口关闭仍然立即生效。

严格顺序只覆盖主目标 Python 的标准流和结构化组件。`os.write`、`WriteConsole`、原生扩展、备用屏程序和后代进程继续经由 ConPTY 显示，但 ConPTY 会重绘和合并这些输出，Aether 不对它们作严格排序承诺。未使用 `aether.render` 的脚本继续使用 0.1.0 的 legacy 渲染路径；可设置 `AETHER_RENDERING=legacy` 强制回退。

- stdout/stderr 是一个 ConPTY 合并流，不提供通道分离；也不提供正式 attach API。同 PID 接入已有有限成功实验，但 handler/回滚等尚未完成，首版采用 re-exec。
- 单会话，不含标签页/窗格、多会话管理、录制回放、插件、终端鼠标协议、图形协议、POSIX 或安全沙箱。文本选区不等于鼠标协议支持。
- 字符宽度使用 Unicode W/F 与组合字符规则；不承诺完整 emoji ZWJ/字素簇或歧义宽度布局。resize 不重排已有行。
- Ctrl+D 不被翻译为 Unix EOF；Windows 行输入中 Ctrl+Z+Enter 的行为取决于目标。Ctrl+C 在 GUI 始终复制，中断用 Ctrl+Break/菜单。
- Job Object 最佳努力绑定；失败不阻断启动，也不保证终止所有脱离会话的后代。
- 若没有 GUI，先检查 AETHER_MODE 和入口是否为普通脚本；若 Tkinter 不可用，修复当前 Python 的 Tk 安装，或使用库模式/显式 --pipe。
- 解释器/命令不可用时明确报错，不回退其他 Python；用 probe --json 检查 sys.executable、prefix、pip、ConPTY 和 Tk 可用性。
- IPython/prompt_toolkit 应在可写临时 IPYTHONDIR 中验证；历史目录只读不是 ConPTY 不可行的证据。兼容结论以真实流程验证记录为准。
- 无标准流或失效 stderr 的初始化错误使用原生 MessageBoxW，运行中的 broker 故障保留屏幕并显示错误。
- ConPTY 宿主流是 UTF-8，但字节 API 的目标代码页可能仍是 936。Python Unicode print 正常；若目标用 os.write 发送 UTF-8，目标应先显式 SetConsoleOutputCP(65001)，否则按其当前 console 代码页解释字节。
- 安装验证使用全新 venv，并确认导入来自该环境的 site-packages；Conda、Tk 和原生 DLL 的可用性取决于当前 Python 安装，遇到问题应先运行 probe 检查，不能据单一环境推断所有安装均可用。

## 开发验证

```powershell
& $py -m pytest -q -W error
& $py -m pytest -q -m 'not integration'
& $py -m pytest -q -m integration
```

自动化测试可通过 `python -m pytest -q -W error` 运行。Windows NumLock 不作为 Alt，输入法提交字符优先作为文本。GUI 测试会创建短暂窗口并自动关闭；图形事件测试与人工视觉检查分开记录。

管道模式的宿主 stdin EOF 只停止输入转发，ConPTY 输入连接保持到目标退出或会话关闭。关闭 ConPTY 输入不是普通管道的半关闭，在较新 Windows 上可能终止客户端；需要结束目标输入时发送其终端 EOF 操作（例如 Windows 行输入 Ctrl+Z+Enter）。

prompt_toolkit 3.0.53 的 Windows 同步 `Application.run()` 在部分远程环境退出时会打印 `RuntimeError: Executor shutdown has been called`，目标仍可能成功退出。这是其 Windows 输入回调与 asyncio 关闭的竞态；Aether 保留该输出。目标可以自行管理 `run_async()`，并在返回后让循环继续运行一小段时间再关闭；这只在已测场景中缓解问题，不能保证任意上游版本或负载下无竞态。

0.2.0 已知未修复缺陷：在完整手动验收流程中，用户观察到 Ctrl+Break 后进入 `CTRL_D:` 输入，再按 Ctrl+D 和 Backspace 时可能发生光标跳行和屏幕文字重叠。当前自动化 GUI 回归覆盖了回显、Ctrl+Break、Ctrl+D、Backspace 及滚动历史，但未能复现该显示异常；该问题仍未修复。
