synthesisui 0.16.415 → 0.16.416

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.
@@ -159,12 +159,11 @@ export async function chooseAgents(input) {
159
159
  const pre = remembered ?? (wired.length > 0 ? wired : await detected(input.root));
160
160
  if (!input.ask)
161
161
  return { agents: pre, from: "evidence", unknown: [] };
162
- const typed = await input.ask(pre);
163
- /** Enter mantém a pré-marcação - a confirmação é uma tecla, e continua sendo escolha dele. */
164
- if (typed.trim() === "")
165
- return { agents: pre, from: "asked", unknown: [] };
166
- const read = parseAgents(typed);
167
- return { agents: read.agents, from: "asked", unknown: read.unknown };
162
+ const picked = await input.ask(pre);
163
+ /** Sem terminal para perguntar, a evidência decide - o mesmo caminho de quem não tem TTY. */
164
+ if (picked === null)
165
+ return { agents: pre, from: "evidence", unknown: [] };
166
+ return { agents: picked, from: "asked", unknown: [] };
168
167
  }
169
168
  /**
170
169
  * A ESCOLHA REGISTRADA - e é ela que distingue "ele desmarcou" de "sobrou arquivo no disco".
@@ -7,6 +7,7 @@ import { resolveRegistry } from "../config.js";
7
7
  import { notAProject, projectRootFrom } from "../is-a-project.js";
8
8
  import { fmt, say } from "../lang.js";
9
9
  import { body, bodyWrapped, paint, section, snippet } from "../output.js";
10
+ import { canPick, pickMany } from "../pick-many.js";
10
11
  import { readShellAnswer, rememberShellNo } from "../shell-answer.js";
11
12
  import { existingRc, hasHook, pinnedInHook, rcPathFor, shellFrom, shellSnippet, withHook, } from "../shell-hook.js";
12
13
  import { SKILLS } from "../skills.js";
@@ -226,32 +227,29 @@ version) {
226
227
  async function askWhichAgents(pre) {
227
228
  console.log("");
228
229
  console.log(section(say("Which agents")));
229
- for (const agent of AGENTS) {
230
- const on = pre.includes(agent.id);
231
- console.log(body(`${on ? "[x]" : "[ ]"} ${agent.label.padEnd(13)} ${on
232
- ? fmt(say("{dir} is here"), { dir: agent.evidence })
233
- : fmt(say("no {dir} in this repo"), { dir: agent.evidence })}`));
234
- }
230
+ console.log(body(paint.faint(say("Space marks, arrows move, Enter confirms - nothing is written yet."))));
235
231
  console.log("");
236
- console.log(body(say("Enter keeps this. To change it, type the ones you want - or `none`:")));
237
- console.log(body(paint.faint(` ${AGENTS.map((a) => a.id).join(" ")}`)));
238
- const { createInterface } = await import("node:readline/promises");
239
- const rl = createInterface({
240
- input: process.stdin,
241
- output: process.stdout,
232
+ if (!canPick())
233
+ return null;
234
+ const picked = await pickMany({
235
+ items: AGENTS.map((agent) => ({
236
+ id: agent.id,
237
+ label: agent.label,
238
+ note: pre.includes(agent.id)
239
+ ? fmt(say("{dir} is here"), { dir: agent.evidence })
240
+ : fmt(say("no {dir} in this repo"), { dir: agent.evidence }),
241
+ })),
242
+ picked: pre,
242
243
  });
243
- const typed = await rl.question(" > ").catch(() => "");
244
- rl.close();
245
- return typed;
244
+ return picked;
246
245
  }
247
246
  /**
248
- * O QUE FICOU ESCOLHIDO, ecoado antes de agir - achado da revisão de DX no fecho.
247
+ * O QUE FICOU ESCOLHIDO, ecoado antes de agir.
249
248
  *
250
- * A frase da lista diz que digitar SUBSTITUI o conjunto, e o risco não é de vocabulário: é de
251
- * hábito. Quem está acostumado com seletor incremental `[x] Codex` marcado, digita `claude`
252
- * querendo acrescentar, e perde o Codex. A consequência aparecia embaixo, misturada com as outras
253
- * linhas; uma linha só, antes de qualquer escrita, torna o engano visível no lugar onde ele
254
- * aconteceu.
249
+ * Nasceu na etapa 12 contra um engano que o seletor de agora não permite mais - lá, digitar
250
+ * `claude` SUBSTITUÍA a lista, e quem tinha hábito de seletor incremental perdia o outro. Com o
251
+ * espaço marcando, marcar é aditivo por natureza. A linha fica porque a outra metade dela continua
252
+ * valendo: ela é a última coisa antes das escritas, e diz o que foi para o disco.
255
253
  */
256
254
  function echoChoice(agents) {
257
255
  console.log("");
@@ -43,9 +43,9 @@ register("pt-BR", {
43
43
  * razão da frase `import my design system`: onde a máquina lê, a precisão vence a gentileza.
44
44
  */
45
45
  "Which agents": "Quais agentes",
46
+ "Space marks, arrows move, Enter confirms - nothing is written yet.": "O espaço marca, as setas movem, o Enter confirma - nada foi escrito ainda.",
46
47
  "{dir} is here": "{dir} está aqui",
47
48
  "no {dir} in this repo": "nenhum {dir} neste repo",
48
- "Enter keeps this. To change it, type the ones you want - or `none`:": "Enter mantém isto. Para mudar, digite os que você quer - ou `none`:",
49
49
  "· {label} not chosen - these stay in the repo: {files}": "· {label} não escolhido - estes ficam no repo: {files}",
50
50
  "· {label} not chosen - nothing of it was written": "· {label} não escolhido - nada dele foi escrito",
51
51
  "· Cursor is no longer wired by this CLI - what we wrote is still here: {files}": "· o Cursor não é mais fiado por este CLI - o que a gente escreveu continua aqui: {files}",
@@ -214,7 +214,19 @@
214
214
  * O que o cliente ganha ao rodar `upgrade`: nada de uma ferramenta que ele não usa é reescrito no
215
215
  * repositório dele.
216
216
  */
217
- export const MATERIALISER_SINCE = "0.16.415";
217
+ /**
218
+ * 0.16.415 -> 0.16.416 em 10/09: a lista de agentes passa a ser MARCADA com o espaço, e não
219
+ * digitada. Ele testou a versão anterior no próprio repositório e disse: *"eu não consigo
220
+ * selecionar, eu queria clicar ou selecionar com o espaço para checar"* - a tela desenhava `[x]` e
221
+ * `[ ]`, a forma universal de caixa de seleção, e exigia digitar o nome do agente.
222
+ *
223
+ * A marca sobe porque a ESCOLHA decide o conjunto de arquivos que cai na pasta dele, e um
224
+ * `connect` pinado numa versão anterior pede aquela escolha de um jeito que ele já tentou e que
225
+ * não funcionou.
226
+ *
227
+ * O que o cliente ganha ao rodar `upgrade`: a lista responde ao espaço, como a caixa promete.
228
+ */
229
+ export const MATERIALISER_SINCE = "0.16.416";
218
230
  /**
219
231
  * A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
220
232
  *
@@ -0,0 +1,108 @@
1
+ /**
2
+ * ELE MARCA COM O ESPAÇO - a lista de escolha MÚLTIPLA do terminal.
3
+ *
4
+ * O DEFEITO QUE ISTO CONSERTA, e ele foi achado por ELE testando a entrega anterior no próprio
5
+ * repositório (10/09): *"eu não consigo selecionar, eu queria clicar ou selecionar com o espaço
6
+ * para checar"*. A lista desenhava `[x]` e `[ ]` - a forma universal de caixa de seleção - e
7
+ * exigia digitar o nome do item. A tela prometia uma interação que não existia, e a promessa da
8
+ * tela é a única que a pessoa lê.
9
+ *
10
+ * O IRMÃO JÁ EXISTIA: `commands/import.ts` tem um seletor de escolha ÚNICA desde 31/07 - setas,
11
+ * Enter, Ctrl-C tratado, redesenho subindo N linhas. Este é o mesmo mecanismo com a diferença que
12
+ * importa: o espaço marca e desmarca, e a resposta é uma lista. Os dois ficam, porque a pergunta
13
+ * "qual" e a pergunta "quais" são diferentes na tela; o que não pode existir é uma TERCEIRA
14
+ * leitura de tecla neste CLI.
15
+ *
16
+ * ─────────────────────────────────────────────────────────────────────────
17
+ * AS TRÊS COISAS QUE PODEM DAR ERRADO FORA DO NOSSO PROGRAMA, e é por elas que este módulo é
18
+ * pequeno e fechado:
19
+ *
20
+ * o raw mode fica ligado o shell dele para de ecoar o que ele digita, muito depois de a
21
+ * gente ter terminado. Restaurado em TODA saída, inclusive a de erro
22
+ * Ctrl-C vira escolha sair não é escolher. O `onQuit` encerra sem resolver a promessa
23
+ * não há terminal não se tenta ler tecla nenhuma: quem chama decide o que fazer,
24
+ * e no `connect` isso já tem resposta desde a etapa 12
25
+ *
26
+ * `stdin` e `stdout` são ARGUMENTOS porque um seletor que fala com `process` direto não tem como
27
+ * ser provado sem um terminal - e o que precisa de prova aqui é a leitura de tecla, não o
28
+ * desenho.
29
+ */
30
+ /** O terminal tem teclado? Sem isto, não se tenta ler tecla nenhuma. */
31
+ export const canPick = (stdin = process.stdin, stdout = process.stdout) => Boolean(stdin.isTTY) &&
32
+ Boolean(stdout.isTTY) &&
33
+ typeof stdin.setRawMode === "function";
34
+ const UP = "";
35
+ const DOWN = "";
36
+ const ENTER_KEYS = ["\r", "\n"];
37
+ const CTRL_C = "";
38
+ export function pickMany(input) {
39
+ const stdin = (input.stdin ?? process.stdin);
40
+ const stdout = input.stdout ?? process.stdout;
41
+ const items = input.items;
42
+ const marked = new Set(input.picked);
43
+ let at = 0;
44
+ let first = true;
45
+ const render = () => {
46
+ if (!first)
47
+ stdout.write(`[${items.length}A`);
48
+ first = false;
49
+ for (const [i, item] of items.entries()) {
50
+ const box = marked.has(item.id) ? "[x]" : "[ ]";
51
+ const cursor = i === at ? ">" : " ";
52
+ const note = item.note ? ` ${item.note}` : "";
53
+ stdout.write(` ${cursor} ${box} ${item.label.padEnd(13)}${note}\n`);
54
+ }
55
+ };
56
+ render();
57
+ return new Promise((resolve) => {
58
+ /**
59
+ * UMA SAÍDA SÓ, e é ela que garante a promessa do raw mode. Toda tecla que termina a
60
+ * pergunta passa por aqui - inclusive o Ctrl-C.
61
+ */
62
+ const leave = () => {
63
+ stdin.off("data", onData);
64
+ if (stdin.isTTY)
65
+ stdin.setRawMode?.(false);
66
+ stdin.pause();
67
+ };
68
+ const onData = (buf) => {
69
+ const key = buf.toString();
70
+ if (key === CTRL_C) {
71
+ leave();
72
+ stdout.write("\n");
73
+ (input.onQuit ?? (() => process.exit(130)))();
74
+ return;
75
+ }
76
+ if (key === UP || key === "k") {
77
+ at = (at + items.length - 1) % items.length;
78
+ render();
79
+ return;
80
+ }
81
+ if (key === DOWN || key === "j" || key === "\t") {
82
+ at = (at + 1) % items.length;
83
+ render();
84
+ return;
85
+ }
86
+ if (key === " ") {
87
+ const id = items[at]?.id;
88
+ if (id !== undefined) {
89
+ if (marked.has(id))
90
+ marked.delete(id);
91
+ else
92
+ marked.add(id);
93
+ render();
94
+ }
95
+ return;
96
+ }
97
+ if (ENTER_KEYS.includes(key)) {
98
+ leave();
99
+ /** Na ORDEM da lista, e não na ordem em que ele marcou - a tela é que ele leu. */
100
+ resolve(items.filter((i) => marked.has(i.id)).map((i) => i.id));
101
+ }
102
+ };
103
+ if (stdin.isTTY)
104
+ stdin.setRawMode?.(true);
105
+ stdin.resume();
106
+ stdin.on("data", onData);
107
+ });
108
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.415",
3
+ "version": "0.16.416",
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": {