wizz-method 1.6.0 → 1.8.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/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": "1.6.0",
4
+ "version": "1.8.0",
5
5
  "description": "Wizz Method — método de agência orientado por IA em PT-BR (fork independente do BMad Method)",
6
6
  "keywords": [
7
7
  "agile",
@@ -27,7 +27,7 @@ Usar quando precisa de **interação real**.
27
27
  - Drei;
28
28
  - GLSL / shaders;
29
29
  - Framer Motion ou Motion para integração com scroll;
30
- - GSAP apenas se necessário.
30
+ - GSAP (ScrollTrigger) quando o scroll orquestra a cena — 100% gratuito desde a v3.13, todos os plugins incluídos.
31
31
 
32
32
  **Exemplos:** objeto 3D que segue o cursor; esfera de partículas mouse-reactive; câmera que avança no scroll; wireframe terrain em tempo real; produto 3D configurável; background WebGL interativo.
33
33
 
@@ -107,6 +107,8 @@ Usar quando 3D é exagero ou não aumenta conversão.
107
107
 
108
108
  **Inclui:** text reveal; parallax; masked typography; image reveal; hover cards; SVG motion; scroll storytelling; magnetic buttons; animated mockups; cinematic section transitions; gradient background animado; background noise; reveal por seção; staggered animations.
109
109
 
110
+ **Stack recomendada:** Framer Motion/Motion para UI React declarativa; GSAP + ScrollTrigger/SplitText para scroll storytelling, pinning e text reveal avançado (gratuito desde a v3.13); anime.js v4 para stagger, SVG motion e microinterações imperativas leves (~10kb core, MIT).
111
+
110
112
  **Usar quando:**
111
113
 
112
114
  - o projeto é Advanced Site;
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: premium-landing-ui-researcher
3
- description: Pesquisar animações, componentes, referências visuais e padrões de conversão para criar landing pages premium em React, Next.js, Tailwind, shadcn/ui, Framer Motion, Three.js e React Three Fiber. Use esta skill quando o usuário pedir para analisar um projeto existente, classificar o nível de complexidade do site (básico, intermediário, avançado, 3D high-end com modo Signature), escolher componentes ou animações adequados, melhorar uma UI genérica, criar uma landing page completa, transformar uma oferta em página estratégica, projetar também um dashboard SaaS junto da landing, criar um site de autoridade/portfolio para agência, estúdio, consultoria, marca pessoal ou lead passivo, criar case studies e selected work editoriais, criar uma experiência 3D cinematográfica para marcas premium, buscar referências em React Bits, Cult UI, Componentry, Animmaster Lib, Ali Imam, Watermelon UI, StyleUI, Refero, Impeccable, Taste Skill, Design Motion Principles, MotionSites, Vibe Code Components, Landing Love, Godly, Design Spells, Mobbin, ScreensDesign, DesignVault, Spline, Unicorn Studio, Skiper UI, 21st.dev (Magic MCP ou 21st CLI), ou implementar uma experiência visual sofisticada orientada à conversão ou à autoridade.
3
+ description: Pesquisar animações, componentes, referências visuais e padrões de conversão para criar landing pages premium em React, Next.js, Tailwind, shadcn/ui, Framer Motion, Three.js e React Three Fiber. Use esta skill quando o usuário pedir para analisar um projeto existente, classificar o nível de complexidade do site (básico, intermediário, avançado, 3D high-end com modo Signature), escolher componentes ou animações adequados, melhorar uma UI genérica, criar uma landing page completa, transformar uma oferta em página estratégica, projetar também um dashboard SaaS junto da landing, criar um site de autoridade/portfolio para agência, estúdio, consultoria, marca pessoal ou lead passivo, criar case studies e selected work editoriais, criar uma experiência 3D cinematográfica para marcas premium, buscar referências em React Bits, Cult UI, Componentry, Animmaster Lib, Ali Imam, Watermelon UI, StyleUI, Bklit UI, Refero, Impeccable, Taste Skill, Design Motion Principles, MotionSites, Vibe Code Components, Landing Love, Godly, Design Spells, Mobbin, ScreensDesign, DesignVault, Spline, Unicorn Studio, Skiper UI, 21st.dev (Magic MCP ou 21st CLI), escolher entre GSAP, anime.js e Framer Motion para animações e scroll storytelling, ou implementar uma experiência visual sofisticada orientada à conversão ou à autoridade.
4
4
  ---
5
5
 
6
6
  # Premium Landing UI Researcher
@@ -49,7 +49,7 @@ Classificar o projeto em um dos 5 níveis (regras, motion permitido/proibido e b
49
49
  | Processo obrigatório de 12 passos e checkpoint de honestidade | [mandatory-process](references/mandatory-process.md) |
50
50
  | Source-First Mandate completo + protocolo em 5 fases (inventário, repos em cache, Magic MCP, 21st CLI, confirmação; 21st = fonte paga complementar) | [source-first-protocol](references/source-first-protocol.md) |
51
51
  | Audit Protocol: Pass 1 Taste, Pass 2 Impeccable, Pass 3 Cross-check, Pass 4 A11y/Perf | [audit-protocol](references/audit-protocol.md) |
52
- | Fontes de componentes (React Bits, Cult UI, Ali Imam, Watermelon, StyleUI, Skiper UI, 21st.dev + 21st CLI), fontes de referência e inspiração visual, Clone Policy, Paid Source Policy | [component-sources](references/component-sources.md) |
52
+ | Fontes de componentes (React Bits, Cult UI, Ali Imam, Watermelon, StyleUI, Skiper UI, Bklit UI, 21st.dev + 21st CLI), animation engines (GSAP, anime.js), fontes de referência e inspiração visual, Clone Policy, Paid Source Policy | [component-sources](references/component-sources.md) |
53
53
  | Stack default, direção visual, paletas, tipografia e regras de seleção de animação | [stack-and-visual-direction](references/stack-and-visual-direction.md) |
54
54
  | Estrutura obrigatória da landing, case studies/portfolio, copywriting, conversão e CTA externo/WhatsApp | [landing-page-strategy](references/landing-page-strategy.md) |
55
55
  | Prompts base (landing completa e hero 3D com scroll) | [prompt-templates](references/prompt-templates.md) |
@@ -131,6 +131,14 @@ Usar para: templates, landing page layouts, páginas prontas, seções instaláv
131
131
  - Mesmo gate de custo do 21st.dev: só com aprovação do usuário, quando as gratuitas não cobrirem
132
132
  - O que o usuário já comprou/baixou dela vale como recurso local (inspecionar em `modelos lp/`)
133
133
 
134
+ ### Bklit UI
135
+
136
+ - Site: https://bklit.com/ · GitHub: https://github.com/bklit/bklit-ui (MIT, gratuito, open source)
137
+ - Charts e componentes utilitários React/TypeScript/Tailwind via registry shadcn (~15 tipos: área, barras, linha, pizza, radar, Sankey, candlestick, choropleth, gauge)
138
+ - **Não clonar**: instalar direto pelo registry — `npx shadcn@latest add @bklit/<chart>` (ex: `@bklit/line-chart`)
139
+ - Fonte prioritária para: dashboards SaaS, data viz, seções de métricas/estatísticas na landing, gráficos animados premium (complementa/substitui Recharts cru)
140
+ - O Bklit Studio (playground) é proprietário; os componentes em si são MIT
141
+
134
142
  ### Skiper UI
135
143
 
136
144
  Skiper UI **não deve ser clonado**.
@@ -147,6 +155,24 @@ Usar para: uncommon shadcn components, efeitos visuais diferenciados, seções p
147
155
 
148
156
  Evitar: depender de componentes premium sem autorização; assumir acesso pago.
149
157
 
158
+ ## Animation Engine Libraries (libs de código, via npm)
159
+
160
+ Motores de animação instaláveis como dependência npm normal (não são registries de componentes; não precisam de clone — os repos podem ser inspecionados para exemplos/demos).
161
+
162
+ ### GSAP
163
+
164
+ - GitHub: https://github.com/greensock/GSAP.git · Docs: https://gsap.com/docs/
165
+ - **100% gratuito desde a v3.13** (aquisição pela Webflow, 2025), incluindo todos os plugins antes pagos: ScrollTrigger, ScrollSmoother, SplitText, MorphSVG, DrawSVG, etc. Instalar: `npm i gsap` (+ `@gsap/react` para o hook `useGSAP`)
166
+ - Usar para: scroll storytelling e animações scroll-driven complexas (ScrollTrigger), timelines longas e sequenciadas, text reveal por caractere/palavra (SplitText), morph de SVG, animações fora do ciclo do React, sites high-end estilo Awwwards
167
+ - Preferir GSAP sobre Framer Motion quando: a animação é orquestrada por scroll com pinning/scrub, a timeline tem muitos passos encadeados, ou o efeito anima elementos fora de componentes React
168
+
169
+ ### anime.js
170
+
171
+ - GitHub: https://github.com/juliangarnier/anime.git · Docs: https://animejs.com/ (MIT, gratuito)
172
+ - v4: API modular e tree-shakeable (~10kb core), timelines, stagger, springs, draggable, scroll observer, SVG (draw/morph/motion path), WAAPI. Instalar: `npm i animejs`
173
+ - Usar para: microinterações e animações leves quando GSAP seria peso demais, stagger/grid animations, animação de SVG, contadores/números animados, projetos sem React ou fora do ecossistema Motion
174
+ - Regra de escolha: Framer Motion/Motion para UI React declarativa → anime.js para efeitos leves imperativos → GSAP para scroll orquestrado e timelines cinematográficas
175
+
150
176
  ## Authorized Reference Inspection Sources
151
177
 
152
178
  Estas fontes podem ser inspecionadas ou consultadas para princípios, critérios de qualidade, motion e taste, mas **não devem ser tratadas como dependências de projeto**.
@@ -53,6 +53,9 @@ Para cada efeito visual ainda não resolvido pela fase 1, inspecionar o **cache
53
53
  | SaaS components / dashboards / product UI blocks | `~/.claude/design-sources/watermelon/` | `git -C ~/.claude/design-sources/watermelon pull` |
54
54
  | Templates / landing layouts prontos / páginas base | `~/.claude/design-sources/styleui/` | `git -C ~/.claude/design-sources/styleui pull` |
55
55
  | Componentes shadcn premium específicos | **Skiper UI** | `npx shadcn add @skiper-ui/skiperXX` (sem clone, instala direto) |
56
+ | Charts / data viz / seções de métricas / gráficos de dashboard | **Bklit UI** | `npx shadcn@latest add @bklit/<chart>` (sem clone, registry shadcn) |
57
+ | Scroll storytelling / pinning / timelines cinematográficas / SplitText | **GSAP** (lib npm, 100% gratuita desde a v3.13) | `npm i gsap @gsap/react` (sem clone) |
58
+ | Microinterações imperativas leves / stagger / SVG motion / contadores | **anime.js v4** (lib npm, MIT) | `npm i animejs` (sem clone) |
56
59
 
57
60
  **Fluxo de uso do cache:**
58
61
 
@@ -10,6 +10,7 @@
10
10
  - Watermelon UI: https://github.com/WatermelonCorp/watermellon-registry.git
11
11
  - StyleUI: https://github.com/heyfabrika/styleui.git
12
12
  - Skiper UI command: `npx shadcn add @skiper-ui/skiper40`
13
+ - Bklit UI (charts shadcn, MIT, gratuito): https://github.com/bklit/bklit-ui — `npx shadcn@latest add @bklit/<chart>`
13
14
  - Componentry (React animado, gratuito/open source, Vercel OSS): https://componentry.dev/
14
15
  - Componentry MCP (via shadcn MCP + registry `@componentry`): https://componentry.dev/docs/mcp
15
16
  - Animmaster Lib (300 componentes animados, PAGO): https://animmasterlib.dev/
@@ -22,6 +23,11 @@
22
23
  - 21st instalação: `21st add <user>/<slug>` ou `npx shadcn@latest add https://21st.dev/r/<user>/<slug>`
23
24
  - 21st API keys (CI/headless, env API_KEY_21ST): https://21st.dev/settings/api-keys
24
25
 
26
+ ## Animation Engine Libraries (npm)
27
+
28
+ - GSAP (100% gratuito desde a v3.13, todos os plugins incluídos): https://github.com/greensock/GSAP.git — `npm i gsap @gsap/react`
29
+ - anime.js v4 (MIT, ~10kb core): https://github.com/juliangarnier/anime.git — `npm i animejs`
30
+
25
31
  ## Reference Sources (taste, qualidade visual, motion)
26
32
 
27
33
  - Impeccable: https://github.com/pbakaus/impeccable.git
@@ -8,11 +8,13 @@ Se o usuário não especificar stack, assumir:
8
8
  - Tailwind CSS;
9
9
  - shadcn/ui;
10
10
  - Framer Motion ou Motion;
11
+ - GSAP (com ScrollTrigger/SplitText, 100% gratuito desde a v3.13) para scroll storytelling e timelines cinematográficas;
12
+ - anime.js v4 para microinterações imperativas leves e SVG motion;
11
13
  - React Bits;
12
14
  - React Three Fiber para projetos 3D;
13
15
  - Three.js para WebGL avançado;
14
16
  - lucide-react para ícones;
15
- - Recharts para dashboards SaaS.
17
+ - Recharts ou Bklit UI (charts shadcn) para dashboards SaaS.
16
18
 
17
19
  Se o projeto for simples, reduzir stack e evitar complexidade desnecessária.
18
20
 
@@ -29,17 +29,36 @@
29
29
  // placeholder stays in `.mcp.json`, and the summary explains how to
30
30
  // configure it later.
31
31
  //
32
+ // GLOBAL KEY STORE (`~/.claude/wizz-env.json`): a key the user already typed
33
+ // in ANY project is reused silently in every new install — the store is read
34
+ // by the installer only (C7 still holds: the runtime never reads it), and a
35
+ // hit is copied into the new project's `settings.local.json`. A key typed at
36
+ // the prompt is saved to the store too, so it is only ever asked once.
37
+ //
32
38
  // API (decomposed per E3 so each piece is unit-testable without a TTY):
33
39
  // extractEnvPlaceholders(mcps) — pure
34
40
  // resolveEnvVars(vars, opts) — I/O read (providers + prompt)
35
41
  // persistEnvValues(toPersist, opts) — I/O write (settings.local.json)
36
42
  // persistProjectEnv(projectDir, envRecord) — the actual writer, reusable
43
+ // persistGlobalEnv(storePath, envRecord) — writer do store global
37
44
  // promptMissingEnvVars(mcps, opts) — thin orchestrator of the above
38
45
 
39
46
  const path = require('node:path');
47
+ const os = require('node:os');
40
48
  const fs = require('../fs-native');
41
49
  const prompts = require('../prompts');
42
50
 
51
+ // Global key store, read by the INSTALLER only (never by the Claude Code
52
+ // runtime — C7 still holds). A value found here is copied into the project's
53
+ // `.claude/settings.local.json` at install time, which IS what reaches the
54
+ // MCP subprocess. This is what makes a key typed once in project A resolve
55
+ // silently in projects B, C, D... without ever living in the global
56
+ // settings.json `env` (which would expose it to every session of every
57
+ // project — the exact pattern the 360° audit flagged as a security critical).
58
+ function defaultGlobalEnvPath() {
59
+ return path.join(os.homedir(), '.claude', 'wizz-env.json');
60
+ }
61
+
43
62
  // Deliberately POSIX-strict (uppercase + underscore only): this both matches
44
63
  // standard env var naming and doubles as a defensive filter against false
45
64
  // positives like `{bin}` (no `$` prefix at all, so it never matches) or a
@@ -168,12 +187,51 @@ function createDotenvFileProvider(dotenvPath) {
168
187
  }
169
188
 
170
189
  /**
171
- * Try each provider in order, returning the first non-empty value found.
190
+ * The global key store (`~/.claude/wizz-env.json`, flat `{ "VAR": "value" }`
191
+ * map, chmod 600) as a provider. Read-only here — `persistGlobalEnv` is the
192
+ * writer. Marked `persistToProject: true`: unlike `process.env`, a value from
193
+ * this store is NOT in the runtime's environment, so the resolver must copy
194
+ * it into the project's `settings.local.json` for it to actually reach the
195
+ * MCP subprocess (C7).
196
+ * @param {string} storePath - Absolute path to the global store file
197
+ * @returns {{name: string, persistToProject: boolean, available: () => Promise<boolean>, get: (name: string) => Promise<string|undefined>}}
198
+ */
199
+ function createGlobalStoreProvider(storePath) {
200
+ let cache = null;
201
+
202
+ async function load() {
203
+ if (cache) return cache;
204
+ cache = {};
205
+ if (!storePath || !(await fs.pathExists(storePath))) return cache;
206
+ try {
207
+ const parsed = JSON.parse(await fs.readFile(storePath, 'utf8'));
208
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) cache = parsed;
209
+ } catch {
210
+ // Malformed/unreadable store — treat as empty, never throw.
211
+ cache = {};
212
+ }
213
+ return cache;
214
+ }
215
+
216
+ return {
217
+ name: 'global-store',
218
+ persistToProject: true,
219
+ available: async () => !!storePath && (await fs.pathExists(storePath)),
220
+ get: async (name) => {
221
+ const value = (await load())[name];
222
+ return typeof value === 'string' && value !== '' ? value : undefined;
223
+ },
224
+ };
225
+ }
226
+
227
+ /**
228
+ * Try each provider in order, returning the first non-empty value found and
229
+ * the provider that had it (so the caller can honor `persistToProject`).
172
230
  * A provider throwing or being unavailable is skipped, never fatal — one
173
231
  * broken provider must not block the chain (e.g. an unreadable `.env`).
174
232
  * @param {string} name - Var name to look up
175
233
  * @param {Array<Object>} providers
176
- * @returns {Promise<string|undefined>}
234
+ * @returns {Promise<{value: string, provider: Object}|undefined>}
177
235
  */
178
236
  async function findInProviders(name, providers) {
179
237
  for (const provider of providers || []) {
@@ -182,7 +240,7 @@ async function findInProviders(name, providers) {
182
240
  const isAvailable = typeof provider.available === 'function' ? await provider.available() : true;
183
241
  if (!isAvailable) continue;
184
242
  const value = await provider.get(name);
185
- if (value !== undefined && value !== null && value !== '') return value;
243
+ if (value !== undefined && value !== null && value !== '') return { value, provider };
186
244
  } catch {
187
245
  continue;
188
246
  }
@@ -221,7 +279,14 @@ function createDefaultPrompter() {
221
279
  * @param {boolean} [opts.interactive=false] - Whether to prompt for missing vars
222
280
  * @param {Array<Object>} [opts.providers] - Provider chain, tried in order
223
281
  * @param {(entry) => Promise<string|undefined>} [opts.prompter] - Injectable prompt fn
224
- * @returns {Promise<{filled: Array, skipped: Array, existing: Array, toPersist: Record<string,string>}>}
282
+ * @returns {Promise<{filled: Array, skipped: Array, existing: Array, imported: Array,
283
+ * toPersist: Record<string,string>, toPersistGlobal: Record<string,string>}>}
284
+ * `existing` = found in a runtime-visible source (process.env/.env), nothing
285
+ * to write. `imported` = found in a `persistToProject` provider (the global
286
+ * store): resolved without prompting, but must be written to the project's
287
+ * settings.local.json (included in `toPersist`). `toPersistGlobal` = the
288
+ * subset of typed answers that should ALSO be saved to the global store so
289
+ * the next project never asks.
225
290
  */
226
291
  async function resolveEnvVars(vars, opts = {}) {
227
292
  const { interactive = false, providers = [createProcessEnvProvider()], prompter = null } = opts;
@@ -229,7 +294,9 @@ async function resolveEnvVars(vars, opts = {}) {
229
294
  const filled = [];
230
295
  const skipped = [];
231
296
  const existing = [];
297
+ const imported = [];
232
298
  const toPersist = {};
299
+ const toPersistGlobal = {};
233
300
 
234
301
  for (const entry of vars || []) {
235
302
  // A default is resolved by the Claude Code runtime itself; asking would
@@ -242,7 +309,12 @@ async function resolveEnvVars(vars, opts = {}) {
242
309
 
243
310
  const found = await findInProviders(entry.name, providers);
244
311
  if (found !== undefined) {
245
- existing.push(entry);
312
+ if (found.provider && found.provider.persistToProject) {
313
+ imported.push(entry);
314
+ toPersist[entry.name] = found.value;
315
+ } else {
316
+ existing.push(entry);
317
+ }
246
318
  continue;
247
319
  }
248
320
 
@@ -259,9 +331,10 @@ async function resolveEnvVars(vars, opts = {}) {
259
331
 
260
332
  filled.push(entry);
261
333
  toPersist[entry.name] = answer;
334
+ toPersistGlobal[entry.name] = answer;
262
335
  }
263
336
 
264
- return { filled, skipped, existing, toPersist };
337
+ return { filled, skipped, existing, imported, toPersist, toPersistGlobal };
265
338
  }
266
339
 
267
340
  /**
@@ -352,23 +425,80 @@ async function persistEnvValues(toPersist, opts = {}) {
352
425
  return persistProjectEnv(projectDir, toPersist);
353
426
  }
354
427
 
428
+ /**
429
+ * Merge `envRecord` into the flat global store (`~/.claude/wizz-env.json`).
430
+ * Additive only — a key already present is never overwritten (same rule as
431
+ * `persistProjectEnv`; a prompt only ever fires for a var no provider had,
432
+ * so an overwrite here would always mean clobbering something newer). File
433
+ * is chmod 600 after any write that changed it, same best-effort semantics
434
+ * as the project writer.
435
+ *
436
+ * @param {string} storePath - Absolute path to the global store file
437
+ * @param {Record<string,string>} envRecord - Vars to merge
438
+ * @returns {Promise<string|null>} Path written/merged, or null when empty
439
+ */
440
+ async function persistGlobalEnv(storePath, envRecord) {
441
+ if (!storePath || !envRecord || Object.keys(envRecord).length === 0) return null;
442
+
443
+ await fs.ensureDir(path.dirname(storePath));
444
+
445
+ let store = {};
446
+ if (await fs.pathExists(storePath)) {
447
+ try {
448
+ const parsed = JSON.parse(await fs.readFile(storePath, 'utf8'));
449
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) store = parsed;
450
+ } catch {
451
+ // A corrupt store must never eat keys the user just typed: keep the
452
+ // broken file aside and start a fresh store with the new values.
453
+ try {
454
+ await fs.rename(storePath, `${storePath}.bak`);
455
+ } catch {
456
+ // Even the rename failing must not block the install.
457
+ }
458
+ }
459
+ }
460
+
461
+ let changed = false;
462
+ for (const [key, value] of Object.entries(envRecord)) {
463
+ if (Object.prototype.hasOwnProperty.call(store, key)) continue;
464
+ store[key] = value;
465
+ changed = true;
466
+ }
467
+
468
+ if (changed) {
469
+ await fs.writeJson(storePath, store, { spaces: 2 });
470
+ if (process.platform !== 'win32') {
471
+ try {
472
+ await fs.chmod(storePath, 0o600);
473
+ } catch {
474
+ // Best-effort, same as persistProjectEnv.
475
+ }
476
+ }
477
+ }
478
+
479
+ return storePath;
480
+ }
481
+
355
482
  /**
356
483
  * Render the DX summary (E6) — the highest-value part of this feature: turn
357
484
  * a skipped var from a dead end into a 30-second fix. One line per var,
358
485
  * grouped by outcome. Never includes a raw secret value, only names/status.
359
- * @param {{filled: Array, skipped: Array, existing: Array}} resolved
486
+ * @param {{filled: Array, skipped: Array, existing: Array, imported: Array}} resolved
360
487
  * @returns {string|null} Formatted block, or null when there is nothing to say
361
488
  */
362
- function formatSummary({ filled, skipped, existing }) {
363
- const total = (filled?.length || 0) + (skipped?.length || 0) + (existing?.length || 0);
489
+ function formatSummary({ filled, skipped, existing, imported }) {
490
+ const total = (filled?.length || 0) + (skipped?.length || 0) + (existing?.length || 0) + (imported?.length || 0);
364
491
  if (total === 0) return null;
365
492
 
366
493
  const lines = [];
367
494
  for (const entry of existing || []) {
368
495
  lines.push(` ✓ ${entry.name.padEnd(28)} já existia no ambiente`);
369
496
  }
497
+ for (const entry of imported || []) {
498
+ lines.push(` ✓ ${entry.name.padEnd(28)} importada do global (~/.claude/wizz-env.json)`);
499
+ }
370
500
  for (const entry of filled || []) {
371
- lines.push(` ✓ ${entry.name.padEnd(28)} configurada agora (.claude/settings.local.json)`);
501
+ lines.push(` ✓ ${entry.name.padEnd(28)} configurada agora (+ salva no global p/ próximos projetos)`);
372
502
  }
373
503
  for (const entry of skipped || []) {
374
504
  if (entry.hasDefault) {
@@ -403,18 +533,26 @@ function formatSummary({ filled, skipped, existing }) {
403
533
  * @param {string} opts.projectDir - Project root (for persistence + the
404
534
  * default `.env` provider)
405
535
  * @param {boolean} [opts.interactive=false]
406
- * @param {Array<Object>} [opts.providers] - Defaults to `[processEnv, dotenvFile(<projectDir>/.env)]`
536
+ * @param {Array<Object>} [opts.providers] - Defaults to `[processEnv,
537
+ * dotenvFile(<projectDir>/.env), globalStore(~/.claude/wizz-env.json)]`
538
+ * @param {string} [opts.globalEnvPath] - Global store path (default
539
+ * `~/.claude/wizz-env.json`); used for both the default provider chain and
540
+ * the save-on-prompt write. Injectable so tests never touch the real home.
407
541
  * @param {(entry) => Promise<string|undefined>} [opts.prompter] - Defaults to
408
542
  * the masked `password()` prompter
409
- * @returns {Promise<{filled: Array, skipped: Array, existing: Array, envFile: string|null}>}
543
+ * @returns {Promise<{filled: Array, skipped: Array, existing: Array, imported: Array, envFile: string|null}>}
410
544
  */
411
545
  async function promptMissingEnvVars(mcps, opts = {}) {
412
- const { projectDir, interactive = false, providers, prompter } = opts;
546
+ const { projectDir, interactive = false, providers, prompter, globalEnvPath = defaultGlobalEnvPath() } = opts;
413
547
 
414
548
  const vars = extractEnvPlaceholders(mcps);
415
- if (vars.length === 0) return { filled: [], skipped: [], existing: [], envFile: null };
549
+ if (vars.length === 0) return { filled: [], skipped: [], existing: [], imported: [], envFile: null };
416
550
 
417
- const resolvedProviders = providers || [createProcessEnvProvider(), createDotenvFileProvider(path.join(projectDir, '.env'))];
551
+ const resolvedProviders = providers || [
552
+ createProcessEnvProvider(),
553
+ createDotenvFileProvider(path.join(projectDir, '.env')),
554
+ createGlobalStoreProvider(globalEnvPath),
555
+ ];
418
556
  const resolvedPrompter = interactive ? prompter || createDefaultPrompter() : null;
419
557
 
420
558
  const resolved = await resolveEnvVars(vars, {
@@ -428,10 +566,27 @@ async function promptMissingEnvVars(mcps, opts = {}) {
428
566
  envFile = await persistEnvValues(resolved.toPersist, { projectDir, target: 'settings-local' });
429
567
  }
430
568
 
569
+ // Typed answers also go to the global store so the NEXT project resolves
570
+ // them silently. Failure here must never block the install — the project
571
+ // write above already succeeded, which is what this install needs.
572
+ if (Object.keys(resolved.toPersistGlobal).length > 0) {
573
+ try {
574
+ await persistGlobalEnv(globalEnvPath, resolved.toPersistGlobal);
575
+ } catch {
576
+ // Global save is a convenience for future installs, never a blocker.
577
+ }
578
+ }
579
+
431
580
  const summary = formatSummary(resolved);
432
581
  if (summary) await prompts.log.info(summary);
433
582
 
434
- return { filled: resolved.filled, skipped: resolved.skipped, existing: resolved.existing, envFile };
583
+ return {
584
+ filled: resolved.filled,
585
+ skipped: resolved.skipped,
586
+ existing: resolved.existing,
587
+ imported: resolved.imported,
588
+ envFile,
589
+ };
435
590
  }
436
591
 
437
592
  module.exports = {
@@ -439,8 +594,11 @@ module.exports = {
439
594
  resolveEnvVars,
440
595
  persistEnvValues,
441
596
  persistProjectEnv,
597
+ persistGlobalEnv,
442
598
  promptMissingEnvVars,
443
599
  createProcessEnvProvider,
444
600
  createDotenvFileProvider,
601
+ createGlobalStoreProvider,
602
+ defaultGlobalEnvPath,
445
603
  formatSummary,
446
604
  };
@@ -19,6 +19,7 @@
19
19
  // a `claude mcp add` command renderer for the recommend path.
20
20
 
21
21
  const path = require('node:path');
22
+ const os = require('node:os');
22
23
  const crypto = require('node:crypto');
23
24
  const fs = require('../fs-native');
24
25
  const { defaultExec } = require('./cli-config');
@@ -325,6 +326,48 @@ async function partitionAlreadyConfigured({ projectDir, mcps }) {
325
326
  return { toPrepare, alreadyConfigured };
326
327
  }
327
328
 
329
+ /**
330
+ * Split resolved MCP entries into those still worth offering/installing in
331
+ * the project and those the user already configured GLOBALLY (user scope, the
332
+ * `mcpServers` key of `~/.claude.json`). A server configured there is live in
333
+ * every project already — usually with the real key embedded — so writing the
334
+ * registry's `${VAR}`-placeholder copy into the project `.mcp.json` would at
335
+ * best duplicate it and at worst shadow a working global config with a broken
336
+ * placeholder one, then prompt the user for a key they already provided.
337
+ *
338
+ * Read failures (missing/malformed `~/.claude.json`) fall back to "nothing is
339
+ * global", the safe default: worst case the user sees the old behavior.
340
+ *
341
+ * @param {Object} args
342
+ * @param {Array<{id: string}>} args.mcps - Resolved MCP entries to partition
343
+ * @param {string} [args.claudeJsonPath] - Override of `~/.claude.json` (tests)
344
+ * @returns {Promise<{toInstall: Array<Object>, globallyConfigured: string[]}>}
345
+ */
346
+ async function partitionGloballyConfigured({ mcps, claudeJsonPath }) {
347
+ if (!mcps || mcps.length === 0) return { toInstall: [], globallyConfigured: [] };
348
+
349
+ const file = claudeJsonPath || path.join(os.homedir(), '.claude.json');
350
+ let globalIds = new Set();
351
+ if (await fs.pathExists(file)) {
352
+ try {
353
+ const config = JSON.parse(await fs.readFile(file, 'utf8'));
354
+ const servers =
355
+ config && typeof config === 'object' && config.mcpServers && !Array.isArray(config.mcpServers) ? config.mcpServers : null;
356
+ if (servers) globalIds = new Set(Object.keys(servers));
357
+ } catch {
358
+ globalIds = new Set();
359
+ }
360
+ }
361
+
362
+ const toInstall = [];
363
+ const globallyConfigured = [];
364
+ for (const mcp of mcps) {
365
+ if (mcp && mcp.id && globalIds.has(mcp.id)) globallyConfigured.push(mcp.id);
366
+ else toInstall.push(mcp);
367
+ }
368
+ return { toInstall, globallyConfigured };
369
+ }
370
+
328
371
  /**
329
372
  * Merge the chosen MCP entries into `<projectDir>/.mcp.json`, additively.
330
373
  * Reads any existing file (preserving unknown keys and existing servers),
@@ -442,6 +485,7 @@ module.exports = {
442
485
  prepareMcp,
443
486
  prepareMcps,
444
487
  partitionAlreadyConfigured,
488
+ partitionGloballyConfigured,
445
489
  resolveBinPath,
446
490
  shellQuote,
447
491
  substituteBin,
@@ -17,7 +17,7 @@ const {
17
17
  bundledTargetWarnings,
18
18
  } = require('./modules/channel-plan');
19
19
  const channelResolver = require('./modules/channel-resolver');
20
- const { resolveMcps } = require('./modules/mcp-config');
20
+ const { resolveMcps, partitionGloballyConfigured } = require('./modules/mcp-config');
21
21
  const { resolveClis, detectClis } = require('./modules/cli-config');
22
22
  const prompts = require('./prompts');
23
23
  const { parseSetEntries } = require('./set-overrides');
@@ -571,9 +571,21 @@ class UI {
571
571
  if (!selectedModules.includes('bmm')) return { toWrite: [], toRecommend: [] };
572
572
 
573
573
  const registry = this._loadSkillsRegistry();
574
- const resolved = resolveMcps(registry, selectedAreas);
574
+ let resolved = resolveMcps(registry, selectedAreas);
575
575
  if (resolved.length === 0) return { toWrite: [], toRecommend: [] };
576
576
 
577
+ // A server the user already configured globally (user scope, `mcpServers`
578
+ // in ~/.claude.json) is live in every project — offering it again would
579
+ // duplicate config and re-prompt for a key that already works. Filter it
580
+ // out of every path below (multiselect, --mcps, --yes) with an info line
581
+ // so nothing disappears silently.
582
+ const { toInstall, globallyConfigured } = await partitionGloballyConfigured({ mcps: resolved });
583
+ if (globallyConfigured.length > 0) {
584
+ await prompts.log.info(`MCPs já configurados no seu Claude global (~/.claude.json), pulados: ${globallyConfigured.join(', ')}`);
585
+ resolved = toInstall;
586
+ if (resolved.length === 0) return { toWrite: [], toRecommend: [] };
587
+ }
588
+
577
589
  const byId = new Map(resolved.map((m) => [m.id, m]));
578
590
  const split = (writeIds) => {
579
591
  const writeSet = new Set(writeIds);