synthesisui 0.16.241 → 0.16.243
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/dist/commands/align.js +100 -2
- package/dist/commands/init.js +32 -4
- package/dist/commands/login.js +24 -1
- package/dist/index.js +5 -2
- package/dist/wiring-prompt.js +34 -0
- package/package.json +1 -1
package/dist/commands/align.js
CHANGED
|
@@ -5,8 +5,10 @@ import { pinnedHookVersion } from "../agent-wiring.js";
|
|
|
5
5
|
import { isOlderCli } from "../cli-version.js";
|
|
6
6
|
import { readToken, resolveRegistry } from "../config.js";
|
|
7
7
|
import { unsentEvents } from "../doctor/ledger.js";
|
|
8
|
+
import { readRequests } from "../doctor/requests.js";
|
|
8
9
|
import { CHECKER_SINCE, installedBehind, MATERIALISER_SINCE, READER_SINCE, } from "../install-marks.js";
|
|
9
10
|
import { measuredScope } from "../measured-scope.js";
|
|
11
|
+
import { body } from "../output.js";
|
|
10
12
|
import { SKILLS } from "../skills.js";
|
|
11
13
|
/**
|
|
12
14
|
* QUAL CLI MEDIU O CENSO EM DISCO - e era o `reader`, um inteiro, até 11/08.
|
|
@@ -313,6 +315,32 @@ export function repairSaid(body, lock) {
|
|
|
313
315
|
then: "claude --continue --dangerously-skip-permissions",
|
|
314
316
|
};
|
|
315
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* QUANTAS RESPOSTAS ESTÃO ESPERANDO PARA SEREM BUSCADAS - o cruzamento das duas listas.
|
|
320
|
+
*
|
|
321
|
+
* A plataforma manda os ids que ela já decidiu; o arquivo local diz o que ainda está aberto AQUI.
|
|
322
|
+
* A interseção é a notícia: pedidos que foram respondidos e cuja resposta esta máquina nunca viu.
|
|
323
|
+
*
|
|
324
|
+
* `null` quando não há nada, quando o arquivo não existe, ou quando a plataforma é velha demais para
|
|
325
|
+
* mandar o campo - um CLI novo contra um servidor antigo fica exatamente como estava, calado.
|
|
326
|
+
*/
|
|
327
|
+
async function decidedWaiting(root, decided) {
|
|
328
|
+
if (!decided || decided.length === 0)
|
|
329
|
+
return null;
|
|
330
|
+
const open = await readRequests(root).catch(() => []);
|
|
331
|
+
if (open.length === 0)
|
|
332
|
+
return null;
|
|
333
|
+
const answered = new Set(decided);
|
|
334
|
+
const n = open.filter((r) => answered.has(r.id)).length;
|
|
335
|
+
if (n === 0)
|
|
336
|
+
return null;
|
|
337
|
+
return {
|
|
338
|
+
says: n === 1
|
|
339
|
+
? "A request you filed has been answered on the platform, and this machine has not picked it up yet."
|
|
340
|
+
: `${n} requests you filed have been answered on the platform, and this machine has not picked them up yet.`,
|
|
341
|
+
run: "npx synthesisui sync",
|
|
342
|
+
};
|
|
343
|
+
}
|
|
316
344
|
export async function versionBehind(root, opts = {}) {
|
|
317
345
|
const locks = (await locksIn(root)).filter((l) => l.slug && !l.adopted);
|
|
318
346
|
const lock = locks[0];
|
|
@@ -350,6 +378,24 @@ export async function versionBehind(root, opts = {}) {
|
|
|
350
378
|
* a versão é a consequência - dizer a consequência primeiro manda a pessoa rodar o comando sem
|
|
351
379
|
* saber por quê.
|
|
352
380
|
*/
|
|
381
|
+
/**
|
|
382
|
+
* ALGUÉM JÁ RESPONDEU UM PEDIDO SEU, E VOCÊ NÃO TEM COMO SABER.
|
|
383
|
+
*
|
|
384
|
+
* O agente arquiva a lacuna, a pessoa decide na plataforma - e a decisão fica lá. O arquivo local
|
|
385
|
+
* nunca é tocado por design: o próximo `sync` lê o status, fecha o pedido na máquina e imprime o
|
|
386
|
+
* próximo passo. O buraco está no "próximo": até alguém rodar o comando, uma resposta dada há dias
|
|
387
|
+
* não existe deste lado, e quem esperava por ela descobre por acaso.
|
|
388
|
+
*
|
|
389
|
+
* O cruzamento tem que ser AQUI, e é por isso que a plataforma manda ids em vez de uma frase: só
|
|
390
|
+
* esta máquina sabe o que ainda está ABERTO na fila local. Um pedido decidido que já foi fechado
|
|
391
|
+
* aqui não é notícia, e contá-lo faria o hook cobrar para sempre um trabalho já feito.
|
|
392
|
+
*
|
|
393
|
+
* Vem antes da versão e do reparo porque é a única linha desta lista sobre uma pergunta que a
|
|
394
|
+
* PESSOA fez - e uma resposta que ela pediu vale mais que uma novidade que ela não pediu.
|
|
395
|
+
*/
|
|
396
|
+
const waiting = await decidedWaiting(root, body.decided);
|
|
397
|
+
if (waiting)
|
|
398
|
+
return waiting;
|
|
353
399
|
const repaired = repairSaid(body, lock);
|
|
354
400
|
if (repaired)
|
|
355
401
|
return repaired;
|
|
@@ -486,17 +532,54 @@ export async function misalignments(root, opts = {}) {
|
|
|
486
532
|
items.push(remote);
|
|
487
533
|
return items;
|
|
488
534
|
}
|
|
535
|
+
/**
|
|
536
|
+
* O ESTADO DE QUEM AINDA NÃO TEM SISTEMA - o silêncio que custava a jornada inteira.
|
|
537
|
+
*
|
|
538
|
+
* `localMisalignments` devolve lista VAZIA quando não há sistema instalado, e a razão está escrita
|
|
539
|
+
* lá em cima: um repo sem DS não está desalinhado, ele só não começou. Correto - e o efeito é que a
|
|
540
|
+
* pessoa que acabou de rodar `connect`, que é exatamente quem mais precisa de uma instrução, abre a
|
|
541
|
+
* primeira sessão do agente em silêncio absoluto. A frase do próximo passo mora hoje na aba do
|
|
542
|
+
* browser que ela acabou de deixar; se fechou a aba, ou voltou no dia seguinte, não existe mais
|
|
543
|
+
* nada dizendo o que fazer.
|
|
544
|
+
*
|
|
545
|
+
* Então isto não é um desalinho e não entra naquela lista: é uma frase de ESTADO, e ela fala em
|
|
546
|
+
* forma de CONVERSA. "Peça ao seu agente: importe meu design system" é executável por quem está
|
|
547
|
+
* lendo - um comando decorado não é, e a esta altura ela nem sabe que comandos existem.
|
|
548
|
+
*
|
|
549
|
+
* DOIS ESTADOS, e não três. O terceiro que o plano previa - "instalado mas não fiado" - ficou de
|
|
550
|
+
* fora com motivo medido: a única leitura confiável de fiação (`readWiring`, no doctor) varre o
|
|
551
|
+
* repositório inteiro, e isto roda em TODA abertura de sessão. Um aviso que custa um walk completo
|
|
552
|
+
* a cada sessão é um aviso que alguém desliga.
|
|
553
|
+
*/
|
|
554
|
+
export async function nextStepFor(root) {
|
|
555
|
+
const locks = (await locksIn(root)).filter((l) => l.slug && !l.adopted);
|
|
556
|
+
if (locks.length > 0)
|
|
557
|
+
return null;
|
|
558
|
+
const measured = await readFile(join(root, "_synthesisui", "census.json"), "utf8").then(() => true, () => false);
|
|
559
|
+
return measured
|
|
560
|
+
? 'This repo has been measured but has no design system yet. Ask me: "continue the import."'
|
|
561
|
+
: 'This repo has no design system contract yet. Ask me: "import my design system."';
|
|
562
|
+
}
|
|
489
563
|
/**
|
|
490
564
|
* A CAUDA DE UM COMANDO QUE TERMINOU: o que ainda está fora, ou nada.
|
|
491
565
|
*
|
|
492
566
|
* Muda quando não falta nada, que é o caso normal e é o que a mantém legível. Ela existe porque
|
|
493
567
|
* `sync`, `connect` e `upgrade` deixavam a pessoa sem saber se tinha acabado - e a resposta exigia
|
|
494
568
|
* lembrar de um sexto comando (dono, 07/08).
|
|
569
|
+
*
|
|
570
|
+
* E QUANDO NÃO HÁ SISTEMA NENHUM ela dizia menos ainda: nada. O `connect` terminava mudo no único
|
|
571
|
+
* repo em que ele é a primeira coisa que alguém roda.
|
|
495
572
|
*/
|
|
496
573
|
export async function reportWhatIsLeft(root, opts = {}) {
|
|
497
574
|
const items = await misalignments(root, opts).catch(() => []);
|
|
498
|
-
if (items.length === 0)
|
|
575
|
+
if (items.length === 0) {
|
|
576
|
+
const next = await nextStepFor(root).catch(() => null);
|
|
577
|
+
if (next) {
|
|
578
|
+
console.log("");
|
|
579
|
+
console.log(body(next));
|
|
580
|
+
}
|
|
499
581
|
return;
|
|
582
|
+
}
|
|
500
583
|
console.log("");
|
|
501
584
|
console.log(describeMisalignments(items, "after"));
|
|
502
585
|
}
|
|
@@ -506,6 +589,21 @@ export async function align(opts) {
|
|
|
506
589
|
...(opts.cli ? { cli: opts.cli } : {}),
|
|
507
590
|
});
|
|
508
591
|
const text = describeMisalignments(items, opts.shell ? "shell" : "session");
|
|
509
|
-
if (text)
|
|
592
|
+
if (text) {
|
|
510
593
|
console.log(text);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* A PRIMEIRA SESSÃO DEPOIS DO `connect` PASSA A TER UMA PRIMEIRA FRASE.
|
|
598
|
+
*
|
|
599
|
+
* Este é o hook `SessionStart`, e até aqui ele só falava de desalinho - então num repo sem
|
|
600
|
+
* sistema ele nunca falava. É onde a tese da landing vira experiência ou não vira nada: o agente
|
|
601
|
+
* abre sabendo o que falta, e quem está lendo descobre o que pedir sem ter guardado nada.
|
|
602
|
+
*
|
|
603
|
+
* Só quando há algo a dizer. O silêncio de um ambiente pronto é a razão de alguém ainda ler esta
|
|
604
|
+
* linha na vez em que ela aparece.
|
|
605
|
+
*/
|
|
606
|
+
const next = await nextStepFor(root).catch(() => null);
|
|
607
|
+
if (next)
|
|
608
|
+
console.log(next);
|
|
511
609
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -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
|
-
|
|
44
|
-
|
|
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("
|
|
47
|
-
console.log("
|
|
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("");
|
package/dist/commands/login.js
CHANGED
|
@@ -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
|
-
|
|
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