claude-simple 0.1.0
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/README.md +225 -0
- package/client.js +139 -0
- package/dist/addon-fit.min.js +2 -0
- package/dist/app.0.1.0.min.js +825 -0
- package/dist/highlight.min.css +10 -0
- package/dist/highlight.min.js +1244 -0
- package/dist/index.html +16 -0
- package/dist/marked.min.js +6 -0
- package/dist/style.0.1.0.min.css +1050 -0
- package/dist/xterm.min.css +209 -0
- package/dist/xterm.min.js +2 -0
- package/gateway.js +349 -0
- package/package.json +41 -0
- package/public/addon-fit.min.js +2 -0
- package/public/agent-panel.js +1079 -0
- package/public/api.js +78 -0
- package/public/app.js +95 -0
- package/public/base.css +4 -0
- package/public/cache.js +27 -0
- package/public/chat.js +1087 -0
- package/public/files.js +476 -0
- package/public/git.js +130 -0
- package/public/index.html +61 -0
- package/public/lib.js +2 -0
- package/public/router.js +71 -0
- package/public/settings.js +90 -0
- package/public/shell/boot.js +159 -0
- package/public/shell/dashboard.js +269 -0
- package/public/shell/hub.js +192 -0
- package/public/shell/layout.js +195 -0
- package/public/shell/notes.js +323 -0
- package/public/shell/session-context.js +19 -0
- package/public/shell/session-list.js +442 -0
- package/public/shell/session-nav.js +299 -0
- package/public/shell/templates.js +342 -0
- package/public/shell/util.js +63 -0
- package/public/shell.js +589 -0
- package/public/state.js +362 -0
- package/public/style.css +1048 -0
- package/public/terminal.js +177 -0
- package/public/xterm.min.css +209 -0
- package/public/xterm.min.js +2 -0
- package/server.js +12 -0
- package/src/agents/claude.js +58 -0
- package/src/agents/codex.js +284 -0
- package/src/agents/grok.js +318 -0
- package/src/agents/meta-agent.js +1952 -0
- package/src/agents/models.js +339 -0
- package/src/agents/sessions.js +1508 -0
- package/src/app.js +1282 -0
package/README.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Claude Simple UI (multi-agent)
|
|
2
|
+
|
|
3
|
+
A lightweight web UI for **Claude Code**, **OpenAI Codex**, and **Grok**. No build step — pure ES Modules in the browser.
|
|
4
|
+
|
|
5
|
+
Inspired by [cc-connect](https://github.com/chenhg5/cc-connect) agent adapters: Claude via the Agent SDK, Codex via `codex exec --json`, Grok via `grok --output-format streaming-json`.
|
|
6
|
+
|
|
7
|
+
## Deployment modes
|
|
8
|
+
|
|
9
|
+
| Mode | When to use | Hub (中心端) needed? | How to run |
|
|
10
|
+
|------|-------------|----------------------|------------|
|
|
11
|
+
| **Standalone(本地 / 单机)** | 本机开发、只有一台机器 | **不需要** | `node server.js` |
|
|
12
|
+
| **Hub + Edge(多机)** | 多台机器、一个公网入口、统一 WebUI | **需要** | Hub: Go 二进制;每台业务机: `npm run client` |
|
|
13
|
+
| **Edge only** | 已有 Hub,本机只当业务节点 | 需要已有 Hub | `npm run client` |
|
|
14
|
+
|
|
15
|
+
**本地跑不需要中心端。** 单机默认就是 Standalone:UI + 后端 + agent 都在同一个 Node 进程里。
|
|
16
|
+
|
|
17
|
+
前端会探测 `GET /api/hub`:没有该接口即按单机模式工作,逻辑与多机无关。
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Standalone (default) Multi-machine (optional)
|
|
21
|
+
───────────────────── ────────────────────────
|
|
22
|
+
Browser → node server.js Browser → Go Hub (public/)
|
|
23
|
+
(UI + agents) ├─► client.js @ machine A
|
|
24
|
+
├─► client.js @ machine B
|
|
25
|
+
└─► client.js @ machine C
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start (local — no hub)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install
|
|
32
|
+
node server.js
|
|
33
|
+
# open http://localhost:3000
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or with auto-reload:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm run dev
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
First run creates an account. Token persists 7 days — no repeated login.
|
|
43
|
+
|
|
44
|
+
Requires CLIs on `PATH` for the agents you use:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Claude Code — install Claude Code CLI
|
|
48
|
+
# Codex
|
|
49
|
+
npm install -g @openai/codex
|
|
50
|
+
# Grok (xAI) — install grok CLI and run `grok login`
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Environment Variables (standalone / edge Node app)
|
|
54
|
+
|
|
55
|
+
| Variable | Default | Description |
|
|
56
|
+
| ---------------------- | --------------------------------- | ---------------------------------------- |
|
|
57
|
+
| `PORT` | `3000` | HTTP port (standalone) |
|
|
58
|
+
| `AGENTS` | `claude,codex,grok` | Enabled agents (comma-separated) |
|
|
59
|
+
| `CLAUDE_CONFIG_DIRS` | `~/.claude` | Claude Code config dirs, comma-separated |
|
|
60
|
+
| `CLAUDE_CLI_PATH` | `claude` | Path to Claude Code executable |
|
|
61
|
+
| `CODEX_CLI_PATH` | `codex` | Path to Codex CLI |
|
|
62
|
+
| `CODEX_HOME` | `~/.codex` | Codex home (sessions, config) |
|
|
63
|
+
| `GROK_CLI_PATH` | `grok` | Path to Grok CLI |
|
|
64
|
+
| `GROK_HOME` | `~/.grok` | Grok home (sessions) |
|
|
65
|
+
| `JWT_SECRET` | persisted in `.credentials.json` | Override JWT signing key |
|
|
66
|
+
| `AUTH_USERNAME` / `AUTH_PASSWORD` | — | Bootstrap first user from env |
|
|
67
|
+
|
|
68
|
+
Edge-only env (when connecting to a hub):
|
|
69
|
+
|
|
70
|
+
| Variable | Description |
|
|
71
|
+
| ---------------- | ------------------------------------------------ |
|
|
72
|
+
| `GATEWAY_URL` | Hub control WS, e.g. `wss://host/machine-connect` |
|
|
73
|
+
| `MACHINE_TOKEN`| Shared secret with hub |
|
|
74
|
+
| `MACHINE_ID` | Unique id for this host (default: hostname) |
|
|
75
|
+
| `LOCAL_PORT` | Local bind port (default `13000`, `127.0.0.1`) |
|
|
76
|
+
|
|
77
|
+
## Features
|
|
78
|
+
|
|
79
|
+
### Chat
|
|
80
|
+
|
|
81
|
+
- Multi-agent: pick **Claude / Codex / Grok** in options or New Session
|
|
82
|
+
- Slash commands: `/agent`, `/model`, `/effort`, `/clear`
|
|
83
|
+
- Stream responses via WebSocket
|
|
84
|
+
- Markdown rendering with syntax-highlighted code blocks
|
|
85
|
+
- Tool use cards (expandable), permission request banners
|
|
86
|
+
- Long messages auto-collapse (user: 200 chars, assistant: 500 chars)
|
|
87
|
+
- Token usage bar after each turn: `↑12.3k ↓2.1k · 8.5k cached · $0.023`
|
|
88
|
+
- Shell commands via `!cmd` prefix (runs in session cwd)
|
|
89
|
+
|
|
90
|
+
### Sessions & projects
|
|
91
|
+
|
|
92
|
+
- **Projects** view: group by working directory; show agent mix (CC/CX/GX), last activity title
|
|
93
|
+
- **Recent** timeline: chronological “what did I do” by day
|
|
94
|
+
- Filters: agent (All/Claude/Codex/Grok) + time range (Today / 7d / 14d / 30d / All) + **★ Fav**
|
|
95
|
+
- Search: metadata (path, title, session notes) + **deep content peek** via `GET /api/activity?q=…`
|
|
96
|
+
- Double-click a project header to open that cwd
|
|
97
|
+
- **Session conversion**: convert / clone sessions between Claude Code, Codex, and Grok
|
|
98
|
+
- **Project Goal + Notes**: one-line goal and multi-line notes per project (`.project_notes.json`)
|
|
99
|
+
- **Session favorites & notes**: star sessions; per-session notes (`.session_meta.json`)
|
|
100
|
+
- **Thin sessions**: ≤2 user turns show a `thin·N` badge
|
|
101
|
+
- Session history loads on click; sidebar is resizable
|
|
102
|
+
|
|
103
|
+
### Files preview
|
|
104
|
+
|
|
105
|
+
- Text / code: syntax highlighting (highlight.js)
|
|
106
|
+
- **docx / xlsx / pptx / pdf** preview (client-side libraries; binary over base64, ≤15MB)
|
|
107
|
+
|
|
108
|
+
### Workspaces
|
|
109
|
+
|
|
110
|
+
- Multiple Claude Code config dirs (different accounts, MCPs, settings) — Claude only
|
|
111
|
+
- Manage via ⚙️ → Settings: add name + path, delete
|
|
112
|
+
- Workspace tab bar appears when more than one workspace is configured
|
|
113
|
+
|
|
114
|
+
### Files / Git / Shell tabs
|
|
115
|
+
|
|
116
|
+
- **Files**: browse, upload, download, delete; custom root (`~` supported)
|
|
117
|
+
- **Git**: status, staged/unstaged diff, graph log; custom root
|
|
118
|
+
- **Shell**: interactive PTY via xterm.js + Python helper (`vim` / `htop` etc.)
|
|
119
|
+
|
|
120
|
+
### Meta Agent(内置 AI 助手)
|
|
121
|
+
|
|
122
|
+
Top bar **✦ Agent** — OpenAI-compatible tool agent (DeepSeek / OpenAI / local gateway…).
|
|
123
|
+
|
|
124
|
+
| Feature | Description |
|
|
125
|
+
|---------|-------------|
|
|
126
|
+
| **工作报告** | 手动生成今日 / 近 N 日「我做了啥」;可选整点自动日报 |
|
|
127
|
+
| **项目问答** | 搜会话、读摘要、项目 goal/notes、git status/log |
|
|
128
|
+
| **代码** | `write_file` + `run_command` 生成并运行(有危险命令拦截) |
|
|
129
|
+
| **VLM** | 粘贴/选图 → 上传为 `[imgN](路径)` 文本引用;需要时 `analyze_images`;`thread_id` 支持多轮追问 |
|
|
130
|
+
|
|
131
|
+
API Key 存在服务端 `.ai_config.json`(已 gitignore)。配置入口:Agent 面板 ⚙。
|
|
132
|
+
|
|
133
|
+
**Session 持久化(同一 sessionId;多图 = 多个 VLM thread 文件)**
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
.ai/sessions/ # 或 AI_DATA_DIR/sessions
|
|
137
|
+
index.json
|
|
138
|
+
{sessionId}/
|
|
139
|
+
meta.json
|
|
140
|
+
messages.jsonl # 对话(一行一条,含 tool)
|
|
141
|
+
vlm/
|
|
142
|
+
{threadId}.jsonl # 每个看图线程单独文件
|
|
143
|
+
# 多图/多次 analyze → 多个 threadId
|
|
144
|
+
# 文件内多行 = 该线程的多轮追问
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
删除会话会整目录删除。旧版 `session.json`、`vlm.jsonl`、`vlm/*.json`、全局 `.ai/vlm/` 会自动迁移。
|
|
148
|
+
|
|
149
|
+
Meta Agent 的实现入口:`src/agents/meta-agent.js`、`src/app.js`(`/api/ai/*`)与 `public/agent-panel.js`。
|
|
150
|
+
|
|
151
|
+
## Multi-machine (optional)
|
|
152
|
+
|
|
153
|
+
Only needed when sessions live on **several hosts** and you want **one public WebUI**.
|
|
154
|
+
|
|
155
|
+
For full hub/edge setup, env vars, and routing details see **[gateway/README.md](gateway/README.md)**.
|
|
156
|
+
|
|
157
|
+
### Quick overview
|
|
158
|
+
|
|
159
|
+
| Role | Tech | Responsibility |
|
|
160
|
+
|------|------|----------------|
|
|
161
|
+
| **Hub (中心端)** | **Go** binary `claude-gateway` + `public/` | Public UI, login, merge sessions, route by `machineId` |
|
|
162
|
+
| **Edge (业务机)** | **Node** `npm run client` | Agents, local sessions, files, git, shell on that machine |
|
|
163
|
+
| **Standalone** | **Node** `node server.js` | Everything on one machine — **no hub** |
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# --- Hub (public VPS) ---
|
|
167
|
+
cd gateway && make linux-amd64
|
|
168
|
+
MACHINE_TOKEN=shared-secret \
|
|
169
|
+
HUB_USERNAME=admin HUB_PASSWORD=login-pass \
|
|
170
|
+
PUBLIC_DIR=/opt/claude-simple/public \
|
|
171
|
+
GATEWAY_ADDR=0.0.0.0:8080 \
|
|
172
|
+
./dist/claude-gateway-linux-amd64
|
|
173
|
+
|
|
174
|
+
# --- Edge on machine A ---
|
|
175
|
+
MACHINE_TOKEN=shared-secret MACHINE_ID=laptop-a \
|
|
176
|
+
GATEWAY_URL=wss://ui.example.com/machine-connect npm run client
|
|
177
|
+
|
|
178
|
+
# --- Edge on machine B ---
|
|
179
|
+
MACHINE_TOKEN=shared-secret MACHINE_ID=build-b \
|
|
180
|
+
GATEWAY_URL=wss://ui.example.com/machine-connect npm run client
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
In hub mode the UI:
|
|
184
|
+
|
|
185
|
+
- **Remembers** last machine in `localStorage` (no picker on every refresh)
|
|
186
|
+
- Full-screen picker only when first time or last machine is offline
|
|
187
|
+
- Top bar **Machines** menu: online/offline list + switch anytime
|
|
188
|
+
- Session list is filtered to the selected machine
|
|
189
|
+
|
|
190
|
+
An edge may still run `node server.js` for a **local-only** UI on that host (independent of the hub).
|
|
191
|
+
|
|
192
|
+
### Why Node on edges? (and Go later)
|
|
193
|
+
|
|
194
|
+
- **Hub is Go** — binary deploy, no Node on the public VPS.
|
|
195
|
+
- **Edge stays Node for now** — Claude Agent SDK, session scanners, and streaming agents are implemented in the existing Node app.
|
|
196
|
+
- A full Go rewrite of the edge is **not planned short-term** (install convenience can later use a Go wrapper around the Node app if needed).
|
|
197
|
+
|
|
198
|
+
Legacy Node hub: `npm run gateway` → `gateway.js` (prefer the Go binary).
|
|
199
|
+
|
|
200
|
+
## Stack
|
|
201
|
+
|
|
202
|
+
- **App backend (standalone / edge)**: Node.js + Express + `ws` + `@anthropic-ai/claude-agent-sdk` + Python PTY helper
|
|
203
|
+
- **Hub (optional multi-machine)**: Go (`gateway/`) — static UI, auth, fan-in sessions, HTTP/WS relay
|
|
204
|
+
- **Frontend**: ES modules + [mini-react](https://github.com/forechoandlook/mini-react) + DaisyUI + Tailwind + xterm.js
|
|
205
|
+
- **Storage**: IndexedDB (session cache) + JSON files (credentials, workspaces, project notes, session meta)
|
|
206
|
+
- **No build step** (dev): browser loads `public/*.js` directly; optional `npm run build` for `dist/`
|
|
207
|
+
|
|
208
|
+
## Agent backends
|
|
209
|
+
|
|
210
|
+
| Agent | How it runs | Resume | Session store |
|
|
211
|
+
| ------ | ----------------------------------------------------- | ----------------- | ---------------------------------------- |
|
|
212
|
+
| Claude | `@anthropic-ai/claude-agent-sdk` → Claude Code CLI | SDK `resume` | `~/.claude/projects/**/*.jsonl` |
|
|
213
|
+
| Codex | `codex exec --json` / `codex exec resume <id>` | thread_id | `~/.codex/sessions/**/rollout-*.jsonl` |
|
|
214
|
+
| Grok | `grok -p … --output-format streaming-json` | `--resume <id>` | `~/.grok/sessions/<cwd>/<id>/` |
|
|
215
|
+
|
|
216
|
+
## Scripts
|
|
217
|
+
|
|
218
|
+
| Script | Description |
|
|
219
|
+
|--------|-------------|
|
|
220
|
+
| `npm run dev` | Standalone with `--watch` |
|
|
221
|
+
| `npm start` / `npm run serve` | Production standalone |
|
|
222
|
+
| `npm run build` | Bundle frontend into `dist/` |
|
|
223
|
+
| `npm run client` | Edge worker (requires hub) |
|
|
224
|
+
| `npm run gateway` | Legacy Node hub |
|
|
225
|
+
| `npm run gateway:build` | Build Go hub via `gateway/Makefile` |
|
package/client.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createApp, init } from './src/app.js';
|
|
3
|
+
import { WebSocket } from 'ws';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import crypto from 'crypto';
|
|
6
|
+
|
|
7
|
+
const GATEWAY_URL = process.env.GATEWAY_URL; // e.g. wss://your-server.com/machine-connect
|
|
8
|
+
const MACHINE_ID = process.env.MACHINE_ID || os.hostname();
|
|
9
|
+
const MACHINE_TOKEN = process.env.MACHINE_TOKEN; // shared secret with gateway
|
|
10
|
+
const LOCAL_PORT = parseInt(process.env.LOCAL_PORT || '13000');
|
|
11
|
+
|
|
12
|
+
if (!GATEWAY_URL) { console.error('GATEWAY_URL is required'); process.exit(1); }
|
|
13
|
+
if (!MACHINE_TOKEN) { console.error('MACHINE_TOKEN is required'); process.exit(1); }
|
|
14
|
+
|
|
15
|
+
// ─── Start local app (only binds to 127.0.0.1) ───────────────────────────────
|
|
16
|
+
await init();
|
|
17
|
+
const { server } = createApp();
|
|
18
|
+
await new Promise(resolve => server.listen(LOCAL_PORT, '127.0.0.1', resolve));
|
|
19
|
+
console.log(`[client] Local app listening on 127.0.0.1:${LOCAL_PORT}`);
|
|
20
|
+
|
|
21
|
+
// ─── Active WS tunnels: tunnelId → local WebSocket ───────────────────────────
|
|
22
|
+
const tunnels = new Map();
|
|
23
|
+
|
|
24
|
+
// ─── Gateway connection ───────────────────────────────────────────────────────
|
|
25
|
+
let controlWs = null;
|
|
26
|
+
let reconnectDelay = 1000;
|
|
27
|
+
|
|
28
|
+
function sendControl(msg) {
|
|
29
|
+
if (controlWs?.readyState === WebSocket.OPEN) {
|
|
30
|
+
controlWs.send(JSON.stringify(msg));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function connect() {
|
|
35
|
+
console.log(`[client] Connecting to gateway ${GATEWAY_URL} ...`);
|
|
36
|
+
controlWs = new WebSocket(GATEWAY_URL, {
|
|
37
|
+
headers: { 'x-machine-token': MACHINE_TOKEN },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
controlWs.on('open', () => {
|
|
41
|
+
reconnectDelay = 1000;
|
|
42
|
+
console.log(`[client] Connected. Registering as "${MACHINE_ID}"`);
|
|
43
|
+
sendControl({
|
|
44
|
+
type: 'register',
|
|
45
|
+
machineId: MACHINE_ID,
|
|
46
|
+
meta: { hostname: os.hostname(), platform: os.platform() },
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
controlWs.on('message', async (raw) => {
|
|
51
|
+
let msg;
|
|
52
|
+
try { msg = JSON.parse(raw.toString()); } catch { return; }
|
|
53
|
+
|
|
54
|
+
// ── HTTP proxy ────────────────────────────────────────────────────────────
|
|
55
|
+
if (msg.type === 'http-req') {
|
|
56
|
+
try {
|
|
57
|
+
const url = `http://127.0.0.1:${LOCAL_PORT}${msg.path}`;
|
|
58
|
+
const res = await fetch(url, {
|
|
59
|
+
method: msg.method,
|
|
60
|
+
headers: { ...msg.headers, host: `127.0.0.1:${LOCAL_PORT}` },
|
|
61
|
+
body: msg.body || undefined,
|
|
62
|
+
});
|
|
63
|
+
const body = await res.text();
|
|
64
|
+
sendControl({
|
|
65
|
+
type: 'http-res',
|
|
66
|
+
reqId: msg.reqId,
|
|
67
|
+
status: res.status,
|
|
68
|
+
headers: Object.fromEntries(res.headers.entries()),
|
|
69
|
+
body,
|
|
70
|
+
});
|
|
71
|
+
} catch (e) {
|
|
72
|
+
sendControl({ type: 'http-res', reqId: msg.reqId, status: 502, headers: {}, body: e.message });
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── WS tunnel open ────────────────────────────────────────────────────────
|
|
78
|
+
if (msg.type === 'ws-open') {
|
|
79
|
+
const { tunnelId, path, query } = msg;
|
|
80
|
+
const localUrl = `ws://127.0.0.1:${LOCAL_PORT}${path}${query || ''}`;
|
|
81
|
+
const localWs = new WebSocket(localUrl);
|
|
82
|
+
|
|
83
|
+
localWs.on('open', () => {
|
|
84
|
+
tunnels.set(tunnelId, localWs);
|
|
85
|
+
sendControl({ type: 'ws-ready', tunnelId });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
localWs.on('message', (data) => {
|
|
89
|
+
sendControl({ type: 'ws-msg', tunnelId, data: data.toString() });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
localWs.on('close', () => {
|
|
93
|
+
tunnels.delete(tunnelId);
|
|
94
|
+
sendControl({ type: 'ws-close', tunnelId });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
localWs.on('error', (e) => {
|
|
98
|
+
tunnels.delete(tunnelId);
|
|
99
|
+
sendControl({ type: 'ws-error', tunnelId, message: e.message });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── WS tunnel message (gateway → local) ───────────────────────────────────
|
|
106
|
+
if (msg.type === 'ws-msg') {
|
|
107
|
+
const localWs = tunnels.get(msg.tunnelId);
|
|
108
|
+
if (localWs?.readyState === WebSocket.OPEN) localWs.send(msg.data);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── WS tunnel close (gateway side closed) ─────────────────────────────────
|
|
113
|
+
if (msg.type === 'ws-close') {
|
|
114
|
+
const localWs = tunnels.get(msg.tunnelId);
|
|
115
|
+
tunnels.delete(msg.tunnelId);
|
|
116
|
+
if (localWs?.readyState === WebSocket.OPEN) localWs.close();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (msg.type === 'ping') {
|
|
121
|
+
sendControl({ type: 'pong' });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
controlWs.on('close', () => {
|
|
126
|
+
console.log(`[client] Disconnected. Reconnecting in ${reconnectDelay}ms ...`);
|
|
127
|
+
// Close all open tunnels
|
|
128
|
+
for (const localWs of tunnels.values()) {
|
|
129
|
+
try { localWs.close(); } catch {}
|
|
130
|
+
}
|
|
131
|
+
tunnels.clear();
|
|
132
|
+
setTimeout(connect, reconnectDelay);
|
|
133
|
+
reconnectDelay = Math.min(reconnectDelay * 2, 30_000);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
controlWs.on('error', e => console.error('[client] WS error:', e.message));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
connect();
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(self,(()=>(()=>{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core,t=e._renderService.dimensions;if(0===t.css.cell.width||0===t.css.cell.height)return;const r=0===this._terminal.options.scrollback?0:e.viewport.scrollBarWidth,i=window.getComputedStyle(this._terminal.element.parentElement),o=parseInt(i.getPropertyValue("height")),s=Math.max(0,parseInt(i.getPropertyValue("width"))),n=window.getComputedStyle(this._terminal.element),l=o-(parseInt(n.getPropertyValue("padding-top"))+parseInt(n.getPropertyValue("padding-bottom"))),a=s-(parseInt(n.getPropertyValue("padding-right"))+parseInt(n.getPropertyValue("padding-left")))-r;return{cols:Math.max(2,Math.floor(a/t.css.cell.width)),rows:Math.max(1,Math.floor(l/t.css.cell.height))}}}})(),e})()));
|
|
2
|
+
//# sourceMappingURL=xterm-addon-fit.js.map
|