@love-moon/conductor-cli 0.2.22 → 0.2.23
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/bin/conductor-config.js +143 -89
- package/package.json +4 -4
- package/src/daemon.js +37 -9
package/bin/conductor-config.js
CHANGED
|
@@ -12,9 +12,10 @@ import { RUNTIME_SUPPORTED_BACKENDS } from "../src/runtime-backends.js";
|
|
|
12
12
|
|
|
13
13
|
const CONFIG_DIR = path.join(os.homedir(), ".conductor");
|
|
14
14
|
const CONFIG_FILE = path.join(CONFIG_DIR, "config.yaml");
|
|
15
|
+
const packageJson = JSON.parse(
|
|
16
|
+
fs.readFileSync(new URL("../package.json", import.meta.url), "utf-8"),
|
|
17
|
+
);
|
|
15
18
|
|
|
16
|
-
// 市面上主流 Coding CLI 配置
|
|
17
|
-
// 格式: { name: { command: string, description: string, execArgs?: string } }
|
|
18
19
|
const DEFAULT_CLIs = {
|
|
19
20
|
claude: {
|
|
20
21
|
command: "claude",
|
|
@@ -26,21 +27,11 @@ const DEFAULT_CLIs = {
|
|
|
26
27
|
execArgs: "--dangerously-bypass-approvals-and-sandbox --ask-for-approval never",
|
|
27
28
|
description: "OpenAI Codex CLI"
|
|
28
29
|
},
|
|
29
|
-
// gemini: {
|
|
30
|
-
// command: "gemini",
|
|
31
|
-
// execArgs: "",
|
|
32
|
-
// description: "Google Gemini CLI"
|
|
33
|
-
// },
|
|
34
30
|
opencode: {
|
|
35
31
|
command: "opencode",
|
|
36
32
|
execArgs: "",
|
|
37
33
|
description: "OpenCode CLI (Conductor runs opencode serve with permission=allow)"
|
|
38
34
|
},
|
|
39
|
-
// kimi: {
|
|
40
|
-
// command: "kimi",
|
|
41
|
-
// execArgs: "--yolo --print --prompt",
|
|
42
|
-
// description: "Kimi CLI"
|
|
43
|
-
// },
|
|
44
35
|
};
|
|
45
36
|
|
|
46
37
|
const backendUrl =
|
|
@@ -48,8 +39,8 @@ const backendUrl =
|
|
|
48
39
|
process.env.BACKEND_URL ||
|
|
49
40
|
"https://conductor-ai.top";
|
|
50
41
|
const defaultDaemonName = os.hostname() || "my-daemon";
|
|
42
|
+
const cliVersion = packageJson.version || "unknown";
|
|
51
43
|
|
|
52
|
-
// ANSI 颜色代码
|
|
53
44
|
const COLORS = {
|
|
54
45
|
yellow: "\x1b[33m",
|
|
55
46
|
green: "\x1b[32m",
|
|
@@ -58,6 +49,8 @@ const COLORS = {
|
|
|
58
49
|
bold: "\x1b[1m"
|
|
59
50
|
};
|
|
60
51
|
|
|
52
|
+
let lastDeviceAuthConfig = null;
|
|
53
|
+
|
|
61
54
|
function colorize(text, color) {
|
|
62
55
|
return `${COLORS[color] || ""}${text}${COLORS.reset}`;
|
|
63
56
|
}
|
|
@@ -79,11 +72,15 @@ function buildConfigEntryLines(cli, info, { commented = false } = {}) {
|
|
|
79
72
|
}
|
|
80
73
|
|
|
81
74
|
async function main() {
|
|
82
|
-
// 解析命令行参数
|
|
83
75
|
const argv = yargs(hideBin(process.argv))
|
|
84
76
|
.option("token", {
|
|
85
77
|
type: "string",
|
|
86
|
-
description: "Conductor token
|
|
78
|
+
description: "Conductor token"
|
|
79
|
+
})
|
|
80
|
+
.option("manual", {
|
|
81
|
+
type: "boolean",
|
|
82
|
+
default: false,
|
|
83
|
+
description: "Enter token manually instead of browser device authorization"
|
|
87
84
|
})
|
|
88
85
|
.option("force", {
|
|
89
86
|
type: "boolean",
|
|
@@ -96,13 +93,13 @@ async function main() {
|
|
|
96
93
|
description: "Show help"
|
|
97
94
|
})
|
|
98
95
|
.usage("Usage: conductor config [options]")
|
|
99
|
-
.example("conductor config", "
|
|
100
|
-
.example("conductor config --
|
|
96
|
+
.example("conductor config", "Authorize this device in the browser and write config")
|
|
97
|
+
.example("conductor config --manual", "Enter token manually")
|
|
98
|
+
.example("conductor config --token <token>", "Configure with token")
|
|
101
99
|
.example("conductor config --token <token> --force", "Force overwrite existing config")
|
|
102
100
|
.help()
|
|
103
101
|
.argv;
|
|
104
102
|
|
|
105
|
-
// 检查配置文件是否存在
|
|
106
103
|
if (fs.existsSync(CONFIG_FILE) && !argv.force) {
|
|
107
104
|
process.stderr.write(
|
|
108
105
|
colorize(`Config already exists at ${CONFIG_FILE}. Use --force to overwrite.\n`, "yellow")
|
|
@@ -110,66 +107,68 @@ async function main() {
|
|
|
110
107
|
process.exit(1);
|
|
111
108
|
}
|
|
112
109
|
|
|
113
|
-
|
|
114
|
-
let
|
|
115
|
-
if (!token) {
|
|
116
|
-
token = await promptForToken();
|
|
117
|
-
if (!token) {
|
|
118
|
-
process.stderr.write(colorize("No token provided. Aborting.\n", "yellow"));
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// 检测已安装的 CLI
|
|
110
|
+
let resolvedBackendUrl = backendUrl;
|
|
111
|
+
let resolvedWebsocketUrl = null;
|
|
124
112
|
const detectedCLIs = detectInstalledCLIs();
|
|
125
113
|
|
|
126
|
-
// 如果没有检测到任何 CLI,显示警告
|
|
127
114
|
if (detectedCLIs.length === 0) {
|
|
128
115
|
console.log("");
|
|
129
116
|
console.log(colorize("=".repeat(70), "yellow"));
|
|
130
117
|
console.log(colorize("⚠️ WARNING: No coding CLI detected!", "yellow"));
|
|
131
118
|
console.log(colorize("=".repeat(70), "yellow"));
|
|
132
|
-
console.log(
|
|
119
|
+
console.log("");
|
|
133
120
|
console.log(colorize("Conductor requires at least one coding CLI to work properly.", "yellow"));
|
|
134
|
-
console.log(
|
|
121
|
+
console.log("");
|
|
135
122
|
console.log(colorize("Please install one of the following CLIs first:", "yellow"));
|
|
136
123
|
console.log("");
|
|
137
|
-
|
|
138
|
-
Object.entries(DEFAULT_CLIs).forEach(([
|
|
124
|
+
|
|
125
|
+
Object.entries(DEFAULT_CLIs).forEach(([, info]) => {
|
|
139
126
|
console.log(` • ${colorize(info.command, "cyan")} - ${info.description}`);
|
|
140
127
|
});
|
|
141
|
-
|
|
128
|
+
|
|
142
129
|
console.log("");
|
|
143
130
|
console.log(colorize("After installing a CLI, run 'conductor config' again.", "yellow"));
|
|
144
131
|
console.log(colorize("=".repeat(70), "yellow"));
|
|
145
132
|
console.log("");
|
|
146
|
-
|
|
147
|
-
// 询问是否继续创建配置
|
|
133
|
+
|
|
148
134
|
const shouldContinue = await promptYesNo(
|
|
149
135
|
"Do you want to continue creating the config anyway? (y/N): "
|
|
150
136
|
);
|
|
151
|
-
|
|
137
|
+
|
|
152
138
|
if (!shouldContinue) {
|
|
153
139
|
process.exit(1);
|
|
154
140
|
}
|
|
155
141
|
} else {
|
|
156
|
-
// 显示检测到的 CLI
|
|
157
142
|
console.log("");
|
|
158
143
|
console.log(colorize("✓ Detected the following coding CLIs:", "green"));
|
|
159
|
-
detectedCLIs.forEach(cli => {
|
|
144
|
+
detectedCLIs.forEach((cli) => {
|
|
160
145
|
const info = DEFAULT_CLIs[cli];
|
|
161
146
|
console.log(` • ${colorize(info.command, "cyan")} - ${info.description}`);
|
|
162
147
|
});
|
|
163
148
|
console.log("");
|
|
164
149
|
}
|
|
165
150
|
|
|
166
|
-
|
|
151
|
+
let token = argv.token;
|
|
152
|
+
if (!token) {
|
|
153
|
+
if (argv.manual) {
|
|
154
|
+
token = await promptForToken();
|
|
155
|
+
if (!token) {
|
|
156
|
+
process.stderr.write(colorize("No token provided. Aborting.\n", "yellow"));
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
const authResult = await authorizeDeviceAndGetToken();
|
|
161
|
+
token = authResult.agentToken;
|
|
162
|
+
resolvedBackendUrl = authResult.backendUrl || resolvedBackendUrl;
|
|
163
|
+
resolvedWebsocketUrl = authResult.websocketUrl || null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
167
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
168
168
|
|
|
169
|
-
// 构建配置内容
|
|
170
169
|
const lines = [
|
|
171
170
|
`agent_token: ${yamlQuote(token)}`,
|
|
172
|
-
`backend_url: ${yamlQuote(
|
|
171
|
+
`backend_url: ${yamlQuote(resolvedBackendUrl)}`,
|
|
173
172
|
`daemon_name: ${yamlQuote(defaultDaemonName)}`,
|
|
174
173
|
"log_level: debug",
|
|
175
174
|
"workspace: '~/ws/fires'",
|
|
@@ -178,14 +177,16 @@ async function main() {
|
|
|
178
177
|
"allow_cli_list:"
|
|
179
178
|
];
|
|
180
179
|
|
|
181
|
-
|
|
180
|
+
if (resolvedWebsocketUrl) {
|
|
181
|
+
lines.splice(2, 0, `websocket_url: ${yamlQuote(resolvedWebsocketUrl)}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
182
184
|
if (detectedCLIs.length > 0) {
|
|
183
|
-
detectedCLIs.forEach(cli => {
|
|
185
|
+
detectedCLIs.forEach((cli) => {
|
|
184
186
|
const info = DEFAULT_CLIs[cli];
|
|
185
187
|
lines.push(...buildConfigEntryLines(cli, info));
|
|
186
188
|
});
|
|
187
189
|
} else {
|
|
188
|
-
// 如果没有检测到任何 CLI,添加示例注释
|
|
189
190
|
lines.push(" # No CLI detected. Add your installed CLI here:");
|
|
190
191
|
Object.entries(DEFAULT_CLIs).forEach(([key, info]) => {
|
|
191
192
|
lines.push(...buildConfigEntryLines(key, info, { commented: true }));
|
|
@@ -202,22 +203,18 @@ async function main() {
|
|
|
202
203
|
);
|
|
203
204
|
|
|
204
205
|
fs.writeFileSync(CONFIG_FILE, lines.join("\n"), "utf-8");
|
|
205
|
-
|
|
206
|
+
|
|
206
207
|
console.log(colorize(`✓ Wrote Conductor config to ${CONFIG_FILE}`, "green"));
|
|
207
|
-
|
|
208
|
+
|
|
208
209
|
if (detectedCLIs.length === 0) {
|
|
209
210
|
console.log("");
|
|
210
211
|
console.log(colorize("⚠️ Remember to install a coding CLI before using Conductor!", "yellow"));
|
|
211
212
|
}
|
|
212
213
|
}
|
|
213
214
|
|
|
214
|
-
/**
|
|
215
|
-
* 检测系统中已安装的 CLI
|
|
216
|
-
* @returns {string[]} 已安装的 CLI key 列表
|
|
217
|
-
*/
|
|
218
215
|
function detectInstalledCLIs() {
|
|
219
216
|
const detected = [];
|
|
220
|
-
|
|
217
|
+
|
|
221
218
|
for (const [key, info] of Object.entries(DEFAULT_CLIs)) {
|
|
222
219
|
if (!RUNTIME_SUPPORTED_BACKENDS.includes(key)) {
|
|
223
220
|
continue;
|
|
@@ -226,52 +223,29 @@ function detectInstalledCLIs() {
|
|
|
226
223
|
detected.push(key);
|
|
227
224
|
}
|
|
228
225
|
}
|
|
229
|
-
|
|
226
|
+
|
|
230
227
|
return detected;
|
|
231
228
|
}
|
|
232
229
|
|
|
233
|
-
/**
|
|
234
|
-
* 检查命令是否在系统 PATH 中可用
|
|
235
|
-
* @param {string} command - 命令名称
|
|
236
|
-
* @returns {boolean}
|
|
237
|
-
*/
|
|
238
230
|
function isCommandAvailable(command) {
|
|
239
231
|
try {
|
|
240
232
|
const platform = os.platform();
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (platform === "win32") {
|
|
244
|
-
// Windows: 使用 where 命令
|
|
245
|
-
checkCmd = `where ${command}`;
|
|
246
|
-
} else {
|
|
247
|
-
// Unix/Linux/macOS: 使用 which 或 command -v
|
|
248
|
-
checkCmd = `command -v ${command}`;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
execSync(checkCmd, {
|
|
233
|
+
const checkCmd = platform === "win32" ? `where ${command}` : `command -v ${command}`;
|
|
234
|
+
execSync(checkCmd, {
|
|
252
235
|
stdio: "pipe",
|
|
253
|
-
timeout: 5000
|
|
236
|
+
timeout: 5000
|
|
254
237
|
});
|
|
255
238
|
return true;
|
|
256
|
-
} catch
|
|
257
|
-
// 对于某些 CLI,可能有特定的检测方式
|
|
258
|
-
// 例如检查特定的配置文件或目录
|
|
239
|
+
} catch {
|
|
259
240
|
return checkAlternativeInstallations(command);
|
|
260
241
|
}
|
|
261
242
|
}
|
|
262
243
|
|
|
263
|
-
/**
|
|
264
|
-
* 检查 CLI 的替代安装方式
|
|
265
|
-
* @param {string} command - 命令名称
|
|
266
|
-
* @returns {boolean}
|
|
267
|
-
*/
|
|
268
244
|
function checkAlternativeInstallations(command) {
|
|
269
|
-
// 检查常见的全局安装路径
|
|
270
245
|
const homeDir = os.homedir();
|
|
271
246
|
const platform = os.platform();
|
|
272
|
-
|
|
273
247
|
const commonPaths = [];
|
|
274
|
-
|
|
248
|
+
|
|
275
249
|
if (platform === "win32") {
|
|
276
250
|
commonPaths.push(
|
|
277
251
|
path.join(homeDir, "AppData", "Roaming", "npm", `${command}.cmd`),
|
|
@@ -290,15 +264,89 @@ function checkAlternativeInstallations(command) {
|
|
|
290
264
|
`/opt/${command}/bin/${command}`
|
|
291
265
|
);
|
|
292
266
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
267
|
+
|
|
268
|
+
return commonPaths.some((checkPath) => fs.existsSync(checkPath));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function authorizeDeviceAndGetToken() {
|
|
272
|
+
const startResponse = await fetch(new URL("/api/auth/device/start", backendUrl), {
|
|
273
|
+
method: "POST",
|
|
274
|
+
headers: { "Content-Type": "application/json" },
|
|
275
|
+
body: JSON.stringify({
|
|
276
|
+
cli_version: cliVersion,
|
|
277
|
+
hostname: defaultDaemonName,
|
|
278
|
+
platform: os.platform(),
|
|
279
|
+
backend_url: backendUrl,
|
|
280
|
+
}),
|
|
281
|
+
});
|
|
282
|
+
const startData = await parseJsonResponse(startResponse);
|
|
283
|
+
if (!startResponse.ok) {
|
|
284
|
+
throw new Error(startData.error || `Failed to start device authorization (${startResponse.status})`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
console.log("");
|
|
288
|
+
console.log(colorize("Open this link in your browser to authorize this device:", "cyan"));
|
|
289
|
+
console.log("");
|
|
290
|
+
console.log(`Device code: ${colorize(startData.user_code, "bold")}`);
|
|
291
|
+
console.log(`Direct link: ${startData.verification_uri_complete}`);
|
|
292
|
+
console.log("");
|
|
293
|
+
console.log("Only approve the request if the web page shows the same device code.");
|
|
294
|
+
console.log("");
|
|
295
|
+
console.log("Waiting for authorization...");
|
|
296
|
+
|
|
297
|
+
const intervalSeconds =
|
|
298
|
+
typeof startData.interval === "number" && Number.isFinite(startData.interval)
|
|
299
|
+
? Math.max(1, startData.interval)
|
|
300
|
+
: 3;
|
|
301
|
+
const expiresInSeconds =
|
|
302
|
+
typeof startData.expires_in === "number" && Number.isFinite(startData.expires_in)
|
|
303
|
+
? Math.max(intervalSeconds, startData.expires_in)
|
|
304
|
+
: 600;
|
|
305
|
+
const deadlineAt = Date.now() + expiresInSeconds * 1000;
|
|
306
|
+
|
|
307
|
+
while (Date.now() <= deadlineAt) {
|
|
308
|
+
await sleep(intervalSeconds * 1000);
|
|
309
|
+
|
|
310
|
+
const pollResponse = await fetch(new URL("/api/auth/device/poll", backendUrl), {
|
|
311
|
+
method: "POST",
|
|
312
|
+
headers: { "Content-Type": "application/json" },
|
|
313
|
+
body: JSON.stringify({ device_code: startData.device_code }),
|
|
314
|
+
});
|
|
315
|
+
const pollData = await parseJsonResponse(pollResponse);
|
|
316
|
+
if (!pollResponse.ok) {
|
|
317
|
+
throw new Error(pollData.error || `Failed to poll device authorization (${pollResponse.status})`);
|
|
298
318
|
}
|
|
319
|
+
|
|
320
|
+
if (pollData.status === "pending") {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (pollData.status === "approved") {
|
|
324
|
+
const result = {
|
|
325
|
+
agentToken: pollData.agent_token,
|
|
326
|
+
backendUrl: pollData.backend_url || backendUrl,
|
|
327
|
+
websocketUrl: pollData.websocket_url || null,
|
|
328
|
+
};
|
|
329
|
+
lastDeviceAuthConfig = result;
|
|
330
|
+
console.log(colorize("✓ Device authorized", "green"));
|
|
331
|
+
return result;
|
|
332
|
+
}
|
|
333
|
+
if (pollData.status === "denied" || pollData.status === "expired" || pollData.status === "consumed") {
|
|
334
|
+
throw new Error(pollData.message || `Device authorization ${pollData.status}`);
|
|
335
|
+
}
|
|
336
|
+
if (pollData.error) {
|
|
337
|
+
throw new Error(pollData.error);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
throw new Error("Device authorization timed out");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async function parseJsonResponse(response) {
|
|
345
|
+
try {
|
|
346
|
+
return await response.json();
|
|
347
|
+
} catch {
|
|
348
|
+
return {};
|
|
299
349
|
}
|
|
300
|
-
|
|
301
|
-
return false;
|
|
302
350
|
}
|
|
303
351
|
|
|
304
352
|
async function promptForToken() {
|
|
@@ -330,6 +378,12 @@ async function promptYesNo(question) {
|
|
|
330
378
|
}
|
|
331
379
|
}
|
|
332
380
|
|
|
381
|
+
function sleep(ms) {
|
|
382
|
+
return new Promise((resolve) => {
|
|
383
|
+
setTimeout(resolve, ms);
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
333
387
|
function yamlQuote(value) {
|
|
334
388
|
return JSON.stringify(value);
|
|
335
389
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/conductor-cli",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"gitCommitId": "
|
|
3
|
+
"version": "0.2.23",
|
|
4
|
+
"gitCommitId": "e1d19e3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"conductor": "bin/conductor.js"
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"test": "node --test test/*.test.js"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@love-moon/ai-sdk": "0.2.
|
|
21
|
-
"@love-moon/conductor-sdk": "0.2.
|
|
20
|
+
"@love-moon/ai-sdk": "0.2.23",
|
|
21
|
+
"@love-moon/conductor-sdk": "0.2.23",
|
|
22
22
|
"dotenv": "^16.4.5",
|
|
23
23
|
"enquirer": "^2.4.1",
|
|
24
24
|
"js-yaml": "^4.1.1",
|
package/src/daemon.js
CHANGED
|
@@ -284,6 +284,15 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
284
284
|
let requestShutdown = async () => {};
|
|
285
285
|
let shutdownSignalHandled = false;
|
|
286
286
|
let forcedSignalExitHandled = false;
|
|
287
|
+
let processHandlersAttached = false;
|
|
288
|
+
|
|
289
|
+
const removeProcessListener = (eventName, handler) => {
|
|
290
|
+
if (typeof process.off === "function") {
|
|
291
|
+
process.off(eventName, handler);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
process.removeListener(eventName, handler);
|
|
295
|
+
};
|
|
287
296
|
|
|
288
297
|
const exitAndReturn = (code) => {
|
|
289
298
|
exitFn(code);
|
|
@@ -597,7 +606,6 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
597
606
|
);
|
|
598
607
|
};
|
|
599
608
|
|
|
600
|
-
process.on("exit", cleanupLock);
|
|
601
609
|
const signalExitCode = (signal) => (signal === "SIGINT" ? 130 : 143);
|
|
602
610
|
const handleSignal = (signal) => {
|
|
603
611
|
if (shutdownSignalHandled) {
|
|
@@ -621,17 +629,33 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
621
629
|
}
|
|
622
630
|
})();
|
|
623
631
|
};
|
|
624
|
-
|
|
632
|
+
const onSigInt = () => {
|
|
625
633
|
handleSignal("SIGINT");
|
|
626
|
-
}
|
|
627
|
-
|
|
634
|
+
};
|
|
635
|
+
const onSigTerm = () => {
|
|
628
636
|
handleSignal("SIGTERM");
|
|
629
|
-
}
|
|
630
|
-
|
|
637
|
+
};
|
|
638
|
+
const onUncaughtException = (err) => {
|
|
631
639
|
logError(`Uncaught exception: ${err}`);
|
|
632
640
|
cleanupLock();
|
|
633
641
|
exitFn(1);
|
|
634
|
-
}
|
|
642
|
+
};
|
|
643
|
+
const detachProcessHandlers = () => {
|
|
644
|
+
if (!processHandlersAttached) {
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
processHandlersAttached = false;
|
|
648
|
+
removeProcessListener("exit", cleanupLock);
|
|
649
|
+
removeProcessListener("SIGINT", onSigInt);
|
|
650
|
+
removeProcessListener("SIGTERM", onSigTerm);
|
|
651
|
+
removeProcessListener("uncaughtException", onUncaughtException);
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
process.on("exit", cleanupLock);
|
|
655
|
+
process.on("SIGINT", onSigInt);
|
|
656
|
+
process.on("SIGTERM", onSigTerm);
|
|
657
|
+
process.on("uncaughtException", onUncaughtException);
|
|
658
|
+
processHandlersAttached = true;
|
|
635
659
|
|
|
636
660
|
if (config.CLEAN_ALL) {
|
|
637
661
|
cleanAllAgents(BACKEND_HTTP, AGENT_TOKEN, fetchFn)
|
|
@@ -641,8 +665,11 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
641
665
|
.catch((err) => {
|
|
642
666
|
log(`Failed to clean daemons: ${err.message}`);
|
|
643
667
|
})
|
|
644
|
-
.finally(() =>
|
|
645
|
-
|
|
668
|
+
.finally(() => {
|
|
669
|
+
detachProcessHandlers();
|
|
670
|
+
exitFn(0);
|
|
671
|
+
});
|
|
672
|
+
return { close: detachProcessHandlers };
|
|
646
673
|
}
|
|
647
674
|
|
|
648
675
|
log("Daemon starting...");
|
|
@@ -2946,6 +2973,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
2946
2973
|
|
|
2947
2974
|
return {
|
|
2948
2975
|
close: () => {
|
|
2976
|
+
detachProcessHandlers();
|
|
2949
2977
|
void shutdownDaemon();
|
|
2950
2978
|
},
|
|
2951
2979
|
};
|