engine-kit 0.2.2 → 0.2.4

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/CONTRATO-MOTOR.md CHANGED
@@ -157,6 +157,10 @@ polui o histórico e obriga todo consumidor a decidir se atualiza — para desco
157
157
  | `embed-house` | daemon | `embed-house` | `engine-registry` ✅ | ✅ | `copilot-marketplace` |
158
158
  | `neural-link` | **cli** | `neural-link` | `engine-registry` ✅ | ✅ | `neural-link-runtime` (redirecionado) |
159
159
  | `vox-engine` | daemon (`pipe`), **self-managed** | `vox-engine` | `engine-registry` ✅ | ✅ | `copilot-marketplace` |
160
+ | `vxk` | **cli** (shim stdio; daemon único próprio, desligado pelo `ensure` via `runtime.shutdownPath`) | `visual-explainer` | `engine-registry` ✅ | ✅ | — (nunca esteve lá) |
161
+ | `cerne` | daemon, **self-managed** | `cerne` | `engine-registry` ✅ | ✅ | — (nunca esteve lá) |
162
+ | `skill-engine` | **cli** | `skill-engine` | `engine-registry` ✅ | ✅ | — (nunca esteve lá) |
163
+ | `client-harness` | **cli** (daemon único próprio, desligado pelo `ensure` via `runtime.shutdownPath`) | `harness-padroes-cliente` | `engine-registry` ✅ | ✅ | — (nunca esteve lá) |
160
164
 
161
165
  Cada repo de motor tem o próprio `publish.ps1`, que **builda** o que é específico dele e **delega**
162
166
  ao `kit/publish-base.ps1` a parte comum (identidade do `gh`, assinatura, release, manifest, prova).
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Allan Santos
3
+ Copyright (c) 2026 The engine-registry maintainers
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -27,11 +27,12 @@ vive num lugar só e o consumidor apenas **declara a dependência**.
27
27
  |---|---|---|---|
28
28
  | `embed-house` | 1.0.5 | `daemon` (http) | Casa de embeddings (MiniLM-L6-v2, 384-dim): carrega o modelo uma vez e serve vetores a N consumidores. |
29
29
  | `vox-engine` | 0.28.7 | `daemon` (pipe), **self-managed** | Motor de voz (STT/TTS) compartilhado, com auto-unload quando ocioso. Traz o próprio instalador e updater. |
30
- | `mcp-gateway` | 0.16.4 | `daemon` (http) | Agregador MCP: conecta uma vez em cada servidor MCP e reexpõe tudo num endpoint único. |
30
+ | `mcp-gateway` | 0.17.2 | `daemon` (http) | Agregador MCP: conecta uma vez em cada servidor MCP e reexpõe tudo num endpoint único. |
31
31
  | `neural-link` | 0.7.15 | `cli` | Dispatcher adaptativo de hooks: recebe o evento por stdin, pontua os handlers e devolve uma decisão composta. |
32
- | `vxk` | 0.18.0 | `cli` | Explicações visuais animadas e narradas como servidor MCP (stdio): devolve UM HTML autocontido e offline. A narração vem do `vox-engine`. |
33
- | `cerne` | 0.4.10 | `daemon`, **self-managed** | Leitura ativa de vídeo (captura, quadros, transcrição, tradução) exposta via MCP. Instalador Inno por usuário, Windows. |
34
- | `skill-engine` | 0.1.0 | `cli` | Gerencia skills de agentes fora de qualquer IDE: busca repositórios em quarentena, varre com regras determinísticas e só ativa o que sai limpo. |
32
+ | `vxk` | 0.21.0 | `cli` | Explicações visuais animadas e narradas como servidor MCP (stdio): devolve UM HTML autocontido e offline, ou o vídeo MP4 (preset, aplicativo real, Motion) e o PDF. Um daemon único por máquina atende os agentes. A narração vem do `vox-engine`. |
33
+ | `cerne` | 0.4.14 | `daemon`, **self-managed** | Leitura ativa de vídeo (captura, quadros, transcrição, tradução) exposta via MCP. Instalador Inno por usuário, Windows. |
34
+ | `skill-engine` | 0.2.1 | `cli` | Gerencia skills de agentes fora de qualquer IDE: busca repositórios em quarentena, varre com regras determinísticas e só ativa o que sai limpo. |
35
+ | `client-harness` | 0.1.5 | `cli` | Daemon local que faz o Claude Code e o Codex respeitarem os padrões de cada cliente: confere cada ação do agente contra as regras da pasta e recusa antes de gravar, com o motivo. |
35
36
 
36
37
  **Todos** são publicados aqui, assinados com Ed25519 e verificados antes de instalar.
37
38
 
@@ -42,7 +43,10 @@ Um motor pode ser de dois tipos (`kind`):
42
43
  - **`cli`** — executável invocado **por evento** (ex.: um dispatcher de hooks). Não há processo
43
44
  para manter vivo: provisionar já é entregar. O `lifecycle` devolve o caminho do binário sem
44
45
  spawn, sem health e sem runtime — forçar um daemon aqui seria inventar um ciclo de vida que o
45
- motor não tem.
46
+ motor não tem. Exceção: `vxk` (shim stdio por agente) e `client-harness` são `cli` mas delegam a
47
+ um daemon único por máquina. O kit não sobe nem espera esse daemon, mas o manifest declara `runtime`
48
+ (`run/daemon.json`, `shutdownPath`, token) para o `ensure` pedir o desligamento antes de trocar a
49
+ instalação — no Windows um daemon vivo segura a pasta e o swap falharia com EPERM.
46
50
 
47
51
  E pode declarar `status: "self-managed"`: o motor traz o **próprio instalador/updater** (o caso do
48
52
  `vox-engine`, um app Python). A entrada aqui é o **descritor de registro** — onde está a release,
@@ -55,7 +59,7 @@ Sem clonar o repositório, com Node 18 ou mais:
55
59
 
56
60
  ```sh
57
61
  npx engine-kit ensure vxk # instala ou atualiza; reusa se já estiver em dia
58
- npx engine-kit ensure vxk --version 0.18.0 # pin de versão
62
+ npx engine-kit ensure vxk --version 0.21.0 # pin de versão
59
63
  ```
60
64
 
61
65
  O `npx` baixa o kit do npm e o kit baixa o motor da release, confere o **sha256**, a **assinatura Ed25519**
@@ -267,7 +271,7 @@ plataforma, e a release mora aqui, no registry público, com download **sem logi
267
271
 
268
272
  | App | Versão | Plataformas | O que faz |
269
273
  |---|---|---|---|
270
- | — | — | — | Nenhum app registrado ainda. |
274
+ | `agent-launcher` | 1.3.5 | `win32-x64` | Abre Claude Code, Codex, Copilot CLI, VS Code e o Copilot app nos seus projetos por qualquer host de modelos. Leva só o launcher; cada ferramenta se instala pelo cartão dela. |
271
275
 
272
276
  Um app só entra nesta tabela e em `manifest.apps` **depois** de a release pública existir e passar
273
277
  em `node kit/verify-release.mjs --app <nome>` (download anônimo de todos os assets, `.sha256` e
package/kit/lifecycle.mjs CHANGED
@@ -8,7 +8,9 @@
8
8
  // • daemon — processo longo que anuncia `runtime.json` e responde health. É o fluxo abaixo.
9
9
  // • cli — executável invocado POR EVENTO (ex.: o dispatcher de hooks): não há processo para
10
10
  // manter vivo, nem runtime, nem health. Provisionar já é entregar. Forçar um daemon
11
- // aqui seria inventar um ciclo de vida que o motor não tem.
11
+ // aqui seria inventar um ciclo de vida que o motor não tem. Exceção: o vxk é cli mas
12
+ // delega a um daemon próprio; o kit não o sobe, só o derruba (`shutdown`, via
13
+ // `runtime.shutdownPath`) antes de trocar a instalação.
12
14
  import { spawn } from "node:child_process";
13
15
  import { readFileSync, statSync } from "node:fs";
14
16
  import { join } from "node:path";
@@ -63,6 +65,14 @@ async function findAlive(engine, healthCheck) {
63
65
  return health ? { runtime: rt, health } : null;
64
66
  }
65
67
 
68
+ /** Espera o processo sair (até `ms`). true = saiu. */
69
+ async function waitExit(pid, ms) {
70
+ for (const fim = Date.now() + ms; Date.now() < fim; await sleep(500)) {
71
+ try { process.kill(pid, 0); } catch { return true; }
72
+ }
73
+ return false;
74
+ }
75
+
66
76
  /**
67
77
  * Pede ao motor que se encerre, usando SÓ o que o registry declara.
68
78
  *
@@ -74,7 +84,7 @@ async function findAlive(engine, healthCheck) {
74
84
  *
75
85
  * @returns {Promise<{ok:true, stopped:boolean} | {ok:false, reason:string}>} nunca lança.
76
86
  */
77
- export async function shutdown(engine, { log = () => {}, timeoutMs = 5000 } = {}) {
87
+ export async function shutdown(engine, { log = () => {}, timeoutMs = 5000, waitBusyMs = 120000 } = {}) {
78
88
  const path = engine.runtime?.shutdownPath;
79
89
  if (!path) { return { ok: true, stopped: false }; }
80
90
 
@@ -93,10 +103,16 @@ export async function shutdown(engine, { log = () => {}, timeoutMs = 5000 } = {}
93
103
  const res = await fetch(`http://127.0.0.1:${rt.port}${path}`, {
94
104
  method: "POST", headers, signal: AbortSignal.timeout(timeoutMs),
95
105
  });
96
- if (!res.ok) { return { ok: false, reason: `${engine.name}: shutdown respondeu HTTP ${res.status}` }; }
97
- log(`[engine-kit] ${engine.name}: shutdown pedido (pid ${rt.pid ?? "?"})`);
106
+ // 202 = aceito, mas o motor está ocupado e só fecha quando o trabalho em curso acabar (vxk com build rodando);
107
+ // 503 = já está fechando. Nos dois o swap só é seguro depois que o processo sair.
108
+ const adiado = res.status === 202 || res.status === 503;
109
+ if (!res.ok && !adiado) { return { ok: false, reason: `${engine.name}: shutdown respondeu HTTP ${res.status}` }; }
110
+ log(`[engine-kit] ${engine.name}: shutdown pedido (pid ${rt.pid ?? "?"}, HTTP ${res.status})`);
98
111
  // O motor responde ANTES de sair. Sem esta espera, o swap ainda encontra o arquivo em uso.
99
112
  await sleep(1500);
113
+ if (adiado && rt.pid && !(await waitExit(rt.pid, waitBusyMs))) {
114
+ return { ok: false, reason: `${engine.name}: ainda ocupado após ${waitBusyMs / 1000} s (fecha ao fim do trabalho em curso); rode de novo depois` };
115
+ }
100
116
  return { ok: true, stopped: true };
101
117
  } catch (e) {
102
118
  return { ok: false, reason: `${engine.name}: shutdown falhou (${e?.message || e})` };
package/kit/provision.mjs CHANGED
@@ -16,6 +16,16 @@ import { checkTrust, trustPath } from "./trust.mjs";
16
16
 
17
17
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
18
18
 
19
+ // No Windows o handle de quem acabou de sair solta um instante depois: rename ocupado repete com backoff, depois lança.
20
+ export async function renameRetry(a, b, tries = 8) {
21
+ for (let i = 1; ; await sleep(100 * i++)) {
22
+ try { return renameSync(a, b); } catch (e) { if (i >= tries || !/^(EBUSY|EPERM|EACCES)$/.test(e.code)) { throw e; } }
23
+ }
24
+ }
25
+
26
+ const dropFile = (p) => { try { unlinkSync(p); } catch { /* ignore */ } };
27
+ const rmTree = (p) => rmSync(p, { recursive: true, force: true });
28
+
19
29
  /** Lock mais velho que isto é considerado ÓRFÃO (processo morreu antes do finally). */
20
30
  const LOCK_STALE_MS = 120000;
21
31
 
@@ -56,19 +66,13 @@ function extractTgz(tgz, dest) {
56
66
  * `openSync` e o `finally` travaria todos os consumidores para sempre.
57
67
  */
58
68
  function claimLock(lock) {
59
- try {
69
+ const open = () => {
60
70
  const fd = openSync(lock, "wx");
61
71
  try { writeFileSync(lock, String(process.pid)); } catch { /* diagnóstico é best-effort */ }
62
72
  return fd;
63
- } catch {
64
- try {
65
- if (Date.now() - statSync(lock).mtimeMs > LOCK_STALE_MS) {
66
- unlinkSync(lock);
67
- const fd = openSync(lock, "wx");
68
- try { writeFileSync(lock, String(process.pid)); } catch { /* ignore */ }
69
- return fd;
70
- }
71
- } catch { /* outro consumidor ganhou a corrida */ }
73
+ };
74
+ try { return open(); } catch {
75
+ try { if (Date.now() - statSync(lock).mtimeMs > LOCK_STALE_MS) { unlinkSync(lock); return open(); } } catch { /* outro consumidor ganhou a corrida */ }
72
76
  return null;
73
77
  }
74
78
  }
@@ -305,7 +309,7 @@ export async function provision(engine, { log = () => {}, allowNetwork = true, o
305
309
  // 3) Integridade (mismatch = apaga + ABORT).
306
310
  const actual = sha256File(tgz);
307
311
  if (actual !== expected) {
308
- try { unlinkSync(tgz); } catch { /* ignore */ }
312
+ dropFile(tgz);
309
313
  return { ok: false, reason: `${engine.name}: SHA256 mismatch (esperado ${expected.slice(0, 12)}…, obtido ${actual.slice(0, 12)}…) → ABORT` };
310
314
  }
311
315
 
@@ -313,13 +317,13 @@ export async function provision(engine, { log = () => {}, allowNetwork = true, o
313
317
  // final: se a assinatura não confere, o .tgz é apagado e nada é instalado.
314
318
  const auth = await verifyAuthenticity(engine, buf, { log, fetcher, trustedKeys });
315
319
  if (!auth.ok) {
316
- try { unlinkSync(tgz); } catch { /* ignore */ }
320
+ dropFile(tgz);
317
321
  return { ok: false, reason: auth.reason };
318
322
  }
319
323
 
320
324
  // 4) Extrai em staging e faz o swap (com shutdown-first se já existe).
321
325
  const staging = join(home, `stage-${engine.version}`);
322
- rmSync(staging, { recursive: true, force: true });
326
+ rmTree(staging);
323
327
  mkdirSync(staging, { recursive: true });
324
328
  const ex = extractTgz(tgz, staging);
325
329
  if (!ex.ok) { return { ok: false, reason: `${engine.name}: ${ex.reason}` }; }
@@ -329,8 +333,8 @@ export async function provision(engine, { log = () => {}, allowNetwork = true, o
329
333
  // sha256 e pela Ed25519. O package.json do artefato tem de dizer a versão que o registry pede.
330
334
  const got = installedVersion(staging);
331
335
  if (got !== engine.version) {
332
- rmSync(staging, { recursive: true, force: true });
333
- try { unlinkSync(tgz); } catch { /* ignore */ }
336
+ rmTree(staging);
337
+ dropFile(tgz);
334
338
  return { ok: false, reason: `${engine.name}: o artefato diz v${got ?? "? (package.json ausente/ilegível)"} e o registry pede v${engine.version} → ABORT` };
335
339
  }
336
340
 
@@ -341,12 +345,12 @@ export async function provision(engine, { log = () => {}, allowNetwork = true, o
341
345
  if (existsSync(binDir)) {
342
346
  if (onBeforeReplace) { try { await onBeforeReplace(); } catch { /* best-effort */ } }
343
347
  aside = `${binDir}.old-${process.pid}`;
344
- rmSync(aside, { recursive: true, force: true });
348
+ rmTree(aside);
345
349
  try {
346
- renameSync(binDir, aside);
350
+ await renameRetry(binDir, aside);
347
351
  } catch (e) {
348
- rmSync(staging, { recursive: true, force: true });
349
- try { unlinkSync(tgz); } catch { /* ignore */ }
352
+ rmTree(staging);
353
+ dropFile(tgz);
350
354
  return { ok: false, reason: `${engine.name}: não consegui trocar ${binDir} (${e.code || e.message}): arquivos em uso — feche quem usa o motor e rode de novo; a instalação atual ficou intacta` };
351
355
  }
352
356
  }
@@ -355,16 +359,16 @@ export async function provision(engine, { log = () => {}, allowNetwork = true, o
355
359
  // isto, funciona na máquina que já tem a pasta e falha justamente na máquina limpa.
356
360
  mkdirSync(dirname(binDir), { recursive: true });
357
361
  try {
358
- renameSync(staging, binDir);
362
+ await renameRetry(staging, binDir);
359
363
  } catch (e) {
360
364
  if (aside) { try { renameSync(aside, binDir); } catch { /* o reason abaixo diz onde ficou */ } }
361
365
  return { ok: false, reason: `${engine.name}: swap falhou (${e.code || e.message})${aside && existsSync(aside) ? `; a instalação anterior está em ${aside}` : ""}` };
362
366
  }
363
367
  if (aside) {
364
- try { rmSync(aside, { recursive: true, force: true }); }
368
+ try { rmTree(aside); }
365
369
  catch (e) { log(`[engine-kit] ${engine.name}: instalado, mas a versão anterior ficou em ${aside} (${e.code || e.message})`); }
366
370
  }
367
- try { unlinkSync(tgz); } catch { /* ignore */ }
371
+ dropFile(tgz);
368
372
 
369
373
  if (!existsSync(entryPath)) {
370
374
  return { ok: false, reason: `${engine.name}: artefato extraído incompleto (sem ${engine.install.entry})` };
package/manifest.json CHANGED
@@ -3,11 +3,37 @@
3
3
  "registry": "engine-registry",
4
4
  "version": 1,
5
5
  "owner": {
6
- "name": "Allan Santos",
6
+ "name": "The engine-registry maintainers",
7
7
  "url": "https://github.com/AllanSantos-DV"
8
8
  },
9
9
  "description": "Catálogo público de motores compartilhados e aplicativos distribuídos. engine-kit consome apenas engines; apps declara versões e downloads sem instalar aplicativos automaticamente.",
10
- "apps": [],
10
+ "apps": [
11
+ {
12
+ "name": "agent-launcher",
13
+ "version": "1.3.5",
14
+ "summary": "Abre Claude Code, Codex, Copilot CLI, VS Code e o Copilot app nos seus projetos por qualquer host de modelos. Leva só o launcher; cada ferramenta se instala pelo cartão dela. Windows.",
15
+ "homepage": "https://allansantos-dv.github.io/agent-launcher/",
16
+ "license": "MIT",
17
+ "platforms": [
18
+ "win32-x64"
19
+ ],
20
+ "release": {
21
+ "repo": "AllanSantos-DV/engine-registry",
22
+ "tag": "agent-launcher-v{version}",
23
+ "assets": [
24
+ {
25
+ "name": "agent-launcher-{version}-win32-x64.tgz",
26
+ "platform": "win32-x64",
27
+ "checksum": "{asset}.sha256",
28
+ "signature": "{asset}.sig"
29
+ }
30
+ ],
31
+ "publicKey": "bbfc8c1a639872d4e5fc44ac4172443886153b4579b4f3e0466220c02d09407a",
32
+ "signatureAlgorithm": "ed25519-sha256-raw",
33
+ "signatureRequired": true
34
+ }
35
+ }
36
+ ],
11
37
  "engines": [
12
38
  {
13
39
  "name": "embed-house",
@@ -80,7 +106,7 @@
80
106
  },
81
107
  {
82
108
  "name": "mcp-gateway",
83
- "version": "0.16.4",
109
+ "version": "0.17.2",
84
110
  "kind": "daemon",
85
111
  "summary": "Agregador MCP: conecta UMA vez em cada servidor MCP e reexpõe todas as tools num endpoint Streamable HTTP único, com auth bearer, namespacing condicional, resiliência e auditoria. Mata a multiplicação de processos por sessão.",
86
112
  "pagesPendente": "sem página de produto com vídeo; ela nasce do repositório do próprio motor e este registro só aponta para ela",
@@ -137,9 +163,9 @@
137
163
  },
138
164
  {
139
165
  "name": "vxk",
140
- "version": "0.18.0",
166
+ "version": "0.21.0",
141
167
  "kind": "cli",
142
- "summary": "VXK — explicações visuais animadas e narradas como servidor MCP (stdio): qualquer cliente MCP (Claude Code, Codex, Copilot) compõe a biblioteca de componentes, ícones e o controle único de reprodução e recebe UM HTML autocontido e offline. A narração é assada pelo motor vox-engine.",
168
+ "summary": "VXK — explicações visuais animadas e narradas como servidor MCP (stdio): qualquer cliente MCP (Claude Code, Codex, Copilot) compõe a biblioteca de componentes, ícones e o controle único de reprodução e recebe UM HTML autocontido e offline, ou o vídeo MP4 da mesma explicação (por preset, do aplicativo real ou em Motion) e o PDF. A narração é assada pelo motor vox-engine.",
143
169
  "pagesUrl": "https://allansantos-dv.github.io/visual-explainer/",
144
170
  "pagesPendente": "a página no ar não oferece o caminho Ver o que o produto faz antes do vídeo",
145
171
  "home": "~/.vxk",
@@ -158,11 +184,20 @@
158
184
  "platforms": [
159
185
  "any"
160
186
  ],
161
- "notes": "kind=cli: o host MCP sobe `node ~/.vxk/bin/vxk-mcp.mjs` por stdio (direto ou como backend do mcp-gateway); o build é job assíncrono porque o gateway corta chamadas em 30 s. O motor publica ~/.vxk/cli.json (mcp, builder, version) a cada arranque. Traz também o builder CLI (bin/build-artifact.mjs). Narração requer o vox-engine (Windows); sem ele o build falha alto em vez de gerar mudo."
187
+ "notes": "kind=cli: o host MCP sobe `node ~/.vxk/bin/vxk-mcp.mjs` por stdio (direto ou como backend do mcp-gateway); o build é job assíncrono porque o gateway corta chamadas em 30 s. Desde a 0.19.0 esse arquivo é um shim fino: quem trabalha é UM daemon por máquina e por usuário (bin/vxk-daemon.mjs, 127.0.0.1, porta derivada do usuário), que o shim sobe se não houver e compartilha entre os agentes; ele anuncia porta, pid e shutdownToken em ~/.vxk/run/daemon.json. O bloco `runtime` declara só o que o kit usa para trocar a instalação: o kit NÃO sobe nem espera esse daemon (o shim cuida disso), mas ao atualizar pede POST /shutdown com o x-shutdown-token antes do swap (ocupado, o daemon responde 202 e fecha quando o trabalho em curso termina: o kit espera o processo sair), porque no Windows o daemon vivo segura a pasta bin e a troca falharia com EPERM. O motor publica ~/.vxk/cli.json (mcp, builder, version) a cada arranque. Traz também o builder CLI (bin/build-artifact.mjs). Entrega HTML, MP4 (vxk_build target=mp4, vxk_video por preset, vxk_app_video do aplicativo real, vxk_motion) e PDF. Narração requer o vox-engine (Windows); sem ele o build falha alto em vez de gerar mudo.",
188
+ "runtime": {
189
+ "transport": "http",
190
+ "runtimeFile": "run/daemon.json",
191
+ "healthPath": "/health",
192
+ "shutdownPath": "/shutdown",
193
+ "heartbeatMs": 30000,
194
+ "shutdownTokenField": "shutdownToken",
195
+ "shutdownTokenHeader": "x-shutdown-token"
196
+ }
162
197
  },
163
198
  {
164
199
  "name": "cerne",
165
- "version": "0.4.10",
200
+ "version": "0.4.14",
166
201
  "kind": "daemon",
167
202
  "status": "self-managed",
168
203
  "summary": "Leitura ativa de vídeo por link ou arquivo: captura, seleção de quadros, transcrição, tradução e consolidação em memória, exposta via servidor MCP por um único daemon local (CerneEngine) compartilhado entre os consumidores.",
@@ -188,7 +223,7 @@
188
223
  },
189
224
  {
190
225
  "name": "skill-engine",
191
- "version": "0.1.0",
226
+ "version": "0.2.1",
192
227
  "kind": "cli",
193
228
  "summary": "Skill Manager v2: motor portátil que gerencia skills de agentes fora de qualquer IDE — busca repositórios de skills em quarentena, varre com regras determinísticas e só ativa o que sai limpo, nas pastas que Claude Code, Codex, Copilot e OpenCode leem. CLI `skill-engine`, não é daemon.",
194
229
  "pagesPendente": "sem página de produto com vídeo; ela nasce do repositório do próprio motor e este registro só aponta para ela (a sessão do skill-manager a constrói)",
@@ -209,6 +244,38 @@
209
244
  "any"
210
245
  ],
211
246
  "notes": "kind=cli: o lifecycle entrega o binário e NÃO sobe processo. `extractTo` é aninhado como no neural-link: o home guarda ESTADO que sobrevive ao update, e só `runtimes/skill-engine` é trocado. Código-fonte no repo privado AllanSantos-DV/skill-engine; a release pública mora aqui."
247
+ },
248
+ {
249
+ "name": "client-harness",
250
+ "version": "0.1.5",
251
+ "kind": "cli",
252
+ "summary": "Daemon local que faz o Claude Code e o Codex respeitarem os padrões de cada cliente: confere cada ação do agente (hooks) contra as regras do cliente da pasta e recusa antes de gravar, em cerca de 1 ms, com o motivo e o que usar no lugar.",
253
+ "pagesUrl": "https://allansantos-dv.github.io/harness-padroes-cliente/",
254
+ "home": "~/.client-harness",
255
+ "install": {
256
+ "type": "github-release",
257
+ "repo": "AllanSantos-DV/engine-registry",
258
+ "tag": "client-harness-v{version}",
259
+ "asset": "client-harness-any.tgz",
260
+ "checksum": "{asset}.sha256",
261
+ "extractTo": "runtimes/client-harness",
262
+ "entry": "runtimes/client-harness/src/cli.mjs",
263
+ "publicKey": "b3624d289f9e026388ee0b2c1aba0b1e2328b546b607c7f283548e85972052c4",
264
+ "signatureAlgorithm": "ed25519-sha256-raw",
265
+ "signatureRequired": true
266
+ },
267
+ "platforms": [
268
+ "any"
269
+ ],
270
+ "notes": "kind=cli: o lifecycle entrega o binário e NÃO sobe processo (`client-harness start` sobe o daemon, que os hooks dos agentes consultam; o hook de SessionStart gravado por `client-harness install` o sobe a cada sessão). `extractTo` é aninhado como no neural-link e no skill-engine: o home guarda ESTADO (token, eventos, run/) que sobrevive ao update, e só `runtimes/client-harness` é trocado. Daemon único por máquina e usuário: bind numa porta derivada do usuário (47000-47999, CLIENT_HARNESS_PORT troca), run/daemon.json com porta, pid e shutdownToken. O bloco `runtime` declara só o que o kit usa para trocar a instalação: POST /shutdown?relaunch=1 com x-shutdown-token antes do swap (no Windows o daemon vivo segura a pasta); com relaunch=1 o daemon deixa um runner que sobe o daemon novo depois da troca, então um update não deixa a máquina sem checagem. O motor publica ~/.client-harness/cli.json em toda invocação do CLI sem --data. Artefato cross-platform (Node puro, sem dependências). Código-fonte no repo AllanSantos-DV/harness-padroes-cliente; a release pública mora aqui.",
271
+ "runtime": {
272
+ "transport": "http",
273
+ "runtimeFile": "run/daemon.json",
274
+ "healthPath": "/health",
275
+ "shutdownPath": "/shutdown?relaunch=1",
276
+ "shutdownTokenField": "shutdownToken",
277
+ "shutdownTokenHeader": "x-shutdown-token"
278
+ }
212
279
  }
213
280
  ]
214
281
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "engine-kit",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Cliente das dependências compartilhadas (motores) do engine-registry: resolve, provisiona com SHA256 e assinatura Ed25519 fail-closed, e mantém o motor vivo — uma instalação por máquina, N consumidores.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,7 +24,7 @@
24
24
  "node": ">=18"
25
25
  },
26
26
  "scripts": {
27
- "test": "node kit/budget.mjs && node tests/test-silencio.mjs && node tests/smoke.mjs && node tests/test-edge.mjs && node tests/test-signature.mjs && node tests/test-ensure.mjs && node tests/test-update.mjs && node tests/test-trust.mjs && node tests/test-tar.mjs && node tests/test-apps.mjs && node tests/test-resolve-cache.mjs && node tests/test-doctor-exit.mjs && node tests/test-mcp-gateway-token.mjs && node tests/test-gateway-provision.mjs && node tests/test-neural-link-alignment.mjs && node tests/test-site.mjs && node tests/test-pages-branch.mjs && node tests/test-readme-versions.mjs && node tests/test-pages-gate.mjs",
27
+ "test": "node kit/budget.mjs && node tests/test-silencio.mjs && node tests/smoke.mjs && node tests/test-edge.mjs && node tests/test-signature.mjs && node tests/test-ensure.mjs && node tests/test-ensure-daemon.mjs && node tests/test-update.mjs && node tests/test-trust.mjs && node tests/test-tar.mjs && node tests/test-apps.mjs && node tests/test-resolve-cache.mjs && node tests/test-doctor-exit.mjs && node tests/test-mcp-gateway-token.mjs && node tests/test-gateway-provision.mjs && node tests/test-neural-link-alignment.mjs && node tests/test-site.mjs && node tests/test-pages-branch.mjs && node tests/test-readme-versions.mjs && node tests/test-pages-gate.mjs && node tests/test-regras-escopo.mjs",
28
28
  "test:pages-live": "node tests/test-pages-gate.mjs --live",
29
29
  "test:network": "node tests/smoke.mjs --network && node tests/test-signature.mjs --network",
30
30
  "test:e2e": "node tests/e2e-install.mjs",
@@ -41,7 +41,7 @@
41
41
  "shared-infra"
42
42
  ],
43
43
  "author": {
44
- "name": "Allan Santos",
44
+ "name": "The engine-registry maintainers",
45
45
  "url": "https://github.com/AllanSantos-DV"
46
46
  },
47
47
  "repository": {