@wendongfly/myhi 1.0.6 → 1.0.8
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.
- package/bin/myhi.js +23 -4
- package/dist/chat.html +40 -14
- package/package.json +1 -1
package/bin/myhi.js
CHANGED
|
@@ -7,7 +7,22 @@ import { homedir } from 'os';
|
|
|
7
7
|
|
|
8
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
9
|
const __dirname = dirname(__filename);
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
// 解析 -p / --port 参数
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
let cmd = null;
|
|
14
|
+
for (let i = 0; i < args.length; i++) {
|
|
15
|
+
if ((args[i] === '-p' || args[i] === '--port') && args[i + 1]) {
|
|
16
|
+
process.env.PORT = args[i + 1];
|
|
17
|
+
args.splice(i, 2); i--;
|
|
18
|
+
} else if (args[i].startsWith('--port=')) {
|
|
19
|
+
process.env.PORT = args[i].split('=')[1];
|
|
20
|
+
args.splice(i, 1); i--;
|
|
21
|
+
} else if (!cmd && !args[i].startsWith('-')) {
|
|
22
|
+
cmd = args[i];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!cmd) cmd = args[0];
|
|
11
26
|
|
|
12
27
|
const configDir = join(homedir(), '.myhi');
|
|
13
28
|
const pidFile = join(configDir, 'daemon.pid');
|
|
@@ -90,12 +105,13 @@ if (cmd === 'attach') {
|
|
|
90
105
|
|
|
91
106
|
} else if (cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
92
107
|
console.log(`
|
|
93
|
-
myhi
|
|
108
|
+
myhi — 基于 Web 的终端共享工具
|
|
94
109
|
通过局域网或 Tailscale 在手机/平板上控制电脑终端,无需中继服务器。
|
|
95
110
|
|
|
96
111
|
服务器:
|
|
97
112
|
myhi 前台启动服务器
|
|
98
113
|
myhi start 前台启动服务器
|
|
114
|
+
myhi -p <port> 指定监听端口(默认 3000)
|
|
99
115
|
myhi -d, --daemon 后台启动服务器(日志写入 ~/.myhi/daemon.log)
|
|
100
116
|
myhi stop 停止后台服务器
|
|
101
117
|
myhi restart 重启后台服务器
|
|
@@ -110,10 +126,12 @@ myhi v1.0.0 — 基于 Web 的终端共享工具
|
|
|
110
126
|
快捷键(attach 模式):
|
|
111
127
|
Ctrl+] 分离终端(退出但不关闭会话)
|
|
112
128
|
|
|
129
|
+
选项:
|
|
130
|
+
-p, --port <port> 监听端口(默认 3000)
|
|
131
|
+
|
|
113
132
|
环境变量:
|
|
114
133
|
PORT 监听端口(默认 3000)
|
|
115
134
|
HOST 监听地址(默认 0.0.0.0)
|
|
116
|
-
HTTPS_PORT HTTPS 端口(默认 3443)
|
|
117
135
|
MYHI_AUTO_ATTACH 启动时自动创建会话(默认 1,设 0 关闭)
|
|
118
136
|
MYHI_CWD 会话工作目录(默认当前目录)
|
|
119
137
|
MYHI_SERVER attach 连接的服务器地址(默认 http://localhost:3000)
|
|
@@ -128,7 +146,8 @@ myhi v1.0.0 — 基于 Web 的终端共享工具
|
|
|
128
146
|
|
|
129
147
|
示例:
|
|
130
148
|
myhi # 启动后扫描二维码即可在手机上操作
|
|
131
|
-
|
|
149
|
+
myhi -p 8080 # 指定 8080 端口启动
|
|
150
|
+
myhi -p 8080 -d # 8080 端口后台启动
|
|
132
151
|
MYHI_CWD=/project myhi # 指定会话工作目录
|
|
133
152
|
myhi attach # 从另一台机器连接到会话
|
|
134
153
|
`);
|
package/dist/chat.html
CHANGED
|
@@ -129,7 +129,13 @@
|
|
|
129
129
|
/* ── 输入栏 ─────────────────────────────── */
|
|
130
130
|
#input-area { background: #161b22; border-top: 1px solid #30363d; flex-shrink: 0; padding: 0.5rem 0.75rem 0.35rem; }
|
|
131
131
|
#input-area.no-shortcuts { padding-bottom: max(0.5rem, env(safe-area-inset-bottom)); }
|
|
132
|
-
#input-box { background: #0d1117; border: 1px solid #30363d; border-radius: 12px; transition: border-color 0.2s; overflow: hidden; }
|
|
132
|
+
#input-box { background: #0d1117; border: 1px solid #30363d; border-radius: 12px; transition: border-color 0.2s; overflow: hidden; position: relative; }
|
|
133
|
+
#img-preview { display: none; align-items: center; gap: 0.5rem; padding: 0.5rem 0.75rem; border-bottom: 1px solid #21262d; background: #161b22; }
|
|
134
|
+
#img-preview.show { display: flex; }
|
|
135
|
+
#img-preview img { width: 48px; height: 48px; object-fit: cover; border-radius: 6px; border: 1px solid #30363d; }
|
|
136
|
+
#img-preview .img-info { flex: 1; font-size: 0.78rem; color: #8b949e; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
137
|
+
#img-preview .img-remove { width: 24px; height: 24px; border-radius: 50%; border: none; background: #21262d; color: #8b949e; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; flex-shrink: 0; }
|
|
138
|
+
#img-preview .img-remove:hover { background: #f85149; color: #fff; }
|
|
133
139
|
#input-box.focused { border-color: #58a6ff; }
|
|
134
140
|
#input-box.disabled { opacity: 0.5; pointer-events: none; }
|
|
135
141
|
#cmd-input { display: block; width: 100%; background: transparent; color: #e6edf3; border: none; padding: 0.6rem 0.75rem 0.3rem; font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; font-size: 0.85rem; outline: none; resize: none; line-height: 1.4; max-height: 120px; overflow-y: auto; }
|
|
@@ -223,6 +229,11 @@
|
|
|
223
229
|
<div id="input-area">
|
|
224
230
|
<div id="readonly-overlay">只读模式 — 点击状态栏"获取控制"开始输入</div>
|
|
225
231
|
<div id="input-box">
|
|
232
|
+
<div id="img-preview">
|
|
233
|
+
<img id="img-preview-thumb" src="" alt="">
|
|
234
|
+
<span class="img-info">已添加图片,输入文字后发送</span>
|
|
235
|
+
<button class="img-remove" onclick="clearPendingImage()" title="移除图片">×</button>
|
|
236
|
+
</div>
|
|
226
237
|
<textarea id="cmd-input" rows="1" placeholder="输入消息..." autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
|
|
227
238
|
<div id="input-toolbar">
|
|
228
239
|
<button class="tb-btn" id="btn-photo" onclick="openCamera()" title="拍照">
|
|
@@ -997,11 +1008,30 @@
|
|
|
997
1008
|
setWorkState('idle');
|
|
998
1009
|
});
|
|
999
1010
|
|
|
1011
|
+
// ── 待发送图片 ──────────────────────────────────
|
|
1012
|
+
let _pendingImage = null; // { path, blobUrl }
|
|
1013
|
+
|
|
1014
|
+
window.clearPendingImage = function() {
|
|
1015
|
+
if (_pendingImage?.blobUrl) URL.revokeObjectURL(_pendingImage.blobUrl);
|
|
1016
|
+
_pendingImage = null;
|
|
1017
|
+
document.getElementById('img-preview').classList.remove('show');
|
|
1018
|
+
cmdInput.placeholder = currentSession?.mode === 'agent' ? '输入消息...' : '输入命令...';
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
function setPendingImage(path, blobUrl) {
|
|
1022
|
+
_pendingImage = { path, blobUrl };
|
|
1023
|
+
const preview = document.getElementById('img-preview');
|
|
1024
|
+
document.getElementById('img-preview-thumb').src = blobUrl || '';
|
|
1025
|
+
preview.classList.add('show');
|
|
1026
|
+
cmdInput.placeholder = '输入文字描述后发送,或直接发送分析图片...';
|
|
1027
|
+
cmdInput.focus();
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1000
1030
|
// ── 输入处理 ──────────────────────────────────
|
|
1001
1031
|
window.sendCommand = sendCommand;
|
|
1002
1032
|
function sendCommand() {
|
|
1003
|
-
const text = cmdInput.value;
|
|
1004
|
-
if (!text) return;
|
|
1033
|
+
const text = cmdInput.value.trim();
|
|
1034
|
+
if (!text && !_pendingImage) return;
|
|
1005
1035
|
|
|
1006
1036
|
// 命名模式:发送的是新会话名称
|
|
1007
1037
|
if (window._renameMode) {
|
|
@@ -1017,33 +1047,30 @@
|
|
|
1017
1047
|
if (!isController) { addStatusMessage('请先获取控制权'); return; }
|
|
1018
1048
|
endStream();
|
|
1019
1049
|
|
|
1020
|
-
|
|
1021
|
-
const
|
|
1022
|
-
const imgPath = imgMatch ? imgMatch[1] : null;
|
|
1023
|
-
const userText = imgMatch ? text.slice(imgMatch[0].length).trim() : text;
|
|
1050
|
+
const imgPath = _pendingImage?.path || null;
|
|
1051
|
+
const userText = text;
|
|
1024
1052
|
const displayText = imgPath ? `📷 ${userText || '分析图片'}` : text;
|
|
1025
1053
|
|
|
1026
1054
|
addInputMessage(displayText);
|
|
1027
1055
|
|
|
1028
1056
|
if (currentSession?.mode === 'agent') {
|
|
1029
|
-
// Agent 模式:组合图片+文字
|
|
1030
1057
|
const prompt = imgPath
|
|
1031
1058
|
? `请直接用 Read 工具读取图片 ${imgPath} 并分析,不要搜索其他文件。${userText || '描述图片内容'}`
|
|
1032
1059
|
: text;
|
|
1033
1060
|
socket.emit('agent:query', { prompt });
|
|
1034
1061
|
showThinking();
|
|
1035
1062
|
} else {
|
|
1036
|
-
// PTY 模式:如果有图片,发送路径+用户文字
|
|
1037
1063
|
const input = imgPath
|
|
1038
1064
|
? (userText ? `${userText} ${imgPath}` : imgPath)
|
|
1039
1065
|
: text;
|
|
1040
1066
|
socket.emit('input', input + '\r');
|
|
1041
1067
|
}
|
|
1042
|
-
cmdHistory.unshift(text);
|
|
1068
|
+
cmdHistory.unshift(imgPath ? `[图片] ${userText}` : text);
|
|
1043
1069
|
if (cmdHistory.length > 100) cmdHistory.pop();
|
|
1044
1070
|
historyIdx = -1;
|
|
1045
1071
|
cmdInput.value = '';
|
|
1046
1072
|
cmdInput.style.height = 'auto';
|
|
1073
|
+
clearPendingImage();
|
|
1047
1074
|
cmdInput.focus();
|
|
1048
1075
|
}
|
|
1049
1076
|
|
|
@@ -1310,10 +1337,9 @@
|
|
|
1310
1337
|
const form = new FormData(); form.append('image', blob, 'photo.jpg');
|
|
1311
1338
|
const res = await fetch(`/upload?sessionId=${SESSION_ID}`, { method: 'POST', body: form }); const data = await res.json();
|
|
1312
1339
|
if (data.path) {
|
|
1313
|
-
//
|
|
1314
|
-
const
|
|
1315
|
-
|
|
1316
|
-
sendCommand();
|
|
1340
|
+
// 暂存图片,等用户输入文字后一起发送
|
|
1341
|
+
const thumbUrl = URL.createObjectURL(blob);
|
|
1342
|
+
setPendingImage(data.path, thumbUrl);
|
|
1317
1343
|
}
|
|
1318
1344
|
else addStatusMessage('上传失败: ' + (data.error || '未知'));
|
|
1319
1345
|
} catch (e) { addStatusMessage('出错: ' + e.message); }
|