synthesisui 0.16.241 → 0.16.242

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.
@@ -7,6 +7,7 @@ import { readToken, resolveRegistry } from "../config.js";
7
7
  import { unsentEvents } from "../doctor/ledger.js";
8
8
  import { CHECKER_SINCE, installedBehind, MATERIALISER_SINCE, READER_SINCE, } from "../install-marks.js";
9
9
  import { measuredScope } from "../measured-scope.js";
10
+ import { body } from "../output.js";
10
11
  import { SKILLS } from "../skills.js";
11
12
  /**
12
13
  * QUAL CLI MEDIU O CENSO EM DISCO - e era o `reader`, um inteiro, até 11/08.
@@ -486,17 +487,54 @@ export async function misalignments(root, opts = {}) {
486
487
  items.push(remote);
487
488
  return items;
488
489
  }
490
+ /**
491
+ * O ESTADO DE QUEM AINDA NÃO TEM SISTEMA - o silêncio que custava a jornada inteira.
492
+ *
493
+ * `localMisalignments` devolve lista VAZIA quando não há sistema instalado, e a razão está escrita
494
+ * lá em cima: um repo sem DS não está desalinhado, ele só não começou. Correto - e o efeito é que a
495
+ * pessoa que acabou de rodar `connect`, que é exatamente quem mais precisa de uma instrução, abre a
496
+ * primeira sessão do agente em silêncio absoluto. A frase do próximo passo mora hoje na aba do
497
+ * browser que ela acabou de deixar; se fechou a aba, ou voltou no dia seguinte, não existe mais
498
+ * nada dizendo o que fazer.
499
+ *
500
+ * Então isto não é um desalinho e não entra naquela lista: é uma frase de ESTADO, e ela fala em
501
+ * forma de CONVERSA. "Peça ao seu agente: importe meu design system" é executável por quem está
502
+ * lendo - um comando decorado não é, e a esta altura ela nem sabe que comandos existem.
503
+ *
504
+ * DOIS ESTADOS, e não três. O terceiro que o plano previa - "instalado mas não fiado" - ficou de
505
+ * fora com motivo medido: a única leitura confiável de fiação (`readWiring`, no doctor) varre o
506
+ * repositório inteiro, e isto roda em TODA abertura de sessão. Um aviso que custa um walk completo
507
+ * a cada sessão é um aviso que alguém desliga.
508
+ */
509
+ export async function nextStepFor(root) {
510
+ const locks = (await locksIn(root)).filter((l) => l.slug && !l.adopted);
511
+ if (locks.length > 0)
512
+ return null;
513
+ const measured = await readFile(join(root, "_synthesisui", "census.json"), "utf8").then(() => true, () => false);
514
+ return measured
515
+ ? 'This repo has been measured but has no design system yet. Ask me: "continue the import."'
516
+ : 'This repo has no design system contract yet. Ask me: "import my design system."';
517
+ }
489
518
  /**
490
519
  * A CAUDA DE UM COMANDO QUE TERMINOU: o que ainda está fora, ou nada.
491
520
  *
492
521
  * Muda quando não falta nada, que é o caso normal e é o que a mantém legível. Ela existe porque
493
522
  * `sync`, `connect` e `upgrade` deixavam a pessoa sem saber se tinha acabado - e a resposta exigia
494
523
  * lembrar de um sexto comando (dono, 07/08).
524
+ *
525
+ * E QUANDO NÃO HÁ SISTEMA NENHUM ela dizia menos ainda: nada. O `connect` terminava mudo no único
526
+ * repo em que ele é a primeira coisa que alguém roda.
495
527
  */
496
528
  export async function reportWhatIsLeft(root, opts = {}) {
497
529
  const items = await misalignments(root, opts).catch(() => []);
498
- if (items.length === 0)
530
+ if (items.length === 0) {
531
+ const next = await nextStepFor(root).catch(() => null);
532
+ if (next) {
533
+ console.log("");
534
+ console.log(body(next));
535
+ }
499
536
  return;
537
+ }
500
538
  console.log("");
501
539
  console.log(describeMisalignments(items, "after"));
502
540
  }
@@ -506,6 +544,21 @@ export async function align(opts) {
506
544
  ...(opts.cli ? { cli: opts.cli } : {}),
507
545
  });
508
546
  const text = describeMisalignments(items, opts.shell ? "shell" : "session");
509
- if (text)
547
+ if (text) {
510
548
  console.log(text);
549
+ return;
550
+ }
551
+ /**
552
+ * A PRIMEIRA SESSÃO DEPOIS DO `connect` PASSA A TER UMA PRIMEIRA FRASE.
553
+ *
554
+ * Este é o hook `SessionStart`, e até aqui ele só falava de desalinho - então num repo sem
555
+ * sistema ele nunca falava. É onde a tese da landing vira experiência ou não vira nada: o agente
556
+ * abre sabendo o que falta, e quem está lendo descobre o que pedir sem ter guardado nada.
557
+ *
558
+ * Só quando há algo a dizer. O silêncio de um ambiente pronto é a razão de alguém ainda ler esta
559
+ * linha na vez em que ela aparece.
560
+ */
561
+ const next = await nextStepFor(root).catch(() => null);
562
+ if (next)
563
+ console.log(next);
511
564
  }
@@ -1,4 +1,6 @@
1
1
  import { DEFAULT_CONFIG, writeProjectConfig } from "../config.js";
2
+ import { body, section, snippet } from "../output.js";
3
+ import { wiringPrompt } from "../wiring-prompt.js";
2
4
  import { add } from "./add.js";
3
5
  /**
4
6
  * Bootstraps a project for SynthesisUI: writes `_synthesisui/config.json`
@@ -40,11 +42,37 @@ export async function init(opts) {
40
42
  // + rules + CLAUDE.md all arrive via `add`).
41
43
  if (opts.ds) {
42
44
  console.log("");
43
- await add(opts.ds, { registry: opts.registry, dir: root });
44
- // The layers exist and nothing installs them unless this is said out loud.
45
+ /**
46
+ * O FECHO DEIXA DE SER UMA LISTA DE TAREFAS (etapa 1.4 do plano).
47
+ *
48
+ * `add` imprime "One-time setup (once per app)" com três passos numerados
49
+ * para a PESSOA fazer à mão: os dois `@import`, o `data-ds` na raiz e a
50
+ * fiação das fontes. É onde 27/07 mediu 0% de fiação - ninguém fazia, e o
51
+ * sistema que acabara de chegar não aparecia na tela.
52
+ *
53
+ * Quem roda este comando tem um agente aberto do lado. Então o setup
54
+ * manual sai (`setupHints: false`) e no lugar dele vai a coisa que se
55
+ * cola - o mesmo texto que a tela do onboarding entrega, gêmeo por spec.
56
+ * As edições continuam existindo; elas só mudam de mão.
57
+ */
58
+ await add(opts.ds, {
59
+ registry: opts.registry,
60
+ dir: root,
61
+ setupHints: false,
62
+ });
63
+ console.log("");
64
+ console.log(section("Two steps left, and neither is a file you edit"));
65
+ console.log(body("1. Turn the check and the tools on - run this BEFORE you open your agent,"));
66
+ console.log(body(" because hooks and tools are read when a session starts:"));
67
+ console.log("");
68
+ console.log(snippet(["npx synthesisui@latest connect"]));
69
+ console.log("");
70
+ console.log(body("2. Open your agent and paste this once - it does the wiring:"));
71
+ console.log("");
72
+ console.log(snippet(wiringPrompt(opts.ds).split("\n")));
45
73
  console.log("");
46
- console.log(" • synthesisui connect so the check runs on its own, and your agent");
47
- console.log(" can ask this system instead of guessing");
74
+ console.log(body("Then ask it for something real. The check introduces itself on the first"));
75
+ console.log(body("clean file and goes quiet after that."));
48
76
  return;
49
77
  }
50
78
  console.log("");
@@ -1,6 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { release } from "node:os";
3
- import { resolveRegistry, writeToken } from "../config.js";
3
+ import { readCredentials, resolveRegistry, sameRegistry, writeToken, } from "../config.js";
4
4
  import { RegistryError } from "../registry.js";
5
5
  const CLIENT_ID = "synthesisui-cli";
6
6
  const GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
@@ -34,6 +34,29 @@ function openBrowser(url) {
34
34
  /** Device authorization (RFC 8628): opens the browser, waits for approval. */
35
35
  export async function login(opts) {
36
36
  const base = resolveRegistry(opts.registry);
37
+ /**
38
+ * O LOGIN QUE RECONHECE QUEM JÁ ENTROU (etapa 1.5 do plano, vazamento T1.6).
39
+ *
40
+ * Este comando abria um browser e pedia aprovação mesmo com um token válido
41
+ * na máquina, para o mesmo host. A install mission manda logar depois de a
42
+ * pessoa já ter logado no navegador, e o wizard manda logar no terminal - de
43
+ * modo que "faça de novo o que você acabou de fazer" era o caminho normal, e
44
+ * não um caso de borda. Pedir de novo o que já foi feito é a coisa que faz
45
+ * alguém desconfiar de que o produto sabe o que está acontecendo.
46
+ *
47
+ * Sai por aqui só quando o token é para ESTE host: um token de outro
48
+ * registry não serve, e reconhecê-lo seria um 401 mais tarde disfarçado de
49
+ * sucesso agora.
50
+ */
51
+ if (!opts.force) {
52
+ const existing = await readCredentials();
53
+ if (existing && sameRegistry(existing.registry, base)) {
54
+ console.log("");
55
+ console.log(`✓ Already signed in to ${base} on this machine.`);
56
+ console.log(" Run `synthesisui login --force` to sign in as someone else.");
57
+ return;
58
+ }
59
+ }
37
60
  const codeRes = await fetch(`${base}/api/auth/device/code`, {
38
61
  method: "POST",
39
62
  headers: { "content-type": "application/json" },
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ const CLI_VERSION = JSON.parse(readFileSync(new URL("../package.json", import.me
36
36
  const HELP = `synthesisui - bring SynthesisUI design systems into your project
37
37
 
38
38
  Usage - deterministic, FREE:
39
- synthesisui login [options] connect the CLI to your account (device-flow)
39
+ synthesisui login [options] connect the CLI to your account (device-flow; --force to switch accounts)
40
40
  synthesisui init [options] write _synthesisui/config.json (target, dirs); --ds to bring one in
41
41
  synthesisui list [options] list the published design systems
42
42
  synthesisui add <slug> [options] materialize a DS into _synthesisui/ds/<slug>/
@@ -387,7 +387,10 @@ async function main() {
387
387
  break;
388
388
  }
389
389
  case "login":
390
- await login({ registry });
390
+ // `--force` porque o comando agora sai cedo quando esta máquina já tem
391
+ // sessão para este host - trocar de conta continua possível, e passa a
392
+ // ser dito em vez de ser o comportamento padrão.
393
+ await login({ registry, force: args.includes("--force") });
391
394
  break;
392
395
  case "init": {
393
396
  const target = typeof flags.target === "string" ? flags.target : undefined;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * O PASTE QUE TIRA AS QUATRO EDIÇÕES DA MÃO DA PESSOA.
3
+ *
4
+ * `init --ds` termina imprimindo uma seção "One-time setup" com três passos
5
+ * numerados para o humano fazer à mão: os dois `@import`, o `data-ds` na raiz
6
+ * e a fiação das fontes. Foi exatamente aí que 27/07 mediu 0% de fiação -
7
+ * ninguém fazia, e o sistema que acabara de chegar não aparecia na tela.
8
+ *
9
+ * A pessoa que roda esse comando tem um agente aberto ao lado. Então o fecho
10
+ * deixa de ser uma lista de tarefas e passa a ser uma coisa para colar.
11
+ *
12
+ * GÊMEO POR SPEC com `apps/web/src/lib/ds/install-mission.ts`, que serve o
13
+ * mesmo texto na tela do onboarding - `wiring-prompt.spec.ts` reprova o drift.
14
+ * O CLI é publicado standalone e não pode importar de `apps/web`, então a
15
+ * cópia é inevitável; o que não é inevitável é ela divergir em silêncio, e um
16
+ * prompt que diverge entre a tela e o terminal ensina duas fiações diferentes
17
+ * para o mesmo sistema.
18
+ */
19
+ export function initCommand(slug) {
20
+ return `npx synthesisui@latest init --styles tailwind --ds ${slug}`;
21
+ }
22
+ export function wiringPrompt(slug) {
23
+ return `Wire the "${slug}" design system into this project. I already ran the install in my terminal.
24
+
25
+ 1. Run: npx synthesisui@latest doctor
26
+ If it says "no system installed", I skipped the install - run ${initCommand(slug)} first, then carry on.
27
+ 2. Do the ONE-TIME SETUP it names, all of it: the two @import lines in the project's global stylesheet (the path is relative to that file), data-ds="${slug}" on the root element, and the font wiring - importing from the fonts file it writes and mapping those variables in the stylesheet. The type is the step people skip.
28
+ 3. Run doctor again. It must NOT say "not wired up yet". If it does, it names exactly which piece is missing - fix that and run it again.
29
+ 4. Run: npx synthesisui@latest connect
30
+ If it answers "already had it" on both lines, everything is live and you are done.
31
+ If it WROTE either of them, then they are not live in this session, because hooks and tools are only read at startup. Stop there and tell me, in bold, on its own line: **RESTART THIS SESSION - the check is installed but not running yet.** Do not keep writing files after that; a session that cannot be checked is the state this whole setup exists to avoid.
32
+
33
+ Do not change any of my existing styles.`;
34
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.241",
3
+ "version": "0.16.242",
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": {