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,127 @@
1
+ // 轻量 markdown 渲染(行内 code / 粗体 / 标题 / 列表 / 表格 / 段落),不引外部库。
2
+ // 从 app.js 抽出独立成文件:node --test 可直接加载(module.exports 守卫),
3
+ // 浏览器经 index.html 先于 app.js 装载为全局函数。
4
+ "use strict";
5
+
6
+ function esc(s) {
7
+ return String(s == null ? "" : s).replace(/[&<>"']/g, c => (
8
+ {"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"}[c]));
9
+ }
10
+
11
+ function mdInline(s) {
12
+ return s.replace(/`([^`]+)`/g, "<code>$1</code>")
13
+ .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
14
+ }
15
+
16
+ // --- 表格:按单元格切分一行(处理 \| 转义;去掉行首尾分隔符产生的空 cell)---
17
+ function _splitRow(ln) {
18
+ const cells = [];
19
+ let cur = "";
20
+ for (let i = 0; i < ln.length; i++) {
21
+ const ch = ln[i];
22
+ if (ch === "\\" && ln[i + 1] === "|") { cur += "|"; i++; continue; }
23
+ if (ch === "|") { cells.push(cur.trim()); cur = ""; continue; }
24
+ cur += ch;
25
+ }
26
+ cells.push(cur.trim());
27
+ if (cells.length && cells[0] === "") cells.shift();
28
+ if (cells.length && cells[cells.length - 1] === "") cells.pop();
29
+ return cells;
30
+ }
31
+
32
+ function _renderTable(rows) {
33
+ const cells = rows.map(_splitRow);
34
+ const aligns = cells[1].map(a =>
35
+ /^:-+:$/.test(a) ? "center" : /-+:$/.test(a) ? "right" : "left");
36
+ const attr = i => (aligns[i] && aligns[i] !== "left" ? ` class="a-${aligns[i]}"` : "");
37
+ const tr = (row, tag) => "<tr>" + row.map((c, i) =>
38
+ `<${tag}${attr(i)}>` + mdInline(c) + `</${tag}>`).join("") + "</tr>";
39
+ return '<div class="mtable"><table><thead>' + tr(cells[0], "th") +
40
+ "</thead><tbody>" + cells.slice(2).map(r => tr(r, "td")).join("") +
41
+ "</tbody></table></div>";
42
+ }
43
+
44
+ const isPipeRow = ln => /^\s*\|.*\|\s*$/.test(ln);
45
+
46
+ // 流式尾巴:未闭合的 `code / **bold 半截记号先剥掉,等闭合再渲染样式
47
+ function stripIncompleteMarkers(s) {
48
+ let t = String(s == null ? "" : s);
49
+ if (((t.match(/`/g) || []).length) % 2) t = t.slice(0, t.lastIndexOf("`"));
50
+ if (((t.match(/\*\*/g) || []).length) % 2) t = t.slice(0, t.lastIndexOf("**"));
51
+ return t;
52
+ }
53
+
54
+ // emoji 裸小节标题启发式:emoji 开头 + 无句末标点 + 去空白 ≤24 字符 → 当作 ## 小节标题渲染
55
+ const _EMOJI_START = /^\p{Extended_Pictographic}/u;
56
+ const _END_PUNCT = /[。!?;:,.;:))】\]」"']$/;
57
+ function isBareSectionTitle(ln) {
58
+ const t = ln.trim();
59
+ if (!t || t.includes("|")) return false;
60
+ if (!_EMOJI_START.test(t)) return false;
61
+ if (_END_PUNCT.test(t)) return false;
62
+ return [...t].length <= 24;
63
+ }
64
+
65
+ // 分块渲染(打字机逐块揭示用):把 markdown 切成自包含块——列表整段(含
66
+ // 开闭标签)为一块,表格一块,其余逐行一块。mdLite = mdLiteBlocks 拼接,
67
+ // 输出逐字节不变;块高度稳定(纯 opacity 揭示,绝不 clip/限高)。
68
+ function mdLiteBlocks(src) {
69
+ const lines = esc(String(src == null ? "" : src)).split("\n");
70
+ const out = [];
71
+ let list = null;
72
+ let items = [];
73
+ const closeList = () => {
74
+ if (list) { out.push("<" + list + ">" + items.join("") + "</" + list + ">"); list = null; items = []; }
75
+ };
76
+ for (let i = 0; i < lines.length; i++) {
77
+ const ln = lines[i];
78
+ // 表格:管道行且下一行是分隔行(|---|:--:|)→ 整表吞并渲染
79
+ if (isPipeRow(ln) && i + 1 < lines.length
80
+ && /^\s*\|[\s:|-]+\|\s*$/.test(lines[i + 1])) {
81
+ closeList();
82
+ const rows = [];
83
+ let j = i;
84
+ while (j < lines.length && isPipeRow(lines[j])) rows.push(lines[j++]);
85
+ out.push(_renderTable(rows));
86
+ i = j - 1;
87
+ continue;
88
+ }
89
+ // 标题(# ~ ####)
90
+ const h = ln.match(/^\s*(#{1,4})\s+(.+)$/);
91
+ if (h) {
92
+ closeList();
93
+ out.push('<div class="mh mh' + h[1].length + '">' + mdInline(h[2]) + "</div>");
94
+ continue;
95
+ }
96
+ let m = ln.match(/^\s*(\d+)[.)]\s+(.*)$/);
97
+ if (m) {
98
+ if (list !== "ol") { closeList(); list = "ol"; }
99
+ items.push("<li>" + mdInline(m[2]) + "</li>");
100
+ continue;
101
+ }
102
+ m = ln.match(/^\s*[-*]\s+(.*)$/);
103
+ if (m) {
104
+ if (list !== "ul") { closeList(); list = "ul"; }
105
+ items.push("<li>" + mdInline(m[1]) + "</li>");
106
+ continue;
107
+ }
108
+ closeList();
109
+ if (!ln.trim()) { out.push("<div class=\"mgap\"></div>"); continue; }
110
+ if (isBareSectionTitle(ln)) {
111
+ out.push('<div class="mh mh2">' + mdInline(ln.trim()) + "</div>");
112
+ continue;
113
+ }
114
+ out.push("<div class=\"mline\">" + mdInline(ln) + "</div>");
115
+ }
116
+ closeList();
117
+ return out;
118
+ }
119
+
120
+ function mdLite(src) {
121
+ return mdLiteBlocks(src).join("");
122
+ }
123
+
124
+ if (typeof module !== "undefined" && module.exports) {
125
+ module.exports = {esc, mdInline, mdLite, mdLiteBlocks, stripIncompleteMarkers,
126
+ isBareSectionTitle, _splitRow, _renderTable, isPipeRow};
127
+ }
@@ -0,0 +1,122 @@
1
+ // 通用模态框:从 app.js 抽出独立成文件,node --test 可直接加载
2
+ // (module.exports 守卫,照 md_lite.js / pad_ledger.js 的先例)。
3
+ //
4
+ // 两个不变量(都是真机踩出来的):
5
+ //
6
+ // 1. **代际号**:嵌套开窗时(如 TOFU 确认框顶掉登录框)旧 close 不得清掉
7
+ // 新窗。每个 modalShell 拿一个自增号,close 只在自己仍是最新时清 DOM。
8
+ // 缺了它,下面 async onClick 的修复会把刚开的 TOFU 确认框清空——表现
9
+ // 为「点了连接就没反应」(确认框开了又被旧 close 抹掉)。
10
+ //
11
+ // 2. **onClick 可 async**:返回 Promise 时等结果再决定关不关(返回 false
12
+ // = 不关)。旧实现 `b.onClick() !== false` 对 Promise 恒为真,弹窗必关
13
+ // ——删除失败/保存失败想留框都做不到。同步返回 false 的旧行为不变。
14
+ //
15
+ // Enter 提交对 `.modal` 内任意 input/select 生效,**textarea 除外**:编辑器
16
+ // (.fp-edit)里按 Enter 是换行,不能被提交关窗。
17
+
18
+ function _esc(s) {
19
+ return String(s == null ? "" : s).replace(/[&<>"']/g, (c) => ({
20
+ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;",
21
+ })[c]);
22
+ }
23
+
24
+ let _modalSeq = 0;
25
+
26
+ // buttons: [{label, primary?, onClick() -> bool|Promise<bool>(false=不关窗)}]
27
+ function modalShell(title, bodyHtml, buttons, opts = {}) {
28
+ const root = opts.root || document.getElementById("modal-root");
29
+ const my = ++_modalSeq;
30
+ delete root.dataset.pw; // 新模态顶掉密码模态标记:代关只认密码模态
31
+ const btns = buttons.map((b, i) =>
32
+ `<button class="${b.primary ? "primary" : ""}" data-i="${i}">` +
33
+ `${_esc(b.label)}</button>`).join("");
34
+ root.innerHTML = `
35
+ <div class="modal">
36
+ <h3>${_esc(title)}</h3>
37
+ <div class="modal-body">${bodyHtml}</div>
38
+ <div class="modal-btns">${btns}</div>
39
+ </div>`;
40
+
41
+ const close = () => {
42
+ // 只清自己这一代:期间若又开了新模态,旧 close 什么都不做
43
+ if (my === _modalSeq) root.innerHTML = "";
44
+ };
45
+
46
+ const fire = (b) => {
47
+ // 同步 onClick 保持同步语义(调用方的时序假设不受影响);只有真的返回
48
+ // Promise(async onClick)才转异步等待。一律包成 Promise 会把同步回调
49
+ // 推到 microtask,改掉既有「点了立即有副作用」的行为。
50
+ let r;
51
+ try {
52
+ r = b.onClick ? b.onClick() : true;
53
+ } catch (e) {
54
+ console.error("modal onClick 失败:", e);
55
+ return;
56
+ }
57
+ if (r && typeof r.then === "function") {
58
+ r.then((v) => { if (v !== false) close(); })
59
+ .catch((e) => console.error("modal onClick 失败:", e));
60
+ return;
61
+ }
62
+ if (r !== false) close();
63
+ };
64
+
65
+ root.querySelectorAll(".modal-btns button").forEach((btn) => {
66
+ btn.onclick = () => fire(buttons[Number(btn.dataset.i)]);
67
+ });
68
+
69
+ root.onkeydown = (ev) => {
70
+ if (ev.key !== "Enter" || ev.shiftKey) return;
71
+ // textarea 里 Enter 是换行;只对 input/select 提交
72
+ const t = ev.target;
73
+ if (!t || (t.tagName !== "INPUT" && t.tagName !== "SELECT")) return;
74
+ ev.preventDefault();
75
+ fire(buttons.find((b) => b.primary) || buttons[0]);
76
+ };
77
+
78
+ root.onmousedown = (ev) => {
79
+ if (ev.target === root) {
80
+ opts.onDismiss && opts.onDismiss();
81
+ close();
82
+ }
83
+ };
84
+ return close;
85
+ }
86
+
87
+ function modalAsk(title, message, onOk, opts = {}) {
88
+ const rememberHtml = opts.remember
89
+ ? `<label class="modal-check"><input type="checkbox" id="modal-remember" checked> ` +
90
+ `${_esc(opts.rememberLabel || "记住密码")}</label>`
91
+ : "";
92
+ modalShell(title,
93
+ `<p>${_esc(message)}</p>` +
94
+ `<input type="${opts.password === false ? "text" : "password"}" ` +
95
+ `id="modal-value" autofocus>` +
96
+ rememberHtml,
97
+ [
98
+ {label: "确定", primary: true, onClick: () => {
99
+ const remember = opts.remember
100
+ ? document.getElementById("modal-remember").checked : false;
101
+ onOk(document.getElementById("modal-value").value, remember);
102
+ }},
103
+ {label: "取消", onClick: () => { opts.onCancel && opts.onCancel(); }},
104
+ ],
105
+ {onDismiss: opts.onCancel, root: opts.root});
106
+ const r = opts.root || document.getElementById("modal-root");
107
+ r.dataset.pw = "1";
108
+ const v = document.getElementById("modal-value");
109
+ if (v) v.focus();
110
+ }
111
+
112
+ function modalConfirm(title, message, onOk, yesLabel = "信任", noLabel = "拒绝",
113
+ opts = {}) {
114
+ modalShell(title, `<p>${_esc(message)}</p>`, [
115
+ {label: yesLabel, primary: true, onClick: () => onOk(true)},
116
+ {label: noLabel, onClick: () => onOk(false)},
117
+ ], opts);
118
+ }
119
+
120
+ if (typeof module !== "undefined" && module.exports) {
121
+ module.exports = {modalShell, modalAsk, modalConfirm, _esc};
122
+ }
@@ -0,0 +1,46 @@
1
+ // pad 账目纯函数(node --test 可直测)。唯一权威是 xterm 缓冲:
2
+ // worker 的 padded 应答只当唤醒信号,不入账。
3
+ "use strict";
4
+
5
+ // 卡片像素高 → 行数(至少 1 行);cellH 非法时兜底 1 行。
6
+ function rowsForPx(hPx, cellH) {
7
+ if (!(cellH > 0)) return 1;
8
+ if (!(hPx > 0)) return 1;
9
+ return Math.max(1, Math.ceil(hPx / cellH));
10
+ }
11
+
12
+ // 从 startLine 起的**连续**空白行数,最多到 limit(不含)。translateToString(true)
13
+ // 去尾空白后为空即判空;越界/缺行视为空白(宁可少补,不可虚记)。
14
+ // 这段连续空白就是该卡的预留区:其后一旦出现真实输出,pad 只能追加在流底、
15
+ // 垫不进本区(该卡即「被盖帽」,长高只许截断)。旧实现按 [marker, 光标)
16
+ // 整段数空白——把卡下方的命令输出也算进区宽,于是「blank < 区宽」恒成立、
17
+ // 流底卡被误判成上游卡:一边夹紧钉高一边反复重贴(真机截断 + 闪)。
18
+ // 纯空白行必须 trim 后再判:translateToString(true) 只裁到「最后写入的单元格」,
19
+ // zsh 重画提示符时擦过的旧行留下整行空格、原样返回——不 trim 就把空格行当成
20
+ // 真实输出掐断空白 run,预留区永远数不满(真机流底卡出生即盖帽的来源之一)。
21
+ // 空格行上没有可见字形,卡盖上去不构成遮挡,判空白是安全的。
22
+ function blankRun(buf, startLine, limit) {
23
+ if (!buf) return 0;
24
+ const end = Math.max(startLine, limit | 0);
25
+ let n = 0;
26
+ while (startLine + n < end) {
27
+ const line = buf.getLine(startLine + n);
28
+ const s = line ? line.translateToString(true) : "";
29
+ // pad 自身回显行(__ot_pad N)与垫行注入的提示符行(PS ...>/#/$)不打断
30
+ // 空白 run:hook 清显/量高竞态下残留——数进去等于把卡夹到 1 行
31
+ // (真机「卡片乱七八糟」根因:垫行落在提示符下方,提示符行截断空白)
32
+ if (s && s.trim() && !/__ot_pad/.test(s) && !/[>$#%]\s*$/.test(s.trim())) break;
33
+ n++;
34
+ }
35
+ return n;
36
+ }
37
+
38
+ // 还缺多少:need 行(含 +1 保险)减去预留区里已有的连续空白 run。
39
+ // 只补真缺口,永不为负。
40
+ function padGap(need, blank) {
41
+ return Math.max(0, need - blank);
42
+ }
43
+
44
+ if (typeof module !== "undefined" && module.exports) {
45
+ module.exports = { rowsForPx, blankRun, padGap };
46
+ }