multiclaw 0.9.0 → 0.10.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/init.d.ts +3 -15
- package/dist/commands/prune.d.ts +2 -2
- package/dist/commands/sessions.d.ts +13 -0
- package/dist/commands/tui.d.ts +4 -0
- package/dist/index.js +153 -150
- package/package.json +3 -3
package/dist/commands/init.d.ts
CHANGED
|
@@ -9,24 +9,12 @@
|
|
|
9
9
|
* `agents.<id>(` in the block string.
|
|
10
10
|
*/
|
|
11
11
|
export declare const PRESET_AGENT_IDS: Record<string, string[]>;
|
|
12
|
-
/**
|
|
13
|
-
* The override choices for a preset: the leader (which belongs to no preset)
|
|
14
|
-
* plus the preset's own agents. Filtering by preset is what stops an override
|
|
15
|
-
* for an agent the preset does not contain from being offered and then dropped.
|
|
16
|
-
*/
|
|
17
|
-
export declare function overrideChoices(preset: string): Array<{
|
|
18
|
-
value: string;
|
|
19
|
-
name: string;
|
|
20
|
-
}>;
|
|
21
12
|
export declare function generateConfig(opts: {
|
|
22
13
|
preset: string;
|
|
23
14
|
workDir: string;
|
|
24
15
|
projectName: string;
|
|
25
16
|
provider: string;
|
|
26
|
-
model?: string;
|
|
27
|
-
overrides?: Array<{
|
|
28
|
-
id: string;
|
|
29
|
-
model: string;
|
|
30
|
-
}>;
|
|
31
17
|
}): string;
|
|
32
|
-
export declare function initCommand(
|
|
18
|
+
export declare function initCommand(options?: {
|
|
19
|
+
force?: boolean;
|
|
20
|
+
}): Promise<void>;
|
package/dist/commands/prune.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export interface PruneOptions {
|
|
2
2
|
config?: string;
|
|
3
|
-
|
|
3
|
+
sessions?: boolean;
|
|
4
4
|
runs?: boolean;
|
|
5
5
|
all?: boolean;
|
|
6
6
|
dryRun?: boolean;
|
|
@@ -11,7 +11,7 @@ export interface PruneItem {
|
|
|
11
11
|
label: string;
|
|
12
12
|
path: string;
|
|
13
13
|
size: number;
|
|
14
|
-
kind: "
|
|
14
|
+
kind: "session" | "run" | "all-runs";
|
|
15
15
|
}
|
|
16
16
|
/** Scan `.multiclaw/` and list prune-able items with their sizes. */
|
|
17
17
|
export declare function scanPruneItems(workDir: string): PruneItem[];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface SessionsCommandOptions {
|
|
2
|
+
config?: string;
|
|
3
|
+
}
|
|
4
|
+
/** `multiclaw sessions ls` — list saved sessions, newest first. */
|
|
5
|
+
export declare function sessionsListCommand(options: SessionsCommandOptions): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* `multiclaw sessions rm <id>` — delete a session by id or a unique prefix.
|
|
8
|
+
*
|
|
9
|
+
* A prefix is accepted because the ids are UUIDs no one types in full. An
|
|
10
|
+
* ambiguous prefix is refused rather than guessed, so a `rm` never deletes a
|
|
11
|
+
* session the user did not mean.
|
|
12
|
+
*/
|
|
13
|
+
export declare function sessionsRemoveCommand(id: string, options: SessionsCommandOptions): Promise<void>;
|
package/dist/commands/tui.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export interface TuiCommandOptions {
|
|
2
2
|
config?: string;
|
|
3
|
+
/** Start a fresh session instead of continuing the most recent one. */
|
|
4
|
+
new?: boolean;
|
|
5
|
+
/** Resume a session: a specific id, or `true` to open the picker. */
|
|
6
|
+
resume?: string | boolean;
|
|
3
7
|
}
|
|
4
8
|
export declare function tuiCommand(options: TuiCommandOptions): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { readFileSync as readFileSync4 } from "fs";
|
|
5
|
-
import { dirname as
|
|
5
|
+
import { dirname as dirname5, join as join6 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
|
|
@@ -232,8 +232,8 @@ async function loadConfigSafe(configPath) {
|
|
|
232
232
|
// src/commands/init.ts
|
|
233
233
|
import * as fs2 from "fs";
|
|
234
234
|
import * as path2 from "path";
|
|
235
|
-
import {
|
|
236
|
-
import { detectProviders as detectProviders2, specForProvider as specForProvider2
|
|
235
|
+
import { input, select } from "@inquirer/prompts";
|
|
236
|
+
import { detectProviders as detectProviders2, specForProvider as specForProvider2 } from "@multiclawcli/core";
|
|
237
237
|
var AGENT_BLOCKS = {
|
|
238
238
|
backend: ` agents.productManager(),
|
|
239
239
|
agents.architect({ dependsOn: ["product-manager"] }),
|
|
@@ -264,101 +264,37 @@ var PRESET_DESCRIPTIONS = {
|
|
|
264
264
|
frontend: "Architect \u2192 UI Designer \u2192 UI Design Reviewer \u2192 Frontend Developer \u2192 Tester \u2192 Code Reviewer (6 agents)",
|
|
265
265
|
fullstack: "Product Manager \u2192 Architect \u2192 Arch Design Reviewer \u2192 Backend Developer + UI Designer \u2192 UI Design Reviewer \u2192 Frontend Developer \u2192 Tester \u2192 Code Reviewer + DevOps (10 agents)"
|
|
266
266
|
};
|
|
267
|
-
var PRESET_AGENT_IDS = {
|
|
268
|
-
backend: [
|
|
269
|
-
"productManager",
|
|
270
|
-
"architect",
|
|
271
|
-
"archDesignReviewer",
|
|
272
|
-
"backendDeveloper",
|
|
273
|
-
"tester",
|
|
274
|
-
"codeReviewer",
|
|
275
|
-
"devops"
|
|
276
|
-
],
|
|
277
|
-
frontend: [
|
|
278
|
-
"architect",
|
|
279
|
-
"uiDesigner",
|
|
280
|
-
"uiDesignReviewer",
|
|
281
|
-
"frontendDeveloper",
|
|
282
|
-
"tester",
|
|
283
|
-
"codeReviewer"
|
|
284
|
-
],
|
|
285
|
-
fullstack: [
|
|
286
|
-
"productManager",
|
|
287
|
-
"architect",
|
|
288
|
-
"archDesignReviewer",
|
|
289
|
-
"backendDeveloper",
|
|
290
|
-
"uiDesigner",
|
|
291
|
-
"uiDesignReviewer",
|
|
292
|
-
"frontendDeveloper",
|
|
293
|
-
"tester",
|
|
294
|
-
"codeReviewer",
|
|
295
|
-
"devops"
|
|
296
|
-
]
|
|
297
|
-
};
|
|
298
|
-
var OVERRIDE_LABELS = {
|
|
299
|
-
leader: "\u2605 Leader",
|
|
300
|
-
productManager: "Product Manager",
|
|
301
|
-
architect: "Architect",
|
|
302
|
-
archDesignReviewer: "Arch Design Reviewer",
|
|
303
|
-
backendDeveloper: "Backend Developer",
|
|
304
|
-
frontendDeveloper: "Frontend Developer",
|
|
305
|
-
uiDesigner: "UI Designer",
|
|
306
|
-
uiDesignReviewer: "UI Design Reviewer",
|
|
307
|
-
tester: "Tester",
|
|
308
|
-
codeReviewer: "Code Reviewer",
|
|
309
|
-
devops: "DevOps"
|
|
310
|
-
};
|
|
311
|
-
function overrideChoices(preset) {
|
|
312
|
-
const ids = PRESET_AGENT_IDS[preset] ?? [];
|
|
313
|
-
return [
|
|
314
|
-
{ value: "leader", name: OVERRIDE_LABELS.leader },
|
|
315
|
-
...ids.map((id) => ({ value: id, name: OVERRIDE_LABELS[id] ?? id }))
|
|
316
|
-
];
|
|
317
|
-
}
|
|
318
267
|
function generateConfig(opts) {
|
|
319
268
|
const name = JSON.stringify(opts.projectName);
|
|
320
269
|
const workDir = JSON.stringify(opts.workDir);
|
|
321
|
-
const defaultsParts = [`provider: ${JSON.stringify(opts.provider)}`];
|
|
322
|
-
if (opts.model) defaultsParts.push(`model: ${JSON.stringify(opts.model)}`);
|
|
323
|
-
const leaderOverride = opts.overrides?.find((o) => o.id === "leader");
|
|
324
|
-
const leaderLine = leaderOverride ? ` leader: agents.leader({ model: ${JSON.stringify(leaderOverride.model)} }),` : ` leader: agents.leader(),`;
|
|
325
|
-
let agentBlock = AGENT_BLOCKS[opts.preset];
|
|
326
|
-
if (opts.overrides && opts.overrides.length > 0) {
|
|
327
|
-
for (const ov of opts.overrides) {
|
|
328
|
-
const id = ov.id;
|
|
329
|
-
if (id === "leader") continue;
|
|
330
|
-
const model = JSON.stringify(ov.model);
|
|
331
|
-
const re = new RegExp(`(agents\\.${id}\\([^)]*\\))`, "g");
|
|
332
|
-
agentBlock = agentBlock.replace(re, (match) => {
|
|
333
|
-
const body = match.slice(id.length + 8, -1).trim().replace(/^\{/, "").replace(/\}$/, "").trim();
|
|
334
|
-
const sep = body ? ", " : "";
|
|
335
|
-
return `agents.${id}({ ${body}${sep}model: ${model} })`;
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
270
|
return `import { defineConfig, agents } from "multiclaw"
|
|
340
271
|
|
|
341
272
|
export default defineConfig({
|
|
342
273
|
name: ${name},
|
|
343
274
|
workDir: ${workDir},
|
|
344
|
-
|
|
275
|
+
leader: agents.leader(),
|
|
345
276
|
context: {
|
|
346
277
|
projectName: ${name},
|
|
347
278
|
},
|
|
348
279
|
defaults: {
|
|
349
|
-
${
|
|
280
|
+
provider: ${JSON.stringify(opts.provider)},
|
|
350
281
|
},
|
|
351
282
|
agents: [
|
|
352
|
-
${
|
|
283
|
+
${AGENT_BLOCKS[opts.preset]}
|
|
353
284
|
],
|
|
354
285
|
})
|
|
355
286
|
`;
|
|
356
287
|
}
|
|
357
|
-
async function initCommand() {
|
|
288
|
+
async function initCommand(options = {}) {
|
|
358
289
|
const configPath = path2.resolve(process.cwd(), "multiclaw.config.ts");
|
|
359
290
|
if (fs2.existsSync(configPath)) {
|
|
360
|
-
|
|
361
|
-
|
|
291
|
+
if (!options.force) {
|
|
292
|
+
console.error(
|
|
293
|
+
"\x1B[31mError: multiclaw.config.ts already exists (use --force to overwrite)\x1B[0m"
|
|
294
|
+
);
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
console.log("\x1B[33mWarning: overwriting existing multiclaw.config.ts\x1B[0m");
|
|
362
298
|
}
|
|
363
299
|
const defaultProjectName = path2.basename(process.cwd());
|
|
364
300
|
const projectName = await input({
|
|
@@ -386,45 +322,12 @@ async function initCommand() {
|
|
|
386
322
|
if (!spec) {
|
|
387
323
|
throw new Error(`Unknown provider: ${provider}`);
|
|
388
324
|
}
|
|
389
|
-
const modelAnswer = await select({
|
|
390
|
-
message: "Team model preference:",
|
|
391
|
-
choices: [
|
|
392
|
-
{ value: "", name: "Role-based tier defaults (recommended)" },
|
|
393
|
-
new Separator("---"),
|
|
394
|
-
...[
|
|
395
|
-
{ label: "Strong (architect, devops)", tier: "strong" },
|
|
396
|
-
{ label: "Balanced (backend, frontend)", tier: "balanced" },
|
|
397
|
-
{ label: "Fast (reviewer, tester)", tier: "fast" }
|
|
398
|
-
].map((t) => {
|
|
399
|
-
const m = tierModel(spec, t.tier);
|
|
400
|
-
return m ? { value: m, name: `${t.label} \u2192 ${m}` } : void 0;
|
|
401
|
-
}).filter((x) => x !== void 0),
|
|
402
|
-
new Separator("---"),
|
|
403
|
-
...spec.models.map((m) => ({ value: m.id, name: m.label }))
|
|
404
|
-
]
|
|
405
|
-
});
|
|
406
|
-
const modelDefault = modelAnswer || void 0;
|
|
407
|
-
const overrideIds = await checkbox({
|
|
408
|
-
message: "Override model for specific agents? (space to select, enter to confirm)",
|
|
409
|
-
choices: overrideChoices(preset)
|
|
410
|
-
});
|
|
411
|
-
const overrides = [];
|
|
412
|
-
for (const id of overrideIds) {
|
|
413
|
-
const ov = await select({
|
|
414
|
-
message: `Model for ${id}:`.replace(/([A-Z])/g, " $1").replace(/^./, (c) => c.toUpperCase()),
|
|
415
|
-
choices: spec.models.map((m) => ({ value: m.id, name: m.label }))
|
|
416
|
-
});
|
|
417
|
-
overrides.push({ id, model: ov });
|
|
418
|
-
}
|
|
419
325
|
const workDir = await input({
|
|
420
326
|
message: "Work directory (where agents read and write files):",
|
|
421
327
|
default: "."
|
|
422
328
|
});
|
|
423
329
|
try {
|
|
424
|
-
fs2.writeFileSync(
|
|
425
|
-
configPath,
|
|
426
|
-
generateConfig({ preset, workDir, projectName, provider, model: modelDefault, overrides })
|
|
427
|
-
);
|
|
330
|
+
fs2.writeFileSync(configPath, generateConfig({ preset, workDir, projectName, provider }));
|
|
428
331
|
if (workDir !== ".") {
|
|
429
332
|
fs2.mkdirSync(path2.resolve(process.cwd(), workDir), { recursive: true });
|
|
430
333
|
}
|
|
@@ -442,11 +345,7 @@ async function initCommand() {
|
|
|
442
345
|
` ${detectedProvider.command}${detectedProvider.version ? ` ${detectedProvider.version}` : ""}`
|
|
443
346
|
);
|
|
444
347
|
}
|
|
445
|
-
console.log(` model: ${modelDefault || "role-based tier defaults"}`);
|
|
446
348
|
console.log(` workDir: ${workDir}`);
|
|
447
|
-
if (overrides.length > 0) {
|
|
448
|
-
console.log(` overrides: ${overrides.map((o) => `${o.id}=${o.model}`).join(", ")}`);
|
|
449
|
-
}
|
|
450
349
|
console.log("");
|
|
451
350
|
if (detectedProvider?.status === "missing") {
|
|
452
351
|
console.log(`\x1B[33m\u26A0 ${spec.label} is not installed.\x1B[0m`);
|
|
@@ -471,28 +370,32 @@ async function initCommand() {
|
|
|
471
370
|
// src/commands/prune.ts
|
|
472
371
|
import * as fs3 from "fs";
|
|
473
372
|
import * as path3 from "path";
|
|
474
|
-
import { checkbox
|
|
373
|
+
import { checkbox, confirm } from "@inquirer/prompts";
|
|
475
374
|
function scanPruneItems(workDir) {
|
|
476
375
|
const items = [];
|
|
477
376
|
const multiclawDir = path3.join(workDir, ".multiclaw");
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
377
|
+
const sessionsDir = path3.join(multiclawDir, "sessions");
|
|
378
|
+
const index = readSessionIndex(sessionsDir);
|
|
379
|
+
const metaById = new Map(index.map((meta) => [meta.id, meta]));
|
|
380
|
+
const onDisk = fs3.existsSync(sessionsDir) ? fs3.readdirSync(sessionsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
|
|
381
|
+
const sessionItems2 = [];
|
|
382
|
+
for (const id of /* @__PURE__ */ new Set([...metaById.keys(), ...onDisk])) {
|
|
383
|
+
const dir = path3.join(sessionsDir, id);
|
|
384
|
+
const size = fs3.existsSync(dir) ? dirSize(dir) : 0;
|
|
385
|
+
const meta = metaById.get(id);
|
|
386
|
+
const title = meta?.title && meta.title.trim() !== "" ? meta.title : "(untitled)";
|
|
387
|
+
const turns = meta?.turnCount ?? 0;
|
|
388
|
+
sessionItems2.push({
|
|
389
|
+
id,
|
|
390
|
+
label: `${title} (${turns} turn${turns === 1 ? "" : "s"}, ${formatBytes(size)})`,
|
|
391
|
+
path: dir,
|
|
392
|
+
size,
|
|
393
|
+
kind: "session",
|
|
394
|
+
lastActiveAt: meta?.lastActiveAt ?? 0
|
|
494
395
|
});
|
|
495
396
|
}
|
|
397
|
+
sessionItems2.sort((a, b) => b.lastActiveAt - a.lastActiveAt);
|
|
398
|
+
for (const { lastActiveAt: _unused, ...item } of sessionItems2) items.push(item);
|
|
496
399
|
const runsDir = path3.join(multiclawDir, "runs");
|
|
497
400
|
if (fs3.existsSync(runsDir)) {
|
|
498
401
|
const entries = fs3.readdirSync(runsDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("run-")).sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -527,8 +430,9 @@ function deletePruneItems(items) {
|
|
|
527
430
|
const failed = [];
|
|
528
431
|
for (const item of items) {
|
|
529
432
|
try {
|
|
530
|
-
if (item.kind === "
|
|
531
|
-
fs3.
|
|
433
|
+
if (item.kind === "session") {
|
|
434
|
+
fs3.rmSync(item.path, { recursive: true, force: true });
|
|
435
|
+
removeSessionFromIndex(path3.dirname(item.path), path3.basename(item.path));
|
|
532
436
|
deleted.push(item.id);
|
|
533
437
|
} else if (item.kind === "run") {
|
|
534
438
|
fs3.rmSync(item.path, { recursive: true, force: true });
|
|
@@ -555,11 +459,11 @@ function resolvePruneTargets(options, items) {
|
|
|
555
459
|
if (options.all) {
|
|
556
460
|
return items.map((i) => i.id);
|
|
557
461
|
}
|
|
558
|
-
if (options.
|
|
462
|
+
if (options.sessions && options.runs) {
|
|
559
463
|
return items.map((i) => i.id);
|
|
560
464
|
}
|
|
561
|
-
if (options.
|
|
562
|
-
return items.filter((i) => i.kind === "
|
|
465
|
+
if (options.sessions) {
|
|
466
|
+
return items.filter((i) => i.kind === "session").map((i) => i.id);
|
|
563
467
|
}
|
|
564
468
|
if (options.runs) {
|
|
565
469
|
return items.filter((i) => i.kind === "run" || i.kind === "all-runs").map((i) => i.id);
|
|
@@ -570,7 +474,7 @@ async function pruneCommand(options) {
|
|
|
570
474
|
const config = await loadConfig(options.config);
|
|
571
475
|
const items = scanPruneItems(config.workDir);
|
|
572
476
|
if (items.length === 0) {
|
|
573
|
-
console.log("Nothing to prune \u2014 no
|
|
477
|
+
console.log("Nothing to prune \u2014 no sessions or runs found.");
|
|
574
478
|
return;
|
|
575
479
|
}
|
|
576
480
|
let selectedIds;
|
|
@@ -582,7 +486,7 @@ async function pruneCommand(options) {
|
|
|
582
486
|
value: item.id,
|
|
583
487
|
name: item.label
|
|
584
488
|
}));
|
|
585
|
-
selectedIds = await
|
|
489
|
+
selectedIds = await checkbox({
|
|
586
490
|
message: "Select items to prune (space to select, enter to confirm):",
|
|
587
491
|
choices
|
|
588
492
|
});
|
|
@@ -635,6 +539,46 @@ async function pruneCommand(options) {
|
|
|
635
539
|
process.exitCode = 1;
|
|
636
540
|
}
|
|
637
541
|
}
|
|
542
|
+
function readSessionIndex(sessionsDir) {
|
|
543
|
+
try {
|
|
544
|
+
const parsed = JSON.parse(
|
|
545
|
+
fs3.readFileSync(path3.join(sessionsDir, "index.json"), "utf-8")
|
|
546
|
+
);
|
|
547
|
+
if (!Array.isArray(parsed)) return [];
|
|
548
|
+
const entries = [];
|
|
549
|
+
for (const value of parsed) {
|
|
550
|
+
if (typeof value !== "object" || value === null) continue;
|
|
551
|
+
const entry = value;
|
|
552
|
+
if (typeof entry.id !== "string") continue;
|
|
553
|
+
entries.push({
|
|
554
|
+
id: entry.id,
|
|
555
|
+
title: typeof entry.title === "string" ? entry.title : "",
|
|
556
|
+
turnCount: typeof entry.turnCount === "number" ? entry.turnCount : 0,
|
|
557
|
+
lastActiveAt: typeof entry.lastActiveAt === "number" ? entry.lastActiveAt : 0
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
return entries;
|
|
561
|
+
} catch {
|
|
562
|
+
return [];
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function removeSessionFromIndex(sessionsDir, id) {
|
|
566
|
+
const indexPath = path3.join(sessionsDir, "index.json");
|
|
567
|
+
let parsed;
|
|
568
|
+
try {
|
|
569
|
+
parsed = JSON.parse(fs3.readFileSync(indexPath, "utf-8"));
|
|
570
|
+
} catch {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (!Array.isArray(parsed)) return;
|
|
574
|
+
const next = parsed.filter(
|
|
575
|
+
(entry) => !(typeof entry === "object" && entry !== null && entry.id === id)
|
|
576
|
+
);
|
|
577
|
+
if (next.length === parsed.length) return;
|
|
578
|
+
const tmp = `${indexPath}.tmp`;
|
|
579
|
+
fs3.writeFileSync(tmp, JSON.stringify(next), "utf-8");
|
|
580
|
+
fs3.renameSync(tmp, indexPath);
|
|
581
|
+
}
|
|
638
582
|
function formatBytes(bytes) {
|
|
639
583
|
if (bytes === 0) return "0 B";
|
|
640
584
|
const k = 1024;
|
|
@@ -997,9 +941,9 @@ function resumeOptionsFromReport(report, requirementOverride) {
|
|
|
997
941
|
if (agents2.length === 0) {
|
|
998
942
|
throw new Error("The previous run has no agent results \u2014 nothing to resume");
|
|
999
943
|
}
|
|
1000
|
-
const
|
|
944
|
+
const sessions2 = {};
|
|
1001
945
|
for (const result of latest.values()) {
|
|
1002
|
-
if (result.sessionId)
|
|
946
|
+
if (result.sessionId) sessions2[result.agentId] = result.sessionId;
|
|
1003
947
|
}
|
|
1004
948
|
const requirement = requirementOverride ?? report.requirement;
|
|
1005
949
|
if (requirement === void 0) {
|
|
@@ -1011,7 +955,7 @@ function resumeOptionsFromReport(report, requirementOverride) {
|
|
|
1011
955
|
agents: agents2,
|
|
1012
956
|
// Absent, not `{}`: core reads a missing `sessions` as "start every agent
|
|
1013
957
|
// fresh", which is exactly the single-agent retry of a failed agent.
|
|
1014
|
-
...Object.keys(
|
|
958
|
+
...Object.keys(sessions2).length > 0 ? { sessions: sessions2 } : {}
|
|
1015
959
|
}
|
|
1016
960
|
};
|
|
1017
961
|
}
|
|
@@ -1128,13 +1072,67 @@ function walkFiles(root, visit) {
|
|
|
1128
1072
|
}
|
|
1129
1073
|
}
|
|
1130
1074
|
|
|
1075
|
+
// src/commands/sessions.ts
|
|
1076
|
+
function sessionItems(workDir) {
|
|
1077
|
+
return scanPruneItems(workDir).filter((item) => item.kind === "session");
|
|
1078
|
+
}
|
|
1079
|
+
async function sessionsListCommand(options) {
|
|
1080
|
+
const config = await loadConfig(options.config);
|
|
1081
|
+
const items = sessionItems(config.workDir);
|
|
1082
|
+
if (items.length === 0) {
|
|
1083
|
+
console.log("No saved sessions.");
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
for (const item of items) {
|
|
1087
|
+
console.log(` \x1B[2m${item.id}\x1B[0m ${item.label}`);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
async function sessionsRemoveCommand(id, options) {
|
|
1091
|
+
const config = await loadConfig(options.config);
|
|
1092
|
+
const items = sessionItems(config.workDir);
|
|
1093
|
+
const exact = items.find((item) => item.id === id);
|
|
1094
|
+
const matches = exact ? [exact] : items.filter((item) => item.id.startsWith(id));
|
|
1095
|
+
if (matches.length === 0) {
|
|
1096
|
+
console.error(`No session matching "${id}".`);
|
|
1097
|
+
process.exitCode = 1;
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
if (matches.length > 1) {
|
|
1101
|
+
console.error(
|
|
1102
|
+
`"${id}" matches ${matches.length} sessions \u2014 use more of the id:
|
|
1103
|
+
${matches.map((m) => ` ${m.id}`).join("\n")}`
|
|
1104
|
+
);
|
|
1105
|
+
process.exitCode = 1;
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
const { deleted, failed } = deletePruneItems(matches);
|
|
1109
|
+
if (deleted.length > 0) {
|
|
1110
|
+
console.log(`\x1B[32m\u2713\x1B[0m Removed session ${matches[0].id}`);
|
|
1111
|
+
}
|
|
1112
|
+
if (failed.length > 0) {
|
|
1113
|
+
console.error(`\x1B[31m\u2717\x1B[0m Failed to remove ${matches[0].id}: ${failed[0].error}`);
|
|
1114
|
+
process.exitCode = 1;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1131
1118
|
// src/commands/tui.ts
|
|
1119
|
+
function initialSession(options) {
|
|
1120
|
+
if (options.new) return { kind: "new" };
|
|
1121
|
+
if (options.resume !== void 0) {
|
|
1122
|
+
return {
|
|
1123
|
+
kind: "resume",
|
|
1124
|
+
...typeof options.resume === "string" ? { id: options.resume } : {}
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
return void 0;
|
|
1128
|
+
}
|
|
1132
1129
|
async function tuiCommand(options) {
|
|
1133
1130
|
const config = await loadConfig(options.config);
|
|
1134
1131
|
await assertRuntimesAvailable(config);
|
|
1135
1132
|
const { startTui } = await import("@multiclawcli/dashboard/tui");
|
|
1136
1133
|
await startTui({
|
|
1137
1134
|
config,
|
|
1135
|
+
initialSession: initialSession(options),
|
|
1138
1136
|
onTurnStart: (eventBus, logDir) => {
|
|
1139
1137
|
const writer = new FileLogWriter(logDir);
|
|
1140
1138
|
const detach = writer.attach(eventBus);
|
|
@@ -1152,7 +1150,7 @@ async function tuiCommand(options) {
|
|
|
1152
1150
|
// src/commands/upgrade.ts
|
|
1153
1151
|
import { execSync } from "child_process";
|
|
1154
1152
|
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
|
|
1155
|
-
import { dirname as
|
|
1153
|
+
import { dirname as dirname4, join as join5, resolve as resolve4 } from "path";
|
|
1156
1154
|
import { fileURLToPath } from "url";
|
|
1157
1155
|
var PACKAGE_NAME = "multiclaw";
|
|
1158
1156
|
var RED2 = "\x1B[31m";
|
|
@@ -1171,7 +1169,7 @@ function findPackageRoot(start) {
|
|
|
1171
1169
|
let dir = resolve4(start);
|
|
1172
1170
|
for (; ; ) {
|
|
1173
1171
|
if (readManifest(dir)?.name === PACKAGE_NAME) return dir;
|
|
1174
|
-
const parent =
|
|
1172
|
+
const parent = dirname4(dir);
|
|
1175
1173
|
if (parent === dir) return void 0;
|
|
1176
1174
|
dir = parent;
|
|
1177
1175
|
}
|
|
@@ -1202,7 +1200,7 @@ var MANUAL = {
|
|
|
1202
1200
|
unknown: "npm install -g multiclaw@latest"
|
|
1203
1201
|
};
|
|
1204
1202
|
function createUpgradeContext() {
|
|
1205
|
-
const pkgRoot = findPackageRoot(
|
|
1203
|
+
const pkgRoot = findPackageRoot(dirname4(fileURLToPath(import.meta.url)));
|
|
1206
1204
|
if (!pkgRoot) return void 0;
|
|
1207
1205
|
const currentVersion = readInstalledVersion(pkgRoot);
|
|
1208
1206
|
if (!currentVersion) return void 0;
|
|
@@ -1275,18 +1273,23 @@ async function upgradeCommand() {
|
|
|
1275
1273
|
|
|
1276
1274
|
// src/index.ts
|
|
1277
1275
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
1278
|
-
var __dirname2 =
|
|
1276
|
+
var __dirname2 = dirname5(__filename2);
|
|
1279
1277
|
var pkg = JSON.parse(readFileSync4(join6(__dirname2, "../package.json"), "utf-8"));
|
|
1280
1278
|
var program = new Command();
|
|
1281
|
-
program.name("multiclaw").description("Multi-agent orchestration CLI").version(pkg.version, "-v, --version", "Display version number").
|
|
1282
|
-
|
|
1279
|
+
program.name("multiclaw").description("Multi-agent orchestration CLI").version(pkg.version, "-v, --version", "Display version number").option("--config <path>", "Config file path (default: auto-discover ./multiclaw.config.ts)").option("--new", "Start a new session instead of continuing the most recent").option("-r, --resume [id]", "Resume a session by id, or open the session picker").action(
|
|
1280
|
+
(options) => tuiCommand(options)
|
|
1281
|
+
);
|
|
1282
|
+
program.command("init").description("Scaffold a config file (interactive)").option("--force", "Overwrite existing multiclaw.config.ts").action((opts) => initCommand(opts));
|
|
1283
1283
|
program.command("run [requirement]").description("Run an orchestration headlessly").option("--config <path>", "Config file path (default: auto-discover ./multiclaw.config.ts)").option("--no-leader", "Batch mode only: skip routing and run the full team").option("--dry-run", "Preview the routed plan without running the pipeline").option(
|
|
1284
1284
|
"--resume [path]",
|
|
1285
1285
|
"Resume a previous run, continuing agent sessions (default: the latest)"
|
|
1286
1286
|
).action((requirement, opts) => runCommand(requirement, opts));
|
|
1287
1287
|
program.command("upgrade").description("Upgrade multiclaw to the latest version").action(() => upgradeCommand());
|
|
1288
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
|
|
1289
|
+
program.command("prune").description("Clean up saved sessions and old run directories (interactive)").option("--config <path>", "Config file path (default: auto-discover ./multiclaw.config.ts)").option("--sessions", "Prune saved sessions 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));
|
|
1290
|
+
var sessions = program.command("sessions").description("Manage saved console sessions");
|
|
1291
|
+
sessions.command("ls").description("List saved sessions, newest first").option("--config <path>", "Config file path (default: auto-discover ./multiclaw.config.ts)").action((opts) => sessionsListCommand(opts));
|
|
1292
|
+
sessions.command("rm <id>").description("Delete a saved session by id (or a unique prefix)").option("--config <path>", "Config file path (default: auto-discover ./multiclaw.config.ts)").action((id, opts) => sessionsRemoveCommand(id, opts));
|
|
1290
1293
|
program.parseAsync(process.argv).catch((err) => {
|
|
1291
1294
|
const message = err instanceof Error ? err.message : String(err);
|
|
1292
1295
|
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.10.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.10.1",
|
|
34
|
+
"@multiclawcli/dashboard": "0.10.1",
|
|
35
35
|
"commander": "^15.0.0",
|
|
36
36
|
"jiti": "^2.7.0"
|
|
37
37
|
},
|