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 +21 -0
- package/README.md +84 -10
- package/dist/main.js +198 -194
- package/dist/main.js.map +1 -1
- package/package.json +26 -3
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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
12
|
-
|
|
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
|
-
|
|
91
|
+
## 许可证
|
|
15
92
|
|
|
16
|
-
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
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>"${xml(entry)}" serve --state-dir "${xml(directory)}"</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.
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
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>"${xml(entry)}" serve --state-dir "${xml(directory)}"</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";
|