@skylandnpm/octopus-proxy 0.1.75 → 0.1.76

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 CHANGED
@@ -8,31 +8,39 @@ Octopus Proxy 可运行在 macOS、Linux 和 Windows 主机上,需要 Node.js
8
8
 
9
9
  ### 一键检查和安装
10
10
 
11
- 在仓库根目录运行以下脚本。脚本会复用能正常执行的 Node.js 22(含 npm)、OpenCode 2.0.19 和 Octopus Proxy,缺失或版本不匹配时安装。首次安装 Proxy 使用 npm 的 `@latest`。OpenCode 固定为 `2.0.19`,与[当前适配器的兼容基线](../../docs/opencode-compatibility.md)一致。
11
+ 在仓库根目录运行以下脚本。脚本会先探测本机程序的版本,Git、OpenSSL、Node.js 22(含 npm)、OpenCode 2.0.19 和 Octopus Proxy 符合要求时会显示 `already installed` 或已安装版本并跳过安装。首次安装 Proxy 使用 npm 的 `@latest`。OpenCode 固定为 `2.0.19`,与[当前适配器的兼容基线](../../docs/opencode-compatibility.md)一致。
12
12
 
13
13
  Linux 或 WSL(x64 / ARM64)
14
14
 
15
15
  ```bash
16
16
  ./scripts/installer/install-octopus-tools.sh
17
- export PATH="$HOME/.local/share/octopus-tools/npm/bin:$HOME/.local/share/octopus-tools/node/bin:$PATH"
18
- octopus-proxy onboard --tool opencode
19
17
  ```
20
18
 
21
- 脚本以当前用户安装。将上面的 `export` 加入 shell 启动文件可在新终端使用。安装需要 `flock`,自动下载的 Node.js 需要 glibc 及 `curl`、`tar`、`sha256sum`。Alpine 等 musl 发行版可先通过发行版软件源安装 Node.js 22 和 npm,再运行脚本。Proxy 的 `node-pty` 依赖在 Linux 上需要 Python 3、`make` 和 C/C++ 编译器,Debian、Ubuntu 或 WSL 的 Ubuntu 可用 `sudo apt install python3 build-essential` 安装。如果脚本安装了 Proxy,运行 `NPM_CONFIG_PREFIX="$HOME/.local/share/octopus-tools/npm" octopus-proxy update` 可用 CLI 更新这份安装。
19
+ 脚本以当前用户安装。安装完成后执行脚本输出的 `export`,将检测到的工具目录加入 PATH。也可把该命令写入 shell 启动文件。安装需要 `flock`,自动下载的 Node.js 需要 glibc 及 `curl`、`tar`、`sha256sum`。Alpine 等 musl 发行版可先通过发行版软件源安装 Node.js 22 和 npm,再运行脚本。Proxy 的 `node-pty` 依赖在 Linux 上需要 Python 3、`make` 和 C/C++ 编译器,Debian、Ubuntu 或 WSL 的 Ubuntu 可用 `sudo apt install python3 build-essential` 安装。如果脚本安装了 Proxy,运行 `NPM_CONFIG_PREFIX="$HOME/.local/share/octopus-tools/npm" octopus-proxy update` 可用 CLI 更新这份安装。
20
+
21
+ 更新 PATH 后,运行 `octopus-proxy onboard --tool opencode` 配置 Proxy。
22
+
23
+ 缺少 Git 或 OpenSSL 时,脚本使用系统已配置的软件源,通过 `apt-get`、`dnf`、`yum`、`zypper`、`pacman` 或 `apk` 安装。普通用户安装这两个系统工具时会调用 `sudo`。
22
24
 
23
25
  Windows(x64 / ARM64)
24
26
 
27
+ 以管理员身份打开 PowerShell,从仓库根目录运行安装脚本。脚本会复用系统 PATH 中版本符合要求的工具,也会检查本机的 Program Files 安装目录。安装缺少的工具时使用 `%ProgramFiles%\Octopus\Tools`。脚本将所选工具的目录加入系统 PATH,供本机所有用户使用。 [Windows 环境变量作用域](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables)
28
+
25
29
  ```powershell
26
30
  powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\installer\install-octopus-tools.ps1
27
31
  ```
28
32
 
29
- 用 Windows 自带的 `powershell.exe` 启动。若系统缺少 PowerShell 7(`pwsh.exe`),脚本会以自身所在目录为基准,优先查找 `PowerShell-7.6.9-win-x64.zip` 或 `PowerShell-7.6.9-win-arm64.zip`,从任意工作目录启动都适用。将对应架构的 ZIP 与脚本一起复制到目标 Windows 主机即可使用本地包。脚本校验 SHA-256 后解压到 `%LOCALAPPDATA%\Octopus\Tools\powershell`,再继续安装其余工具。
33
+ 用 Windows 自带的 `powershell.exe` 启动。若系统缺少 PowerShell 7(`pwsh.exe`),脚本会以自身所在目录为基准,优先查找 `PowerShell-7.6.9-win-x64.zip` 或 `PowerShell-7.6.9-win-arm64.zip`,从任意工作目录启动都适用。将对应架构的 ZIP 与脚本一起复制到目标 Windows 主机即可使用本地包。脚本校验 SHA-256 后解压到 `%ProgramFiles%\Octopus\Tools\powershell`,再继续安装其余工具。
30
34
 
31
35
  本地包缺失或无法通过校验时,脚本会在线下载。可将 `OCTOPUS_POWERSHELL_MIRROR` 设为镜像基址,脚本会拼接 `v7.6.9/PowerShell-7.6.9-win-<架构>.zip`。镜像下载或校验失败时回退到官方源。 [PowerShell 7.6.9 官方发行页](https://github.com/PowerShell/PowerShell/releases/tag/v7.6.9)
32
36
 
33
- 安装完成后新开终端即可读取更新的用户 PATH。如果系统 PATH 中已有其他 Node.js 并优先命中,在当前 PowerShell 执行 `$env:Path = "$env:LOCALAPPDATA\Octopus\Tools\npm;$env:LOCALAPPDATA\Octopus\Tools\node;$env:Path"`。支持 Windows 远控的 Proxy 版本可直接在原生 Windows x64 执行 `octopus-proxy onboard` 和 `octopus-proxy run`,详见[主机安装与配对](../../docs/claw-remote-desktop.md#windows-被控主机)。脚本安装的 npm 版本需要包含此功能。
37
+ 缺少 Git 时,脚本优先使用同目录下的 `MinGit-2.56.1-64-bit.zip` 或 `MinGit-2.56.1-arm64.zip`。本地包缺失或校验失败时,从国内镜像下载,失败后回退到 Git for Windows 官方源。可用 `OCTOPUS_GIT_MIRROR` 指定包含 `v2.56.1.windows.1/` 的镜像基址。校验通过后,脚本将 MinGit 安装到 `%ProgramFiles%\Octopus\Tools\git`。 [Git for Windows 2.56.1 官方发行页](https://github.com/git-for-windows/git/releases/tag/v2.56.1.windows.1)
38
+
39
+ 缺少 OpenSSL 时,脚本优先校验并使用同目录下的 `OpenSSL-3.5.9-win-x64.zip` 或 `OpenSSL-3.5.9-win-arm64.zip`,用 PowerShell 的 `Expand-Archive` 安装到 `%ProgramFiles%\Octopus\Tools\openssl`。如果系统级 `OPENSSL_CONF` 尚未设置,脚本会配置它,供 Proxy 生成主机证书。ZIP 缺失或校验失败时,脚本会查找或下载 Git for Windows 的上游包,先用 XZ Utils 解压,再交给 Windows 的 `tar.exe`。可用 `OCTOPUS_OPENSSL_MIRROR` 指定保留相同发行标签子目录的上游包镜像。 [Git for Windows OpenSSL x64](https://github.com/git-for-windows/pacman-repo/releases/tag/2026-09-30T09-24-18.347486000Z) · [ARM64](https://github.com/git-for-windows/pacman-repo/releases/tag/2026-09-30T10-08-42.867521900Z) · [XZ Utils](https://github.com/tukaani-project/xz/releases/tag/v5.8.4)
34
40
 
35
- 只检查版本时,Linux 追加 `--check`,Windows 追加 `-Check`。Windows 的检查包含 PowerShell 7。任何一项缺失或版本不符时返回退出码 `1`。
41
+ 安装完成后新开终端即可读取更新的系统 PATH。支持 Windows 远控的 Proxy 版本可直接在原生 Windows x64 执行 `octopus-proxy onboard` 和 `octopus-proxy run`,详见[主机安装与配对](../../docs/claw-remote-desktop.md#windows-被控主机)。脚本安装的 npm 版本需要包含此功能。
42
+
43
+ 只检查版本时,Linux 追加 `--check`,Windows 追加 `-Check`。Windows 从系统 PATH 检查各工具,包含 PowerShell 7,检查时无需管理员权限。任何一项缺失或版本不符时返回退出码 `1`。
36
44
 
37
45
  Node.js 和 npm 默认使用 `https://npmmirror.com/mirrors/node` 和 `https://registry.npmmirror.com`,下载或安装失败时回退到官方源。可通过 `OCTOPUS_NODE_MIRROR` 和 `OCTOPUS_NPM_REGISTRY` 为本次运行指定源。Node.js 压缩包按官方 SHA-256 校验,npm 源设置仅用于本次安装。
38
46
 
@@ -67,6 +75,8 @@ octopus-proxy doctor
67
75
  octopus-proxy restart
68
76
  ```
69
77
 
78
+ Windows 上,Claw 的项目终端使用 `ComSpec` 指向的本机可执行文件,找不到时使用 `%SystemRoot%\System32\cmd.exe`。无需设置 `SHELL`。若看到 `/bin/sh` 报错,请更新 Windows 主机上的 Proxy,并从本机终端重新运行 `octopus-proxy run` 后在 Claw 新建终端。
79
+
70
80
  Claw 新建终端时若出现 `posix_spawnp failed.`,错误来自 Proxy 主机上的 PTY 启动。`node-pty 1.1.0` 的 macOS 预编译 `spawn-helper` 存在安装后缺少执行权限的[上游问题](https://github.com/microsoft/node-pty/issues/850);同一 macOS 版本的两台电脑也可能因安装方式、CPU 架构和安装目录不同而只有一台失败。
71
81
 
72
82
  Proxy 在新建终端前检查实际加载的辅助程序,并自动补齐缺失的执行权限。`octopus-proxy doctor` 会显示辅助程序路径及当前 Node 架构,`doctor --fix` 可修复权限;检查独立于配置是否有效。若安装目录不允许当前账号修改,诊断会提示由安装账号修复或重新安装,不会自动提权。辅助程序缺失需重新安装;权限正常仍失败时,终端错误会显示 shell、工作目录、辅助程序路径和 Node 版本,供进一步核查。诊断应在发生错误的 Proxy 主机、同一 Node 安装环境下运行,之后执行 `octopus-proxy restart` 并在 Claw 新建终端。
@@ -147,13 +157,15 @@ octopus-proxy onboard --tool pi
147
157
 
148
158
  ## 更新
149
159
 
150
- 在交互式终端中运行:
160
+ macOS 和 Linux 上可运行:
151
161
 
152
162
  ```sh
153
163
  octopus-proxy update
154
164
  ```
155
165
 
156
- 命令通过 `npm view @skylandnpm/octopus-proxy@latest version` 查询最新版本。存在新版本时,输入 `y` 或 `yes` 确认覆盖安装;直接回车或输入其他内容会取消。确认后使用 npm 全局安装查到的版本,校验安装版本成功后才重启已安装的服务,并检查运行状态后反馈结果。安装期间不会主动停止服务。配置、主机身份和已有服务定义会保留;未安装服务时仅更新 npm 包。
166
+ 命令通过 `npm view @skylandnpm/octopus-proxy@latest version` 查询最新版本。存在新版本时,输入 `y` 或 `yes` 确认覆盖安装;直接回车或输入其他内容会取消。脚本中可使用 `octopus-proxy update -y`(或 `--yes`),发现新版本就直接安装,无需交互终端。当前版本已是最新或高于 npm 最新版本时,不安装也不重启服务。
167
+
168
+ 命令使用 npm 全局安装查到的版本,校验安装版本成功后才重启已安装的服务,并检查运行状态后反馈结果。安装期间不会主动停止服务。配置、主机身份和已有服务定义会保留;未安装服务时仅更新 npm 包。Windows 请先停止前台 Proxy,通过 npm 更新后再运行 `octopus-proxy run`。
157
169
 
158
170
  安装或版本校验失败时不会触发服务重启;包安装失败不保证旧版本文件完整保留。已安装新版本但重启失败时会单独提示,可运行 `octopus-proxy doctor` 和 `octopus-proxy logs` 排查。此命令用于 npm 全局安装,覆盖安装前会检查 npm 安装目录是否与当前命令一致;源码、本地安装或 Node.js 环境不一致时会中止并给出提示。
159
171
 
@@ -4517,7 +4517,27 @@ function missingPermissions(permissions) {
4517
4517
  }
4518
4518
 
4519
4519
  // src/remote/windows-adapter.ts
4520
- var WINDOWS_DESKTOP_UNAVAILABLE = "Windows \u8FDC\u63A7\u9700\u8981\u5728\u5DF2\u767B\u5F55\u4E14\u89E3\u9501\u7684\u672C\u673A\u684C\u9762\u4E2D\u4EE5\u524D\u53F0\u65B9\u5F0F\u8FD0\u884C Proxy\uFF1B\u9501\u5C4F\u3001UAC \u548C\u8FDC\u7A0B\u684C\u9762\u4F1A\u8BDD\u4E0D\u652F\u6301";
4520
+ var WINDOWS_DESKTOP_UNAVAILABLE = "Windows \u684C\u9762\u4E0D\u53EF\u7528\uFF1B\u8BF7\u5728\u5DF2\u767B\u5F55\u4E14\u89E3\u9501\u7684\u672C\u673A\u684C\u9762\u4E2D\u8FD0\u884C octopus-proxy run";
4521
+ function windowsDesktopIssue(code) {
4522
+ switch (code) {
4523
+ case 10:
4524
+ return "Proxy \u4E0D\u5728\u6D3B\u52A8\u7684 Windows \u672C\u673A\u63A7\u5236\u53F0\u4F1A\u8BDD\u4E2D\uFF1B\u8BF7\u5728\u672C\u673A\u684C\u9762\u8FD0\u884C octopus-proxy run\uFF0CRDP \u548C Windows \u670D\u52A1\u4F1A\u8BDD\u4E0D\u652F\u6301";
4525
+ case 11:
4526
+ return "Proxy \u672A\u8FDE\u63A5 Windows \u4EA4\u4E92\u5F0F\u684C\u9762\uFF1B\u8BF7\u4ECE\u684C\u9762\u7528\u6237\u7684\u7EC8\u7AEF\u8FD0\u884C octopus-proxy run";
4527
+ case 12:
4528
+ return "Windows \u666E\u901A\u684C\u9762\u5F53\u524D\u4E0D\u53EF\u8BBF\u95EE\uFF1B\u8BF7\u5728\u4E3B\u673A\u4E0A\u89E3\u9501\u6216\u5173\u95ED UAC \u5B89\u5168\u684C\u9762\uFF0C\u7136\u540E\u91CD\u8BD5";
4529
+ case 13:
4530
+ return "Proxy \u7684 Windows \u767B\u5F55\u4F1A\u8BDD\u4E0E\u5F53\u524D\u684C\u9762\u4E0D\u5339\u914D\uFF0C\u6216\u65E0\u6CD5\u9A8C\u8BC1\uFF1B\u8BF7\u7528\u5F53\u524D\u684C\u9762\u7528\u6237\u7684\u666E\u901A\u7EC8\u7AEF\u91CD\u65B0\u8FD0\u884C octopus-proxy run";
4531
+ case 14:
4532
+ return "Windows \u684C\u9762\u8F85\u52A9\u7A0B\u5E8F\u65E0\u6CD5\u521D\u59CB\u5316\u663E\u793A\u7F29\u653E\uFF1B\u8BF7\u8FD0\u884C octopus-proxy doctor \u68C0\u67E5";
4533
+ case 15:
4534
+ return "\u6CA1\u6709\u53EF\u622A\u5C4F\u7684\u6D3B\u52A8\u663E\u793A\u5668\uFF0C\u8BF7\u68C0\u67E5 Proxy \u4E3B\u673A\u7684\u56FE\u5F62\u4F1A\u8BDD\u3002";
4535
+ }
4536
+ }
4537
+ function windowsDesktopError(error) {
4538
+ if (!(error instanceof Error)) return void 0;
4539
+ return windowsDesktopIssue(error.code) ?? windowsDesktopIssue(error.cause?.code);
4540
+ }
4521
4541
  var WindowsRemoteAdapter = class {
4522
4542
  constructor(context, dependencies = nativeDependencies) {
4523
4543
  this.context = context;
@@ -4541,7 +4561,9 @@ var WindowsRemoteAdapter = class {
4541
4561
  async checkDependencies(_config) {
4542
4562
  }
4543
4563
  unavailableReason(permissions) {
4544
- if (permissions.locked || !permissions.session || !permissions.capture || !permissions.input) return WINDOWS_DESKTOP_UNAVAILABLE;
4564
+ if (permissions.locked || !permissions.session || !permissions.capture || !permissions.input) {
4565
+ return windowsDesktopIssue(permissions.desktopIssue) ?? WINDOWS_DESKTOP_UNAVAILABLE;
4566
+ }
4545
4567
  }
4546
4568
  createSession(options) {
4547
4569
  return new WindowsRemoteSession(options, this.dependencies);
@@ -4589,7 +4611,7 @@ var executeDesktop = async (file, args) => {
4589
4611
  const detail = error;
4590
4612
  if (detail.code === "ENOENT") throw new Error(`\u684C\u9762\u622A\u5C4F\u7F3A\u5C11\u4F9D\u8D56\uFF1A${file}\u3002\u8BF7\u5728 Proxy \u4E3B\u673A\u5B89\u88C5\u540E\u91CD\u8BD5\u3002`);
4591
4613
  if (detail.killed) throw new Error("\u684C\u9762\u622A\u5C4F\u8D85\u65F6\uFF0C\u8BF7\u68C0\u67E5 Proxy \u4E3B\u673A\u7684\u684C\u9762\u4F1A\u8BDD\u540E\u91CD\u8BD5\u3002");
4592
- throw new Error(`\u684C\u9762\u622A\u5C4F\u5931\u8D25\uFF1A${detail.stderr?.trim().slice(0, 2048) || detail.message}`);
4614
+ throw new Error(`\u684C\u9762\u622A\u5C4F\u5931\u8D25\uFF1A${detail.stderr?.trim().slice(0, 2048) || detail.message}`, { cause: error });
4593
4615
  }
4594
4616
  };
4595
4617
  var DESKTOP_PERMISSION_GUIDANCE = "\u8BF7\u5728\u201C\u7CFB\u7EDF\u8BBE\u7F6E \u2192 \u9690\u79C1\u4E0E\u5B89\u5168\u6027 \u2192 \u5C4F\u5E55\u5F55\u5236\uFF08\u6216\u5C4F\u5E55\u4E0E\u7CFB\u7EDF\u97F3\u9891\u5F55\u5236\uFF09\u201D\u4E2D\u542F\u7528\u7CFB\u7EDF\u6388\u6743\u5F39\u7A97\u663E\u793A\u7684\u5B9E\u9645\u7A0B\u5E8F\u3002\u7EC8\u7AEF\u8FD0\u884C\u65F6\u53EF\u80FD\u663E\u793A Terminal\u3001iTerm \u6216\u7F16\u8F91\u5668\uFF0C\u540E\u53F0\u670D\u52A1\u53EF\u80FD\u663E\u793A node\uFF1B\u4E0D\u4E00\u5B9A\u4F1A\u51FA\u73B0 octopus-desktop-capture\u3002\u7EC8\u7AEF\u6388\u6743\u4E0D\u4EE3\u8868\u540E\u53F0\u670D\u52A1\u5DF2\u6388\u6743\uFF1B\u8BF7\u5728 Claw \u70B9\u51FB\u201C\u622A\u5C4F\u201D\u89E6\u53D1\u670D\u52A1\u7684\u6743\u9650\u7533\u8BF7\uFF0C\u6309\u5B9E\u9645\u540D\u79F0\u6388\u6743\u540E\u91CD\u542F Proxy\u3002";
@@ -4640,8 +4662,10 @@ var PlatformDesktop = class {
4640
4662
  this.helper = void 0;
4641
4663
  throw error;
4642
4664
  });
4643
- return this.execute(await this.helper, args).catch(() => {
4644
- throw new Error(WINDOWS_DESKTOP_UNAVAILABLE);
4665
+ return this.execute(await this.helper, args).catch((error) => {
4666
+ const message = windowsDesktopError(error);
4667
+ if (message) throw new Error(message);
4668
+ throw error;
4645
4669
  });
4646
4670
  }
4647
4671
  linuxMode() {
@@ -5051,7 +5075,7 @@ export {
5051
5075
  remoteResolutionTiers,
5052
5076
  fitVideo,
5053
5077
  checkWindowsPrivatePath,
5054
- WINDOWS_DESKTOP_UNAVAILABLE,
5078
+ windowsDesktopError,
5055
5079
  DESKTOP_PERMISSION_GUIDANCE,
5056
5080
  requestDesktopPermission,
5057
5081
  checkDesktop,
@@ -7,7 +7,6 @@ import {
7
7
  RemotePlatformRegistry,
8
8
  RemoteQualitySchema,
9
9
  SignalSchema,
10
- WINDOWS_DESKTOP_UNAVAILABLE,
11
10
  __commonJS,
12
11
  __toESM,
13
12
  assertDirectSignal,
@@ -16,8 +15,9 @@ import {
16
15
  probeRemote,
17
16
  remotePlatformError,
18
17
  remoteSessionBudget,
18
+ windowsDesktopError,
19
19
  z
20
- } from "./chunk-TSX64XMW.js";
20
+ } from "./chunk-X5EQMMYB.js";
21
21
 
22
22
  // ../../node_modules/.pnpm/ajv@8.17.1/node_modules/ajv/dist/compile/codegen/code.js
23
23
  var require_code = __commonJS({
@@ -14075,7 +14075,7 @@ var ProjectRegistry = class {
14075
14075
 
14076
14076
  // src/terminal.ts
14077
14077
  import { randomUUID as randomUUID17 } from "node:crypto";
14078
- import { access as access2, stat as stat10 } from "node:fs/promises";
14078
+ import { access as access2, stat as stat11 } from "node:fs/promises";
14079
14079
  import { constants as constants5 } from "node:fs";
14080
14080
  import { platform as platform3, userInfo as userInfo3 } from "node:os";
14081
14081
  import { delimiter } from "node:path";
@@ -14130,7 +14130,80 @@ async function checkTerminalSupport(fix) {
14130
14130
  }
14131
14131
  }
14132
14132
 
14133
+ // src/terminal-windows.ts
14134
+ import { stat as stat10 } from "node:fs/promises";
14135
+ import { win32 } from "node:path";
14136
+ var shellVariables = [
14137
+ "PathExt",
14138
+ "OS",
14139
+ "SystemDrive",
14140
+ "UserProfile",
14141
+ "UserName",
14142
+ "UserDomain",
14143
+ "HomeDrive",
14144
+ "HomePath",
14145
+ "AppData",
14146
+ "LocalAppData",
14147
+ "Temp",
14148
+ "Tmp",
14149
+ "ProgramData",
14150
+ "ProgramFiles",
14151
+ "ProgramFiles(x86)",
14152
+ "ProgramW6432",
14153
+ "CommonProgramFiles",
14154
+ "CommonProgramFiles(x86)",
14155
+ "CommonProgramW6432",
14156
+ "PSModulePath"
14157
+ ];
14158
+ async function windowsTerminal(environment, home) {
14159
+ const variables = new Map(Object.entries(environment).map(([key, value]) => [key.toLowerCase(), value]));
14160
+ const get = (name) => variables.get(name.toLowerCase());
14161
+ const root = get("SystemRoot") || get("windir") || "C:\\Windows";
14162
+ const system = win32.join(root, "System32");
14163
+ const fallback = win32.join(system, "cmd.exe");
14164
+ const candidates = /* @__PURE__ */ new Set([get("ComSpec"), fallback]);
14165
+ for (const shell of candidates) {
14166
+ if (!shell || win32.parse(shell).root.length < 3 || win32.extname(shell).toLowerCase() !== ".exe") continue;
14167
+ if (!(await stat10(shell).catch(() => void 0))?.isFile()) continue;
14168
+ const env = {};
14169
+ for (const name of shellVariables) {
14170
+ const value = get(name);
14171
+ if (value !== void 0) env[name] = value;
14172
+ }
14173
+ return { shell, env: {
14174
+ ...env,
14175
+ ComSpec: shell,
14176
+ SystemRoot: root,
14177
+ windir: root,
14178
+ UserProfile: env.UserProfile || home,
14179
+ Path: [get("Path"), system, root].filter(Boolean).join(win32.delimiter),
14180
+ TERM: "xterm-256color"
14181
+ } };
14182
+ }
14183
+ throw new Error(`\u7EC8\u7AEF Shell \u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u6267\u884C\uFF1A${fallback}\uFF1B\u8BF7\u68C0\u67E5 Proxy \u4E3B\u673A\u7684 ComSpec \u6216 SystemRoot \u73AF\u5883\u53D8\u91CF`);
14184
+ }
14185
+
14133
14186
  // src/terminal.ts
14187
+ async function terminalShell() {
14188
+ const system = platform3(), account = userInfo3();
14189
+ if (system === "win32") return windowsTerminal(process.env, account.homedir);
14190
+ const shell = process.env.SHELL || (system === "darwin" ? "/bin/zsh" : "/bin/sh");
14191
+ try {
14192
+ await access2(shell, constants5.X_OK);
14193
+ } catch {
14194
+ throw new Error(`\u7EC8\u7AEF Shell \u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u6267\u884C\uFF1A${shell}\uFF1B\u8BF7\u4FEE\u6B63 Proxy \u670D\u52A1\u7684 SHELL \u73AF\u5883\u53D8\u91CF`);
14195
+ }
14196
+ const path = [
14197
+ process.env.PATH,
14198
+ "/usr/bin",
14199
+ "/bin",
14200
+ "/usr/sbin",
14201
+ "/sbin",
14202
+ "/usr/local/bin",
14203
+ ...system === "darwin" ? ["/opt/homebrew/bin"] : []
14204
+ ].filter(Boolean).join(delimiter);
14205
+ return { shell, env: { PATH: path, HOME: account.homedir, TERM: "xterm-256color", USER: account.username } };
14206
+ }
14134
14207
  var TerminalManager = class {
14135
14208
  constructor(workspace, emit) {
14136
14209
  this.workspace = workspace;
@@ -14152,33 +14225,13 @@ var TerminalManager = class {
14152
14225
  const count = [...this.terminals.values()].filter((t) => t.owner === owner).length;
14153
14226
  if (count >= 8) throw new Error("Terminal limit reached");
14154
14227
  const cwd = await this.workspace.path(project, typeof p.cwd === "string" ? p.cwd : ".");
14155
- if (!(await stat10(cwd)).isDirectory()) throw new Error(`\u7EC8\u7AEF\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u662F\u76EE\u5F55\uFF1A${cwd}`);
14156
- const shell = process.env.SHELL || (platform3() === "darwin" ? "/bin/zsh" : "/bin/sh");
14157
- try {
14158
- await access2(shell, constants5.X_OK);
14159
- } catch {
14160
- throw new Error(`\u7EC8\u7AEF Shell \u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u6267\u884C\uFF1A${shell}\uFF1B\u8BF7\u4FEE\u6B63 Proxy \u670D\u52A1\u7684 SHELL \u73AF\u5883\u53D8\u91CF`);
14161
- }
14228
+ if (!(await stat11(cwd)).isDirectory()) throw new Error(`\u7EC8\u7AEF\u5DE5\u4F5C\u76EE\u5F55\u4E0D\u662F\u76EE\u5F55\uFF1A${cwd}`);
14229
+ const { shell, env } = await terminalShell();
14162
14230
  const { helper } = await ensureTerminalSupport();
14163
14231
  const id = randomUUID17();
14164
- const path = [
14165
- process.env.PATH,
14166
- "/usr/bin",
14167
- "/bin",
14168
- "/usr/sbin",
14169
- "/sbin",
14170
- "/usr/local/bin",
14171
- ...platform3() === "darwin" ? ["/opt/homebrew/bin"] : []
14172
- ].filter(Boolean).join(delimiter);
14173
14232
  let terminalProcess;
14174
14233
  try {
14175
- terminalProcess = pty.spawn(shell, [], {
14176
- name: "xterm-256color",
14177
- cols: 80,
14178
- rows: 24,
14179
- cwd,
14180
- env: { PATH: path, HOME: userInfo3().homedir, TERM: "xterm-256color", USER: userInfo3().username }
14181
- });
14234
+ terminalProcess = pty.spawn(shell, [], { name: "xterm-256color", cols: 80, rows: 24, cwd, env });
14182
14235
  } catch (error) {
14183
14236
  throw new Error(`\u7EC8\u7AEF\u542F\u52A8\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}\uFF08shell=${shell}\uFF0Ccwd=${cwd}${helper ? `\uFF0Chelper=${helper}` : ""}\uFF0C${process.platform}/${process.arch}\uFF0CNode ${process.version}\uFF09\uFF1B\u8BF7\u5728 Proxy \u4E3B\u673A\u8FD0\u884C octopus-proxy doctor --fix \u68C0\u67E5`);
14184
14237
  }
@@ -14251,10 +14304,10 @@ function windowsSessionIdentity(stateDirectory) {
14251
14304
  throw error;
14252
14305
  });
14253
14306
  const file = await helper;
14254
- const identity = (await exec(file, ["session"], { timeout: 5e3, maxBuffer: 4096, windowsHide: true }).catch(() => {
14255
- throw new Error(WINDOWS_DESKTOP_UNAVAILABLE);
14307
+ const identity = (await exec(file, ["session"], { timeout: 5e3, maxBuffer: 4096, windowsHide: true }).catch((error) => {
14308
+ throw new Error(windowsDesktopError(error) ?? "Windows \u684C\u9762\u8F85\u52A9\u7A0B\u5E8F\u65E0\u6CD5\u67E5\u8BE2\u5F53\u524D\u767B\u5F55\u4F1A\u8BDD\uFF1B\u8BF7\u8FD0\u884C octopus-proxy doctor \u68C0\u67E5");
14256
14309
  })).stdout.trim();
14257
- if (!/^win32:[1-9]\d*:S-1(?:-\d+)+$/.test(identity)) throw new Error(WINDOWS_DESKTOP_UNAVAILABLE);
14310
+ if (!/^win32:[1-9]\d*:S-1-5-5-\d+-\d+$/.test(identity)) throw new Error("Windows \u684C\u9762\u8F85\u52A9\u7A0B\u5E8F\u8FD4\u56DE\u65E0\u6548\u7684\u767B\u5F55\u4F1A\u8BDD\uFF1B\u8BF7\u66F4\u65B0 Proxy \u540E\u91CD\u8BD5");
14258
14311
  return identity;
14259
14312
  };
14260
14313
  }
@@ -14537,7 +14590,7 @@ import { platform as platform5 } from "node:os";
14537
14590
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
14538
14591
 
14539
14592
  // src/node-resolution.ts
14540
- import { realpath as realpath8, stat as stat11 } from "node:fs/promises";
14593
+ import { realpath as realpath8, stat as stat12 } from "node:fs/promises";
14541
14594
  import { execFile as execFile7 } from "node:child_process";
14542
14595
  import { promisify as promisify7 } from "node:util";
14543
14596
  import { delimiter as delimiter2, join as join14 } from "node:path";
@@ -14606,7 +14659,7 @@ async function resolveCommandOnPath(name, extraDirs = []) {
14606
14659
  for (const dir of new Set(dirs)) {
14607
14660
  const full = join14(dir, file);
14608
14661
  try {
14609
- const info = await stat11(full);
14662
+ const info = await stat12(full);
14610
14663
  if (info.isFile() && (windows || info.mode & 73)) return full;
14611
14664
  } catch {
14612
14665
  }
package/dist/cli.js CHANGED
@@ -36,14 +36,14 @@ import {
36
36
  supportedPlatform,
37
37
  systemdUnitPath,
38
38
  toolSearchDirs
39
- } from "./chunk-4VKDJVX3.js";
39
+ } from "./chunk-ZBKVXNM5.js";
40
40
  import {
41
41
  DESKTOP_PERMISSION_GUIDANCE,
42
42
  PlatformDesktop,
43
43
  checkDesktop,
44
44
  checkWindowsPrivatePath,
45
45
  requestDesktopPermission
46
- } from "./chunk-TSX64XMW.js";
46
+ } from "./chunk-X5EQMMYB.js";
47
47
 
48
48
  // src/cli.ts
49
49
  import { spawn as spawn2 } from "node:child_process";
@@ -820,7 +820,7 @@ async function npmOutput(args) {
820
820
  }
821
821
  }
822
822
  async function confirmUpdate(latest) {
823
- if (!stdin2.isTTY) throw new Error("\u66F4\u65B0\u9700\u8981\u5728\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\u4E2D\u786E\u8BA4\uFF1B\u8BF7\u5728\u7EC8\u7AEF\u8FD0\u884C octopus-proxy update");
823
+ if (!stdin2.isTTY) throw new Error("\u66F4\u65B0\u9700\u8981\u5728\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\u4E2D\u786E\u8BA4\uFF1B\u4E5F\u53EF\u8FD0\u884C octopus-proxy update -y \u81EA\u52A8\u786E\u8BA4");
824
824
  const rl = createInterface2({ input: stdin2, output: stdout2 });
825
825
  try {
826
826
  return /^(y|yes|是|确认)$/i.test((await rl.question(`\u662F\u5426\u8986\u76D6\u5B89\u88C5 ${proxyVersion} \u2192 ${latest}\uFF1F\u5B89\u88C5\u5E76\u6821\u9A8C\u6210\u529F\u540E\u4F1A\u91CD\u542F\u670D\u52A1 [y/N]: `)).trim());
@@ -843,7 +843,7 @@ async function restartAndVerify() {
843
843
  }
844
844
  throw new Error(`\u670D\u52A1\u91CD\u542F\u540E\u672A\u8FD0\u884C\uFF1B\u8BF7\u8FD0\u884C octopus-proxy doctor / logs \u68C0\u67E5\uFF08${logLocationHint()}\uFF09`);
845
845
  }
846
- async function runUpdate() {
846
+ async function runUpdate({ yes = false } = {}) {
847
847
  console.log(`\u5F53\u524D\u7248\u672C\uFF1A${proxyVersion}\uFF1B\u6B63\u5728\u67E5\u8BE2 npm \u6700\u65B0\u7248\u672C\u2026`);
848
848
  const output = await npmOutput(["view", `${PACKAGE}@latest`, "version", "--json", "--prefer-online", "--registry", REGISTRY]);
849
849
  let latest;
@@ -858,7 +858,7 @@ async function runUpdate() {
858
858
  return;
859
859
  }
860
860
  console.log(`\u53D1\u73B0\u65B0\u7248\u672C\uFF1A${latest}`);
861
- if (!await confirmUpdate(latest)) {
861
+ if (!yes && !await confirmUpdate(latest)) {
862
862
  console.log("\u5DF2\u53D6\u6D88\u66F4\u65B0");
863
863
  return;
864
864
  }
@@ -929,6 +929,9 @@ install \u9009\u9879:
929
929
  --force \u91CD\u5199\u670D\u52A1\u5B9A\u4E49\u5E76\u91CD\u542F\uFF08\u914D\u7F6E\u6216\u8DEF\u5F84\u53D8\u5316\u540E\u4F7F\u7528\uFF09
930
930
  --node-path <p> \u56FA\u5B9A\u670D\u52A1\u4F7F\u7528\u7684 node \u7EDD\u5BF9\u8DEF\u5F84
931
931
 
932
+ update \u9009\u9879:
933
+ -y, --yes \u53D1\u73B0\u65B0\u7248\u672C\u65F6\u76F4\u63A5\u66F4\u65B0\uFF0C\u65E0\u9700\u4EA4\u4E92\u786E\u8BA4
934
+
932
935
  projects \u9009\u9879:
933
936
  --remove <\u5E8F\u53F7> \u5220\u9664\u6307\u5B9A\u9879\u76EE\uFF08\u5148\u8FD0\u884C projects \u67E5\u770B\u5E8F\u53F7\uFF1B\u81EA\u52A8\u5907\u4EFD\u539F\u914D\u7F6E\uFF09
934
937
 
@@ -959,6 +962,10 @@ function parse(argv) {
959
962
  flags2["--follow"] = "true";
960
963
  continue;
961
964
  }
965
+ if (arg === "-y") {
966
+ flags2["--yes"] = "true";
967
+ continue;
968
+ }
962
969
  if (!arg.startsWith("--")) {
963
970
  if (command2 === void 0) command2 = arg;
964
971
  else throw new Error(`\u65E0\u6CD5\u8BC6\u522B\u7684\u53C2\u6570\uFF1A${arg}`);
@@ -1034,7 +1041,7 @@ try {
1034
1041
  await manageProjects(configPath, flags["--remove"]);
1035
1042
  break;
1036
1043
  case "update":
1037
- await runUpdate();
1044
+ await runUpdate({ yes: flags["--yes"] === "true" });
1038
1045
  break;
1039
1046
  case "identity": {
1040
1047
  const config = await loadConfig(configPath);
@@ -1109,9 +1116,11 @@ try {
1109
1116
  }
1110
1117
  break;
1111
1118
  }
1112
- case "run":
1119
+ case "run": {
1113
1120
  await runDaemon(configPath);
1121
+ console.log("Octopus Proxy is running. \u8BF7\u52FF\u5173\u95ED\u6B64\u7A97\u53E3\uFF0C\u5426\u5219 Proxy \u5C06\u505C\u6B62\u8FD0\u884C\uFF1B\u5982\u9700\u9000\u51FA\uFF0C\u8BF7\u6309 Ctrl+C\u3002");
1114
1122
  break;
1123
+ }
1115
1124
  case "logs":
1116
1125
  await showLogs(flags["--follow"] === "true");
1117
1126
  break;
package/dist/main.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  ConfigError,
4
4
  runDaemon
5
- } from "./chunk-4VKDJVX3.js";
6
- import "./chunk-TSX64XMW.js";
5
+ } from "./chunk-ZBKVXNM5.js";
6
+ import "./chunk-X5EQMMYB.js";
7
7
 
8
8
  // src/main.ts
9
9
  runDaemon(process.env.OCTOPUS_PROXY_CONFIG ?? "proxy.json").catch((error) => {
@@ -11,7 +11,7 @@ import {
11
11
  fitVideo,
12
12
  remoteResolutionTiers,
13
13
  remoteSessionBudget
14
- } from "./chunk-TSX64XMW.js";
14
+ } from "./chunk-X5EQMMYB.js";
15
15
 
16
16
  // src/remote-worker.ts
17
17
  import rtc from "@roamhq/wrtc";
@@ -11,11 +11,16 @@ using System.Security.Principal;
11
11
  using System.Text;
12
12
  using System.Threading;
13
13
  using System.Windows.Forms;
14
+ using Microsoft.Win32.SafeHandles;
14
15
 
15
16
  internal static class WindowsRemote {
16
17
  private const int ClipboardLimit = 1024 * 1024;
17
18
  private const int MaxLine = 1400000;
18
19
  private const int CopyOperation = 0x40CC0020; // SRCCOPY | CAPTUREBLT
20
+ private const int TokenLogonSid = 28;
21
+ private const int UoiUserSid = 4;
22
+ private const uint WinstaReadAttributes = 0x0002;
23
+ private enum DesktopIssue { None = 0, Console = 10, WindowStation = 11, InputDesktop = 12, Logon = 13, Dpi = 14, NoDisplays = 15 }
19
24
  private static readonly UTF8Encoding Utf8 = new UTF8Encoding(false, true);
20
25
  private static readonly object MessageLock = new object();
21
26
 
@@ -23,12 +28,15 @@ internal static class WindowsRemote {
23
28
  [DllImport("kernel32.dll")] private static extern uint WTSGetActiveConsoleSessionId();
24
29
  [DllImport("kernel32.dll")] private static extern uint GetCurrentThreadId();
25
30
  [DllImport("user32.dll")] private static extern IntPtr GetProcessWindowStation();
31
+ [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr OpenWindowStation(string name, bool inherit, uint access);
32
+ [DllImport("user32.dll")] private static extern bool CloseWindowStation(IntPtr station);
26
33
  [DllImport("user32.dll")] private static extern IntPtr GetThreadDesktop(uint thread);
27
34
  [DllImport("user32.dll")] private static extern IntPtr OpenInputDesktop(uint flags, bool inherit, uint access);
28
35
  [DllImport("user32.dll")] private static extern bool CloseDesktop(IntPtr desktop);
29
36
  [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern bool GetUserObjectInformation(IntPtr handle, int index, StringBuilder value, uint size, out uint needed);
30
37
  [DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetUserObjectInformationW", ExactSpelling = true)]
31
- private static extern bool GetUserObjectSid(IntPtr handle, int index, byte[] value, uint size, out uint needed);
38
+ private static extern bool GetUserObjectSid(IntPtr handle, int index, [Out] byte[] value, uint size, out uint needed);
39
+ [DllImport("advapi32.dll", SetLastError = true)] private static extern bool GetTokenInformation(SafeAccessTokenHandle token, int kind, IntPtr value, uint size, out uint needed);
32
40
  [DllImport("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint = "LogonUserW", ExactSpelling = true)]
33
41
  private static extern bool LogonUser(string user, string domain, IntPtr password, int logonType, int provider, out IntPtr token);
34
42
  [DllImport("kernel32.dll")] private static extern bool CloseHandle(IntPtr handle);
@@ -104,23 +112,54 @@ internal static class WindowsRemote {
104
112
  private static uint GetProcessSession() {
105
113
  using (var process = Process.GetCurrentProcess()) return (uint)process.SessionId;
106
114
  }
115
+ private static readonly Lazy<SecurityIdentifier> ProcessLogonSid = new Lazy<SecurityIdentifier>(ReadLogonSid);
116
+ private static SecurityIdentifier ReadLogonSid() {
117
+ using (var account = WindowsIdentity.GetCurrent()) {
118
+ var token = account.AccessToken;
119
+ uint length;
120
+ GetTokenInformation(token, TokenLogonSid, IntPtr.Zero, 0, out length);
121
+ // TOKEN_GROUPS contains DWORD GroupCount followed by an aligned
122
+ // SID_AND_ATTRIBUTES array. TokenLogonSid returns a single entry.
123
+ if (length < (uint)(3 * IntPtr.Size) || length > 1024) return null;
124
+ IntPtr groups = Marshal.AllocHGlobal((int)length);
125
+ try {
126
+ if (!GetTokenInformation(token, TokenLogonSid, groups, length, out length) || Marshal.ReadInt32(groups) != 1) return null;
127
+ IntPtr sid = Marshal.ReadIntPtr(groups, IntPtr.Size);
128
+ return sid == IntPtr.Zero ? null : new SecurityIdentifier(sid);
129
+ } finally { Marshal.FreeHGlobal(groups); }
130
+ }
131
+ }
107
132
  private static string ActiveDesktop() {
133
+ DesktopIssue issue;
134
+ return ActiveDesktop(out issue);
135
+ }
136
+ private static string ActiveDesktop(out DesktopIssue issue) {
137
+ issue = DesktopIssue.Console;
108
138
  uint session = WTSGetActiveConsoleSessionId();
109
139
  if (session == 0 || session == uint.MaxValue || session != ProcessSession) return null;
110
- IntPtr station = GetProcessWindowStation();
111
- if (!string.Equals(ObjectName(station), "WinSta0", StringComparison.OrdinalIgnoreCase)) return null;
140
+ issue = DesktopIssue.WindowStation;
141
+ if (!string.Equals(ObjectName(GetProcessWindowStation()), "WinSta0", StringComparison.OrdinalIgnoreCase)) return null;
142
+ issue = DesktopIssue.InputDesktop;
112
143
  if (!string.Equals(ObjectName(GetThreadDesktop(GetCurrentThreadId())), "Default", StringComparison.OrdinalIgnoreCase)) return null;
113
144
  IntPtr desktop = OpenInputDesktop(0, false, 1); // DESKTOP_READOBJECTS
114
145
  if (desktop == IntPtr.Zero) return null;
115
146
  try {
116
147
  if (!string.Equals(ObjectName(desktop), "Default", StringComparison.OrdinalIgnoreCase)) return null;
117
- var sid = new byte[68];
118
- uint length;
119
- if (!GetUserObjectSid(station, 4, sid, (uint)sid.Length, out length) || length == 0) return null;
120
- using (var account = WindowsIdentity.GetCurrent()) {
121
- if (!account.User.Equals(new SecurityIdentifier(sid, 0))) return null;
122
- return "win32:" + session + ":" + account.User.Value;
123
- }
148
+ issue = DesktopIssue.Logon;
149
+ var logon = ProcessLogonSid.Value;
150
+ if (logon == null) return null;
151
+ // WinSta0 belongs to a logon session (S-1-5-5-X-Y), not an
152
+ // account. Re-read its SID so a user switch cannot reuse a lease.
153
+ IntPtr station = OpenWindowStation("WinSta0", false, WinstaReadAttributes);
154
+ if (station == IntPtr.Zero) return null;
155
+ try {
156
+ var sid = new byte[68];
157
+ uint length;
158
+ if (!GetUserObjectSid(station, UoiUserSid, sid, (uint)sid.Length, out length) || length == 0
159
+ || !logon.Equals(new SecurityIdentifier(sid, 0))) return null;
160
+ issue = DesktopIssue.None;
161
+ return "win32:" + session + ":" + logon.Value;
162
+ } finally { CloseWindowStation(station); }
124
163
  } finally { CloseDesktop(desktop); }
125
164
  }
126
165
  private static List<Display> Displays() {
@@ -505,15 +544,18 @@ internal static class WindowsRemote {
505
544
  Console.WriteLine("OK"); return 0;
506
545
  }
507
546
  if (args.Length == 1 && args[0] == "self-test") return SelfTest() ? 0 : 1;
508
- if (!SetProcessDpiAwarenessContext(new IntPtr(-4))) return 1; // per-monitor v2
509
- string identity = ActiveDesktop();
547
+ // Set per-monitor v2 awareness before opening the desktop.
548
+ DesktopIssue issue = DesktopIssue.Dpi;
549
+ string identity = SetProcessDpiAwarenessContext(new IntPtr(-4)) ? ActiveDesktop(out issue) : null;
510
550
  if (args.Length == 1 && args[0] == "check") {
511
551
  bool ready = identity != null && Displays().Count != 0;
552
+ if (identity != null && !ready) issue = DesktopIssue.NoDisplays;
512
553
  string flag = ready ? "true" : "false";
513
- Console.WriteLine("{\"capture\":" + flag + ",\"input\":" + flag + ",\"session\":" + flag + "}");
554
+ Console.WriteLine("{\"capture\":" + flag + ",\"input\":" + flag + ",\"session\":" + flag
555
+ + ",\"desktopIssue\":" + (int)issue + "}");
514
556
  return 0;
515
557
  }
516
- if (identity == null) return 1;
558
+ if (identity == null) return (int)issue;
517
559
  if (args.Length == 1 && args[0] == "session") { Console.WriteLine(identity); return 0; }
518
560
  var displays = Displays();
519
561
  if (args.Length == 1 && args[0] == "list") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skylandnpm/octopus-proxy",
3
- "version": "0.1.75",
3
+ "version": "0.1.76",
4
4
  "type": "module",
5
5
  "description": "Octopus host proxy: OpenCode, Codex, ZCode and Pi adapters with service installer CLI",
6
6
  "bin": {