zcode-pro 0.16.0__tar.gz → 0.17.0__tar.gz

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.
Files changed (49) hide show
  1. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/PKG-INFO +1 -1
  2. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/README.en.md +44 -10
  3. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/README.md +44 -10
  4. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/bin/zcode-pro.cmd +29 -24
  5. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/bin/zcode-pro.mjs +11 -7
  6. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/bin/zcode-pro.vbs +16 -16
  7. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/dist/inject.js +21 -0
  8. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/package.json +1 -1
  9. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/host/helper.mjs +1 -1
  10. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/host/main.mjs +19 -0
  11. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/host/paths.mjs +45 -1
  12. zcode_pro-0.17.0/src/host/setup.mjs +514 -0
  13. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/core.js +22 -1
  14. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/index.js +6 -1
  15. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/zcode_pro/__init__.py +1 -1
  16. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/zcode_pro/cli.py +14 -1
  17. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/.gitignore +0 -0
  18. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/LICENSE +0 -0
  19. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/README_PYPI.md +0 -0
  20. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/bin/zcode-pro +0 -0
  21. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/cli.mjs +0 -0
  22. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/pyproject.toml +0 -0
  23. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/host/cdp.mjs +0 -0
  24. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/host/pickFolder.mjs +0 -0
  25. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/host/settings.mjs +0 -0
  26. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/host/taskIndex.mjs +0 -0
  27. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/host/taskOrder.mjs +0 -0
  28. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/host/webdav.mjs +0 -0
  29. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/alias.js +0 -0
  30. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/copy-path.js +0 -0
  31. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/file-menu.js +0 -0
  32. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/idle-reclaim.js +0 -0
  33. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/image-menu.js +0 -0
  34. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/open-folder.js +0 -0
  35. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/pinned-collapse.js +0 -0
  36. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/pinned-expand.js +0 -0
  37. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/plugin-updates.js +0 -0
  38. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/project-menu.js +0 -0
  39. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/relocate-dialog.js +0 -0
  40. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/session-switch.js +0 -0
  41. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/settings-dialog.js +0 -0
  42. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/settings-entry.js +0 -0
  43. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/styles.js +0 -0
  44. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/task-order.js +0 -0
  45. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/title-lock.js +0 -0
  46. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/toolbar-icons.js +0 -0
  47. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/features/ws-running.js +0 -0
  48. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/src/inject/ui.js +0 -0
  49. {zcode_pro-0.16.0 → zcode_pro-0.17.0}/zcode_pro/__main__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: zcode-pro
3
- Version: 0.16.0
3
+ Version: 0.17.0
4
4
  Summary: ZCode Pro — ZCode 桌面版界面增强启动器(不修改客户端文件),核心功能:项目自定义别名
5
5
  Project-URL: Homepage, https://github.com/duanluan/zcode-pro
6
6
  Project-URL: Repository, https://github.com/duanluan/zcode-pro
@@ -2,9 +2,9 @@
2
2
 
3
3
  [简体中文](README.md) | English
4
4
 
5
- ZCode Pro enhances the ZCode desktop UI. No client files are modified; just launch via the ZCode Pro shortcut.
5
+ ZCode Pro enhances the ZCode desktop UI. No client files are modified; just launch via the ZCode Pro shortcut or the `zcode-pro` terminal command.
6
6
 
7
- ![AUR](https://img.shields.io/aur/version/zcode-pro) ![Platform](https://img.shields.io/badge/platform-win%20%7C%20mac%20%7C%20linux%20%7C%20AUR-blue)
7
+ ![npm](https://img.shields.io/npm/v/zcode-pro) ![PyPI](https://img.shields.io/pypi/v/zcode-pro) ![AUR](https://img.shields.io/aur/version/zcode-pro) ![Platform](https://img.shields.io/badge/platform-win%20%7C%20mac%20%7C%20linux%20%7C%20AUR-blue)
8
8
 
9
9
  > **Opening settings: after launching ZCode Pro, right-click the settings button (gear) at the bottom of the ZCode sidebar to open the ZCode Pro settings window.**
10
10
 
@@ -41,6 +41,28 @@ All features can be toggled or adjusted in the settings window.
41
41
 
42
42
  Once installed, launch ZCode via the "ZCode Pro" shortcut in your app menu / Start menu, or the `zcode-pro` command in a terminal — the enhancements are enabled automatically in this mode; launching ZCode through its original entry does not load them. After launching, right-click the settings button at the bottom of the ZCode sidebar to open the ZCode Pro settings window.
43
43
 
44
+ ### 📦 npm / PyPI
45
+
46
+ At runtime the Node runtime bundled with ZCode is preferred; if ZCode cannot be found, system Node.js (≥ 22) is used.
47
+
48
+ ```bash
49
+ # Option 1: install/uninstall via npm
50
+ npm install -g zcode-pro
51
+ npm uninstall -g zcode-pro
52
+ # Option 2: install/uninstall via pip
53
+ pip install zcode-pro
54
+ pip uninstall zcode-pro
55
+
56
+ # Launch via the command
57
+ zcode-pro
58
+ # Add a Start-menu shortcut
59
+ zcode-pro setup
60
+ # Add a Start-menu shortcut + a desktop shortcut
61
+ zcode-pro setup --desktop
62
+ # Undo the shortcuts
63
+ zcode-pro setup --uninstall
64
+ ```
65
+
44
66
  ### 🐧 Linux / macOS
45
67
 
46
68
  ```bash
@@ -55,15 +77,19 @@ cd zcode-pro && ./scripts/install.sh
55
77
 
56
78
  ### 🪟 Windows
57
79
 
80
+ Run with **PowerShell (Admin)**:
58
81
  ```powershell
59
82
  git clone https://github.com/duanluan/zcode-pro.git
60
83
  cd zcode-pro
61
- powershell -ExecutionPolicy Bypass -File scripts\install.ps1 # Start menu shortcut
62
- powershell -ExecutionPolicy Bypass -File scripts\install.ps1 -Desktop # plus a desktop shortcut
63
- ```
64
84
 
65
- - Installs to `%LOCALAPPDATA%\ZCodePro`; shortcuts launch silently without a console window;
66
- - Uninstall: `powershell -ExecutionPolicy Bypass -File scripts\install.ps1 -Uninstall`.
85
+ # Add a Start-menu shortcut
86
+ powershell -ExecutionPolicy Bypass -File scripts\install.ps1
87
+ # Add a Start-menu shortcut + a desktop shortcut
88
+ powershell -ExecutionPolicy Bypass -File scripts\install.ps1 -Desktop
89
+
90
+ # Uninstall
91
+ powershell -ExecutionPolicy Bypass -File scripts\install.ps1 -Uninstall
92
+ ```
67
93
 
68
94
  ### 📦 AUR (Arch / Manjaro)
69
95
 
@@ -80,12 +106,19 @@ If you previously installed via `install.sh`, run `./scripts/install.sh --uninst
80
106
 
81
107
  ### 🚀 Run without installing
82
108
 
83
- No standalone Node.js required: when a system Node is missing, the runtime bundled with ZCode is used.
109
+ No standalone Node.js required: the runtime bundled with ZCode is preferred and used even without a system Node; works whether ZCode is installed in the default or a custom directory (auto-detected, falling back to the registry).
84
110
 
85
111
  ```bash
86
112
  git clone https://github.com/duanluan/zcode-pro.git && cd zcode-pro
87
- ./bin/zcode-pro # Linux / macOS (auto-detects /opt/ZCode, /Applications/ZCode.app)
88
- bin\zcode-pro.cmd # Windows (auto-detects %LOCALAPPDATA%\Programs\ZCode\ZCode.exe)
113
+
114
+ # Linux / macOS (auto-detects /opt/ZCode, /Applications/ZCode.app)
115
+ ./bin/zcode-pro
116
+ # Windows (auto-detects default locations, falls back to the registry)
117
+ bin\zcode-pro.cmd
118
+
119
+ # Manually add a Start-menu shortcut (--desktop also adds a desktop shortcut, --uninstall undoes)
120
+ ./bin/zcode-pro setup
121
+ bin\zcode-pro.cmd setup
89
122
  ```
90
123
 
91
124
  ## 📖 Reference
@@ -99,6 +132,7 @@ Command-line options (see `zcode-pro --help` for all):
99
132
  | `--zcode-path` | Explicit path to the ZCode executable |
100
133
  | `--inject-only` | Attach to a running instance only; do not launch a new one |
101
134
  | `--verbose` | Verbose logging |
135
+ | `setup [--desktop] [--uninstall]` | Install/undo local shortcuts (`--desktop` also adds a desktop one) |
102
136
 
103
137
  Each option can also be overridden with a `ZCODEPRO_*` environment variable.
104
138
 
@@ -2,9 +2,9 @@
2
2
 
3
3
  [English](README.en.md) | 简体中文
4
4
 
5
- ZCode 桌面版界面增强工具。不修改客户端文件,通过 ZCode Pro 快捷方式启动即可。
5
+ ZCode 桌面版界面增强工具。不修改客户端文件,通过 ZCode Pro 快捷方式或终端命令 `zcode-pro` 启动即可。
6
6
 
7
- ![AUR 版本](https://img.shields.io/aur/version/zcode-pro) ![平台](https://img.shields.io/badge/platform-win%20%7C%20mac%20%7C%20linux%20%7C%20AUR-blue)
7
+ ![npm](https://img.shields.io/npm/v/zcode-pro) ![PyPI](https://img.shields.io/pypi/v/zcode-pro) ![AUR 版本](https://img.shields.io/aur/version/zcode-pro) ![平台](https://img.shields.io/badge/platform-win%20%7C%20mac%20%7C%20linux%20%7C%20AUR-blue)
8
8
 
9
9
  > ⚙️ **打开设置:启动 ZCode Pro 后,右键点击 ZCode 侧边栏底部的设置按钮(齿轮)即可打开 ZCode Pro 设置窗口。**
10
10
 
@@ -41,6 +41,28 @@ ZCode 桌面版界面增强工具。不修改客户端文件,通过 ZCode Pro
41
41
 
42
42
  安装后,通过应用菜单 / 开始菜单中的“ZCode Pro”快捷方式或终端命令 `zcode-pro` 启动 ZCode,增强在该方式下自动生效;使用原有 ZCode 入口启动时不加载增强。
43
43
 
44
+ ### 📦 npm / PyPI
45
+
46
+ 运行时优先复用 ZCode 自带的运行时;未找到 ZCode 时回退系统 Node.js(≥ 22)。
47
+
48
+ ```bash
49
+ # 方式一:npm 安装/卸载
50
+ npm install -g zcode-pro
51
+ npm uninstall -g zcode-pro
52
+ # 方式二:pip 安装/卸载
53
+ pip install zcode-pro
54
+ pip uninstall zcode-pro
55
+
56
+ # 通过命令启动
57
+ zcode-pro
58
+ # 添加开始菜单快捷方式
59
+ zcode-pro setup
60
+ # 添加开始菜单快捷方式 + 桌面快捷方式
61
+ zcode-pro setup --desktop
62
+ # 撤销添加快捷方式
63
+ zcode-pro setup --uninstall
64
+ ```
65
+
44
66
  ### 🐧 Linux / macOS
45
67
 
46
68
  - 安装至 `~/.local`,可通过 `PREFIX=` 指定其他位置;
@@ -55,14 +77,18 @@ cd zcode-pro && ./scripts/install.sh
55
77
 
56
78
  ### 🪟 Windows
57
79
 
58
- - 程序安装至 `%LOCALAPPDATA%\ZCodePro`,快捷方式静默启动,不显示控制台窗口;
59
- - 卸载:`powershell -ExecutionPolicy Bypass -File scripts\install.ps1 -Uninstall`。
60
-
80
+ 使用 **PowerShell (管理员)** 运行:
61
81
  ```powershell
62
82
  git clone https://github.com/duanluan/zcode-pro.git
63
83
  cd zcode-pro
64
- powershell -ExecutionPolicy Bypass -File scripts\install.ps1 # 开始菜单快捷方式
65
- powershell -ExecutionPolicy Bypass -File scripts\install.ps1 -Desktop # 加桌面快捷方式
84
+
85
+ # 添加开始菜单快捷方式
86
+ powershell -ExecutionPolicy Bypass -File scripts\install.ps1
87
+ # 添加开始菜单快捷方式 + 桌面快捷方式
88
+ powershell -ExecutionPolicy Bypass -File scripts\install.ps1 -Desktop
89
+
90
+ # 卸载
91
+ powershell -ExecutionPolicy Bypass -File scripts\install.ps1 -Uninstall
66
92
  ```
67
93
 
68
94
  ### 📦 AUR
@@ -80,12 +106,19 @@ git clone https://aur.archlinux.org/zcode-pro.git && cd zcode-pro && makepkg -si
80
106
 
81
107
  ### 🚀 手动运行(不安装)
82
108
 
83
- 无需单独安装 Node.js:缺少系统 Node 时自动使用 ZCode 内置运行时。
109
+ 无需单独安装 Node.js:优先使用 ZCode 内置运行时,缺少系统 Node 时亦能运行;ZCode 安装在默认或自定义目录均可(自动探测,必要时查注册表)。
84
110
 
85
111
  ```bash
86
112
  git clone https://github.com/duanluan/zcode-pro.git && cd zcode-pro
87
- ./bin/zcode-pro # Linux / macOS(自动探测 /opt/ZCode、/Applications/ZCode.app)
88
- bin\zcode-pro.cmd # Windows(自动探测 %LOCALAPPDATA%\Programs\ZCode\ZCode.exe)
113
+
114
+ # Linux / macOS(自动探测 /opt/ZCode、/Applications/ZCode.app)
115
+ ./bin/zcode-pro
116
+ # Windows(自动探测默认安装位置,找不到时查注册表)
117
+ bin\zcode-pro.cmd
118
+
119
+ # 手动添加开始菜单快捷方式(--desktop 同时添加桌面快捷方式,--uninstall 撤销添加快捷方式)
120
+ ./bin/zcode-pro setup
121
+ bin\zcode-pro.cmd setup
89
122
  ```
90
123
 
91
124
  ## 📖 参考
@@ -99,6 +132,7 @@ bin\zcode-pro.cmd # Windows(自动探测 %LOCALAPPDATA%\Programs\ZCod
99
132
  | `--zcode-path` | 显式指定 ZCode 可执行文件 |
100
133
  | `--inject-only` | 只接管已在运行的实例,不启动新实例 |
101
134
  | `--verbose` | 详细日志 |
135
+ | `setup [--desktop] [--uninstall]` | 安装/撤销本机快捷方式(`--desktop` 同时加桌面) |
102
136
 
103
137
  各选项也可用环境变量 `ZCODEPRO_*` 覆盖。
104
138
 
@@ -1,24 +1,29 @@
1
- @echo off
2
- rem ZCode Pro 启动器(Windows)
3
- rem 优先用 ZCode 自带的 Node 运行时(ELECTRON_RUN_AS_NODE),无需安装 Node.js。
4
- setlocal
5
- set "SCRIPT_DIR=%~dp0"
6
- set "APP_DIR=%SCRIPT_DIR%.."
7
- set "ENTRY=%APP_DIR%\cli.mjs"
8
-
9
- where node >nul 2>nul
10
- if %errorlevel%==0 (
11
- node "%ENTRY%" %*
12
- exit /b %errorlevel%
13
- )
14
-
15
- set "ZCODE_BIN=%ZCODEPRO_ZCODE_PATH%"
16
- if not exist "%ZCODE_BIN%" set "ZCODE_BIN=%LOCALAPPDATA%\Programs\ZCode\ZCode.exe"
17
- if not exist "%ZCODE_BIN%" set "ZCODE_BIN=%PROGRAMFILES%\ZCode\ZCode.exe"
18
- if not exist "%ZCODE_BIN%" (
19
- echo [zcodepro] 未找到 node,也未找到 ZCode 可执行文件。请安装 Node.js ^>= 22 或设置 ZCODEPRO_ZCODE_PATH。 1>&2
20
- exit /b 1
21
- )
22
- set "ELECTRON_RUN_AS_NODE=1"
23
- "%ZCODE_BIN%" "%ENTRY%" %*
24
- exit /b %errorlevel%
1
+ @echo off
2
+ rem ZCode Pro 启动器(Windows)
3
+ rem 优先用 ZCode 自带的 Node 运行时(ELECTRON_RUN_AS_NODE),无需安装 Node.js。
4
+ setlocal
5
+ set "SCRIPT_DIR=%~dp0"
6
+ set "APP_DIR=%SCRIPT_DIR%.."
7
+ set "ENTRY=%APP_DIR%\cli.mjs"
8
+
9
+ where node >nul 2>nul
10
+ if %errorlevel%==0 (
11
+ node "%ENTRY%" %*
12
+ exit /b %errorlevel%
13
+ )
14
+
15
+ set "ZCODE_BIN=%ZCODEPRO_ZCODE_PATH%"
16
+ if not exist "%ZCODE_BIN%" set "ZCODE_BIN=%LOCALAPPDATA%\Programs\ZCode\ZCode.exe"
17
+ if not exist "%ZCODE_BIN%" set "ZCODE_BIN=%PROGRAMFILES%\ZCode\ZCode.exe"
18
+ rem Custom install dir: look up App Paths in registry (per-machine then per-user).
19
+ if not exist "%ZCODE_BIN%" for /f "tokens=2*" %%A in (
20
+ 'reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\zcode.exe" /ve 2^>nul ^|^|
21
+ reg query "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\zcode.exe" /ve 2^>nul'
22
+ ) do if not exist "%ZCODE_BIN%" if exist "%%B" set "ZCODE_BIN=%%B"
23
+ if not exist "%ZCODE_BIN%" (
24
+ echo [zcodepro] 未找到 node,也未找到 ZCode 可执行文件。请安装 Node.js ^>= 22 或设置 ZCODEPRO_ZCODE_PATH。 1>&2
25
+ exit /b 1
26
+ )
27
+ set "ELECTRON_RUN_AS_NODE=1"
28
+ "%ZCODE_BIN%" "%ENTRY%" %*
29
+ exit /b %errorlevel%
@@ -3,20 +3,24 @@
3
3
  // 优先用 ZCode 自带的 Node 运行时(ELECTRON_RUN_AS_NODE),避免系统 node 过旧
4
4
  // (<22.5 缺 node:sqlite、<22 缺全局 WebSocket)时启动失败;
5
5
  // 找不到 ZCode 可执行文件时回退当前 node 进程(npm 环境必有 node)。
6
- import { spawn } from 'node:child_process';
6
+ import { spawn, spawnSync } from 'node:child_process';
7
7
  import { fileURLToPath } from 'node:url';
8
8
 
9
- const cliPath = fileURLToPath(new URL('../cli.mjs', import.meta.url));
9
+ // 动态导入必须用 URL 对象:Windows 上绝对路径(C:\...)会被当作 c: 协议报错
10
+ const cliUrl = new URL('../cli.mjs', import.meta.url);
11
+ const cliPath = fileURLToPath(cliUrl);
10
12
  const argv = process.argv.slice(2);
11
13
 
12
14
  async function runWithZcodeRuntime() {
13
15
  const { resolveZcodeExecutable } = await import('../src/host/paths.mjs');
14
16
  const zcodeBin = resolveZcodeExecutable('');
17
+ const env = { ...process.env, ELECTRON_RUN_AS_NODE: '1' };
18
+ // 预检:注册表等来源找到的 ZCode 可能不完整(缺 resources/ICU 数据等),以 Node
19
+ // 模式跑不起来的直接抛错回退系统 node,避免启动器跟着报难懂的错误退出
20
+ const pre = spawnSync(zcodeBin, ['-e', 'process.exit(0)'], { env, stdio: 'ignore' });
21
+ if (pre.status !== 0) throw new Error(`ZCode 运行时不可用(${zcodeBin})`);
15
22
  await new Promise((resolve, reject) => {
16
- const child = spawn(zcodeBin, [cliPath, ...argv], {
17
- stdio: 'inherit',
18
- env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' },
19
- });
23
+ const child = spawn(zcodeBin, [cliPath, ...argv], { stdio: 'inherit', env });
20
24
  child.on('error', reject);
21
25
  child.on('exit', (code, signal) => {
22
26
  if (signal) process.kill(process.pid, signal);
@@ -29,5 +33,5 @@ async function runWithZcodeRuntime() {
29
33
  try {
30
34
  await runWithZcodeRuntime();
31
35
  } catch {
32
- await import(cliPath);
36
+ await import(cliUrl);
33
37
  }
@@ -1,16 +1,16 @@
1
- ' ZCode Pro 静默启动器(Windows)
2
- ' 以隐藏窗口运行 zcode-pro.cmd:快捷方式启动时不会常驻黑色控制台窗口,
3
- ' 而 helper/注入进程继续在后台维持增强。
4
- Set fso = CreateObject("Scripting.FileSystemObject")
5
- Set sh = CreateObject("WScript.Shell")
6
- dir = fso.GetParentFolderName(WScript.ScriptFullName)
7
- cmd = """" & dir & "\zcode-pro.cmd"""
8
- If WScript.Arguments.Count > 0 Then
9
- Dim parts()
10
- ReDim parts(WScript.Arguments.Count - 1)
11
- For i = 0 To WScript.Arguments.Count - 1
12
- parts(i) = """" & WScript.Arguments(i) & """"
13
- Next
14
- cmd = cmd & " " & Join(parts, " ")
15
- End If
16
- sh.Run cmd, 0, False
1
+ ' ZCode Pro 静默启动器(Windows)
2
+ ' 以隐藏窗口运行 zcode-pro.cmd:快捷方式启动时不会常驻黑色控制台窗口,
3
+ ' 而 helper/注入进程继续在后台维持增强。
4
+ Set fso = CreateObject("Scripting.FileSystemObject")
5
+ Set sh = CreateObject("WScript.Shell")
6
+ dir = fso.GetParentFolderName(WScript.ScriptFullName)
7
+ cmd = """" & dir & "\zcode-pro.cmd"""
8
+ If WScript.Arguments.Count > 0 Then
9
+ Dim parts()
10
+ ReDim parts(WScript.Arguments.Count - 1)
11
+ For i = 0 To WScript.Arguments.Count - 1
12
+ parts(i) = """" & WScript.Arguments(i) & """"
13
+ Next
14
+ cmd = cmd & " " & Join(parts, " ")
15
+ End If
16
+ sh.Run cmd, 0, False
@@ -611,6 +611,18 @@
611
611
  failed: "Operation failed",
612
612
  retryHint: "Please retry"
613
613
  };
614
+ var cachedSystemLocale = null;
615
+ function startLocaleResolver() {
616
+ if (cachedSystemLocale !== null) return;
617
+ const get = typeof window !== "undefined" ? window.zcode?.getSystemLocale : null;
618
+ if (typeof get !== "function") return;
619
+ Promise.resolve(get()).then((locale) => {
620
+ if (typeof locale === "string" && locale) {
621
+ cachedSystemLocale = locale.toLowerCase().startsWith("zh") ? "zh-CN" : "en-US";
622
+ }
623
+ }).catch(() => {
624
+ });
625
+ }
614
626
  function isZhLocale() {
615
627
  let pref = null;
616
628
  try {
@@ -618,6 +630,7 @@
618
630
  } catch {
619
631
  }
620
632
  if (pref === "zh-CN" || pref === "en-US") return pref === "zh-CN";
633
+ if (cachedSystemLocale) return cachedSystemLocale === "zh-CN";
621
634
  return /^zh/i.test(navigator.language || "zh-CN");
622
635
  }
623
636
  function t() {
@@ -5747,8 +5760,16 @@
5747
5760
  if (typeof window === "undefined") return;
5748
5761
  if (window.__zcodeproInjected) return;
5749
5762
  window.__zcodeproInjected = true;
5763
+ try {
5764
+ startLocaleResolver();
5765
+ } catch {
5766
+ }
5750
5767
  const start = () => {
5751
5768
  ensureStyle();
5769
+ try {
5770
+ startLocaleResolver();
5771
+ } catch {
5772
+ }
5752
5773
  observeRadixPopups((content) => {
5753
5774
  try {
5754
5775
  handleProjectMenu(content);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zcode-pro",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "ZCode Pro — 为 ZCode 桌面版增加界面增强(不修改客户端文件,通过 ZCode Pro 快捷方式启动即可),核心功能:项目自定义别名",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,7 +15,7 @@ import { pickFolderSystem } from './pickFolder.mjs';
15
15
  import { reorderWorkspaceTasks, reorderGroupMembers } from './taskOrder.mjs';
16
16
  import { webdavRequest, webdavPutWithMkcol, webdavHttpText } from './webdav.mjs';
17
17
 
18
- const VERSION = '0.16.0';
18
+ const VERSION = '0.17.0';
19
19
 
20
20
  // 全局提示词固定在用户主目录:官方加载器按 HOME/USERPROFILE 拼 .zcode/AGENTS.md,
21
21
  // 不读 ZCODE_DATA_BASE_DIR(数据根迁走时全局指令仍在原位)。
@@ -12,6 +12,7 @@ import { randomBytes } from 'node:crypto';
12
12
  import { resolveZcodeExecutable, resolveDataRootDir } from './paths.mjs';
13
13
  import { CdpConnection, fetchBrowserWsUrl } from './cdp.mjs';
14
14
  import { startHelper } from './helper.mjs';
15
+ import { runSetup } from './setup.mjs';
15
16
 
16
17
  const here = dirname(fileURLToPath(import.meta.url));
17
18
  const INJECT_BUNDLE = resolve(here, '..', '..', 'dist', 'inject.js');
@@ -29,6 +30,11 @@ function usage() {
29
30
  --verbose 输出详细日志
30
31
  -h, --help 显示帮助
31
32
 
33
+ 子命令:
34
+ setup 安装本机集成(快捷方式:Linux 应用菜单 / Windows 开始菜单 / macOS 应用)
35
+ setup --desktop 同时在桌面创建快捷方式
36
+ setup --uninstall 移除由 setup 安装的集成(含桌面)
37
+
32
38
  环境变量:
33
39
  ZCODEPRO_ZCODE_PATH ZCode 可执行文件路径(同 --zcode-path)
34
40
  ZCODEPRO_CDP_PORT CDP 端口
@@ -47,6 +53,9 @@ function parseArgs(argv) {
47
53
  else if (a === '--inject-only' || a === '--no-launch') out.injectOnly = true;
48
54
  else if (a === '--verbose' || a === '-v') out.verbose = true;
49
55
  else if (a === '-h' || a === '--help') out.help = true;
56
+ else if (a === 'setup') out.setup = true;
57
+ else if (a === '--uninstall') out.setupUninstall = true;
58
+ else if (a === '--desktop') out.setupDesktop = true;
50
59
  else { console.error('未知参数: ' + a); out.help = true; }
51
60
  }
52
61
  out.cdpPort = out.cdpPort || Number(process.env.ZCODEPRO_CDP_PORT) || 9333;
@@ -122,6 +131,16 @@ async function buildInjectSource(token, helperPort, features) {
122
131
  export async function run(argv) {
123
132
  const args = parseArgs(argv);
124
133
  if (args.help) { console.log(usage()); return; }
134
+ // setup 不依赖 ZCode 运行(装快捷方式等本机集成),先于 ZCode 定位处理
135
+ if (args.setup || args.setupUninstall || args.setupDesktop) {
136
+ if (!args.setup && (args.setupUninstall || args.setupDesktop)) {
137
+ console.error('未知用法: --uninstall / --desktop 仅可与 setup 连用');
138
+ console.log(usage());
139
+ return;
140
+ }
141
+ await runSetup({ uninstall: args.setupUninstall, desktop: args.setupDesktop });
142
+ return;
143
+ }
125
144
  const log = (...a) => { if (args.verbose) console.log('[zcodepro]', ...a); };
126
145
 
127
146
  let exe;
@@ -1,8 +1,51 @@
1
1
  // 跨平台定位 ZCode 桌面版可执行文件。
2
- // 优先级:--zcode-path 参数 > ZCODEPRO_ZCODE_PATH 环境变量 > 平台默认候选路径 > PATH 上的 zcode。
2
+ // 优先级:--zcode-path 参数 > ZCODEPRO_ZCODE_PATH 环境变量 > 平台默认候选路径 > 注册表(Windows) > PATH 上的 zcode。
3
3
  import { existsSync } from 'node:fs';
4
4
  import { join } from 'node:path';
5
5
  import { arch, homedir, platform } from 'node:os';
6
+ import { execFileSync } from 'node:child_process';
7
+
8
+ // Windows 注册表探测(结果缓存):自定义安装目录的安装器会在 App Paths(可执行
9
+ // 文件全路径)或卸载表 DisplayIcon(控制面板-卸载软件里可见)留有登记,固定
10
+ // 候选找不到时兜底。reg 输出按行取 REG_SZ 后的值,容忍引号与 ",0" 图标序号后缀。
11
+ let winRegistryCandidates;
12
+
13
+ function windowsRegistryCandidates() {
14
+ if (winRegistryCandidates) return winRegistryCandidates;
15
+ winRegistryCandidates = [];
16
+ const seen = new Set();
17
+ const push = (raw) => {
18
+ const p = String(raw || '').trim().replace(/^"|"$/g, '').replace(/,\d+$/, '');
19
+ if (/\.exe$/i.test(p) && !seen.has(p.toLowerCase())) {
20
+ seen.add(p.toLowerCase());
21
+ winRegistryCandidates.push(p);
22
+ }
23
+ };
24
+ const reg = (args) => {
25
+ try {
26
+ return execFileSync('reg', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
27
+ } catch { return ''; }
28
+ };
29
+ const valueAfter = (out) => (out.split(/REG_SZ\s+/)[1] || '').trim();
30
+ // App Paths:安装器注册的可执行文件全路径(ShellExecute 同款查找)
31
+ for (const root of ['HKLM', 'HKCU']) {
32
+ const out = reg(['query', `${root}\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\zcode.exe`, '/ve']);
33
+ if (out) push(valueAfter(out));
34
+ }
35
+ // 卸载表 DisplayIcon:安装目录自定义时通常只有这里能找到
36
+ for (const key of [
37
+ 'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
38
+ 'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
39
+ 'HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
40
+ ]) {
41
+ const out = reg(['query', key, '/s', '/v', 'DisplayIcon']);
42
+ for (const line of out.split(/\r?\n/)) {
43
+ const seg = (line.split(/REG_SZ\s+/)[1] || '').trim();
44
+ if (seg && /zcode/i.test(seg)) push(seg);
45
+ }
46
+ }
47
+ return winRegistryCandidates;
48
+ }
6
49
 
7
50
  function candidates() {
8
51
  const home = homedir();
@@ -14,6 +57,7 @@ function candidates() {
14
57
  join(localAppData, 'Programs', 'zcode', 'zcode.exe'),
15
58
  join(programFiles, 'ZCode', 'ZCode.exe'),
16
59
  join(programFiles, 'zcode', 'zcode.exe'),
60
+ ...windowsRegistryCandidates(),
17
61
  ];
18
62
  }
19
63
  if (platform() === 'darwin') {
@@ -0,0 +1,514 @@
1
+ // zcode-pro setup:本机集成设置入口(npm / pip 等包管理器安装不写系统目录,
2
+ // 需要菜单入口等集成时显式运行本子命令)。按「设置项」组织(id / 标题 /
3
+ // install / uninstall),以后新增设置项往 items 里加即可。
4
+ // setup 不依赖 ZCode 运行,在 main.run 里先于 ZCode 定位处理。
5
+ //
6
+ // 快捷方式各平台形态(与 install.sh / install.ps1 的条目内容保持一致):
7
+ // - Linux:应用菜单 .desktop(Exec 指向 PATH 中的 zcode-pro,Icon=zcode 复用 ZCode 图标)
8
+ // - Windows:开始菜单 .lnk → wscript 运行生成的 vbs(静默启动、无控制台窗口),
9
+ // 图标取 ZCode.exe;包管理器安装不带 vbs,这里按 PATH 命令现生成
10
+ // - macOS:~/Applications 下的“ZCode Pro.app”包(图标复制 ZCode 的 app.icns,无则省略)
11
+ //
12
+ // Windows 中文编码注意(vbs / PowerShell 均受代码页影响):
13
+ // - vbs 由 wscript 按 ANSI 代码页解析,不支持 UTF-8——生成内容只用 ASCII;
14
+ // - PowerShell 脚本一律经 -EncodedCommand 传 Base64(UTF-16LE),中文不经命令行与
15
+ // 文件编码,避开代码页问题;
16
+ // - 仓库内 PowerShell 脚本(install.ps1 等)必须保存为带 BOM 的 UTF-8。
17
+ import { execFileSync } from 'node:child_process';
18
+ import { accessSync, chmodSync, constants as fsConstants, copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
19
+ import { basename, dirname, join, sep } from 'node:path';
20
+ import { homedir, platform } from 'node:os';
21
+ import { fileURLToPath } from 'node:url';
22
+ import { resolveZcodeExecutable } from './paths.mjs';
23
+
24
+ const here = dirname(fileURLToPath(import.meta.url)); // …/src/host(随包安装后为包内对应目录)
25
+ const pkgRoot = dirname(dirname(here)); // 安装根目录(含 cli.mjs 与 bin/)
26
+
27
+ const SUPPORTED = ['linux', 'win32', 'darwin'];
28
+
29
+ // —— PATH 中的 zcode-pro 命令 ——
30
+
31
+ function commandInPath(plat) {
32
+ const pathSep = plat === 'win32' ? ';' : ':';
33
+ // Windows 上 npm 会生成 zcode-pro.cmd / zcode-pro.ps1,pip 生成 zcode-pro.exe,
34
+ // 只有 .cmd / .exe 能被 wscript 的 sh.Run 直接运行
35
+ const names = plat === 'win32' ? ['zcode-pro.cmd', 'zcode-pro.exe'] : ['zcode-pro'];
36
+ for (const dir of (process.env.PATH || '').split(pathSep)) {
37
+ if (!dir) continue;
38
+ for (const name of names) {
39
+ const p = join(dir, name);
40
+ try {
41
+ if (plat === 'win32') {
42
+ if (existsSync(p)) return p;
43
+ } else {
44
+ accessSync(p, fsConstants.X_OK); // 可执行
45
+ return p;
46
+ }
47
+ } catch { /* 继续找 */ }
48
+ }
49
+ }
50
+ return null;
51
+ }
52
+
53
+ // PATH 命令是否属于当前运行的这份安装:
54
+ // - POSIX npm / AUR / install.sh:命令(经符号链接)最终指向本包目录内的启动器;
55
+ // - Windows npm:全局命令是普通 .cmd(非符号链接),按 npm 目录布局认定——
56
+ // 命令旁的 node_modules\zcode-pro 是否即当前包;
57
+ // - pip:命令是 Python 控制台脚本,位于解释器目录而非包目录——POSIX 为文本脚本,
58
+ // 校验其 shebang 解释器所属环境是否包含当前包;Windows 为 exe 启动器,读不出
59
+ // 内容,只能按文件名认定(同机多 Python 环境时以 PATH 靠前者为准,属已知局限)。
60
+ function sameInstall(cmdPath, plat) {
61
+ let real = cmdPath;
62
+ try { real = realpathSync(cmdPath); } catch { /* 保持原路径 */ }
63
+ if (real === pkgRoot || real.startsWith(pkgRoot + sep)) return true;
64
+ if (plat === 'win32') {
65
+ try {
66
+ if (realpathSync(join(dirname(real), 'node_modules', 'zcode-pro')) === pkgRoot) return true;
67
+ } catch { /* 布局不符则继续 */ }
68
+ return pkgRoot.split(/[\\/]/).includes('zcode_pro')
69
+ && basename(real).toLowerCase().startsWith('zcode-pro');
70
+ }
71
+ if (pkgRoot.split(sep).includes('zcode_pro')) {
72
+ try {
73
+ const shebang = readFileSync(real, 'utf-8').match(/^#!\s*(\S+)/);
74
+ if (shebang && pkgRoot.startsWith(dirname(dirname(shebang[1])) + sep)) return true;
75
+ } catch { /* 读不到内容则不认定 */ }
76
+ }
77
+ return false;
78
+ }
79
+
80
+ // —— Windows 快捷方式(路径 / 脚本生成) ——
81
+
82
+ // 本功能创建的 .lnk 归属标记(PowerShell -like 通配):vbs 方案的 Arguments 指向
83
+ // 我们的 vbs;WSH 异常降级方案的 start 窗口标题用固定名。卸载据此只删自己的
84
+ const OWN_LNK_MARKERS = ['*zcode-pro-setup.vbs*', '*ZCodePro-Setup*'];
85
+
86
+ // 归属判断的 PowerShell 表达式:每个 -like 都必须带完整变量前缀(写成共享的
87
+ // “-like a -or -like b” 是语法错误)
88
+ const ownsExpr = (v) => OWN_LNK_MARKERS.map((m) => `${v}.Arguments -like '${m}'`).join(' -or ');
89
+
90
+ function windowsPaths() {
91
+ const appData = process.env.APPDATA || join(homedir(), 'AppData', 'Roaming');
92
+ const localAppData = process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local');
93
+ return {
94
+ appData,
95
+ localAppData,
96
+ lnkPath: join(appData, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'ZCode Pro.lnk'),
97
+ vbsDir: join(localAppData, 'ZCodePro', 'setup'),
98
+ vbsPath: join(localAppData, 'ZCodePro', 'setup', 'zcode-pro-setup.vbs'),
99
+ };
100
+ }
101
+
102
+ // 经 Base64(UTF-16LE) 的 -EncodedCommand 执行 PowerShell:中文不经过命令行与
103
+ // 文件编码,任何系统代码页下都安全。返回 stdout;非零退出码抛错
104
+ function runPowerShell(script) {
105
+ const b64 = Buffer.from(script, 'utf16le').toString('base64');
106
+ return execFileSync('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-EncodedCommand', b64],
107
+ { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' });
108
+ }
109
+
110
+ // WSH 是否可用:复现 vbs 的启动调用(WScript.Shell.Run 隐藏窗口执行)。
111
+ // 部分机器(精简系统/安全软件/组件禁用)上该调用报 0x800A01AD,需降级 cmd 启动。
112
+ // ZCODEPRO_FORCE_NO_WSH=1 供测试强制走降级路径。
113
+ let wshOkCache;
114
+
115
+ function windowsWshOk() {
116
+ if (process.env.ZCODEPRO_FORCE_NO_WSH === '1') return false;
117
+ if (wshOkCache !== undefined) return wshOkCache;
118
+ try {
119
+ runPowerShell([
120
+ "$ErrorActionPreference = 'Stop'",
121
+ '$sh = New-Object -ComObject WScript.Shell',
122
+ "$sh.Run('cmd /c exit 0', 0, $true) | Out-Null",
123
+ ].join('\r\n'));
124
+ wshOkCache = true;
125
+ } catch {
126
+ wshOkCache = false;
127
+ }
128
+ return wshOkCache;
129
+ }
130
+
131
+ // .lnk 是否本功能创建(按 Arguments 里的归属标记判断;读不到按外来处理,不覆盖)
132
+ function lnkIsOurs(lnkPath) {
133
+ try {
134
+ const out = runPowerShell([
135
+ "$ErrorActionPreference = 'Stop'",
136
+ `$l = (New-Object -ComObject WScript.Shell).CreateShortcut('${psQuote(lnkPath)}')`,
137
+ `Write-Output (${OWN_LNK_MARKERS.map((m) => `$l.Arguments -like '${m}'`).join(' -or ')})`,
138
+ ].join('\r\n'));
139
+ return /True/.test(out);
140
+ } catch {
141
+ return false;
142
+ }
143
+ }
144
+
145
+ const psQuote = (s) => String(s).replace(/'/g, "''"); // PowerShell 单引号字面量转义
146
+
147
+ // 快捷方式计划(导出仅供测试审查,不执行)。desktop 为真时同时创建桌面快捷方式,
148
+ // 桌面目录由 PowerShell 的 GetFolderPath 解析(正确跟随 OneDrive 重定向等)。
149
+ // wshOk 为假(WSH 异常的机器)时降级:.lnk 直接经 cmd 最小化启动,不再依赖
150
+ // wscript+vbs,代价是启动瞬间任务栏短暂闪一个最小化控制台
151
+ export function windowsShortcutPlan(cmd, desktop = false, wshOk = true) {
152
+ const paths = windowsPaths();
153
+ // vbs 由 wscript 按 ANSI 代码页解析(不支持 UTF-8),内容只用 ASCII
154
+ const vbsContent = [
155
+ `' Generated by zcode-pro setup: run zcode-pro silently (no console window); forwards all arguments`,
156
+ 'Set sh = CreateObject("WScript.Shell")',
157
+ `cmd = """${cmd.replace(/"/g, '""')}"""`,
158
+ 'If WScript.Arguments.Count > 0 Then',
159
+ ' Dim parts()',
160
+ ' ReDim parts(WScript.Arguments.Count - 1)',
161
+ ' For i = 0 To WScript.Arguments.Count - 1',
162
+ ' parts(i) = """" & WScript.Arguments(i) & """"',
163
+ ' Next',
164
+ ' cmd = cmd & " " & Join(parts, " ")',
165
+ 'End If',
166
+ 'sh.Run cmd, 0, False',
167
+ '',
168
+ ].join('\r\n');
169
+ // 图标复用已安装的 ZCode:探测走 paths.mjs 同一条优先级(含注册表,覆盖自定义
170
+ // 安装目录);未安装 ZCode 时留空,快捷方式回落系统默认图标
171
+ let icon = '';
172
+ try { icon = resolveZcodeExecutable(''); } catch { /* 未安装 ZCode,省略图标 */ }
173
+ const iconLine = icon ? ` $l.IconLocation = '${psQuote(`${icon},0`)}'` : '';
174
+ const setProps = wshOk ? [
175
+ 'function setProps($l) {',
176
+ ' $l.TargetPath = "$env:SystemRoot\\System32\\wscript.exe"',
177
+ ` $l.Arguments = '${psQuote(`"${paths.vbsPath}"`)}'`,
178
+ ` $l.WorkingDirectory = '${psQuote(paths.vbsDir)}'`,
179
+ " $l.Description = 'ZCode 桌面版增强启动器(自定义别名等,不修改客户端文件)'",
180
+ iconLine,
181
+ '}',
182
+ ] : [
183
+ 'function setProps($l) {',
184
+ ' $l.TargetPath = "$env:SystemRoot\\System32\\cmd.exe"',
185
+ ` $l.Arguments = '/c start "ZCodePro-Setup" /min "${psQuote(cmd)}"'`,
186
+ ` $l.WorkingDirectory = '${psQuote(paths.vbsDir)}'`,
187
+ " $l.Description = 'ZCode 桌面版增强启动器(自定义别名等,不修改客户端文件)'",
188
+ iconLine,
189
+ ' $l.WindowStyle = 7',
190
+ '}',
191
+ ];
192
+ const psScript = [
193
+ "$ErrorActionPreference = 'Stop'",
194
+ '$wsh = New-Object -ComObject WScript.Shell',
195
+ ...setProps,
196
+ `$lnk = $wsh.CreateShortcut('${psQuote(paths.lnkPath)}')`,
197
+ 'setProps $lnk',
198
+ '$lnk.Save()',
199
+ ...(desktop ? [
200
+ "$desk = Join-Path ([Environment]::GetFolderPath('Desktop')) 'ZCode Pro.lnk'",
201
+ '$dl = $wsh.CreateShortcut($desk)',
202
+ '# 已存在且非本功能创建(Arguments 无归属标记)时不覆盖',
203
+ `if (-not (Test-Path $desk) -or (${ownsExpr('$dl')})) {`,
204
+ ' setProps $dl',
205
+ ' $dl.Save()',
206
+ '}',
207
+ ] : []),
208
+ ].filter(Boolean).join('\r\n');
209
+ return { ...paths, vbsContent, psScript, icon, wshOk };
210
+ }
211
+
212
+ // —— 快捷方式设置项 ——
213
+
214
+ const DESKTOP_FILE = 'zcode-pro.desktop';
215
+ // setup 创建的条目带标记:卸载只动自己的,不误删 install.sh / AUR / install.ps1 的同名条目
216
+ const SETUP_MARK = 'X-ZCodePro-Setup=true';
217
+
218
+ function desktopEntry(execCmd) {
219
+ const exec = /\s/.test(execCmd) ? `"${execCmd}"` : execCmd;
220
+ return [
221
+ '[Desktop Entry]',
222
+ 'Type=Application',
223
+ 'Name=ZCode Pro',
224
+ 'Comment=ZCode 桌面版增强启动器(自定义别名等,不修改客户端文件)',
225
+ `Exec=${exec}`,
226
+ 'Icon=zcode',
227
+ 'Terminal=false',
228
+ 'Categories=Development;IDE;',
229
+ 'Keywords=zcode;pro;enhance;',
230
+ SETUP_MARK,
231
+ '',
232
+ ].join('\n');
233
+ }
234
+
235
+ function applicationsDir() {
236
+ return join(process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share'), 'applications');
237
+ }
238
+
239
+ // Linux 桌面目录:优先 xdg-user-dir(正确跟随本地化目录名,如 ~/桌面)
240
+ function desktopDir() {
241
+ try {
242
+ const dir = execFileSync('xdg-user-dir', ['DESKTOP'], { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
243
+ if (dir) return dir;
244
+ } catch { /* 无该命令时按常见值回退 */ }
245
+ return existsSync(join(homedir(), 'Desktop')) ? join(homedir(), 'Desktop') : join(homedir(), '桌面');
246
+ }
247
+
248
+ // macOS 应用包目录、桌面入口(指向应用包的符号链接)与图标来源
249
+ function macAppBundle() {
250
+ return join(homedir(), 'Applications', 'ZCode Pro.app');
251
+ }
252
+
253
+ function macDesktopLink() {
254
+ return join(homedir(), 'Desktop', 'ZCode Pro.app');
255
+ }
256
+
257
+ function plistContent(hasIcon) {
258
+ const pairs = [
259
+ ['CFBundleName', 'ZCode Pro'],
260
+ ['CFBundleDisplayName', 'ZCode Pro'],
261
+ ['CFBundleIdentifier', 'com.duanluan.zcodepro'],
262
+ ['CFBundleVersion', '1'],
263
+ ['CFBundleShortVersionString', '1'],
264
+ ['CFBundlePackageType', 'APPL'],
265
+ ['CFBundleExecutable', 'zcode-pro'],
266
+ ];
267
+ if (hasIcon) pairs.push(['CFBundleIconFile', 'app']);
268
+ const body = pairs.map(([k, v]) => ` <key>${k}</key>\n <string>${v}</string>`).join('\n');
269
+ return `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict>\n${body}\n</dict>\n</plist>\n`;
270
+ }
271
+
272
+ const shortcutItem = {
273
+ id: 'desktop-shortcut',
274
+ title: '快捷方式“ZCode Pro”(应用菜单/开始菜单,图标复用 ZCode)',
275
+ supported: (plat) => SUPPORTED.includes(plat),
276
+
277
+ install(plat, opts = {}) {
278
+ const cmd = commandInPath(plat);
279
+ if (!cmd) {
280
+ throw new Error('未在 PATH 中找到 zcode-pro 命令,无法创建快捷方式;请确认安装方式后重试。');
281
+ }
282
+ if (!sameInstall(cmd, plat)) {
283
+ throw new Error(`PATH 中的 zcode-pro(${cmd})不属于当前运行的这份安装,` +
284
+ '为避免快捷方式指向旧版本已跳过;请用那份安装自己的方式管理快捷方式。');
285
+ }
286
+ if (plat === 'linux') return this.installLinux(cmd, opts);
287
+ if (plat === 'win32') return this.installWindows(cmd, opts);
288
+ return this.installMac(cmd, opts);
289
+ },
290
+
291
+ installLinux(cmd, opts = {}) {
292
+ // 系统级(AUR 等)已有同名条目时不再建用户级:desktop 文件 id 相同时用户级
293
+ // 会遮蔽系统级(项目既有约定),残留后表现为菜单一直运行另一份
294
+ if (existsSync('/usr/share/applications/zcode-pro.desktop')) {
295
+ return { skipped: '检测到系统级 ZCode Pro 快捷方式(AUR 等已安装),用户级条目会遮蔽它,已跳过' };
296
+ }
297
+ const appsDir = applicationsDir();
298
+ const target = join(appsDir, DESKTOP_FILE);
299
+ const parts = [];
300
+ if (existsSync(target)) {
301
+ const cur = readFileSync(target, 'utf-8');
302
+ if (!cur.includes(SETUP_MARK)) {
303
+ return { skipped: `${target} 已存在且非 setup 创建(如 install.sh),未覆盖` };
304
+ }
305
+ const exec = (cur.match(/^Exec=(.+)$/m) || [])[1] || '';
306
+ if (exec.includes(cmd)) {
307
+ parts.push('应用菜单条目已存在且指向当前命令,无需重复创建');
308
+ } else {
309
+ writeFileSync(target, desktopEntry(cmd));
310
+ parts.push(`已更新 ${target}(Exec=${cmd})`);
311
+ }
312
+ } else {
313
+ mkdirSync(appsDir, { recursive: true });
314
+ writeFileSync(target, desktopEntry(cmd));
315
+ parts.push(`已写入 ${target}(Exec=${cmd})`);
316
+ }
317
+ try { execFileSync('update-desktop-database', [appsDir], { stdio: 'ignore' }); } catch { /* 无该命令时忽略 */ }
318
+ // 桌面条目独立判断(不因菜单条目幂等而短路)
319
+ if (opts.desktop) {
320
+ const dDir = desktopDir();
321
+ const dTarget = join(dDir, DESKTOP_FILE);
322
+ if (!existsSync(dTarget) || readFileSync(dTarget, 'utf-8').includes(SETUP_MARK)) {
323
+ writeFileSync(dTarget, desktopEntry(cmd));
324
+ parts.push(`桌面 ${dTarget}`);
325
+ } else {
326
+ parts.push('桌面已存在非 setup 创建的条目,未覆盖');
327
+ }
328
+ }
329
+ if (parts.length === 1 && parts[0].startsWith('应用菜单条目已存在')) return { skipped: parts[0] };
330
+ return { detail: parts.join(';') };
331
+ },
332
+
333
+ installWindows(cmd, opts = {}) {
334
+ const wshOk = windowsWshOk();
335
+ const plan = windowsShortcutPlan(cmd, !!opts.desktop, wshOk);
336
+ // .lnk 已存在且不属于本功能(Arguments 无归属标记)= install.ps1 等其他方式创建,不覆盖
337
+ if (existsSync(plan.lnkPath) && !lnkIsOurs(plan.lnkPath)) {
338
+ return { skipped: `已存在其他方式创建的 ${plan.lnkPath},未覆盖` };
339
+ }
340
+ if (wshOk) {
341
+ mkdirSync(plan.vbsDir, { recursive: true });
342
+ writeFileSync(plan.vbsPath, plan.vbsContent);
343
+ }
344
+ runPowerShell(plan.psScript);
345
+ const how = wshOk
346
+ ? `经 ${plan.vbsPath} 静默启动`
347
+ : '本机 WSH 异常,经 cmd 最小化启动(启动时会短暂闪烁控制台窗口)';
348
+ return { detail: `已创建 ${plan.lnkPath}(${how}${opts.desktop ? ',含桌面' : ''})` };
349
+ },
350
+
351
+ installMac(cmd, opts = {}) {
352
+ const app = macAppBundle();
353
+ const contents = join(app, 'Contents');
354
+ const marker = join(contents, 'zcodepro-setup-marker');
355
+ if (existsSync(app) && !existsSync(marker)) {
356
+ return { skipped: `${app} 已存在且非 setup 创建,未覆盖` };
357
+ }
358
+ let iconCopied = false;
359
+ for (const src of [join('/', 'Applications', 'ZCode.app', 'Contents', 'Resources', 'app.icns'),
360
+ join(homedir(), 'Applications', 'ZCode.app', 'Contents', 'Resources', 'app.icns')]) {
361
+ if (!existsSync(src)) continue;
362
+ try {
363
+ mkdirSync(join(contents, 'Resources'), { recursive: true });
364
+ copyFileSync(src, join(contents, 'Resources', 'app.icns'));
365
+ iconCopied = true;
366
+ break;
367
+ } catch { /* 复制失败则省略图标 */ }
368
+ }
369
+ mkdirSync(join(contents, 'MacOS'), { recursive: true });
370
+ const launcher = join(contents, 'MacOS', 'zcode-pro');
371
+ writeFileSync(launcher, `#!/bin/sh\nexec "${cmd}" "$@"\n`);
372
+ chmodSync(launcher, 0o755);
373
+ writeFileSync(join(contents, 'Info.plist'), plistContent(iconCopied));
374
+ writeFileSync(marker, '');
375
+ let detail = `已创建 ${app}(启动器指向 ${cmd}${iconCopied ? ',图标已复制' : ''})`;
376
+ if (opts.desktop) {
377
+ const link = macDesktopLink();
378
+ if (existsSync(link)) {
379
+ detail += ';桌面已存在同名入口,未覆盖';
380
+ } else {
381
+ try {
382
+ symlinkSync(app, link);
383
+ detail += `;桌面 ${link}`;
384
+ } catch { /* 建链接失败仅省略桌面入口 */ }
385
+ }
386
+ }
387
+ return { detail };
388
+ },
389
+
390
+ uninstall(plat) {
391
+ if (plat === 'linux') return this.uninstallLinux();
392
+ if (plat === 'win32') return this.uninstallWindows();
393
+ return this.uninstallMac();
394
+ },
395
+
396
+ uninstallLinux() {
397
+ const target = join(applicationsDir(), DESKTOP_FILE);
398
+ const dTarget = join(desktopDir(), DESKTOP_FILE);
399
+ const removed = [];
400
+ if (existsSync(target) && readFileSync(target, 'utf-8').includes(SETUP_MARK)) {
401
+ rmSync(target);
402
+ removed.push(target);
403
+ }
404
+ // setup --desktop 建的桌面条目一并清理(同样只动带标记的)
405
+ if (existsSync(dTarget) && readFileSync(dTarget, 'utf-8').includes(SETUP_MARK)) {
406
+ rmSync(dTarget);
407
+ removed.push(dTarget);
408
+ }
409
+ if (!removed.length) return { skipped: '未发现 setup 创建的快捷方式' };
410
+ try { execFileSync('update-desktop-database', [applicationsDir()], { stdio: 'ignore' }); } catch { /* 无该命令时忽略 */ }
411
+ return { detail: `已移除 ${removed.join('、')}` };
412
+ },
413
+
414
+ uninstallWindows() {
415
+ const paths = windowsPaths();
416
+ const hadVbs = existsSync(paths.vbsPath);
417
+ const hadLnk = existsSync(paths.lnkPath);
418
+ if (!hadVbs && !hadLnk) return { skipped: '未发现 setup 创建的快捷方式' };
419
+ // .lnk 可能已被 install.ps1 等重建并指向它自己:只有 Arguments 带本功能归属
420
+ // 标记才删;校验失败也保留 .lnk(宁可漏删不误删)
421
+ let lnkRemoved = false;
422
+ let verified = false;
423
+ try {
424
+ runPowerShell([
425
+ "$ErrorActionPreference = 'Stop'",
426
+ '$wsh = New-Object -ComObject WScript.Shell',
427
+ `$lnk = $wsh.CreateShortcut('${psQuote(paths.lnkPath)}')`,
428
+ `if (${ownsExpr('$lnk')}) { Remove-Item -LiteralPath '${psQuote(paths.lnkPath)}' -Force }`,
429
+ // setup --desktop 建的桌面 .lnk 一并清理(归属校验同开始菜单)
430
+ "$desk = Join-Path ([Environment]::GetFolderPath('Desktop')) 'ZCode Pro.lnk'",
431
+ 'if (Test-Path $desk) {',
432
+ ' $dl = $wsh.CreateShortcut($desk)',
433
+ ` if (${ownsExpr('$dl')}) { Remove-Item -LiteralPath $desk -Force }`,
434
+ '}',
435
+ ].join('\r\n'));
436
+ verified = true;
437
+ lnkRemoved = !existsSync(paths.lnkPath);
438
+ } catch { /* 校验失败则不动 .lnk */ }
439
+ let vbsCleaned = !hadVbs;
440
+ if (hadVbs) {
441
+ try {
442
+ rmSyncWithRetry(paths.vbsDir);
443
+ vbsCleaned = true;
444
+ } catch (err) {
445
+ // 目录被杀软/索引短暂锁定导致 EPERM:先试原生 rmdir,仍失败但里面的文件
446
+ // 已删干净时按成功处理(残留空目录无害,下次运行会覆盖)
447
+ try {
448
+ execFileSync('cmd', ['/c', 'rmdir', '/s', '/q', paths.vbsDir], { stdio: 'ignore' });
449
+ vbsCleaned = true;
450
+ } catch {
451
+ vbsCleaned = !existsSync(paths.vbsPath);
452
+ if (!vbsCleaned) console.error(` (${paths.vbsDir} 删除失败,可能被占用,可稍后手动删除)`);
453
+ }
454
+ }
455
+ }
456
+ const parts = [];
457
+ if (hadLnk) {
458
+ parts.push(lnkRemoved ? '开始菜单/桌面快捷方式' : verified ? '快捷方式已非本功能创建,未删除' : '快捷方式归属校验失败,未删除 .lnk');
459
+ }
460
+ if (hadVbs) parts.push(vbsCleaned ? '生成的启动脚本' : '启动脚本(删除失败)');
461
+ return { detail: `已移除:${parts.join(';')}` };
462
+ },
463
+
464
+ uninstallMac() {
465
+ const app = macAppBundle();
466
+ const marker = join(app, 'Contents', 'zcodepro-setup-marker');
467
+ if (!existsSync(marker)) return { skipped: '未发现 setup 创建的应用包' };
468
+ // setup --desktop 建的桌面入口一并清理(只删指向本应用包的链接)
469
+ const link = macDesktopLink();
470
+ try {
471
+ if (existsSync(link) && realpathSync(link) === realpathSync(app)) rmSync(link);
472
+ } catch { /* 链接失效则忽略 */ }
473
+ rmSync(app, { recursive: true, force: true });
474
+ return { detail: `已移除 ${app}` };
475
+ },
476
+ };
477
+
478
+ // Windows 上刚写过的目录可能被杀软/索引短暂占用,立即删除偶发 EPERM:短暂等待后重试
479
+ function rmSyncWithRetry(target, tries = 4) {
480
+ for (let i = 0; ; i++) {
481
+ try {
482
+ rmSync(target, { recursive: true, force: true });
483
+ return;
484
+ } catch (err) {
485
+ if (i >= tries - 1) throw err;
486
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 200);
487
+ }
488
+ }
489
+ }
490
+
491
+ // —— 设置项注册处:以后新增设置项在这里加 ——
492
+
493
+ const items = [shortcutItem];
494
+
495
+ export async function runSetup({ uninstall = false, platform: plat = platform(), desktop = false } = {}) {
496
+ console.log(`[zcodepro] setup ${uninstall ? '卸载' : '安装'}本机集成${desktop && !uninstall ? '(含桌面)' : ''}:`);
497
+ let failed = 0;
498
+ for (const item of items) {
499
+ if (!item.supported(plat)) {
500
+ console.log(` · ${item.title}:当前平台(${plat})暂不支持,跳过`);
501
+ continue;
502
+ }
503
+ try {
504
+ const res = uninstall ? item.uninstall(plat) : item.install(plat, { desktop });
505
+ if (res && res.skipped) console.log(` · ${item.title}:${res.skipped}`);
506
+ else console.log(` ✓ ${item.title}:${res && res.detail ? res.detail : '完成'}`);
507
+ } catch (err) {
508
+ failed++;
509
+ console.error(` ✗ ${item.title}:${err?.message || err}`);
510
+ }
511
+ }
512
+ console.log(uninstall ? '' : ' 撤销:zcode-pro setup --uninstall');
513
+ if (failed) process.exitCode = 1;
514
+ }
@@ -617,11 +617,32 @@ const en = {
617
617
  };
618
618
 
619
619
  // 界面语言:与官方 IntlProvider 的解析一致——先读应用的 localStorage 偏好
620
- // (zcode-locale-preference:zh-CN/en-US/system),'system' 或读取失败时回退 navigator.language
620
+ // (zcode-locale-preference:zh-CN/en-US/system),显式偏好直接生效;'system' 或未设置时
621
+ // 官方在主进程用系统首选语言解析(macOS 上可能与渲染进程的 navigator.language 不一致:
622
+ // 系统是中文而 Chromium 报 en-US,导致注入界面误判成英文)。这里通过 preload 暴露的
623
+ // window.zcode.getSystemLocale()(IPC,异步)取主进程结果并缓存;取回前先按
624
+ // navigator.language 兜底,避免阻塞首次渲染。
625
+ let cachedSystemLocale = null; // 'zh-CN' | 'en-US' | null(尚未取回)
626
+
627
+ export function startLocaleResolver() {
628
+ if (cachedSystemLocale !== null) return; // 已取回则跳过,避免重复 IPC(index.js 会调用两次)
629
+ const get = typeof window !== 'undefined' ? window.zcode?.getSystemLocale : null;
630
+ if (typeof get !== 'function') return;
631
+ Promise.resolve(get())
632
+ .then((locale) => {
633
+ if (typeof locale === 'string' && locale) {
634
+ // 与主进程解析一致:zh 开头视为中文,其余视为英文(兼容 'zh-Hans-CN' 等原始格式)
635
+ cachedSystemLocale = locale.toLowerCase().startsWith('zh') ? 'zh-CN' : 'en-US';
636
+ }
637
+ })
638
+ .catch(() => { /* 取回失败:沿用 navigator.language 兜底 */ });
639
+ }
640
+
621
641
  export function isZhLocale() {
622
642
  let pref = null;
623
643
  try { pref = window.localStorage.getItem('zcode-locale-preference'); } catch { /* ignore */ }
624
644
  if (pref === 'zh-CN' || pref === 'en-US') return pref === 'zh-CN';
645
+ if (cachedSystemLocale) return cachedSystemLocale === 'zh-CN';
625
646
  return /^zh/i.test(navigator.language || 'zh-CN');
626
647
  }
627
648
 
@@ -1,6 +1,6 @@
1
1
  // ZCode Pro 注入脚本入口(由 esbuild 打包为 dist/inject.js,launcher 拼上启动参数后注入页面)。
2
2
  // 幂等守卫:页面刷新/重复注入时只初始化一次。
3
- import { observeRadixPopups } from './core.js';
3
+ import { observeRadixPopups, startLocaleResolver } from './core.js';
4
4
  import { handleProjectMenu } from './features/project-menu.js';
5
5
  import { handleFileMenu } from './features/file-menu.js';
6
6
  import { startImageMenu } from './features/image-menu.js';
@@ -23,10 +23,15 @@ import { ensureStyle } from './ui.js';
23
23
  if (window.__zcodeproInjected) return;
24
24
  window.__zcodeproInjected = true;
25
25
 
26
+ // 尽早取回系统语言(异步 IPC):system 偏好下 macOS 的 navigator.language 可能与
27
+ // 实际不符;start() 里会再调一次,覆盖 preload 桥未就绪的情况
28
+ try { startLocaleResolver(); } catch { /* ignore */ }
29
+
26
30
  // 注意:本脚本可能在 document start 阶段执行(head 尚未就绪),
27
31
  // 因此所有 DOM 操作都推迟到 DOMContentLoaded 之后。
28
32
  const start = () => {
29
33
  ensureStyle();
34
+ try { startLocaleResolver(); } catch { /* ignore */ }
30
35
  observeRadixPopups((content) => {
31
36
  try { handleProjectMenu(content); } catch { /* 单个功能失败不影响其他 */ }
32
37
  try { handleFileMenu(content); } catch { /* ignore */ }
@@ -1,3 +1,3 @@
1
1
  """ZCode Pro 的 PyPI 安装包:内置 Node 应用源码,通过 zcode-pro 命令启动。"""
2
2
 
3
- __version__ = "0.16.0"
3
+ __version__ = "0.17.0"
@@ -51,9 +51,22 @@ def _find_zcode_binary() -> str | None:
51
51
  return None
52
52
 
53
53
 
54
+ def _runtime_ok(zcode_bin: str) -> bool:
55
+ """预检 ZCode 自带运行时是否可用:注册表等来源找到的可执行文件可能不完整
56
+ (缺 resources/ICU 数据等),以 Node 模式跑不起来就回退系统 node。"""
57
+ env = dict(os.environ, ELECTRON_RUN_AS_NODE="1")
58
+ try:
59
+ return subprocess.run(
60
+ [zcode_bin, "-e", "process.exit(0)"],
61
+ env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
62
+ ).returncode == 0
63
+ except OSError:
64
+ return False
65
+
66
+
54
67
  def main() -> int:
55
68
  zcode_bin = _find_zcode_binary()
56
- if zcode_bin:
69
+ if zcode_bin and _runtime_ok(zcode_bin):
57
70
  # 用 ZCode 内置的 Node 运行时执行启动器(ELECTRON_RUN_AS_NODE 使其表现为纯 Node)
58
71
  env = dict(os.environ, ELECTRON_RUN_AS_NODE="1")
59
72
  return subprocess.run([zcode_bin, str(CLI_MJS), *sys.argv[1:]], env=env).returncode
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes