基于官方 nousresearch/hermes-agent 镜像的 CLI、Docker Compose 与 Web 管理界面,支持多实例部署;敏感配置与实例数据由 .gitignore 排除。 Co-authored-by: Cursor <cursoragent@cursor.com>
69 lines
2.9 KiB
HTML
69 lines
2.9 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}实例 {{ iid }} · Hermes 引导 · Hermes Agent Web{% endblock %}
|
|
{% block body %}
|
|
<p><a href="{{ url_for('index') }}">← 返回实例列表</a></p>
|
|
<h2 style="margin-top:0;">实例 <code>{{ iid }}</code> · 引导终端</h2>
|
|
<p class="muted">打开本页后会<strong>自动</strong>在后台执行官方引导链路(未完成 setup 时先跑向导,再启动网关,再进入 <code>onboard</code>)。
|
|
你一般<strong>不用输入命令</strong>,只需在终端里按程序提问操作;需要改配置可用「编辑配置」。</p>
|
|
<div id="term-wrap" style="height:min(75vh,620px);border:1px solid var(--border);border-radius:8px;padding:2px;background:#0c1017;"></div>
|
|
<p class="muted" style="margin-top:0.75rem;">关闭本页请先 <kbd>Ctrl+C</kbd> 结束当前命令。需联网加载 xterm(CDN)。</p>
|
|
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.min.css" crossorigin="anonymous" />
|
|
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js" crossorigin="anonymous"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.11.0/lib/addon-fit.min.js" crossorigin="anonymous"></script>
|
|
<script>
|
|
(function () {
|
|
var wrap = document.getElementById('term-wrap');
|
|
var term = new Terminal({
|
|
cursorBlink: true,
|
|
fontSize: 14,
|
|
fontFamily: 'ui-monospace, SF Mono, Menlo, Consolas, monospace',
|
|
theme: { background: '#0c1017', foreground: '#e7edf4' }
|
|
});
|
|
var FitCtor = (typeof FitAddon !== 'undefined' && FitAddon.FitAddon) ? FitAddon.FitAddon : FitAddon;
|
|
var fit = new FitCtor();
|
|
term.loadAddon(fit);
|
|
term.open(wrap);
|
|
fit.fit();
|
|
term.writeln('\x1b[90m正在连接引导终端…\x1b[0m');
|
|
|
|
var proto = (location.protocol === 'https:') ? 'wss:' : 'ws:';
|
|
var tok = {{ ws_token | tojson }};
|
|
var ws = new WebSocket(proto + '//' + location.host + '/ws/instance/{{ iid }}/pty?token=' + encodeURIComponent(tok));
|
|
ws.onopen = function () {
|
|
term.reset();
|
|
fit.fit();
|
|
sendResize();
|
|
};
|
|
ws.onmessage = function (ev) {
|
|
var d = ev.data;
|
|
if (typeof d === 'string') term.write(d);
|
|
else term.write(new Uint8Array(d));
|
|
};
|
|
ws.onerror = function () {
|
|
term.write('\r\n\x1b[31m[WebSocket 错误]\x1b[0m\r\n');
|
|
};
|
|
ws.onclose = function (ev) {
|
|
term.write('\r\n\x1b[33m[连接已关闭' + (ev && ev.code ? ' code=' + ev.code : '') + ']\x1b[0m\r\n');
|
|
};
|
|
term.onData(function (data) {
|
|
if (ws.readyState === WebSocket.OPEN) ws.send(data);
|
|
});
|
|
|
|
function sendResize() {
|
|
if (ws.readyState !== WebSocket.OPEN) return;
|
|
try {
|
|
fit.fit();
|
|
ws.send(JSON.stringify({ t: 'resize', cols: term.cols, rows: term.rows }));
|
|
} catch (e) {}
|
|
}
|
|
window.addEventListener('resize', function () {
|
|
fit.fit();
|
|
sendResize();
|
|
});
|
|
new ResizeObserver(sendResize).observe(wrap);
|
|
setTimeout(sendResize, 100);
|
|
})();
|
|
</script>
|
|
{% endblock %}
|