@love-moon/conductor-cli 0.7.4 → 0.7.6
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/CHANGELOG.md +23 -0
- package/bin/conductor-channel.js +3 -4
- package/bin/conductor-chrome.js +3 -2
- package/bin/conductor-config.js +55 -5
- package/bin/conductor-daemon.js +18 -7
- package/bin/conductor-fire.js +23 -19
- package/bin/conductor-send-file.js +2 -3
- package/bin/conductor-serve-ai.js +1 -1
- package/bin/conductor.js +4 -0
- package/package.json +5 -5
- package/src/ai-manager-handlers.js +1 -1
- package/src/cli-update-notifier.js +11 -4
- package/src/conductor-paths.js +61 -0
- package/src/custom-command-handlers.js +4 -2
- package/src/daemon.js +34 -20
- package/src/entity-helpers.js +2 -8
- package/src/fire/resume.js +17 -17
- package/src/runtime-backends.js +2 -4
- package/src/serve-ai/config.js +7 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @love-moon/conductor-cli
|
|
2
2
|
|
|
3
|
+
## 0.7.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 7bbb412: Add `CONDUCTOR_HOME` support for relocating user-level configuration, logs,
|
|
8
|
+
Fire locks, sessions, update metadata, and AI manager caches while leaving
|
|
9
|
+
project-scoped `.conductor` directories and Fire task markers in place.
|
|
10
|
+
|
|
11
|
+
Migrate device authorization to `conductor.conductor-ai.top` while preserving
|
|
12
|
+
compatibility with the legacy official endpoint and self-hosted backends.
|
|
13
|
+
|
|
14
|
+
- Updated dependencies [7bbb412]
|
|
15
|
+
- @love-moon/conductor-sdk@0.7.6
|
|
16
|
+
- @love-moon/ai-sdk@0.7.6
|
|
17
|
+
|
|
18
|
+
## 0.7.5
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Updated dependencies [f91a5df]
|
|
23
|
+
- @love-moon/ai-sdk@0.7.5
|
|
24
|
+
- @love-moon/conductor-sdk@0.7.5
|
|
25
|
+
|
|
3
26
|
## 0.7.4
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/bin/conductor-channel.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
4
|
import path from "node:path";
|
|
6
5
|
import process from "node:process";
|
|
7
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -10,6 +9,7 @@ import yargs from "yargs/yargs";
|
|
|
10
9
|
import { hideBin } from "yargs/helpers";
|
|
11
10
|
import { loadConfig } from "@love-moon/conductor-sdk";
|
|
12
11
|
import { envForExplicitConfigFile } from "../src/config-env.js";
|
|
12
|
+
import { resolveConductorConfigPath } from "../src/conductor-paths.js";
|
|
13
13
|
|
|
14
14
|
const isMainModule = (() => {
|
|
15
15
|
const currentFile = fileURLToPath(import.meta.url);
|
|
@@ -18,8 +18,7 @@ const isMainModule = (() => {
|
|
|
18
18
|
})();
|
|
19
19
|
|
|
20
20
|
function resolveDefaultConfigPath(env = process.env) {
|
|
21
|
-
|
|
22
|
-
return path.join(home, ".conductor", "config.yaml");
|
|
21
|
+
return resolveConductorConfigPath(undefined, env);
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
function readTextFile(filePath) {
|
|
@@ -55,7 +54,7 @@ export async function connectFeishuChannel(options = {}) {
|
|
|
55
54
|
throw new Error("fetch is not available");
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
const resolvedConfigPath =
|
|
57
|
+
const resolvedConfigPath = resolveConductorConfigPath(options.configFile, env);
|
|
59
58
|
const rawYaml = readTextFile(resolvedConfigPath);
|
|
60
59
|
const config = loadConfig(resolvedConfigPath, {
|
|
61
60
|
env: envForExplicitConfigFile(options.configFile, env),
|
package/bin/conductor-chrome.js
CHANGED
|
@@ -9,6 +9,7 @@ import yargs from 'yargs';
|
|
|
9
9
|
import { hideBin } from 'yargs/helpers';
|
|
10
10
|
import { launch as launchChrome } from 'chrome-launcher';
|
|
11
11
|
import CDP from 'chrome-remote-interface';
|
|
12
|
+
import { resolveConductorConfigPath } from '../src/conductor-paths.js';
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
const BACKEND_URLS = {
|
|
@@ -52,7 +53,7 @@ const PAGE_AUTOMATION_PATH = new URL('pageAutomation.js', AUTOMATION_SRC_ROOT);
|
|
|
52
53
|
const PROVIDERS_DIR = new URL('providers/', AUTOMATION_SRC_ROOT);
|
|
53
54
|
let automationScriptCache;
|
|
54
55
|
|
|
55
|
-
const CONFIG_PATH =
|
|
56
|
+
const CONFIG_PATH = resolveConductorConfigPath();
|
|
56
57
|
|
|
57
58
|
function expandHomeDir(maybePath) {
|
|
58
59
|
if (!maybePath) {
|
|
@@ -80,7 +81,7 @@ async function loadUserConfig() {
|
|
|
80
81
|
}
|
|
81
82
|
} catch (error) {
|
|
82
83
|
if (error.code !== 'ENOENT') {
|
|
83
|
-
console.warn(
|
|
84
|
+
console.warn(`Failed to read ${CONFIG_PATH}:`, error);
|
|
84
85
|
}
|
|
85
86
|
cachedUserConfig = {};
|
|
86
87
|
}
|
package/bin/conductor-config.js
CHANGED
|
@@ -9,9 +9,10 @@ import { execFileSync, execSync } from "node:child_process";
|
|
|
9
9
|
import yargs from "yargs/yargs";
|
|
10
10
|
import { hideBin } from "yargs/helpers";
|
|
11
11
|
import { RUNTIME_SUPPORTED_BACKENDS } from "../src/runtime-backends.js";
|
|
12
|
+
import { resolveConductorConfigPath } from "../src/conductor-paths.js";
|
|
12
13
|
|
|
13
|
-
const
|
|
14
|
-
const
|
|
14
|
+
const CONFIG_FILE = resolveConductorConfigPath();
|
|
15
|
+
const CONFIG_DIR = path.dirname(CONFIG_FILE);
|
|
15
16
|
const packageJson = JSON.parse(
|
|
16
17
|
fs.readFileSync(new URL("../package.json", import.meta.url), "utf-8"),
|
|
17
18
|
);
|
|
@@ -68,6 +69,9 @@ const defaultDaemonName = os.hostname() || "my-daemon";
|
|
|
68
69
|
const cliVersion = packageJson.version || "unknown";
|
|
69
70
|
const OPENCODE_INSTALL_URL = "https://opencode.ai/install";
|
|
70
71
|
const OPENCODE_NPM_PACKAGE = "opencode-ai";
|
|
72
|
+
const LEGACY_CONDUCTOR_PUBLIC_HOST = "conductor-ai.top";
|
|
73
|
+
const CONDUCTOR_PUBLIC_HOST = "conductor.conductor-ai.top";
|
|
74
|
+
const CONDUCTOR_PUBLIC_ORIGIN = "https://conductor.conductor-ai.top";
|
|
71
75
|
|
|
72
76
|
const COLORS = {
|
|
73
77
|
yellow: "\x1b[33m",
|
|
@@ -518,7 +522,9 @@ async function authorizeDeviceAndGetToken() {
|
|
|
518
522
|
console.log(colorize("Open this link in your browser to authorize this device:", "cyan"));
|
|
519
523
|
console.log("");
|
|
520
524
|
console.log(`Device code: ${colorize(startData.user_code, "bold")}`);
|
|
521
|
-
console.log(
|
|
525
|
+
console.log(
|
|
526
|
+
`Direct link: ${normalizeOfficialConductorUrl(startData.verification_uri_complete, backendUrl)}`,
|
|
527
|
+
);
|
|
522
528
|
console.log("");
|
|
523
529
|
console.log("Only approve the request if the web page shows the same device code.");
|
|
524
530
|
console.log("");
|
|
@@ -551,10 +557,17 @@ async function authorizeDeviceAndGetToken() {
|
|
|
551
557
|
continue;
|
|
552
558
|
}
|
|
553
559
|
if (pollData.status === "approved") {
|
|
560
|
+
const resolvedBackendUrl = normalizeOfficialConductorUrl(
|
|
561
|
+
pollData.backend_url || backendUrl,
|
|
562
|
+
backendUrl,
|
|
563
|
+
);
|
|
554
564
|
const result = {
|
|
555
565
|
agentToken: pollData.agent_token,
|
|
556
|
-
backendUrl:
|
|
557
|
-
websocketUrl:
|
|
566
|
+
backendUrl: resolvedBackendUrl,
|
|
567
|
+
websocketUrl: normalizeOfficialConductorUrl(
|
|
568
|
+
pollData.websocket_url || null,
|
|
569
|
+
backendUrl,
|
|
570
|
+
),
|
|
558
571
|
};
|
|
559
572
|
lastDeviceAuthConfig = result;
|
|
560
573
|
console.log(colorize("✓ Device authorized", "green"));
|
|
@@ -571,6 +584,43 @@ async function authorizeDeviceAndGetToken() {
|
|
|
571
584
|
throw new Error("Device authorization timed out");
|
|
572
585
|
}
|
|
573
586
|
|
|
587
|
+
function isOfficialConductorBackend(value) {
|
|
588
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
try {
|
|
592
|
+
const hostname = new URL(value).hostname;
|
|
593
|
+
return hostname === LEGACY_CONDUCTOR_PUBLIC_HOST || hostname === CONDUCTOR_PUBLIC_HOST;
|
|
594
|
+
} catch {
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function normalizeOfficialConductorUrl(value, requestBackendUrl) {
|
|
600
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
601
|
+
return value;
|
|
602
|
+
}
|
|
603
|
+
if (!isOfficialConductorBackend(requestBackendUrl)) {
|
|
604
|
+
return value;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
try {
|
|
608
|
+
const parsed = new URL(value);
|
|
609
|
+
if (parsed.hostname !== LEGACY_CONDUCTOR_PUBLIC_HOST) {
|
|
610
|
+
return value;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const canonicalOrigin = new URL(CONDUCTOR_PUBLIC_ORIGIN);
|
|
614
|
+
parsed.protocol = parsed.protocol === "ws:" || parsed.protocol === "wss:"
|
|
615
|
+
? "wss:"
|
|
616
|
+
: canonicalOrigin.protocol;
|
|
617
|
+
parsed.host = canonicalOrigin.host;
|
|
618
|
+
return parsed.toString();
|
|
619
|
+
} catch {
|
|
620
|
+
return value;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
574
624
|
async function parseJsonResponse(response) {
|
|
575
625
|
try {
|
|
576
626
|
return await response.json();
|
package/bin/conductor-daemon.js
CHANGED
|
@@ -9,10 +9,16 @@ import { hideBin } from "yargs/helpers";
|
|
|
9
9
|
import yaml from "js-yaml";
|
|
10
10
|
|
|
11
11
|
import { startDaemon } from "../src/daemon.js";
|
|
12
|
+
import {
|
|
13
|
+
materializeConductorPathEnv,
|
|
14
|
+
resolveConductorConfigPath,
|
|
15
|
+
resolveConductorHome,
|
|
16
|
+
} from "../src/conductor-paths.js";
|
|
12
17
|
|
|
13
18
|
const argv = hideBin(process.argv);
|
|
14
19
|
|
|
15
20
|
const CLI_NAME = process.env.CONDUCTOR_CLI_NAME || "conductor-daemon";
|
|
21
|
+
const CONDUCTOR_HOME = resolveConductorHome();
|
|
16
22
|
|
|
17
23
|
function parseJsonArrayEnv(value) {
|
|
18
24
|
if (typeof value !== "string" || !value.trim()) {
|
|
@@ -75,8 +81,7 @@ function formatBeijingTimestampForFile(date = new Date()) {
|
|
|
75
81
|
|
|
76
82
|
function loadUserConfig(configFilePath) {
|
|
77
83
|
try {
|
|
78
|
-
const
|
|
79
|
-
const configPath = configFilePath || path.join(home, ".conductor", "config.yaml");
|
|
84
|
+
const configPath = resolveConductorConfigPath(configFilePath);
|
|
80
85
|
if (!fs.existsSync(configPath)) {
|
|
81
86
|
return {};
|
|
82
87
|
}
|
|
@@ -146,7 +151,7 @@ const args = yargs(argv)
|
|
|
146
151
|
.option("nohup", {
|
|
147
152
|
type: "boolean",
|
|
148
153
|
default: false,
|
|
149
|
-
describe:
|
|
154
|
+
describe: `Run in background and write logs to ${path.join(CONDUCTOR_HOME, "logs", "<timestamp>.log")}`,
|
|
150
155
|
})
|
|
151
156
|
.option("force", {
|
|
152
157
|
type: "boolean",
|
|
@@ -162,15 +167,21 @@ const args = yargs(argv)
|
|
|
162
167
|
type: "string",
|
|
163
168
|
describe: "Path to Conductor config file",
|
|
164
169
|
})
|
|
165
|
-
.example(
|
|
170
|
+
.example(`$0 --config-file ${resolveConductorConfigPath()}`, "Run with daemon_name from config")
|
|
166
171
|
.example("$0 --nohup", "Run daemon in background with logfile")
|
|
167
172
|
.example("$0 --nohup --force", "Restart daemon in background by stopping the existing one")
|
|
168
173
|
.help()
|
|
169
174
|
.strict()
|
|
170
175
|
.parse();
|
|
171
176
|
|
|
177
|
+
const materializedConductorPathEnv = materializeConductorPathEnv(args.configFile);
|
|
178
|
+
Object.assign(process.env, materializedConductorPathEnv);
|
|
179
|
+
const effectiveConfigFile = args.configFile
|
|
180
|
+
? materializedConductorPathEnv.CONDUCTOR_CONFIG
|
|
181
|
+
: undefined;
|
|
182
|
+
|
|
172
183
|
if (args.nohup) {
|
|
173
|
-
const workspaceRoot = resolveWorkspaceRoot(
|
|
184
|
+
const workspaceRoot = resolveWorkspaceRoot(effectiveConfigFile);
|
|
174
185
|
const runningPid = findRunningDaemonPid(workspaceRoot);
|
|
175
186
|
if (runningPid && !args.force) {
|
|
176
187
|
process.stderr.write(
|
|
@@ -179,7 +190,7 @@ if (args.nohup) {
|
|
|
179
190
|
process.exit(1);
|
|
180
191
|
}
|
|
181
192
|
|
|
182
|
-
const logsDir = path.join(
|
|
193
|
+
const logsDir = path.join(resolveConductorHome(), "logs");
|
|
183
194
|
fs.mkdirSync(logsDir, { recursive: true });
|
|
184
195
|
const timestamp = formatBeijingTimestampForFile(new Date());
|
|
185
196
|
const logPath = path.join(logsDir, `${timestamp}.log`);
|
|
@@ -197,7 +208,7 @@ if (args.nohup) {
|
|
|
197
208
|
|
|
198
209
|
startDaemon({
|
|
199
210
|
CLEAN_ALL: args.cleanAll,
|
|
200
|
-
CONFIG_FILE:
|
|
211
|
+
CONFIG_FILE: effectiveConfigFile,
|
|
201
212
|
FORCE: args.force,
|
|
202
213
|
...resolveLauncherConfig(),
|
|
203
214
|
});
|
package/bin/conductor-fire.js
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
normalizeRuntimeBackendAlias,
|
|
37
37
|
normalizeRuntimeBackendName,
|
|
38
38
|
} from "../src/runtime-backends.js";
|
|
39
|
+
import { resolveConductorConfigPath, resolveConductorHome } from "../src/conductor-paths.js";
|
|
39
40
|
|
|
40
41
|
const __filename = fileURLToPath(import.meta.url);
|
|
41
42
|
const __dirname = path.dirname(__filename);
|
|
@@ -111,8 +112,7 @@ export function shouldFireReportTaskStatus({ launchedByDaemon = false, phase } =
|
|
|
111
112
|
|
|
112
113
|
// Load allow_cli_list from config file (no defaults - must be configured)
|
|
113
114
|
function loadFireConfigYaml(configFilePath) {
|
|
114
|
-
const
|
|
115
|
-
const configPath = configFilePath || process.env.CONDUCTOR_CONFIG || path.join(home, ".conductor", "config.yaml");
|
|
115
|
+
const configPath = resolveConductorConfigPath(configFilePath);
|
|
116
116
|
try {
|
|
117
117
|
if (fs.existsSync(configPath)) {
|
|
118
118
|
const content = fs.readFileSync(configPath, "utf8");
|
|
@@ -313,14 +313,14 @@ function resolveLockWorkingDirectory(workingDirectory) {
|
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
export function resolveFreshSessionBootstrapLockPath(backendName, workingDirectory) {
|
|
316
|
+
export function resolveFreshSessionBootstrapLockPath(backendName, workingDirectory, env = process.env) {
|
|
317
317
|
const normalizedBackend = String(backendName || "").trim().toLowerCase();
|
|
318
318
|
if (normalizedBackend !== "codex") {
|
|
319
319
|
return null;
|
|
320
320
|
}
|
|
321
321
|
const lockKey = `${normalizedBackend}:${resolveLockWorkingDirectory(workingDirectory)}`;
|
|
322
322
|
const digest = createHash("sha1").update(lockKey).digest("hex");
|
|
323
|
-
return path.join(
|
|
323
|
+
return path.join(resolveConductorHome(env), "locks", `session-bootstrap-${digest}.lock`);
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
function acquireFileLock(lockPath) {
|
|
@@ -605,7 +605,7 @@ async function main() {
|
|
|
605
605
|
if (discoveryError) {
|
|
606
606
|
throw discoveryError;
|
|
607
607
|
}
|
|
608
|
-
process.stdout.write(`No supported backends configured.\n\nAdd allow_cli_list to your config file (
|
|
608
|
+
process.stdout.write(`No supported backends configured.\n\nAdd allow_cli_list to your config file (${resolveConductorConfigPath(cliArgs.configFile)}):\n allow_cli_list:\n codex: codex --dangerously-bypass-approvals-and-sandbox\n claude: claude --dangerously-skip-permissions\n kimi: kimi\n opencode: opencode\n`);
|
|
609
609
|
} else {
|
|
610
610
|
if (supportedBackends.length > 0) {
|
|
611
611
|
process.stdout.write(`Supported backends (from config):\n`);
|
|
@@ -1306,6 +1306,7 @@ export async function parseCliArgs(argvInput = process.argv) {
|
|
|
1306
1306
|
// Handle help early
|
|
1307
1307
|
if (helpWithoutSeparator) {
|
|
1308
1308
|
const defaultBackend = supportedBackends[0] || externalBackends[0] || "none";
|
|
1309
|
+
const defaultConfigPath = resolveConductorConfigPath(configFileFromArgs);
|
|
1309
1310
|
process.stdout.write(`${CLI_NAME} - Conductor-aware AI coding agent runner
|
|
1310
1311
|
|
|
1311
1312
|
Usage: ${CLI_NAME} [options] -- [backend options and prompt]
|
|
@@ -1320,7 +1321,7 @@ Options:
|
|
|
1320
1321
|
-v, --version Show Conductor CLI version and exit
|
|
1321
1322
|
-h, --help Show this help message
|
|
1322
1323
|
|
|
1323
|
-
Config file format (
|
|
1324
|
+
Config file format (${defaultConfigPath}):
|
|
1324
1325
|
allow_cli_list:
|
|
1325
1326
|
codex: codex --dangerously-bypass-approvals-and-sandbox
|
|
1326
1327
|
claude: claude --dangerously-skip-permissions
|
|
@@ -1335,9 +1336,11 @@ Examples:
|
|
|
1335
1336
|
${CLI_NAME} --backend codex --resume <id> # Resume Codex session
|
|
1336
1337
|
${CLI_NAME} --backend kimi --resume <id> # Resume Kimi session
|
|
1337
1338
|
${CLI_NAME} --list-backends # Show configured backends
|
|
1338
|
-
${CLI_NAME} --config-file
|
|
1339
|
+
${CLI_NAME} --config-file ${defaultConfigPath} -- "fix the bug"
|
|
1339
1340
|
|
|
1340
1341
|
Environment:
|
|
1342
|
+
CONDUCTOR_HOME User data directory [default: ~/.conductor]
|
|
1343
|
+
CONDUCTOR_CONFIG Config file override (takes precedence over CONDUCTOR_HOME)
|
|
1341
1344
|
CONDUCTOR_BACKEND Default backend
|
|
1342
1345
|
CONDUCTOR_PROJECT_ID Project ID to attach to
|
|
1343
1346
|
CONDUCTOR_TASK_ID Attach to existing task instead of creating new one
|
|
@@ -1460,30 +1463,31 @@ function normalizeTaskId(value) {
|
|
|
1460
1463
|
return value.trim();
|
|
1461
1464
|
}
|
|
1462
1465
|
|
|
1463
|
-
function resolveFireStateDir(workingDirectory) {
|
|
1466
|
+
export function resolveFireStateDir(workingDirectory, env = process.env) {
|
|
1464
1467
|
// Priority:
|
|
1465
1468
|
// 1. CONDUCTOR_FIRE_STATE_DIR env override (used by tests to isolate the
|
|
1466
1469
|
// marker dir into a tmpdir; also handy for ops to relocate state).
|
|
1467
|
-
// 2. Explicit `workingDirectory` argument
|
|
1468
|
-
//
|
|
1469
|
-
//
|
|
1470
|
+
// 2. Explicit `workingDirectory` argument. Production marker writes use
|
|
1471
|
+
// this project-scoped path so commands such as send-file can discover
|
|
1472
|
+
// the active task while walking up from the current directory.
|
|
1473
|
+
// 3. $CONDUCTOR_HOME/state (defaults to ~/.conductor/state) only when no
|
|
1474
|
+
// project working directory is available.
|
|
1470
1475
|
//
|
|
1471
1476
|
// We deliberately do NOT default to process.cwd(), which would pollute
|
|
1472
1477
|
// every project directory a user runs `conductor fire` from and cause tests
|
|
1473
1478
|
// to leak marker files into the repo.
|
|
1474
1479
|
const envOverride =
|
|
1475
|
-
typeof
|
|
1476
|
-
|
|
1477
|
-
?
|
|
1480
|
+
typeof env.CONDUCTOR_FIRE_STATE_DIR === "string" &&
|
|
1481
|
+
env.CONDUCTOR_FIRE_STATE_DIR.trim()
|
|
1482
|
+
? env.CONDUCTOR_FIRE_STATE_DIR.trim()
|
|
1478
1483
|
: "";
|
|
1479
1484
|
if (envOverride) {
|
|
1480
1485
|
return path.resolve(envOverride);
|
|
1481
1486
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
return path.join(baseDir, ".conductor", "state");
|
|
1487
|
+
if (typeof workingDirectory === "string" && workingDirectory.trim()) {
|
|
1488
|
+
return path.join(path.resolve(workingDirectory.trim()), ".conductor", "state");
|
|
1489
|
+
}
|
|
1490
|
+
return path.join(resolveConductorHome(env), "state");
|
|
1487
1491
|
}
|
|
1488
1492
|
|
|
1489
1493
|
/**
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import fsp from "node:fs/promises";
|
|
5
|
-
import os from "node:os";
|
|
6
5
|
import path from "node:path";
|
|
7
6
|
import process from "node:process";
|
|
8
7
|
import { fileURLToPath } from "node:url";
|
|
@@ -11,9 +10,9 @@ import yargs from "yargs/yargs";
|
|
|
11
10
|
import { hideBin } from "yargs/helpers";
|
|
12
11
|
import { ConductorConfig, loadConfig } from "@love-moon/conductor-sdk";
|
|
13
12
|
import { envForExplicitConfigFile } from "../src/config-env.js";
|
|
13
|
+
import { resolveConductorConfigPath } from "../src/conductor-paths.js";
|
|
14
14
|
|
|
15
15
|
const DEFAULT_MIME_TYPE = "application/octet-stream";
|
|
16
|
-
const DEFAULT_CONFIG_PATH = path.join(os.homedir(), ".conductor", "config.yaml");
|
|
17
16
|
const FIRE_TASK_MARKER_PREFIX = "active-fire";
|
|
18
17
|
|
|
19
18
|
const EXTENSION_TO_MIME = {
|
|
@@ -110,7 +109,7 @@ export function detectTaskId(options = {}) {
|
|
|
110
109
|
}
|
|
111
110
|
|
|
112
111
|
function loadCliConfig(configFile, env = process.env) {
|
|
113
|
-
const configPath = configFile
|
|
112
|
+
const configPath = resolveConductorConfigPath(configFile, env);
|
|
114
113
|
const configEnv = envForExplicitConfigFile(configFile, env);
|
|
115
114
|
if (fs.existsSync(configPath)) {
|
|
116
115
|
return loadConfig(configPath, { env: configEnv });
|
|
@@ -117,7 +117,7 @@ async function main() {
|
|
|
117
117
|
default: false,
|
|
118
118
|
describe: "Overwrite an existing config-ai-serve.yaml",
|
|
119
119
|
})
|
|
120
|
-
.example("$0 init", "Create
|
|
120
|
+
.example("$0 init", "Create config-ai-serve.yaml under CONDUCTOR_HOME")
|
|
121
121
|
.example("$0 init --config-file /tmp/custom/config.yaml", "Create /tmp/custom/config-ai-serve.yaml"),
|
|
122
122
|
async (args) => {
|
|
123
123
|
const { conductorConfigPath, serveAiConfigPath } = resolveServeAiConfigPaths(args.configFile);
|
package/bin/conductor.js
CHANGED
|
@@ -140,6 +140,10 @@ Options:
|
|
|
140
140
|
-h, --help Show this help message
|
|
141
141
|
-v, --version Show version information
|
|
142
142
|
|
|
143
|
+
Environment:
|
|
144
|
+
CONDUCTOR_HOME User data directory (default: ~/.conductor)
|
|
145
|
+
CONDUCTOR_CONFIG Config file override (takes precedence over CONDUCTOR_HOME)
|
|
146
|
+
|
|
143
147
|
Examples:
|
|
144
148
|
conductor fire -- "fix the bug"
|
|
145
149
|
conductor fire --backend claude -- "add feature"
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/conductor-cli",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"gitCommitId": "
|
|
3
|
+
"version": "0.7.6",
|
|
4
|
+
"gitCommitId": "c939df2",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/lovemoon-ai/conductor.git"
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"test": "node --test test/*.test.js"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@love-moon/ai-sdk": "0.7.
|
|
28
|
-
"@love-moon/conductor-sdk": "0.7.
|
|
27
|
+
"@love-moon/ai-sdk": "0.7.6",
|
|
28
|
+
"@love-moon/conductor-sdk": "0.7.6",
|
|
29
29
|
"@github/copilot-sdk": "^0.3.0",
|
|
30
30
|
"chrome-launcher": "^1.2.1",
|
|
31
31
|
"chrome-remote-interface": "^0.33.0",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"optionalDependencies": {
|
|
40
40
|
"@roamhq/wrtc": "^0.10.0",
|
|
41
|
-
"@love-moon/chat-web": "0.7.
|
|
41
|
+
"@love-moon/chat-web": "0.7.6"
|
|
42
42
|
},
|
|
43
43
|
"pnpm": {
|
|
44
44
|
"onlyBuiltDependencies": [
|
|
@@ -9,7 +9,7 @@ const BASE_QUOTA_TOOLS = ["codex", "claude", "kimi", "copilot"];
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @param {object} opts
|
|
12
|
-
* @param {string} [opts.configPath] Path to
|
|
12
|
+
* @param {string} [opts.configPath] Path to config.yaml. Defaults to $CONDUCTOR_HOME/config.yaml.
|
|
13
13
|
*/
|
|
14
14
|
export function createAiManagerHandlers(opts = {}) {
|
|
15
15
|
const manager = new AiManager(opts.configPath ? { configPath: opts.configPath } : undefined);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
-
import os from "node:os";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
|
|
5
4
|
import { buildUpgradeCommand, fetchLatestVersion, isNewerVersion } from "./version-check.js";
|
|
5
|
+
import { resolveConductorHome } from "./conductor-paths.js";
|
|
6
6
|
|
|
7
7
|
export const DEFAULT_VERSION_CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
8
8
|
export const DEFAULT_VERSION_NOTIFY_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
@@ -44,8 +44,13 @@ function isTimestampOlderThan(value, ageMs, nowMs) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export function resolveVersionCheckCachePath(options = {}) {
|
|
47
|
-
const
|
|
48
|
-
|
|
47
|
+
const env = options.env || process.env;
|
|
48
|
+
const conductorHome = options.conductorHome
|
|
49
|
+
? path.resolve(options.conductorHome)
|
|
50
|
+
: options.homeDir
|
|
51
|
+
? resolveConductorHome({}, { userHome: options.homeDir })
|
|
52
|
+
: resolveConductorHome(env);
|
|
53
|
+
return path.join(conductorHome, DEFAULT_CACHE_FILE);
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
export function normalizeVersionCheckCache(value) {
|
|
@@ -171,7 +176,9 @@ export async function maybeCheckForUpdates(options = {}) {
|
|
|
171
176
|
const fetchLatestVersionFn = options.fetchLatestVersion || fetchLatestVersion;
|
|
172
177
|
const cacheOptions = {
|
|
173
178
|
cachePath: options.cachePath,
|
|
174
|
-
|
|
179
|
+
conductorHome: options.conductorHome,
|
|
180
|
+
homeDir: options.homeDir,
|
|
181
|
+
env,
|
|
175
182
|
readFile: options.readFile,
|
|
176
183
|
writeFile: options.writeFile,
|
|
177
184
|
mkdir: options.mkdir,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export const CONDUCTOR_HOME_ENV_VAR = "CONDUCTOR_HOME";
|
|
5
|
+
export const CONDUCTOR_CONFIG_ENV_VAR = "CONDUCTOR_CONFIG";
|
|
6
|
+
|
|
7
|
+
function normalizeOptionalString(value) {
|
|
8
|
+
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function resolveUserHome(env = process.env, fallbackHome = os.homedir()) {
|
|
12
|
+
return (
|
|
13
|
+
normalizeOptionalString(env?.HOME) ||
|
|
14
|
+
normalizeOptionalString(env?.USERPROFILE) ||
|
|
15
|
+
normalizeOptionalString(fallbackHome) ||
|
|
16
|
+
"/tmp"
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function resolveHomeRelativePath(value, env = process.env, fallbackHome = os.homedir()) {
|
|
21
|
+
const normalized = normalizeOptionalString(value);
|
|
22
|
+
if (!normalized) {
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
const userHome = resolveUserHome(env, fallbackHome);
|
|
26
|
+
if (normalized === "~") {
|
|
27
|
+
return path.resolve(userHome);
|
|
28
|
+
}
|
|
29
|
+
if (normalized.startsWith("~/") || normalized.startsWith("~\\")) {
|
|
30
|
+
return path.resolve(userHome, normalized.slice(2));
|
|
31
|
+
}
|
|
32
|
+
return path.resolve(normalized);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function resolveConductorHome(env = process.env, options = {}) {
|
|
36
|
+
const configuredHome = normalizeOptionalString(env?.[CONDUCTOR_HOME_ENV_VAR]);
|
|
37
|
+
if (configuredHome) {
|
|
38
|
+
return resolveHomeRelativePath(configuredHome, env, options.fallbackHome);
|
|
39
|
+
}
|
|
40
|
+
const userHome = normalizeOptionalString(options.userHome) || resolveUserHome(env, options.fallbackHome);
|
|
41
|
+
return path.join(path.resolve(userHome), ".conductor");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function resolveConductorConfigPath(configFile, env = process.env, options = {}) {
|
|
45
|
+
const explicitPath = normalizeOptionalString(configFile);
|
|
46
|
+
if (explicitPath) {
|
|
47
|
+
return resolveHomeRelativePath(explicitPath, env, options.fallbackHome);
|
|
48
|
+
}
|
|
49
|
+
const configuredPath = normalizeOptionalString(env?.[CONDUCTOR_CONFIG_ENV_VAR]);
|
|
50
|
+
if (configuredPath) {
|
|
51
|
+
return resolveHomeRelativePath(configuredPath, env, options.fallbackHome);
|
|
52
|
+
}
|
|
53
|
+
return path.join(resolveConductorHome(env, options), "config.yaml");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function materializeConductorPathEnv(configFile, env = process.env, options = {}) {
|
|
57
|
+
return {
|
|
58
|
+
CONDUCTOR_HOME: resolveConductorHome(env, options),
|
|
59
|
+
CONDUCTOR_CONFIG: resolveConductorConfigPath(configFile, env, options),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -7,6 +7,8 @@ import { randomUUID } from "node:crypto";
|
|
|
7
7
|
|
|
8
8
|
import yaml from "js-yaml";
|
|
9
9
|
|
|
10
|
+
import { resolveConductorConfigPath } from "./conductor-paths.js";
|
|
11
|
+
|
|
10
12
|
const VALID_ACTIONS = new Set(["list", "run", "status"]);
|
|
11
13
|
const MAX_TAIL_CHARS = 12_000;
|
|
12
14
|
const MAX_RUNS = 200;
|
|
@@ -25,7 +27,7 @@ export const CUSTOM_COMMANDS_CAPABILITY = "custom_commands";
|
|
|
25
27
|
* @param {typeof spawn} [opts.spawnFn]
|
|
26
28
|
*/
|
|
27
29
|
export function createCustomCommandHandlers(opts = {}) {
|
|
28
|
-
const configPath = opts.configPath
|
|
30
|
+
const configPath = resolveConductorConfigPath(opts.configPath);
|
|
29
31
|
const spawnFn = opts.spawnFn || spawn;
|
|
30
32
|
const runs = new Map();
|
|
31
33
|
const runningByKey = new Map();
|
|
@@ -202,7 +204,7 @@ export async function loadCustomCommands(configPath) {
|
|
|
202
204
|
* @param {string} configPath
|
|
203
205
|
* @returns {CustomCommand[]}
|
|
204
206
|
*/
|
|
205
|
-
export function parseCustomCommandsConfig(config, configPath =
|
|
207
|
+
export function parseCustomCommandsConfig(config, configPath = resolveConductorConfigPath()) {
|
|
206
208
|
const root = config && typeof config === "object" ? config : {};
|
|
207
209
|
const rawCommands = root.custom_commands;
|
|
208
210
|
if (rawCommands === undefined || rawCommands === null) {
|
package/src/daemon.js
CHANGED
|
@@ -17,6 +17,11 @@ import {
|
|
|
17
17
|
} from "@love-moon/conductor-sdk";
|
|
18
18
|
import { DaemonLogCollector } from "./log-collector.js";
|
|
19
19
|
import { envForExplicitConfigFile } from "./config-env.js";
|
|
20
|
+
import {
|
|
21
|
+
materializeConductorPathEnv,
|
|
22
|
+
resolveConductorConfigPath,
|
|
23
|
+
resolveConductorHome,
|
|
24
|
+
} from "./conductor-paths.js";
|
|
20
25
|
import { createAiManagerHandlers, handleAiManagerRequest } from "./ai-manager-handlers.js";
|
|
21
26
|
import {
|
|
22
27
|
CUSTOM_COMMANDS_CAPABILITY,
|
|
@@ -62,8 +67,6 @@ const __dirname = path.dirname(__filename);
|
|
|
62
67
|
const PACKAGE_ROOT = path.join(__dirname, "..");
|
|
63
68
|
const moduleRequire = createRequire(import.meta.url);
|
|
64
69
|
const CLI_PATH = path.resolve(PACKAGE_ROOT, "bin", "conductor-fire.js");
|
|
65
|
-
const DAEMON_LOG_DIR = path.join(os.homedir(), ".conductor", "logs");
|
|
66
|
-
const DAEMON_LOG_PATH = path.join(DAEMON_LOG_DIR, "conductor-daemon.log");
|
|
67
70
|
const CLI_VERSION = (() => {
|
|
68
71
|
try {
|
|
69
72
|
return JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf-8")).version;
|
|
@@ -71,6 +74,14 @@ const CLI_VERSION = (() => {
|
|
|
71
74
|
return "unknown";
|
|
72
75
|
}
|
|
73
76
|
})();
|
|
77
|
+
|
|
78
|
+
export function resolveDaemonLogPaths(env = process.env) {
|
|
79
|
+
const logDir = path.join(resolveConductorHome(env), "logs");
|
|
80
|
+
return {
|
|
81
|
+
logDir,
|
|
82
|
+
logPath: path.join(logDir, "conductor-daemon.log"),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
74
85
|
const PLAN_LIMIT_MESSAGES = {
|
|
75
86
|
manual_fire_active_task: "Free plan limit reached: only 1 active fire task is allowed.",
|
|
76
87
|
app_active_task: "Free plan limit reached: only 1 active app task is allowed.",
|
|
@@ -119,8 +130,9 @@ export function probePtyTaskCapability({
|
|
|
119
130
|
|
|
120
131
|
function appendDaemonLog(line) {
|
|
121
132
|
try {
|
|
122
|
-
|
|
123
|
-
fs.
|
|
133
|
+
const { logDir, logPath } = resolveDaemonLogPaths();
|
|
134
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
135
|
+
fs.appendFileSync(logPath, line);
|
|
124
136
|
} catch {
|
|
125
137
|
// ignore file log errors
|
|
126
138
|
}
|
|
@@ -156,8 +168,7 @@ function sleepSync(ms) {
|
|
|
156
168
|
|
|
157
169
|
function getUserConfig(configFilePath) {
|
|
158
170
|
try {
|
|
159
|
-
const
|
|
160
|
-
const configPath = configFilePath || path.join(home, ".conductor", "config.yaml");
|
|
171
|
+
const configPath = resolveConductorConfigPath(configFilePath);
|
|
161
172
|
if (fs.existsSync(configPath)) {
|
|
162
173
|
const content = fs.readFileSync(configPath, "utf8");
|
|
163
174
|
const parsed = yaml.load(content);
|
|
@@ -179,7 +190,7 @@ function getUserConfig(configFilePath) {
|
|
|
179
190
|
//
|
|
180
191
|
// Resolution order:
|
|
181
192
|
// 1. CONDUCTOR_FIRE_TMUX_MODE env var ("1"/"true"/"on" enable, "0"/"false"/"off" disable)
|
|
182
|
-
// 2. fire_tmux_mode boolean in
|
|
193
|
+
// 2. fire_tmux_mode boolean in the resolved Conductor config.yaml
|
|
183
194
|
// 3. Default: false
|
|
184
195
|
function getFireTmuxModeEnabled(userConfig) {
|
|
185
196
|
const rawEnv = process.env.CONDUCTOR_FIRE_TMUX_MODE;
|
|
@@ -669,10 +680,15 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
669
680
|
};
|
|
670
681
|
|
|
671
682
|
let fileConfig;
|
|
683
|
+
const materializedConductorPathEnv = materializeConductorPathEnv(
|
|
684
|
+
config.CONFIG_FILE,
|
|
685
|
+
process.env,
|
|
686
|
+
);
|
|
687
|
+
const effectiveConfigPath = materializedConductorPathEnv.CONDUCTOR_CONFIG;
|
|
672
688
|
const configFileEnv = envForExplicitConfigFile(config.CONFIG_FILE, process.env);
|
|
673
689
|
try {
|
|
674
690
|
fileConfig = loadConfig(config.CONFIG_FILE, { env: configFileEnv });
|
|
675
|
-
log(`Loaded config from ${
|
|
691
|
+
log(`Loaded config from ${effectiveConfigPath}`);
|
|
676
692
|
} catch (err) {
|
|
677
693
|
if (!(err instanceof ConfigFileNotFound)) {
|
|
678
694
|
log(`Failed to load config: ${err.message}`);
|
|
@@ -712,7 +728,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
712
728
|
os.hostname()
|
|
713
729
|
).trim();
|
|
714
730
|
if (!AGENT_NAME) {
|
|
715
|
-
logError(
|
|
731
|
+
logError(`Daemon name is required. Set daemon_name in ${effectiveConfigPath} or CONDUCTOR_DAEMON_NAME.`);
|
|
716
732
|
return exitAndReturn(1);
|
|
717
733
|
}
|
|
718
734
|
const homeDir = process.env.HOME || os.homedir() || "/tmp";
|
|
@@ -2144,8 +2160,8 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
2144
2160
|
if (advertisedCapabilities.length > 0) {
|
|
2145
2161
|
extraHeaders["x-conductor-capabilities"] = advertisedCapabilities.join(",");
|
|
2146
2162
|
}
|
|
2147
|
-
const aiManagerHandlers = createAiManagerHandlers({ configPath:
|
|
2148
|
-
const customCommandHandlers = createCustomCommandHandlers({ configPath:
|
|
2163
|
+
const aiManagerHandlers = createAiManagerHandlers({ configPath: effectiveConfigPath });
|
|
2164
|
+
const customCommandHandlers = createCustomCommandHandlers({ configPath: effectiveConfigPath });
|
|
2149
2165
|
|
|
2150
2166
|
const client = createWebSocketClient(sdkConfig, {
|
|
2151
2167
|
extraHeaders,
|
|
@@ -2690,15 +2706,16 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
2690
2706
|
|
|
2691
2707
|
let logFd = null;
|
|
2692
2708
|
if (shouldRespawn) {
|
|
2709
|
+
const { logDir: daemonLogDir, logPath: daemonLogPath } = resolveDaemonLogPaths();
|
|
2693
2710
|
try {
|
|
2694
|
-
mkdirSyncFn(
|
|
2711
|
+
mkdirSyncFn(daemonLogDir, { recursive: true });
|
|
2695
2712
|
} catch {
|
|
2696
2713
|
/* ignore */
|
|
2697
2714
|
}
|
|
2698
|
-
logFd = fs.openSync(
|
|
2715
|
+
logFd = fs.openSync(daemonLogPath, "a");
|
|
2699
2716
|
if (!isBackgroundProcess) {
|
|
2700
2717
|
log(
|
|
2701
|
-
`[${reason}] Foreground daemon will be respawned in background. Logs: ${
|
|
2718
|
+
`[${reason}] Foreground daemon will be respawned in background. Logs: ${daemonLogPath}`
|
|
2702
2719
|
);
|
|
2703
2720
|
}
|
|
2704
2721
|
}
|
|
@@ -2719,6 +2736,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
2719
2736
|
stdio: ["ignore", logFd, logFd],
|
|
2720
2737
|
env: {
|
|
2721
2738
|
...process.env,
|
|
2739
|
+
...materializedConductorPathEnv,
|
|
2722
2740
|
CONDUCTOR_LOCK_HANDOFF_TOKEN: handoffToken,
|
|
2723
2741
|
CONDUCTOR_LOCK_HANDOFF_FROM_PID: String(process.pid),
|
|
2724
2742
|
CONDUCTOR_LOCK_HANDOFF_EXPIRES_AT: String(handoffExpiresAt),
|
|
@@ -5272,15 +5290,13 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
5272
5290
|
|
|
5273
5291
|
const env = {
|
|
5274
5292
|
...stripPtyTaskScopedEnv(process.env),
|
|
5293
|
+
...materializedConductorPathEnv,
|
|
5275
5294
|
PWD: taskDir,
|
|
5276
5295
|
CONDUCTOR_PROJECT_ID: projectId,
|
|
5277
5296
|
CONDUCTOR_TASK_ID: taskId,
|
|
5278
5297
|
CONDUCTOR_LAUNCHED_BY_DAEMON: "1",
|
|
5279
5298
|
...(cliCommand ? { CONDUCTOR_CLI_COMMAND: cliCommand } : {}),
|
|
5280
5299
|
};
|
|
5281
|
-
if (config.CONFIG_FILE) {
|
|
5282
|
-
env.CONDUCTOR_CONFIG = config.CONFIG_FILE;
|
|
5283
|
-
}
|
|
5284
5300
|
if (AGENT_TOKEN) {
|
|
5285
5301
|
env.CONDUCTOR_AGENT_TOKEN = AGENT_TOKEN;
|
|
5286
5302
|
}
|
|
@@ -5915,6 +5931,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
5915
5931
|
|
|
5916
5932
|
const env = {
|
|
5917
5933
|
...stripPtyTaskScopedEnv(process.env),
|
|
5934
|
+
...materializedConductorPathEnv,
|
|
5918
5935
|
PWD: taskDir,
|
|
5919
5936
|
CONDUCTOR_PROJECT_ID: normalizedProjectId,
|
|
5920
5937
|
CONDUCTOR_TASK_ID: normalizedTargetTaskId,
|
|
@@ -5922,9 +5939,6 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
5922
5939
|
...(cliCommand ? { CONDUCTOR_CLI_COMMAND: cliCommand } : {}),
|
|
5923
5940
|
};
|
|
5924
5941
|
env.CONDUCTOR_RESUME_CWD = resolvedResumeCwd;
|
|
5925
|
-
if (config.CONFIG_FILE) {
|
|
5926
|
-
env.CONDUCTOR_CONFIG = config.CONFIG_FILE;
|
|
5927
|
-
}
|
|
5928
5942
|
if (AGENT_TOKEN) {
|
|
5929
5943
|
env.CONDUCTOR_AGENT_TOKEN = AGENT_TOKEN;
|
|
5930
5944
|
}
|
package/src/entity-helpers.js
CHANGED
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import fs from "node:fs";
|
|
18
|
-
import os from "node:os";
|
|
19
18
|
import path from "node:path";
|
|
20
19
|
import process from "node:process";
|
|
21
20
|
import { fileURLToPath } from "node:url";
|
|
22
21
|
|
|
23
22
|
import { envForExplicitConfigFile } from "./config-env.js";
|
|
23
|
+
import { resolveConductorConfigPath } from "./conductor-paths.js";
|
|
24
24
|
|
|
25
25
|
// RFC §4 exit codes
|
|
26
26
|
export const EXIT = {
|
|
@@ -50,14 +50,8 @@ export function getCliVersion() {
|
|
|
50
50
|
return cachedPkgVersion;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const DEFAULT_CONFIG_PATH = path.join(os.homedir(), ".conductor", "config.yaml");
|
|
54
|
-
|
|
55
53
|
export function resolveConfigPath(configFile, env = process.env) {
|
|
56
|
-
|
|
57
|
-
return path.resolve(configFile);
|
|
58
|
-
}
|
|
59
|
-
const home = env.HOME || env.USERPROFILE || os.homedir();
|
|
60
|
-
return path.join(home, ".conductor", "config.yaml");
|
|
54
|
+
return resolveConductorConfigPath(configFile, env);
|
|
61
55
|
}
|
|
62
56
|
|
|
63
57
|
/**
|
package/src/fire/resume.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import { promises as fsp } from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
|
|
6
5
|
import yaml from "js-yaml";
|
|
@@ -19,6 +18,7 @@ import {
|
|
|
19
18
|
normalizeRuntimeBackendAlias,
|
|
20
19
|
resolveConfiguredRuntimeBackend,
|
|
21
20
|
} from "../runtime-backends.js";
|
|
21
|
+
import { resolveConductorConfigPath, resolveConductorHome } from "../conductor-paths.js";
|
|
22
22
|
|
|
23
23
|
function normalizeBackend(backend) {
|
|
24
24
|
return String(backend || "").trim().toLowerCase();
|
|
@@ -28,23 +28,23 @@ function normalizeSessionId(sessionId) {
|
|
|
28
28
|
return typeof sessionId === "string" ? sessionId.trim() : "";
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function
|
|
32
|
-
if (options
|
|
33
|
-
return options.
|
|
31
|
+
function resolveConductorStorageDir(options = {}) {
|
|
32
|
+
if (typeof options.conductorHome === "string" && options.conductorHome.trim()) {
|
|
33
|
+
return path.resolve(options.conductorHome.trim());
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
const env = options.env || process.env;
|
|
36
|
+
if (typeof options.homeDir === "string" && options.homeDir.trim()) {
|
|
37
|
+
return resolveConductorHome(env, { userHome: options.homeDir.trim() });
|
|
38
|
+
}
|
|
39
|
+
return resolveConductorHome(env);
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
function resolveConfigFilePath(options = {}) {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
: "";
|
|
45
|
-
return configuredPath
|
|
46
|
-
? path.resolve(configuredPath)
|
|
47
|
-
: path.join(resolveHomeDir(options), ".conductor", "config.yaml");
|
|
43
|
+
const env = options.env || process.env;
|
|
44
|
+
if (typeof options.homeDir === "string" && options.homeDir.trim() && !env.CONDUCTOR_HOME) {
|
|
45
|
+
return resolveConductorConfigPath(options.configFilePath, env, { userHome: options.homeDir.trim() });
|
|
46
|
+
}
|
|
47
|
+
return resolveConductorConfigPath(options.configFilePath, env);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function normalizeProjectPathCandidate(value) {
|
|
@@ -93,10 +93,10 @@ function md5Hex(value) {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
async function loadConductorSessionRecords(options = {}) {
|
|
96
|
-
const
|
|
96
|
+
const conductorHome = resolveConductorStorageDir(options);
|
|
97
97
|
const defaultPaths = [
|
|
98
|
-
path.join(
|
|
99
|
-
path.join(
|
|
98
|
+
path.join(conductorHome, "session.yaml"),
|
|
99
|
+
path.join(conductorHome, "sessions"),
|
|
100
100
|
];
|
|
101
101
|
const recordFiles = [];
|
|
102
102
|
const pushFile = (filePath) => {
|
package/src/runtime-backends.js
CHANGED
|
@@ -4,6 +4,7 @@ import { pathToFileURL } from "node:url";
|
|
|
4
4
|
|
|
5
5
|
import yaml from "js-yaml";
|
|
6
6
|
import { BUILT_IN_BACKENDS as AI_SDK_BUILT_IN_BACKENDS } from "@love-moon/ai-sdk";
|
|
7
|
+
import { resolveConductorConfigPath } from "./conductor-paths.js";
|
|
7
8
|
|
|
8
9
|
// CLI display order for built-in backends. ai-sdk owns the canonical set of
|
|
9
10
|
// built-in backends; CLI just picks an ordering for "Supported Backends:" log
|
|
@@ -349,10 +350,7 @@ export function isCommandOptionalBuiltInRuntimeBackend(backend) {
|
|
|
349
350
|
}
|
|
350
351
|
|
|
351
352
|
function readConfigEnvValue(configFilePath, key) {
|
|
352
|
-
const targetPath =
|
|
353
|
-
typeof configFilePath === "string" && configFilePath.trim()
|
|
354
|
-
? path.resolve(configFilePath.trim())
|
|
355
|
-
: path.join(process.env.HOME || "", ".conductor", "config.yaml");
|
|
353
|
+
const targetPath = resolveConductorConfigPath(configFilePath);
|
|
356
354
|
try {
|
|
357
355
|
if (!targetPath || !fs.existsSync(targetPath)) {
|
|
358
356
|
return "";
|
package/src/serve-ai/config.js
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
|
|
5
4
|
import yaml from "js-yaml";
|
|
5
|
+
import { resolveConductorConfigPath } from "../conductor-paths.js";
|
|
6
6
|
|
|
7
7
|
export const DEFAULT_CONDUCTOR_CONFIG_BASENAME = "config.yaml";
|
|
8
8
|
export const DEFAULT_SERVE_AI_CONFIG_BASENAME = "config-ai-serve.yaml";
|
|
9
9
|
|
|
10
|
-
function resolvePrimaryConfigPath(configFilePath) {
|
|
11
|
-
|
|
12
|
-
return path.resolve(configFilePath.trim());
|
|
13
|
-
}
|
|
14
|
-
if (typeof process.env.CONDUCTOR_CONFIG === "string" && process.env.CONDUCTOR_CONFIG.trim()) {
|
|
15
|
-
return path.resolve(process.env.CONDUCTOR_CONFIG.trim());
|
|
16
|
-
}
|
|
17
|
-
return path.join(os.homedir(), ".conductor", DEFAULT_CONDUCTOR_CONFIG_BASENAME);
|
|
10
|
+
function resolvePrimaryConfigPath(configFilePath, env = process.env) {
|
|
11
|
+
return resolveConductorConfigPath(configFilePath, env);
|
|
18
12
|
}
|
|
19
13
|
|
|
20
|
-
export function resolveServeAiConfigPaths(configFilePath) {
|
|
21
|
-
const conductorConfigPath = resolvePrimaryConfigPath(configFilePath);
|
|
14
|
+
export function resolveServeAiConfigPaths(configFilePath, env = process.env) {
|
|
15
|
+
const conductorConfigPath = resolvePrimaryConfigPath(configFilePath, env);
|
|
22
16
|
return {
|
|
23
17
|
conductorConfigPath,
|
|
24
18
|
serveAiConfigPath: path.join(path.dirname(conductorConfigPath), DEFAULT_SERVE_AI_CONFIG_BASENAME),
|
|
@@ -37,8 +31,8 @@ function parseYamlFile(filePath) {
|
|
|
37
31
|
return parsed;
|
|
38
32
|
}
|
|
39
33
|
|
|
40
|
-
export function loadServeAiRuntimeConfig(configFilePath) {
|
|
41
|
-
const { conductorConfigPath, serveAiConfigPath } = resolveServeAiConfigPaths(configFilePath);
|
|
34
|
+
export function loadServeAiRuntimeConfig(configFilePath, env = process.env) {
|
|
35
|
+
const { conductorConfigPath, serveAiConfigPath } = resolveServeAiConfigPaths(configFilePath, env);
|
|
42
36
|
const conductorExists = fs.existsSync(conductorConfigPath);
|
|
43
37
|
const serveAiExists = fs.existsSync(serveAiConfigPath);
|
|
44
38
|
|