@love-moon/conductor-cli 0.2.18 → 0.2.20

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.
@@ -26,6 +26,7 @@ import {
26
26
  resolveSessionRunDirectory as resolveCliSessionRunDirectory,
27
27
  resumeProviderForBackend as resumeProviderForCliBackend,
28
28
  } from "../src/fire/resume.js";
29
+ import { filterRuntimeSupportedAllowCliList, normalizeRuntimeBackendName } from "../src/runtime-backends.js";
29
30
 
30
31
  const __filename = fileURLToPath(import.meta.url);
31
32
  const __dirname = path.dirname(__filename);
@@ -50,7 +51,7 @@ function loadAllowCliList(configFilePath) {
50
51
  const content = fs.readFileSync(configPath, "utf8");
51
52
  const parsed = yaml.load(content);
52
53
  if (parsed && typeof parsed === "object" && parsed.allow_cli_list) {
53
- return parsed.allow_cli_list;
54
+ return filterRuntimeSupportedAllowCliList(parsed.allow_cli_list);
54
55
  }
55
56
  }
56
57
  } catch (error) {
@@ -59,6 +60,35 @@ function loadAllowCliList(configFilePath) {
59
60
  return {};
60
61
  }
61
62
 
63
+ export function resolveAiSessionCommandLine(backend, allowCliList, env = process.env) {
64
+ const normalizedBackend = normalizeRuntimeBackendName(backend);
65
+ if (normalizedBackend !== "opencode") {
66
+ return "";
67
+ }
68
+
69
+ const opencodeEnvCommand =
70
+ typeof env?.CONDUCTOR_OPENCODE_COMMAND === "string" ? env.CONDUCTOR_OPENCODE_COMMAND.trim() : "";
71
+ if (opencodeEnvCommand) {
72
+ return opencodeEnvCommand;
73
+ }
74
+
75
+ const configuredCommand =
76
+ allowCliList && typeof allowCliList === "object" && typeof allowCliList.opencode === "string"
77
+ ? allowCliList.opencode.trim()
78
+ : "";
79
+ if (configuredCommand) {
80
+ return configuredCommand;
81
+ }
82
+
83
+ const daemonCommand =
84
+ typeof env?.CONDUCTOR_CLI_COMMAND === "string" ? env.CONDUCTOR_CLI_COMMAND.trim() : "";
85
+ if (daemonCommand) {
86
+ return daemonCommand;
87
+ }
88
+
89
+ return "";
90
+ }
91
+
62
92
  const DEFAULT_POLL_INTERVAL_MS = parseInt(
63
93
  process.env.CONDUCTOR_CLI_POLL_INTERVAL_MS || process.env.CCODEX_POLL_INTERVAL_MS || "2000",
64
94
  10,
@@ -68,6 +98,30 @@ const DEFAULT_ERROR_LOOP_BACKOFF_MS = 3 * 60 * 1000;
68
98
  const DEFAULT_ERROR_LOOP_THRESHOLD = 3;
69
99
  const SESSION_BOOTSTRAP_LOCK_TIMEOUT_MS = 15_000;
70
100
  const SESSION_BOOTSTRAP_LOCK_RETRY_MS = 50;
101
+ const FIRE_WATCHDOG_INTERVAL_MS = getBoundedEnvInt(
102
+ "CONDUCTOR_FIRE_WATCHDOG_INTERVAL_MS",
103
+ 10_000,
104
+ 1_000,
105
+ 60_000,
106
+ );
107
+ const FIRE_WATCHDOG_STALE_WS_MS = getBoundedEnvInt(
108
+ "CONDUCTOR_FIRE_WATCHDOG_STALE_WS_MS",
109
+ 45_000,
110
+ 5_000,
111
+ 5 * 60_000,
112
+ );
113
+ const FIRE_WATCHDOG_CONNECT_GRACE_MS = getBoundedEnvInt(
114
+ "CONDUCTOR_FIRE_WATCHDOG_CONNECT_GRACE_MS",
115
+ 15_000,
116
+ 1_000,
117
+ 60_000,
118
+ );
119
+ const FIRE_WATCHDOG_RECONNECT_COOLDOWN_MS = getBoundedEnvInt(
120
+ "CONDUCTOR_FIRE_WATCHDOG_RECONNECT_COOLDOWN_MS",
121
+ 15_000,
122
+ 1_000,
123
+ 2 * 60_000,
124
+ );
71
125
 
72
126
  function sleepSync(ms) {
73
127
  if (!Number.isFinite(ms) || ms <= 0) {
@@ -161,6 +215,176 @@ function appendFireLocalLog(line) {
161
215
  }
162
216
  }
163
217
 
218
+ function formatFireDisconnectDiagnostics(event = {}) {
219
+ const parts = [];
220
+ if (event.reason) {
221
+ parts.push(`reason=${event.reason}`);
222
+ }
223
+ if (typeof event.closeCode === "number") {
224
+ parts.push(`close_code=${event.closeCode}`);
225
+ }
226
+ if (event.closeReason) {
227
+ parts.push(`close_reason=${sanitizeForLog(event.closeReason, 120)}`);
228
+ }
229
+ if (event.socketError) {
230
+ parts.push(`socket_error=${sanitizeForLog(event.socketError, 120)}`);
231
+ }
232
+ if (typeof event.missedPongs === "number" && event.missedPongs > 0) {
233
+ parts.push(`missed_pongs=${event.missedPongs}`);
234
+ }
235
+ return parts.join(" ") || "reason=connection_lost";
236
+ }
237
+
238
+ function formatIsoTimestamp(value) {
239
+ if (!Number.isFinite(value) || value <= 0) {
240
+ return "n/a";
241
+ }
242
+ return new Date(value).toISOString();
243
+ }
244
+
245
+ function formatFireWatchdogState({ connectedAt, lastPongAt, lastInboundAt }) {
246
+ return [
247
+ `connected_at=${formatIsoTimestamp(connectedAt)}`,
248
+ `last_pong_at=${formatIsoTimestamp(lastPongAt)}`,
249
+ `last_inbound_at=${formatIsoTimestamp(lastInboundAt)}`,
250
+ ].join(" ");
251
+ }
252
+
253
+ export class FireWatchdog {
254
+ constructor({
255
+ intervalMs = FIRE_WATCHDOG_INTERVAL_MS,
256
+ staleWsMs = FIRE_WATCHDOG_STALE_WS_MS,
257
+ connectGraceMs = FIRE_WATCHDOG_CONNECT_GRACE_MS,
258
+ reconnectCooldownMs = FIRE_WATCHDOG_RECONNECT_COOLDOWN_MS,
259
+ onForceReconnect,
260
+ logger = () => {},
261
+ now = () => Date.now(),
262
+ } = {}) {
263
+ this.intervalMs = intervalMs;
264
+ this.staleWsMs = staleWsMs;
265
+ this.connectGraceMs = connectGraceMs;
266
+ this.reconnectCooldownMs = reconnectCooldownMs;
267
+ this.onForceReconnect = onForceReconnect;
268
+ this.logger = logger;
269
+ this.now = now;
270
+ this.wsConnected = false;
271
+ this.lastConnectedAt = null;
272
+ this.lastPongAt = null;
273
+ this.lastInboundAt = null;
274
+ this.lastHealAt = 0;
275
+ this.healAttempts = 0;
276
+ this.awaitingHealthySignalAt = null;
277
+ this.timer = null;
278
+ }
279
+
280
+ start() {
281
+ if (this.timer) {
282
+ return;
283
+ }
284
+ this.timer = setInterval(() => {
285
+ void this.runOnce();
286
+ }, this.intervalMs);
287
+ if (typeof this.timer.unref === "function") {
288
+ this.timer.unref();
289
+ }
290
+ }
291
+
292
+ stop() {
293
+ if (!this.timer) {
294
+ return;
295
+ }
296
+ clearInterval(this.timer);
297
+ this.timer = null;
298
+ }
299
+
300
+ onConnected({ isReconnect = false, connectedAt = this.now() } = {}) {
301
+ this.wsConnected = true;
302
+ this.lastConnectedAt = connectedAt;
303
+ this.lastPongAt =
304
+ Number.isFinite(this.lastPongAt) && this.lastPongAt > connectedAt ? this.lastPongAt : connectedAt;
305
+ if (isReconnect && this.healAttempts > 0) {
306
+ this.awaitingHealthySignalAt = connectedAt;
307
+ } else if (!isReconnect) {
308
+ this.awaitingHealthySignalAt = null;
309
+ }
310
+ }
311
+
312
+ onDisconnected() {
313
+ this.wsConnected = false;
314
+ }
315
+
316
+ onPong({ at = this.now() } = {}) {
317
+ this.lastPongAt = at;
318
+ this.markHealthy("pong", at);
319
+ }
320
+
321
+ onInbound(at = this.now()) {
322
+ this.lastInboundAt = at;
323
+ this.markHealthy("inbound", at);
324
+ }
325
+
326
+ markHealthy(signal, at = this.now()) {
327
+ if (!this.awaitingHealthySignalAt || this.healAttempts === 0) {
328
+ return;
329
+ }
330
+ if (at < this.awaitingHealthySignalAt) {
331
+ return;
332
+ }
333
+ this.logger(
334
+ `[watchdog] Backend websocket healthy again after self-heal via ${signal} (${formatFireWatchdogState({
335
+ connectedAt: this.lastConnectedAt,
336
+ lastPongAt: this.lastPongAt,
337
+ lastInboundAt: this.lastInboundAt,
338
+ })})`,
339
+ );
340
+ this.awaitingHealthySignalAt = null;
341
+ this.healAttempts = 0;
342
+ }
343
+
344
+ async runOnce() {
345
+ if (!this.wsConnected || typeof this.onForceReconnect !== "function") {
346
+ return false;
347
+ }
348
+ const now = this.now();
349
+ if (!Number.isFinite(this.lastConnectedAt) || now - this.lastConnectedAt < this.connectGraceMs) {
350
+ return false;
351
+ }
352
+ const lastWsHealthAt = Math.max(this.lastPongAt || 0, this.lastInboundAt || 0, this.lastConnectedAt || 0);
353
+ if (lastWsHealthAt && now - lastWsHealthAt <= this.staleWsMs) {
354
+ return false;
355
+ }
356
+ if (this.lastHealAt && now - this.lastHealAt < this.reconnectCooldownMs) {
357
+ return false;
358
+ }
359
+
360
+ this.lastHealAt = now;
361
+ this.healAttempts += 1;
362
+ this.awaitingHealthySignalAt = null;
363
+ this.wsConnected = false;
364
+ this.logger(
365
+ `[watchdog] stale_ws_health; restarting fire websocket (${this.healAttempts}) (${formatFireWatchdogState({
366
+ connectedAt: this.lastConnectedAt,
367
+ lastPongAt: this.lastPongAt,
368
+ lastInboundAt: this.lastInboundAt,
369
+ })})`,
370
+ );
371
+ await this.onForceReconnect("watchdog:stale_ws_health");
372
+ return true;
373
+ }
374
+
375
+ getDebugState() {
376
+ return {
377
+ wsConnected: this.wsConnected,
378
+ lastConnectedAt: this.lastConnectedAt,
379
+ lastPongAt: this.lastPongAt,
380
+ lastInboundAt: this.lastInboundAt,
381
+ lastHealAt: this.lastHealAt,
382
+ healAttempts: this.healAttempts,
383
+ awaitingHealthySignalAt: this.awaitingHealthySignalAt,
384
+ };
385
+ }
386
+ }
387
+
164
388
  async function main() {
165
389
  const cliArgs = parseCliArgs();
166
390
  let runtimeProjectPath = process.cwd();
@@ -180,7 +404,7 @@ async function main() {
180
404
 
181
405
  if (cliArgs.listBackends) {
182
406
  if (supportedBackends.length === 0) {
183
- process.stdout.write(`No backends configured.\n\nAdd allow_cli_list to your config file (~/.conductor/config.yaml):\n allow_cli_list:\n codex: codex --dangerously-bypass-approvals-and-sandbox\n claude: claude --dangerously-skip-permissions\n copilot: copilot --allow-all-paths --allow-all-tools\n kimi: kimi\n`);
407
+ process.stdout.write(`No supported backends configured.\n\nAdd allow_cli_list to your config file (~/.conductor/config.yaml):\n allow_cli_list:\n codex: codex --dangerously-bypass-approvals-and-sandbox\n claude: claude --dangerously-skip-permissions\n opencode: opencode\n`);
184
408
  } else {
185
409
  process.stdout.write(`Supported backends (from config):\n`);
186
410
  for (const [name, command] of Object.entries(allowCliList)) {
@@ -209,6 +433,17 @@ async function main() {
209
433
  let pendingRemoteStopEvent = null;
210
434
  let conductor = null;
211
435
  let reconnectResumeInFlight = false;
436
+ let fireShuttingDown = false;
437
+ const fireWatchdog = new FireWatchdog({
438
+ onForceReconnect: async (reason) => {
439
+ if (!conductor || typeof conductor.forceReconnect !== "function") {
440
+ return;
441
+ }
442
+ await conductor.forceReconnect(reason);
443
+ },
444
+ logger: log,
445
+ });
446
+ fireWatchdog.start();
212
447
 
213
448
  const scheduleReconnectRecovery = ({ isReconnect }) => {
214
449
  if (!isReconnect) {
@@ -244,6 +479,7 @@ async function main() {
244
479
  };
245
480
 
246
481
  const handleStopTaskCommand = async (event) => {
482
+ fireWatchdog.onInbound();
247
483
  if (!event || typeof event !== "object") {
248
484
  return;
249
485
  }
@@ -292,7 +528,19 @@ async function main() {
292
528
  projectPath: runtimeProjectPath,
293
529
  extraEnv: env,
294
530
  configFile: cliArgs.configFile,
295
- onConnected: scheduleReconnectRecovery,
531
+ onConnected: (event) => {
532
+ fireWatchdog.onConnected(event);
533
+ scheduleReconnectRecovery(event);
534
+ },
535
+ onDisconnected: (event) => {
536
+ fireWatchdog.onDisconnected();
537
+ if (!fireShuttingDown) {
538
+ log(`[fire-ws] Disconnected from backend: ${formatFireDisconnectDiagnostics(event)}`);
539
+ }
540
+ },
541
+ onPong: (event) => {
542
+ fireWatchdog.onPong(event);
543
+ },
296
544
  onStopTask: handleStopTaskCommand,
297
545
  });
298
546
 
@@ -333,11 +581,14 @@ async function main() {
333
581
 
334
582
  const resolvedResumeSessionId = cliArgs.resumeSessionId;
335
583
 
584
+ const sessionCommandLine = resolveAiSessionCommandLine(cliArgs.backend, allowCliList, process.env);
585
+
336
586
  backendSession = createAiSession(cliArgs.backend, {
337
587
  initialImages: cliArgs.initialImages,
338
588
  cwd: runtimeProjectPath,
339
589
  resumeSessionId: resolvedResumeSessionId,
340
590
  configFile: cliArgs.configFile,
591
+ ...(sessionCommandLine ? { commandLine: sessionCommandLine } : {}),
341
592
  logger: { log },
342
593
  });
343
594
 
@@ -359,6 +610,7 @@ async function main() {
359
610
  taskId: taskContext.taskId,
360
611
  pollIntervalMs: Math.max(cliArgs.pollIntervalMs, 500),
361
612
  initialPrompt: taskContext.shouldProcessInitialPrompt ? cliArgs.initialPrompt : "",
613
+ initialPromptDelivery: taskContext.initialPromptDelivery || "none",
362
614
  includeInitialImages: Boolean(cliArgs.initialPrompt && cliArgs.initialImages.length),
363
615
  cliArgs: cliArgs.rawBackendArgs,
364
616
  backendName: cliArgs.backend,
@@ -391,11 +643,13 @@ async function main() {
391
643
  };
392
644
  const onSigint = () => {
393
645
  shutdownSignal = shutdownSignal || "SIGINT";
646
+ fireShuttingDown = true;
394
647
  signals.abort();
395
648
  requestBackendShutdown("SIGINT");
396
649
  };
397
650
  const onSigterm = () => {
398
651
  shutdownSignal = shutdownSignal || "SIGTERM";
652
+ fireShuttingDown = true;
399
653
  signals.abort();
400
654
  requestBackendShutdown("SIGTERM");
401
655
  };
@@ -460,6 +714,8 @@ async function main() {
460
714
  }
461
715
  }
462
716
  } finally {
717
+ fireShuttingDown = true;
718
+ fireWatchdog.stop();
463
719
  if (backendSession && typeof backendSession.close === "function") {
464
720
  try {
465
721
  await backendSession.close();
@@ -578,7 +834,6 @@ export function parseCliArgs(argvInput = process.argv) {
578
834
  alias: "b",
579
835
  type: "string",
580
836
  describe: `Backend to use (loaded from config: ${supportedBackends.join(", ") || "none configured"})`,
581
- ...(supportedBackends.length > 0 ? { choices: supportedBackends } : {}),
582
837
  })
583
838
  .option("list-backends", {
584
839
  type: "boolean",
@@ -640,12 +895,12 @@ Config file format (~/.conductor/config.yaml):
640
895
  allow_cli_list:
641
896
  codex: codex --dangerously-bypass-approvals-and-sandbox
642
897
  claude: claude --dangerously-skip-permissions
643
- copilot: copilot --allow-all-paths --allow-all-tools
898
+ opencode: opencode
644
899
 
645
900
  Examples:
646
901
  ${CLI_NAME} -- "fix the bug" # Use default backend
647
902
  ${CLI_NAME} --backend claude -- "fix the bug" # Use Claude CLI backend
648
- ${CLI_NAME} --backend copilot -- "fix the bug" # Use GitHub Copilot CLI backend
903
+ ${CLI_NAME} --backend opencode -- "fix the bug" # Use OpenCode backend
649
904
  ${CLI_NAME} --backend codex --resume <id> # Resume Codex session
650
905
  ${CLI_NAME} --list-backends # Show configured backends
651
906
  ${CLI_NAME} --config-file ~/.conductor/config.yaml -- "fix the bug"
@@ -668,7 +923,22 @@ Environment:
668
923
  })
669
924
  .parse();
670
925
 
671
- const backend = conductorArgs.backend || supportedBackends[0];
926
+ const backend = conductorArgs.backend
927
+ ? normalizeRuntimeBackendName(conductorArgs.backend)
928
+ : supportedBackends[0];
929
+ const shouldRequireBackend =
930
+ !Boolean(conductorArgs.listBackends) &&
931
+ !listBackendsWithoutSeparator &&
932
+ !Boolean(conductorArgs.version) &&
933
+ !versionWithoutSeparator;
934
+ if (backend && !supportedBackends.includes(backend) && shouldRequireBackend) {
935
+ throw new Error(
936
+ `Unsupported backend "${backend}". Supported backends: ${supportedBackends.join(", ") || "none configured"}.`,
937
+ );
938
+ }
939
+ if (!backend && shouldRequireBackend) {
940
+ throw new Error("No supported backends configured. Add codex, claude, or opencode to allow_cli_list.");
941
+ }
672
942
 
673
943
  const prompt = (backendArgs._ || []).map((part) => String(part)).join(" ").trim();
674
944
  const initialImages = normalizeArray(backendArgs.image || backendArgs.i).map((img) => String(img));
@@ -806,6 +1076,7 @@ async function ensureTaskContext(conductor, opts) {
806
1076
  taskId: opts.providedTaskId,
807
1077
  appUrl: null,
808
1078
  shouldProcessInitialPrompt: Boolean(opts.initialPrompt),
1079
+ initialPromptDelivery: opts.initialPrompt ? "synthetic" : "none",
809
1080
  };
810
1081
  }
811
1082
 
@@ -837,6 +1108,7 @@ async function ensureTaskContext(conductor, opts) {
837
1108
  taskId: session.task_id,
838
1109
  appUrl: session.app_url || null,
839
1110
  shouldProcessInitialPrompt: Boolean(opts.initialPrompt),
1111
+ initialPromptDelivery: opts.initialPrompt ? "queued" : "none",
840
1112
  };
841
1113
  }
842
1114
 
@@ -1016,6 +1288,7 @@ export class BridgeRunner {
1016
1288
  taskId,
1017
1289
  pollIntervalMs,
1018
1290
  initialPrompt,
1291
+ initialPromptDelivery,
1019
1292
  includeInitialImages,
1020
1293
  cliArgs,
1021
1294
  backendName,
@@ -1027,6 +1300,10 @@ export class BridgeRunner {
1027
1300
  this.taskId = taskId;
1028
1301
  this.pollIntervalMs = pollIntervalMs;
1029
1302
  this.initialPrompt = initialPrompt;
1303
+ this.initialPromptDelivery =
1304
+ typeof initialPromptDelivery === "string" && initialPromptDelivery.trim()
1305
+ ? initialPromptDelivery.trim()
1306
+ : "none";
1030
1307
  this.includeInitialImages = includeInitialImages;
1031
1308
  this.cliArgs = cliArgs;
1032
1309
  this.backendName = backendName || "codex";
@@ -1042,6 +1319,10 @@ export class BridgeRunner {
1042
1319
  this.runningTurn = false;
1043
1320
  this.processedMessageIds = new Set();
1044
1321
  this.inFlightMessageIds = new Set();
1322
+ this.pendingInitialPrompt =
1323
+ this.initialPromptDelivery === "queued" && typeof initialPrompt === "string" && initialPrompt.trim()
1324
+ ? initialPrompt.trim()
1325
+ : "";
1045
1326
  this.sessionStreamReplyCounts = new Map();
1046
1327
  this.lastRuntimeStatusSignature = null;
1047
1328
  this.lastRuntimeStatusPayload = null;
@@ -1230,8 +1511,8 @@ export class BridgeRunner {
1230
1511
  return;
1231
1512
  }
1232
1513
 
1233
- if (this.initialPrompt) {
1234
- this.copilotLog("processing initial prompt");
1514
+ if (this.initialPrompt && this.initialPromptDelivery === "synthetic") {
1515
+ this.copilotLog("processing initial prompt via synthetic attach flow");
1235
1516
  await this.handleSyntheticMessage(this.initialPrompt, {
1236
1517
  includeImages: this.includeInitialImages,
1237
1518
  });
@@ -1239,6 +1520,7 @@ export class BridgeRunner {
1239
1520
  if (this.stopped) {
1240
1521
  return;
1241
1522
  }
1523
+
1242
1524
  while (!this.stopped) {
1243
1525
  if (this.needsReconnectRecovery && !this.runningTurn) {
1244
1526
  await this.recoverAfterReconnect();
@@ -1783,6 +2065,11 @@ export class BridgeRunner {
1783
2065
  if (replyTo) {
1784
2066
  this.inFlightMessageIds.add(replyTo);
1785
2067
  }
2068
+ const isQueuedInitialPromptMessage =
2069
+ Boolean(this.pendingInitialPrompt) &&
2070
+ String(message.role || "").toLowerCase() === "user" &&
2071
+ content === this.pendingInitialPrompt;
2072
+ const useInitialImages = isQueuedInitialPromptMessage && this.includeInitialImages;
1786
2073
  if (
1787
2074
  this.useSessionFileReplyStream &&
1788
2075
  typeof this.backendSession?.setSessionReplyTarget === "function"
@@ -1830,6 +2117,7 @@ export class BridgeRunner {
1830
2117
  }
1831
2118
 
1832
2119
  const result = await this.backendSession.runTurn(content, {
2120
+ useInitialImages,
1833
2121
  onProgress: (payload) => {
1834
2122
  void this.reportRuntimeStatus(payload, replyTo);
1835
2123
  },
@@ -1873,6 +2161,9 @@ export class BridgeRunner {
1873
2161
  if (replyTo) {
1874
2162
  this.processedMessageIds.add(replyTo);
1875
2163
  }
2164
+ if (isQueuedInitialPromptMessage) {
2165
+ this.pendingInitialPrompt = "";
2166
+ }
1876
2167
  this.resetErrorLoop();
1877
2168
  if (this.useSessionFileReplyStream) {
1878
2169
  this.copilotLog(`session_file turn settled replyTo=${replyTo || "latest"}`);
package/bin/conductor.js CHANGED
@@ -10,6 +10,7 @@
10
10
  * update - Update the CLI to the latest version
11
11
  * diagnose - Diagnose a task in production/backend
12
12
  * send-file - Upload a local file into a task session
13
+ * channel - Connect user-owned chat channel providers
13
14
  */
14
15
 
15
16
  import { fileURLToPath } from "node:url";
@@ -46,7 +47,7 @@ if (argv[0] === "--version" || argv[0] === "-v") {
46
47
  const subcommand = argv[0];
47
48
 
48
49
  // Valid subcommands
49
- const validSubcommands = ["fire", "daemon", "config", "update", "diagnose", "send-file"];
50
+ const validSubcommands = ["fire", "daemon", "config", "update", "diagnose", "send-file", "channel"];
50
51
 
51
52
  if (!validSubcommands.includes(subcommand)) {
52
53
  console.error(`Error: Unknown subcommand '${subcommand}'`);
@@ -90,6 +91,7 @@ Subcommands:
90
91
  update Update the CLI to the latest version
91
92
  diagnose Diagnose a task and print likely root cause
92
93
  send-file Upload a local file into a task session
94
+ channel Connect user-owned chat channel providers
93
95
 
94
96
  Options:
95
97
  -h, --help Show this help message
@@ -101,6 +103,7 @@ Examples:
101
103
  conductor daemon --config-file ~/.conductor/config.yaml
102
104
  conductor diagnose <task-id>
103
105
  conductor send-file ./screenshot.png
106
+ conductor channel connect feishu
104
107
  conductor config
105
108
  conductor update
106
109
 
@@ -111,6 +114,7 @@ For subcommand-specific help:
111
114
  conductor update --help
112
115
  conductor diagnose --help
113
116
  conductor send-file --help
117
+ conductor channel --help
114
118
 
115
119
  Version: ${pkgJson.version}
116
120
  `);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@love-moon/conductor-cli",
3
- "version": "0.2.18",
4
- "gitCommitId": "942418b",
3
+ "version": "0.2.20",
4
+ "gitCommitId": "d622756",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "conductor": "bin/conductor.js"
@@ -17,17 +17,25 @@
17
17
  "test": "node --test test/*.test.js"
18
18
  },
19
19
  "dependencies": {
20
- "@love-moon/ai-sdk": "0.2.18",
21
- "@love-moon/conductor-sdk": "0.2.18",
20
+ "@love-moon/ai-sdk": "0.2.20",
21
+ "@love-moon/conductor-sdk": "0.2.20",
22
22
  "dotenv": "^16.4.5",
23
23
  "enquirer": "^2.4.1",
24
24
  "js-yaml": "^4.1.1",
25
+ "node-pty": "^1.0.0",
25
26
  "ws": "^8.18.0",
26
27
  "yargs": "^17.7.2",
27
28
  "chrome-launcher": "^1.2.1",
28
29
  "chrome-remote-interface": "^0.33.0"
29
30
  },
31
+ "optionalDependencies": {
32
+ "@roamhq/wrtc": "^0.10.0"
33
+ },
30
34
  "pnpm": {
35
+ "onlyBuiltDependencies": [
36
+ "node-pty",
37
+ "@roamhq/wrtc"
38
+ ],
31
39
  "overrides": {
32
40
  "@love-moon/ai-sdk": "file:../modules/ai-sdk",
33
41
  "@love-moon/conductor-sdk": "file:../modules/conductor-sdk"