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.
package/README.md CHANGED
@@ -72,7 +72,7 @@ The Wizz layer applies to everyone: standard closing (✅/➡️/🎯), token ec
72
72
  npx wizz-method install
73
73
  ```
74
74
 
75
- Follow the prompts, then open your AI IDE (Claude Code, Cursor, Codex and others) in the project folder.
75
+ The guided install takes four steps (folder, what you'll use it for, your AI tool, your name) and shows what will be installed before it starts. Choose Português on the first screen or pass `--lang pt`; `--advanced` asks the complete question list. Then open your AI tool (Claude Code, Cursor, Codex and others) in the project folder.
76
76
 
77
77
  ### 2. Customize the Wizz layer
78
78
 
@@ -106,6 +106,7 @@ Without a TTY, the default language is "Português (BR)". Use `--lang` or the `W
106
106
  | Command | Does |
107
107
  |---|---|
108
108
  | `npx wizz-method install` | Installs modules, hooks, skills and MCPs into the project |
109
+ | `npx wizz-method catalog [--area <area>]` | Lists every area, skill and tool, marking what's installed |
109
110
  | `npx wizz-method status` | Shows what's installed and each module's version |
110
111
  | `npx wizz-method doctor` | Diagnostics: hooks, rtk, jq, optional Python and `_wizz/` drift |
111
112
  | `npx wizz-method resolve [--skill <name>]` | Regenerates the customization snapshots in `_wizz/_resolved/` |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "wizz-method",
4
- "version": "2.12.0",
4
+ "version": "2.13.0",
5
5
  "description": "Wizz Method: método de agência orientado por IA em PT-BR",
6
6
  "keywords": [
7
7
  "agile",
@@ -242,7 +242,7 @@ areas:
242
242
  not_when:
243
243
  - "Analisar/resumir um vídeo que já se tem em mãos: é claude-video."
244
244
  - "Cortar/montar footage já captado: é buttercut (corte fino) ou wizz-remotion-best-practices (montagem final)."
245
- - "Gerar imagem/vídeo por IA: é fal-video. O get-brolls busca material REAL, não sintético."
245
+ - "Gerar imagem/vídeo por IA: é wizz-ad-creative, que escreve o prompt para o usuário colar no Flow ou em outra IA. O get-brolls busca material REAL, não sintético."
246
246
  - "Desenhar uma tela/cena em HTML: é hyperframes."
247
247
  - "Minerar/estudar criativo ou oferta de concorrente (Biblioteca de Anúncios, 'baixa esse anúncio pra eu ver'): é wizz-lowticket-minerador na área ads. O get-brolls capta insert para a SUA edição, com procedência registrada — não é ferramenta de inteligência competitiva, e o fato de saber baixar um Reel não o torna uma."
248
248
  rel:
@@ -302,14 +302,6 @@ areas:
302
302
  check: "test -x ./tools/ctc-align/.venv/bin/ctc-forced-aligner"
303
303
  install: 'mkdir -p tools/ctc-align && uv venv --python 3.12 tools/ctc-align/.venv && uv pip install --python tools/ctc-align/.venv/bin/python "git+https://github.com/MahmoudAshraf97/ctc-forced-aligner.git"'
304
304
  verify: "command -v ffmpeg >/dev/null 2>&1 && command -v uv >/dev/null 2>&1"
305
- - id: fal-video
306
- when: "GERAR vídeo/imagem por IA (Veo/Kling/Wan e outros modelos via API da fal.ai), estilo UGC/ad-ready. Substitui o arcads: mesmo caso de uso (vídeo gerado por IA), custo 50-95% menor por vídeo, sem clone-and-run pesado. Precisa FAL_KEY em .env. Sem CLI binária oficial: usa o client Node oficial @fal-ai/client como dependência do projeto."
307
- rel:
308
- pairs_with: [wizz-ad-creative]
309
- platform: [darwin, linux]
310
- check: "test -f node_modules/@fal-ai/client/package.json"
311
- # Pin de supply chain (2026-08-25): versão fixa (client oficial fal.ai); atualizar conscientemente.
312
- install: "npm install @fal-ai/client@1.10.1"
313
305
  - id: apple-design
314
306
  when: "Revisar/auditar UI NATIVA (iOS/macOS: SwiftUI, Flutter, React Native, Tauri, Electron) contra o Apple HIG e o Liquid Glass — compliance com o HIG, acessibilidade, dark mode, navegação. Cobre o gap que o wizz-taste-skill declara fora de escopo ('native mobile: use Apple HIG directly')."
315
307
  not_when: "UI web: aí é wizz-ui-ux-pro-max / wizz-impeccable."
@@ -573,9 +565,10 @@ areas:
573
565
  # para as CLIs de vídeo quando o roteiro vira vídeo", prometendo um handoff
574
566
  # que a área não pode fazer: as CLIs de vídeo (get-brolls, hyperframes,
575
567
  # buttercut, claude-video) moram em `designer`, e o único cli daqui é o
576
- # postiz. Medido: essa frase puxava pedido de ASSET ("imagens de apoio pro
577
- # meu Reel", "carrossel pro Instagram") para social, que não tem a
578
- # ferramenta. A fronteira ficou no summary; a nota ficou aqui.
568
+ # buffer (era o postiz até 2026-09-25). Medido: essa frase puxava pedido
569
+ # de ASSET ("imagens de apoio pro meu Reel", "carrossel pro Instagram")
570
+ # para social, que não tem a ferramenta. A fronteira ficou no summary;
571
+ # a nota ficou aqui.
579
572
  summary: "Social e roteiros virais: o TEXTO e o ROTEIRO de Reels/Shorts, hooks, legenda, thread, calendário de conteúdo. Persona Rafa, aplica o Blueprint de Roteiro Viral. Captar, desenhar ou montar o asset em si (b-roll, vídeo, carrossel, imagem) não é desta área."
580
573
  skills:
581
574
  - id: wizz-social-content
@@ -584,7 +577,7 @@ areas:
584
577
  - "Editar/revisar texto de social já escrito: aí é wizz-copy-editing."
585
578
  - "Pedir SÓ o asset de um Reel/vídeo — b-roll, 'imagens de apoio', 'cenas de apoio', imagem de corte, footage — sem que o texto ou o roteiro façam parte do pedido: a captação é get-brolls, na área designer. Esta entrada entra quando o TEXTO ou o ROTEIRO é parte do que se pede; se o pedido junta roteiro E captação, são 2 áreas."
586
579
  rel:
587
- before: [get-brolls, postiz]
580
+ before: [buffer, get-brolls]
588
581
  pairs_with: [wizz-canvas-design, wizz-content-strategy, wizz-copy-editing, wizz-launch-strategy]
589
582
  - id: wizz-content-strategy
590
583
  when: "Decidir O QUE produzir de conteúdo (topic clusters, calendário, pilares)."
@@ -596,20 +589,22 @@ areas:
596
589
  after: [wizz-copywriting]
597
590
  pairs_with: [wizz-social-content]
598
591
  clis:
599
- - id: postiz
600
- when: "PUBLICAR/agendar de verdade em redes sociais (30+ integrações) a partir do conteúdo que o wizz-social já gerou. Separa 'gerar' (Wizz) de 'publicar' (Postiz). Precisa autenticar antes (`postiz auth:login` ou env `POSTIZ_API_KEY`); para imagem/vídeo a própria CLI faz o upload."
592
+ - id: buffer
593
+ when: "PUBLICAR/agendar de verdade em redes sociais (Instagram, TikTok, YouTube, LinkedIn, Facebook, Threads, X, Pinterest, Bluesky, Mastodon, Google Business) a partir do conteúdo que o wizz-social já gerou. Separa 'gerar' (Wizz) de 'publicar' (Buffer). Precisa de API key (`buffer init` ou env `BUFFER_API_KEY`). Imagem/vídeo NÃO sobe do disco: a mídia precisa estar numa URL pública HTTPS e direta (Cloudinary, Cloudflare R2), estável até o post publicar."
601
594
  not_when: "Escrever o roteiro ou a legenda: isso é skill da área (wizz-social-content, wizz-copy-editing)."
602
595
  rel:
603
596
  after: [wizz-social-content]
604
- # Item 30 da auditoria (2026-09-11, Parte 3.7). Pin de supply chain
605
- # (2026-09-12): versão publicada fixa no npm, atualizar conscientemente.
597
+ # Substituiu o postiz em 2026-09-25: a nuvem da Postiz não tem plano
598
+ # grátis (a partir de US$ 29/mês) e o self-host exige servidor próprio
599
+ # mais um app de desenvolvedor por rede. O plano grátis do Buffer tem
600
+ # 3 canais, 10 posts na fila por canal e API/CLI/MCP inclusos.
606
601
  # CLI, não MCP: o schema de `mcps:` deste registry só aceita stdio
607
- # ({command,args,env}) e o MCP hospedado da Postiz é HTTP+OAuth
608
- # (https://mcp.postiz.com/mcp-oauth-dynamic) — não encaixa. Existe uma
609
- # skill wrapper (`gitroomhq/postiz-agent`) que só dirige esta mesma CLI;
610
- # opcional, não registrada aqui.
611
- check: "command -v postiz >/dev/null 2>&1"
612
- install: "npm install -g postiz@2.0.16"
602
+ # ({command,args,env}) e o MCP do Buffer é HTTP com bearer
603
+ # (https://mcp.buffer.com/mcp) — não encaixa.
604
+ # Pin de supply chain (2026-09-25): versão publicada fixa no npm,
605
+ # atualizar conscientemente.
606
+ check: "command -v buffer >/dev/null 2>&1"
607
+ install: "npm install -g @bufferapp/cli@1.2.0"
613
608
 
614
609
  ads:
615
610
  agent: wizz-ads
@@ -620,9 +615,9 @@ areas:
620
615
  rel:
621
616
  pairs_with: [wizz-ad-creative, wizz-tracking-audit]
622
617
  - id: wizz-ad-creative
623
- when: "Gerar/iterar criativos e headlines de anúncio em escala (RSA, variações)."
618
+ when: "Gerar/iterar criativos e headlines de anúncio em escala (RSA, variações). Inclui escrever o PROMPT de imagem/vídeo por IA (Veo, Kling, Flow e similares) para o usuário gerar fora do método: nenhuma API de geração está integrada."
624
619
  rel:
625
- pairs_with: [fal-video, wizz-copywriting, wizz-paid-ads, wizz-video-effects]
620
+ pairs_with: [wizz-copywriting, wizz-paid-ads, wizz-video-effects]
626
621
  - id: wizz-analytics-tracking
627
622
  when: "Tracking de conversão pros anúncios (compartilhada com growth)."
628
623
  rel:
@@ -1,5 +1,5 @@
1
1
  code: bmm
2
- name: "Wizz Method"
2
+ name: "Wizz Product & Code"
3
3
  description: "Full-lifecycle AI agile development: analysis, planning, architecture, implementation"
4
4
  default_selected: true # This module will be selected by default for new installations
5
5
 
@@ -27,7 +27,7 @@
27
27
  | **Source B-roll / inserts: find real footage of what the script names** | `get-brolls` (darwin) |
28
28
  | Edit/cut recorded footage (cuts, selects, roughcut) | `buttercut` (darwin-arm64 only) |
29
29
  | Analyze an existing reference video (frames + transcription) | `claude-video` |
30
- | AI-generated video/image (Veo/Kling/Wan), UGC / ad-ready style | `fal-video` (designer area; needs `FAL_KEY`) |
30
+ | AI-generated video/image (Veo/Kling/Wan), UGC / ad-ready style | `wizz-ad-creative` (ads area) writes the prompt; the user generates it in Flow or another AI tool, outside the method |
31
31
 
32
32
  ## Full chain (script → publishing)
33
33
 
@@ -76,7 +76,7 @@ The Diretor (`wizz-router`) has already done the triage and handed it to you bec
76
76
  - `areas:` — `agent` (must match the menu's) and `skills:` (each with an `id` + short `when`). Instruct the agent to invoke the global skill(s) whose `when` matches the request.
77
77
  - `utility:` — cross-cutting skills (find-skills, enhance-prompt, wizz-router). Offer when it fits.
78
78
  - `mcps:` (per area) and `mcp_utility:` (cross-cutting) — MCP servers the area uses to actually ACT (e.g. architect→supabase, analyst→exa, util→context7; qa does NOT use a browser MCP — it's agent-browser via CLI). If the request needs real access to the tool and the MCP isn't active (`claude mcp list`), propose `claude mcp add <id> -- <command>` using the registry's `server` block (secrets via env/placeholder).
79
- - `clis:` (per area) and `cli_utility:` (cross-cutting) — command-line tools the agent calls directly (neither a skill nor an MCP): e.g. qa→agent-browser; designer→hyperframes/get-brolls/claude-video/buttercut/voicebox/fal-video (video); growth→scrapling; seo→distribb; social→postiz. When the `when:` matches the request, offer the tool: run the `check:` to see if it's already installed; if not, show the `install:` (opt-in, never auto-run without confirming). Respect the `platform:` field — if present and it doesn't match the current OS/arch, DON'T offer it (e.g., `buttercut` is `darwin-arm64`, Apple Silicon only). Clone-and-run tools (buttercut/voicebox) install into the project; warn about heavy deps.
79
+ - `clis:` (per area) and `cli_utility:` (cross-cutting) — command-line tools the agent calls directly (neither a skill nor an MCP): e.g. qa→agent-browser; designer→hyperframes/get-brolls/claude-video/buttercut/voicebox (video); growth→scrapling; seo→distribb; social→buffer. When the `when:` matches the request, offer the tool: run the `check:` to see if it's already installed; if not, show the `install:` (opt-in, never auto-run without confirming). Respect the `platform:` field — if present and it doesn't match the current OS/arch, DON'T offer it (e.g., `buttercut` is `darwin-arm64`, Apple Silicon only). Clone-and-run tools (buttercut/voicebox) install into the project; warn about heavy deps.
80
80
  - `rel:` (on any entry) — **this is where the method knows that a request about A also needs C.** `pairs_with` (they go together), `before`/`after` (mandatory order) and `instead_of` (one or the other, not both). Every edge is reciprocal by construction: the schema validator rejects one-way edges, so the relation shows up whichever side the user enters from. As the multi-area orchestrator you are the biggest beneficiary of this — roughly a third of the edges cross areas (count them in the registry when you need the number; don't quote one from memory). When building the dispatch, follow the same order the area agents follow ([rel protocol](../../_shared/rel-protocol.md)): LIST every id the chosen entry's `rel` names — including the ones you don't recognise, which are exactly the point — resolve each id → area via the `ids:` map in `{project-root}/_wizz/_config/registry/index.yaml`, read that area's slice for its `when:`, and then either bring it in or discard it with a stated reason. A target in another area becomes a dispatch to THAT area's agent, and a `before` target sets the ORDER of the plan, not a parallel item. Never deliver only the half the user named when `rel` says the other half is missing: a related capability you never named is one the method lost.
81
81
  - `squads:` — advisory panels (run via `wizz-party-mode`). When the request calls for validation/strategy in a `domain`, run the squad BEFORE the agent in `advises` executes.
82
82
 
@@ -58,7 +58,7 @@ Use this table to map skills/CLIs/MCPs directly **outside a Wizz project** (no `
58
58
  | Effects and sound of a short vertical video (ad or Reels): which effect goes where and when not, which SFX for each event and at what level, ducking, master loudness, safe-zone/density/excess/rhythm gates | `wizz-video-effects` (designer registry; decides the effects, Remotion assembles them) | 2 |
59
59
  | **Find B-roll / inserts / cutaways / supporting footage** for a video or Reel (real footage of the fact, person, product, news item or screen the script names; YouTube/Instagram/TikTok/Pexels/Pixabay/Commons/NASA) | CLI `get-brolls` (designer registry; **macOS**, global `skills add` + its own `install.sh`; needs ffmpeg with drawtext, Node 22+, Python 3.11+). Ends in a Storyboard the HUMAN approves — the agent never self-approves. Stock only when the user explicitly asks for stock | 1 |
60
60
  | **Edit/cut video** (cuts, selects, roughcut) | CLI `buttercut` (designer registry; **Apple Silicon only**, clone-and-run in the project, heavy deps) | 1 |
61
- | Generate an AI video/image (Veo/Kling/Wan), UGC / ad-ready style | CLI `fal-video` (designer registry; official `@fal-ai/client`, needs `FAL_KEY` in `.env`). Replaced the old arcads entry: same use case, 50-95% cheaper per video, no heavy clone-and-run | 1 |
61
+ | Generate an AI video/image (Veo/Kling/Wan), UGC / ad-ready style | `wizz-ad-creative` writes the prompt for the user to paste into Flow or another AI tool. No generation API is integrated in the method | 1 |
62
62
  | Analyze/understand an existing video (frames + transcription) | CLI `claude-video` (designer registry; `npx skills add bradautomates/claude-video`) | 2 |
63
63
  | Narration / voice / TTS / voice cloning for video | CLI `voicebox` (designer registry; local app with MCP endpoint) | 2 |
64
64
  | Research components compatible with the project | `wizz-ui-component-curator` (demos, code, and public registries with evidence) | 1 |
@@ -83,7 +83,7 @@ Use this table to map skills/CLIs/MCPs directly **outside a Wizz project** (no `
83
83
  | EXECUTE the SEO distribution: publish articles, build backlinks through the Distribb network, connect Search Console, autopublish news | CLI `distribb` (seo registry) — the doing half; the strategy half is the SEO row above | 2 |
84
84
  | Competitor comparison, alternatives page, war room | `wizz-competitor-alternatives` | 2 |
85
85
  | Content, social media, newsletter, threads | `wizz-content-strategy` + `wizz-social-content` + `wizz-copy-editing` | 2 |
86
- | Actually PUBLISH/schedule to social networks (30+ integrations), not write it | CLI `postiz` (social registry; needs auth). Separates "generate" (Wizz skills above) from "publish" | 2 |
86
+ | Actually PUBLISH/schedule to social networks (Instagram, TikTok, YouTube, LinkedIn and more), not write it | CLI `buffer` (social registry; needs `BUFFER_API_KEY`; media must be a public HTTPS URL). Separates "generate" (Wizz skills above) from "publish" | 2 |
87
87
  | Cold email, B2B outbound, prospecting | `wizz-cold-email` | 2 |
88
88
  | Referral, referrals, affiliate program | `wizz-referral-program` | 2 |
89
89
  | Sales material, pitch, deck, proposal | `wizz-sales-enablement` | 2 |
@@ -1,40 +1,91 @@
1
1
  const prompts = require('./prompts');
2
+ const { t } = require('./guided/messages');
3
+
4
+ // Wizz brand palette (brand book): Wizz Orange and its deep shade.
5
+ const BRAND_ORANGE = [255, 69, 0];
6
+ const BRAND_DEEP = [204, 55, 0];
7
+ // Built from the char code on purpose: a literal escape byte in source turns
8
+ // the file binary for git (see the review-prep.mjs incident).
9
+ const ESC = String.fromCodePoint(27);
10
+
11
+ /**
12
+ * Paint text with a 24-bit brand color, or leave it plain when the terminal
13
+ * has no color support (NO_COLOR, pipes, CI logs).
14
+ * @param {number[]} rgb - [r, g, b]
15
+ * @param {string} text - Text to paint
16
+ * @param {boolean} enabled - Whether color is supported
17
+ * @returns {string}
18
+ */
19
+ function paint(rgb, text, enabled) {
20
+ if (!enabled) return text;
21
+ return `${ESC}[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m${text}${ESC}[39m`;
22
+ }
23
+
24
+ const LOGO_WIDE = [
25
+ '██╗ ██╗ ██╗ ███████╗ ███████╗ ███╗ ███╗ ███████╗ ████████╗ ██╗ ██╗ ██████╗ ██████╗ ™',
26
+ '██║ ██║ ██║ ╚══███╔╝ ╚══███╔╝ ████╗ ████║ ██╔════╝ ╚══██╔══╝ ██║ ██║ ██╔═══██╗ ██╔══██╗',
27
+ '██║ █╗ ██║ ██║ ███╔╝ ███╔╝ ██╔████╔██║ █████╗ ██║ ███████║ ██║ ██║ ██║ ██║',
28
+ '██║███╗██║ ██║ ███╔╝ ███╔╝ ██║╚██╔╝██║ ██╔══╝ ██║ ██╔══██║ ██║ ██║ ██║ ██║',
29
+ '╚███╔███╔╝ ██║ ███████╗ ███████╗ ██║ ╚═╝ ██║ ███████╗ ██║ ██║ ██║ ╚██████╔╝ ██████╔╝',
30
+ ' ╚══╝╚══╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ',
31
+ ];
32
+
33
+ const LOGO_NARROW = [
34
+ '██╗ ██╗ ██╗ ███████╗ ███████╗ ™',
35
+ '██║ ██║ ██║ ╚══███╔╝ ╚══███╔╝',
36
+ '██║ █╗ ██║ ██║ ███╔╝ ███╔╝ ',
37
+ '██║███╗██║ ██║ ███╔╝ ███╔╝ ',
38
+ '╚███╔███╔╝ ██║ ███████╗ ███████╗',
39
+ ' ╚══╝╚══╝ ╚═╝ ╚══════╝ ╚══════╝',
40
+ ];
2
41
 
3
42
  const CLIUtils = {
4
43
  /**
5
- * Display Wizz Method logo and version using @clack intro + box
44
+ * Display the Wizz Method opening screen. With `steps`, it also lists the
45
+ * guided path's four steps so the user knows what comes next.
46
+ * @param {Object} [opts]
47
+ * @param {boolean} [opts.steps=false] - Show the "what happens next" list
48
+ * @param {'en'|'pt'} [opts.lang='en'] - Display language
49
+ * @param {string} [opts.version] - Version shown under the logo
6
50
  */
7
- async displayLogo() {
51
+ async displayLogo(opts = {}) {
52
+ const { steps = false, lang = 'en', version } = opts;
8
53
  const color = await prompts.getColor();
54
+ const colorOn = color.isColorSupported !== false;
9
55
  const termWidth = process.stdout.columns || 80;
10
56
 
11
- // Full "WIZZ METHOD" logo for wide terminals, "WIZZ" only for narrow
12
- const logoWide = [
13
- '██╗ ██╗ ██╗ ███████╗ ███████╗ ███╗ ███╗ ███████╗ ████████╗ ██╗ ██╗ ██████╗ ██████╗ ™',
14
- '██║ ██║ ██║ ╚══███╔╝ ╚══███╔╝ ████╗ ████║ ██╔════╝ ╚══██╔══╝ ██║ ██║ ██╔═══██╗ ██╔══██╗',
15
- '██║ █╗ ██║ ██║ ███╔╝ ███╔╝ ██╔████╔██║ █████╗ ██║ ███████║ ██║ ██║ ██║ ██║',
16
- '██║███╗██║ ██║ ███╔╝ ███╔╝ ██║╚██╔╝██║ ██╔══╝ ██║ ██╔══██║ ██║ ██║ ██║ ██║',
17
- '╚███╔███╔╝ ██║ ███████╗ ███████╗ ██║ ╚═╝ ██║ ███████╗ ██║ ██║ ██║ ╚██████╔╝ ██████╔╝',
18
- ' ╚══╝╚══╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ',
19
- ];
20
-
21
- const logoNarrow = [
22
- ' ██╗ ██╗ ██╗ ███████╗ ███████╗ ™',
23
- ' ██║ ██║ ██║ ╚══███╔╝ ╚══███╔╝',
24
- ' ██║ █╗ ██║ ██║ ███╔╝ ███╔╝ ',
25
- ' ██║███╗██║ ██║ ███╔╝ ███╔╝ ',
26
- ' ╚███╔███╔╝ ██║ ███████╗ ███████╗',
27
- ' ╚══╝╚══╝ ╚═╝ ╚══════╝ ╚══════╝',
28
- ];
29
-
30
- const logoLines = termWidth >= 95 ? logoWide : logoNarrow;
31
- const logo = logoLines.map((line) => color.blue(line)).join('\n');
32
- const tagline = color.white(' Método de agência orientado por IA\n © Wizz! comms. · method.wizzcomms.com');
33
-
34
- await prompts.box(`${logo}\n${tagline}`, '', {
35
- contentAlign: 'center',
57
+ const wide = termWidth >= 100;
58
+ const logoLines = wide ? LOGO_WIDE : LOGO_NARROW;
59
+ // Top half in Wizz Orange, bottom half in the deep shade: reads as a
60
+ // light-to-shadow gradient without a per-character color ramp.
61
+ const half = Math.ceil(logoLines.length / 2);
62
+ const logo = logoLines.map((line, i) => paint(i < half ? BRAND_ORANGE : BRAND_DEEP, line, colorOn));
63
+ if (!wide) logo.push(paint(BRAND_ORANGE, 'M E T H O D', colorOn));
64
+
65
+ const lines = [...logo, ''];
66
+ const versionTag = version ? color.dim(`v${version}`) : '';
67
+ lines.push(`${color.white(t(lang, 'tagline'))} ${versionTag}`.trimEnd());
68
+
69
+ if (steps) {
70
+ lines.push('', `${color.bold(t(lang, 'whatHappens'))} ${color.dim(`(${t(lang, 'duration')})`)}`);
71
+ for (const [i, name] of t(lang, 'stepNames').entries()) {
72
+ lines.push(`${paint(BRAND_ORANGE, String(i + 1), colorOn)} ▸ ${name}`);
73
+ }
74
+ }
75
+
76
+ lines.push('', color.dim('© Wizz! comms. · method.wizzcomms.com'));
77
+
78
+ // A terminal that reports 0 columns (some ptys, expect, CI with a TTY)
79
+ // makes clack's box compute a negative width and throw. Print the same
80
+ // lines without the frame instead of failing the install.
81
+ if (!process.stdout.columns || process.stdout.columns < 40) {
82
+ process.stdout.write(`${lines.join('\n')}\n\n`);
83
+ return;
84
+ }
85
+ await prompts.box(lines.join('\n'), '', {
86
+ contentAlign: steps ? 'left' : 'center',
36
87
  rounded: true,
37
- formatBorder: color.blue,
88
+ formatBorder: (text) => paint(BRAND_DEEP, text, colorOn),
38
89
  });
39
90
  },
40
91
 
@@ -50,4 +101,4 @@ const CLIUtils = {
50
101
  },
51
102
  };
52
103
 
53
- module.exports = { CLIUtils };
104
+ module.exports = { CLIUtils, paint, BRAND_ORANGE, BRAND_DEEP };
@@ -0,0 +1,87 @@
1
+ const path = require('node:path');
2
+ const os = require('node:os');
3
+ const fs = require('node:fs');
4
+ const { getProjectRoot } = require('../project-root');
5
+ const { loadYamlCached } = require('../core/yaml-cache');
6
+ const { AREA_LABELS, cliBadges, cliDescription } = require('../guided/labels');
7
+ const { normalizeLang, t } = require('../guided/messages');
8
+ const { paint, BRAND_ORANGE } = require('../banner');
9
+
10
+ const SKILL_DIRS = ['.claude/skills', '.agents/skills'];
11
+
12
+ /**
13
+ * Whether a skill folder exists in the project or in the global Claude
14
+ * skills folder. Only a hint for the ✓ mark; nothing depends on it.
15
+ */
16
+ function isSkillInstalled(id, projectDir, home) {
17
+ const places = [...SKILL_DIRS.map((dir) => path.join(projectDir, dir, id)), path.join(home, '.claude', 'skills', id)];
18
+ return places.some((place) => fs.existsSync(place));
19
+ }
20
+
21
+ /** First sentence of a registry `when:`, cut to fit one terminal line. */
22
+ function shortWhen(when, max = 90) {
23
+ const first = String(when || '').split(/(?<=[.!?])\s/)[0];
24
+ return first.length > max ? `${first.slice(0, max - 1)}…` : first;
25
+ }
26
+
27
+ /**
28
+ * Render the catalog as plain text lines.
29
+ * @param {Object} registry - Parsed skills-registry.yaml
30
+ * @param {Object} opts
31
+ * @param {'en'|'pt'} opts.lang - Display language
32
+ * @param {string|null} opts.area - Only this area, or null for all
33
+ * @param {(id: string) => boolean} opts.installed - Installed check
34
+ * @param {boolean} opts.color - Whether to paint
35
+ * @returns {string[]}
36
+ */
37
+ function renderCatalog(registry, opts) {
38
+ const { lang, area, installed, color } = opts;
39
+ const areas = Object.entries(registry.areas || {}).filter(([key]) => !area || key === area);
40
+ const lines = [];
41
+ for (const [key, entry] of areas) {
42
+ const label = AREA_LABELS[key] ? AREA_LABELS[key][lang] : { name: key, help: entry.summary || '' };
43
+ lines.push('', `${paint(BRAND_ORANGE, `■ ${label.name}`, color)} (${key}) · ${label.help}`);
44
+ for (const skill of entry.skills || []) {
45
+ const mark = installed(skill.id) ? '✓' : ' ';
46
+ lines.push(` ${mark} ${skill.id.padEnd(40)} ${shortWhen(skill.when)}`);
47
+ }
48
+ for (const cli of entry.clis || []) {
49
+ const badges = cliBadges(cli, lang);
50
+ const needs = badges.length > 0 ? ` [${badges.join(', ')}]` : ` [${t(lang, 'free')}]`;
51
+ lines.push(` ⌘ ${cli.id.padEnd(40)} ${cliDescription(cli.id, lang)}${needs}`);
52
+ }
53
+ for (const mcp of entry.mcps || []) {
54
+ lines.push(` ◇ ${mcp.id.padEnd(40)} ${shortWhen(mcp.when)}`);
55
+ }
56
+ }
57
+ return lines;
58
+ }
59
+
60
+ module.exports = {
61
+ command: 'catalog',
62
+ description: 'List every area, skill and tool Wizz Method can install (✓ = installed)',
63
+ options: [
64
+ ['--area <area>', 'Only one area, e.g. social or dev'],
65
+ ['--lang <lang>', 'Language for area names and tool descriptions: en (default) or pt'],
66
+ ],
67
+ action: async (options) => {
68
+ const registry = await loadYamlCached(path.join(getProjectRoot(), 'skills-registry.yaml'));
69
+ if (options.area && !(registry.areas || {})[options.area]) {
70
+ process.stderr.write(`Unknown area "${options.area}". Areas: ${Object.keys(registry.areas).join(', ')}\n`);
71
+ process.exitCode = 1;
72
+ return;
73
+ }
74
+ const projectDir = process.cwd();
75
+ const home = os.homedir();
76
+ const lang = normalizeLang(options.lang) || 'en';
77
+ const lines = renderCatalog(registry, {
78
+ lang,
79
+ area: options.area || null,
80
+ installed: (id) => isSkillInstalled(id, projectDir, home),
81
+ color: !!process.stdout.isTTY && !process.env.NO_COLOR,
82
+ });
83
+ lines.push('', t(lang, 'catalogLegend'), '');
84
+ process.stdout.write(lines.join('\n'));
85
+ },
86
+ renderCatalog,
87
+ };
@@ -47,6 +47,11 @@ module.exports = {
47
47
  ['--output-folder <path>', 'Output folder path relative to project root (default: _wizz-output)'],
48
48
  ['--custom-source <sources>', 'Comma-separated Git URLs or local paths to install custom modules from'],
49
49
  ['-y, --yes', 'Accept all defaults and skip prompts where possible'],
50
+ ['--lang <lang>', 'Installer language for the guided install: en (default) or pt'],
51
+ [
52
+ '--advanced',
53
+ 'Skip the guided install and ask every question: module list, community modules, release channels, artifact folders and the routing meter',
54
+ ],
50
55
  [
51
56
  '--channel <channel>',
52
57
  'Apply channel (stable|next) to all external modules being installed. --all-stable and --all-next are aliases.',
@@ -171,6 +176,10 @@ module.exports = {
171
176
  process.exit(1);
172
177
  }
173
178
  if (result.success) {
179
+ if (config.guided) {
180
+ const { showGuidedOutro } = require('../guided/outro');
181
+ await showGuidedOutro(config);
182
+ }
174
183
  process.exit(0);
175
184
  }
176
185
  }
@@ -0,0 +1,53 @@
1
+ const os = require('node:os');
2
+ const path = require('node:path');
3
+ const fs = require('node:fs');
4
+
5
+ /**
6
+ * Signs that an AI tool is present on this machine: a binary on PATH, a
7
+ * config folder in the home directory or an installed app. Any one is
8
+ * enough; this only decides which option comes pre-checked, never what
9
+ * gets installed.
10
+ */
11
+ const SIGNS = {
12
+ 'claude-code': { bins: ['claude'], homeDirs: ['.claude'], apps: [] },
13
+ codex: { bins: ['codex'], homeDirs: ['.codex'], apps: [] },
14
+ cursor: { bins: ['cursor'], homeDirs: ['.cursor'], apps: ['Cursor.app'] },
15
+ 'github-copilot': { bins: [], homeDirs: [], apps: [], vscodeExtension: 'github.copilot' },
16
+ };
17
+
18
+ function onPath(bin) {
19
+ const dirs = (process.env.PATH || '').split(path.delimiter).filter(Boolean);
20
+ const exts = process.platform === 'win32' ? ['.exe', '.cmd', ''] : [''];
21
+ return dirs.some((dir) => exts.some((ext) => fs.existsSync(path.join(dir, bin + ext))));
22
+ }
23
+
24
+ function hasVscodeExtension(prefix, home) {
25
+ const extDir = path.join(home, '.vscode', 'extensions');
26
+ try {
27
+ return fs.readdirSync(extDir).some((name) => name.toLowerCase().startsWith(prefix));
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Which of the preferred AI tools look installed.
35
+ * @param {Object} [deps] - Injection seam for tests
36
+ * @param {string} [deps.home] - Home directory
37
+ * @returns {string[]} Tool ids, e.g. ['claude-code', 'cursor']
38
+ */
39
+ function detectAiTools(deps = {}) {
40
+ const home = deps.home || os.homedir();
41
+ const found = [];
42
+ for (const [id, sign] of Object.entries(SIGNS)) {
43
+ const present =
44
+ sign.bins.some((bin) => onPath(bin)) ||
45
+ sign.homeDirs.some((dir) => fs.existsSync(path.join(home, dir))) ||
46
+ sign.apps.some((app) => fs.existsSync(path.join('/Applications', app))) ||
47
+ (sign.vscodeExtension ? hasVscodeExtension(sign.vscodeExtension, home) : false);
48
+ if (present) found.push(id);
49
+ }
50
+ return found;
51
+ }
52
+
53
+ module.exports = { detectAiTools };