multiclaw 0.7.0 → 0.8.1
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/dist/commands/prune.d.ts +28 -0
- package/dist/commands/prune.test.d.ts +1 -0
- package/dist/commands/resume.d.ts +5 -2
- package/dist/index.js +247 -38
- package/package.json +3 -3
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface PruneOptions {
|
|
2
|
+
config?: string;
|
|
3
|
+
history?: boolean;
|
|
4
|
+
runs?: boolean;
|
|
5
|
+
all?: boolean;
|
|
6
|
+
dryRun?: boolean;
|
|
7
|
+
yes?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface PruneItem {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
path: string;
|
|
13
|
+
size: number;
|
|
14
|
+
kind: "history" | "run" | "all-runs";
|
|
15
|
+
}
|
|
16
|
+
/** Scan `.multiclaw/` and list prune-able items with their sizes. */
|
|
17
|
+
export declare function scanPruneItems(workDir: string): PruneItem[];
|
|
18
|
+
/** Delete the selected items. Returns what was removed. */
|
|
19
|
+
export declare function deletePruneItems(items: PruneItem[]): {
|
|
20
|
+
deleted: string[];
|
|
21
|
+
failed: Array<{
|
|
22
|
+
path: string;
|
|
23
|
+
error: string;
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
26
|
+
/** Resolve non-interactive flags into item ids, or undefined if interactive. */
|
|
27
|
+
export declare function resolvePruneTargets(options: PruneOptions, items: PruneItem[]): string[] | undefined;
|
|
28
|
+
export declare function pruneCommand(options: PruneOptions): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OrchestratorResult } from "@multiclawcli/core";
|
|
2
2
|
/** A previous run's report, as much of it as resume needs. */
|
|
3
3
|
export interface ResumeTarget {
|
|
4
4
|
runDir: string;
|
|
@@ -28,5 +28,8 @@ export declare function readReport(runDir: string): OrchestratorResult;
|
|
|
28
28
|
*/
|
|
29
29
|
export declare function resumeOptionsFromReport(report: OrchestratorResult, requirementOverride?: string): {
|
|
30
30
|
requirement: string;
|
|
31
|
-
resume:
|
|
31
|
+
resume: {
|
|
32
|
+
agents: string[];
|
|
33
|
+
sessions?: Record<string, string>;
|
|
34
|
+
};
|
|
32
35
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync as
|
|
5
|
-
import { dirname as dirname4, join as
|
|
4
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
5
|
+
import { dirname as dirname4, join as join6 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
|
|
@@ -468,9 +468,217 @@ async function initCommand() {
|
|
|
468
468
|
);
|
|
469
469
|
}
|
|
470
470
|
|
|
471
|
+
// src/commands/prune.ts
|
|
472
|
+
import * as fs3 from "fs";
|
|
473
|
+
import * as path3 from "path";
|
|
474
|
+
import { checkbox as checkbox2, confirm } from "@inquirer/prompts";
|
|
475
|
+
function scanPruneItems(workDir) {
|
|
476
|
+
const items = [];
|
|
477
|
+
const multiclawDir = path3.join(workDir, ".multiclaw");
|
|
478
|
+
const historyFile = path3.join(multiclawDir, "console", "history.json");
|
|
479
|
+
if (fs3.existsSync(historyFile)) {
|
|
480
|
+
const stat = fs3.statSync(historyFile);
|
|
481
|
+
const raw = fs3.readFileSync(historyFile, "utf-8");
|
|
482
|
+
let turns = 0;
|
|
483
|
+
try {
|
|
484
|
+
const parsed = JSON.parse(raw);
|
|
485
|
+
if (Array.isArray(parsed)) turns = parsed.length;
|
|
486
|
+
} catch {
|
|
487
|
+
}
|
|
488
|
+
items.push({
|
|
489
|
+
id: "history",
|
|
490
|
+
label: `Console history (${turns} turn${turns === 1 ? "" : "s"}, ${formatBytes(stat.size)})`,
|
|
491
|
+
path: historyFile,
|
|
492
|
+
size: stat.size,
|
|
493
|
+
kind: "history"
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
const runsDir = path3.join(multiclawDir, "runs");
|
|
497
|
+
if (fs3.existsSync(runsDir)) {
|
|
498
|
+
const entries = fs3.readdirSync(runsDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("run-")).sort((a, b) => a.name.localeCompare(b.name));
|
|
499
|
+
let totalRunsSize = 0;
|
|
500
|
+
for (const entry of entries) {
|
|
501
|
+
const runPath = path3.join(runsDir, entry.name);
|
|
502
|
+
const size = dirSize(runPath);
|
|
503
|
+
totalRunsSize += size;
|
|
504
|
+
const agentCount = countAgentsInRun(runPath);
|
|
505
|
+
items.push({
|
|
506
|
+
id: entry.name,
|
|
507
|
+
label: `${entry.name} (${agentCount} agent${agentCount === 1 ? "" : "s"}, ${formatBytes(size)})`,
|
|
508
|
+
path: runPath,
|
|
509
|
+
size,
|
|
510
|
+
kind: "run"
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
if (entries.length > 1) {
|
|
514
|
+
items.push({
|
|
515
|
+
id: "all-runs",
|
|
516
|
+
label: `All runs (${entries.length} total, ${formatBytes(totalRunsSize)})`,
|
|
517
|
+
path: runsDir,
|
|
518
|
+
size: totalRunsSize,
|
|
519
|
+
kind: "all-runs"
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return items;
|
|
524
|
+
}
|
|
525
|
+
function deletePruneItems(items) {
|
|
526
|
+
const deleted = [];
|
|
527
|
+
const failed = [];
|
|
528
|
+
for (const item of items) {
|
|
529
|
+
try {
|
|
530
|
+
if (item.kind === "history") {
|
|
531
|
+
fs3.unlinkSync(item.path);
|
|
532
|
+
deleted.push(item.id);
|
|
533
|
+
} else if (item.kind === "run") {
|
|
534
|
+
fs3.rmSync(item.path, { recursive: true, force: true });
|
|
535
|
+
deleted.push(item.id);
|
|
536
|
+
} else if (item.kind === "all-runs") {
|
|
537
|
+
const runsDir = item.path;
|
|
538
|
+
const entries = fs3.readdirSync(runsDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("run-"));
|
|
539
|
+
for (const entry of entries) {
|
|
540
|
+
fs3.rmSync(path3.join(runsDir, entry.name), { recursive: true, force: true });
|
|
541
|
+
}
|
|
542
|
+
try {
|
|
543
|
+
fs3.rmdirSync(runsDir);
|
|
544
|
+
} catch {
|
|
545
|
+
}
|
|
546
|
+
deleted.push(item.id);
|
|
547
|
+
}
|
|
548
|
+
} catch (err) {
|
|
549
|
+
failed.push({ path: item.path, error: err instanceof Error ? err.message : String(err) });
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return { deleted, failed };
|
|
553
|
+
}
|
|
554
|
+
function resolvePruneTargets(options, items) {
|
|
555
|
+
if (options.all) {
|
|
556
|
+
return items.map((i) => i.id);
|
|
557
|
+
}
|
|
558
|
+
if (options.history && options.runs) {
|
|
559
|
+
return items.map((i) => i.id);
|
|
560
|
+
}
|
|
561
|
+
if (options.history) {
|
|
562
|
+
return items.filter((i) => i.kind === "history").map((i) => i.id);
|
|
563
|
+
}
|
|
564
|
+
if (options.runs) {
|
|
565
|
+
return items.filter((i) => i.kind === "run" || i.kind === "all-runs").map((i) => i.id);
|
|
566
|
+
}
|
|
567
|
+
return void 0;
|
|
568
|
+
}
|
|
569
|
+
async function pruneCommand(options) {
|
|
570
|
+
const config = await loadConfig(options.config);
|
|
571
|
+
const items = scanPruneItems(config.workDir);
|
|
572
|
+
if (items.length === 0) {
|
|
573
|
+
console.log("Nothing to prune \u2014 no history or runs found.");
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
let selectedIds;
|
|
577
|
+
const nonInteractive = resolvePruneTargets(options, items);
|
|
578
|
+
if (nonInteractive !== void 0) {
|
|
579
|
+
selectedIds = nonInteractive;
|
|
580
|
+
} else {
|
|
581
|
+
const choices = items.map((item) => ({
|
|
582
|
+
value: item.id,
|
|
583
|
+
name: item.label
|
|
584
|
+
}));
|
|
585
|
+
selectedIds = await checkbox2({
|
|
586
|
+
message: "Select items to prune (space to select, enter to confirm):",
|
|
587
|
+
choices
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
if (selectedIds.length === 0) {
|
|
591
|
+
console.log("No items selected \u2014 nothing pruned.");
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const selected = items.filter((i) => selectedIds.includes(i.id));
|
|
595
|
+
const totalSize = selected.reduce((sum, i) => sum + i.size, 0);
|
|
596
|
+
if (options.dryRun) {
|
|
597
|
+
console.log("");
|
|
598
|
+
console.log("\x1B[2mDry run \u2014 the following would be deleted:\x1B[0m");
|
|
599
|
+
for (const item of selected) {
|
|
600
|
+
console.log(` \x1B[31m- ${item.label}\x1B[0m`);
|
|
601
|
+
}
|
|
602
|
+
console.log(`
|
|
603
|
+
Total: ${formatBytes(totalSize)}`);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (!options.yes) {
|
|
607
|
+
const ok = await confirm({
|
|
608
|
+
message: `Delete ${selected.length} item${selected.length === 1 ? "" : "s"} (${formatBytes(totalSize)})? This cannot be undone.`,
|
|
609
|
+
default: false
|
|
610
|
+
});
|
|
611
|
+
if (!ok) {
|
|
612
|
+
console.log("Cancelled \u2014 nothing pruned.");
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
const { deleted, failed } = deletePruneItems(selected);
|
|
617
|
+
console.log("");
|
|
618
|
+
if (deleted.length > 0) {
|
|
619
|
+
console.log(
|
|
620
|
+
`\x1B[32m\u2713\x1B[0m Pruned ${deleted.length} item${deleted.length === 1 ? "" : "s"} (${formatBytes(totalSize)} freed)`
|
|
621
|
+
);
|
|
622
|
+
for (const id of deleted) {
|
|
623
|
+
const item = items.find((i) => i.id === id);
|
|
624
|
+
if (item) console.log(` \x1B[2m- ${item.label}\x1B[0m`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
if (failed.length > 0) {
|
|
628
|
+
console.log("");
|
|
629
|
+
console.log(
|
|
630
|
+
`\x1B[31m\u2717\x1B[0m Failed to delete ${failed.length} item${failed.length === 1 ? "" : "s"}:`
|
|
631
|
+
);
|
|
632
|
+
for (const { path: p, error } of failed) {
|
|
633
|
+
console.log(` \x1B[31m- ${p}: ${error}\x1B[0m`);
|
|
634
|
+
}
|
|
635
|
+
process.exitCode = 1;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function formatBytes(bytes) {
|
|
639
|
+
if (bytes === 0) return "0 B";
|
|
640
|
+
const k = 1024;
|
|
641
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
642
|
+
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1);
|
|
643
|
+
return `${(bytes / k ** i).toFixed(i === 0 ? 0 : 1)} ${sizes[i]}`;
|
|
644
|
+
}
|
|
645
|
+
function dirSize(root) {
|
|
646
|
+
let total = 0;
|
|
647
|
+
const stack = [root];
|
|
648
|
+
while (stack.length > 0) {
|
|
649
|
+
const dir = stack.pop();
|
|
650
|
+
if (dir === void 0) continue;
|
|
651
|
+
for (const name of fs3.readdirSync(dir)) {
|
|
652
|
+
const absolute = path3.join(dir, name);
|
|
653
|
+
const stat = fs3.statSync(absolute);
|
|
654
|
+
if (stat.isDirectory()) {
|
|
655
|
+
stack.push(absolute);
|
|
656
|
+
} else {
|
|
657
|
+
total += stat.size;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return total;
|
|
662
|
+
}
|
|
663
|
+
function countAgentsInRun(runDir) {
|
|
664
|
+
const logsDir = path3.join(runDir, "logs");
|
|
665
|
+
if (!fs3.existsSync(logsDir)) return 0;
|
|
666
|
+
const reportPath = path3.join(logsDir, "report.json");
|
|
667
|
+
if (!fs3.existsSync(reportPath)) {
|
|
668
|
+
return fs3.readdirSync(logsDir).filter((n) => n.endsWith(".log")).length;
|
|
669
|
+
}
|
|
670
|
+
try {
|
|
671
|
+
const raw = fs3.readFileSync(reportPath, "utf-8");
|
|
672
|
+
const report = JSON.parse(raw);
|
|
673
|
+
return report.agentResults?.length ?? 0;
|
|
674
|
+
} catch {
|
|
675
|
+
return fs3.readdirSync(logsDir).filter((n) => n.endsWith(".log")).length;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
471
679
|
// src/commands/run.ts
|
|
472
|
-
import * as
|
|
473
|
-
import * as
|
|
680
|
+
import * as fs6 from "fs";
|
|
681
|
+
import * as path6 from "path";
|
|
474
682
|
import {
|
|
475
683
|
describeResumePlan,
|
|
476
684
|
Orchestrator as OrchestratorImpl
|
|
@@ -669,8 +877,8 @@ ${C.bold}${C.purple}${edge[0]}${line}${edge[3]}`);
|
|
|
669
877
|
};
|
|
670
878
|
|
|
671
879
|
// src/reporters/FileLogWriter.ts
|
|
672
|
-
import * as
|
|
673
|
-
import * as
|
|
880
|
+
import * as fs4 from "fs";
|
|
881
|
+
import * as path4 from "path";
|
|
674
882
|
var FileLogWriter = class {
|
|
675
883
|
constructor(logDir, onError = (message) => process.stderr.write(`
|
|
676
884
|
[multiclaw] ${message}
|
|
@@ -681,7 +889,7 @@ var FileLogWriter = class {
|
|
|
681
889
|
/** Agent ids whose log could not be opened — never retried, so a broken
|
|
682
890
|
* directory does not produce one failing stream per output chunk. */
|
|
683
891
|
this.failed = /* @__PURE__ */ new Set();
|
|
684
|
-
|
|
892
|
+
fs4.mkdirSync(logDir, { recursive: true });
|
|
685
893
|
}
|
|
686
894
|
attach(eventBus) {
|
|
687
895
|
return eventBus.subscribe((e) => this.handle(e));
|
|
@@ -705,7 +913,7 @@ var FileLogWriter = class {
|
|
|
705
913
|
}
|
|
706
914
|
if (e.type === "orchestration:complete") {
|
|
707
915
|
try {
|
|
708
|
-
|
|
916
|
+
fs4.writeFileSync(path4.join(this.logDir, "report.json"), JSON.stringify(e.payload, null, 2));
|
|
709
917
|
} catch (err) {
|
|
710
918
|
this.onError(`Could not write report.json: ${errorMessage(err)}`);
|
|
711
919
|
}
|
|
@@ -716,7 +924,7 @@ var FileLogWriter = class {
|
|
|
716
924
|
if (this.failed.has(agentId)) return void 0;
|
|
717
925
|
let stream = this.streams.get(agentId);
|
|
718
926
|
if (!stream) {
|
|
719
|
-
stream =
|
|
927
|
+
stream = fs4.createWriteStream(path4.join(this.logDir, `${agentId}.log`));
|
|
720
928
|
stream.on("error", (err) => {
|
|
721
929
|
this.failed.add(agentId);
|
|
722
930
|
this.streams.delete(agentId);
|
|
@@ -747,32 +955,32 @@ function errorMessage(err) {
|
|
|
747
955
|
}
|
|
748
956
|
|
|
749
957
|
// src/commands/resume.ts
|
|
750
|
-
import * as
|
|
751
|
-
import * as
|
|
958
|
+
import * as fs5 from "fs";
|
|
959
|
+
import * as path5 from "path";
|
|
752
960
|
function resolveRunDir(runsRoot, target) {
|
|
753
961
|
if (typeof target !== "string" || target === "latest") {
|
|
754
962
|
return latestRunDir(runsRoot);
|
|
755
963
|
}
|
|
756
|
-
const resolved =
|
|
757
|
-
const runDir =
|
|
758
|
-
if (!
|
|
964
|
+
const resolved = path5.resolve(target);
|
|
965
|
+
const runDir = path5.basename(resolved) === "logs" ? path5.dirname(resolved) : resolved;
|
|
966
|
+
if (!fs5.existsSync(runDir)) {
|
|
759
967
|
throw new Error(`Run directory not found: ${runDir}`);
|
|
760
968
|
}
|
|
761
969
|
return runDir;
|
|
762
970
|
}
|
|
763
971
|
function latestRunDir(runsRoot) {
|
|
764
|
-
const names =
|
|
972
|
+
const names = fs5.existsSync(runsRoot) ? fs5.readdirSync(runsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("run-")).map((entry) => entry.name).sort() : [];
|
|
765
973
|
const latest = names[names.length - 1];
|
|
766
974
|
if (!latest) {
|
|
767
975
|
throw new Error(`No runs found to resume \u2014 looked in ${runsRoot}`);
|
|
768
976
|
}
|
|
769
|
-
return
|
|
977
|
+
return path5.join(runsRoot, latest);
|
|
770
978
|
}
|
|
771
979
|
function readReport(runDir) {
|
|
772
|
-
const reportPath =
|
|
980
|
+
const reportPath = path5.join(runDir, "logs", "report.json");
|
|
773
981
|
let raw;
|
|
774
982
|
try {
|
|
775
|
-
raw =
|
|
983
|
+
raw = fs5.readFileSync(reportPath, "utf-8");
|
|
776
984
|
} catch {
|
|
777
985
|
throw new Error(`No report.json in ${runDir} \u2014 it may not be a complete run`);
|
|
778
986
|
}
|
|
@@ -817,14 +1025,14 @@ async function runCommand(requirementArg, options) {
|
|
|
817
1025
|
}
|
|
818
1026
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
819
1027
|
if (options.resume !== void 0) {
|
|
820
|
-
const runsRoot =
|
|
1028
|
+
const runsRoot = path6.join(config.workDir, ".multiclaw", "runs");
|
|
821
1029
|
const runDir = resolveRunDir(runsRoot, options.resume);
|
|
822
1030
|
const report = readReport(runDir);
|
|
823
1031
|
const { requirement, resume } = resumeOptionsFromReport(report, requirementArg);
|
|
824
1032
|
config.workDir = runDir;
|
|
825
1033
|
config.resume = resume;
|
|
826
1034
|
config.context = { ...config.context, requirement };
|
|
827
|
-
config.logDir =
|
|
1035
|
+
config.logDir = path6.join(runDir, `logs-resume-${stamp}`);
|
|
828
1036
|
const plan = describeResumePlan({
|
|
829
1037
|
agents: resume.agents,
|
|
830
1038
|
sessions: resume.sessions,
|
|
@@ -832,7 +1040,7 @@ async function runCommand(requirementArg, options) {
|
|
|
832
1040
|
});
|
|
833
1041
|
console.log(`\x1B[2mResuming ${runDir} (${resume.agents.length} agents) \u2014 ${plan}\x1B[0m`);
|
|
834
1042
|
} else {
|
|
835
|
-
config.workDir =
|
|
1043
|
+
config.workDir = path6.join(config.workDir, ".multiclaw", "runs", `run-${stamp}`);
|
|
836
1044
|
}
|
|
837
1045
|
if (options.dryRun) {
|
|
838
1046
|
const preview = await new OrchestratorImpl(config).preview();
|
|
@@ -848,7 +1056,7 @@ async function runCommand(requirementArg, options) {
|
|
|
848
1056
|
event.payload.deliverables = collectDeliverables(config.workDir, before);
|
|
849
1057
|
});
|
|
850
1058
|
new ConsoleReporter().attach(orchestrator.eventBus);
|
|
851
|
-
const logDir = config.logDir ??
|
|
1059
|
+
const logDir = config.logDir ?? path6.join(config.workDir, "logs");
|
|
852
1060
|
const logs = new FileLogWriter(logDir);
|
|
853
1061
|
logs.attach(orchestrator.eventBus);
|
|
854
1062
|
console.log(`\x1B[2mLogs: ${logDir}\x1B[0m`);
|
|
@@ -906,16 +1114,16 @@ function collectDeliverables(root, before) {
|
|
|
906
1114
|
return deliverables.sort((a, b) => a.path.localeCompare(b.path)).slice(0, 20);
|
|
907
1115
|
}
|
|
908
1116
|
function walkFiles(root, visit) {
|
|
909
|
-
if (!
|
|
1117
|
+
if (!fs6.existsSync(root)) return;
|
|
910
1118
|
const stack = [root];
|
|
911
1119
|
while (stack.length > 0) {
|
|
912
1120
|
const dir = stack.pop();
|
|
913
|
-
for (const name of
|
|
1121
|
+
for (const name of fs6.readdirSync(dir)) {
|
|
914
1122
|
if (name === ".multiclaw") continue;
|
|
915
|
-
const absolute =
|
|
916
|
-
const stat =
|
|
1123
|
+
const absolute = path6.join(dir, name);
|
|
1124
|
+
const stat = fs6.statSync(absolute);
|
|
917
1125
|
if (stat.isDirectory()) stack.push(absolute);
|
|
918
|
-
else if (stat.isFile()) visit(absolute,
|
|
1126
|
+
else if (stat.isFile()) visit(absolute, path6.relative(root, absolute), stat);
|
|
919
1127
|
}
|
|
920
1128
|
}
|
|
921
1129
|
}
|
|
@@ -943,18 +1151,18 @@ async function tuiCommand(options) {
|
|
|
943
1151
|
|
|
944
1152
|
// src/commands/upgrade.ts
|
|
945
1153
|
import { execSync } from "child_process";
|
|
946
|
-
import { existsSync as
|
|
947
|
-
import { dirname as dirname3, join as
|
|
1154
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
|
|
1155
|
+
import { dirname as dirname3, join as join5, resolve as resolve4 } from "path";
|
|
948
1156
|
import { fileURLToPath } from "url";
|
|
949
1157
|
var PACKAGE_NAME = "multiclaw";
|
|
950
1158
|
var RED2 = "\x1B[31m";
|
|
951
1159
|
var GREEN2 = "\x1B[32m";
|
|
952
1160
|
var RESET2 = "\x1B[0m";
|
|
953
1161
|
function readManifest(dir) {
|
|
954
|
-
const manifest =
|
|
955
|
-
if (!
|
|
1162
|
+
const manifest = join5(dir, "package.json");
|
|
1163
|
+
if (!existsSync6(manifest)) return void 0;
|
|
956
1164
|
try {
|
|
957
|
-
return JSON.parse(
|
|
1165
|
+
return JSON.parse(readFileSync3(manifest, "utf8"));
|
|
958
1166
|
} catch {
|
|
959
1167
|
return void 0;
|
|
960
1168
|
}
|
|
@@ -972,11 +1180,11 @@ function readInstalledVersion(pkgRoot) {
|
|
|
972
1180
|
return readManifest(pkgRoot)?.version;
|
|
973
1181
|
}
|
|
974
1182
|
function detectInstaller(pkgRoot) {
|
|
975
|
-
const
|
|
976
|
-
if (
|
|
977
|
-
if (
|
|
978
|
-
if (
|
|
979
|
-
if (
|
|
1183
|
+
const path7 = pkgRoot.replaceAll("\\", "/");
|
|
1184
|
+
if (path7.includes("/pnpm/global/") || path7.includes("/.pnpm/")) return "pnpm";
|
|
1185
|
+
if (path7.includes("/install/global/") || path7.includes("/.bun/")) return "bun";
|
|
1186
|
+
if (path7.includes("/yarn/global/")) return "yarn";
|
|
1187
|
+
if (path7.includes("/node_modules/")) return "npm";
|
|
980
1188
|
return "unknown";
|
|
981
1189
|
}
|
|
982
1190
|
var COMMANDS = {
|
|
@@ -1068,7 +1276,7 @@ async function upgradeCommand() {
|
|
|
1068
1276
|
// src/index.ts
|
|
1069
1277
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
1070
1278
|
var __dirname2 = dirname4(__filename2);
|
|
1071
|
-
var pkg = JSON.parse(
|
|
1279
|
+
var pkg = JSON.parse(readFileSync4(join6(__dirname2, "../package.json"), "utf-8"));
|
|
1072
1280
|
var program = new Command();
|
|
1073
1281
|
program.name("multiclaw").description("Multi-agent orchestration CLI").version(pkg.version, "-v, --version", "Display version number").action((options) => tuiCommand(options));
|
|
1074
1282
|
program.command("init").description("Scaffold a config file (interactive)").action(() => initCommand());
|
|
@@ -1078,6 +1286,7 @@ program.command("run [requirement]").description("Run an orchestration headlessl
|
|
|
1078
1286
|
).action((requirement, opts) => runCommand(requirement, opts));
|
|
1079
1287
|
program.command("upgrade").description("Upgrade multiclaw to the latest version").action(() => upgradeCommand());
|
|
1080
1288
|
program.command("doctor").description("Check which agent providers are installed and ready").option("--config <path>", "Config file path (default: auto-discover ./multiclaw.config.ts)").action((opts) => doctorCommand(opts.config));
|
|
1289
|
+
program.command("prune").description("Clean up history entries and old run directories (interactive)").option("--config <path>", "Config file path (default: auto-discover ./multiclaw.config.ts)").option("--history", "Prune console history only (non-interactive)").option("--runs", "Prune all runs only (non-interactive)").option("--all", "Prune everything (non-interactive)").option("--dry-run", "Show what would be deleted without deleting").option("--yes", "Skip confirmation prompt").action((opts) => pruneCommand(opts));
|
|
1081
1290
|
program.parseAsync(process.argv).catch((err) => {
|
|
1082
1291
|
const message = err instanceof Error ? err.message : String(err);
|
|
1083
1292
|
console.error(`\x1B[31mError: ${message}\x1B[0m`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "multiclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"multiclaw": "dist/index.js"
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"@agentclientprotocol/claude-agent-acp": "^0.95.0",
|
|
31
31
|
"@agentclientprotocol/codex-acp": "^2.2.2",
|
|
32
32
|
"@inquirer/prompts": "^8.7.4",
|
|
33
|
-
"@multiclawcli/core": "0.
|
|
34
|
-
"@multiclawcli/dashboard": "0.
|
|
33
|
+
"@multiclawcli/core": "0.8.1",
|
|
34
|
+
"@multiclawcli/dashboard": "0.8.1",
|
|
35
35
|
"commander": "^15.0.0",
|
|
36
36
|
"jiti": "^2.7.0"
|
|
37
37
|
},
|