ot-cli 0.2.1__py3-none-any.whl

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 (91) hide show
  1. openterminal/__init__.py +3 -0
  2. openterminal/agent/__init__.py +39 -0
  3. openterminal/agent/backend.py +180 -0
  4. openterminal/agent/build.py +115 -0
  5. openterminal/agent/middleware.py +111 -0
  6. openterminal/agent/model.py +41 -0
  7. openterminal/agent/prompt.py +63 -0
  8. openterminal/agent/runner.py +340 -0
  9. openterminal/agent/sysprobe.py +164 -0
  10. openterminal/agent/token_est.py +49 -0
  11. openterminal/app.py +52 -0
  12. openterminal/approval.py +7 -0
  13. openterminal/backend.py +3 -0
  14. openterminal/banner.py +7 -0
  15. openterminal/cli.py +426 -0
  16. openterminal/cmdset.py +7 -0
  17. openterminal/config.py +322 -0
  18. openterminal/connections/__init__.py +20 -0
  19. openterminal/connections/db.py +240 -0
  20. openterminal/connections/models.py +289 -0
  21. openterminal/connections/secrets.py +68 -0
  22. openterminal/connections_db.py +6 -0
  23. openterminal/core/__init__.py +41 -0
  24. openterminal/core/ai_flow.py +781 -0
  25. openterminal/core/auth.py +499 -0
  26. openterminal/core/cmdset_runner.py +352 -0
  27. openterminal/core/exec_events.py +650 -0
  28. openterminal/core/history.py +79 -0
  29. openterminal/core/hook_gate.py +415 -0
  30. openterminal/core/messages.py +193 -0
  31. openterminal/core/nested_ssh.py +178 -0
  32. openterminal/core/pipeline.py +1187 -0
  33. openterminal/core/pump.py +190 -0
  34. openterminal/core/transcript.py +26 -0
  35. openterminal/frontend/__init__.py +21 -0
  36. openterminal/frontend/approval.py +40 -0
  37. openterminal/frontend/banner.py +40 -0
  38. openterminal/frontend/headless.py +290 -0
  39. openterminal/frontend/render.py +41 -0
  40. openterminal/frontend/taskview.py +184 -0
  41. openterminal/frontend/term.py +1417 -0
  42. openterminal/headless.py +7 -0
  43. openterminal/history_db.py +7 -0
  44. openterminal/local_pty.py +3 -0
  45. openterminal/local_win.py +3 -0
  46. openterminal/policy.py +362 -0
  47. openterminal/render.py +7 -0
  48. openterminal/secrets_store.py +5 -0
  49. openterminal/session/__init__.py +17 -0
  50. openterminal/session/base.py +328 -0
  51. openterminal/session/local_posix.py +184 -0
  52. openterminal/session/local_win.py +173 -0
  53. openterminal/session/ssh.py +321 -0
  54. openterminal/shell/__init__.py +17 -0
  55. openterminal/shell/cmdset.py +49 -0
  56. openterminal/shell/integration.py +1298 -0
  57. openterminal/shell_integration.py +7 -0
  58. openterminal/shell_session.py +8 -0
  59. openterminal/skills/__init__.py +45 -0
  60. openterminal/skills/markdown-tables/SKILL.md +247 -0
  61. openterminal/ssh_pty.py +6 -0
  62. openterminal/sysprobe.py +7 -0
  63. openterminal/taskview.py +7 -0
  64. openterminal/term_frontend.py +7 -0
  65. openterminal/token_est.py +3 -0
  66. openterminal/transcript.py +7 -0
  67. openterminal/web/__init__.py +1 -0
  68. openterminal/web/files_api.py +789 -0
  69. openterminal/web/frontend/index.html +37 -0
  70. openterminal/web/frontend/static/app.js +3632 -0
  71. openterminal/web/frontend/static/block_stream.js +274 -0
  72. openterminal/web/frontend/static/block_view.js +383 -0
  73. openterminal/web/frontend/static/files_panel.js +782 -0
  74. openterminal/web/frontend/static/md_lite.js +127 -0
  75. openterminal/web/frontend/static/modal.js +122 -0
  76. openterminal/web/frontend/static/pad_ledger.js +46 -0
  77. openterminal/web/frontend/static/style.css +915 -0
  78. openterminal/web/frontend/static/ui/ui.css +1 -0
  79. openterminal/web/frontend/static/ui/ui.js +6292 -0
  80. openterminal/web/frontend/vendor/addon-fit.js +2 -0
  81. openterminal/web/frontend/vendor/xterm.css +8 -0
  82. openterminal/web/frontend/vendor/xterm.js +8 -0
  83. openterminal/web/protocol.py +41 -0
  84. openterminal/web/server.py +322 -0
  85. openterminal/web/worker.py +62 -0
  86. ot_cli-0.2.1.dist-info/METADATA +439 -0
  87. ot_cli-0.2.1.dist-info/RECORD +91 -0
  88. ot_cli-0.2.1.dist-info/WHEEL +5 -0
  89. ot_cli-0.2.1.dist-info/entry_points.txt +3 -0
  90. ot_cli-0.2.1.dist-info/licenses/LICENSE +202 -0
  91. ot_cli-0.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,184 @@
1
+ """任务级展示策略:模型思考、命令面板、真实输出、总结分段成框。
2
+
3
+ 模型文字全程可见:每个思考段经 Live 面板流式呈现(无内容时显示
4
+ "思考中…"动效),段落结束(命令开始/被拒/收尾)定格为独立框;最终总结
5
+ 单独成"总结"框;命令输出原样透传,退出码非 0 只附一行提示。
6
+
7
+ TaskPresenter 不依赖 rich:展示经注入的 sink 与 stream 完成,便于纯逻辑
8
+ 单测。CLI 层负责用 rich console 装配这些 sink 与 ThinkingStream。
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+ from typing import Callable, Protocol
15
+
16
+
17
+ class _Stream(Protocol):
18
+ def show(self, text: str) -> None: ...
19
+ def halt(self) -> None: ...
20
+ def box(self, text: str) -> None: ...
21
+
22
+
23
+ @dataclass
24
+ class TaskPresenter:
25
+ emit: Callable[[str], None]
26
+ command_panel: Callable[[str], None]
27
+ denied_line: Callable[[str], None]
28
+ error_line: Callable[[str], None]
29
+ summary_panel: Callable[[str], None]
30
+ stream: _Stream | None = None
31
+
32
+ def __post_init__(self) -> None:
33
+ self.pending = 0
34
+ self.segment_text = "" # 当前思考段内容(展示与定格的唯一事实源)
35
+
36
+ # --- 思考段 ---
37
+ def _show(self) -> None:
38
+ if self.stream is not None:
39
+ self.stream.show(self.segment_text)
40
+
41
+ def begin(self) -> None:
42
+ self.segment_text = ""
43
+ self._show() # 无内容 → spinner"思考中…"
44
+
45
+ def pause(self) -> None:
46
+ # Live 必须在 prompt_toolkit 接管终端前停掉;segment_text 保留,
47
+ # 段落收尾时照常定格成框
48
+ if self.stream is not None:
49
+ self.stream.halt()
50
+
51
+ def on_token(self, text: str) -> None:
52
+ if not text:
53
+ return
54
+ self.segment_text += text
55
+ self._show()
56
+
57
+ def _segment_end(self) -> None:
58
+ """当前思考段收尾:有内容则定格成框。"""
59
+ if self.stream is not None:
60
+ self.stream.halt()
61
+ if self.segment_text:
62
+ self.stream.box(self.segment_text)
63
+ self.segment_text = ""
64
+
65
+ # --- 命令生命周期(backend 回调)---
66
+ def on_start(self, command: str) -> None:
67
+ self._segment_end()
68
+ self.pending += 1
69
+ self.command_panel(command)
70
+
71
+ def on_output(self, text: str) -> None:
72
+ if text:
73
+ self.emit(text)
74
+
75
+ def on_finish(self, command: str, exit_code: int) -> None:
76
+ self.pending = max(0, self.pending - 1)
77
+ if exit_code != 0:
78
+ # 非零退出只提示、不当失败:探测类命令非零退出是常态
79
+ # (command -v 查缺工具、ls 探测、grep 无匹配都是非零)
80
+ self.emit(f"(命令退出码 {exit_code},非 0)\n")
81
+ if self.pending == 0:
82
+ # 命令都结束了,等模型继续思考/总结 → 重新点亮 Live
83
+ self._show()
84
+
85
+ # --- 拒绝/预算 ---
86
+ def on_denied(self, text: str) -> None:
87
+ self._segment_end()
88
+ self.denied_line(text.split("\n")[0])
89
+
90
+ def on_reject(self) -> None:
91
+ self._segment_end()
92
+ self.denied_line("已拒绝该命令,模型将改道或说明。")
93
+
94
+ def on_limit(self, text: str) -> None:
95
+ self._segment_end()
96
+ self.error_line(text)
97
+
98
+ def on_error(self, text: str) -> None:
99
+ self.on_limit(text)
100
+
101
+ # --- 收尾 ---
102
+ def finish(self, final_text: str) -> None:
103
+ # 最后一个流式段落就是总结,定格成总结框;没流式过(如非流式
104
+ # 模型、或被拒后直接收尾)才用 final 事件文本,避免内容重复
105
+ text = self.segment_text or final_text
106
+ if self.stream is not None:
107
+ self.stream.halt()
108
+ self.segment_text = ""
109
+ if text:
110
+ self.summary_panel(text)
111
+
112
+
113
+ class ThinkingStream:
114
+ """模型思考段的 Live 展示:无内容转圈"思考中…",有内容实时刷新;
115
+ 段落结束由调用方 box() 定格成完整框。
116
+
117
+ 防刷屏三件套(真机踩坑:直播帧变高后会在屏幕底部滚动,光标定位
118
+ 错乱导致每次刷新整帧追加,看起来像死循环):
119
+ - 直播视图只展示尾部少量字符,帧高远小于终端高度,覆盖刷新不失位
120
+ - Live 用 transient:halt 时擦掉活动帧,box() 再打印完整内容不重复
121
+ - 渲染对象每次刷新按最新文本重建(_LiveView),无需逐 token update
122
+
123
+ 非 TTY(测试/管道)下 Live 不可用:show/halt 空操作,box() 直接
124
+ 静态打印。与 prompt_toolkit 不能同时占用终端:任何输入 prompt
125
+ 出现前调用方必须先 halt()。
126
+ """
127
+
128
+ _TAIL_CHARS = 240 # 直播视图最多展示的尾部字符数(完整内容在定格框里)
129
+
130
+ def __init__(self, console, title: str = "💭 思考过程") -> None:
131
+ self._console = console
132
+ self._title = title
133
+ self._live = None
134
+ self._text = ""
135
+
136
+ def _panel(self, text: str):
137
+ from rich.panel import Panel
138
+ from rich.spinner import Spinner
139
+ from rich.text import Text
140
+
141
+ if text:
142
+ return Panel(Text(text), title=self._title,
143
+ title_align="left", border_style="cyan")
144
+ return Panel(Spinner("dots", text="思考中…"), title=self._title,
145
+ title_align="left", border_style="cyan")
146
+
147
+ def _render_live(self):
148
+ text = self._text
149
+ if len(text) > self._TAIL_CHARS:
150
+ text = "⋯(已省略前文,段落结束完整显示)\n" + text[-self._TAIL_CHARS:]
151
+ return self._panel(text)
152
+
153
+ def show(self, text: str) -> None:
154
+ if not self._console.is_terminal:
155
+ return
156
+ self._text = text
157
+ if self._live is None:
158
+ from rich.live import Live
159
+
160
+ self._live = Live(_LiveView(self), console=self._console,
161
+ refresh_per_second=6, transient=True)
162
+ self._live.start()
163
+
164
+ def halt(self) -> None:
165
+ if self._live is not None:
166
+ self._live.stop()
167
+ self._live = None
168
+
169
+ def box(self, text: str) -> None:
170
+ self.halt()
171
+ self._text = ""
172
+ self._console.print(self._panel(text))
173
+
174
+
175
+ class _LiveView:
176
+ """Live 每次刷新调用:按流对象的最新状态重建渲染。"""
177
+
178
+ def __init__(self, stream: ThinkingStream) -> None:
179
+ self._stream = stream
180
+
181
+ def __rich_console__(self, console, options):
182
+ yield self._stream._render_live()
183
+
184
+