ccoding 0.2.1__tar.gz → 0.2.2__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.
- {ccoding-0.2.1 → ccoding-0.2.2}/PKG-INFO +15 -1
- {ccoding-0.2.1 → ccoding-0.2.2}/README.md +14 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/pyproject.toml +1 -1
- ccoding-0.2.2/screenshot_mobile1.jpeg +0 -0
- ccoding-0.2.2/screenshot_mobile2.jpeg +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/web/app.js +176 -10
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/web/index.html +17 -1
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/web/style.css +102 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/.github/workflows/publish.yml +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/.gitignore +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/CLAUDE.md +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/LICENSE +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/screenshot.png +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/__init__.py +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/__main__.py +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/api.py +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/cli.py +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/config.py +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/env_schema.json +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/logo.py +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/remote.py +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/remote_cli.py +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/remote_client.py +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/remote_common.py +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/remote_server.py +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/ui.py +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/web/__init__.py +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/web/assets/addon-fit.js +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/web/assets/xterm-LICENSE +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/web/assets/xterm-addon-fit-LICENSE +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/web/assets/xterm.css +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/src/ccode/web/assets/xterm.js +0 -0
- {ccoding-0.2.1 → ccoding-0.2.2}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ccoding
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Curses-based Claude Code launcher
|
|
5
5
|
Project-URL: Homepage, https://github.com/8DE4732A/ccode
|
|
6
6
|
Project-URL: Repository, https://github.com/8DE4732A/ccode
|
|
@@ -57,6 +57,11 @@ Description-Content-Type: text/markdown
|
|
|
57
57
|
|
|
58
58
|

|
|
59
59
|
|
|
60
|
+
<p align="center">
|
|
61
|
+
<img src="screenshot_mobile1.jpeg" alt="Mobile remote terminal screenshot 1" width="45%" />
|
|
62
|
+
<img src="screenshot_mobile2.jpeg" alt="Mobile remote terminal screenshot 2" width="45%" />
|
|
63
|
+
</p>
|
|
64
|
+
|
|
60
65
|
## 功能特点
|
|
61
66
|
|
|
62
67
|
- 终端 curses UI,键盘全操作
|
|
@@ -127,6 +132,15 @@ ccode-remote # 查看 remote hub / client connector 状态
|
|
|
127
132
|
|
|
128
133
|
需要 Python >=3.10。基础 TUI 不再强制安装 remote 依赖。Remote/Web 模式需要额外依赖,并需要系统中已安装 `tmux` 和 `claude`;Windows 建议在 WSL 中使用 remote 模式。
|
|
129
134
|
|
|
135
|
+
使用 Remote/Web 时,推荐在 `~/.tmux.conf` 中配置:
|
|
136
|
+
|
|
137
|
+
```tmux
|
|
138
|
+
set -g history-limit 50000
|
|
139
|
+
set -g mouse on
|
|
140
|
+
set -g default-terminal "screen-256color"
|
|
141
|
+
set-option -ga terminal-overrides ",xterm-256color:Tc"
|
|
142
|
+
```
|
|
143
|
+
|
|
130
144
|
```bash
|
|
131
145
|
git clone https://github.com/8DE4732A/ccode
|
|
132
146
|
cd ccode
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
9
|
+
<p align="center">
|
|
10
|
+
<img src="screenshot_mobile1.jpeg" alt="Mobile remote terminal screenshot 1" width="45%" />
|
|
11
|
+
<img src="screenshot_mobile2.jpeg" alt="Mobile remote terminal screenshot 2" width="45%" />
|
|
12
|
+
</p>
|
|
13
|
+
|
|
9
14
|
## 功能特点
|
|
10
15
|
|
|
11
16
|
- 终端 curses UI,键盘全操作
|
|
@@ -76,6 +81,15 @@ ccode-remote # 查看 remote hub / client connector 状态
|
|
|
76
81
|
|
|
77
82
|
需要 Python >=3.10。基础 TUI 不再强制安装 remote 依赖。Remote/Web 模式需要额外依赖,并需要系统中已安装 `tmux` 和 `claude`;Windows 建议在 WSL 中使用 remote 模式。
|
|
78
83
|
|
|
84
|
+
使用 Remote/Web 时,推荐在 `~/.tmux.conf` 中配置:
|
|
85
|
+
|
|
86
|
+
```tmux
|
|
87
|
+
set -g history-limit 50000
|
|
88
|
+
set -g mouse on
|
|
89
|
+
set -g default-terminal "screen-256color"
|
|
90
|
+
set-option -ga terminal-overrides ",xterm-256color:Tc"
|
|
91
|
+
```
|
|
92
|
+
|
|
79
93
|
```bash
|
|
80
94
|
git clone https://github.com/8DE4732A/ccode
|
|
81
95
|
cd ccode
|
|
Binary file
|
|
Binary file
|
|
@@ -3,23 +3,127 @@ const sessionListEl = document.getElementById("session-list");
|
|
|
3
3
|
const tokenFormEl = document.getElementById("token-form");
|
|
4
4
|
const tokenInputEl = document.getElementById("token-input");
|
|
5
5
|
const statusEl = document.getElementById("status");
|
|
6
|
+
const actionsEl = document.getElementById("actions");
|
|
7
|
+
const actionsToggleButton = document.getElementById("actions-toggle");
|
|
6
8
|
const backButton = document.getElementById("back");
|
|
7
9
|
const refreshButton = document.getElementById("refresh");
|
|
8
10
|
const changeTokenButton = document.getElementById("change-token");
|
|
9
11
|
const reconnectButton = document.getElementById("reconnect");
|
|
10
12
|
const focusButton = document.getElementById("focus");
|
|
13
|
+
const mobileKeybarEl = document.getElementById("mobile-keybar");
|
|
14
|
+
|
|
15
|
+
const inputSequences = {
|
|
16
|
+
tab: "\t",
|
|
17
|
+
esc: "\x1b",
|
|
18
|
+
"ctrl-c": "\x03",
|
|
19
|
+
"ctrl-d": "\x04",
|
|
20
|
+
"ctrl-l": "\x0c",
|
|
21
|
+
enter: "\r",
|
|
22
|
+
backspace: "\x7f",
|
|
23
|
+
up: "\x1b[A",
|
|
24
|
+
down: "\x1b[B",
|
|
25
|
+
right: "\x1b[C",
|
|
26
|
+
left: "\x1b[D",
|
|
27
|
+
};
|
|
11
28
|
|
|
12
29
|
const tokenStorageKey = "ccode.remote.adminToken";
|
|
30
|
+
const fontSizeStorageKey = "ccode.remote.terminalFontSize";
|
|
13
31
|
let token = window.sessionStorage.getItem(tokenStorageKey) || "";
|
|
14
32
|
let socket = null;
|
|
15
33
|
let term = null;
|
|
16
34
|
let fitAddon = null;
|
|
17
35
|
let currentSession = null;
|
|
36
|
+
let terminalFontSize = Number(window.localStorage.getItem(fontSizeStorageKey)) || 14;
|
|
37
|
+
let terminalTouchScroll = null;
|
|
38
|
+
let terminalViewportEl = null;
|
|
39
|
+
let resizeFrame = null;
|
|
40
|
+
let viewportHeight = "";
|
|
41
|
+
let keyboardInset = "";
|
|
18
42
|
|
|
19
43
|
function setStatus(message) {
|
|
20
44
|
statusEl.textContent = message;
|
|
21
45
|
}
|
|
22
46
|
|
|
47
|
+
function sendInput(data) {
|
|
48
|
+
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
socket.send(JSON.stringify({ type: "input", data }));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function focusTerminal() {
|
|
55
|
+
ensureTerminal();
|
|
56
|
+
term.focus();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function adjustTerminalFontSize(delta) {
|
|
60
|
+
const nextFontSize = Math.min(22, Math.max(10, terminalFontSize + delta));
|
|
61
|
+
if (nextFontSize === terminalFontSize) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
terminalFontSize = nextFontSize;
|
|
65
|
+
window.localStorage.setItem(fontSizeStorageKey, String(terminalFontSize));
|
|
66
|
+
if (term) {
|
|
67
|
+
term.options.fontSize = terminalFontSize;
|
|
68
|
+
fitAndSendResize();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function closeActionMenu() {
|
|
73
|
+
actionsEl.classList.remove("open");
|
|
74
|
+
actionsToggleButton.setAttribute("aria-expanded", "false");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function beginTerminalTouchScroll(y) {
|
|
78
|
+
if (!term || terminalTouchScroll) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
terminalTouchScroll = { y, remainder: 0, active: false };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function scrollTerminalFromTouch(y, event) {
|
|
85
|
+
if (!terminalTouchScroll || !term) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const deltaY = terminalTouchScroll.y - y;
|
|
89
|
+
if (!terminalTouchScroll.active && Math.abs(deltaY) < 8) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
terminalTouchScroll.active = true;
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
if (terminalViewportEl) {
|
|
95
|
+
terminalViewportEl.dispatchEvent(new WheelEvent("wheel", { deltaY, bubbles: true, cancelable: true }));
|
|
96
|
+
}
|
|
97
|
+
const lineHeight = Math.max(14, terminalFontSize * 1.35);
|
|
98
|
+
const delta = deltaY + terminalTouchScroll.remainder;
|
|
99
|
+
const lines = Math.trunc(delta / lineHeight);
|
|
100
|
+
terminalTouchScroll.remainder = delta - lines * lineHeight;
|
|
101
|
+
terminalTouchScroll.y = y;
|
|
102
|
+
if (lines) {
|
|
103
|
+
term.scrollLines(lines);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function endTerminalTouchScroll() {
|
|
108
|
+
terminalTouchScroll = null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function startTerminalTouchScroll(event) {
|
|
112
|
+
if (event.touches.length === 1) {
|
|
113
|
+
beginTerminalTouchScroll(event.touches[0].clientY);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function moveTerminalTouchScroll(event) {
|
|
118
|
+
if (event.touches.length === 1) {
|
|
119
|
+
scrollTerminalFromTouch(event.touches[0].clientY, event);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function stopTerminalTouchScroll() {
|
|
124
|
+
endTerminalTouchScroll();
|
|
125
|
+
}
|
|
126
|
+
|
|
23
127
|
function ensureTerminal() {
|
|
24
128
|
if (term) {
|
|
25
129
|
return;
|
|
@@ -28,7 +132,7 @@ function ensureTerminal() {
|
|
|
28
132
|
cursorBlink: true,
|
|
29
133
|
convertEol: true,
|
|
30
134
|
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace',
|
|
31
|
-
fontSize:
|
|
135
|
+
fontSize: terminalFontSize,
|
|
32
136
|
theme: {
|
|
33
137
|
background: "#05070a",
|
|
34
138
|
foreground: "#d6e2f0",
|
|
@@ -39,15 +143,36 @@ function ensureTerminal() {
|
|
|
39
143
|
fitAddon = new FitAddon.FitAddon();
|
|
40
144
|
term.loadAddon(fitAddon);
|
|
41
145
|
term.open(terminalEl);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
146
|
+
terminalViewportEl = terminalEl.querySelector(".xterm-viewport");
|
|
147
|
+
term.onData(sendInput);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function updateViewportInsets() {
|
|
151
|
+
const viewport = window.visualViewport;
|
|
152
|
+
const nextViewportHeight = `${viewport ? viewport.height : window.innerHeight}px`;
|
|
153
|
+
const nextKeyboardInset = `${viewport ? Math.max(0, window.innerHeight - viewport.height - viewport.offsetTop) : 0}px`;
|
|
154
|
+
if (nextViewportHeight !== viewportHeight) {
|
|
155
|
+
viewportHeight = nextViewportHeight;
|
|
156
|
+
document.documentElement.style.setProperty("--viewport-height", viewportHeight);
|
|
157
|
+
}
|
|
158
|
+
if (nextKeyboardInset !== keyboardInset) {
|
|
159
|
+
keyboardInset = nextKeyboardInset;
|
|
160
|
+
document.documentElement.style.setProperty("--keyboard-inset", keyboardInset);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function requestFitAndSendResize() {
|
|
165
|
+
if (resizeFrame !== null) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
resizeFrame = window.requestAnimationFrame(() => {
|
|
169
|
+
resizeFrame = null;
|
|
170
|
+
fitAndSendResize();
|
|
47
171
|
});
|
|
48
172
|
}
|
|
49
173
|
|
|
50
174
|
function fitAndSendResize() {
|
|
175
|
+
updateViewportInsets();
|
|
51
176
|
if (!term || !fitAddon || terminalEl.hidden) {
|
|
52
177
|
return;
|
|
53
178
|
}
|
|
@@ -71,9 +196,12 @@ function showTokenMode(message = "enter token") {
|
|
|
71
196
|
terminalEl.hidden = true;
|
|
72
197
|
sessionListEl.hidden = true;
|
|
73
198
|
tokenFormEl.hidden = false;
|
|
199
|
+
actionsToggleButton.hidden = true;
|
|
200
|
+
closeActionMenu();
|
|
74
201
|
backButton.hidden = true;
|
|
75
202
|
focusButton.hidden = true;
|
|
76
203
|
reconnectButton.hidden = true;
|
|
204
|
+
mobileKeybarEl.hidden = true;
|
|
77
205
|
refreshButton.hidden = true;
|
|
78
206
|
changeTokenButton.hidden = true;
|
|
79
207
|
setStatus(message);
|
|
@@ -87,9 +215,12 @@ function showListMode() {
|
|
|
87
215
|
tokenFormEl.hidden = true;
|
|
88
216
|
terminalEl.hidden = true;
|
|
89
217
|
sessionListEl.hidden = false;
|
|
218
|
+
actionsToggleButton.hidden = false;
|
|
219
|
+
closeActionMenu();
|
|
90
220
|
backButton.hidden = true;
|
|
91
221
|
focusButton.hidden = true;
|
|
92
222
|
reconnectButton.hidden = true;
|
|
223
|
+
mobileKeybarEl.hidden = true;
|
|
93
224
|
refreshButton.hidden = false;
|
|
94
225
|
changeTokenButton.hidden = false;
|
|
95
226
|
}
|
|
@@ -98,9 +229,12 @@ function showTerminalMode() {
|
|
|
98
229
|
tokenFormEl.hidden = true;
|
|
99
230
|
sessionListEl.hidden = true;
|
|
100
231
|
terminalEl.hidden = false;
|
|
232
|
+
actionsToggleButton.hidden = false;
|
|
233
|
+
closeActionMenu();
|
|
101
234
|
backButton.hidden = false;
|
|
102
235
|
focusButton.hidden = false;
|
|
103
236
|
reconnectButton.hidden = false;
|
|
237
|
+
mobileKeybarEl.hidden = false;
|
|
104
238
|
refreshButton.hidden = true;
|
|
105
239
|
changeTokenButton.hidden = false;
|
|
106
240
|
}
|
|
@@ -276,13 +410,45 @@ changeTokenButton.addEventListener("click", () => {
|
|
|
276
410
|
token = "";
|
|
277
411
|
showTokenMode("enter token");
|
|
278
412
|
});
|
|
279
|
-
window.addEventListener("resize",
|
|
413
|
+
window.addEventListener("resize", requestFitAndSendResize);
|
|
414
|
+
actionsToggleButton.addEventListener("click", () => {
|
|
415
|
+
const open = actionsEl.classList.toggle("open");
|
|
416
|
+
actionsToggleButton.setAttribute("aria-expanded", String(open));
|
|
417
|
+
});
|
|
418
|
+
actionsEl.addEventListener("click", (event) => {
|
|
419
|
+
if (event.target.closest("button")) {
|
|
420
|
+
closeActionMenu();
|
|
421
|
+
}
|
|
422
|
+
});
|
|
280
423
|
backButton.addEventListener("click", loadSessions);
|
|
281
424
|
refreshButton.addEventListener("click", loadSessions);
|
|
282
425
|
reconnectButton.addEventListener("click", connect);
|
|
283
|
-
focusButton.addEventListener("click",
|
|
284
|
-
|
|
285
|
-
|
|
426
|
+
focusButton.addEventListener("click", focusTerminal);
|
|
427
|
+
terminalEl.addEventListener("touchstart", startTerminalTouchScroll, { passive: true });
|
|
428
|
+
terminalEl.addEventListener("touchmove", moveTerminalTouchScroll, { passive: false });
|
|
429
|
+
terminalEl.addEventListener("touchend", stopTerminalTouchScroll);
|
|
430
|
+
terminalEl.addEventListener("touchcancel", stopTerminalTouchScroll);
|
|
431
|
+
mobileKeybarEl.addEventListener("pointerdown", (event) => {
|
|
432
|
+
const inputButton = event.target.closest("button[data-sequence]");
|
|
433
|
+
const zoomButton = event.target.closest("button[data-zoom]");
|
|
434
|
+
if (!inputButton && !zoomButton) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
event.preventDefault();
|
|
438
|
+
if (zoomButton) {
|
|
439
|
+
adjustTerminalFontSize(Number(zoomButton.dataset.zoom));
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const data = inputSequences[inputButton.dataset.sequence];
|
|
443
|
+
if (data) {
|
|
444
|
+
sendInput(data);
|
|
445
|
+
focusTerminal();
|
|
446
|
+
}
|
|
286
447
|
});
|
|
448
|
+
if (window.visualViewport) {
|
|
449
|
+
window.visualViewport.addEventListener("resize", requestFitAndSendResize);
|
|
450
|
+
window.visualViewport.addEventListener("scroll", requestFitAndSendResize);
|
|
451
|
+
}
|
|
452
|
+
updateViewportInsets();
|
|
287
453
|
|
|
288
454
|
loadSessions();
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
<h1>ccode remote</h1>
|
|
15
15
|
<p id="status">enter token</p>
|
|
16
16
|
</div>
|
|
17
|
-
<
|
|
17
|
+
<button id="actions-toggle" class="actions-toggle" type="button" hidden>Menu</button>
|
|
18
|
+
<div id="actions" class="actions">
|
|
18
19
|
<button id="back" type="button" hidden>Sessions</button>
|
|
19
20
|
<button id="refresh" type="button" hidden>Refresh</button>
|
|
20
21
|
<button id="change-token" type="button" hidden>Change token</button>
|
|
@@ -31,6 +32,21 @@
|
|
|
31
32
|
</form>
|
|
32
33
|
<section id="session-list" hidden></section>
|
|
33
34
|
<div id="terminal" tabindex="0" hidden></div>
|
|
35
|
+
<div id="mobile-keybar" class="mobile-keybar" hidden>
|
|
36
|
+
<button type="button" data-zoom="-1">A−</button>
|
|
37
|
+
<button type="button" data-zoom="1">A+</button>
|
|
38
|
+
<button type="button" data-sequence="tab">Tab</button>
|
|
39
|
+
<button type="button" data-sequence="esc">Esc</button>
|
|
40
|
+
<button type="button" data-sequence="ctrl-c">Ctrl+C</button>
|
|
41
|
+
<button type="button" data-sequence="ctrl-d">Ctrl+D</button>
|
|
42
|
+
<button type="button" data-sequence="ctrl-l">Ctrl+L</button>
|
|
43
|
+
<button type="button" data-sequence="enter">Enter</button>
|
|
44
|
+
<button type="button" data-sequence="backspace">⌫</button>
|
|
45
|
+
<button type="button" data-sequence="up">↑</button>
|
|
46
|
+
<button type="button" data-sequence="down">↓</button>
|
|
47
|
+
<button type="button" data-sequence="left">←</button>
|
|
48
|
+
<button type="button" data-sequence="right">→</button>
|
|
49
|
+
</div>
|
|
34
50
|
</main>
|
|
35
51
|
<script src="assets/xterm.js"></script>
|
|
36
52
|
<script src="assets/addon-fit.js"></script>
|
|
@@ -10,6 +10,11 @@ main {
|
|
|
10
10
|
margin: 0;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
:root {
|
|
14
|
+
--keyboard-inset: 0px;
|
|
15
|
+
--viewport-height: 100vh;
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
body {
|
|
14
19
|
background: #090b10;
|
|
15
20
|
color: #e8eef8;
|
|
@@ -47,6 +52,10 @@ p {
|
|
|
47
52
|
justify-content: flex-end;
|
|
48
53
|
}
|
|
49
54
|
|
|
55
|
+
.actions-toggle {
|
|
56
|
+
display: none;
|
|
57
|
+
}
|
|
58
|
+
|
|
50
59
|
button,
|
|
51
60
|
input {
|
|
52
61
|
border-radius: 8px;
|
|
@@ -168,12 +177,105 @@ button[hidden],
|
|
|
168
177
|
min-height: 0;
|
|
169
178
|
overflow: hidden;
|
|
170
179
|
padding: 12px;
|
|
180
|
+
touch-action: none;
|
|
171
181
|
}
|
|
172
182
|
|
|
173
183
|
#terminal:focus-within {
|
|
174
184
|
outline: 2px solid #4f8cff;
|
|
175
185
|
}
|
|
176
186
|
|
|
187
|
+
.mobile-keybar {
|
|
188
|
+
display: none;
|
|
189
|
+
}
|
|
190
|
+
|
|
177
191
|
.xterm {
|
|
178
192
|
height: 100%;
|
|
179
193
|
}
|
|
194
|
+
|
|
195
|
+
@media (max-width: 720px), (pointer: coarse) {
|
|
196
|
+
html,
|
|
197
|
+
body,
|
|
198
|
+
main {
|
|
199
|
+
height: var(--viewport-height);
|
|
200
|
+
overflow: hidden;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
main {
|
|
204
|
+
padding: 8px;
|
|
205
|
+
gap: 8px;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
header {
|
|
209
|
+
align-items: flex-start;
|
|
210
|
+
gap: 8px;
|
|
211
|
+
position: relative;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.actions-toggle:not([hidden]) {
|
|
215
|
+
display: block;
|
|
216
|
+
margin-left: auto;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.actions {
|
|
220
|
+
background: rgba(9, 11, 16, 0.98);
|
|
221
|
+
border: 1px solid #1c2638;
|
|
222
|
+
border-radius: 12px;
|
|
223
|
+
box-shadow: 0 14px 40px rgba(0, 0, 0, 0.38);
|
|
224
|
+
display: none;
|
|
225
|
+
flex-direction: column;
|
|
226
|
+
min-width: 180px;
|
|
227
|
+
padding: 8px;
|
|
228
|
+
position: absolute;
|
|
229
|
+
right: 0;
|
|
230
|
+
top: calc(100% + 6px);
|
|
231
|
+
z-index: 20;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.actions.open {
|
|
235
|
+
display: flex;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.actions button {
|
|
239
|
+
text-align: left;
|
|
240
|
+
width: 100%;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
#session-list {
|
|
244
|
+
overflow: auto;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
#terminal {
|
|
248
|
+
margin-bottom: 58px;
|
|
249
|
+
min-height: 120px;
|
|
250
|
+
padding: 8px;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.mobile-keybar:not([hidden]) {
|
|
254
|
+
background: rgba(9, 11, 16, 0.96);
|
|
255
|
+
border: 1px solid #1c2638;
|
|
256
|
+
border-radius: 12px;
|
|
257
|
+
bottom: calc(8px + var(--keyboard-inset));
|
|
258
|
+
display: flex;
|
|
259
|
+
gap: 6px;
|
|
260
|
+
left: 8px;
|
|
261
|
+
max-width: calc(100vw - 16px);
|
|
262
|
+
overflow-x: auto;
|
|
263
|
+
padding: 8px;
|
|
264
|
+
position: fixed;
|
|
265
|
+
right: 8px;
|
|
266
|
+
scrollbar-width: none;
|
|
267
|
+
z-index: 10;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.mobile-keybar::-webkit-scrollbar {
|
|
271
|
+
display: none;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.mobile-keybar button {
|
|
275
|
+
flex: 0 0 auto;
|
|
276
|
+
min-width: 48px;
|
|
277
|
+
padding: 10px 12px;
|
|
278
|
+
touch-action: manipulation;
|
|
279
|
+
user-select: none;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|