synthesisui 0.16.360 → 0.16.361

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.
@@ -676,12 +676,21 @@ export function renderNextStep(next, freshSession) {
676
676
  * pessoa tivesse perdido uma opção da tela. */
677
677
  if (next.open?.length)
678
678
  lines.push(...bodyWrapped(next.open.length === 1
679
- ? "That flag lets it write without asking each time. Drop it to approve every change yourself."
680
- : "Those flags let it write without asking each time. Drop them to approve every change yourself.").map(paint.dim));
681
- if (freshSession)
682
- lines.push(...bodyWrapped(freshSession.mcp
683
- ? "A new session is what loads the skills and tools just installed, and the project's tools ask for approval once - say yes."
684
- : "A new session is what loads the skills just installed.").map(paint.dim));
679
+ ? "Drop the flag to approve each write yourself."
680
+ : "Drop the flags to approve each write yourself.").map(paint.dim));
681
+ if (freshSession) {
682
+ const list = freshSession.loads;
683
+ const said = list.length === 0
684
+ ? "and"
685
+ : list.length === 1
686
+ ? list[0]
687
+ : `${list.slice(0, -1).join(", ")} and ${list.at(-1)}`;
688
+ lines.push(...bodyWrapped(list.length === 0
689
+ ? "A new session reads the wiring this run changed."
690
+ : `This run moved ${said}, and a new session is what reads ${list.length === 1 ? "it" : "them"}.`).map(paint.dim));
691
+ if (freshSession.mcp)
692
+ lines.push(...bodyWrapped("The project's tools ask for approval once - say yes.").map(paint.dim));
693
+ }
685
694
  return lines.join("\n");
686
695
  }
687
696
  /**
@@ -3,7 +3,7 @@ import { dirname, join } from "node:path";
3
3
  import { wireAgent } from "../agent-wiring.js";
4
4
  import { blockHomes, syncClaudeMd } from "../claude-md.js";
5
5
  import { resolveRegistry } from "../config.js";
6
- import { body, paint, section, snippet } from "../output.js";
6
+ import { body, bodyWrapped, paint, section, snippet } from "../output.js";
7
7
  import { readShellAnswer, rememberShellNo } from "../shell-answer.js";
8
8
  import { existingRc, hasHook, pinnedInHook, rcPathFor, shellFrom, shellSnippet, withHook, } from "../shell-hook.js";
9
9
  import { SKILLS } from "../skills.js";
@@ -173,8 +173,12 @@ version) {
173
173
  return;
174
174
  }
175
175
  console.log("");
176
- console.log(body("Your editor tells you when this repo drifts, once a session. Your terminal does not, and that is where most people notice something is stale."));
177
- console.log(body(paint.faint(`Adding it writes one block to ${rc}. It stays silent when nothing is wrong, never blocks your prompt, and checks at most once an hour.`)));
176
+ /**
177
+ * A OFERTA EM DUAS LINHAS - eram cinco, e ela ficava entre a lista do que foi instalado e a ação
178
+ * recomendada, empurrando para baixo a única coisa que a pessoa procurava. O que ela precisa dizer
179
+ * é o que ganha, o que a gente escreve e onde: nada disso saiu.
180
+ */
181
+ console.log(bodyWrapped(`Your terminal can warn you when this repo drifts - one block in ${rc}, silent unless something is wrong, at most once an hour.`).join("\n"));
178
182
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
179
183
  console.log(body(paint.blue(" npx synthesisui@latest connect --shell")));
180
184
  return;
@@ -228,6 +232,37 @@ export async function connect(opts) {
228
232
  // after them, not before.
229
233
  const contract = await syncClaudeMd(root);
230
234
  console.log(section("Connected"));
235
+ /**
236
+ * A LISTA DA TELA, JUNTADA ANTES DE IMPRIMIR - e é isto que faz a rodada silenciosa caber numa
237
+ * linha.
238
+ *
239
+ * O QUE O CLIENTE GANHA: ele lê o que MUDOU, e o que não mudou vira uma linha só. Medido em 03/09
240
+ * na segunda rodada deste comando: 41 linhas na tela, das quais 10 diziam `already had it` /
241
+ * `already current` - um quarto da tela informando ausência de novidade, exatamente entre a pessoa
242
+ * e a única instrução que ela procurava.
243
+ *
244
+ * A DISTINÇÃO NÃO SE PERDE: cada peça que mudou continua nomeada, com o seu detalhe embaixo. O que
245
+ * colapsa é o silêncio, que não tem detalhe para dar.
246
+ */
247
+ const rows = [];
248
+ const row = (moved, line, under) => {
249
+ rows.push({ moved, line, ...(under ? { under } : {}) });
250
+ };
251
+ const flush = () => {
252
+ for (const r of rows) {
253
+ if (!r.moved)
254
+ continue;
255
+ console.log(body(r.line));
256
+ if (r.under)
257
+ console.log(snippet([r.under]));
258
+ }
259
+ const quiet = rows.filter((r) => !r.moved).length;
260
+ if (quiet === 0)
261
+ return;
262
+ console.log(body(paint.dim(quiet === rows.length
263
+ ? "· nothing to change - this environment is already current"
264
+ : `· ${quiet} other ${quiet === 1 ? "piece" : "pieces"} already current`)));
265
+ };
231
266
  /**
232
267
  * E O QUE FALTA, DITO EM VOZ ALTA - porque este comando estava mentindo por omissão.
233
268
  *
@@ -240,45 +275,61 @@ export async function connect(opts) {
240
275
  * `CLAUDE.md`, achou o bloco vazio, e não havia nada dizendo que faltava um comando. Lacuna
241
276
  * silenciosa é o que faz um produto correto parecer quebrado.
242
277
  *
243
- * E SÃO DOIS CAMINHOS, não um. A primeira versão desta mensagem mandava `add <slug>` e só - o que
244
- * desorienta justamente quem acabou de rodar `connect` num projeto que AINDA NÃO TEM sistema, que é
245
- * a primeira corrida e o caso mais comum. Para essa pessoa o passo seguinte é a skill que este mesmo
246
- * comando acabou de instalar, e não um comando de terminal. Quem já tem sistema na plataforma é o
247
- * outro caso, e ele também é dito.
278
+ * E ELE DIZ A LACUNA, NÃO O QUE FAZER - corrigido em 03/09, com o dono lendo a própria tela.
279
+ *
280
+ * Ele carregava os dois caminhos ("ask the agent for `/sui-init`" e "`synthesisui add <slug>`"), e
281
+ * o resultado era UMA TELA COM DUAS INSTRUÇÕES para o mesmo passo: esta linha mandava pedir uma
282
+ * slash command, e a ação recomendada, quinze linhas abaixo, mandava dizer uma frase. Quem lê não
283
+ * tem como saber qual das duas é a certa.
284
+ *
285
+ * E A FRASE É A CERTA, por decisão de produto (dono, 03/09): *"as skills são chamadas de acordo
286
+ * com a necessidade que o Claude vai encontrando, não por comando nosso diretamente"*. Nomear
287
+ * `/sui-init` ensina o cliente a dirigir o agente por comando, que é o oposto de como a esteira
288
+ * foi desenhada - e amarra a instrução ao nome de uma skill que pode ser dividida amanhã.
289
+ *
290
+ * `nextStepFor` JÁ COBRE OS DOIS CAMINHOS, e com a mesma distinção: medido sem sistema instalado
291
+ * leva a `list --mine` + `add <slug>`; nunca medido leva à frase. Então isto para de repeti-los e
292
+ * fica com o que só ele diz - a CONSEQUÊNCIA da lacuna, que é o que fez alguém perder tempo em
293
+ * 20/08 achando o bloco do `CLAUDE.md` vazio sem nada explicando por quê.
248
294
  */
249
295
  const anyInstalled = (await installedSlugs(root).catch(() => [])).length > 0;
250
296
  if (!anyInstalled)
251
- console.log(body("· no design system installed here yet - the agent has no index, and memory has nothing to belong to.\n This repo has none yet: ask the agent for `/sui-init`, or `/sui-import-ds` to turn this project into one.\n You already have one: `synthesisui list` shows them, `synthesisui add <slug>` brings it in."));
297
+ console.log(bodyWrapped("· no design system installed here yet - the agent has no index, and memory has nothing to belong to.").join("\n"));
252
298
  /**
253
299
  * O QUE ESTE CLI REESCREVEU NA PASTA DO SISTEMA - dito primeiro, porque é o que a pessoa não sabia
254
300
  * que estava devendo. Ela rodou `connect` para atualizar a fiação; os arquivos do install estarem
255
301
  * velhos era invisível, e o `upgrade` não alcançava (ver `refreshInstall`).
256
302
  */
257
303
  if (refreshed)
258
- console.log(body(`✓ _synthesisui/ds/${refreshed.slug}/ rewritten by this CLI${refreshed.was
304
+ row(true, `✓ _synthesisui/ds/${refreshed.slug}/ rewritten by this CLI${refreshed.was
259
305
  ? ` - it was written by ${refreshed.was}`
260
- : " - it carried no CLI version, so it predates this"}`));
306
+ : " - it carried no CLI version, so it predates this"}`);
261
307
  if (want.hook) {
262
- console.log(body(wired.hook === "added"
263
- ? "✓ .claude/settings.json the check now runs after every write"
264
- : wired.hook === "updated"
265
- ? /**
266
- * A LINHA QUE FALTAVA, e a ausência dela fazia o comando mentir: rodando o 0.16.157, a
267
- * saída dizia "already had it" e imprimia `npx synthesisui@0.16.153 hook` embaixo
268
- * (dono, 06/08). Quem lê "already had it" fecha o terminal.
269
- */
270
- `✓ .claude/settings.json the check moved from ${wired.was?.match(/synthesisui@(\d+\.\d+\.\d+)/)?.[1] ?? "an older version"} to this one`
271
- : "· .claude/settings.json already had it"));
272
- console.log(snippet([wired.command]));
308
+ row(wired.hook !== "already there", (() => {
309
+ return wired.hook === "added"
310
+ ? "✓ .claude/settings.json the check now runs after every write"
311
+ : wired.hook === "updated"
312
+ ? /**
313
+ * A LINHA QUE FALTAVA, e a ausência dela fazia o comando mentir: rodando o 0.16.157, a
314
+ * saída dizia "already had it" e imprimia `npx synthesisui@0.16.153 hook` embaixo
315
+ * (dono, 06/08). Quem lê "already had it" fecha o terminal.
316
+ */
317
+ `✓ .claude/settings.json the check moved from ${wired.was?.match(/synthesisui@(\d+\.\d+\.\d+)/)?.[1] ?? "an older version"} to this one`
318
+ : "· .claude/settings.json already had it";
319
+ })(), wired.command);
273
320
  /**
274
321
  * A SEGUNDA COSTURA, dita por nome. As onze ferramentas MCP são PULL e o hook de escrita roda
275
322
  * DEPOIS de uma escrita - nenhum dos dois chega a tempo de dizer "você não está logado nesta
276
323
  * máquina" ou "este sistema não sabe de onde foi medido". O `SessionStart` é o único que chega.
277
324
  */
278
325
  if (wired.session !== "skipped")
279
- console.log(body(wired.session === "already there"
326
+ row(wired.session !== "already there", wired.session === "already there"
280
327
  ? "· .claude/settings.json the session check was already there"
281
- : `✓ .claude/settings.json every session now opens with what this environment is missing${wired.session === "updated" ? ", pointed at this version" : ""}`));
328
+ : wired.session === "updated"
329
+ ? /** A distinção `added` x `updated` vale desde 06/08: um estado que não se
330
+ * distingue de "nada a fazer" faz a instrução de atualizar mentir. */
331
+ "✓ .claude/settings.json the session check moved to this one"
332
+ : "✓ .claude/settings.json each session opens with what is missing");
282
333
  }
283
334
  /**
284
335
  * OS CAMINHOS DE MCP, POR NOME - e o número vem da LISTA, não da minha memória.
@@ -289,13 +340,13 @@ export async function connect(opts) {
289
340
  */
290
341
  if (want.mcp && wired.mcp !== "skipped") {
291
342
  for (const one of wired.mcp)
292
- console.log(body(one.status === "added"
293
- ? `✓ ${one.path.padEnd(22)} ${MCP_TOOL_COUNT} tools, so the agent can ask instead of guess`
343
+ row(one.status !== "already there", one.status === "added"
344
+ ? `✓ ${one.path.padEnd(22)} ${MCP_TOOL_COUNT} tools the agent can ask`
294
345
  : one.status === "updated"
295
346
  ? /** Uma entrada pinada antes de o MCP flutuar ficava pinada para sempre - nenhum
296
347
  * `connect` a soltava, e o agente segurava um leitor velho sem saber. */
297
- `✓ ${one.path.padEnd(22)} unpinned - it now follows the published reader`
298
- : `· ${one.path.padEnd(22)} already had it`));
348
+ `✓ ${one.path.padEnd(22)} unpinned - it follows the published reader`
349
+ : `· ${one.path.padEnd(22)} already had it`);
299
350
  }
300
351
  /**
301
352
  * ONDE O BLOCO CAIU, DITO POR NOME.
@@ -311,7 +362,7 @@ export async function connect(opts) {
311
362
  */
312
363
  for (const home of await blockHomes(root)) {
313
364
  const moved = contract.changed.includes(home);
314
- console.log(body(`${moved ? "✓" : "·"} ${home.padEnd(22)} ${moved
365
+ row(moved, `${moved ? "✓" : "·"} ${home.padEnd(22)} ${moved
315
366
  ? home === "CLAUDE.md"
316
367
  ? /**
317
368
  * A FRASE DIZ O QUE O BLOCO CARREGA, e ela descrevia um conteúdo que não existia.
@@ -323,10 +374,10 @@ export async function connect(opts) {
323
374
  contract.count === 0
324
375
  ? "how to turn this repo into your system"
325
376
  : "rewritten for what is installed"
326
- : "the same rules, where this agent reads them"
377
+ : "the same rules, where this agent reads"
327
378
  : contract.count === 0
328
379
  ? "already says how to start"
329
- : "already says what is installed"}`));
380
+ : "already says what is installed"}`);
330
381
  }
331
382
  /**
332
383
  * The fourth layer, and the one that had no installer at all: the import
@@ -369,21 +420,26 @@ export async function connect(opts) {
369
420
  if (had == null)
370
421
  continue;
371
422
  await rm(dir, { recursive: true, force: true }).catch(() => { });
372
- console.log(body(`✕ /${legacy.padEnd(21)} removed - renamed to /sui-import-ds`));
423
+ row(true, `✕ /${legacy.padEnd(21)} removed - renamed to /sui-import-ds`);
373
424
  }
425
+ /** Se ALGUMA skill mudou nesta rodada - é o que autoriza a frase da sessão nova a citá-las. */
426
+ let skillsMoved = false;
374
427
  for (const skill of skills) {
375
428
  const skillPath = join(root, skill.path);
376
429
  const before = await readFile(skillPath, "utf8").catch(() => null);
377
430
  if (before !== skill.source) {
378
431
  await mkdir(dirname(skillPath), { recursive: true });
379
432
  await writeFile(skillPath, skill.source, "utf8");
433
+ skillsMoved = true;
380
434
  }
381
- console.log(body(before === skill.source
435
+ row(before !== skill.source, before === skill.source
382
436
  ? `· ${skill.label.padEnd(22)} already current`
383
437
  : before == null
384
438
  ? `✓ ${skill.label.padEnd(22)} ${skill.what}`
385
- : `✓ ${skill.label.padEnd(22)} updated to this CLI's pipeline`));
439
+ : `✓ ${skill.label.padEnd(22)} updated to this CLI's pipeline`);
386
440
  }
441
+ /** A tela sai agora, junta: o que mudou por nome, e o silêncio numa linha. */
442
+ flush();
387
443
  /**
388
444
  * O CI: escrito quando pedido, e NÃO MAIS OFERECIDO aqui.
389
445
  *
@@ -419,9 +475,29 @@ export async function connect(opts) {
419
475
  const mcpMoved = Boolean(want.mcp &&
420
476
  Array.isArray(wired.mcp) &&
421
477
  wired.mcp.some((m) => moved(m.status)));
478
+ /**
479
+ * O QUE ESTA RODADA MOVEU, PELO NOME - e são os nomes que a pessoa acabou de ler nas linhas com
480
+ * `✓`, não os nossos nomes internos. Uma razão que ela pode conferir contra a própria tela.
481
+ */
482
+ const bothChecks = moved(wired.hook) && moved(wired.session);
483
+ const loads = [
484
+ /** Os dois checks juntos viram "the checks": nomeá-los separados custava uma linha inteira da
485
+ * tela para uma distinção que não muda nada do que a pessoa faz em seguida. */
486
+ ...(bothChecks
487
+ ? ["the checks"]
488
+ : moved(wired.hook)
489
+ ? ["the write check"]
490
+ : moved(wired.session)
491
+ ? ["the session check"]
492
+ : []),
493
+ ...(mcpMoved ? ["the tools"] : []),
494
+ ...(skillsMoved ? ["the skills"] : []),
495
+ ];
422
496
  await reportWhatIsLeft(root, {
423
497
  cli: opts.version,
424
- ...(needsRestart ? { freshSession: { mcp: mcpMoved } } : {}),
498
+ ...(needsRestart || skillsMoved
499
+ ? { freshSession: { loads, mcp: mcpMoved } }
500
+ : {}),
425
501
  });
426
502
  /**
427
503
  * O CUSTO DO HOOK, DITO SEM UM NÚMERO QUE NÃO É NOSSO.
@@ -437,11 +513,20 @@ export async function connect(opts) {
437
513
  * que se sustenta.
438
514
  */
439
515
  if (wired.command.startsWith("npx synthesisui@")) {
516
+ /**
517
+ * O CUSTO DO HOOK, EM UMA LINHA - eram cinco, e elas fechavam a tela.
518
+ *
519
+ * Medido em 03/09: a tela do `connect` tinha 41 linhas, e este parágrafo era 5 delas, na última
520
+ * posição, falando de resolução de pacote npm. Para quem acabou de instalar, é o assunto mais
521
+ * distante do design system dele possível, no momento em que ele deveria estar abrindo o agente.
522
+ *
523
+ * A METADE QUE FICA É A ACIONÁVEL. O diagnóstico completo - por que o npx custa, o que foi
524
+ * medido, quanto o caminho local ganhou - vive na documentação do comando, guardado por
525
+ * `apps/web/src/lib/cli/commands.spec.ts`.
526
+ */
440
527
  console.log("");
441
- console.log(body(paint.dim("The hook runs through npx, which resolves this package against the")));
442
- console.log(body(paint.dim("registry on every edit - that wait is the network, not the check itself")));
443
- console.log(body(paint.dim("(the analysis is about 80ms). Adding synthesisui to your devDependencies")));
444
- console.log(body(paint.dim("makes npx resolve it locally instead, which is several times faster;")));
445
- console.log(body(paint.dim("run this again afterwards and it will switch by itself.")));
528
+ console.log(bodyWrapped("The hook resolves against the npm registry on each edit - that is the wait, not the check. Add synthesisui to your devDependencies and run this again; it switches by itself to local, several times faster.")
529
+ .map(paint.dim)
530
+ .join("\n"));
446
531
  }
447
532
  }
package/dist/skills.js CHANGED
@@ -36,7 +36,7 @@ export const SKILLS = [
36
36
  path: ADAPT_SKILL_PATH,
37
37
  source: ADAPT_SKILL,
38
38
  label: "/sui-adapt",
39
- what: "one component against the system, and what to do about it",
39
+ what: "one component against the system",
40
40
  },
41
41
  /**
42
42
  * A DE CONSTRUÇÃO, e ela é a que responde ao pedido mais comum de todos:
@@ -52,7 +52,7 @@ export const SKILLS = [
52
52
  path: COMPOSE_SKILL_PATH,
53
53
  source: COMPOSE_SKILL,
54
54
  label: "/sui-compose",
55
- what: "build what the system does not have, layer by layer",
55
+ what: "build what the system does not have",
56
56
  },
57
57
  /**
58
58
  * A DE LIGAR O SISTEMA NO APP, e ela é a única aqui que fecha um passo que a esteira já sabia
@@ -67,6 +67,6 @@ export const SKILLS = [
67
67
  path: CONFIGURE_SKILL_PATH,
68
68
  source: CONFIGURE_SKILL,
69
69
  label: "/sui-configure-ds",
70
- what: "the system wired into the app, so the tokens reach the browser",
70
+ what: "the tokens, wired into your app",
71
71
  },
72
72
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.360",
3
+ "version": "0.16.361",
4
4
  "description": "Bring SynthesisUI design systems into any project - tokens, typed components, whole pages and an agent-ready CLAUDE.md manifest.",
5
5
  "type": "module",
6
6
  "bin": {