cwm-assistant 0.1.0 → 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tangzhijie
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,19 +1,93 @@
1
1
  # CWM 助手
2
2
 
3
- 运行于浏览器所在电脑,为 CWM 网页提供本机 Git、Workspace 和工具操作。
4
- 需要 Node.js 24 LTS、Git 2.38 或更高版本。首版支持 macOS/Windows 的 Chrome/Edge。
3
+ Code Workspace Manager(CWM)的本机助手。运行在打开工作台网页的电脑上,为网页提供本机
4
+ Git、Workspace 目录和工具命令操作,并在登录电脑时自动启动。
5
+
6
+ 助手只监听 `127.0.0.1:46322`,不上传仓库内容;中心服务保存账号、需求与版本,助手保存设备
7
+ 路径、工具配置和本地任务。
8
+
9
+ ## 运行要求
10
+
11
+ - Node.js 22 或更高版本(推荐 24 LTS)
12
+ - Git 2.38 或更高版本(Windows 使用 Git for Windows)
13
+ - macOS 或 Windows 上的 Chrome / Edge
14
+
15
+ ## 安装
16
+
17
+ ```sh
18
+ npm install -g cwm-assistant
19
+ ```
20
+
21
+ 从 npm 安装不会注册自启动,需要运行 `setup` 完成初始化。
22
+
23
+ ## 初始化
5
24
 
6
25
  ```sh
7
- npm install -g cwm-assistant@0.1.0
8
26
  cwm-assistant setup
9
27
  ```
10
28
 
11
- 初始化时输入可信 HTTPS 工作台地址(同机开发可用 HTTP localhost),确认用户级自启动,
12
- 将一次性配对码输入网页。程序只监听 loopback。npm 安装本身不注册自启动。
29
+ `setup` 依次完成:
30
+
31
+ 1. 输入可信工作台地址(HTTPS;同机开发可用 `http://127.0.0.1:46321`)
32
+ 2. 选择工作目录,用于保存仓库和 Git worktree
33
+ 3. 确认登录电脑时自动启动(macOS 写入用户 LaunchAgent,Windows 创建当前用户计划任务)
34
+ 4. 启动助手并生成一次性配对码
35
+
36
+ 回到工作台网页,点击顶部的「本机助手」完成连接并确认本机目录。配对码 5 分钟内有效。
37
+
38
+ 非交互安装(脚本或远程动手):
39
+
40
+ ```sh
41
+ cwm-assistant setup --workbench http://127.0.0.1:46321 --root ~/code-workspace --yes --autostart
42
+ ```
43
+
44
+ `--workbench` 可重复,每次授权一个工作台来源;`--root` 必须是已存在的绝对路径;
45
+ `--port` 可覆盖默认端口(1024–65535)。
46
+
47
+ ## 命令
48
+
49
+ | 命令 | 说明 |
50
+ | -------------------------- | ------------------------------------ |
51
+ | `setup` | 首次初始化,写入配置并启动 |
52
+ | `start` | 启动助手(已运行则直接返回) |
53
+ | `stop` | 停止助手并关闭自启动 |
54
+ | `restart` | 重启助手,保留自启动设置 |
55
+ | `status` | 查询运行状态、版本和端口 |
56
+ | `--version` | 打印助手版本号 |
57
+ | `doctor` | 检查 Node、Git、入口文件、目录和配置 |
58
+ | `pair` | 生成新的配对码,用于更换浏览器 |
59
+ | `logs` | 查看最近 100 行日志 |
60
+ | `autostart enable/disable` | 单独切换登录自启动(需先 `stop`) |
61
+ | `uninstall` | 停止助手并撤销全部配对,保留项目文件 |
62
+
63
+ 所有命令都接受 `--state-dir`,用于指定状态目录(默认 `~/.cwm-assistant`,
64
+ 可用环境变量 `CWM_ASSISTANT_HOME` 覆盖)。
65
+
66
+ ## 更新与卸载
67
+
68
+ 升级前先运行 `cwm-assistant stop`,确认没有正在运行的本地任务,再执行:
69
+
70
+ ```sh
71
+ npm install -g cwm-assistant@latest
72
+ cwm-assistant start
73
+ ```
74
+
75
+ 卸载前运行 `cwm-assistant uninstall` 撤销配对并移除自启动,再执行:
76
+
77
+ ```sh
78
+ npm uninstall -g cwm-assistant
79
+ ```
80
+
81
+ `uninstall` 不会删除仓库、Workspace 和 `~/.cwm-assistant` 中的状态文件。
82
+
83
+ ## 常见问题
84
+
85
+ - 网页提示「需要更新」:工作台与助手协议版本不一致,升级助手到最新版本。
86
+ - 网页提示「待配对」:工作台不在助手授权列表内,运行 `setup` 补充地址,或运行 `pair`
87
+ 后重新连接。
88
+ - 浏览器提示访问本地网络:允许当前工作台访问 `127.0.0.1:46322`,再重新检测。
89
+ - 诊断端口占用或启动失败:运行 `cwm-assistant doctor` 和 `cwm-assistant logs`。
13
90
 
14
- 公共 npm 发布之前,请使用项目构建的 `.tgz` 验收包,不要安装来源不明的同名包。
91
+ ## 许可证
15
92
 
16
- `status` 查询状态,`doctor` 检查配置,`pair` 创建新配对码,`logs` 查看日志。
17
- `stop` 停止并关闭自启动;`start` 再次启动;`autostart enable` 恢复自启动。
18
- 运行中的本地任务完成或取消之前,不要更新。卸载前运行 `cwm-assistant uninstall`,
19
- 再运行 `npm uninstall -g cwm-assistant`。卸载保留用户仓库和 Workspace。
93
+ [MIT](LICENSE)
package/dist/main.js CHANGED
@@ -15,199 +15,6 @@ import os9 from "node:os";
15
15
  import path24 from "node:path";
16
16
  import { createInterface } from "node:readline/promises";
17
17
 
18
- // ../../packages/runtime/src/assistant/tokens.ts
19
- import { generateKeyPairSync, randomUUID, sign, verify } from "node:crypto";
20
- function createIdentityKeys() {
21
- const keys = generateKeyPairSync("ed25519");
22
- return {
23
- id: randomUUID(),
24
- publicKey: keys.publicKey.export({ type: "spki", format: "pem" }).toString(),
25
- privateKey: keys.privateKey.export({ type: "pkcs8", format: "pem" }).toString()
26
- };
27
- }
28
- function signMessage(value, privateKey) {
29
- const payload = Buffer.from(JSON.stringify(value)).toString("base64url");
30
- return payload + "." + sign(null, Buffer.from(payload), privateKey).toString("base64url");
31
- }
32
- function verifyMessage(token, publicKey) {
33
- const parts = token.split(".");
34
- if (parts.length !== 2 || !verify(null, Buffer.from(parts[0]), publicKey, Buffer.from(parts[1], "base64url")))
35
- throw new Error("\u7B7E\u540D\u65E0\u6548");
36
- return JSON.parse(Buffer.from(parts[0], "base64url").toString("utf8"));
37
- }
38
- function verifyGrant(token, publicKey) {
39
- const grant = verifyMessage(token, publicKey);
40
- if (!Number.isFinite(grant.exp) || grant.exp <= Date.now()) throw new Error("\u8FDE\u63A5\u6388\u6743\u5DF2\u8FC7\u671F");
41
- if (!grant.userId || !grant.origin || !grant.browserId || !grant.deviceId)
42
- throw new Error("\u8FDE\u63A5\u6388\u6743\u4E0D\u5B8C\u6574");
43
- return grant;
44
- }
45
- function workbenchOrigin(value) {
46
- const url = new URL(value);
47
- if (url.username || url.password || url.pathname !== "/" || url.search || url.hash)
48
- throw new Error("\u5DE5\u4F5C\u53F0\u5730\u5740\u53EA\u9700\u586B\u5199\u534F\u8BAE\u3001\u4E3B\u673A\u548C\u7AEF\u53E3");
49
- if (url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)))
50
- throw new Error("\u8DE8\u673A\u5668\u5DE5\u4F5C\u53F0\u5FC5\u987B\u4F7F\u7528\u53EF\u4FE1 HTTPS \u5730\u5740");
51
- return url.origin;
52
- }
53
-
54
- // ../../packages/runtime/src/git/commands.ts
55
- import { spawn } from "node:child_process";
56
-
57
- // ../../packages/runtime/src/git/context.ts
58
- import { AsyncLocalStorage } from "node:async_hooks";
59
- var gitExecution = new AsyncLocalStorage();
60
-
61
- // ../../packages/runtime/src/git/commands.ts
62
- var GitCommandError = class extends Error {
63
- exitCode;
64
- stdout = "";
65
- constructor(message, exitCode) {
66
- super(message);
67
- this.name = "GitCommandError";
68
- this.exitCode = exitCode;
69
- }
70
- };
71
- function runGit(cwd, args, options = {}) {
72
- return new Promise((resolve, reject) => {
73
- const execution = gitExecution.getStore();
74
- execution?.signal?.throwIfAborted();
75
- const child = spawn(execution?.executable || "git", ["-C", cwd, ...args], {
76
- signal: execution?.signal,
77
- shell: false,
78
- windowsHide: true,
79
- stdio: ["ignore", "pipe", "pipe"],
80
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
81
- });
82
- let stdout = "", stderr = "", pending = "";
83
- let failure;
84
- const stop = (message) => {
85
- if (failure) return;
86
- failure = new GitCommandError(message, null);
87
- child.kill("SIGKILL");
88
- clearTimeout(timer);
89
- child.stdout.destroy();
90
- child.stderr.destroy();
91
- reject(failure);
92
- };
93
- const timer = setTimeout(() => stop(`Git \u64CD\u4F5C\u8D85\u65F6\uFF1A${args[0]}`), options.timeoutMs ?? 12e4);
94
- child.stdout.setEncoding("utf8");
95
- child.stderr.setEncoding("utf8");
96
- child.stdout.on("data", (chunk) => {
97
- if (failure) return;
98
- stdout += chunk;
99
- if (stdout.length > 1024 * 1024) stop("Git \u8F93\u51FA\u8D85\u8FC7\u9650\u5236");
100
- });
101
- child.stderr.on("data", (chunk) => {
102
- if (failure) return;
103
- stderr += chunk;
104
- pending += chunk;
105
- const lines2 = pending.split(/[\r\n]+/);
106
- pending = lines2.pop() ?? "";
107
- for (const line of lines2) if (line) options.onLog?.(line);
108
- if (stderr.length > 1024 * 1024) stop("Git \u8F93\u51FA\u8D85\u8FC7\u9650\u5236");
109
- });
110
- child.once("error", (error) => {
111
- clearTimeout(timer);
112
- reject(error);
113
- });
114
- child.once("close", (code) => {
115
- clearTimeout(timer);
116
- if (pending) options.onLog?.(pending);
117
- if (failure) return reject(failure);
118
- if (code !== 0)
119
- return reject(
120
- Object.assign(
121
- new GitCommandError(stderr.trim() || `Git ${args[0]} \u5931\u8D25\uFF08\u9000\u51FA\u7801 ${code}\uFF09`, code),
122
- { stdout: stdout.trimEnd() }
123
- )
124
- );
125
- resolve(stdout.trimEnd());
126
- });
127
- });
128
- }
129
- async function tryGit(cwd, args) {
130
- try {
131
- return await runGit(cwd, args);
132
- } catch (error) {
133
- if (error instanceof GitCommandError && error.exitCode !== null) return void 0;
134
- throw error;
135
- }
136
- }
137
-
138
- // src/autostart.ts
139
- import { execFile } from "node:child_process";
140
- import fs from "node:fs";
141
- import os from "node:os";
142
- import path from "node:path";
143
- import { promisify } from "node:util";
144
- var execute = promisify(execFile);
145
- var xml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
146
- async function startAutostart() {
147
- if (process.platform === "darwin")
148
- await execute("launchctl", ["kickstart", `gui/${process.getuid()}/com.cwm.assistant`]);
149
- else if (process.platform === "win32")
150
- await execute("schtasks.exe", ["/Run", "/TN", "CWM Assistant"]);
151
- else throw new Error("\u81EA\u542F\u52A8\u4EC5\u652F\u6301 Windows \u548C macOS");
152
- }
153
- async function disableAutostart() {
154
- if (process.platform === "darwin") {
155
- const domain = `gui/${process.getuid()}/com.cwm.assistant`;
156
- await execute("launchctl", ["disable", domain]);
157
- fs.rmSync(path.join(os.homedir(), "Library", "LaunchAgents", "com.cwm.assistant.plist"), {
158
- force: true
159
- });
160
- } else if (process.platform === "win32") {
161
- await execute("schtasks.exe", ["/Delete", "/TN", "CWM Assistant", "/F"]);
162
- } else throw new Error("\u81EA\u542F\u52A8\u4EC5\u652F\u6301 Windows \u548C macOS");
163
- }
164
- async function configureAutostart(enable, directory, entry) {
165
- if (process.platform === "darwin") {
166
- const file = path.join(os.homedir(), "Library", "LaunchAgents", "com.cwm.assistant.plist");
167
- const domain = `gui/${process.getuid()}`;
168
- try {
169
- await execute("launchctl", ["bootout", domain + "/com.cwm.assistant"]);
170
- } catch {
171
- }
172
- if (!enable) {
173
- fs.rmSync(file, { force: true });
174
- return;
175
- }
176
- fs.mkdirSync(path.dirname(file), { recursive: true });
177
- fs.writeFileSync(
178
- file,
179
- `<?xml version="1.0" encoding="UTF-8"?>
180
- <plist version="1.0"><dict>
181
- <key>Label</key><string>com.cwm.assistant</string>
182
- <key>ProgramArguments</key><array><string>${xml(process.execPath)}</string><string>${xml(entry)}</string><string>serve</string></array>
183
- <key>EnvironmentVariables</key><dict><key>CWM_ASSISTANT_HOME</key><string>${xml(directory)}</string></dict>
184
- <key>RunAtLoad</key><true/><key>KeepAlive</key><true/>
185
- <key>StandardOutPath</key><string>${xml(path.join(directory, "assistant.log"))}</string>
186
- <key>StandardErrorPath</key><string>${xml(path.join(directory, "assistant.log"))}</string>
187
- </dict></plist>`,
188
- { mode: 384 }
189
- );
190
- await execute("launchctl", ["enable", domain + "/com.cwm.assistant"]);
191
- await execute("launchctl", ["bootstrap", domain, file]);
192
- } else if (process.platform === "win32") {
193
- const task = "CWM Assistant";
194
- if (!enable) {
195
- await execute("schtasks.exe", ["/Delete", "/TN", task, "/F"]);
196
- return;
197
- }
198
- const user = os.userInfo().username;
199
- const file = path.join(directory, "autostart.xml");
200
- fs.writeFileSync(
201
- file,
202
- `\uFEFF<?xml version="1.0" encoding="UTF-16"?>
203
- <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"><Triggers><LogonTrigger><Enabled>true</Enabled><UserId>${xml(user)}</UserId></LogonTrigger></Triggers><Principals><Principal id="Author"><UserId>${xml(user)}</UserId><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals><Settings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>false</StopIfGoingOnBatteries><ExecutionTimeLimit>PT0S</ExecutionTimeLimit><RestartOnFailure><Interval>PT1M</Interval><Count>3</Count></RestartOnFailure></Settings><Actions Context="Author"><Exec><Command>${xml(process.execPath)}</Command><Arguments>&quot;${xml(entry)}&quot; serve --state-dir &quot;${xml(directory)}&quot;</Arguments></Exec></Actions></Task>`,
204
- { encoding: "utf16le" }
205
- );
206
- await execute("schtasks.exe", ["/Create", "/TN", task, "/XML", file, "/F"]);
207
- await execute("schtasks.exe", ["/Run", "/TN", task]);
208
- } else throw new Error("\u81EA\u542F\u52A8\u4EC5\u652F\u6301 Windows \u548C macOS");
209
- }
210
-
211
18
  // ../../node_modules/@sinclair/typebox/build/esm/type/guard/value.mjs
212
19
  var value_exports = {};
213
20
  __export(value_exports, {
@@ -2816,7 +2623,7 @@ var Type = type_exports2;
2816
2623
 
2817
2624
  // ../../packages/contracts/src/assistant.ts
2818
2625
  var ASSISTANT_PROTOCOL = 1;
2819
- var ASSISTANT_VERSION = "0.1.0";
2626
+ var ASSISTANT_VERSION = "0.1.1";
2820
2627
  var ASSISTANT_PORT = 46322;
2821
2628
 
2822
2629
  // ../../packages/contracts/src/index.ts
@@ -2856,6 +2663,199 @@ var ErrorSchema = Type.Object({
2856
2663
  details: Type.Optional(Type.Unknown())
2857
2664
  });
2858
2665
 
2666
+ // ../../packages/runtime/src/assistant/tokens.ts
2667
+ import { generateKeyPairSync, randomUUID, sign, verify } from "node:crypto";
2668
+ function createIdentityKeys() {
2669
+ const keys = generateKeyPairSync("ed25519");
2670
+ return {
2671
+ id: randomUUID(),
2672
+ publicKey: keys.publicKey.export({ type: "spki", format: "pem" }).toString(),
2673
+ privateKey: keys.privateKey.export({ type: "pkcs8", format: "pem" }).toString()
2674
+ };
2675
+ }
2676
+ function signMessage(value, privateKey) {
2677
+ const payload = Buffer.from(JSON.stringify(value)).toString("base64url");
2678
+ return payload + "." + sign(null, Buffer.from(payload), privateKey).toString("base64url");
2679
+ }
2680
+ function verifyMessage(token, publicKey) {
2681
+ const parts = token.split(".");
2682
+ if (parts.length !== 2 || !verify(null, Buffer.from(parts[0]), publicKey, Buffer.from(parts[1], "base64url")))
2683
+ throw new Error("\u7B7E\u540D\u65E0\u6548");
2684
+ return JSON.parse(Buffer.from(parts[0], "base64url").toString("utf8"));
2685
+ }
2686
+ function verifyGrant(token, publicKey) {
2687
+ const grant = verifyMessage(token, publicKey);
2688
+ if (!Number.isFinite(grant.exp) || grant.exp <= Date.now()) throw new Error("\u8FDE\u63A5\u6388\u6743\u5DF2\u8FC7\u671F");
2689
+ if (!grant.userId || !grant.origin || !grant.browserId || !grant.deviceId)
2690
+ throw new Error("\u8FDE\u63A5\u6388\u6743\u4E0D\u5B8C\u6574");
2691
+ return grant;
2692
+ }
2693
+ function workbenchOrigin(value) {
2694
+ const url = new URL(value);
2695
+ if (url.username || url.password || url.pathname !== "/" || url.search || url.hash)
2696
+ throw new Error("\u5DE5\u4F5C\u53F0\u5730\u5740\u53EA\u9700\u586B\u5199\u534F\u8BAE\u3001\u4E3B\u673A\u548C\u7AEF\u53E3");
2697
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)))
2698
+ throw new Error("\u8DE8\u673A\u5668\u5DE5\u4F5C\u53F0\u5FC5\u987B\u4F7F\u7528\u53EF\u4FE1 HTTPS \u5730\u5740");
2699
+ return url.origin;
2700
+ }
2701
+
2702
+ // ../../packages/runtime/src/git/commands.ts
2703
+ import { spawn } from "node:child_process";
2704
+
2705
+ // ../../packages/runtime/src/git/context.ts
2706
+ import { AsyncLocalStorage } from "node:async_hooks";
2707
+ var gitExecution = new AsyncLocalStorage();
2708
+
2709
+ // ../../packages/runtime/src/git/commands.ts
2710
+ var GitCommandError = class extends Error {
2711
+ exitCode;
2712
+ stdout = "";
2713
+ constructor(message, exitCode) {
2714
+ super(message);
2715
+ this.name = "GitCommandError";
2716
+ this.exitCode = exitCode;
2717
+ }
2718
+ };
2719
+ function runGit(cwd, args, options = {}) {
2720
+ return new Promise((resolve, reject) => {
2721
+ const execution = gitExecution.getStore();
2722
+ execution?.signal?.throwIfAborted();
2723
+ const child = spawn(execution?.executable || "git", ["-C", cwd, ...args], {
2724
+ signal: execution?.signal,
2725
+ shell: false,
2726
+ windowsHide: true,
2727
+ stdio: ["ignore", "pipe", "pipe"],
2728
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
2729
+ });
2730
+ let stdout = "", stderr = "", pending = "";
2731
+ let failure;
2732
+ const stop = (message) => {
2733
+ if (failure) return;
2734
+ failure = new GitCommandError(message, null);
2735
+ child.kill("SIGKILL");
2736
+ clearTimeout(timer);
2737
+ child.stdout.destroy();
2738
+ child.stderr.destroy();
2739
+ reject(failure);
2740
+ };
2741
+ const timer = setTimeout(() => stop(`Git \u64CD\u4F5C\u8D85\u65F6\uFF1A${args[0]}`), options.timeoutMs ?? 12e4);
2742
+ child.stdout.setEncoding("utf8");
2743
+ child.stderr.setEncoding("utf8");
2744
+ child.stdout.on("data", (chunk) => {
2745
+ if (failure) return;
2746
+ stdout += chunk;
2747
+ if (stdout.length > 1024 * 1024) stop("Git \u8F93\u51FA\u8D85\u8FC7\u9650\u5236");
2748
+ });
2749
+ child.stderr.on("data", (chunk) => {
2750
+ if (failure) return;
2751
+ stderr += chunk;
2752
+ pending += chunk;
2753
+ const lines2 = pending.split(/[\r\n]+/);
2754
+ pending = lines2.pop() ?? "";
2755
+ for (const line of lines2) if (line) options.onLog?.(line);
2756
+ if (stderr.length > 1024 * 1024) stop("Git \u8F93\u51FA\u8D85\u8FC7\u9650\u5236");
2757
+ });
2758
+ child.once("error", (error) => {
2759
+ clearTimeout(timer);
2760
+ reject(error);
2761
+ });
2762
+ child.once("close", (code) => {
2763
+ clearTimeout(timer);
2764
+ if (pending) options.onLog?.(pending);
2765
+ if (failure) return reject(failure);
2766
+ if (code !== 0)
2767
+ return reject(
2768
+ Object.assign(
2769
+ new GitCommandError(stderr.trim() || `Git ${args[0]} \u5931\u8D25\uFF08\u9000\u51FA\u7801 ${code}\uFF09`, code),
2770
+ { stdout: stdout.trimEnd() }
2771
+ )
2772
+ );
2773
+ resolve(stdout.trimEnd());
2774
+ });
2775
+ });
2776
+ }
2777
+ async function tryGit(cwd, args) {
2778
+ try {
2779
+ return await runGit(cwd, args);
2780
+ } catch (error) {
2781
+ if (error instanceof GitCommandError && error.exitCode !== null) return void 0;
2782
+ throw error;
2783
+ }
2784
+ }
2785
+
2786
+ // src/autostart.ts
2787
+ import { execFile } from "node:child_process";
2788
+ import fs from "node:fs";
2789
+ import os from "node:os";
2790
+ import path from "node:path";
2791
+ import { promisify } from "node:util";
2792
+ var execute = promisify(execFile);
2793
+ var xml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
2794
+ async function startAutostart() {
2795
+ if (process.platform === "darwin")
2796
+ await execute("launchctl", ["kickstart", `gui/${process.getuid()}/com.cwm.assistant`]);
2797
+ else if (process.platform === "win32")
2798
+ await execute("schtasks.exe", ["/Run", "/TN", "CWM Assistant"]);
2799
+ else throw new Error("\u81EA\u542F\u52A8\u4EC5\u652F\u6301 Windows \u548C macOS");
2800
+ }
2801
+ async function disableAutostart() {
2802
+ if (process.platform === "darwin") {
2803
+ const domain = `gui/${process.getuid()}/com.cwm.assistant`;
2804
+ await execute("launchctl", ["disable", domain]);
2805
+ fs.rmSync(path.join(os.homedir(), "Library", "LaunchAgents", "com.cwm.assistant.plist"), {
2806
+ force: true
2807
+ });
2808
+ } else if (process.platform === "win32") {
2809
+ await execute("schtasks.exe", ["/Delete", "/TN", "CWM Assistant", "/F"]);
2810
+ } else throw new Error("\u81EA\u542F\u52A8\u4EC5\u652F\u6301 Windows \u548C macOS");
2811
+ }
2812
+ async function configureAutostart(enable, directory, entry) {
2813
+ if (process.platform === "darwin") {
2814
+ const file = path.join(os.homedir(), "Library", "LaunchAgents", "com.cwm.assistant.plist");
2815
+ const domain = `gui/${process.getuid()}`;
2816
+ try {
2817
+ await execute("launchctl", ["bootout", domain + "/com.cwm.assistant"]);
2818
+ } catch {
2819
+ }
2820
+ if (!enable) {
2821
+ fs.rmSync(file, { force: true });
2822
+ return;
2823
+ }
2824
+ fs.mkdirSync(path.dirname(file), { recursive: true });
2825
+ fs.writeFileSync(
2826
+ file,
2827
+ `<?xml version="1.0" encoding="UTF-8"?>
2828
+ <plist version="1.0"><dict>
2829
+ <key>Label</key><string>com.cwm.assistant</string>
2830
+ <key>ProgramArguments</key><array><string>${xml(process.execPath)}</string><string>${xml(entry)}</string><string>serve</string></array>
2831
+ <key>EnvironmentVariables</key><dict><key>CWM_ASSISTANT_HOME</key><string>${xml(directory)}</string></dict>
2832
+ <key>RunAtLoad</key><true/><key>KeepAlive</key><true/>
2833
+ <key>StandardOutPath</key><string>${xml(path.join(directory, "assistant.log"))}</string>
2834
+ <key>StandardErrorPath</key><string>${xml(path.join(directory, "assistant.log"))}</string>
2835
+ </dict></plist>`,
2836
+ { mode: 384 }
2837
+ );
2838
+ await execute("launchctl", ["enable", domain + "/com.cwm.assistant"]);
2839
+ await execute("launchctl", ["bootstrap", domain, file]);
2840
+ } else if (process.platform === "win32") {
2841
+ const task = "CWM Assistant";
2842
+ if (!enable) {
2843
+ await execute("schtasks.exe", ["/Delete", "/TN", task, "/F"]);
2844
+ return;
2845
+ }
2846
+ const user = os.userInfo().username;
2847
+ const file = path.join(directory, "autostart.xml");
2848
+ fs.writeFileSync(
2849
+ file,
2850
+ `\uFEFF<?xml version="1.0" encoding="UTF-16"?>
2851
+ <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"><Triggers><LogonTrigger><Enabled>true</Enabled><UserId>${xml(user)}</UserId></LogonTrigger></Triggers><Principals><Principal id="Author"><UserId>${xml(user)}</UserId><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals><Settings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>false</StopIfGoingOnBatteries><ExecutionTimeLimit>PT0S</ExecutionTimeLimit><RestartOnFailure><Interval>PT1M</Interval><Count>3</Count></RestartOnFailure></Settings><Actions Context="Author"><Exec><Command>${xml(process.execPath)}</Command><Arguments>&quot;${xml(entry)}&quot; serve --state-dir &quot;${xml(directory)}&quot;</Arguments></Exec></Actions></Task>`,
2852
+ { encoding: "utf16le" }
2853
+ );
2854
+ await execute("schtasks.exe", ["/Create", "/TN", task, "/XML", file, "/F"]);
2855
+ await execute("schtasks.exe", ["/Run", "/TN", task]);
2856
+ } else throw new Error("\u81EA\u542F\u52A8\u4EC5\u652F\u6301 Windows \u548C macOS");
2857
+ }
2858
+
2859
2859
  // src/server.ts
2860
2860
  import Fastify from "fastify";
2861
2861
  import { randomBytes as randomBytes2, randomUUID as randomUUID7, timingSafeEqual } from "node:crypto";
@@ -6076,6 +6076,10 @@ async function buildAssistant(store, options = {}) {
6076
6076
 
6077
6077
  // src/cli.ts
6078
6078
  async function cli(args, entry) {
6079
+ if (["-v", "--version", "version"].includes(args[0])) {
6080
+ console.log(ASSISTANT_VERSION);
6081
+ return;
6082
+ }
6079
6083
  const dirIndex = args.indexOf("--state-dir");
6080
6084
  const directory = dirIndex >= 0 ? path24.resolve(args[dirIndex + 1]) : defaultStateDirectory();
6081
6085
  const command = args[0] || "status";