synthesisui 0.16.425 → 0.16.427

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.
@@ -0,0 +1,167 @@
1
+ import { resolve } from "node:path";
2
+ import { readToken, resolveRegistry } from "../config.js";
3
+ import { installedSlugs } from "../installed.js";
4
+ import { body, paint, section } from "../output.js";
5
+ const isState = (x) => !!x &&
6
+ typeof x === "object" &&
7
+ typeof x.on === "boolean" &&
8
+ typeof x.landsOn === "string";
9
+ /**
10
+ * O ESTADO, EM UMA LINHA - e o nome do sistema junto, porque um repo pode ter dois.
11
+ *
12
+ * O SLUG É O LOCAL, NUNCA O QUE VOLTOU DA REDE - achado da revisão de QA no fecho. Ele é a
13
+ * PERGUNTA, não a resposta: num repo com dois sistemas instalados, uma resposta que trouxesse
14
+ * outro nome faria o terminal dizer que mudou o sistema em que ela não mexeu. A lei de imprimir o
15
+ * lido de volta vale para o ESTADO, que é o que a rota decide.
16
+ */
17
+ const stateLine = (s, slug) => `Autopilot is ${s.on ? "on" : "off"} for you in ${slug}.`;
18
+ /**
19
+ * POR QUE ELE ESTÁ ASSIM - e as três respostas são coisas diferentes.
20
+ *
21
+ * `unavailable` é a lacuna declarada: dizer "alguém te desligou" sobre uma tabela que aquele banco
22
+ * ainda não tem é falso duas vezes - ninguém desligou, e o que houve foi uma leitura que não
23
+ * respondeu. Lacuna calada é bug (lei 8).
24
+ */
25
+ const whyLine = (s) => s.why === "chosen"
26
+ ? `you switched this ${s.on ? "on" : "off"} yourself, and it stands even if the system default changes.`
27
+ : s.why === "default"
28
+ ? "you have no choice of your own here, so the system default answers for you."
29
+ : "per-person Autopilot has not reached this system yet, so nobody switched you off - off is the honest answer until it does.";
30
+ /** Onde o trabalho dela cai - a segunda pergunta de quem acabou de mudar a automação. */
31
+ const landsLine = (s, base, slug) => s.landsOn === "branch"
32
+ ? `your work lands in YOUR branch, not on the main line your team installs - it reaches them when whoever owns this system approves it: ${base}/dashboard/mine/${slug}`
33
+ : "your work goes straight to the main line your team installs.";
34
+ function say(s, base, slug) {
35
+ console.log(section("Autopilot"));
36
+ console.log(body(paint.strong(stateLine(s, slug))));
37
+ console.log(body(paint.dim(whyLine(s))));
38
+ console.log(body(paint.dim(landsLine(s, base, slug))));
39
+ console.log("");
40
+ }
41
+ export async function autopilot(opts) {
42
+ const root = resolve(opts.dir ?? process.cwd());
43
+ const base = resolveRegistry(opts.registry);
44
+ const asked = opts.wanted?.trim().toLowerCase();
45
+ /**
46
+ * A PALAVRA É CONFERIDA ANTES DE QUALQUER IDA À REDE - e antes do login, inclusive. Mandar
47
+ * `maybe` para o servidor e deixá-lo recusar gastaria uma volta para dizer o que já se sabe
48
+ * aqui, e devolveria a ela uma frase sobre a rota em vez das duas escolhas que existem.
49
+ */
50
+ if (asked && asked !== "on" && asked !== "off") {
51
+ console.log(section("Autopilot"));
52
+ console.log(body(`"${opts.wanted}" is not a choice here. The two are: ${paint.strong("on")} and ${paint.strong("off")}.`));
53
+ console.log(body(paint.faint(" synthesisui autopilot # what it is right now, without changing it")));
54
+ /**
55
+ * E ELE SAI COM FALHA - achado da revisão de QA no fecho: um script que roda
56
+ * `synthesisui autopilot $CHOICE` com a variável vazia ou com um valor digitado errado
57
+ * seguiria como se tivesse mudado. É a mesma lei do 403, e o caminho mais provável de chegar
58
+ * aqui é justamente automação, não uma pessoa digitando.
59
+ */
60
+ process.exitCode = 1;
61
+ return;
62
+ }
63
+ const token = await readToken(opts.home);
64
+ if (!token) {
65
+ console.log(section("Autopilot"));
66
+ console.log(body("Not logged in, so there is nothing to read here - this switch is yours on the platform, not a file in this clone:"));
67
+ console.log(body(paint.blue(" synthesisui login")));
68
+ return;
69
+ }
70
+ /**
71
+ * QUAL SISTEMA - o `.lock` é o que diz qual slug este repositório alimenta.
72
+ *
73
+ * Num repo com dois, ele age no primeiro em ordem alfabética, e a saída NOMEIA qual: um comando
74
+ * que age calado sobre "o primeiro" é um comando que ela não tem como conferir.
75
+ */
76
+ const installed = await installedSlugs(root);
77
+ const slug = installed[0];
78
+ if (!slug) {
79
+ console.log(section("Autopilot"));
80
+ console.log(body("No installed system here, so there is no Autopilot to read. Install the one this repo follows first:"));
81
+ console.log(body(paint.blue(" npx synthesisui@latest add <slug>")));
82
+ console.log(body(paint.faint(" npx synthesisui@latest list --mine # the slugs you own")));
83
+ return;
84
+ }
85
+ const url = `${base}/api/registry/ds/${slug}/autopilot`;
86
+ const auth = { authorization: `Bearer ${token}` };
87
+ /** A leitura, e ela é a mesma nos dois caminhos - perguntar, e dizer o que continua valendo. */
88
+ const read = async () => {
89
+ const res = await fetch(url, { headers: auth });
90
+ if (!res.ok)
91
+ return null;
92
+ const data = (await res.json().catch(() => null));
93
+ return isState(data) ? data : null;
94
+ };
95
+ const unreachable = (why) => {
96
+ console.log(section("Autopilot"));
97
+ console.log(body(`This machine could not reach the platform, so nothing was read and nothing was changed: ${base}${why instanceof Error ? ` (${why.message})` : ""}. Try again when you are back online, or point it at the right place with --registry <url>.`));
98
+ process.exitCode = 1;
99
+ };
100
+ if (!asked) {
101
+ try {
102
+ const now = await read();
103
+ if (!now) {
104
+ console.log(section("Autopilot"));
105
+ console.log(body(`${paint.strong(slug)} did not answer for this account at ${base}. If your session is old, log in again: ${paint.blue("synthesisui login")}`));
106
+ process.exitCode = 1;
107
+ return;
108
+ }
109
+ say(now, base, slug);
110
+ }
111
+ catch (error) {
112
+ unreachable(error);
113
+ }
114
+ return;
115
+ }
116
+ try {
117
+ const res = await fetch(url, {
118
+ method: "POST",
119
+ headers: { ...auth, "content-type": "application/json" },
120
+ body: JSON.stringify({ choice: asked }),
121
+ });
122
+ if (res.status === 403) {
123
+ /**
124
+ * A RECUSA VEM DO SERVIDOR, INTEIRA - ela nomeia o papel que resolve, e é isso que separa
125
+ * "não deu" de "fale com quem administra as pessoas deste grupo".
126
+ */
127
+ const said = (await res.json().catch(() => null));
128
+ console.log(section("Autopilot"));
129
+ console.log(body(said?.error ??
130
+ "That is not yours to change here, and the server did not say who it belongs to."));
131
+ process.exitCode = 1;
132
+ /** E O QUE CONTINUA VALENDO, porque uma recusa sem o estado deixa ela sem saber onde está. */
133
+ const now = await read().catch(() => null);
134
+ if (now)
135
+ say(now, base, slug);
136
+ return;
137
+ }
138
+ if (res.status === 404) {
139
+ console.log(section("Autopilot"));
140
+ console.log(body(`${paint.strong(slug)} does not reach this account at ${base}, so there is nothing to change here. If your session is old, log in again: ${paint.blue("synthesisui login")}`));
141
+ process.exitCode = 1;
142
+ return;
143
+ }
144
+ if (!res.ok) {
145
+ console.log(section("Autopilot"));
146
+ console.log(body(`${base} answered ${res.status} to that, so nothing was changed. Nothing of yours was lost.`));
147
+ process.exitCode = 1;
148
+ return;
149
+ }
150
+ /**
151
+ * O ESTADO IMPRESSO É O QUE A ROTA RELEU DO BANCO depois de escrever - nunca o que este
152
+ * processo pediu. Ver o cabeçalho: um interruptor que parece ligado e não está é pior que um
153
+ * que recusa.
154
+ */
155
+ const now = (await res.json().catch(() => null));
156
+ if (!isState(now)) {
157
+ console.log(section("Autopilot"));
158
+ console.log(body(`${base} answered something this version cannot read, so it is not saying what the switch is now. Upgrade and try again: npx synthesisui@latest autopilot`));
159
+ process.exitCode = 1;
160
+ return;
161
+ }
162
+ say(now, base, slug);
163
+ }
164
+ catch (error) {
165
+ unreachable(error);
166
+ }
167
+ }
@@ -490,7 +490,13 @@ export async function takeCensus(root, opts) {
490
490
  * `textarea` (dono, 01/08). Fetched rather than shipped, and the fallback is announced
491
491
  * with what it costs - a smaller answer is fine, a silently smaller one is not.
492
492
  */
493
- const fetched = await fetchCatalogue(opts?.registry);
493
+ const fetched = opts?.offline
494
+ ? {
495
+ ok: false,
496
+ why: "offline",
497
+ because: "this run makes no network calls by contract",
498
+ }
499
+ : await fetchCatalogue(opts?.registry);
494
500
  useLiveCatalogue(fetched.ok ? asCatalogueTable(fetched.index) : null);
495
501
  /** Todo arquivo de estilo do escopo, para o ledger - ver o parâmetro `into`. */
496
502
  const styleFiles = [];
@@ -4315,7 +4321,16 @@ export async function runImport(opts) {
4315
4321
  * every file opened, every candidate ruled out - is the machine talking to
4316
4322
  * itself (dono, 01/08).
4317
4323
  */
4318
- phase(1, 3, "Measuring the repository");
4324
+ /**
4325
+ * "1/3" É UMA PROMESSA DE TRÊS PASSOS, e o `inspect` tem UM.
4326
+ *
4327
+ * O contador existe para quem está importando: ele diz em qual das três fases a pessoa está
4328
+ * e que ainda faltam duas. Quem rodou `inspect` não vai mandar nada e não vai resolver
4329
+ * leitura nenhuma - abrir o primeiro contato do produto anunciando dois passos que não vêm é
4330
+ * prometer trabalho que ninguém pediu.
4331
+ */
4332
+ if (!opts.inspect)
4333
+ phase(1, 3, "Measuring the repository");
4319
4334
  console.log(section("Reading your project"));
4320
4335
  /**
4321
4336
  * UMA MEDIÇÃO POR ESCOPO, E DEPOIS A FUSÃO.
@@ -4338,6 +4353,8 @@ export async function runImport(opts) {
4338
4353
  ...(i === 0 && usage.length > 0 ? { usage } : {}),
4339
4354
  ...(one ? { scopeLabel: one } : {}),
4340
4355
  ...(opts.cli ? { cli: opts.cli } : {}),
4356
+ /** `inspect` não fala com ninguém - ver `offline` em `takeCensus`. */
4357
+ ...(opts.inspect ? { offline: true } : {}),
4341
4358
  });
4342
4359
  if (one)
4343
4360
  c.scope = one;
@@ -4359,6 +4376,23 @@ export async function runImport(opts) {
4359
4376
  */
4360
4377
  if (!opts.census)
4361
4378
  await resolveReadParts(census, root);
4379
+ /**
4380
+ * O QUE ELE ESCREVEU VEM ANTES DO QUE ELE REPETIU - e a ordem era a inversa.
4381
+ *
4382
+ * `summarize` abre com "13 distinct design values · 17 colour · 10 spacing", e os componentes
4383
+ * com as variantes que este leitor extraiu do código dele vinham no fim, atrás de tudo. Quem lê
4384
+ * um terminal lê de cima para baixo e conclui pela primeira seção: a leitura profunda era
4385
+ * apresentada como se fosse um contador de pixels repetidos.
4386
+ *
4387
+ * `Button variant(primary|ghost) size(lg)` é a coisa mais difícil que esta esteira faz, e é a
4388
+ * que diz que o vocabulário do frontend dele foi entendido. Ela abre.
4389
+ *
4390
+ * E ABRE NOS DOIS COMANDOS. Duas ordens para a mesma leitura é a mesma doença de duas verdades:
4391
+ * o `import` não ganha nada sendo a versão pior de si mesmo, e quem roda os dois na mesma
4392
+ * semana teria que aprender duas telas (dono, 13/09 - a decisão de ordem é do terminal, não do
4393
+ * produto, e portanto minha).
4394
+ */
4395
+ printComponents(census);
4362
4396
  await summarize(census, root, scope);
4363
4397
  /**
4364
4398
  * ONDE ESTOU vem antes de O QUE EU LEIO, e o `!scope` calava exatamente quem
@@ -4402,6 +4436,27 @@ export async function runImport(opts) {
4402
4436
  // there - config, `ds/<slug>/`, the hook's marker - and the census was the
4403
4437
  // only artefact that travelled with `--dir`, which is how two of them ended
4404
4438
  // up disagreeing about which import happened (dono, 31/07).
4439
+ /**
4440
+ * A FRONTEIRA DO `inspect`, e ela é uma linha só porque a leitura já terminou.
4441
+ *
4442
+ * Tudo acima imprimiu; a primeira escrita é a próxima instrução. Retornar aqui entrega os
4443
+ * quatro contratos de uma vez, sem um caminho paralelo que possa divergir.
4444
+ *
4445
+ * O QUE ELE VÊ NO LUGAR DO ARQUIVO: a frase que diz o que sobra a fazer - e ela NÃO faz.
4446
+ * Um comando de primeiro contato que termina executando o passo seguinte é o comando que a
4447
+ * pessoa não vai rodar de novo.
4448
+ */
4449
+ if (opts.inspect) {
4450
+ sayReach(census);
4451
+ console.log("");
4452
+ console.log(section("Your agent cannot ask any of this yet"));
4453
+ console.log(body("Nothing was written, and nothing was sent."));
4454
+ console.log("");
4455
+ console.log(body("To let it ask before it invents:"));
4456
+ console.log(body(` ${paint.strong("npx synthesisui@latest connect")}`));
4457
+ console.log("");
4458
+ return;
4459
+ }
4405
4460
  const out = opts.census ?? join(root, "_synthesisui", "census.json");
4406
4461
  await mkdir(dirname(out), { recursive: true });
4407
4462
  await writeFile(out, `${JSON.stringify(census, null, 2)}\n`, "utf8");
@@ -4427,7 +4482,8 @@ export async function runImport(opts) {
4427
4482
  if (opts.dry) {
4428
4483
  console.log(body("Nothing was sent. Read the file, then run it without --dry."));
4429
4484
  sayReach(census);
4430
- printComponents(census);
4485
+ /* `printComponents` subiu para antes de `summarize` e vale para todo caminho - repetir
4486
+ aqui imprimiria os componentes duas vezes na mesma execução. */
4431
4487
  printAgentContract();
4432
4488
  console.log("");
4433
4489
  return;
@@ -84,5 +84,21 @@ export function asCatalogueTable(index) {
84
84
  * "using the built-in list" is not.
85
85
  */
86
86
  export function describeFallback(result, floorSize) {
87
- return `${result.because}. Matching used the ${floorSize} names built into this CLI instead of the live catalogue, so a component of yours that lines up with something we added recently will read as exclusively yours. That is a smaller answer, not a wrong one - re-run with a session to get the full one.`;
87
+ /**
88
+ * "RE-RUN WITH A SESSION" É UM CONSELHO ERRADO PARA QUEM JÁ TEM UMA - e `offline` é
89
+ * exatamente esse caso.
90
+ *
91
+ * Os outros dois motivos são falhas de acesso: sem sessão, ou recusada. Entrar resolve, e a
92
+ * frase manda entrar. `offline` é uma ESCOLHA do comando - `inspect` não fala com ninguém por
93
+ * contrato -, então a mesma frase virava uma contradição na tela: o terminal dizia "esta
94
+ * execução não faz chamada de rede" e, na oração seguinte, "rode de novo com uma sessão"
95
+ * para alguém que estava logada o tempo todo (medido em 13/09, rodando na máquina do dono).
96
+ *
97
+ * Dizer a verdade aqui custa uma condição, e a alternativa é ensinar a pessoa a desconfiar
98
+ * das outras frases do mesmo relatório.
99
+ */
100
+ const saida = result.why === "offline"
101
+ ? "`synthesisui import` matches against the full list"
102
+ : "re-run with a session to get the full one";
103
+ return `${result.because}. Matching used the ${floorSize} names built into this CLI instead of the live catalogue, so a component of yours that lines up with something we added recently will read as exclusively yours. That is a smaller answer, not a wrong one - ${saida}.`;
88
104
  }
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { add } from "./commands/add.js";
8
8
  import { adopt } from "./commands/adopt.js";
9
9
  import { advise } from "./commands/advise.js";
10
10
  import { align } from "./commands/align.js";
11
+ import { autopilot } from "./commands/autopilot.js";
11
12
  import { ci } from "./commands/ci.js";
12
13
  import { clean } from "./commands/clean.js";
13
14
  import { component } from "./commands/component.js";
@@ -60,6 +61,10 @@ Usage - deterministic, FREE:
60
61
  synthesisui clean [--force] strip create-next-app boilerplate (dry run without --force)
61
62
 
62
63
  Usage - governance (deterministic, FREE):
64
+ synthesisui inspect [--scope <p>] read this repository and say what it sees: your components,
65
+ their variant axes and options, their parts and shape, and
66
+ where one is built from another - then the values you repeat,
67
+ with file and line. No account, no network, nothing written
63
68
  synthesisui adopt [--write] turn the design system you ALREADY have into a contract
64
69
  synthesisui import [--scope <p>] [--usage <p>] read what you already have and make it a system
65
70
  your agent follows - without touching your CSS
@@ -88,6 +93,11 @@ Usage - governance (deterministic, FREE):
88
93
  declare, and says what it offers instead. create: it
89
94
  reports and gets out of the way. No argument answers
90
95
  where you are. Local to this clone, never committed
96
+ synthesisui autopilot [on|off] the Autopilot, FOR YOU in this system: on applies what it
97
+ finds on your work without asking, off stops it acting
98
+ for you - your sync still goes up either way. No argument
99
+ answers where you are, and it always says which line your
100
+ work is landing in
91
101
  synthesisui hook the check itself; installed by connect, run by your editor
92
102
  synthesisui mcp the system as tools; installed by connect, run by your agent
93
103
 
@@ -251,6 +261,45 @@ async function main() {
251
261
  registry,
252
262
  });
253
263
  break;
264
+ /**
265
+ * `inspect` - LEIA O MEU CÓDIGO E ME DIGA O QUE VOCÊ VÊ.
266
+ *
267
+ * O QUE O CLIENTE GANHA: em segundos, sem criar conta e sem que nada seja escrito no
268
+ * repositório dele, ele vê o vocabulário do próprio frontend lido de volta - os componentes,
269
+ * os eixos de variante com as opções, as partes, a forma como elas se aninham no código dele,
270
+ * e onde um componente dele é feito de outro. Depois os valores que ele repete, com arquivo e
271
+ * linha. E o que este leitor NÃO entendeu, também com arquivo e linha.
272
+ *
273
+ * POR QUE ELE EXISTE, SE `import --dry` já fazia isso: fazia, e com o nome errado para o
274
+ * momento errado. Para quem nunca ouviu falar da plataforma, `import` promete escrita e
275
+ * `--dry` é jargão nosso - e, pior, aquele caminho REALMENTE escrevia três arquivos
276
+ * (`census.json`, `not-expressed.md` e o `ledger.jsonl` que nasce junto com a pasta). A
277
+ * pergunta que ele provocava era *"posso confiar neste comando?"*, quando a única pergunta
278
+ * que deveria estar na cabeça de quem roda é *"como é que ele entendeu o meu Button?"*.
279
+ *
280
+ * ELE NÃO SUBSTITUI O `doctor`, e os dois não se fundem: `inspect` responde "o que você vê?",
281
+ * `doctor` responde "onde isto está derivando?". São perguntas de momentos diferentes, e um
282
+ * comando que responde as duas responde mal as duas.
283
+ */
284
+ case "inspect":
285
+ await runImport({
286
+ root: dir,
287
+ cli: CLI_VERSION,
288
+ inspect: true,
289
+ scope: Array.isArray(flags.scope)
290
+ ? flags.scope
291
+ : typeof flags.scope === "string"
292
+ ? flags.scope
293
+ : undefined,
294
+ usage: [flags.usage]
295
+ .flat()
296
+ .filter((v) => typeof v === "string")
297
+ .flatMap((v) => v.split(","))
298
+ .map((v) => v.trim())
299
+ .filter(Boolean),
300
+ registry,
301
+ });
302
+ break;
254
303
  case "adopt":
255
304
  // Dry by default: `--write` is the only way anything lands on disk.
256
305
  await adopt({
@@ -286,6 +335,20 @@ async function main() {
286
335
  case "mode":
287
336
  await mode({ dir, ...(args[0] ? { wanted: args[0] } : {}) });
288
337
  break;
338
+ /**
339
+ * O INTERRUPTOR DELA NA PLATAFORMA - ver `autopilot.ts`.
340
+ *
341
+ * O irmão de `mode`, e a diferença importa: `mode` é local a este clone, e este muda um estado
342
+ * que mora no servidor e que a tela de configurações mexe pela outra porta. Sem argumento ele
343
+ * RESPONDE, pela mesma razão dos dois.
344
+ */
345
+ case "autopilot":
346
+ await autopilot({
347
+ dir,
348
+ registry,
349
+ ...(args[0] ? { wanted: args[0] } : {}),
350
+ });
351
+ break;
289
352
  // Long-lived: it owns stdin/stdout until the client closes the pipe, so
290
353
  // it must not be reached by anything that prints.
291
354
  case "mcp":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.425",
3
+ "version": "0.16.427",
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": {