@love-moon/conductor-cli 0.2.6 → 0.2.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.
Files changed (2) hide show
  1. package/bin/conductor-fire.js +47 -201
  2. package/package.json +3 -4
@@ -4,24 +4,21 @@
4
4
  * conductor-fire - Conductor-aware AI coding agent runner.
5
5
  *
6
6
  * Supports configurable backends via allow_cli_list in config file.
7
- * This CLI bridges various AI coding agents with Conductor via MCP.
7
+ * This CLI bridges various AI coding agents with Conductor via the Conductor SDK.
8
8
  */
9
9
 
10
10
  import fs from "node:fs";
11
- import { createRequire } from "node:module";
12
11
  import os from "node:os";
13
12
  import path from "node:path";
14
13
  import process from "node:process";
15
14
  import { setTimeout as delay } from "node:timers/promises";
16
15
  import { fileURLToPath } from "node:url";
17
16
 
18
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
19
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
20
17
  import yargs from "yargs/yargs";
21
18
  import { hideBin } from "yargs/helpers";
22
19
  import yaml from "js-yaml";
23
20
  import { TuiDriver, claudeCodeProfile, codexProfile, copilotProfile } from "@love-moon/tui-driver";
24
- import { loadConfig } from "@love-moon/conductor-sdk";
21
+ import { ConductorClient, loadConfig } from "@love-moon/conductor-sdk";
25
22
  import {
26
23
  loadHistoryFromSpec,
27
24
  parseFromSpec,
@@ -31,21 +28,8 @@ import {
31
28
 
32
29
  const __filename = fileURLToPath(import.meta.url);
33
30
  const __dirname = path.dirname(__filename);
34
- const require = createRequire(import.meta.url);
35
31
  const PKG_ROOT = path.join(__dirname, "..");
36
32
  const CLI_PROJECT_PATH = process.cwd();
37
- const REPO_ROOT_FALLBACK = path.resolve(__dirname, "..", "..");
38
- const SDK_FALLBACK_PATH = path.join(REPO_ROOT_FALLBACK, "modules", "sdk");
39
- const SDK_ROOT =
40
- process.env.CONDUCTOR_SDK_PATH ||
41
- (() => {
42
- try {
43
- return path.dirname(require.resolve("@love-moon/conductor-sdk/package.json"));
44
- } catch {
45
- return SDK_FALLBACK_PATH;
46
- }
47
- })();
48
- const MCP_SERVER_SCRIPT = path.join(SDK_ROOT, "dist", "bin", "mcp-server.js");
49
33
 
50
34
  const pkgJson = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, "package.json"), "utf-8"));
51
35
  const CLI_NAME = (process.env.CONDUCTOR_CLI_NAME || path.basename(process.argv[1] || "conductor-fire")).replace(
@@ -122,12 +106,6 @@ const DEFAULT_POLL_INTERVAL_MS = parseInt(
122
106
  10,
123
107
  );
124
108
 
125
- const MCP_SERVER_LAUNCH = {
126
- command: "node",
127
- args: [MCP_SERVER_SCRIPT],
128
- cwd: SDK_ROOT,
129
- };
130
-
131
109
  async function main() {
132
110
  const cliArgs = parseCliArgs();
133
111
 
@@ -223,10 +201,9 @@ async function main() {
223
201
  }
224
202
 
225
203
  const conductor = await ConductorClient.connect({
226
- launcher: MCP_SERVER_LAUNCH,
227
- workingDirectory: CLI_PROJECT_PATH,
228
204
  projectPath: CLI_PROJECT_PATH,
229
205
  extraEnv: env,
206
+ configFile: cliArgs.configFile,
230
207
  });
231
208
 
232
209
  const taskContext = await ensureTaskContext(conductor, {
@@ -268,19 +245,44 @@ async function main() {
268
245
  process.on("SIGINT", onSigint);
269
246
  process.on("SIGTERM", onSigterm);
270
247
 
248
+ if (!launchedByDaemon) {
249
+ try {
250
+ await conductor.sendTaskStatus(taskContext.taskId, {
251
+ status: "RUNNING",
252
+ });
253
+ } catch (error) {
254
+ log(`Failed to report task status (RUNNING): ${error?.message || error}`);
255
+ }
256
+ }
257
+
258
+ let runnerError = null;
271
259
  try {
272
260
  await runner.start(signals.signal);
261
+ } catch (error) {
262
+ runnerError = error;
263
+ throw error;
273
264
  } finally {
274
265
  process.off("SIGINT", onSigint);
275
266
  process.off("SIGTERM", onSigterm);
276
- if (shutdownSignal && !launchedByDaemon) {
267
+ if (!launchedByDaemon) {
268
+ const finalStatus = shutdownSignal
269
+ ? {
270
+ status: "KILLED",
271
+ summary: `terminated by ${shutdownSignal}`,
272
+ }
273
+ : runnerError
274
+ ? {
275
+ status: "KILLED",
276
+ summary: `conductor fire failed: ${runnerError?.message || runnerError}`,
277
+ }
278
+ : {
279
+ status: "COMPLETED",
280
+ summary: "conductor fire exited",
281
+ };
277
282
  try {
278
- await conductor.sendTaskStatus(taskContext.taskId, {
279
- status: "KILLED",
280
- summary: `terminated by ${shutdownSignal}`,
281
- });
283
+ await conductor.sendTaskStatus(taskContext.taskId, finalStatus);
282
284
  } catch (error) {
283
- log(`Failed to report task status (KILLED): ${error?.message || error}`);
285
+ log(`Failed to report task status (${finalStatus.status}): ${error?.message || error}`);
284
286
  }
285
287
  }
286
288
  if (typeof backendSession.close === "function") {
@@ -537,7 +539,19 @@ async function resolveProjectId(conductor, explicit) {
537
539
  try {
538
540
  const record = await conductor.getLocalProjectRecord();
539
541
  if (record?.project_id) {
540
- return record.project_id;
542
+ try {
543
+ const listing = await conductor.listProjects();
544
+ const exists = Array.isArray(listing?.projects)
545
+ ? listing.projects.some((project) => String(project?.id || "") === String(record.project_id))
546
+ : false;
547
+ if (exists) {
548
+ return record.project_id;
549
+ }
550
+ log(`Local session project ${record.project_id} no longer exists; falling back to server project list`);
551
+ } catch (verifyError) {
552
+ log(`Unable to verify local project record; using cached project id: ${verifyError.message}`);
553
+ return record.project_id;
554
+ }
541
555
  }
542
556
  } catch (error) {
543
557
  log(`Unable to resolve project via local session: ${error.message}`);
@@ -973,174 +987,6 @@ class TuiDriverSession {
973
987
  }
974
988
  }
975
989
 
976
- class ConductorClient {
977
- constructor(client, transport, options = {}) {
978
- this.client = client;
979
- this.transport = transport;
980
- this.closed = false;
981
- this.projectPath = options.projectPath || process.cwd();
982
- }
983
-
984
- static async connect({ launcher, workingDirectory, extraEnv, projectPath }) {
985
- if (!fs.existsSync(launcher.args[0])) {
986
- throw new Error(`Conductor MCP server not found at ${launcher.args[0]}`);
987
- }
988
-
989
- const transport = new StdioClientTransport({
990
- command: launcher.command,
991
- args: launcher.args,
992
- env: extraEnv,
993
- cwd: launcher.cwd || workingDirectory,
994
- stderr: "pipe",
995
- });
996
-
997
- const client = new Client({
998
- name: CLI_NAME,
999
- version: pkgJson.version,
1000
- });
1001
-
1002
- client.onerror = (err) => {
1003
- log(`MCP client error: ${err.message}`);
1004
- };
1005
-
1006
- const stderr = transport.stderr;
1007
- if (stderr) {
1008
- stderr.setEncoding("utf-8");
1009
- stderr.on("data", (chunk) => {
1010
- const text = chunk.toString();
1011
- const ts = new Date().toLocaleString("sv-SE", { timeZone: "Asia/Shanghai" }).replace(" ", "T");
1012
- text
1013
- .split(/\r?\n/)
1014
- .filter(Boolean)
1015
- .forEach((line) => process.stderr.write(`[conductor ${ts}] ${line}\n`));
1016
- });
1017
- }
1018
-
1019
- await client.connect(transport);
1020
- return new ConductorClient(client, transport, { projectPath });
1021
- }
1022
-
1023
- async close() {
1024
- if (this.closed) {
1025
- return;
1026
- }
1027
- this.closed = true;
1028
- await this.client.close();
1029
- await this.transport.close();
1030
- }
1031
-
1032
- injectProjectPath(payload = {}) {
1033
- if (!this.projectPath || payload.project_path) {
1034
- return payload;
1035
- }
1036
- return { ...payload, project_path: this.projectPath };
1037
- }
1038
-
1039
- async createTaskSession(payload) {
1040
- const args = payload ? { ...payload } : {};
1041
- return this.callTool("create_task_session", this.injectProjectPath(args));
1042
- }
1043
-
1044
- async sendMessage(taskId, content, metadata) {
1045
- return this.callTool("send_message", {
1046
- task_id: taskId,
1047
- content,
1048
- metadata,
1049
- });
1050
- }
1051
-
1052
- async sendTaskStatus(taskId, payload) {
1053
- return this.callTool("send_task_status", {
1054
- task_id: taskId,
1055
- ...(payload || {}),
1056
- });
1057
- }
1058
-
1059
- async sendRuntimeStatus(taskId, payload) {
1060
- return this.callTool("send_runtime_status", {
1061
- task_id: taskId,
1062
- ...(payload || {}),
1063
- });
1064
- }
1065
-
1066
- async receiveMessages(taskId, limit = 20) {
1067
- return this.callTool("receive_messages", {
1068
- task_id: taskId,
1069
- limit,
1070
- });
1071
- }
1072
-
1073
- async ackMessages(taskId, ackToken) {
1074
- if (!ackToken) {
1075
- return;
1076
- }
1077
- await this.callTool("ack_messages", { task_id: taskId, ack_token: ackToken });
1078
- }
1079
-
1080
- async listProjects() {
1081
- return this.callTool("list_projects", {});
1082
- }
1083
-
1084
- async createProject(name, description, metadata) {
1085
- return this.callTool("create_project", {
1086
- name,
1087
- description,
1088
- metadata,
1089
- });
1090
- }
1091
-
1092
- async getLocalProjectRecord() {
1093
- return this.callTool("get_local_project_id", this.injectProjectPath({}));
1094
- }
1095
-
1096
- async matchProjectByPath() {
1097
- return this.callTool("match_project_by_path", this.injectProjectPath({}));
1098
- }
1099
-
1100
- async bindProjectPath(projectId) {
1101
- return this.callTool("bind_project_path", this.injectProjectPath({ project_id: projectId }));
1102
- }
1103
-
1104
- async callTool(name, args) {
1105
- const result = await this.client.callTool({
1106
- name,
1107
- arguments: args || {},
1108
- });
1109
- if (result?.isError) {
1110
- const message = extractFirstText(result?.content) || `tool ${name} failed`;
1111
- throw new Error(message);
1112
- }
1113
- const structured = result?.structuredContent;
1114
- if (structured && Object.keys(structured).length > 0) {
1115
- return structured;
1116
- }
1117
- const textPayload = extractFirstText(result?.content);
1118
- if (!textPayload) {
1119
- return undefined;
1120
- }
1121
- try {
1122
- return JSON.parse(textPayload);
1123
- } catch {
1124
- return textPayload;
1125
- }
1126
- }
1127
- }
1128
-
1129
- function extractFirstText(blocks) {
1130
- if (!Array.isArray(blocks)) {
1131
- return "";
1132
- }
1133
- for (const block of blocks) {
1134
- if (block?.type === "text" && typeof block.text === "string") {
1135
- const trimmed = block.text.trim();
1136
- if (trimmed) {
1137
- return trimmed;
1138
- }
1139
- }
1140
- }
1141
- return "";
1142
- }
1143
-
1144
990
  class BridgeRunner {
1145
991
  constructor({
1146
992
  backendSession,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@love-moon/conductor-cli",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "conductor": "bin/conductor.js"
@@ -16,9 +16,8 @@
16
16
  "test": "node --test"
17
17
  },
18
18
  "dependencies": {
19
- "@love-moon/tui-driver": "0.2.6",
20
- "@love-moon/conductor-sdk": "0.2.6",
21
- "@modelcontextprotocol/sdk": "^1.20.2",
19
+ "@love-moon/tui-driver": "0.2.7",
20
+ "@love-moon/conductor-sdk": "0.2.7",
22
21
  "dotenv": "^16.4.5",
23
22
  "enquirer": "^2.4.1",
24
23
  "js-yaml": "^4.1.1",