@love-moon/conductor-cli 0.7.5 → 0.7.7
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 +38 -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 +961 -84
- package/src/entity-helpers.js +2 -8
- package/src/fire/resume.js +17 -17
- package/src/handoff-log-mask.js +43 -0
- package/src/runtime-backends.js +2 -4
- package/src/serve-ai/config.js +7 -13
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/handoff-log-mask.js
CHANGED
|
@@ -23,6 +23,49 @@ export function maskHandoffUrlForLogs(value) {
|
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// Secrets other than the handoff token can reach the same log/summary surface
|
|
27
|
+
// once we start capturing a crashing child's output: the daemon puts
|
|
28
|
+
// `CONDUCTOR_AGENT_TOKEN` into the fire's environment AND into tmux's argv
|
|
29
|
+
// (`-e CONDUCTOR_AGENT_TOKEN=…`), and the fire inherits provider keys such as
|
|
30
|
+
// ANTHROPIC_API_KEY / OPENAI_API_KEY. Backends routinely echo those back when
|
|
31
|
+
// they fail ("invalid API key sk-ant-…", usage dumps, env dumps).
|
|
32
|
+
//
|
|
33
|
+
// A single-pattern denylist is the wrong shape for that, so this is a
|
|
34
|
+
// redaction *pass*: exact known secret values first (the strongest signal —
|
|
35
|
+
// the daemon holds its own token at runtime), then structural patterns.
|
|
36
|
+
const SECRET_ASSIGNMENT_RE =
|
|
37
|
+
/\b([A-Z0-9_]*(?:TOKEN|SECRET|KEY|PASSWORD|PASSWD|CREDENTIAL)[A-Z0-9_]*)\s*[=:]\s*("[^"]*"|'[^']*'|\S+)/gi;
|
|
38
|
+
const BEARER_RE = /\b(Bearer|Basic)\s+([A-Za-z0-9._~+/=-]{8,})/gi;
|
|
39
|
+
// Provider key shapes (OpenAI/Anthropic `sk-…`, Google `AIza…`, GitHub `ghp_…`).
|
|
40
|
+
const PROVIDER_KEY_RE = /\b(sk-[A-Za-z0-9_-]{12,}|AIza[A-Za-z0-9_-]{20,}|gh[pousr]_[A-Za-z0-9]{16,})\b/g;
|
|
41
|
+
|
|
42
|
+
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Redact secrets from text that is about to be logged or persisted into a task
|
|
46
|
+
* status summary.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} value
|
|
49
|
+
* @param {string[]} [knownSecrets] exact literal values to strip (e.g. the
|
|
50
|
+
* daemon's own AGENT_TOKEN). Short values are ignored so a 1-2 char token
|
|
51
|
+
* cannot blank out the whole message.
|
|
52
|
+
*/
|
|
53
|
+
export function redactSecretsForLogs(value, knownSecrets = []) {
|
|
54
|
+
if (typeof value !== "string" || !value) {
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
let out = maskHandoffUrlForLogs(value);
|
|
58
|
+
for (const secret of knownSecrets) {
|
|
59
|
+
if (typeof secret === "string" && secret.length >= 8) {
|
|
60
|
+
out = out.replace(new RegExp(escapeRegExp(secret), "g"), "<redacted>");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
out = out.replace(SECRET_ASSIGNMENT_RE, (_, key) => `${key}=<redacted>`);
|
|
64
|
+
out = out.replace(BEARER_RE, (_, scheme) => `${scheme} <redacted>`);
|
|
65
|
+
out = out.replace(PROVIDER_KEY_RE, "<redacted>");
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
26
69
|
// Scrub any handoff URL embedded in an error message before surfacing it via
|
|
27
70
|
// logs or task status summaries. Belt-and-suspenders for the case where an
|
|
28
71
|
// internal error stringifies the outbox payload.
|
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
|
|