synthesisui 0.16.445 → 0.16.446

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.
@@ -1,6 +1,7 @@
1
1
  import { access, mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import { hasCodexBlock, pinnedInCodex, withCodexBlock } from "./codex-mcp.js";
4
+ import { wrapperSource } from "./statusline-wrapper.js";
4
5
  /**
5
6
  * QUANDO A CHECAGEM RODA - e por que `Bash` entrou em 12/09.
6
7
  *
@@ -326,6 +327,39 @@ async function wireCodexMcp(root) {
326
327
  await writeFile(path, next, "utf8");
327
328
  return had ? "updated" : "added";
328
329
  }
330
+ /**
331
+ * A BARRA DE STATUS GANHA UM SEGMENTO, E A DELE CONTINUA INTEIRA - pedido do dono em 21/09:
332
+ * *"uma métrica um pouco na frente do Status que fale a quantidade de vezes que a gente
333
+ * sincronizou (...) se tiver alguma coisa lá, não mexe: mantém o que estava e adiciona"*.
334
+ *
335
+ * O `statusLine` do `.claude/settings.json` é DELE - no caso do dono, o Pulse, que vem do CodeLevel.
336
+ * Por isso não se substitui nada: o que se escreve é um invólucro que CHAMA o comando que estava lá
337
+ * e acrescenta o nosso segmento no fim. Se aquele comando cair, a barra dele ainda sai; se o ledger
338
+ * não puder ser lido, o segmento some e o resto fica.
339
+ *
340
+ * IDEMPOTENTE PELA MARCA: se o `statusLine` já aponta para o nosso arquivo, nada é reescrito - nem
341
+ * o `settings.json`, nem o invólucro, que guardaria o comando ERRADO se fosse regravado (ele já
342
+ * aponta para si mesmo, e o anterior se perderia).
343
+ */
344
+ export async function wireStatusLine(root) {
345
+ const rel = "_synthesisui/statusline.mjs";
346
+ const ours = `node ${rel}`;
347
+ const dir = join(root, ".claude");
348
+ const path = join(dir, "settings.json");
349
+ const settings = await readJson(path);
350
+ const current = settings.statusLine;
351
+ const command = typeof current?.command === "string" ? current.command : "";
352
+ if (command.includes(rel))
353
+ return { status: "already there", command: ours };
354
+ const was = command.trim() ? command : null;
355
+ await mkdir(join(root, "_synthesisui"), { recursive: true });
356
+ await writeFile(join(root, rel), wrapperSource(was), "utf8");
357
+ await mkdir(dir, { recursive: true });
358
+ await writeFile(path, `${JSON.stringify({ ...settings, statusLine: { type: "command", command: ours } }, null, 2)}\n`, "utf8");
359
+ return was
360
+ ? { status: "wrapped", command: ours, was }
361
+ : { status: "added", command: ours };
362
+ }
329
363
  export async function wireAgent(root, version, want) {
330
364
  /**
331
365
  * A ESCOLHA DELE DECIDE QUEM É FIADO (A2 da etapa 12) - ver `agents-chosen.ts`.
@@ -355,6 +389,14 @@ export async function wireAgent(root, version, want) {
355
389
  ? await wireSessionStart(root, await alignCommand(root, version))
356
390
  : "skipped",
357
391
  mcp: want.mcp ? await wireMcp(root, want.agents) : "skipped",
392
+ /**
393
+ * O SEGMENTO DA BARRA vai junto do resto da fiação do Claude Code, e pelo mesmo motivo: o
394
+ * `settings.json` é dele, e num repositório onde ele não escolheu o Claude Code escrever ali é
395
+ * plantar fiação que ninguém pediu.
396
+ */
397
+ statusLine: claude
398
+ ? await wireStatusLine(root)
399
+ : { status: "skipped", command: "" },
358
400
  };
359
401
  }
360
402
  /**
@@ -0,0 +1,105 @@
1
+ /**
2
+ * A BARRA DELE CONTINUA SENDO A DELE, E A NOSSA INFORMAÇÃO ENTRA AO LADO - pedido do dono em
3
+ * 21/09: *"se tiver alguma coisa lá, ele não mexesse; mantivesse o que estava lá e adicionasse
4
+ * mais essa informação"*.
5
+ *
6
+ * O `statusLine` do `.claude/settings.json` costuma já ter um script - no caso dele, o Pulse, que é
7
+ * do CodeLevel. Trocar aquilo pelo nosso seria apagar a barra que ele construiu. Então o que a
8
+ * gente instala é um INVÓLUCRO: ele chama o comando que estava lá, pega a saída inteira e acrescenta
9
+ * o nosso segmento no fim da última linha.
10
+ *
11
+ * ═══ O QUE ESTE MÓDULO É ═══
12
+ *
13
+ * A função que gera o TEXTO do invólucro. Ela existe para o conteúdo do arquivo ser testável sem
14
+ * escrever em disco nem rodar um shell - o que o teste guarda é que o comando anterior é chamado e
15
+ * que a falha dele não derruba a barra.
16
+ */
17
+ /** A marca que torna a instalação idempotente e a remoção segura. */
18
+ export const WRAPPER_MARK = "synthesisui:statusline";
19
+ /**
20
+ * O invólucro, como texto.
21
+ *
22
+ * `anterior` é o comando que já estava no `statusLine` - `null` quando não havia nenhum, e aí a
23
+ * barra passa a ser só o nosso segmento.
24
+ */
25
+ export function wrapperSource(anterior) {
26
+ return `#!/usr/bin/env node
27
+ /* ${WRAPPER_MARK} - managed by the synthesisui CLI. Delete this file to remove it.
28
+ *
29
+ * It runs YOUR status line and appends one segment: how many pulses today and in total.
30
+ * Your command is called first and its output is passed through untouched - if it fails,
31
+ * or if the count cannot be read, you still get your bar. A status line that disappears
32
+ * because a counter broke is worse than no counter.
33
+ */
34
+ import { execSync } from "node:child_process";
35
+ import { readFileSync } from "node:fs";
36
+ import { join } from "node:path";
37
+
38
+ const input = readFileSync(0, "utf8");
39
+ const previous = ${anterior === null ? "null" : JSON.stringify(anterior)};
40
+
41
+ let bar = "";
42
+ if (previous) {
43
+ try {
44
+ /* DOIS SEGUNDOS, E O NÚMERO É AJUSTÁVEL: a barra redesenha o tempo todo, então esperar por ela
45
+ é pagar em travamento do terminal. Quem tem uma barra pesada afrouxa por env em vez de
46
+ perder o segmento - e o teste usa isso para não competir com a suíte inteira. */
47
+ const limit = Number(process.env.SYNTHESISUI_BAR_TIMEOUT_MS) || 2000;
48
+ bar = execSync(previous, { input, encoding: "utf8", timeout: limit });
49
+ } catch {
50
+ /* a barra dele caiu ou demorou: seguimos com o nosso segmento, nunca com a tela vazia */
51
+ }
52
+ }
53
+
54
+ let segment = "";
55
+ try {
56
+ const dir = process.env.SYNTHESISUI_DIR ?? "_synthesisui";
57
+ const raw = readFileSync(join(process.cwd(), dir, "ledger.jsonl"), "utf8");
58
+ const lines = raw
59
+ .split("\\n")
60
+ .filter(Boolean)
61
+ .map((l) => {
62
+ try {
63
+ return JSON.parse(l);
64
+ } catch {
65
+ return null;
66
+ }
67
+ });
68
+ /* A CONTAGEM VIAJA INLINE, e não como import: um segundo arquivo ao lado é mais uma peça para
69
+ sumir, e este script roda a cada respiração do terminal. A regra está em pulse-count.ts, com
70
+ os testes; aqui ela é a mesma condição, escrita uma vez. */
71
+ const FETCHING = new Set(["import", "add", "component", "template", "upgrade", "sync", "absorb", "refit"]);
72
+ const now = new Date();
73
+ let today = 0;
74
+ let total = 0;
75
+ for (const l of lines) {
76
+ if (!l || l.kind !== "run" || typeof l.command !== "string") continue;
77
+ if (l.ok === false || !FETCHING.has(l.command) || typeof l.at !== "string") continue;
78
+ const when = new Date(l.at);
79
+ if (Number.isNaN(when.getTime())) continue;
80
+ total += 1;
81
+ if (
82
+ when.getFullYear() === now.getFullYear() &&
83
+ when.getMonth() === now.getMonth() &&
84
+ when.getDate() === now.getDate()
85
+ )
86
+ today += 1;
87
+ }
88
+ /* Zero e zero não vira "0 · 0": não há o que dizer ainda, e um zero parece defeito. */
89
+ if (total > 0) segment = \`Pulls \${today} today · \${total}\`;
90
+ } catch {
91
+ /* sem ledger ainda, ou ilegível: o segmento simplesmente não aparece */
92
+ }
93
+
94
+ if (!segment) {
95
+ process.stdout.write(bar);
96
+ } else if (!bar.trim()) {
97
+ process.stdout.write(segment);
98
+ } else {
99
+ /* No fim da ÚLTIMA linha: é lá que a barra dele termina, e é onde ele pediu - à frente do Status. */
100
+ const lines = bar.replace(/\\n$/, "").split("\\n");
101
+ lines[lines.length - 1] += " │ " + segment;
102
+ process.stdout.write(lines.join("\\n"));
103
+ }
104
+ `;
105
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.445",
3
+ "version": "0.16.446",
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": {