synthesisui 0.16.445 → 0.16.447
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/agent-wiring.js +68 -0
- package/dist/statusline-wrapper.js +105 -0
- package/package.json +1 -1
package/dist/agent-wiring.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
2
3
|
import { dirname, join } from "node:path";
|
|
3
4
|
import { hasCodexBlock, pinnedInCodex, withCodexBlock } from "./codex-mcp.js";
|
|
5
|
+
import { wrapperSource } from "./statusline-wrapper.js";
|
|
4
6
|
/**
|
|
5
7
|
* QUANDO A CHECAGEM RODA - e por que `Bash` entrou em 12/09.
|
|
6
8
|
*
|
|
@@ -326,6 +328,64 @@ async function wireCodexMcp(root) {
|
|
|
326
328
|
await writeFile(path, next, "utf8");
|
|
327
329
|
return had ? "updated" : "added";
|
|
328
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* A BARRA DE STATUS GANHA UM SEGMENTO, E A DELE CONTINUA INTEIRA - pedido do dono em 21/09:
|
|
333
|
+
* *"uma métrica um pouco na frente do Status que fale a quantidade de vezes que a gente
|
|
334
|
+
* sincronizou (...) se tiver alguma coisa lá, não mexe: mantém o que estava e adiciona"*.
|
|
335
|
+
*
|
|
336
|
+
* O `statusLine` do `.claude/settings.json` é DELE - no caso do dono, o Pulse, que vem do CodeLevel.
|
|
337
|
+
* Por isso não se substitui nada: o que se escreve é um invólucro que CHAMA o comando que estava lá
|
|
338
|
+
* e acrescenta o nosso segmento no fim. Se aquele comando cair, a barra dele ainda sai; se o ledger
|
|
339
|
+
* não puder ser lido, o segmento some e o resto fica.
|
|
340
|
+
*
|
|
341
|
+
* IDEMPOTENTE PELA MARCA: se o `statusLine` já aponta para o nosso arquivo, nada é reescrito - nem
|
|
342
|
+
* o `settings.json`, nem o invólucro, que guardaria o comando ERRADO se fosse regravado (ele já
|
|
343
|
+
* aponta para si mesmo, e o anterior se perderia).
|
|
344
|
+
*/
|
|
345
|
+
export async function wireStatusLine(root,
|
|
346
|
+
/**
|
|
347
|
+
* A CASA, INJETADA - e não lida de `process.env` dentro do teste.
|
|
348
|
+
*
|
|
349
|
+
* O teste precisa de um `~/.claude/settings.json` de mentira, e a primeira versão trocava o
|
|
350
|
+
* `HOME` do processo para consegui-lo. Duas coisas erradas nisso: o gate de provas dependentes de
|
|
351
|
+
* máquina passa a ver este spec como quem lê a casa de quem roda - e ele estaria certo, porque
|
|
352
|
+
* trocar env é global e vaza para o que roda em paralelo ao lado.
|
|
353
|
+
*/
|
|
354
|
+
home = homedir()) {
|
|
355
|
+
const rel = "_synthesisui/statusline.mjs";
|
|
356
|
+
const ours = `node ${rel}`;
|
|
357
|
+
const dir = join(root, ".claude");
|
|
358
|
+
const path = join(dir, "settings.json");
|
|
359
|
+
const settings = await readJson(path);
|
|
360
|
+
const current = settings.statusLine;
|
|
361
|
+
const command = typeof current?.command === "string" ? current.command : "";
|
|
362
|
+
if (command.includes(rel))
|
|
363
|
+
return { status: "already there", command: ours };
|
|
364
|
+
/**
|
|
365
|
+
* A BARRA DELE PODE MORAR NO SETTINGS GLOBAL, E QUASE SEMPRE MORA - achado em 21/09, com o print
|
|
366
|
+
* dele na mão e o invólucro já instalado.
|
|
367
|
+
*
|
|
368
|
+
* A primeira versão olhava só o `.claude/settings.json` DO PROJETO. No repositório dele não havia
|
|
369
|
+
* `statusLine` ali - o Pulse está em `~/.claude/settings.json` -, então o invólucro nasceu com
|
|
370
|
+
* "não havia barra anterior" e deixaria de chamar o Pulse. Na sessão seguinte ele perderia a barra
|
|
371
|
+
* inteira e veria só o nosso segmento: exatamente o contrário do que ele pediu.
|
|
372
|
+
*
|
|
373
|
+
* O Claude Code resolve `statusLine` do projeto SOBRE o global, então o que a gente substitui é o
|
|
374
|
+
* que estava valendo ali - e o que estava valendo é o global quando o projeto cala.
|
|
375
|
+
*/
|
|
376
|
+
const globalPath = join(home, ".claude", "settings.json");
|
|
377
|
+
const globalSettings = await readJson(globalPath);
|
|
378
|
+
const globalLine = globalSettings.statusLine;
|
|
379
|
+
const fallback = typeof globalLine?.command === "string" ? globalLine.command : "";
|
|
380
|
+
const was = command.trim() ? command : fallback.trim() ? fallback : null;
|
|
381
|
+
await mkdir(join(root, "_synthesisui"), { recursive: true });
|
|
382
|
+
await writeFile(join(root, rel), wrapperSource(was), "utf8");
|
|
383
|
+
await mkdir(dir, { recursive: true });
|
|
384
|
+
await writeFile(path, `${JSON.stringify({ ...settings, statusLine: { type: "command", command: ours } }, null, 2)}\n`, "utf8");
|
|
385
|
+
return was
|
|
386
|
+
? { status: "wrapped", command: ours, was }
|
|
387
|
+
: { status: "added", command: ours };
|
|
388
|
+
}
|
|
329
389
|
export async function wireAgent(root, version, want) {
|
|
330
390
|
/**
|
|
331
391
|
* A ESCOLHA DELE DECIDE QUEM É FIADO (A2 da etapa 12) - ver `agents-chosen.ts`.
|
|
@@ -355,6 +415,14 @@ export async function wireAgent(root, version, want) {
|
|
|
355
415
|
? await wireSessionStart(root, await alignCommand(root, version))
|
|
356
416
|
: "skipped",
|
|
357
417
|
mcp: want.mcp ? await wireMcp(root, want.agents) : "skipped",
|
|
418
|
+
/**
|
|
419
|
+
* O SEGMENTO DA BARRA vai junto do resto da fiação do Claude Code, e pelo mesmo motivo: o
|
|
420
|
+
* `settings.json` é dele, e num repositório onde ele não escolheu o Claude Code escrever ali é
|
|
421
|
+
* plantar fiação que ninguém pediu.
|
|
422
|
+
*/
|
|
423
|
+
statusLine: claude
|
|
424
|
+
? await wireStatusLine(root)
|
|
425
|
+
: { status: "skipped", command: "" },
|
|
358
426
|
};
|
|
359
427
|
}
|
|
360
428
|
/**
|
|
@@ -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