@love-moon/conductor-cli 0.15.2 → 0.16.0
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 +81 -0
- package/bin/conductor-auth.js +162 -0
- package/bin/conductor-daemon-query.js +13 -17
- package/bin/conductor-daemon.js +6 -1
- package/bin/conductor-issue.js +215 -13
- package/bin/conductor-project.js +840 -27
- package/bin/conductor-search.js +150 -0
- package/bin/conductor-settings.js +608 -0
- package/bin/conductor-task.js +316 -9
- package/bin/conductor.js +29 -8
- package/package.json +5 -5
- package/src/backend-http.js +240 -0
- package/src/daemon-share-commands.js +528 -0
- package/src/task-commands.js +777 -0
package/bin/conductor-task.js
CHANGED
|
@@ -16,6 +16,26 @@
|
|
|
16
16
|
* schedule list <id>
|
|
17
17
|
* schedule create <id> [<message>] (--delay 10m | --at ISO | --every 1h)
|
|
18
18
|
* schedule delete <id> <schedule-id>
|
|
19
|
+
* schedule update <id> <schedule-id> [<message>] [--delay|--at|--every ...]
|
|
20
|
+
* stop <id> | interrupt <id> [--target-reply-to <msg-id>]
|
|
21
|
+
* restart <id> [--strategy inplace|new_task] [--backend] [--refresh-session]
|
|
22
|
+
* [--daemon-host <h>] [--first-message <text>]
|
|
23
|
+
* delete <id> --yes [--permanent] | archive <id> | unarchive <id> [--daemon-host <h>]
|
|
24
|
+
* rename <id> <title> | pin <id> | unpin <id> | labels <id> [<label-id>...] [--clear]
|
|
25
|
+
* move <id> [<project>] [--back] | share <id> | unshare <id> | shared <token|link>
|
|
26
|
+
* transcribe <audio.wav|mp3> [--language <tag>]
|
|
27
|
+
* persistent <id> [--enable|--disable] [--instructions ...] [--summary ...]
|
|
28
|
+
* round end <id> | round start <id> [<message>] [--backend] [--daemon-host] [--worktree inherit|new|none]
|
|
29
|
+
* cleanup-worktree <id> | terminal open|close|show <id>
|
|
30
|
+
* attachment download <id> <attachment-id> [-o FILE|DIR|-]
|
|
31
|
+
* resume --daemon-host <h> --backend <b> --session <id> [--session-file <p>] [--title] [--prompt]
|
|
32
|
+
*
|
|
33
|
+
* create also takes: --daemon-host, --agent <name>[:backend] (repeatable; first
|
|
34
|
+
* is the worker), --global-backend <host>:<backend>, --worktree,
|
|
35
|
+
* --remote-worktree <host>, --persistent
|
|
36
|
+
* send also takes: --attach FILE (repeatable)
|
|
37
|
+
* messages also takes: --follow [--interval <sec>] [--until-idle]
|
|
38
|
+
* list also takes (without --archived): --all-projects, --project-ids a,b
|
|
19
39
|
*
|
|
20
40
|
* Global flags supported on every write subcommand:
|
|
21
41
|
* --json, --dry-run, --project, --config-file
|
|
@@ -42,6 +62,7 @@ import {
|
|
|
42
62
|
reportError,
|
|
43
63
|
resolveProject,
|
|
44
64
|
} from "../src/entity-helpers.js";
|
|
65
|
+
import * as extra from "../src/task-commands.js";
|
|
45
66
|
|
|
46
67
|
const isMainModule = (() => {
|
|
47
68
|
const currentFile = fileURLToPath(import.meta.url);
|
|
@@ -281,8 +302,14 @@ async function handleList(argv, deps) {
|
|
|
281
302
|
// Validated here rather than with yargs implies/conflicts: those count the
|
|
282
303
|
// boolean defaults as "set", and the parser's fail() does not stop the handler.
|
|
283
304
|
let argsProblem = null;
|
|
284
|
-
if (!argv.archived && (argv.search !== undefined || argv.
|
|
285
|
-
argsProblem = "--search
|
|
305
|
+
if (!argv.archived && (argv.search !== undefined || argv.page !== undefined)) {
|
|
306
|
+
argsProblem = "--search and --page require --archived";
|
|
307
|
+
} else if (!argv.archived && (argv.allProjects || argv.projectIds) && (argv.project || argv.issue || argv.includeMoved)) {
|
|
308
|
+
argsProblem = "--all-projects / --project-ids cannot be combined with --project, --issue, or --include-moved";
|
|
309
|
+
} else if (argv.allProjects && argv.projectIds) {
|
|
310
|
+
argsProblem = "Pass either --all-projects or --project-ids, not both";
|
|
311
|
+
} else if (argv.archived && argv.projectIds) {
|
|
312
|
+
argsProblem = "--project-ids cannot be combined with --archived (use --all-projects)";
|
|
286
313
|
} else if (argv.archived && (argv.issue || argv.status || argv.includeMoved || (argv.allProjects && argv.project))) {
|
|
287
314
|
argsProblem = "--archived cannot be combined with --issue, --status, --include-moved, or --project with --all-projects";
|
|
288
315
|
}
|
|
@@ -291,6 +318,7 @@ async function handleList(argv, deps) {
|
|
|
291
318
|
err.code = "ARGS";
|
|
292
319
|
throw err;
|
|
293
320
|
}
|
|
321
|
+
if (!argv.archived && (argv.allProjects || argv.projectIds)) return extra.handleMultiProjectList(argv, deps);
|
|
294
322
|
const apis = await buildApis(deps);
|
|
295
323
|
if (argv.archived) return handleArchivedList(argv, deps, apis);
|
|
296
324
|
const project = await resolveProject(apis, { env: deps.env, cwd: deps.cwd, project: argv.project });
|
|
@@ -335,6 +363,18 @@ async function handleCreate(argv, deps) {
|
|
|
335
363
|
err.code = "ARGS";
|
|
336
364
|
throw err;
|
|
337
365
|
}
|
|
366
|
+
if (project.hidden) {
|
|
367
|
+
printPretty(
|
|
368
|
+
deps.stderr,
|
|
369
|
+
`Warning: project ${project.name || project.id} is hidden; the task will not show in the web task list until you unhide it.`,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
if (extra.hasExtendedCreateOptions(argv)) {
|
|
373
|
+
// The SDK's createTask only forwards the basic fields; the extended options
|
|
374
|
+
// (daemon, agent group, global backend, worktree, persistent) go straight to
|
|
375
|
+
// the route with the same payload the web Create Task dialog sends.
|
|
376
|
+
return extra.handleExtendedCreate(argv, deps, project.id, title);
|
|
377
|
+
}
|
|
338
378
|
|
|
339
379
|
const body = {
|
|
340
380
|
projectId: project.id,
|
|
@@ -446,6 +486,9 @@ async function handleSend(argv, deps) {
|
|
|
446
486
|
// `--metadata-json '{"actor":"system"}'` cannot spoof CLI audit fields
|
|
447
487
|
// (review H1). The CLI's `actor: "cli"` always wins inside `audit`.
|
|
448
488
|
const metadata = buildAuditMetadata(deps.env, extraMetadata || {});
|
|
489
|
+
if ([].concat(argv.attach || []).filter(Boolean).length > 0) {
|
|
490
|
+
return extra.handleSendWithAttachments(argv, deps, content, metadata);
|
|
491
|
+
}
|
|
449
492
|
const body = {
|
|
450
493
|
role: "user",
|
|
451
494
|
content,
|
|
@@ -466,10 +509,10 @@ async function handleSend(argv, deps) {
|
|
|
466
509
|
// SDK signature is `sendTaskMessage(taskId, content, options?)`. Earlier we
|
|
467
510
|
// were passing the body object as the second arg, which caused the SDK's
|
|
468
511
|
// `typeof content === 'string'` guard to throw at runtime (review B1).
|
|
469
|
-
const result = await apis.tasks.sendTaskMessage(argv.id, content, {
|
|
512
|
+
const result = await extra.retryWhileFireOwnerMissing(() => apis.tasks.sendTaskMessage(argv.id, content, {
|
|
470
513
|
role: body.role,
|
|
471
514
|
metadata: body.metadata,
|
|
472
|
-
});
|
|
515
|
+
}), deps);
|
|
473
516
|
if (argv.json) {
|
|
474
517
|
printJson(deps.stdout, result ?? { sent: true });
|
|
475
518
|
return EXIT.OK;
|
|
@@ -527,6 +570,9 @@ async function handleMessages(argv, deps) {
|
|
|
527
570
|
limit: argv.limit ? Number(argv.limit) : undefined,
|
|
528
571
|
before: argv.before ? String(argv.before) : undefined,
|
|
529
572
|
});
|
|
573
|
+
if (argv.follow) {
|
|
574
|
+
return extra.followMessages(argv, deps, apis, Array.isArray(list) ? list : []);
|
|
575
|
+
}
|
|
530
576
|
if (argv.json) {
|
|
531
577
|
printJson(deps.stdout, Array.isArray(list) ? list : []);
|
|
532
578
|
return EXIT.OK;
|
|
@@ -623,6 +669,43 @@ async function handleScheduleDelete(argv, deps) {
|
|
|
623
669
|
return EXIT.OK;
|
|
624
670
|
}
|
|
625
671
|
|
|
672
|
+
async function handleScheduleUpdate(argv, deps) {
|
|
673
|
+
const hasMessage = (argv.message !== undefined && argv.message !== "") || argv.fromFile || argv.stdin;
|
|
674
|
+
const content = hasMessage
|
|
675
|
+
? readMessageInput({
|
|
676
|
+
positional: argv.message,
|
|
677
|
+
fromFile: argv.fromFile,
|
|
678
|
+
useStdin: Boolean(argv.stdin),
|
|
679
|
+
stdin: deps.stdin,
|
|
680
|
+
})
|
|
681
|
+
: undefined;
|
|
682
|
+
const hasSchedule = argv.delay !== undefined || argv.at !== undefined || argv.every !== undefined;
|
|
683
|
+
const schedule = hasSchedule ? parseScheduleOptions(argv) : undefined;
|
|
684
|
+
return extra.handleScheduleUpdate(argv, deps, content, schedule);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function withMessageInput(cmd) {
|
|
688
|
+
return cmd
|
|
689
|
+
.option("stdin", { type: "boolean", default: false })
|
|
690
|
+
.option("from-file", { type: "string" });
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function scheduleModeOptions(cmd) {
|
|
694
|
+
return cmd
|
|
695
|
+
.option("delay", { type: "string", describe: "Send once after a duration, e.g. 10m or 2h" })
|
|
696
|
+
.option("at", { type: "string", describe: "Send once at an ISO/local date-time string" })
|
|
697
|
+
.option("every", { type: "string", describe: "Repeat every duration, e.g. 30m or 1h" })
|
|
698
|
+
.option("if-idle", { type: "boolean", default: false, describe: "For repeats, only send when the AI is idle" })
|
|
699
|
+
.option("max-runs", { type: "number", describe: "For repeats, stop after N sends" })
|
|
700
|
+
.option("max-skips", { type: "number", describe: "For repeats, stop after N skips" })
|
|
701
|
+
.option("stop-at", { type: "string", describe: "For repeats, stop after this date-time" })
|
|
702
|
+
.option("keep-when-task-stopped", {
|
|
703
|
+
type: "boolean",
|
|
704
|
+
default: false,
|
|
705
|
+
describe: "For repeats, skip instead of completing when the task is not running",
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
|
|
626
709
|
export async function main(argvInput = hideBin(process.argv), deps = {}) {
|
|
627
710
|
const stdout = deps.stdout || process.stdout;
|
|
628
711
|
const stderr = deps.stderr || process.stderr;
|
|
@@ -630,6 +713,9 @@ export async function main(argvInput = hideBin(process.argv), deps = {}) {
|
|
|
630
713
|
const cwd = deps.cwd || process.cwd();
|
|
631
714
|
const consoleErr = { error: (msg) => stderr.write(`${msg}\n`) };
|
|
632
715
|
const handlerDeps = { ...deps, stdout, stderr, env, cwd };
|
|
716
|
+
const run = (handler) => async (argv) => {
|
|
717
|
+
exitCode = await handler(argv, { ...handlerDeps, configFile: argv.configFile });
|
|
718
|
+
};
|
|
633
719
|
|
|
634
720
|
let exitCode = EXIT.OK;
|
|
635
721
|
try {
|
|
@@ -654,7 +740,8 @@ export async function main(argvInput = hideBin(process.argv), deps = {}) {
|
|
|
654
740
|
})
|
|
655
741
|
.option("archived", { type: "boolean", describe: "List archived tasks instead of active ones" })
|
|
656
742
|
.option("search", { type: "string", describe: "With --archived: match title or transcript text" })
|
|
657
|
-
.option("all-projects", { type: "boolean", describe: "
|
|
743
|
+
.option("all-projects", { type: "boolean", describe: "List tasks across every project (with --archived: search every project)" })
|
|
744
|
+
.option("project-ids", { type: "string", describe: "Comma-separated project ids to list together (merged cross-daemon view)" })
|
|
658
745
|
.option("page", { type: "number", describe: "With --archived: result page (10 per page)" }),
|
|
659
746
|
async (argv) => {
|
|
660
747
|
exitCode = await handleList(argv, { ...handlerDeps, configFile: argv.configFile });
|
|
@@ -680,7 +767,17 @@ export async function main(argvInput = hideBin(process.argv), deps = {}) {
|
|
|
680
767
|
.option("parent-task-id", {
|
|
681
768
|
type: "string",
|
|
682
769
|
describe: "Display the new task in the same task-card group as this task",
|
|
683
|
-
})
|
|
770
|
+
})
|
|
771
|
+
.option("daemon-host", { type: "string", describe: "Run the AI on this daemon (defaults to the project's daemon)" })
|
|
772
|
+
.option("agent", {
|
|
773
|
+
type: "string",
|
|
774
|
+
array: true,
|
|
775
|
+
describe: "Multi-agent group member <name>[:backend]; repeat it. The first is the worker, the rest reviewers",
|
|
776
|
+
})
|
|
777
|
+
.option("global-backend", { type: "string", describe: "Run the AI on a global backend <host>:<backend> (RFC 0041)" })
|
|
778
|
+
.option("worktree", { type: "boolean", describe: "Run in a new git worktree on the project's daemon" })
|
|
779
|
+
.option("remote-worktree", { type: "string", describe: "Run in a new git worktree on this remote daemon" })
|
|
780
|
+
.option("persistent", { type: "boolean", describe: "Create a persistent task (RFC 0039)" }),
|
|
684
781
|
async (argv) => {
|
|
685
782
|
exitCode = await handleCreate(argv, { ...handlerDeps, configFile: argv.configFile });
|
|
686
783
|
},
|
|
@@ -709,7 +806,8 @@ export async function main(argvInput = hideBin(process.argv), deps = {}) {
|
|
|
709
806
|
.positional("message", { type: "string" })
|
|
710
807
|
.option("stdin", { type: "boolean", default: false })
|
|
711
808
|
.option("from-file", { type: "string" })
|
|
712
|
-
.option("metadata-json", { type: "string", describe: "Extra JSON metadata to merge into the message" })
|
|
809
|
+
.option("metadata-json", { type: "string", describe: "Extra JSON metadata to merge into the message" })
|
|
810
|
+
.option("attach", { type: "string", array: true, describe: "Attach a local file; repeat for several" }),
|
|
713
811
|
async (argv) => {
|
|
714
812
|
exitCode = await handleSend(argv, { ...handlerDeps, configFile: argv.configFile });
|
|
715
813
|
},
|
|
@@ -730,15 +828,213 @@ export async function main(argvInput = hideBin(process.argv), deps = {}) {
|
|
|
730
828
|
)
|
|
731
829
|
.command(
|
|
732
830
|
"messages <id>",
|
|
733
|
-
"Pull a slice of task messages
|
|
831
|
+
"Pull a slice of task messages (or --follow to stream new ones)",
|
|
734
832
|
(cmd) => cmd
|
|
735
833
|
.positional("id", { type: "string", demandOption: true })
|
|
736
834
|
.option("limit", { type: "number" })
|
|
737
|
-
.option("before", { type: "string", describe: "Cursor: message id to paginate before" })
|
|
835
|
+
.option("before", { type: "string", describe: "Cursor: message id to paginate before" })
|
|
836
|
+
.option("follow", { alias: "f", type: "boolean", default: false, describe: "Keep polling and print new messages" })
|
|
837
|
+
.option("interval", { type: "number", default: 2, describe: "With --follow: poll interval in seconds" })
|
|
838
|
+
.option("until-idle", { type: "boolean", default: false, describe: "With --follow: stop once the task is no longer running" }),
|
|
738
839
|
async (argv) => {
|
|
739
840
|
exitCode = await handleMessages(argv, { ...handlerDeps, configFile: argv.configFile });
|
|
740
841
|
},
|
|
741
842
|
)
|
|
843
|
+
.command(
|
|
844
|
+
"stop <id>",
|
|
845
|
+
"Stop (kill) a running task",
|
|
846
|
+
(cmd) => cmd.positional("id", { type: "string", demandOption: true }),
|
|
847
|
+
run(extra.handleStop),
|
|
848
|
+
)
|
|
849
|
+
.command(
|
|
850
|
+
"interrupt <id>",
|
|
851
|
+
"Interrupt the current AI turn (the Stop button in the chat)",
|
|
852
|
+
(cmd) => cmd
|
|
853
|
+
.positional("id", { type: "string", demandOption: true })
|
|
854
|
+
.option("target-reply-to", { type: "string", describe: "Reply target of the turn to interrupt (defaults to the latest user message)" }),
|
|
855
|
+
run(extra.handleInterrupt),
|
|
856
|
+
)
|
|
857
|
+
.command(
|
|
858
|
+
"restart <id>",
|
|
859
|
+
"Restart a task's AI session (in place, or as a new task)",
|
|
860
|
+
(cmd) => cmd
|
|
861
|
+
.positional("id", { type: "string", demandOption: true })
|
|
862
|
+
.option("strategy", { choices: ["inplace", "new_task"], describe: "inplace resumes the session; new_task forks a successor" })
|
|
863
|
+
.option("backend", { type: "string", describe: "Switch to this AI backend" })
|
|
864
|
+
.option("refresh-session", { type: "boolean", default: false, describe: "Start a fresh AI session in place" })
|
|
865
|
+
.option("daemon-host", { type: "string", describe: "Run the restarted session on this daemon" })
|
|
866
|
+
.option("first-message", { type: "string", describe: "With --strategy new_task: first message instead of the transcript handoff" }),
|
|
867
|
+
run(extra.handleRestart),
|
|
868
|
+
)
|
|
869
|
+
.command(
|
|
870
|
+
"delete <id>",
|
|
871
|
+
"Delete a task (--permanent deletes an archived task for good)",
|
|
872
|
+
(cmd) => cmd
|
|
873
|
+
.positional("id", { type: "string", demandOption: true })
|
|
874
|
+
.option("permanent", { type: "boolean", default: false, describe: "Permanently delete an archived task" })
|
|
875
|
+
.option("yes", { alias: "y", type: "boolean", default: false, describe: "Confirm the deletion" }),
|
|
876
|
+
run(extra.handleDelete),
|
|
877
|
+
)
|
|
878
|
+
.command(
|
|
879
|
+
"archive <id>",
|
|
880
|
+
"Archive (pack) a task: stop it and keep its transcript searchable",
|
|
881
|
+
(cmd) => cmd.positional("id", { type: "string", demandOption: true }),
|
|
882
|
+
run(extra.handleArchive),
|
|
883
|
+
)
|
|
884
|
+
.command(
|
|
885
|
+
"unarchive <id>",
|
|
886
|
+
"Restore an archived task",
|
|
887
|
+
(cmd) => cmd
|
|
888
|
+
.positional("id", { type: "string", demandOption: true })
|
|
889
|
+
.option("daemon-host", { type: "string", describe: "Restore on this daemon (required when the original is offline)" })
|
|
890
|
+
.option("backend", { type: "string", describe: "Restore with this AI backend" }),
|
|
891
|
+
run(extra.handleUnarchive),
|
|
892
|
+
)
|
|
893
|
+
.command(
|
|
894
|
+
"rename <id> <title>",
|
|
895
|
+
"Rename a task",
|
|
896
|
+
(cmd) => cmd.positional("id", { type: "string", demandOption: true }).positional("title", { type: "string", demandOption: true }),
|
|
897
|
+
run(extra.handleRename),
|
|
898
|
+
)
|
|
899
|
+
.command(
|
|
900
|
+
"pin <id>",
|
|
901
|
+
"Pin a task to the top of the list",
|
|
902
|
+
(cmd) => cmd.positional("id", { type: "string", demandOption: true }),
|
|
903
|
+
run((argv, deps) => extra.handlePin(argv, deps, true)),
|
|
904
|
+
)
|
|
905
|
+
.command(
|
|
906
|
+
"unpin <id>",
|
|
907
|
+
"Unpin a task",
|
|
908
|
+
(cmd) => cmd.positional("id", { type: "string", demandOption: true }),
|
|
909
|
+
run((argv, deps) => extra.handlePin(argv, deps, false)),
|
|
910
|
+
)
|
|
911
|
+
.command(
|
|
912
|
+
"move <id> [targetProject]",
|
|
913
|
+
"Show a task under another project (display only; --back files it under its own project)",
|
|
914
|
+
(cmd) => cmd
|
|
915
|
+
.positional("id", { type: "string", demandOption: true })
|
|
916
|
+
.positional("targetProject", { type: "string", describe: "Target project id or name" })
|
|
917
|
+
.option("back", { type: "boolean", default: false }),
|
|
918
|
+
run(extra.handleMove),
|
|
919
|
+
)
|
|
920
|
+
.command(
|
|
921
|
+
"labels <id> [labelIds..]",
|
|
922
|
+
"Replace the labels attached to a task",
|
|
923
|
+
(cmd) => cmd
|
|
924
|
+
.positional("id", { type: "string", demandOption: true })
|
|
925
|
+
.positional("labelIds", { type: "string", array: true })
|
|
926
|
+
.option("clear", { type: "boolean", default: false, describe: "Remove all labels" }),
|
|
927
|
+
run(extra.handleLabels),
|
|
928
|
+
)
|
|
929
|
+
.command(
|
|
930
|
+
"share <id>",
|
|
931
|
+
"Create (or print) the read-only share link of a task",
|
|
932
|
+
(cmd) => cmd.positional("id", { type: "string", demandOption: true }),
|
|
933
|
+
run(extra.handleShare),
|
|
934
|
+
)
|
|
935
|
+
.command(
|
|
936
|
+
"unshare <id>",
|
|
937
|
+
"Revoke a task's share link",
|
|
938
|
+
(cmd) => cmd.positional("id", { type: "string", demandOption: true }),
|
|
939
|
+
run(extra.handleUnshare),
|
|
940
|
+
)
|
|
941
|
+
.command(
|
|
942
|
+
"shared <token>",
|
|
943
|
+
"Read a shared task (token or share link, as opened by the /share page)",
|
|
944
|
+
(cmd) => cmd.positional("token", { type: "string", demandOption: true, describe: "Share token or https://…/share/<token> link" }),
|
|
945
|
+
run(extra.handleShared),
|
|
946
|
+
)
|
|
947
|
+
.command(
|
|
948
|
+
"transcribe <file>",
|
|
949
|
+
"Speech to text for a .wav/.mp3 file (the web composer's voice input)",
|
|
950
|
+
(cmd) => cmd
|
|
951
|
+
.positional("file", { type: "string", demandOption: true })
|
|
952
|
+
.option("language", { type: "string", describe: "Language hint, e.g. zh or en" }),
|
|
953
|
+
run(extra.handleTranscribe),
|
|
954
|
+
)
|
|
955
|
+
.command(
|
|
956
|
+
"persistent <id>",
|
|
957
|
+
"Change persistent-task settings (RFC 0039)",
|
|
958
|
+
(cmd) => cmd
|
|
959
|
+
.positional("id", { type: "string", demandOption: true })
|
|
960
|
+
.option("enable", { type: "boolean" })
|
|
961
|
+
.option("disable", { type: "boolean" })
|
|
962
|
+
.option("auto-end-round", { type: "boolean", describe: "End the round after 1h without a reply (default on; --no-auto-end-round to turn off)" })
|
|
963
|
+
.option("instructions", { type: "string", describe: "Standing instructions carried into every round" })
|
|
964
|
+
.option("summary", { type: "string", describe: "Carried-over summary" }),
|
|
965
|
+
run(extra.handlePersistent),
|
|
966
|
+
)
|
|
967
|
+
.command(
|
|
968
|
+
"round",
|
|
969
|
+
"End the current round or start a new one on a persistent task",
|
|
970
|
+
(cmd) => cmd
|
|
971
|
+
.command(
|
|
972
|
+
"end <id>",
|
|
973
|
+
"End the current round",
|
|
974
|
+
(sub) => sub.positional("id", { type: "string", demandOption: true }),
|
|
975
|
+
run(extra.handleRoundEnd),
|
|
976
|
+
)
|
|
977
|
+
.command(
|
|
978
|
+
"start <id> [message]",
|
|
979
|
+
"Start a new round (fresh AI session) with a first message",
|
|
980
|
+
(sub) => withMessageInput(sub
|
|
981
|
+
.positional("id", { type: "string", demandOption: true })
|
|
982
|
+
.positional("message", { type: "string" }))
|
|
983
|
+
.option("backend", { type: "string" })
|
|
984
|
+
.option("daemon-host", { type: "string" })
|
|
985
|
+
.option("worktree", { choices: ["inherit", "new", "none"] })
|
|
986
|
+
.option("expected-round", { type: "number", describe: "Refuse if another client already moved past this round" }),
|
|
987
|
+
run((argv, deps) => extra.handleRoundStart(argv, deps, () => readMessageInput({
|
|
988
|
+
positional: argv.message,
|
|
989
|
+
fromFile: argv.fromFile,
|
|
990
|
+
useStdin: Boolean(argv.stdin),
|
|
991
|
+
stdin: deps.stdin,
|
|
992
|
+
}))),
|
|
993
|
+
)
|
|
994
|
+
.demandCommand(1),
|
|
995
|
+
)
|
|
996
|
+
.command(
|
|
997
|
+
"cleanup-worktree <id>",
|
|
998
|
+
"Remove a stopped task's isolated git worktree",
|
|
999
|
+
(cmd) => cmd.positional("id", { type: "string", demandOption: true }),
|
|
1000
|
+
run(extra.handleCleanupWorktree),
|
|
1001
|
+
)
|
|
1002
|
+
.command(
|
|
1003
|
+
"terminal",
|
|
1004
|
+
"Open, show, or close the PTY terminal attached to a task",
|
|
1005
|
+
(cmd) => cmd
|
|
1006
|
+
.command("open <id>", "Attach a PTY terminal to the task", (sub) => sub.positional("id", { type: "string", demandOption: true }), run((argv, deps) => extra.handleTerminal(argv, deps, "open")))
|
|
1007
|
+
.command("show <id>", "Show the attached terminal", (sub) => sub.positional("id", { type: "string", demandOption: true }), run((argv, deps) => extra.handleTerminal(argv, deps, "show")))
|
|
1008
|
+
.command("close <id>", "Close and delete the attached terminal", (sub) => sub.positional("id", { type: "string", demandOption: true }), run((argv, deps) => extra.handleTerminal(argv, deps, "close")))
|
|
1009
|
+
.demandCommand(1),
|
|
1010
|
+
)
|
|
1011
|
+
.command(
|
|
1012
|
+
"attachment",
|
|
1013
|
+
"Download a message attachment",
|
|
1014
|
+
(cmd) => cmd
|
|
1015
|
+
.command(
|
|
1016
|
+
"download <id> <attachmentId>",
|
|
1017
|
+
"Save an attachment (ids are in `task messages --json`)",
|
|
1018
|
+
(sub) => sub
|
|
1019
|
+
.positional("id", { type: "string", demandOption: true })
|
|
1020
|
+
.positional("attachmentId", { type: "string", demandOption: true })
|
|
1021
|
+
.option("output", { alias: "o", type: "string", describe: "File or directory to write to; - for stdout" }),
|
|
1022
|
+
run(extra.handleAttachmentDownload),
|
|
1023
|
+
)
|
|
1024
|
+
.demandCommand(1),
|
|
1025
|
+
)
|
|
1026
|
+
.command(
|
|
1027
|
+
"resume",
|
|
1028
|
+
"Resume an existing AI CLI session as a new app task (see `conductor daemon sessions <host>`)",
|
|
1029
|
+
(cmd) => cmd
|
|
1030
|
+
.option("daemon-host", { type: "string", demandOption: true })
|
|
1031
|
+
.option("backend", { type: "string", demandOption: true })
|
|
1032
|
+
.option("session", { type: "string", demandOption: true, describe: "Session id" })
|
|
1033
|
+
.option("session-file", { type: "string", describe: "Session file path on the daemon" })
|
|
1034
|
+
.option("title", { type: "string" })
|
|
1035
|
+
.option("prompt", { type: "string", describe: "First instruction after resuming" }),
|
|
1036
|
+
run(extra.handleResume),
|
|
1037
|
+
)
|
|
742
1038
|
.command(
|
|
743
1039
|
"schedule",
|
|
744
1040
|
"Create, list, and delete scheduled messages for a task",
|
|
@@ -776,6 +1072,17 @@ export async function main(argvInput = hideBin(process.argv), deps = {}) {
|
|
|
776
1072
|
exitCode = await handleScheduleCreate(argv, { ...handlerDeps, configFile: argv.configFile });
|
|
777
1073
|
},
|
|
778
1074
|
)
|
|
1075
|
+
.command(
|
|
1076
|
+
"update <id> <scheduleId> [message]",
|
|
1077
|
+
"Edit an active scheduled message's text and/or schedule",
|
|
1078
|
+
(sub) => scheduleModeOptions(withMessageInput(sub
|
|
1079
|
+
.positional("id", { type: "string", demandOption: true })
|
|
1080
|
+
.positional("scheduleId", { type: "string", demandOption: true })
|
|
1081
|
+
.positional("message", { type: "string" }))),
|
|
1082
|
+
async (argv) => {
|
|
1083
|
+
exitCode = await handleScheduleUpdate(argv, { ...handlerDeps, configFile: argv.configFile });
|
|
1084
|
+
},
|
|
1085
|
+
)
|
|
779
1086
|
.command(
|
|
780
1087
|
"delete <id> <scheduleId>",
|
|
781
1088
|
"Delete an active scheduled message",
|
package/bin/conductor.js
CHANGED
|
@@ -5,17 +5,20 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Subcommands:
|
|
7
7
|
* fire - Run AI coding agents with Conductor integration
|
|
8
|
-
* daemon - Start
|
|
8
|
+
* daemon - Start the daemon, or query/control daemons (list/restart/upgrade/share/...)
|
|
9
9
|
* config - Interactive configuration setup
|
|
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
13
|
* channel - Connect user-owned chat channel providers
|
|
14
14
|
* serve-ai - Start an OpenAI-compatible local AI server
|
|
15
|
-
* project - Manage Conductor projects (list/
|
|
16
|
-
* issue - Manage issues (list/show/create/update/start/done)
|
|
17
|
-
* task - Manage tasks (create/
|
|
15
|
+
* project - Manage Conductor projects (list/create/update/delete/collab/labels/...)
|
|
16
|
+
* issue - Manage issues (list/show/create/update/start/done/delete)
|
|
17
|
+
* task - Manage tasks (create/send/stop/restart/archive/share/schedule/...)
|
|
18
18
|
* remote - Run commands and copy files on another daemon's host
|
|
19
|
+
* search - Search message history across all tasks
|
|
20
|
+
* settings - Manage user preferences (global backends, catchphrases, daily report, ...)
|
|
21
|
+
* auth - Show the signed-in user and manage API tokens
|
|
19
22
|
*/
|
|
20
23
|
|
|
21
24
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -52,6 +55,9 @@ export function runConductorCli(args = argv, deps = {}) {
|
|
|
52
55
|
"issue",
|
|
53
56
|
"task",
|
|
54
57
|
"remote",
|
|
58
|
+
"search",
|
|
59
|
+
"settings",
|
|
60
|
+
"auth",
|
|
55
61
|
];
|
|
56
62
|
|
|
57
63
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
@@ -127,17 +133,23 @@ Usage: conductor <subcommand> [options]
|
|
|
127
133
|
|
|
128
134
|
Subcommands:
|
|
129
135
|
fire Run AI coding agents with Conductor integration
|
|
130
|
-
daemon Start long-running daemon (or: daemon list/tools/quota
|
|
136
|
+
daemon Start long-running daemon (or: daemon list/tools/quota/sessions/restart/
|
|
137
|
+
upgrade/accounts/commands/share to query and control daemons)
|
|
131
138
|
config Interactive configuration setup
|
|
132
139
|
update Update the CLI to the latest version
|
|
133
140
|
diagnose Diagnose a task and print likely root cause
|
|
134
141
|
send-file Upload a local file into a task session
|
|
135
142
|
channel Connect user-owned chat channel providers
|
|
136
143
|
serve-ai Start an OpenAI-compatible local AI server
|
|
137
|
-
project Manage Conductor projects (list/show/create
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
project Manage Conductor projects (list/show/create/update/delete/reorder/
|
|
145
|
+
agents/collab/labels/...)
|
|
146
|
+
issue Manage issues (list/show/create/update/start/done/delete)
|
|
147
|
+
task Manage tasks (create/send/insert/interrupt/stop/restart/delete/
|
|
148
|
+
archive/unarchive/rename/pin/move/labels/share/round/resume/...)
|
|
140
149
|
remote Run commands and copy files on another daemon's host (exec/cp/wait/mcp)
|
|
150
|
+
search Search message history across all tasks
|
|
151
|
+
settings Manage user preferences (global-backends/catchphrases/daily-report/reports/...)
|
|
152
|
+
auth Show the signed-in user and manage API tokens (whoami/tokens)
|
|
141
153
|
|
|
142
154
|
Options:
|
|
143
155
|
-h, --help Show this help message
|
|
@@ -163,8 +175,14 @@ Examples:
|
|
|
163
175
|
conductor task create --title "Refactor module" --prompt "Extract the parser" --backend codex
|
|
164
176
|
conductor task send <task-id> "please add a unit test"
|
|
165
177
|
conductor task schedule create <task-id> "follow up" --delay 10m
|
|
178
|
+
conductor task create --title "Refactor" --agent feature-dev --agent code-reviewer --worktree
|
|
179
|
+
conductor task restart <task-id> --strategy new_task --backend codex
|
|
180
|
+
conductor daemon upgrade macmini --wait
|
|
166
181
|
conductor remote exec --target ubuntu --workspace /home/duino/ws/holomotion ls .
|
|
167
182
|
conductor remote cp ./build.tar.gz ubuntu:/srv/app/build.tar.gz
|
|
183
|
+
conductor search "flaky test" --limit 20
|
|
184
|
+
conductor settings catchphrases add "please add a unit test"
|
|
185
|
+
conductor auth tokens create --name ci
|
|
168
186
|
|
|
169
187
|
For subcommand-specific help:
|
|
170
188
|
conductor fire --help
|
|
@@ -179,6 +197,9 @@ For subcommand-specific help:
|
|
|
179
197
|
conductor issue --help
|
|
180
198
|
conductor task --help
|
|
181
199
|
conductor remote --help
|
|
200
|
+
conductor search --help
|
|
201
|
+
conductor settings --help
|
|
202
|
+
conductor auth --help
|
|
182
203
|
|
|
183
204
|
Version: ${pkgJson.version}
|
|
184
205
|
`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/conductor-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"gitCommitId": "
|
|
3
|
+
"version": "0.16.0",
|
|
4
|
+
"gitCommitId": "77239b9",
|
|
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.
|
|
28
|
-
"@love-moon/conductor-sdk": "0.
|
|
27
|
+
"@love-moon/ai-sdk": "0.16.0",
|
|
28
|
+
"@love-moon/conductor-sdk": "0.16.0",
|
|
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.
|
|
41
|
+
"@love-moon/chat-web": "0.16.0"
|
|
42
42
|
},
|
|
43
43
|
"pnpm": {
|
|
44
44
|
"onlyBuiltDependencies": [
|