@mono-agent/agent-app 0.5.0 → 0.6.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.
Files changed (111) hide show
  1. package/README.md +5 -0
  2. package/dist/adapter-send-tools.d.ts +7 -7
  3. package/dist/adapter-send-tools.d.ts.map +1 -1
  4. package/dist/adapter-send-tools.js +59 -50
  5. package/dist/adapter-send-tools.js.map +1 -1
  6. package/dist/app.js +8 -8
  7. package/dist/app.js.map +1 -1
  8. package/dist/background.d.ts +4 -0
  9. package/dist/background.d.ts.map +1 -1
  10. package/dist/background.js +8 -3
  11. package/dist/background.js.map +1 -1
  12. package/dist/channel-fact-format.d.ts +12 -0
  13. package/dist/channel-fact-format.d.ts.map +1 -0
  14. package/dist/channel-fact-format.js +24 -0
  15. package/dist/channel-fact-format.js.map +1 -0
  16. package/dist/channels.js +3 -3
  17. package/dist/channels.js.map +1 -1
  18. package/dist/cli.d.ts +14 -10
  19. package/dist/cli.d.ts.map +1 -1
  20. package/dist/cli.js +198 -181
  21. package/dist/cli.js.map +1 -1
  22. package/dist/config-reference.js +4 -4
  23. package/dist/config-reference.js.map +1 -1
  24. package/dist/consumer-contract.d.ts.map +1 -1
  25. package/dist/consumer-contract.js +5 -3
  26. package/dist/consumer-contract.js.map +1 -1
  27. package/dist/doctor.d.ts.map +1 -1
  28. package/dist/doctor.js +126 -5
  29. package/dist/doctor.js.map +1 -1
  30. package/dist/init.d.ts +11 -22
  31. package/dist/init.d.ts.map +1 -1
  32. package/dist/init.js +25 -116
  33. package/dist/init.js.map +1 -1
  34. package/dist/interaction-bridge.d.ts +3 -3
  35. package/dist/interaction-bridge.d.ts.map +1 -1
  36. package/dist/memory-recall.d.ts +2 -2
  37. package/dist/memory-recall.d.ts.map +1 -1
  38. package/dist/memory-recall.js +3 -3
  39. package/dist/memory-recall.js.map +1 -1
  40. package/dist/modules/base.d.ts +25 -0
  41. package/dist/modules/base.d.ts.map +1 -0
  42. package/dist/{recipes → modules}/base.js +16 -11
  43. package/dist/modules/base.js.map +1 -0
  44. package/dist/modules/catalog.d.ts +12 -0
  45. package/dist/modules/catalog.d.ts.map +1 -0
  46. package/dist/modules/catalog.js +412 -0
  47. package/dist/modules/catalog.js.map +1 -0
  48. package/dist/modules/index.d.ts +8 -0
  49. package/dist/modules/index.d.ts.map +1 -0
  50. package/dist/modules/index.js +5 -0
  51. package/dist/modules/index.js.map +1 -0
  52. package/dist/modules/known-tools.d.ts +44 -0
  53. package/dist/modules/known-tools.d.ts.map +1 -0
  54. package/dist/modules/known-tools.js +88 -0
  55. package/dist/modules/known-tools.js.map +1 -0
  56. package/dist/modules/types.d.ts +73 -0
  57. package/dist/modules/types.d.ts.map +1 -0
  58. package/dist/{recipes → modules}/types.js +3 -3
  59. package/dist/{recipes → modules}/types.js.map +1 -1
  60. package/dist/posted-message-index.d.ts +2 -2
  61. package/dist/posted-message-index.js +2 -2
  62. package/dist/wizard/answers.d.ts +85 -0
  63. package/dist/wizard/answers.d.ts.map +1 -0
  64. package/dist/wizard/answers.js +231 -0
  65. package/dist/wizard/answers.js.map +1 -0
  66. package/dist/wizard/from-flags.d.ts +26 -0
  67. package/dist/wizard/from-flags.d.ts.map +1 -0
  68. package/dist/wizard/from-flags.js +44 -0
  69. package/dist/wizard/from-flags.js.map +1 -0
  70. package/dist/wizard/index.d.ts +11 -0
  71. package/dist/wizard/index.d.ts.map +1 -0
  72. package/dist/wizard/index.js +6 -0
  73. package/dist/wizard/index.js.map +1 -0
  74. package/dist/wizard/presets.d.ts +40 -0
  75. package/dist/wizard/presets.d.ts.map +1 -0
  76. package/dist/wizard/presets.js +85 -0
  77. package/dist/wizard/presets.js.map +1 -0
  78. package/dist/wizard/prompts.d.ts +52 -0
  79. package/dist/wizard/prompts.d.ts.map +1 -0
  80. package/dist/wizard/prompts.js +141 -0
  81. package/dist/wizard/prompts.js.map +1 -0
  82. package/dist/wizard/run.d.ts +19 -0
  83. package/dist/wizard/run.d.ts.map +1 -0
  84. package/dist/wizard/run.js +345 -0
  85. package/dist/wizard/run.js.map +1 -0
  86. package/package.json +17 -16
  87. package/schema/mono-agent.config.schema.json +5 -3
  88. package/skills/mono-agent-composer/SKILL.md +7 -7
  89. package/skills/mono-agent-composer/references/config-blueprint.md +7 -7
  90. package/skills/mono-agent-composer/references/discovery-questions.md +8 -6
  91. package/skills/mono-agent-composer/references/feature-coverage.md +10 -8
  92. package/skills/mono-agent-composer/references/package-map.md +5 -5
  93. package/skills/mono-agent-composer/references/playbooks.md +22 -22
  94. package/skills/mono-agent-composer/references/validation.md +3 -3
  95. package/dist/recipes/base.d.ts +0 -15
  96. package/dist/recipes/base.d.ts.map +0 -1
  97. package/dist/recipes/base.js.map +0 -1
  98. package/dist/recipes/catalog.d.ts +0 -4
  99. package/dist/recipes/catalog.d.ts.map +0 -1
  100. package/dist/recipes/catalog.js +0 -525
  101. package/dist/recipes/catalog.js.map +0 -1
  102. package/dist/recipes/index.d.ts +0 -11
  103. package/dist/recipes/index.d.ts.map +0 -1
  104. package/dist/recipes/index.js +0 -14
  105. package/dist/recipes/index.js.map +0 -1
  106. package/dist/recipes/types.d.ts +0 -70
  107. package/dist/recipes/types.d.ts.map +0 -1
  108. package/dist/setup.d.ts +0 -29
  109. package/dist/setup.d.ts.map +0 -1
  110. package/dist/setup.js +0 -97
  111. package/dist/setup.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { createInterface } from "node:readline/promises";
3
2
  import { stat } from "node:fs/promises";
4
3
  import { createRequire } from "node:module";
5
4
  import { basename, resolve } from "node:path";
@@ -14,16 +13,19 @@ import { runAuditRuns } from "./audit-runs.js";
14
13
  import { runBackfill } from "./backfill.js";
15
14
  import { defaultBackgroundDeps, resolveInstanceTarget, restartBackground, startBackground, statusBackground, stopBackground, tailLogs, } from "./background.js";
16
15
  import { collectChannelConfigViews } from "./channel-config-view.js";
16
+ import { formatChannelFactValue } from "./channel-fact-format.js";
17
17
  import { resolveChannelDrivers } from "./channels.js";
18
18
  import { findUnknownAppConfigWarnings } from "./config-reference.js";
19
19
  import { validateMonoAgentFolder } from "./doctor.js";
20
- import { initMonoAgentFolder, isWithChannel } from "./init.js";
20
+ import { initMonoAgentFolder } from "./init.js";
21
21
  import { installComposerSkill } from "./install-skill.js";
22
22
  import { runMetrics } from "./metrics.js";
23
- import { findRecipe, RECIPE_CATALOG, recipeIds, resolveRecipeInputs } from "./recipes/index.js";
24
23
  import { buildRunsHealthDisplay, RUNS_HEALTH_MAX_RUNS } from "./runs-health.js";
25
24
  import { purgeSessions } from "./sessions.js";
26
- import { collectSetupOptions } from "./setup.js";
25
+ import { composeWizardPlan } from "./wizard/answers.js";
26
+ import { answersFromCli, isWithChannel } from "./wizard/from-flags.js";
27
+ import { findPreset, PRESET_CATALOG, presetAnswers, presetIds, RECIPE_TO_PRESET } from "./wizard/presets.js";
28
+ import { runInitWizard } from "./wizard/run.js";
27
29
  import * as ui from "./ui.js";
28
30
  const DEFAULT_LOG_LINES = 200;
29
31
  // Node's maximum setInterval/setTimeout delay (2^31 - 1 ms, ~24.8 days). A
@@ -31,7 +33,7 @@ const DEFAULT_LOG_LINES = 200;
31
33
  // busy-waiting; larger values silently overflow to a 1ms delay.
32
34
  const KEEP_ALIVE_INTERVAL_MS = 2_147_483_647;
33
35
  const BACKGROUND_COMMANDS = ["start", "restart", "stop", "status", "logs"];
34
- const KNOWN_COMMANDS = ["init", "setup", "validate", "doctor", "config", "recipes", "start", "restart", "stop", "status", "logs", "tui", "web", "install-skill", "backfill", "audit-runs", "metrics", "memory"];
36
+ const KNOWN_COMMANDS = ["init", "setup", "validate", "doctor", "config", "recipes", "presets", "start", "restart", "stop", "status", "logs", "tui", "web", "install-skill", "backfill", "audit-runs", "metrics", "memory"];
35
37
  export function parseCliArgs(argv) {
36
38
  const [command, ...rest] = argv;
37
39
  if (command === undefined || command === "help" || command === "--help" || command === "-h") {
@@ -43,16 +45,25 @@ export function parseCliArgs(argv) {
43
45
  if (!KNOWN_COMMANDS.includes(command)) {
44
46
  throw new Error(`Unknown command \`${command}\`. Expected ${KNOWN_COMMANDS.join(", ")}.`);
45
47
  }
46
- // `doctor` is an alias of `validate`; normalize here so every downstream
47
- // validate path (env-file resolution, --consumer, routing) applies unchanged.
48
- const cmd = (command === "doctor" ? "validate" : command);
48
+ // `doctor`/`setup`/`recipes` are aliases; normalize here so every downstream
49
+ // path (routing, env-file resolution, --consumer) applies unchanged. `doctor`
50
+ // `validate`, `setup` `init`, `recipes` `presets`.
51
+ const cmd = (command === "doctor"
52
+ ? "validate"
53
+ : command === "setup"
54
+ ? "init"
55
+ : command === "recipes"
56
+ ? "presets"
57
+ : command);
49
58
  const isLogs = cmd === "logs";
50
59
  let configPath;
51
60
  let model;
52
61
  let fallbackModels;
53
62
  let memory;
63
+ let preset;
54
64
  let recipe;
55
65
  let withChannels;
66
+ let yes = false;
56
67
  const positionals = [];
57
68
  let envFile;
58
69
  let target;
@@ -191,9 +202,15 @@ export function parseCliArgs(argv) {
191
202
  memory = raw;
192
203
  break;
193
204
  }
205
+ case "--preset":
206
+ preset = requireValue(rest, ++i, flag);
207
+ break;
194
208
  case "--recipe":
195
209
  recipe = requireValue(rest, ++i, flag);
196
210
  break;
211
+ case "--yes":
212
+ yes = true;
213
+ break;
197
214
  case "--with":
198
215
  withChannels = requireValue(rest, ++i, flag)
199
216
  .split(",")
@@ -269,8 +286,10 @@ export function parseCliArgs(argv) {
269
286
  ...(model === undefined ? {} : { model }),
270
287
  ...(fallbackModels === undefined ? {} : { fallbackModels }),
271
288
  ...(memory === undefined ? {} : { memory }),
289
+ ...(preset === undefined ? {} : { preset }),
272
290
  ...(recipe === undefined ? {} : { recipe }),
273
291
  ...(withChannels === undefined ? {} : { withChannels }),
292
+ ...(yes ? { yes } : {}),
274
293
  positionals,
275
294
  ...(envFile === undefined ? {} : { envFile }),
276
295
  ...(target === undefined ? {} : { target }),
@@ -331,36 +350,32 @@ function requireValue(args, index, flag) {
331
350
  }
332
351
  const HELP_COMMANDS = [
333
352
  {
334
- signature: "mono-agent init [--recipe <id>] [--with <csv>] [--dry-run]\n" +
353
+ signature: "mono-agent init [--preset <id>] [--with <csv>] [--yes] [--dry-run]\n" +
335
354
  " [--model <ref>] [--fallback-models <csv>] [--memory lite|journal|bujo]",
336
355
  lines: [
337
- "Scaffold mono-agent.config.json, IDENTITY.md, and .mono-agent/ in the",
338
- "current folder. With --recipe, build from a blueprint (+ .env.example);",
339
- "--with adds channels, --dry-run previews. Existing files are never overwritten.",
356
+ "Scaffold a mono-agent in the current folder. On a TTY with no flags, launches",
357
+ "the step-by-step wizard; with --yes or any flag, writes the default/preset",
358
+ "scaffold non-interactively. --preset seeds a blueprint, --with adds channels,",
359
+ "--dry-run previews. Existing files are never overwritten.",
340
360
  ],
341
361
  },
342
362
  {
343
- signature: "mono-agent setup [--recipe <id>] [--with <csv>] [--dry-run]\n" +
344
- " [--model <ref>] [--fallback-models <csv>] [--memory lite|journal|bujo]",
345
- lines: [
346
- "Guided setup when attached to a TTY: choose a recipe, answer non-secret",
347
- "inputs, select channel add-ons, scaffold, validate, and print the secrets",
348
- "checklist. In non-TTY contexts it falls back to init-style flags.",
349
- ],
363
+ signature: "mono-agent setup",
364
+ lines: ["Alias of `init`."],
350
365
  },
351
366
  {
352
- signature: "mono-agent recipes list | show <id>",
367
+ signature: "mono-agent presets list | show <id>",
353
368
  lines: [
354
- "List the executable config blueprints, or show one's generated config,",
369
+ "List the built-in setup presets, or show one's generated config,",
355
370
  ".env.example, and follow-up checklist.",
356
371
  ],
357
372
  },
358
373
  {
359
- signature: "mono-agent validate [--recipe <id>] [--consumer <path>] [--config <path>] [--env-file <path>]",
374
+ signature: "mono-agent validate [--preset <id>] [--consumer <path>] [--config <path>] [--env-file <path>]",
360
375
  lines: [
361
376
  "Load every config section and report what would run, wait, or fail.",
362
377
  "--consumer validates another agent folder read-only, including its .env.",
363
- "With --recipe, also report whether the recipe's capabilities are live.",
378
+ "With --preset, also report whether the preset's capabilities are live.",
364
379
  "`mono-agent doctor` is an alias for this command.",
365
380
  ],
366
381
  },
@@ -535,14 +550,12 @@ export async function runCli(argv) {
535
550
  return 0;
536
551
  case "init":
537
552
  return await runInit(args);
538
- case "setup":
539
- return await runSetup(args);
540
553
  case "validate":
541
554
  return await runValidate(args);
542
555
  case "config":
543
556
  return await runConfig(args);
544
- case "recipes":
545
- return runRecipes(args);
557
+ case "presets":
558
+ return runPresets(args);
546
559
  case "start":
547
560
  return await runStart(args);
548
561
  case "restart":
@@ -622,93 +635,83 @@ export async function runCli(argv) {
622
635
  }
623
636
  }
624
637
  async function runInit(args) {
625
- const recipe = resolveRecipeArg(args);
626
- if (recipe === "unknown") {
638
+ // On an interactive TTY with no overriding flags, walk the step-by-step wizard;
639
+ // any flag (or a piped/non-TTY invocation) takes the silent default/preset path.
640
+ const wantsWizard = process.stdin.isTTY === true && process.stdout.isTTY === true
641
+ && args.yes !== true && args.preset === undefined && args.recipe === undefined
642
+ && args.model === undefined && args.fallbackModels === undefined
643
+ && args.memory === undefined && args.withChannels === undefined && args.dryRun !== true;
644
+ if (wantsWizard) {
645
+ // Existing-config pre-check — don't walk the wizard into a guaranteed no-op.
646
+ if (await pathExists(resolve(process.cwd(), "mono-agent.config.json"))) {
647
+ process.stdout.write(ui.hint("Found an existing mono-agent.config.json — `mono-agent init` never overwrites. Run `mono-agent validate`, or start in an empty folder.\n"));
648
+ return 0;
649
+ }
650
+ const outcome = await runInitWizard({ cwd: process.cwd() });
651
+ if (outcome.status === "cancelled") {
652
+ return 1;
653
+ }
654
+ const result = await initMonoAgentFolder({ dir: process.cwd(), answers: outcome.answers });
655
+ printInitResult(result);
656
+ const report = await validateMonoAgentFolder({ env: process.env, cwd: process.cwd(), configPath: result.configPath });
657
+ process.stdout.write("\n" + ui.heading("Validation"));
658
+ for (const section of report.sections) {
659
+ process.stdout.write(formatSection(section));
660
+ }
661
+ process.stdout.write(renderPlanCompleteness(result.plan.validateExpectations, "Selected capabilities", report));
662
+ printSecretsChecklist(result.plan.secrets);
663
+ printNextSteps(result.configPath);
664
+ return report.ok ? 0 : 1;
665
+ }
666
+ const presetId = resolveInitPresetId(args);
667
+ if (presetId === "unknown") {
627
668
  return 1;
628
669
  }
629
670
  const withChannels = resolveWithChannels(args);
630
671
  if (withChannels === "invalid") {
631
672
  return 1;
632
673
  }
633
- const result = await initMonoAgentFolder({
634
- dir: process.cwd(),
674
+ const answers = answersFromCli({
635
675
  ...(args.model === undefined ? {} : { model: args.model }),
636
676
  ...(args.fallbackModels === undefined ? {} : { fallbackModels: args.fallbackModels }),
637
677
  ...(args.memory === undefined ? {} : { memory: args.memory }),
638
- ...(recipe === undefined ? {} : { recipe }),
639
678
  ...(withChannels === undefined ? {} : { withChannels }),
640
- dryRun: args.dryRun,
679
+ ...(presetId === undefined ? {} : { presetId }),
641
680
  });
642
- printInitResult(result, recipe);
643
- printNextSteps(result.configPath, recipe);
681
+ const result = await initMonoAgentFolder({ dir: process.cwd(), answers, dryRun: args.dryRun });
682
+ printInitResult(result);
683
+ printSecretsChecklist(result.plan.secrets);
684
+ printNextSteps(result.configPath);
644
685
  return 0;
645
686
  }
646
- async function runSetup(args) {
647
- if (process.stdin.isTTY !== true) {
648
- return await runInit(args);
649
- }
650
- const recipeArg = resolveRecipeArg(args);
651
- if (recipeArg === "unknown") {
652
- return 1;
653
- }
654
- const withChannelsArg = resolveWithChannels(args);
655
- if (withChannelsArg === "invalid") {
656
- return 1;
657
- }
658
- const rl = createInterface({ input: process.stdin, output: process.stdout });
659
- let collected;
660
- try {
661
- collected = await collectSetupOptions({
662
- prompt: { question: (prompt) => rl.question(prompt) },
663
- ...(recipeArg === undefined ? {} : { recipe: recipeArg }),
664
- ...(args.model === undefined ? {} : { model: args.model }),
665
- ...(args.fallbackModels === undefined ? {} : { fallbackModels: args.fallbackModels }),
666
- ...(withChannelsArg === undefined ? {} : { withChannels: withChannelsArg }),
667
- });
668
- }
669
- finally {
670
- rl.close();
671
- }
672
- const result = await initMonoAgentFolder({
673
- dir: process.cwd(),
674
- ...(collected.fallbackModels.length === 0 ? {} : { fallbackModels: collected.fallbackModels }),
675
- ...(args.memory === undefined ? {} : { memory: args.memory }),
676
- recipe: collected.recipe,
677
- recipeInputs: collected.recipeInputs,
678
- withChannels: collected.withChannels,
679
- dryRun: args.dryRun,
680
- });
681
- printInitResult(result, collected.recipe);
682
- if (!result.dryRun) {
683
- const report = await validateMonoAgentFolder({
684
- env: process.env,
685
- cwd: process.cwd(),
686
- configPath: result.configPath,
687
- });
688
- process.stdout.write("\n" + ui.heading("Validation"));
689
- for (const section of report.sections) {
690
- process.stdout.write(formatSection(section));
687
+ /**
688
+ * Resolve the preset id for `init`: `--preset` wins, `--recipe` is a deprecated
689
+ * alias mapped to the preset that replaced it. Returns the preset id to compose
690
+ * from, `undefined` for the default scaffold, or `"unknown"` after emitting the
691
+ * error/hint (an unknown preset, or a retired recipe with no replacement).
692
+ */
693
+ function resolveInitPresetId(args) {
694
+ if (args.preset !== undefined) {
695
+ const preset = findPreset(args.preset);
696
+ if (preset === undefined) {
697
+ process.stderr.write(ui.errorLine(`Unknown preset \`${args.preset}\`.`));
698
+ process.stderr.write(ui.hint(`Available presets: ${presetIds().join(", ")}. Run \`mono-agent presets list\`.`));
699
+ return "unknown";
691
700
  }
692
- process.stdout.write(renderRecipeCompleteness(collected.recipe, report));
693
- printSecretsChecklist(collected.secrets);
694
- printNextSteps(result.configPath, collected.recipe);
695
- return report.ok ? 0 : 1;
696
- }
697
- printSecretsChecklist(collected.secrets);
698
- printNextSteps(result.configPath, collected.recipe);
699
- return 0;
700
- }
701
- function resolveRecipeArg(args) {
702
- if (args.recipe === undefined) {
703
- return undefined;
701
+ return preset.id;
704
702
  }
705
- const recipe = findRecipe(args.recipe);
706
- if (recipe === undefined) {
707
- process.stderr.write(ui.errorLine(`Unknown recipe \`${args.recipe}\`.`));
708
- process.stderr.write(ui.hint("Run `mono-agent recipes list` to see available recipes."));
709
- return "unknown";
703
+ if (args.recipe !== undefined) {
704
+ const preset = RECIPE_TO_PRESET.get(args.recipe);
705
+ if (preset === undefined) {
706
+ process.stderr.write(ui.errorLine(`Recipe \`${args.recipe}\` was retired; the wizard composes capabilities directly.`));
707
+ process.stderr.write(ui.hint(`Use \`mono-agent init --preset <id>\` (${presetIds().join(", ")}), or \`mono-agent init\` for the step-by-step wizard.`));
708
+ process.stderr.write(ui.hint("See the mono-agent-composer skill and docs/playbooks for capability recipes."));
709
+ return "unknown";
710
+ }
711
+ process.stderr.write(ui.hint(`--recipe is deprecated; using preset ${preset.id}. See \`mono-agent presets list\`.`));
712
+ return preset.id;
710
713
  }
711
- return recipe;
714
+ return undefined;
712
715
  }
713
716
  function resolveWithChannels(args) {
714
717
  if (args.withChannels === undefined) {
@@ -722,7 +725,7 @@ function resolveWithChannels(args) {
722
725
  }
723
726
  return args.withChannels.filter(isWithChannel);
724
727
  }
725
- function printInitResult(result, recipe) {
728
+ function printInitResult(result) {
726
729
  if (result.dryRun) {
727
730
  process.stdout.write(ui.style.dim("Dry run — nothing was written.\n"));
728
731
  }
@@ -736,31 +739,35 @@ function printInitResult(result, recipe) {
736
739
  if (result.knowledgeFiles.length > 0) {
737
740
  process.stdout.write(`\nIdentity references existing knowledge: ${ui.style.cyan(result.knowledgeFiles.join(", "))}\n`);
738
741
  }
739
- if (recipe !== undefined) {
740
- process.stdout.write(`\n${ui.style.bold("Recipe:")} ${ui.style.cyan(recipe.id)} ${ui.style.dim(`(risk: ${recipe.riskLevel})`)}\n`);
741
- if (recipe.envExample !== undefined) {
742
- process.stdout.write(ui.style.dim("Fill the secret placeholders in .env.example, then copy it to .env.\n"));
742
+ // Internal `provider:*` modules are auto-added for local models; they are an
743
+ // implementation detail, not a user-facing capability, so exclude them here.
744
+ const capabilities = result.plan.selectedModules.filter((module) => module.kind !== "provider");
745
+ if (capabilities.length > 0) {
746
+ process.stdout.write("\n" + ui.heading("Capabilities"));
747
+ for (const module of capabilities) {
748
+ process.stdout.write(` ${ui.style.cyan(module.title)} ${ui.style.dim(`(risk: ${riskColor(module.riskLevel)})`)}\n`);
743
749
  }
744
750
  }
751
+ if (result.plan.envExample !== undefined) {
752
+ process.stdout.write("\n" + ui.style.dim("Fill the secret placeholders in .env.example, then copy it to .env.\n"));
753
+ }
745
754
  }
746
755
  function printSecretsChecklist(secrets) {
747
756
  process.stdout.write("\n" + ui.heading("Secrets checklist"));
748
757
  if (secrets.length === 0) {
749
- process.stdout.write(ui.style.dim("No secret recipe inputs were prompted. Review .env.example if the recipe created one.\n"));
758
+ process.stdout.write(ui.style.dim("No secrets required by the selected capabilities.\n"));
750
759
  return;
751
760
  }
752
- process.stdout.write(ui.style.dim("Copy .env.example to .env, then fill these variables. Secret values were not prompted or written to JSON.\n"));
761
+ process.stdout.write(ui.style.dim("Copy .env.example to .env, then fill these variables. Secret values are never written to JSON.\n"));
753
762
  for (const secret of secrets) {
754
- const key = secret.envVar ?? secret.id;
755
- process.stdout.write(` ${ui.style.bold(key)} ${ui.style.dim(`- ${secret.label}: ${secret.description}`)}\n`);
763
+ process.stdout.write(` ${ui.style.bold(secret.envVar)} ${ui.style.dim(`- ${secret.label}: ${secret.description}`)}\n`);
756
764
  }
757
765
  }
758
- function printNextSteps(configPath, recipe) {
759
- const validateCmd = recipe === undefined ? "mono-agent validate" : `mono-agent validate --recipe ${recipe.id}`;
766
+ function printNextSteps(configPath) {
760
767
  process.stdout.write("\n" +
761
768
  ui.heading("Next steps") +
762
769
  ` ${ui.style.bold("1.")} Edit ${configPath} ${ui.style.dim("(model, channels, skills, memory, sandbox)")}\n` +
763
- ` ${ui.style.bold("2.")} ${validateCmd}\n` +
770
+ ` ${ui.style.bold("2.")} mono-agent validate\n` +
764
771
  ` ${ui.style.bold("3.")} mono-agent start\n`);
765
772
  }
766
773
  async function runInstallSkill(args) {
@@ -791,14 +798,16 @@ async function runValidate(args) {
791
798
  for (const section of report.sections) {
792
799
  process.stdout.write(formatSection(section));
793
800
  }
794
- if (args.recipe !== undefined) {
795
- const recipe = findRecipe(args.recipe);
796
- if (recipe === undefined) {
797
- process.stderr.write(ui.errorLine(`Unknown recipe \`${args.recipe}\`.`));
798
- process.stderr.write(ui.hint("Run `mono-agent recipes list` to see available recipes."));
799
- return 1;
800
- }
801
- process.stdout.write(renderRecipeCompleteness(recipe, report));
801
+ const preset = resolveValidatePreset(args);
802
+ if (preset === "unknown") {
803
+ return 1;
804
+ }
805
+ if (preset !== undefined) {
806
+ const plan = composeWizardPlan(presetAnswers(preset), {
807
+ dirBasename: basename(context.cwd),
808
+ skillsRootExists: false,
809
+ });
810
+ process.stdout.write(renderPlanCompleteness(plan.validateExpectations, `Preset: ${preset.id}`, report));
802
811
  }
803
812
  process.stdout.write(report.ok
804
813
  ? `\n${ui.style.green("✓ Config is ready to start.")}\n${ui.style.dim("Run `mono-agent config` for the full field-by-field view.")}\n`
@@ -807,15 +816,42 @@ async function runValidate(args) {
807
816
  return report.ok ? 0 : 1;
808
817
  }
809
818
  /**
810
- * Capability-aware recipe check: for each capability the recipe promises, report
811
- * whether the matching doctor section has reached the expected status. `waiting`
812
- * stays non-fatal (it never changes the validate exit code) but is surfaced as
813
- * "selected recipe incomplete" so the operator knows what is left to wire up.
819
+ * Resolve the preset to check `validate` against: `--preset` wins, `--recipe` is a
820
+ * deprecated alias. Returns the preset, `undefined` (no capability check), or
821
+ * `"unknown"` after emitting the error/hint.
814
822
  */
815
- function renderRecipeCompleteness(recipe, report) {
816
- let out = "\n" + ui.heading(`Recipe: ${recipe.id}`);
823
+ function resolveValidatePreset(args) {
824
+ if (args.preset !== undefined) {
825
+ const preset = findPreset(args.preset);
826
+ if (preset === undefined) {
827
+ process.stderr.write(ui.errorLine(`Unknown preset \`${args.preset}\`.`));
828
+ process.stderr.write(ui.hint(`Available presets: ${presetIds().join(", ")}. Run \`mono-agent presets list\`.`));
829
+ return "unknown";
830
+ }
831
+ return preset;
832
+ }
833
+ if (args.recipe !== undefined) {
834
+ const preset = RECIPE_TO_PRESET.get(args.recipe);
835
+ if (preset === undefined) {
836
+ process.stderr.write(ui.errorLine(`Recipe \`${args.recipe}\` was retired; validate against a preset instead.`));
837
+ process.stderr.write(ui.hint(`Available presets: ${presetIds().join(", ")}. Run \`mono-agent presets list\`.`));
838
+ return "unknown";
839
+ }
840
+ process.stderr.write(ui.hint(`--recipe is deprecated; using preset ${preset.id}. See \`mono-agent presets list\`.`));
841
+ return preset;
842
+ }
843
+ return undefined;
844
+ }
845
+ /**
846
+ * Capability-aware completeness check: for each capability a preset (or module
847
+ * set) promises, report whether the matching doctor section reached the expected
848
+ * status. `waiting` stays non-fatal (it never changes the validate exit code) but
849
+ * is surfaced as "incomplete" so the operator knows what is left to wire up.
850
+ */
851
+ function renderPlanCompleteness(expectations, label, report) {
852
+ let out = "\n" + ui.heading(label);
817
853
  let incomplete = 0;
818
- for (const expectation of recipe.validateExpectations) {
854
+ for (const expectation of expectations) {
819
855
  const section = report.sections.find((entry) => entry.id === expectation.sectionId);
820
856
  const status = section?.status ?? "missing";
821
857
  const met = status === expectation.mustBe;
@@ -829,8 +865,8 @@ function renderRecipeCompleteness(recipe, report) {
829
865
  }
830
866
  }
831
867
  out += incomplete === 0
832
- ? `${ui.style.green(`✓ Recipe ${recipe.id} is fully configured.`)}\n`
833
- : ui.style.yellow(`⚠ Selected recipe incomplete: ${incomplete} capability(ies) not yet live.\n`);
868
+ ? `${ui.style.green(`✓ ${label} is fully configured.`)}\n`
869
+ : ui.style.yellow(`⚠ ${label} incomplete: ${incomplete} capability(ies) not yet live.\n`);
834
870
  return out;
835
871
  }
836
872
  function riskColor(risk) {
@@ -842,72 +878,72 @@ function riskColor(risk) {
842
878
  }
843
879
  return ui.style.green(risk);
844
880
  }
845
- /** `mono-agent recipes list` — one line per recipe. */
846
- export function renderRecipeList() {
847
- let out = ui.banner("mono-agent", "recipes") + "\n";
848
- for (const recipe of RECIPE_CATALOG) {
849
- out += `${ui.style.bold(ui.style.cyan(recipe.id))} ${ui.style.dim(`[${riskColor(recipe.riskLevel)}]`)}\n`;
850
- out += ` ${recipe.title}\n`;
851
- out += ` ${ui.style.dim(recipe.tags.join(", "))}\n`;
852
- }
853
- out += "\n" + ui.style.dim("Scaffold one with: mono-agent init --recipe <id> [--dry-run] [--with slack,cron]\n");
881
+ /** `mono-agent presets list` — one block per preset. */
882
+ export function renderPresetList() {
883
+ let out = ui.banner("mono-agent", "presets") + "\n";
884
+ for (const preset of PRESET_CATALOG) {
885
+ out += `${ui.style.bold(ui.style.cyan(preset.id))} ${ui.style.dim(`[${riskColor(preset.riskLevel)}]`)}\n`;
886
+ out += ` ${preset.title}\n`;
887
+ out += ` ${ui.style.dim(preset.description)}\n`;
888
+ }
889
+ out += "\n" + ui.style.dim("Scaffold one with: mono-agent init --preset <id>\n");
890
+ out += ui.style.dim("Build interactively with: mono-agent init\n");
854
891
  return out;
855
892
  }
856
- /** `mono-agent recipes show <id>` — description, generated JSON, env example, checklist. */
857
- export function renderRecipeShow(recipe) {
858
- const inputs = resolveRecipeInputs(recipe);
859
- let out = ui.banner("mono-agent", `recipe: ${recipe.id}`) + "\n";
860
- out += `${ui.style.bold(recipe.title)} ${ui.style.dim(`(risk: ${riskColor(recipe.riskLevel)})`)}\n`;
861
- out += `${recipe.description}\n`;
862
- if (recipe.playbook !== undefined) {
863
- out += ui.style.dim(`Playbook: docs/playbooks/${recipe.playbook}\n`);
893
+ /** `mono-agent presets show <id>` — description, composed JSON, env example, checklist. */
894
+ export function renderPresetShow(preset) {
895
+ const plan = composeWizardPlan(presetAnswers(preset), { dirBasename: "your-agent", skillsRootExists: false });
896
+ let out = ui.banner("mono-agent", `preset: ${preset.id}`) + "\n";
897
+ out += `${ui.style.bold(preset.title)} ${ui.style.dim(`(risk: ${riskColor(preset.riskLevel)})`)}\n`;
898
+ out += `${preset.description}\n`;
899
+ if (preset.playbook !== undefined) {
900
+ out += ui.style.dim(`Playbook: docs/playbooks/${preset.playbook}\n`);
864
901
  }
865
902
  out += "\n" + ui.heading("Generated mono-agent.config.json");
866
- out += JSON.stringify(recipe.config(inputs), null, 2) + "\n";
867
- const envExample = recipe.envExample?.(inputs);
903
+ out += JSON.stringify(plan.configJson, null, 2) + "\n";
904
+ const envExample = plan.envExample;
868
905
  if (envExample !== undefined && envExample.trim().length > 0) {
869
906
  out += "\n" + ui.heading(".env.example");
870
907
  out += envExample.endsWith("\n") ? envExample : envExample + "\n";
871
908
  }
872
- const files = recipe.files?.(inputs) ?? [];
873
- if (files.length > 0) {
909
+ if (plan.files.length > 0) {
874
910
  out += "\n" + ui.heading("Scaffolded files");
875
- for (const file of files) {
911
+ for (const file of plan.files) {
876
912
  out += ` ${ui.style.cyan(file.path)}\n`;
877
913
  }
878
914
  }
879
- if (recipe.validateExpectations.length > 0) {
915
+ if (plan.validateExpectations.length > 0) {
880
916
  out += "\n" + ui.heading("Follow-up checklist");
881
- for (const expectation of recipe.validateExpectations) {
917
+ for (const expectation of plan.validateExpectations) {
882
918
  const note = expectation.note === undefined ? "" : ` — ${expectation.note}`;
883
919
  out += ` ${ui.style.gray("•")} ${expectation.sectionId} ${ui.style.dim(`must be ${expectation.mustBe}`)}${ui.style.dim(note)}\n`;
884
920
  }
885
921
  }
886
922
  return out;
887
923
  }
888
- /** Dispatch `mono-agent recipes list|show <id>`. */
889
- function runRecipes(args) {
924
+ /** Dispatch `mono-agent presets list|show <id>`. */
925
+ function runPresets(args) {
890
926
  const [sub, id] = args.positionals;
891
927
  if (sub === undefined || sub === "list") {
892
- process.stdout.write(renderRecipeList());
928
+ process.stdout.write(renderPresetList());
893
929
  return 0;
894
930
  }
895
931
  if (sub === "show") {
896
932
  if (id === undefined) {
897
- process.stderr.write(ui.errorLine("Usage: mono-agent recipes show <id>."));
898
- process.stderr.write(ui.hint(`Available: ${recipeIds().join(", ")}.`));
933
+ process.stderr.write(ui.errorLine("Usage: mono-agent presets show <id>."));
934
+ process.stderr.write(ui.hint(`Available: ${presetIds().join(", ")}.`));
899
935
  return 2;
900
936
  }
901
- const recipe = findRecipe(id);
902
- if (recipe === undefined) {
903
- process.stderr.write(ui.errorLine(`Unknown recipe \`${id}\`.`));
904
- process.stderr.write(ui.hint("Run `mono-agent recipes list` to see available recipes."));
937
+ const preset = findPreset(id);
938
+ if (preset === undefined) {
939
+ process.stderr.write(ui.errorLine(`Unknown preset \`${id}\`.`));
940
+ process.stderr.write(ui.hint(`Available: ${presetIds().join(", ")}. Run \`mono-agent presets list\`.`));
905
941
  return 1;
906
942
  }
907
- process.stdout.write(renderRecipeShow(recipe));
943
+ process.stdout.write(renderPresetShow(preset));
908
944
  return 0;
909
945
  }
910
- process.stderr.write(ui.errorLine(`Unknown recipes subcommand \`${sub}\`. Expected list or show.`));
946
+ process.stderr.write(ui.errorLine(`Unknown presets subcommand \`${sub}\`. Expected list or show.`));
911
947
  return 2;
912
948
  }
913
949
  const SOURCE_TAG = {
@@ -1267,25 +1303,6 @@ export function describeChannelStatus(status) {
1267
1303
  }
1268
1304
  return `${status.kind}: ${status.reason}`;
1269
1305
  }
1270
- /**
1271
- * Render a channel-status summary value for the human-readable status line.
1272
- * Objects and arrays are expanded to `key: value` pairs instead of the useless
1273
- * `[object Object]` that a bare `String()` produced (E4: the webhook summary's
1274
- * `invokeUrls` map printed `invokeUrls=[object Object]`).
1275
- */
1276
- function formatChannelFactValue(value) {
1277
- if (value === null || typeof value !== "object") {
1278
- return String(value);
1279
- }
1280
- if (Array.isArray(value)) {
1281
- return `[${value.map(formatChannelFactValue).join(", ")}]`;
1282
- }
1283
- const entries = Object.entries(value);
1284
- if (entries.length === 0) {
1285
- return "{}";
1286
- }
1287
- return `{${entries.map(([key, inner]) => `${key}: ${formatChannelFactValue(inner)}`).join(", ")}}`;
1288
- }
1289
1306
  /**
1290
1307
  * Block the foreground process until SIGINT/SIGTERM, then stop the app and
1291
1308
  * resolve the exit code. A referenced no-op timer owns the event loop so the