wizz-method 2.12.0 → 2.13.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.
@@ -0,0 +1,154 @@
1
+ /**
2
+ * One function per guided screen. Each returns plain data; the order and the
3
+ * loop (change profile, customize) live in flow.js.
4
+ */
5
+ const os = require('node:os');
6
+ const fs = require('../fs-native');
7
+ const prompts = require('../prompts');
8
+ const { paint, BRAND_ORANGE } = require('../banner');
9
+ const { t, LANGUAGES } = require('./messages');
10
+ const { PROFILES } = require('./profiles');
11
+ const { AREA_LABELS, cliBadges, cliDescription } = require('./labels');
12
+ const { detectAiTools } = require('./detect-tools');
13
+
14
+ const TOTAL_STEPS = 4;
15
+ const OTHER_TOOLS = '__other__';
16
+
17
+ async function stepHeader(lang, n) {
18
+ const color = await prompts.getColor();
19
+ const colorOn = color.isColorSupported !== false;
20
+ const track = Array.from({ length: TOTAL_STEPS }, (_, i) => (i < n ? '●' : '○')).join('───');
21
+ await prompts.log.message(`${paint(BRAND_ORANGE, track, colorOn)} ${color.dim(t(lang, 'step', { n, total: TOTAL_STEPS }))}`);
22
+ }
23
+
24
+ async function askLanguage() {
25
+ return prompts.select({
26
+ message: t('en', 'languageQuestion'),
27
+ choices: LANGUAGES.map((l) => ({ name: l.label, value: l.value })),
28
+ default: 'en',
29
+ });
30
+ }
31
+
32
+ async function askDirectory(ui, lang) {
33
+ for (;;) {
34
+ const raw = await prompts.directory({
35
+ message: t(lang, 'dirQuestion'),
36
+ default: process.cwd(),
37
+ placeholder: process.cwd(),
38
+ validate: (input) => ui.validateDirectorySync(input),
39
+ });
40
+ const directory = raw && raw.trim() ? ui.expandUserPath(raw) : process.cwd();
41
+ if (await fs.pathExists(directory)) return directory;
42
+ const create = await prompts.confirm({ message: t(lang, 'dirCreate', { dir: directory }), default: true });
43
+ if (create) return directory;
44
+ await prompts.log.warn(t(lang, 'dirRetry'));
45
+ }
46
+ }
47
+
48
+ async function askProfile(lang, current) {
49
+ return prompts.select({
50
+ message: t(lang, 'profileQuestion'),
51
+ choices: PROFILES.map((p) => ({ name: p[lang].name, value: p.id, hint: p[lang].help })),
52
+ default: current || 'everything',
53
+ });
54
+ }
55
+
56
+ async function askAreas(lang, registry) {
57
+ const keys = Object.keys((registry && registry.areas) || {});
58
+ const selected = await prompts.multiselect({
59
+ message: t(lang, 'areasQuestion'),
60
+ options: keys.map((key) => {
61
+ const label = AREA_LABELS[key] ? AREA_LABELS[key][lang] : { name: key, help: '' };
62
+ return { label: label.name, value: key, hint: label.help };
63
+ }),
64
+ initialValues: keys,
65
+ required: true,
66
+ });
67
+ return selected.length === keys.length ? [] : selected;
68
+ }
69
+
70
+ async function askModules(lang, available) {
71
+ const selected = await prompts.multiselect({
72
+ message: t(lang, 'modulesQuestion'),
73
+ options: available.map((mod) => ({
74
+ label: mod.fromBmad ? `${mod.name} · ${t(lang, 'fromBmad')}` : mod.name,
75
+ value: mod.code,
76
+ hint: mod.description,
77
+ })),
78
+ initialValues: available.filter((mod) => mod.defaultSelected).map((mod) => mod.code),
79
+ required: true,
80
+ });
81
+ return selected;
82
+ }
83
+
84
+ async function askTools(lang, ideManager) {
85
+ const preferred = ideManager.getPreferredIdes();
86
+ const others = ideManager.getOtherIdes();
87
+ const detected = new Set(detectAiTools());
88
+ const initial = preferred.filter((ide) => detected.has(ide.value)).map((ide) => ide.value);
89
+ const picked = await prompts.multiselect({
90
+ message: t(lang, 'toolQuestion'),
91
+ options: [
92
+ ...preferred.map((ide) => ({
93
+ label: ide.name,
94
+ value: ide.value,
95
+ hint: detected.has(ide.value) ? t(lang, 'toolDetected') : undefined,
96
+ })),
97
+ { label: t(lang, 'toolOther'), value: OTHER_TOOLS, hint: t(lang, 'toolOtherHelp') },
98
+ ],
99
+ initialValues: initial.length > 0 ? initial : [preferred[0].value],
100
+ required: true,
101
+ });
102
+ const chosen = picked.filter((id) => id !== OTHER_TOOLS);
103
+ if (!picked.includes(OTHER_TOOLS)) return chosen;
104
+ const extra = await prompts.autocompleteMultiselect({
105
+ message: t(lang, 'toolOtherQuestion'),
106
+ options: others.map((ide) => ({ label: ide.name, value: ide.value })),
107
+ required: chosen.length === 0,
108
+ maxItems: 8,
109
+ });
110
+ return [...chosen, ...(extra || [])];
111
+ }
112
+
113
+ function defaultUserName() {
114
+ let name;
115
+ try {
116
+ name = os.userInfo().username;
117
+ } catch {
118
+ name = process.env.USER || process.env.USERNAME || 'User';
119
+ }
120
+ return name.charAt(0).toUpperCase() + name.slice(1);
121
+ }
122
+
123
+ async function askName(lang) {
124
+ const fallback = defaultUserName();
125
+ const value = await prompts.text({ message: t(lang, 'nameQuestion'), default: fallback, placeholder: fallback });
126
+ return (value && String(value).trim()) || fallback;
127
+ }
128
+
129
+ /**
130
+ * Label and hint for a CLI in the optional-tools list: plain description,
131
+ * then requirement badges ("free" when there are none).
132
+ */
133
+ function cliOption(cli, lang) {
134
+ const badges = cliBadges(cli, lang);
135
+ const needs = badges.length > 0 ? badges.join(' · ') : t(lang, 'free');
136
+ const description = cliDescription(cli.id, lang);
137
+ // Description in the label, not the hint: clack only shows a hint for the
138
+ // focused row, and the point here is comparing tools at a glance.
139
+ const label = description ? `${cli.id.padEnd(14)} ${description} · ${needs}` : `${cli.id.padEnd(14)} ${needs}`;
140
+ return { label, value: cli.id };
141
+ }
142
+
143
+ module.exports = {
144
+ stepHeader,
145
+ askLanguage,
146
+ askDirectory,
147
+ askProfile,
148
+ askAreas,
149
+ askModules,
150
+ askTools,
151
+ askName,
152
+ cliOption,
153
+ defaultUserName,
154
+ };
@@ -6,11 +6,11 @@
6
6
  startMessage: |
7
7
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
8
8
 
9
- Método de agência orientado por IA, em PT-BR. Agentes por área, skills curadas e memória de projeto.
10
- Escolha os módulos durante o setup para montar a instalação do seu jeito.
9
+ Full install: choices come from your flags or from the complete question list.
10
+ For the short guided install, run `npx wizz-method install` with no flags.
11
11
 
12
12
  📖 Docs: https://method.wizzcomms.com/
13
- ✉️ Suporte: support@wizzcomms.com
13
+ ✉️ Support: support@wizzcomms.com
14
14
 
15
15
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
16
16
 
@@ -145,6 +145,10 @@ class ExternalModuleManager {
145
145
  marketplacePlugin: mod.marketplace_plugin === true || mod['marketplace-plugin'] === true || mod.marketplacePlugin === true,
146
146
  postInstallMessage: mod.post_install_message || mod['post-install-message'] || mod.postInstallMessage || null,
147
147
  builtIn: mod.built_in === true,
148
+ // Só aparece no modo avançado do instalador guiado (--advanced ou a
149
+ // lista completa de módulos); experimental ou de nicho demais pra
150
+ // quem instala pela primeira vez.
151
+ advancedOnly: mod.advanced_only === true,
148
152
  isExternal: mod.built_in !== true,
149
153
  // Códigos anteriores sob os quais este módulo foi registrado (ex.: `bmad-loop`
150
154
  // era `bauto`). Deixa um módulo renomeado continuar resolvendo instalações
@@ -27,6 +27,9 @@ const { resolveMcps, partitionGloballyConfigured } = require('./modules/mcp-conf
27
27
  const { resolveClis, detectClis } = require('./modules/cli-config');
28
28
  const prompts = require('./prompts');
29
29
  const { parseSetEntries } = require('./set-overrides');
30
+ const { isGuidedEligible, runGuidedInstall } = require('./guided/flow');
31
+ const guidedSteps = require('./guided/steps');
32
+ const { normalizeLang, t: guidedT } = require('./guided/messages');
30
33
 
31
34
  const manifest = new Manifest();
32
35
 
@@ -232,12 +235,21 @@ class UI {
232
235
  * @returns {Object} Installation configuration
233
236
  */
234
237
  async promptInstall(options = {}) {
235
- await CLIUtils.displayLogo();
236
-
237
- // Display version-specific start message from install-messages.yaml
238
- const { MessageLoader } = require('./message-loader');
239
- const messageLoader = new MessageLoader();
240
- await messageLoader.displayStartMessage();
238
+ // Guided path (2026-09-25): first install in an interactive terminal with
239
+ // no scripting flags. The opening screen lists its four steps and the
240
+ // language comes first; everything else keeps the legacy flow.
241
+ const guided = isGuidedEligible(options);
242
+ let lang = normalizeLang(options.lang) || 'en';
243
+ await CLIUtils.displayLogo({ steps: guided, lang, version: installerPackageJson.version });
244
+
245
+ if (guided) {
246
+ if (!normalizeLang(options.lang)) lang = await guidedSteps.askLanguage();
247
+ } else {
248
+ // Display version-specific start message from install-messages.yaml
249
+ const { MessageLoader } = require('./message-loader');
250
+ const messageLoader = new MessageLoader();
251
+ await messageLoader.displayStartMessage();
252
+ }
241
253
 
242
254
  // Probe the local Python before any other prompts. Since audit item 29
243
255
  // (2026-09-12) this is INFORMATIONAL only: Node is the sole prerequisite
@@ -285,6 +297,9 @@ class UI {
285
297
  }
286
298
  confirmedDirectory = expandedDir;
287
299
  await prompts.log.info(`Using directory from command-line: ${confirmedDirectory}`);
300
+ } else if (guided) {
301
+ await guidedSteps.stepHeader(lang, 1);
302
+ confirmedDirectory = await guidedSteps.askDirectory(this, lang);
288
303
  } else {
289
304
  confirmedDirectory = await this.getConfirmedDirectory();
290
305
  }
@@ -297,6 +312,13 @@ class UI {
297
312
  // Track action type (only set if there's an existing installation)
298
313
  let actionType;
299
314
 
315
+ if (guided && !hasExistingInstall) {
316
+ return runGuidedInstall(this, options, { directory: confirmedDirectory, lang, channelOptions });
317
+ }
318
+ if (guided && hasExistingInstall) {
319
+ await prompts.log.info(guidedT(lang, 'existingFound'));
320
+ }
321
+
300
322
  // Only show action menu if there's an existing installation
301
323
  if (hasExistingInstall) {
302
324
  // Get version information
@@ -310,12 +332,16 @@ class UI {
310
332
  choices.push({
311
333
  name: 'Quick Update',
312
334
  value: 'quick-update',
313
- hint: 'Atualiza só os módulos instalados. Não instala skills/MCPs/CLIs novos (use Modify Wizz Installation para isso)',
335
+ hint: 'Refresh the installed modules only; use Modify to add skills, MCPs or CLIs',
314
336
  });
315
337
  }
316
338
 
317
339
  // Common actions
318
- choices.push({ name: 'Modify Wizz Installation', value: 'update' });
340
+ choices.push({
341
+ name: 'Modify Wizz Installation',
342
+ value: 'update',
343
+ hint: 'Add or remove modules and AI tools, and review the settings',
344
+ });
319
345
 
320
346
  // Check if action is provided via command-line
321
347
  if (options.action) {
@@ -703,7 +729,7 @@ class UI {
703
729
  if (options.yes) return { toWrite: [], toRecommend: resolved };
704
730
 
705
731
  const selected = await prompts.multiselect({
706
- message: 'Quais MCPs configurar no .mcp.json? (não marcados viram comando pra rodar depois)',
732
+ message: options.mcpMessage || 'Quais MCPs configurar no .mcp.json? (não marcados viram comando pra rodar depois)',
707
733
  options: resolved.map((m) => ({
708
734
  label: m.id,
709
735
  value: m.id,
@@ -780,15 +806,24 @@ class UI {
780
806
  // PRE-CHECKED, so pressing enter installs them; everything else stays
781
807
  // unchecked. Nothing installs à revelia — a recommended entry can still be
782
808
  // unchecked here, and the --yes/non-TTY paths above never reach this.
809
+ // The guided installer passes its own message and option builder (short
810
+ // translated description + requirement badges instead of the raw `when`).
811
+ const defaultOption = (c) => ({
812
+ label: c.recommended ? `${c.id} (recomendado)` : c.id,
813
+ value: c.id,
814
+ // M27/M14: prefer the upgrade/verify-failure reason when present —
815
+ // more actionable than the generic `when` in those two cases.
816
+ hint: c.upgradeMessage || c.verifyWarning || c.when,
817
+ });
818
+ const buildOption = options.cliOption
819
+ ? (c) => {
820
+ const option = options.cliOption(c);
821
+ return c.upgradeMessage || c.verifyWarning ? { ...option, hint: c.upgradeMessage || c.verifyWarning } : option;
822
+ }
823
+ : defaultOption;
783
824
  const selected = await prompts.multiselect({
784
- message: 'Quais CLIs instalar agora? (não marcados viram comando pra rodar depois)',
785
- options: missing.map((c) => ({
786
- label: c.recommended ? `${c.id} (recomendado)` : c.id,
787
- value: c.id,
788
- // M27/M14: prefer the upgrade/verify-failure reason when present —
789
- // more actionable than the generic `when` in those two cases.
790
- hint: c.upgradeMessage || c.verifyWarning || c.when,
791
- })),
825
+ message: options.cliMessage || 'Quais CLIs instalar agora? (não marcados viram comando pra rodar depois)',
826
+ options: missing.map((c) => buildOption(c)),
792
827
  initialValues: missing.filter((c) => c.recommended).map((c) => c.id),
793
828
  required: false,
794
829
  });
@@ -1185,19 +1220,19 @@ class UI {
1185
1220
  const coreConfig = {};
1186
1221
  if (options.userName) {
1187
1222
  coreConfig.user_name = options.userName;
1188
- await prompts.log.info(`Using user name from command-line: ${options.userName}`);
1223
+ if (!options.quiet) await prompts.log.info(`Using user name from command-line: ${options.userName}`);
1189
1224
  }
1190
1225
  if (options.communicationLanguage) {
1191
1226
  coreConfig.communication_language = options.communicationLanguage;
1192
- await prompts.log.info(`Using communication language from command-line: ${options.communicationLanguage}`);
1227
+ if (!options.quiet) await prompts.log.info(`Using communication language from command-line: ${options.communicationLanguage}`);
1193
1228
  }
1194
1229
  if (options.documentOutputLanguage) {
1195
1230
  coreConfig.document_output_language = options.documentOutputLanguage;
1196
- await prompts.log.info(`Using document output language from command-line: ${options.documentOutputLanguage}`);
1231
+ if (!options.quiet) await prompts.log.info(`Using document output language from command-line: ${options.documentOutputLanguage}`);
1197
1232
  }
1198
1233
  if (options.outputFolder) {
1199
1234
  coreConfig.output_folder = options.outputFolder;
1200
- await prompts.log.info(`Using output folder from command-line: ${options.outputFolder}`);
1235
+ if (!options.quiet) await prompts.log.info(`Using output folder from command-line: ${options.outputFolder}`);
1201
1236
  }
1202
1237
 
1203
1238
  // Load existing config to merge with provided options
@@ -1234,7 +1269,7 @@ class UI {
1234
1269
  document_output_language: 'English',
1235
1270
  output_folder: '_wizz-output',
1236
1271
  };
1237
- await prompts.log.info('Using default configuration (--yes flag)');
1272
+ if (!options.quiet) await prompts.log.info('Using default configuration (--yes flag)');
1238
1273
  }
1239
1274
  }
1240
1275
 
package/wizz-modules.yaml CHANGED
@@ -145,6 +145,7 @@ modules:
145
145
  code: automator
146
146
  name: "Wizz Automator Epic Builder Experimental"
147
147
  description: "EXPERIMENTAL: only supports claude and codex currently"
148
+ advanced_only: true
148
149
  defaultSelected: false
149
150
  type: experimental
150
151
  npmPackage: bmad-story-automator