@love-moon/conductor-cli 0.1.4 → 0.2.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/bin/conductor-config.js +289 -15
- package/bin/conductor-daemon.js +95 -2
- package/bin/conductor-fire.js +496 -42
- package/package.json +6 -5
- package/src/daemon.js +191 -39
- package/src/fire/history.js +3 -1
package/bin/conductor-config.js
CHANGED
|
@@ -5,47 +5,308 @@ import os from "node:os";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import process from "node:process";
|
|
7
7
|
import readline from "node:readline/promises";
|
|
8
|
+
import { execSync } from "node:child_process";
|
|
9
|
+
import yargs from "yargs/yargs";
|
|
10
|
+
import { hideBin } from "yargs/helpers";
|
|
8
11
|
|
|
9
12
|
const CONFIG_DIR = path.join(os.homedir(), ".conductor");
|
|
10
13
|
const CONFIG_FILE = path.join(CONFIG_DIR, "config.yaml");
|
|
11
14
|
|
|
15
|
+
// 市面上主流 Coding CLI 配置
|
|
16
|
+
// 格式: { name: { command: string, description: string, execArgs?: string } }
|
|
17
|
+
const DEFAULT_CLIs = {
|
|
18
|
+
claude: {
|
|
19
|
+
command: "claude",
|
|
20
|
+
execArgs: "--dangerously-skip-permissions",
|
|
21
|
+
description: "Anthropic Claude CLI"
|
|
22
|
+
},
|
|
23
|
+
codex: {
|
|
24
|
+
command: "codex",
|
|
25
|
+
execArgs: "exec --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check",
|
|
26
|
+
description: "OpenAI Codex CLI"
|
|
27
|
+
},
|
|
28
|
+
copilot: {
|
|
29
|
+
command: "copilot",
|
|
30
|
+
execArgs: "--allow-all-paths --allow-all-tools -i",
|
|
31
|
+
description: "GitHub Copilot CLI"
|
|
32
|
+
},
|
|
33
|
+
gemini: {
|
|
34
|
+
command: "gemini",
|
|
35
|
+
execArgs: "",
|
|
36
|
+
description: "Google Gemini CLI"
|
|
37
|
+
},
|
|
38
|
+
opencode: {
|
|
39
|
+
command: "opencode",
|
|
40
|
+
execArgs: "",
|
|
41
|
+
description: "OpenCode CLI"
|
|
42
|
+
},
|
|
43
|
+
kimi: {
|
|
44
|
+
command: "kimi",
|
|
45
|
+
execArgs: "--yolo --print --prompt",
|
|
46
|
+
description: "Kimi CLI"
|
|
47
|
+
},
|
|
48
|
+
aider: {
|
|
49
|
+
command: "aider",
|
|
50
|
+
execArgs: "",
|
|
51
|
+
description: "Aider AI Pair Programming"
|
|
52
|
+
},
|
|
53
|
+
goose: {
|
|
54
|
+
command: "goose",
|
|
55
|
+
execArgs: "",
|
|
56
|
+
description: "Goose AI Agent CLI"
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
12
60
|
const backendUrl =
|
|
13
61
|
process.env.CONDUCTOR_BACKEND_URL ||
|
|
14
62
|
process.env.BACKEND_URL ||
|
|
15
63
|
"https://conductor-ai.top";
|
|
16
64
|
|
|
17
|
-
|
|
18
|
-
|
|
65
|
+
// ANSI 颜色代码
|
|
66
|
+
const COLORS = {
|
|
67
|
+
yellow: "\x1b[33m",
|
|
68
|
+
green: "\x1b[32m",
|
|
69
|
+
cyan: "\x1b[36m",
|
|
70
|
+
reset: "\x1b[0m",
|
|
71
|
+
bold: "\x1b[1m"
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function colorize(text, color) {
|
|
75
|
+
return `${COLORS[color] || ""}${text}${COLORS.reset}`;
|
|
76
|
+
}
|
|
19
77
|
|
|
20
78
|
async function main() {
|
|
21
|
-
|
|
22
|
-
|
|
79
|
+
// 解析命令行参数
|
|
80
|
+
const argv = yargs(hideBin(process.argv))
|
|
81
|
+
.option("token", {
|
|
82
|
+
type: "string",
|
|
83
|
+
description: "Conductor token (optional, will prompt if not provided)"
|
|
84
|
+
})
|
|
85
|
+
.option("force", {
|
|
86
|
+
type: "boolean",
|
|
87
|
+
default: false,
|
|
88
|
+
description: "Overwrite existing config file"
|
|
89
|
+
})
|
|
90
|
+
.option("help", {
|
|
91
|
+
type: "boolean",
|
|
92
|
+
alias: "h",
|
|
93
|
+
description: "Show help"
|
|
94
|
+
})
|
|
95
|
+
.usage("Usage: conductor config [options]")
|
|
96
|
+
.example("conductor config", "Interactive configuration")
|
|
97
|
+
.example("conductor config --token <your-token>", "Configure with token")
|
|
98
|
+
.example("conductor config --token <token> --force", "Force overwrite existing config")
|
|
99
|
+
.help()
|
|
100
|
+
.argv;
|
|
101
|
+
|
|
102
|
+
// 检查配置文件是否存在
|
|
103
|
+
if (fs.existsSync(CONFIG_FILE) && !argv.force) {
|
|
104
|
+
process.stderr.write(
|
|
105
|
+
colorize(`Config already exists at ${CONFIG_FILE}. Use --force to overwrite.\n`, "yellow")
|
|
106
|
+
);
|
|
23
107
|
process.exit(1);
|
|
24
108
|
}
|
|
25
109
|
|
|
26
|
-
|
|
110
|
+
// 获取 token
|
|
111
|
+
let token = argv.token;
|
|
27
112
|
if (!token) {
|
|
28
|
-
|
|
29
|
-
|
|
113
|
+
token = await promptForToken();
|
|
114
|
+
if (!token) {
|
|
115
|
+
process.stderr.write(colorize("No token provided. Aborting.\n", "yellow"));
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 检测已安装的 CLI
|
|
121
|
+
const detectedCLIs = detectInstalledCLIs();
|
|
122
|
+
|
|
123
|
+
// 如果没有检测到任何 CLI,显示警告
|
|
124
|
+
if (detectedCLIs.length === 0) {
|
|
125
|
+
console.log("");
|
|
126
|
+
console.log(colorize("=".repeat(70), "yellow"));
|
|
127
|
+
console.log(colorize("⚠️ WARNING: No coding CLI detected!", "yellow"));
|
|
128
|
+
console.log(colorize("=".repeat(70), "yellow"));
|
|
129
|
+
console.log(colorize("", "yellow"));
|
|
130
|
+
console.log(colorize("Conductor requires at least one coding CLI to work properly.", "yellow"));
|
|
131
|
+
console.log(colorize("", "yellow"));
|
|
132
|
+
console.log(colorize("Please install one of the following CLIs first:", "yellow"));
|
|
133
|
+
console.log("");
|
|
134
|
+
|
|
135
|
+
Object.entries(DEFAULT_CLIs).forEach(([key, info]) => {
|
|
136
|
+
console.log(` • ${colorize(info.command, "cyan")} - ${info.description}`);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
console.log("");
|
|
140
|
+
console.log(colorize("After installing a CLI, run 'conductor config' again.", "yellow"));
|
|
141
|
+
console.log(colorize("=".repeat(70), "yellow"));
|
|
142
|
+
console.log("");
|
|
143
|
+
|
|
144
|
+
// 询问是否继续创建配置
|
|
145
|
+
const shouldContinue = await promptYesNo(
|
|
146
|
+
"Do you want to continue creating the config anyway? (y/N): "
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (!shouldContinue) {
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
// 显示检测到的 CLI
|
|
154
|
+
console.log("");
|
|
155
|
+
console.log(colorize("✓ Detected the following coding CLIs:", "green"));
|
|
156
|
+
detectedCLIs.forEach(cli => {
|
|
157
|
+
const info = DEFAULT_CLIs[cli];
|
|
158
|
+
console.log(` • ${colorize(info.command, "cyan")} - ${info.description}`);
|
|
159
|
+
});
|
|
160
|
+
console.log("");
|
|
30
161
|
}
|
|
31
162
|
|
|
163
|
+
// 创建配置目录
|
|
32
164
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
33
165
|
|
|
166
|
+
// 构建配置内容
|
|
34
167
|
const lines = [
|
|
35
168
|
`agent_token: ${yamlQuote(token)}`,
|
|
36
169
|
`backend_url: ${yamlQuote(backendUrl)}`,
|
|
170
|
+
"log_level: debug",
|
|
171
|
+
"",
|
|
172
|
+
"# Allowed coding CLIs",
|
|
173
|
+
"allow_cli_list:"
|
|
37
174
|
];
|
|
38
|
-
|
|
39
|
-
|
|
175
|
+
|
|
176
|
+
// 添加检测到的 CLI 到配置
|
|
177
|
+
if (detectedCLIs.length > 0) {
|
|
178
|
+
detectedCLIs.forEach(cli => {
|
|
179
|
+
const info = DEFAULT_CLIs[cli];
|
|
180
|
+
const fullCommand = info.execArgs
|
|
181
|
+
? `${info.command} ${info.execArgs}`
|
|
182
|
+
: info.command;
|
|
183
|
+
lines.push(` ${cli}: ${fullCommand}`);
|
|
184
|
+
});
|
|
185
|
+
} else {
|
|
186
|
+
// 如果没有检测到任何 CLI,添加示例注释
|
|
187
|
+
lines.push(" # No CLI detected. Add your installed CLI here:");
|
|
188
|
+
Object.entries(DEFAULT_CLIs).slice(0, 3).forEach(([key, info]) => {
|
|
189
|
+
const fullCommand = info.execArgs
|
|
190
|
+
? `${info.command} ${info.execArgs}`
|
|
191
|
+
: info.command;
|
|
192
|
+
lines.push(` # ${key}: ${fullCommand}`);
|
|
193
|
+
});
|
|
40
194
|
}
|
|
41
|
-
|
|
42
|
-
lines.push(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
195
|
+
|
|
196
|
+
lines.push(
|
|
197
|
+
"",
|
|
198
|
+
"# Uncomment to use custom envs, such as proxy.",
|
|
199
|
+
"# envs:",
|
|
200
|
+
"# http_proxy: http://127.0.0.1:7890",
|
|
201
|
+
"# https_proxy: http://127.0.0.1:7890",
|
|
202
|
+
"# all_proxy: socks5://127.0.0.1:7890"
|
|
203
|
+
);
|
|
46
204
|
|
|
47
205
|
fs.writeFileSync(CONFIG_FILE, lines.join("\n"), "utf-8");
|
|
48
|
-
|
|
206
|
+
|
|
207
|
+
console.log(colorize(`✓ Wrote Conductor config to ${CONFIG_FILE}`, "green"));
|
|
208
|
+
|
|
209
|
+
if (detectedCLIs.length === 0) {
|
|
210
|
+
console.log("");
|
|
211
|
+
console.log(colorize("⚠️ Remember to install a coding CLI before using Conductor!", "yellow"));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 检测系统中已安装的 CLI
|
|
217
|
+
* @returns {string[]} 已安装的 CLI key 列表
|
|
218
|
+
*/
|
|
219
|
+
function detectInstalledCLIs() {
|
|
220
|
+
const detected = [];
|
|
221
|
+
|
|
222
|
+
for (const [key, info] of Object.entries(DEFAULT_CLIs)) {
|
|
223
|
+
if (isCommandAvailable(info.command)) {
|
|
224
|
+
detected.push(key);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return detected;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* 检查命令是否在系统 PATH 中可用
|
|
233
|
+
* @param {string} command - 命令名称
|
|
234
|
+
* @returns {boolean}
|
|
235
|
+
*/
|
|
236
|
+
function isCommandAvailable(command) {
|
|
237
|
+
try {
|
|
238
|
+
const platform = os.platform();
|
|
239
|
+
let checkCmd;
|
|
240
|
+
|
|
241
|
+
if (platform === "win32") {
|
|
242
|
+
// Windows: 使用 where 命令
|
|
243
|
+
checkCmd = `where ${command}`;
|
|
244
|
+
} else {
|
|
245
|
+
// Unix/Linux/macOS: 使用 which 或 command -v
|
|
246
|
+
checkCmd = `command -v ${command}`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
execSync(checkCmd, {
|
|
250
|
+
stdio: "pipe",
|
|
251
|
+
timeout: 5000
|
|
252
|
+
});
|
|
253
|
+
return true;
|
|
254
|
+
} catch (error) {
|
|
255
|
+
// 对于某些 CLI,可能有特定的检测方式
|
|
256
|
+
// 例如检查特定的配置文件或目录
|
|
257
|
+
return checkAlternativeInstallations(command);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 检查 CLI 的替代安装方式
|
|
263
|
+
* @param {string} command - 命令名称
|
|
264
|
+
* @returns {boolean}
|
|
265
|
+
*/
|
|
266
|
+
function checkAlternativeInstallations(command) {
|
|
267
|
+
// 检查常见的全局安装路径
|
|
268
|
+
const homeDir = os.homedir();
|
|
269
|
+
const platform = os.platform();
|
|
270
|
+
|
|
271
|
+
const commonPaths = [];
|
|
272
|
+
|
|
273
|
+
if (platform === "win32") {
|
|
274
|
+
commonPaths.push(
|
|
275
|
+
path.join(homeDir, "AppData", "Roaming", "npm", `${command}.cmd`),
|
|
276
|
+
path.join(homeDir, "AppData", "Local", "Programs", command, `${command}.exe`),
|
|
277
|
+
path.join("C:", "Program Files", command, `${command}.exe`),
|
|
278
|
+
path.join("C:", "Program Files (x86)", command, `${command}.exe`)
|
|
279
|
+
);
|
|
280
|
+
} else {
|
|
281
|
+
commonPaths.push(
|
|
282
|
+
`/usr/local/bin/${command}`,
|
|
283
|
+
`/opt/homebrew/bin/${command}`,
|
|
284
|
+
`/usr/bin/${command}`,
|
|
285
|
+
path.join(homeDir, ".local", "bin", command),
|
|
286
|
+
path.join(homeDir, ".cargo", "bin", command),
|
|
287
|
+
path.join(homeDir, ".npm-global", "bin", command),
|
|
288
|
+
`/opt/${command}/bin/${command}`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 特殊检查:Copilot CLI 可能是 gh copilot 扩展
|
|
293
|
+
if (command === "copilot" || command === "copilot-chat") {
|
|
294
|
+
try {
|
|
295
|
+
execSync("gh copilot --help", { stdio: "pipe", timeout: 5000 });
|
|
296
|
+
return true;
|
|
297
|
+
} catch {
|
|
298
|
+
// gh copilot 未安装
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 检查文件是否存在
|
|
303
|
+
for (const checkPath of commonPaths) {
|
|
304
|
+
if (fs.existsSync(checkPath)) {
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return false;
|
|
49
310
|
}
|
|
50
311
|
|
|
51
312
|
async function promptForToken() {
|
|
@@ -64,6 +325,19 @@ async function promptForToken() {
|
|
|
64
325
|
}
|
|
65
326
|
}
|
|
66
327
|
|
|
328
|
+
async function promptYesNo(question) {
|
|
329
|
+
const rl = readline.createInterface({
|
|
330
|
+
input: process.stdin,
|
|
331
|
+
output: process.stdout,
|
|
332
|
+
});
|
|
333
|
+
try {
|
|
334
|
+
const answer = (await rl.question(question)).trim().toLowerCase();
|
|
335
|
+
return answer === "y" || answer === "yes";
|
|
336
|
+
} finally {
|
|
337
|
+
rl.close();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
67
341
|
function yamlQuote(value) {
|
|
68
342
|
return JSON.stringify(value);
|
|
69
343
|
}
|
package/bin/conductor-daemon.js
CHANGED
|
@@ -6,6 +6,7 @@ import os from "node:os";
|
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
import yargs from "yargs/yargs";
|
|
8
8
|
import { hideBin } from "yargs/helpers";
|
|
9
|
+
import yaml from "js-yaml";
|
|
9
10
|
|
|
10
11
|
import { startDaemon } from "../src/daemon.js";
|
|
11
12
|
|
|
@@ -13,9 +14,85 @@ const argv = hideBin(process.argv);
|
|
|
13
14
|
|
|
14
15
|
const CLI_NAME = process.env.CONDUCTOR_CLI_NAME || "conductor-daemon";
|
|
15
16
|
|
|
17
|
+
function formatBeijingTimestampForFile(date = new Date()) {
|
|
18
|
+
const base = date
|
|
19
|
+
.toLocaleString("sv-SE", { timeZone: "Asia/Shanghai", hour12: false })
|
|
20
|
+
.replace(" ", "T")
|
|
21
|
+
.replace(/:/g, "-");
|
|
22
|
+
const millis = String(date.getMilliseconds()).padStart(3, "0");
|
|
23
|
+
return `${base}-${millis}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function loadUserConfig(configFilePath) {
|
|
27
|
+
try {
|
|
28
|
+
const home = os.homedir();
|
|
29
|
+
const configPath = configFilePath || path.join(home, ".conductor", "config.yaml");
|
|
30
|
+
if (!fs.existsSync(configPath)) {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
const content = fs.readFileSync(configPath, "utf8");
|
|
34
|
+
const parsed = yaml.load(content);
|
|
35
|
+
if (parsed && typeof parsed === "object") {
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
} catch (_err) {
|
|
39
|
+
// Ignore config parse errors here; daemon will surface them later.
|
|
40
|
+
}
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function expandHomePath(inputPath, homeDir) {
|
|
45
|
+
if (typeof inputPath !== "string" || !inputPath) {
|
|
46
|
+
return inputPath;
|
|
47
|
+
}
|
|
48
|
+
if (inputPath === "~") {
|
|
49
|
+
return homeDir;
|
|
50
|
+
}
|
|
51
|
+
if (inputPath.startsWith("~/") || inputPath.startsWith("~\\")) {
|
|
52
|
+
return path.join(homeDir, inputPath.slice(2));
|
|
53
|
+
}
|
|
54
|
+
return inputPath;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolveWorkspaceRoot(configFilePath) {
|
|
58
|
+
const userConfig = loadUserConfig(configFilePath);
|
|
59
|
+
const home = process.env.HOME || os.homedir() || "/tmp";
|
|
60
|
+
const workspaceRoot = process.env.CONDUCTOR_WS || userConfig.workspace || path.join(home, "ws");
|
|
61
|
+
return expandHomePath(workspaceRoot, home);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function findRunningDaemonPid(workspaceRoot) {
|
|
65
|
+
const lockFile = path.join(workspaceRoot, "daemon.pid");
|
|
66
|
+
if (!fs.existsSync(lockFile)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const pid = parseInt(fs.readFileSync(lockFile, "utf8"), 10);
|
|
71
|
+
if (Number.isNaN(pid)) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
process.kill(pid, 0);
|
|
75
|
+
return pid;
|
|
76
|
+
} catch (err) {
|
|
77
|
+
if (err && err.code === "ESRCH") {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return pidFromLockFile(lockFile);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function pidFromLockFile(lockFile) {
|
|
85
|
+
try {
|
|
86
|
+
const pid = parseInt(fs.readFileSync(lockFile, "utf8"), 10);
|
|
87
|
+
return Number.isNaN(pid) ? null : pid;
|
|
88
|
+
} catch (_err) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
16
93
|
const args = yargs(argv)
|
|
17
94
|
.scriptName(CLI_NAME)
|
|
18
|
-
.usage("Usage: $0 [--name <daemon-name>] [--clean-all] [--config-file <path>] [--nohup]")
|
|
95
|
+
.usage("Usage: $0 [--name <daemon-name>] [--clean-all] [--config-file <path>] [--nohup] [--force]")
|
|
19
96
|
.option("name", {
|
|
20
97
|
alias: "n",
|
|
21
98
|
type: "string",
|
|
@@ -27,6 +104,11 @@ const args = yargs(argv)
|
|
|
27
104
|
default: false,
|
|
28
105
|
describe: "Run in background and write logs to ~/.conductor/logs/<timestamp>.log",
|
|
29
106
|
})
|
|
107
|
+
.option("force", {
|
|
108
|
+
type: "boolean",
|
|
109
|
+
default: false,
|
|
110
|
+
describe: "Force start by stopping an existing daemon process if needed",
|
|
111
|
+
})
|
|
30
112
|
.option("clean-all", {
|
|
31
113
|
type: "boolean",
|
|
32
114
|
default: false,
|
|
@@ -41,14 +123,24 @@ const args = yargs(argv)
|
|
|
41
123
|
"Use custom config file and daemon name",
|
|
42
124
|
)
|
|
43
125
|
.example("$0 --nohup", "Run daemon in background with logfile")
|
|
126
|
+
.example("$0 --nohup --force", "Restart daemon in background by stopping the existing one")
|
|
44
127
|
.help()
|
|
45
128
|
.strict()
|
|
46
129
|
.parse();
|
|
47
130
|
|
|
48
131
|
if (args.nohup) {
|
|
132
|
+
const workspaceRoot = resolveWorkspaceRoot(args.configFile);
|
|
133
|
+
const runningPid = findRunningDaemonPid(workspaceRoot);
|
|
134
|
+
if (runningPid && !args.force) {
|
|
135
|
+
process.stderr.write(
|
|
136
|
+
`${CLI_NAME} detected an existing daemon (PID ${runningPid}). Use --force to restart.\n`,
|
|
137
|
+
);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
|
|
49
141
|
const logsDir = path.join(os.homedir(), ".conductor", "logs");
|
|
50
142
|
fs.mkdirSync(logsDir, { recursive: true });
|
|
51
|
-
const timestamp = new Date()
|
|
143
|
+
const timestamp = formatBeijingTimestampForFile(new Date());
|
|
52
144
|
const logPath = path.join(logsDir, `${timestamp}.log`);
|
|
53
145
|
const filteredArgv = argv.filter((arg) => !(arg === "--nohup" || arg.startsWith("--nohup=")));
|
|
54
146
|
const logFd = fs.openSync(logPath, "a");
|
|
@@ -66,4 +158,5 @@ startDaemon({
|
|
|
66
158
|
NAME: args.name,
|
|
67
159
|
CLEAN_ALL: args.cleanAll,
|
|
68
160
|
CONFIG_FILE: args.configFile,
|
|
161
|
+
FORCE: args.force,
|
|
69
162
|
});
|