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.
@@ -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(): Promise<void>;
18
+ export declare function initCommand(options?: {
19
+ force?: boolean;
20
+ }): Promise<void>;
@@ -1,6 +1,6 @@
1
1
  export interface PruneOptions {
2
2
  config?: string;
3
- history?: boolean;
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: "history" | "run" | "all-runs";
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>;
@@ -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 dirname4, join as join6 } from "path";
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 { checkbox, input, Separator, select } from "@inquirer/prompts";
236
- import { detectProviders as detectProviders2, specForProvider as specForProvider2, tierModel } from "@multiclawcli/core";
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
- ${leaderLine}
275
+ leader: agents.leader(),
345
276
  context: {
346
277
  projectName: ${name},
347
278
  },
348
279
  defaults: {
349
- ${defaultsParts.join(",\n ")},
280
+ provider: ${JSON.stringify(opts.provider)},
350
281
  },
351
282
  agents: [
352
- ${agentBlock}
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
- console.error("\x1B[31mError: multiclaw.config.ts already exists\x1B[0m");
361
- process.exit(1);
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 as checkbox2, confirm } from "@inquirer/prompts";
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 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"
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 === "history") {
531
- fs3.unlinkSync(item.path);
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.history && options.runs) {
462
+ if (options.sessions && options.runs) {
559
463
  return items.map((i) => i.id);
560
464
  }
561
- if (options.history) {
562
- return items.filter((i) => i.kind === "history").map((i) => i.id);
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 history or runs found.");
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 checkbox2({
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 sessions = {};
944
+ const sessions2 = {};
1001
945
  for (const result of latest.values()) {
1002
- if (result.sessionId) sessions[result.agentId] = 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(sessions).length > 0 ? { sessions } : {}
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 dirname3, join as join5, resolve as resolve4 } from "path";
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 = dirname3(dir);
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(dirname3(fileURLToPath(import.meta.url)));
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 = dirname4(__filename2);
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").action((options) => tuiCommand(options));
1282
- program.command("init").description("Scaffold a config file (interactive)").action(() => initCommand());
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 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));
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.9.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.9.0",
34
- "@multiclawcli/dashboard": "0.9.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
  },