39 Commits
Author SHA1 Message Date
weidu12123 3ea4f86d8b Update README.md 2026-10-19 15:25:57 +08:00
weidu12123 19a8aa3c36 docs: highlight Rust + GPUI native architecture in README 2026-10-19 00:43:41 +08:00
weidu12123 9d35744dd0 feat(gpui): add local proxy routing and polish terminal, file browser, and UI interactions 2026-10-19 00:26:34 +08:00
weidu12123 e89fd63001 docs: document the macOS downloads in the README 2026-10-18 12:24:58 +08:00
weidu12123 3d1b009bc9 ci: fix pnpm workspace file and pin pnpm 11 for the release matrix 2026-10-18 12:17:00 +08:00
weidu12123 a3e79b97b6 feat: ship GPUI native client and fix SFTP session recovery
- Rebuild the GPUI client against the 1:1 UI spec with all six modules,
  terminal context menu, SSE reveal, system tray and standalone windows.
- Add an SFTP session self-healing layer: detect dead channels, drop the
  cached session, reconnect and retry across every file and transfer path.
- Add a terminal refresh button that recreates the pane in place, with
  proper loading and error feedback in both GPUI and the Web console.
- Route startup and autostart through vps-switch-gpui when it is present,
  and make repeated launches wake the existing window via a file lock.
- Strip debuginfo in release, add a GitHub Actions matrix workflow for
  macOS and Linux, and refresh the Windows installers and README.
2026-10-18 12:09:49 +08:00
weidu12123 9ce782e6bb docs: add the build guide for the GPUI client
Pins the order (docs, then dependencies, then one board at a time with
the front end rebuilt before moving on), lists every old front-end
capability with its GPUI replacement, the dependencies still missing,
the assets that must be ported verbatim (OS icons, brand logo), and the
four things GPUI cannot reproduce.
2026-10-17 18:29:59 +08:00
weidu12123 aa0a05d352 feat(gpui): add the filter menu at measured sizes
The popover matches the old computed styles: 256px wide, white/95,
16px radius, 14px padding, xl shadow, a three-way status grid with
counts and dots, the amber favourite chip, role chips and dynamic tag
chips. The theme now names Segoe UI explicitly so text metrics stay
stable.
2026-10-17 18:21:10 +08:00
weidu12123 b4f1b05034 chore: keep the reference screenshot out of the repo
It shows real host names and addresses; artifacts/ is ignored.
2026-10-17 18:10:03 +08:00
weidu12123andClaude Fable 5 69e04889f6 fix(gpui): align the header and host cards to measured styles
The old front end is still served by the daemon, so its computed styles
are the source of truth: header is 50px with a white/75 ground and a
zinc-200/75 bottom border; buttons are 29px tall, the repository button
is 12px-rounded while search/filter/check are pills; round controls are
32px on white/85. Cards have no fill by default (bg-white/82 is not a
valid Tailwind class, so the old cards render transparent), hover to
95% white; borders zinc-200/80, 16px radius, 14/16 padding, 1px 4px 2%
shadow, status dots carry an 8px glow, and per-card buttons are
25px/28px.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-17 18:09:59 +08:00
weidu12123andClaude Fable 5 8bed005d85 feat(gpui): rebuild the client against the 1:1 UI spec
The workspace now mirrors the old front end: a top bar with the brand,
repository button and session tabs, search, filter summary, check-all
and theme/settings/add controls; a host card list with OS badges,
status dots, role and pin chips plus per-card actions; a terminal stage
with split, light/dark terminal, right-wing and popout controls; and
the right-side probe drawer with nameplate, CPU, memory, disk, network
and role/tag cards, sampled every 2.8s. The old DockArea scaffold is
gone; layout and colours follow docs/ui-spec.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-17 17:59:00 +08:00
weidu12123 841a362c8f docs: extract the old frontend into a 1:1 UI spec
Read App.vue end to end plus the terminal stage, pane view, detail
drawer and OS badge. The spec pins the design language, page state
machine, top bar, host cards, terminal stage, right drawer, settings
pages, data APIs and the GPUI mapping — the GPUI client is rebuilt
against it instead of approximating the look.
2026-10-17 17:52:44 +08:00
weidu12123andClaude Fable 5 582c4f4958 feat(gpui): render real terminals from the daemon's byte stream
The client creates a pane over HTTP, attaches to the terminal
WebSocket, and re-parses the byte stream with the same
alacritty_terminal release the daemon uses. Rendering splits each row
into ASCII runs shaped with a per-cell force width and wide CJK glyphs
shaped individually at their column, because force_width snaps every
glyph to one cell and would overlap Chinese text.

Verified against the live daemon: the local PowerShell prompt and
vip-hk's full MOTD parse correctly (examples/terminal_smoke.rs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-17 17:48:03 +08:00
weidu12123andClaude Fable 5 5299ff513f feat(gpui): open terminal tabs from the host list, with headless UI tests
Double-clicking a host (or clicking an already selected one) opens a
terminal tab in the center dock. Three headless tests drive the real
panel with real pointer events. They use gpui-base's test_support
because gpui-kit's test-support feature drags in the gpui-fast
dependency tree, which conflicts with gpui-pre 0.3.9 on Windows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-17 17:29:31 +08:00
weidu12123andClaude Fable 5 73d1735f87 feat(gpui): scaffold the native client talking to the daemon
The workspace now builds two products from one repo: the existing
daemon crate and a new vps-switch-gpui crate that renders the daemon's
HTTP API. The client keeps all networking on a dedicated current-thread
tokio runtime and drains events on the UI thread, so GPUI's executor is
never touched from other threads.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-17 17:18:50 +08:00
weidu12123andClaude Fable 5 ff160f41ca docs: settle the GPUI client plan and the development baseline
The plan pins gpui-kit 0.7.3 (locked to gpui-pre 0.3.9) as the client
stack, keeps the backend untouched behind the existing HTTP/WS API, and
ships the client for Windows, macOS and Linux. The dev baseline records
the verified versions, the knowledge sources that outrank memory, the
entry-point skeleton, and the platform facts the next sessions must not
relitigate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-17 17:02:46 +08:00
weidu12123andClaude Opus 4.8 a868ce9a57 fix(terminal): rebuild the scene on history replay instead of appending it
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-17 02:22:35 +08:00
weidu12123andClaude Opus 4.8 21e2f6e13d docs: state the desktop memory numbers without overselling them
The table listed "never opened the window" next to "closed to tray" without
saying what separates them, and the closing line claimed the tray state was
where the app spends most of its time -- implying the 5 MB figure applies
there. It does not.

The distinction is whether the WebView was ever created, not whether the window
is currently visible: hiding only drops it to WebView2's low memory level, so
after the first time the window is opened, closing to tray leaves ~180 MB
resident until the process restarts. Only an autostart instance that was never
opened sits at 5 MB.

Also note the cost of going further (destroying the WebView on hide): a reload
on reopen, and the open tabs are lost today because the frontend does not
persist its layout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-16 22:53:46 +08:00
weidu12123andClaude Opus 4.8 4e1aebf26e fix: repair the issues found in the pre-release review
Backend:

- Downloading a file from the local host through the transfer engine always
  failed: the download branch had no LOCAL_HOST_ID case, so it opened SFTP
  against 127.0.0.1:0 (reproduced: "SSH 连接失败: 地址无效 (os error 10049)").
  The file browser's own download button goes through that path, so it was
  broken in the UI too. The local branch now reuses stream_upload_to_host,
  which already had one, keeping the temp-file/resume/SHA-256 guarantees.
- remove_item only refused the literal "/", so a Windows drive root listed by
  the file browser ("此电脑" returns "C:/") could be deleted recursively. New
  is_root_like() also covers drive roots, "." and "..", shared with
  compress_item, plus unit tests.
- dir_cache grew without bound; capped at 256 entries, oldest evicted first.
- ExecTaskManager capped task count but not buffered bytes: 60 running tasks
  could hold 120 MiB. Added a 32 MiB budget that evicts finished tasks only.
- Web sessions were only ever inserted, never expired, and every auth-settings
  save minted another one. Now a token->issued map with a 30-day TTL, pruned on
  every issue.

Frontend:

- A failed pane creation left the stage spinning "正在连接" forever (no retry,
  and the standalone window has no tab bar to escape through). The failure is
  now kept on the tab and rendered as an error with a retry button.
- The transfer-queue poller only cleared its 350 ms interval on success, so a
  401 or a backend restart left it polling ~3 req/s indefinitely.
- The MCP call detail fetch had no sequence guard, so clicking two rows quickly
  showed the wrong call's args and result.
- Only three call sites handled 401. Since the session table lives in memory,
  every daemon restart invalidates all sessions, so with auth enabled this is
  routine rather than an edge case. All protected requests now go through
  apiFetch and return to the login screen.
- check-update read data.message, which the backend never returns (it returns
  error), so failures only ever showed a bare "检查更新失败".

Docs: corrected the Claude Code setup commands (missing --transport http, and
x-vps-switch-key is not a header the server reads), fixed the Setup.exe PATH
claim, added measured memory numbers for both platforms, and added a
build-from-source section -- a clean clone cannot compile, since frontend/dist
is ignored while RustEmbed treats its absence as a hard error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-16 22:34:52 +08:00
weidu12123andClaude Opus 4.8 7e14188793 perf(desktop): keep the WebView2 tree out of the tray-resident state
The desktop shell used to build the WebView at startup unconditionally, so a
minimized (autostart) instance pulled up the whole Chromium tree and sat in the
tray holding ~189 MB of committed memory while nobody was looking at it. Two
changes, neither of which touches the headless or MCP paths:

- Skip creating the WebView when starting minimized; build it on first show.
  Measured: 0 msedgewebview2 processes while resident, backend ~5 MB.
- Drop the WebView to WebView2's low memory target level
  (ICoreWebView2_19::SetMemoryUsageTargetLevel) when hiding to the tray, and
  restore it before showing again. Measured ~10-20% off committed memory,
  and unlike tearing the WebView down it keeps the page alive, so open tabs
  and terminal panes survive.

The native controller comes from wry's WebViewExtWindows trait, so no fork of
wry is involved. Runtimes without ICoreWebView2_19 are handled by the cast
failing, which is a silent no-op.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-16 22:08:49 +08:00
weidu12123 fa56ce6ced docs: add multi-OS and heterogeneous environment support section 2026-10-16 13:35:37 +08:00
weidu12123andClaude Opus 4.8 c050677760 docs: rewrite README with feature showcases, screenshots, and setup instructions
- State clear project definition as a lightweight VPS manager and MCP server.
- Add showcase sections for repository, terminal, SFTP/transfer, probe, and MCP audit.
- Include desensitized screenshots in docs/images/.
- Provide download links, Linux deployment commands, and Claude Code setup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-16 12:47:11 +08:00
weidu12123andClaude Opus 4.8 67031b31b1 chore(release): prepare v0.1.0 release packaging and clean sensitive data
- Scrub hardcoded hostnames from MCP descriptions, UI placeholders,
  and documentation examples to prevent environment leakage.
- Remove hardcoded development database fallback from src/db.rs.
- Untrack .cargo/config.toml from version control and ignore local mirrors.
- Ignore test artifacts, screenshots, and WiX temporary outputs in .gitignore.
- Add Inno Setup script (installer.iss) for Windows Setup.exe wizard installer.
- Add WiX configuration (wix/main.wxs) for Windows MSI installer.
- Add systemd service template (vps-switch.service) for Linux deployments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-16 10:39:35 +08:00
weidu12123andClaude Opus 4.8 21ac1dd4c5 feat: move MCP call log into its own settings section with full detail and filters
The call log was a bare table crammed into the MCP Server settings tab: no
way to read what an agent actually sent or got back, and no way to narrow
it down.

- mcp_calls gains a `result` column holding the full text returned to the
  agent (previously only a one-line summary survived). Args and result are
  each capped at 256 KiB, and hitting the cap writes the truncation notice
  into the body itself instead of hiding it behind a flag. Existing
  databases pick the column up via ALTER TABLE; old rows stay empty.
- GET /api/mcp-calls now filters by host, tool, ok, and a closed-open
  time range. Host matching expands comma-joined batch targets, so
  `vip-hk` finds "vip-hk,gateway,..." while `hk` still matches nothing.
- The list returns a 400-char args preview only; a new
  GET /api/mcp-calls/:id serves one record's full args and result, so a
  500-row view cannot pull hundreds of megabytes into the browser.
- Settings gets a fourth category, "调用记录": date range (local-day
  boundaries converted to UTC instants), host/tool/result selects, reset,
  and rows that expand into the complete args and result with copy
  buttons.

Verified on the running build against 686 existing records: host, tool,
result, date and combined filters all narrow correctly, and a 300 KB
output shows the in-body truncation note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-16 08:02:36 +08:00
weidu12123 5645b08f22 fix: repair the 10 issues found in the full-fleet test
- exec cancel: send an SSH signal and confirm the process group stopped before reporting cancelled
- terminal: reject pane_id whose host does not match the target host (read/send/key/close/reveal)
- auth: enable login by default on Linux/public bind; keep an explicit user choice
- terminal: build the send baseline from the actual PTY write, not from injection output
- exec: mark stdout/stderr explicitly when output exceeds the 1 MiB cap
- web download: deliver to the browser through the transfer engine and clean up staging
- mcp: unfinished tasks report ok=false; batch inspect fails when any item fails
- local: initialize OS metadata per platform and repair legacy Windows metadata on Linux
- cli config: keep PORT and DATA_DIR in the generated Claude/Codex stdio config
- update check: return 502/ok=false with real stdout/stderr/exitCode on failure
- frontend: add vue-tsc typecheck and fix the standardSQL import
2026-10-16 00:58:31 +08:00
weidu12123andClaude Fable 5 b836cb0b53 fix: stop MCP exec poll loop and add MCP call audit; Linux/VPS deploy support
- vps_exec 超时不再诱导轮询(去掉"请使用 vps_task 查询"的 hint),未完成一律 ok=false,
  终态清空过期 hint;vps_task(get) 增加 wait_seconds 阻塞等待,一次拿到最终结果
- 新增 MCP 调用全程留痕:mcp_calls 表 + GET /api/mcp-calls + 设置页「MCP 调用记录」板块
- Linux/VPS:本机执行走 sh、探针 inspect_local_linux、终端用 $SHELL、压缩解压用 tar;
  数据目录支持 DATA_DIR;非 Windows / HOST=0.0.0.0 默认开启登录密码
- 设置页去 1Shell 化文案;winres 归入 windows build-deps;内嵌应用图标(build.rs + assets/app.ico)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-15 16:38:10 +08:00
weidu12123 8a3d79f92a fix: pin data dir to %APPDATA%, stop resolving by cwd
resolve_data_dir used to prefer cwd/data then exe-sibling/data then %APPDATA%, so the same exe run from different cwds silently used different databases with different secret keys — which broke the stdio bridge handshake via an API-key mismatch. Data now always lives in %APPDATA%\vps-switch\data, independent of cwd and exe location.
2026-10-15 02:14:01 +08:00
weidu12123 3176ffdc4c fix: MCP bridge fallback, loopback-exempt API key, terminal reconnect, and probe status
- stdio bridge now falls back to in-process handling unless the forward returns a
  real JSON-RPC response. A 401 from an API-key / data-dir mismatch used to be
  printed verbatim as the tool result, so clients never completed the handshake
- MCP API key is enforced only when bound to a non-loopback address; on 127.0.0.1
  it is exempt (local-first, like 1Shell). The remote toggle still governs HOST=0.0.0.0
- vps_terminal "key" action now passes through the command denylist; its fallback
  branch could previously carry arbitrary text and bypass the guard
- vps_exec interactive-prompt early-exit now matches only explicit keywords, so
  ordinary commands whose last line ends in ':' / '?' are no longer aborted mid-run
- terminal: add a "重新连接" button that recreates a pane in place after the session ends
- file browser: downloads go through the resumable + SHA-256 transfer engine
  (default local path = Downloads, runs in background, non-blocking)
- standalone terminal window: hide the dead 文件管理 / 详情图表 buttons
- probe status: an SSH connection means online (probe failure no longer flips to
  red); a connect timeout maps to unknown instead of offline; latency now measures
  the connect only, not the probe
- transfer: a resumed download fails instead of silently accepting when the remote
  hash is unavailable; secret.key is no longer overwritten when corrupt; a per-host
  lock serializes pane creation
2026-10-15 01:47:00 +08:00
weidu12123 fbf2ac2231 feat: CodeMirror editor, transfer resume, OS auto-detect, and pane-isolated terminal
- CodeMirror 6 online editor (lazy-loaded chunk) with syntax highlighting,
  search/replace, Ctrl+S save over SFTP, dark/light glass themes
- Streamed transfer resume: keep <target>.vps-part fragment on interruption,
  reconcile source hash on re-issue and continue from fragment end
- Probe self-heals os_info: try recorded OS, fall back to the other family,
  write back the real OS/version/arch (fixes hand-added hosts)
- Terminal uses its own SSH connection per host (released when last pane
  closes) so the login MOTD is not eaten by the shared status-check session
- Shell-integration injection narrowed to headless MCP panes only; remote
  echo off via pty-req ECHO=0 so nothing is drawn (no more line erasing)
- Frontend: roll back optimistic toggles and surface failures on autostart,
  MCP settings, host delete/patch, and CLI sync instead of faking success
2026-10-14 23:16:05 +08:00
weidu12123andClaude Opus 4.8 6c51d7a76f feat: remote shell integration, transfer SHA-256 closure, and channel reclamation
收尾三项加固(对照 OxideTerm 后拍板),并在实测中揪出并修掉一个更严重的老 bug。

- 远端 OSC 133 静默注入:连上后探测 shell(bash/zsh/fish/ash)零安装注入钩子,
  获取真实命令边界与退出码、实时 cwd;Windows 主机自动跳过。
  擦除注入痕迹用绝对定位(CUP)逐行寻址——readline 回车会自己补 \r\n,
  相对上移推算必然差一行;擦后从屏幕网格自校验,未净则重算重试一次。
  等待条件全部带 output_seq 护栏,杜绝"上一阶段提示符误判已完成"的竞态。
- 传输 SHA-256 闭环:上传/下载增量哈希 + 落地重读对账(Linux sha256sum /
  Windows Get-FileHash),字节数或哈希不符直接判任务失败;
  TransferTask 增加 sha256/verified 字段。
- channel 回收:exec 超时后主动 close(Drop 不发 CHANNEL_CLOSE,服务端会悬挂);
  压缩/解压加 600 秒超时 + close;每主机 6 路并发执行闸,防并行调用打满 MaxSessions。
- 修复 exit-status 丢失老 bug:OpenSSH 的 exit-status 常跟在 EOF 之后到达,
  读取循环原来在 Eof 就 break——同一连接上第 2 条起的 exec 全部丢退出码
  (旧构建靠"谎报成功"掩盖,改如实报 failed 后才暴露)。改为读到 Close 才收尾。
  实测:连续 exec exit 1/2/3/4/7 全部精确、单机 8 路并行与 6 台并发全部 exit 0。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-14 13:59:13 +08:00
weidu12123andClaude Opus 4.8 7bf552952f feat: add command blacklist guardrail for MCP command channels
MCP 工具一旦被外部 agent 放行,参数里的命令就不再受它自身权限系统检查
(Claude Code 的 MCP 权限规则只按工具名匹配、不带参数内容),
vps_exec 里的 rm -rf / 在 agent 侧零提示。这是唯一的兜底。

- src/guard.rs:名单判定。归一化(剥引号、\/ 还原、折叠空白、转小写、
  短选项聚簇排序,故 -rf/-fr/-r -f 等价)+ * 通配 + 词边界匹配 + 截断标记检查
- data/deny-list.txt:首启写出内置 25 条不可逆灾难,用户可增删,每次判定重读
- 挂在 vps_exec 与 vps_terminal 的 send 上,命中即拒绝执行、不产生输出
- 工具描述写明护栏存在,并告诉 agent 被拦时不要换写法重试
- GET/PUT /api/guard + 设置页 MCP 标签下的名单编辑区
- 没有白名单、确认流程、风险分级与安全挡位

边界:防手滑,不防蓄意绕过(r\m -rf /、$(echo rm) -rf / 这类纯模式匹配必然漏)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 23:55:31 +08:00
weidu12123andClaude Fable 5 14e7066db9 fix: close pre-release security holes and correctness bugs
- 静态资源回退存在路径穿越,未鉴权即可读出 secret.key 与 vps-switch.db
- MCP 端点默认要求 API Key;删除可被客户端伪造的 x-vps-switch-stdio 豁免头
- 跨源只放行本机回环来源;绑回环时校验 Host,挡 DNS rebinding
- 本机命令输出中文乱码(本机与远端共用 EncodedCommand + UTF-8)
- 远端拿不到退出码时不再报成功;cross 传输超时不再谎报"后台传输中"
- 任务表与单流输出加上限;传输队列双锁死锁改单锁;回收已退出的终端 pane
- 前端终端创建改为按主机去重;终端断线自动重连并给出提示

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-13 22:12:39 +08:00
weidu12123 6fdd134058 feat: complete Windows remote command execution and PowerShell inspect support 2026-10-13 20:34:29 +08:00
weidu12123 3eb4c9ce85 fix(inspector): use %.0f in awk printf to prevent 32-bit mawk disk/mem integer overflow 2026-10-13 19:26:11 +08:00
weidu12123 3710e0926b fix: toggle settings page back to workspace and suppress local inspect powershell window popup 2026-10-13 19:03:46 +08:00
weidu12123 ac92ac981a feat: adopt monochrome topological lock brand icon (Option D) 2026-10-13 18:08:02 +08:00
weidu12123 ace7e83f58 feat: redesign settings with tabs, autostart registry, background mode, and about update check 2026-10-13 17:30:23 +08:00
weidu12123 76627aad6d feat: implement standalone native terminal window and full-width stage 2026-10-13 17:02:26 +08:00
weidu12123 d2348d0c00 feat: initial commit of vps-switch core modules and desktop frontend 2026-10-13 16:36:22 +08:00