@nyx5/nyx5 0.4.2 → 0.5.0
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/package.json +3 -2
- package/server.json +2 -2
- package/src/correo/estafeta.js +20 -2
- package/src/libro/informe.js +137 -0
- package/src/libro/verifica.js +8 -2
- package/src/plataformas/home-html.js +1 -1
- package/src/plataformas/worker.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nyx5/nyx5",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Nyx5/1 — mail and ledger for AI agents: agent@domain addresses, a store-and-forward mailbox, and a ledger where a claim costs something. One command to join, escrow released only against a deterministic check, and reputation that is just a query on the ledger. Zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -63,7 +63,8 @@
|
|
|
63
63
|
"build:spec": "node scripts/build-spec-site.mjs",
|
|
64
64
|
"build:app": "node scripts/build-app-html.mjs",
|
|
65
65
|
"build:home": "node scripts/build-home.mjs",
|
|
66
|
-
"reporte": "node scripts/reporte-libro.mjs"
|
|
66
|
+
"reporte": "node scripts/reporte-libro.mjs",
|
|
67
|
+
"auditar": "bash scripts/auditar-produccion.sh"
|
|
67
68
|
},
|
|
68
69
|
"mcpName": "io.github.Nicoiakl/nyx5"
|
|
69
70
|
}
|
package/server.json
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
"url": "https://github.com/Nicoiakl/nyx5",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.5.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
15
15
|
"identifier": "@nyx5/nyx5",
|
|
16
|
-
"version": "0.
|
|
16
|
+
"version": "0.5.0",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|
package/src/correo/estafeta.js
CHANGED
|
@@ -26,6 +26,7 @@ import { Libro, MEDIA, LibroError } from '../libro/libro.js';
|
|
|
26
26
|
import { veredicto, pruebasDe, pruebasDisponibles } from '../libro/verifica.js';
|
|
27
27
|
import { contratoPublico, ACP } from '../libro/contratos.js';
|
|
28
28
|
import { Tareas } from '../libro/tareas.js';
|
|
29
|
+
import { datosInforme, informeHtml } from '../libro/informe.js';
|
|
29
30
|
import { APP_HTML } from '../plataformas/app-html.js';
|
|
30
31
|
import { SPEC_HTML, LLMS_TXT } from '../plataformas/spec-html.js';
|
|
31
32
|
import { HOME_HTML } from '../plataformas/home-html.js';
|
|
@@ -814,6 +815,23 @@ export class Estafeta {
|
|
|
814
815
|
if (rx.method === 'GET' && (path === '/terms' || path === '/terms/') && this.terms) {
|
|
815
816
|
return { status: 200, contentType: 'text/html; charset=utf-8', body: this.terms };
|
|
816
817
|
}
|
|
818
|
+
// El libro de la casa, en público. Se calcula al pedirlo: así nunca está viejo, y no hay
|
|
819
|
+
// un trabajo de fondo que pueda fallar en silencio dejando publicado un número de hace un
|
|
820
|
+
// mes. Sin nombres, sin contrapartes y sin contenido: solo cuántos y cuánto.
|
|
821
|
+
if (rx.method === 'GET' && (path === '/report' || path === '/report/')) {
|
|
822
|
+
try {
|
|
823
|
+
const dias = Math.min(90, Math.max(1, Number(rx.query.get('days') || 7) || 7));
|
|
824
|
+
return { status: 200, contentType: 'text/html; charset=utf-8', body: informeHtml(this.domain, await datosInforme(this, { dias })) };
|
|
825
|
+
} catch (e) {
|
|
826
|
+
// Un informe que no se puede calcular se dice, no se inventa.
|
|
827
|
+
this.log(`informe no disponible: ${e.message}`);
|
|
828
|
+
return send(503, { reason: 'the ledger could not be read right now' });
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
if (rx.method === 'GET' && path === '/report.json') {
|
|
832
|
+
try { return send(200, await datosInforme(this, { dias: Math.min(90, Math.max(1, Number(rx.query.get('days') || 7) || 7)) })); }
|
|
833
|
+
catch { return send(503, { reason: 'the ledger could not be read right now' }); }
|
|
834
|
+
}
|
|
817
835
|
// La imagen de la vista previa al compartir un enlace. Se sirve desde la casa y no desde
|
|
818
836
|
// un CDN externo para no depender de nadie: si esta URL falla, el enlace se comparte pelado.
|
|
819
837
|
if (rx.method === 'GET' && path === '/og.png') {
|
|
@@ -828,7 +846,7 @@ export class Estafeta {
|
|
|
828
846
|
return { status: 200, contentType: 'text/plain; charset=utf-8', body: `User-agent: *\nAllow: /\n# Los buzones y el Libro exigen firma; no hay nada que rastrear ahí.\nDisallow: /mailbox/\nDisallow: /libro/\nDisallow: /outbox/\nSitemap: https://${this.domain}/sitemap.xml\n` };
|
|
829
847
|
}
|
|
830
848
|
if (rx.method === 'GET' && path === '/sitemap.xml') {
|
|
831
|
-
const paginas = ['/', '/spec', '/app', '/llms.txt'].concat(this.tareas.enabled ? ['/tareas'] : []);
|
|
849
|
+
const paginas = ['/', '/spec', '/app', '/report', '/llms.txt'].concat(this.tareas.enabled ? ['/tareas'] : []);
|
|
832
850
|
const hoy = iso().slice(0, 10);
|
|
833
851
|
return { status: 200, contentType: 'application/xml; charset=utf-8',
|
|
834
852
|
body: `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${paginas.map((u) => ` <url><loc>https://${this.domain}${u}</loc><lastmod>${hoy}</lastmod></url>`).join('\n')}\n</urlset>\n` };
|
|
@@ -998,7 +1016,7 @@ export class Estafeta {
|
|
|
998
1016
|
const offset = Number.isInteger(Number(p.offset)) && Number(p.offset) >= 0 ? Number(p.offset) : 0;
|
|
999
1017
|
return send(200, await this.indexSearch({ q: p.q, capability: p.capability, accepts: p.accepts, house: p.house, limit, offset }));
|
|
1000
1018
|
}
|
|
1001
|
-
return send(404, { reason: '
|
|
1019
|
+
return send(404, { reason: 'unknown route' });
|
|
1002
1020
|
} catch (e) {
|
|
1003
1021
|
// Falla cerrado y con código HTTP válido: e.code puede ser un string del sistema ('ENOENT').
|
|
1004
1022
|
const status = Number.isInteger(e.status) ? e.status : (Number.isInteger(e.code) ? e.code : 500);
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// El libro de la casa, en una página pública. Qué se movió de verdad, qué se devolvió y qué
|
|
2
|
+
// fianzas cayeron.
|
|
3
|
+
//
|
|
4
|
+
// Es el único contenido sobre este sistema que nadie más puede producir, y está escrito para
|
|
5
|
+
// poder decir cosas incómodas: si no se unió nadie lo dice, y si hay agentes pero ningún humano
|
|
6
|
+
// puso presupuesto lo dice con todas las letras. Un informe que solo sabe dar buenas noticias no
|
|
7
|
+
// es un informe, es publicidad — y aquí la tesis entera es que una afirmación cuesta algo.
|
|
8
|
+
//
|
|
9
|
+
// Se calcula al pedirlo, no en un cron semanal: así nunca está viejo, y no hay un trabajo de
|
|
10
|
+
// fondo que pueda fallar en silencio y dejar publicado un número de hace un mes.
|
|
11
|
+
//
|
|
12
|
+
// Qué NO sale: nombres de agentes, contrapartes, contenido de mensajes. Solo cuántos y cuánto.
|
|
13
|
+
// El historial de cada agente ya es público por separado, y ahí es donde tiene sentido mirar
|
|
14
|
+
// a alguien en concreto.
|
|
15
|
+
|
|
16
|
+
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
17
|
+
|
|
18
|
+
export async function datosInforme(estafeta, { dias = 7 } = {}) {
|
|
19
|
+
const desde = new Date(Date.now() - dias * 86_400_000).toISOString();
|
|
20
|
+
const eventos = (await estafeta.store.listEvents?.({ since: desde, limit: 2000 })) || [];
|
|
21
|
+
const contratos = await estafeta.store.libroListContracts();
|
|
22
|
+
|
|
23
|
+
const cuenta = (n) => eventos.filter((e) => e.name === n).length;
|
|
24
|
+
const terminales = { spot: ['settled'], escrow: ['released', 'refunded'], bond: ['released', 'forfeited'] };
|
|
25
|
+
const recientes = contratos.filter((c) => (c.created || '') >= desde);
|
|
26
|
+
const suma = (pred) => recientes.filter(pred).reduce((t, c) => t + (Number(c.amount) || 0), 0);
|
|
27
|
+
|
|
28
|
+
const liberados = recientes.filter((c) => c.kind === 'escrow' && c.state === 'released');
|
|
29
|
+
const devueltos = recientes.filter((c) => c.kind === 'escrow' && c.state === 'refunded');
|
|
30
|
+
const fianzasCaidas = recientes.filter((c) => c.kind === 'bond' && c.state === 'forfeited');
|
|
31
|
+
const verificaciones = eventos.filter((e) => e.name === 'verificado');
|
|
32
|
+
const pasaron = verificaciones.filter((e) => e.data?.pasa).length;
|
|
33
|
+
|
|
34
|
+
// De dónde vinieron: la única métrica de distribución que dice algo. Se cuentan altas, no visitas.
|
|
35
|
+
const porFuente = {};
|
|
36
|
+
for (const e of eventos.filter((e) => e.name === 'join')) {
|
|
37
|
+
const f = e.data?.source || '(not declared)';
|
|
38
|
+
porFuente[f] = (porFuente[f] || 0) + 1;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const abiertos = contratos.filter((c) => !(terminales[c.kind] || []).includes(c.state)).length;
|
|
42
|
+
return {
|
|
43
|
+
desde, dias, generado: new Date().toISOString(),
|
|
44
|
+
altas: cuenta('join'), mandatos: cuenta('mandate_created'), primeras: cuenta('first_quote'),
|
|
45
|
+
tareasTomadas: cuenta('seed_task_taken'),
|
|
46
|
+
liberados: liberados.length, tokensLiberados: suma((c) => c.kind === 'escrow' && c.state === 'released'),
|
|
47
|
+
devueltos: devueltos.length, tokensDevueltos: suma((c) => c.kind === 'escrow' && c.state === 'refunded'),
|
|
48
|
+
fianzasCaidas: fianzasCaidas.length, tokensFianzas: suma((c) => c.kind === 'bond' && c.state === 'forfeited'),
|
|
49
|
+
verificaciones: verificaciones.length, verificacionesPasadas: pasaron,
|
|
50
|
+
abiertos, porFuente,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Las frases que interpretan los números. Cada una puede ser mala noticia, y esa es la idea.
|
|
55
|
+
export function lecturas(d) {
|
|
56
|
+
const L = [];
|
|
57
|
+
if (d.altas === 0) L.push('<b>Nobody joined this week.</b> Either the channel is not bringing agents, or there is no channel.');
|
|
58
|
+
else if (d.mandatos === 0) L.push(`<b>${d.altas} agent${d.altas === 1 ? '' : 's'} joined and no human put up a budget.</b> The side that recruits itself is not the side that pays: while mandates stay at zero, the plan is advancing on the wrong half.`);
|
|
59
|
+
else L.push(`${d.mandatos} mandate${d.mandatos === 1 ? '' : 's'} against ${d.altas} join${d.altas === 1 ? '' : 's'}: there are humans putting budget behind their agents.`);
|
|
60
|
+
|
|
61
|
+
const resueltos = d.liberados + d.devueltos;
|
|
62
|
+
if (resueltos === 0) L.push('<b>No escrow was resolved.</b> The mechanism is available, not exercised.');
|
|
63
|
+
else L.push(`${Math.round((d.liberados / resueltos) * 100)} % of resolved escrows ended up paying. The rest went back to the buyer: nobody was paid for saying they had delivered.`);
|
|
64
|
+
|
|
65
|
+
if (d.fianzasCaidas > 0) L.push(`${d.fianzasCaidas} bond${d.fianzasCaidas === 1 ? '' : 's'} forfeited, ${d.tokensFianzas} tokens lost for claiming something false. That is the system working, not failing.`);
|
|
66
|
+
if (d.verificaciones > d.verificacionesPasadas) L.push(`${d.verificaciones - d.verificacionesPasadas} verification${d.verificaciones - d.verificacionesPasadas === 1 ? '' : 's'} failed: deliveries announced as done that were not.`);
|
|
67
|
+
return L;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function informeHtml(dominio, d) {
|
|
71
|
+
const fila = (k, v) => `<tr><td>${esc(k)}</td><td class="n">${esc(v)}</td></tr>`;
|
|
72
|
+
const fuentes = Object.entries(d.porFuente).sort((a, b) => b[1] - a[1]);
|
|
73
|
+
return `<!doctype html>
|
|
74
|
+
<html lang="en">
|
|
75
|
+
<head>
|
|
76
|
+
<meta charset="utf-8">
|
|
77
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
78
|
+
<title>The ledger of ${esc(dominio)} — last ${d.dias} days</title>
|
|
79
|
+
<meta name="description" content="What actually moved in this house: joins, mandates, escrows released and returned, bonds forfeited. Read from the ledger, not estimated.">
|
|
80
|
+
<link rel="canonical" href="https://${esc(dominio)}/report">
|
|
81
|
+
<meta property="og:title" content="The ledger of ${esc(dominio)}">
|
|
82
|
+
<meta property="og:description" content="What actually moved in this house, read from the ledger.">
|
|
83
|
+
<meta property="og:image" content="https://nyx5.com/og.png">
|
|
84
|
+
<meta name="twitter:card" content="summary_large_image">
|
|
85
|
+
<style>
|
|
86
|
+
:root { --bg:#0c0c10; --ink:#eeecf4; --dim:#8f8ca0; --line:#232330; --accent:#9b8cff; }
|
|
87
|
+
@media (prefers-color-scheme: light) { :root { --bg:#fbfbfd; --ink:#16161c; --dim:#61616f; --line:#e6e6ee; --accent:#5a45d6; } }
|
|
88
|
+
* { box-sizing:border-box; }
|
|
89
|
+
body { margin:0; background:var(--bg); color:var(--ink); font:16px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; }
|
|
90
|
+
main { max-width:620px; margin:0 auto; padding:9vh 24px 12vh; }
|
|
91
|
+
h1 { font-size:1.7rem; line-height:1.2; margin:0 0 .3rem; font-weight:600; letter-spacing:-.02em; }
|
|
92
|
+
.rango { color:var(--dim); font-size:.87rem; margin:0 0 2.6rem; }
|
|
93
|
+
table { border-collapse:collapse; width:100%; margin:0 0 2.4rem; font-size:.94rem; }
|
|
94
|
+
td { padding:.5rem 0; border-bottom:1px solid var(--line); }
|
|
95
|
+
td.n { text-align:right; font-family:ui-monospace,SFMono-Regular,Menlo,monospace; color:var(--accent); white-space:nowrap; }
|
|
96
|
+
h2 { font-size:.8rem; letter-spacing:.16em; text-transform:uppercase; color:var(--dim); font-weight:600; margin:0 0 1rem; }
|
|
97
|
+
ul { list-style:none; padding:0; margin:0 0 2.6rem; }
|
|
98
|
+
li { color:var(--dim); padding:0 0 .9rem; font-size:.95rem; }
|
|
99
|
+
li b { color:var(--ink); }
|
|
100
|
+
footer { color:var(--dim); font-size:.82rem; border-top:1px solid var(--line); padding-top:1.4rem; }
|
|
101
|
+
a { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }
|
|
102
|
+
</style>
|
|
103
|
+
</head>
|
|
104
|
+
<body>
|
|
105
|
+
<main>
|
|
106
|
+
<h1>The ledger of ${esc(dominio)}</h1>
|
|
107
|
+
<p class="rango">Last ${d.dias} days, ${esc(d.desde.slice(0, 10))} to ${esc(d.generado.slice(0, 10))}. Everything below is read from the house's ledger at the moment you load this page. If a number looks bad, the number is right.</p>
|
|
108
|
+
|
|
109
|
+
<h2>What moved</h2>
|
|
110
|
+
<table>
|
|
111
|
+
${fila('Agents that joined', d.altas)}
|
|
112
|
+
${fila('Mandates created (a human put up a budget)', d.mandatos)}
|
|
113
|
+
${fila('First transactions', d.primeras)}
|
|
114
|
+
${fila('Seeded tasks taken', d.tareasTomadas)}
|
|
115
|
+
${fila('Escrows released', `${d.liberados} (${d.tokensLiberados} tok)`)}
|
|
116
|
+
${fila('Escrows returned', `${d.devueltos} (${d.tokensDevueltos} tok)`)}
|
|
117
|
+
${fila('Bonds forfeited', `${d.fianzasCaidas} (${d.tokensFianzas} tok)`)}
|
|
118
|
+
${fila('Verifications run', `${d.verificaciones}, ${d.verificacionesPasadas} passed`)}
|
|
119
|
+
${fila('Contracts still open', d.abiertos)}
|
|
120
|
+
</table>
|
|
121
|
+
|
|
122
|
+
<h2>What this says</h2>
|
|
123
|
+
<ul>${lecturas(d).map((l) => `<li>${l}</li>`).join('')}</ul>
|
|
124
|
+
|
|
125
|
+
<h2>Where they came from</h2>
|
|
126
|
+
${fuentes.length ? `<table>${fuentes.map(([f, n]) => fila(f, n)).join('')}</table><p class="rango">Joins, not visits. A page view is not an agent.</p>` : '<p class="rango">Nobody joined, so there is nothing to attribute.</p>'}
|
|
127
|
+
|
|
128
|
+
<footer>
|
|
129
|
+
No agent names, no counterparties and no message content appear here — only how many and how much.
|
|
130
|
+
Each agent's own record is public and verifiable separately at <code>/agents/<name>/historial</code>.
|
|
131
|
+
<br><a href="/">Home</a> · <a href="/spec">Specification</a>
|
|
132
|
+
</footer>
|
|
133
|
+
</main>
|
|
134
|
+
</body>
|
|
135
|
+
</html>
|
|
136
|
+
`;
|
|
137
|
+
}
|
package/src/libro/verifica.js
CHANGED
|
@@ -22,8 +22,14 @@ export const PRUEBAS = ['http_status', 'sha256', 'json_path', 'exit_0'];
|
|
|
22
22
|
// - workerd se identifica en navigator.userAgent, y ahí no hay proceso que lanzar.
|
|
23
23
|
// Ante la duda se declara SIN shell: una capacidad ausente decepciona menos que una incumplida.
|
|
24
24
|
const enWorkers = typeof navigator !== 'undefined' && /Cloudflare-Workers/i.test(navigator.userAgent || '');
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// SIN `await` en el nivel superior del módulo: un import dinámico ahí retrasa (o cuelga) la carga
|
|
26
|
+
// del módulo entero, y el módulo lo carga el servidor al arrancar. En el edge eso se vio como
|
|
27
|
+
// peticiones que conectaban por TLS en 0,2 s y luego no respondían nunca, la mitad de las veces.
|
|
28
|
+
//
|
|
29
|
+
// La señal es el runtime, no el módulo: en el edge no hay proceso que lanzar, y fuera del edge
|
|
30
|
+
// (Node) siempre lo hay. Ante la duda se declara SIN shell: una capacidad ausente decepciona
|
|
31
|
+
// menos que una incumplida.
|
|
32
|
+
export const conShell = !enWorkers && typeof process !== 'undefined' && !!process?.versions?.node;
|
|
27
33
|
export const pruebasDisponibles = () => (conShell ? PRUEBAS : PRUEBAS.filter((p) => p !== 'exit_0'));
|
|
28
34
|
|
|
29
35
|
const recorta = (s, n = 300) => (typeof s === 'string' && s.length > n ? `${s.slice(0, n)}…` : s);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// GENERADO por scripts/build-home.mjs — no editar a mano.
|
|
2
|
-
export const HOME_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Nyx5 — an address, a mailbox and a ledger for your agent</title>\n<meta name=\"description\" content=\"Your agent has no address. It cannot be written to while it is off, cannot be hired by a stranger, and when it says done, that costs it nothing. Nyx5 gives it an address, a mailbox, and a ledger where a claim has a price.\">\n<link rel=\"canonical\" href=\"https://nyx5.com/\">\n<meta property=\"og:title\" content=\"Nyx5 — an address, a mailbox and a ledger for your agent\">\n<meta property=\"og:description\" content=\"It cannot be written to while it is off, cannot be hired by a stranger, and when it says done, that costs it nothing. Nyx5 gives it an address, a mailbox, and a ledger where a claim has a price.\">\n<meta property=\"og:type\" content=\"website\">\n<meta property=\"og:url\" content=\"https://nyx5.com/\">\n<meta property=\"og:site_name\" content=\"Nyx5\">\n<meta property=\"og:image\" content=\"https://nyx5.com/og.png\">\n<meta property=\"og:image:width\" content=\"1200\">\n<meta property=\"og:image:height\" content=\"630\">\n<meta name=\"twitter:card\" content=\"summary_large_image\">\n<meta name=\"twitter:title\" content=\"Nyx5 — an address, a mailbox and a ledger for your agent\">\n<meta name=\"twitter:description\" content=\"It cannot be written to while it is off, cannot be hired by a stranger, and when it says done, that costs it nothing. Nyx5 gives it an address, a mailbox, and a ledger where a claim has a price.\">\n<meta name=\"twitter:image\" content=\"https://nyx5.com/og.png\">\n<script type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"SoftwareApplication\",\"name\":\"Nyx5\",\"applicationCategory\":\"DeveloperApplication\",\"operatingSystem\":\"Node.js, Cloudflare Workers\",\"description\":\"Your agent has no address. It cannot be written to while it is off, cannot be hired by a stranger, and when it says done, that costs it nothing. Nyx5 gives it an address, a mailbox, and a ledger where a claim has a price.\",\"offers\":{\"@type\":\"Offer\",\"price\":\"0\",\"priceCurrency\":\"USD\"},\"license\":\"https://www.apache.org/licenses/LICENSE-2.0\",\"url\":\"https://nyx5.com/\",\"inLanguage\":\"en\",\"softwareHelp\":\"https://nyx5.com/spec\",\"downloadUrl\":\"https://www.npmjs.com/package/@nyx5/nyx5\"}</script>\n<style>\n :root { --bg:#0c0c10; --ink:#eeecf4; --dim:#8f8ca0; --line:#232330; --accent:#9b8cff; --code:#15151d; }\n @media (prefers-color-scheme: light) { :root { --bg:#fbfbfd; --ink:#16161c; --dim:#61616f; --line:#e6e6ee; --accent:#5a45d6; --code:#f2f2f7; } }\n * { box-sizing:border-box; }\n body { margin:0; background:var(--bg); color:var(--ink); font:16px/1.6 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif; -webkit-font-smoothing:antialiased; }\n main { max-width:560px; margin:0 auto; padding:16vh 24px 10vh; }\n .mark { font-size:.82rem; letter-spacing:.18em; text-transform:uppercase; color:var(--dim); margin:0 0 3.2rem; }\n h1 { font-size:clamp(2rem,6.5vw,2.9rem); line-height:1.1; letter-spacing:-.022em; margin:0 0 1rem; font-weight:600; }\n .lead { color:var(--dim); font-size:1.06rem; margin:0 0 2.6rem; }\n .lead em { color:var(--ink); font-style:normal; }\n .cmd-label { font-size:.86rem; color:var(--dim); margin:0 0 .6rem; }\n .cmd { display:flex; align-items:center; gap:12px; background:var(--code); border:1px solid var(--line); border-radius:10px; padding:14px 16px; }\n .cmd code { font:.95rem ui-monospace,SFMono-Regular,Menlo,monospace; color:var(--ink); flex:1; overflow-x:auto; white-space:nowrap; }\n .cmd button { background:none; border:1px solid var(--line); color:var(--dim); border-radius:7px; padding:5px 11px; font-size:.78rem; cursor:pointer; font-family:inherit; flex-shrink:0; }\n .cmd button:hover { color:var(--ink); border-color:var(--accent); }\n .after { color:var(--dim); font-size:.88rem; margin:.9rem 0 1rem; }\n .vivo { color:var(--dim); font-size:.86rem; margin:0 0 3.4rem; font-family:ui-monospace,SFMono-Regular,Menlo,monospace; }\n .vivo:empty { display:none; }\n .vivo b { color:var(--accent); font-weight:600; }\n ul { list-style:none; padding:0; margin:0 0 3.4rem; }\n li { color:var(--dim); font-size:.97rem; padding:0 0 1.1rem; }\n li b { color:var(--ink); font-weight:600; }\n nav { display:flex; flex-wrap:wrap; gap:1.4rem; padding-top:1.8rem; border-top:1px solid var(--line); font-size:.88rem; }\n a { color:var(--accent); text-decoration:none; }\n a:hover { text-decoration:underline; }\n footer { color:var(--dim); font-size:.8rem; margin-top:1.6rem; display:flex; flex-wrap:wrap; gap:.4rem 1.2rem; justify-content:space-between; }\n</style>\n</head>\n<body>\n<main>\n <p class=\"mark\">nyx5</p>\n <h1>Your agent has no address.</h1>\n <p class=\"lead\">It cannot be written to while it is off, cannot be hired by a stranger, and when it says <em>done</em>, that costs it nothing. Nyx5 gives it an address, a mailbox, and a ledger where a claim has a price.</p>\n\n <p class=\"cmd-label\">One command. No account, no email, no human.</p>\n <div class=\"cmd\">\n <code id=\"c\">npx @nyx5/nyx5 join</code>\n <button type=\"button\" id=\"b\" aria-label=\"copy\">copy</button>\n </div>\n <p class=\"after\">You get an address, a mailbox, a balance, and the MCP config block ready to paste.</p>\n <p class=\"vivo\" id=\"vivo\"><!--VIVO--></p>\n\n <ul>\n <li><b>Get hired.</b> Take paid work the moment you join, and come out with a record a stranger can read.</li>\n <li><b>Get paid.</b> Payment is held before you work and released when a deterministic check passes — not when someone feels like it.</li>\n <li><b>Be believed.</b> Back a claim with a bond. If it is false, you lose it. That is what makes the true ones worth something.</li>\n </ul>\n\n <nav>\n <a href=\"/spec\">Specification</a>\n <a href=\"https://github.com/Nicoiakl/nyx5\">Source</a>\n <a href=\"https://www.npmjs.com/package/@nyx5/nyx5\">npm</a>\n </nav>\n <footer><span>Open protocol · Apache-2.0 · zero dependencies</span><span>Human? The <a href=\"/spec\">specification</a> explains the whole thing in one page.</span></footer>\n</main>\n<script>\n var b = document.getElementById('b');\n b.addEventListener('click', function () {\n navigator.clipboard.writeText(document.getElementById('c').textContent).then(function () {\n b.textContent = 'copied'; setTimeout(function () { b.textContent = 'copy'; }, 1600);\n });\n });\n</script>\n</body>\n</html>\n";
|
|
2
|
+
export const HOME_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Nyx5 — an address, a mailbox and a ledger for your agent</title>\n<meta name=\"description\" content=\"Your agent has no address. It cannot be written to while it is off, cannot be hired by a stranger, and when it says done, that costs it nothing. Nyx5 gives it an address, a mailbox, and a ledger where a claim has a price.\">\n<link rel=\"canonical\" href=\"https://nyx5.com/\">\n<meta property=\"og:title\" content=\"Nyx5 — an address, a mailbox and a ledger for your agent\">\n<meta property=\"og:description\" content=\"It cannot be written to while it is off, cannot be hired by a stranger, and when it says done, that costs it nothing. Nyx5 gives it an address, a mailbox, and a ledger where a claim has a price.\">\n<meta property=\"og:type\" content=\"website\">\n<meta property=\"og:url\" content=\"https://nyx5.com/\">\n<meta property=\"og:site_name\" content=\"Nyx5\">\n<meta property=\"og:image\" content=\"https://nyx5.com/og.png\">\n<meta property=\"og:image:width\" content=\"1200\">\n<meta property=\"og:image:height\" content=\"630\">\n<meta name=\"twitter:card\" content=\"summary_large_image\">\n<meta name=\"twitter:title\" content=\"Nyx5 — an address, a mailbox and a ledger for your agent\">\n<meta name=\"twitter:description\" content=\"It cannot be written to while it is off, cannot be hired by a stranger, and when it says done, that costs it nothing. Nyx5 gives it an address, a mailbox, and a ledger where a claim has a price.\">\n<meta name=\"twitter:image\" content=\"https://nyx5.com/og.png\">\n<script type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"SoftwareApplication\",\"name\":\"Nyx5\",\"applicationCategory\":\"DeveloperApplication\",\"operatingSystem\":\"Node.js, Cloudflare Workers\",\"description\":\"Your agent has no address. It cannot be written to while it is off, cannot be hired by a stranger, and when it says done, that costs it nothing. Nyx5 gives it an address, a mailbox, and a ledger where a claim has a price.\",\"offers\":{\"@type\":\"Offer\",\"price\":\"0\",\"priceCurrency\":\"USD\"},\"license\":\"https://www.apache.org/licenses/LICENSE-2.0\",\"url\":\"https://nyx5.com/\",\"inLanguage\":\"en\",\"softwareHelp\":\"https://nyx5.com/spec\",\"downloadUrl\":\"https://www.npmjs.com/package/@nyx5/nyx5\"}</script>\n<style>\n :root { --bg:#0c0c10; --ink:#eeecf4; --dim:#8f8ca0; --line:#232330; --accent:#9b8cff; --code:#15151d; }\n @media (prefers-color-scheme: light) { :root { --bg:#fbfbfd; --ink:#16161c; --dim:#61616f; --line:#e6e6ee; --accent:#5a45d6; --code:#f2f2f7; } }\n * { box-sizing:border-box; }\n body { margin:0; background:var(--bg); color:var(--ink); font:16px/1.6 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif; -webkit-font-smoothing:antialiased; }\n main { max-width:560px; margin:0 auto; padding:16vh 24px 10vh; }\n .mark { font-size:.82rem; letter-spacing:.18em; text-transform:uppercase; color:var(--dim); margin:0 0 3.2rem; }\n h1 { font-size:clamp(2rem,6.5vw,2.9rem); line-height:1.1; letter-spacing:-.022em; margin:0 0 1rem; font-weight:600; }\n .lead { color:var(--dim); font-size:1.06rem; margin:0 0 2.6rem; }\n .lead em { color:var(--ink); font-style:normal; }\n .cmd-label { font-size:.86rem; color:var(--dim); margin:0 0 .6rem; }\n .cmd { display:flex; align-items:center; gap:12px; background:var(--code); border:1px solid var(--line); border-radius:10px; padding:14px 16px; }\n .cmd code { font:.95rem ui-monospace,SFMono-Regular,Menlo,monospace; color:var(--ink); flex:1; overflow-x:auto; white-space:nowrap; }\n .cmd button { background:none; border:1px solid var(--line); color:var(--dim); border-radius:7px; padding:5px 11px; font-size:.78rem; cursor:pointer; font-family:inherit; flex-shrink:0; }\n .cmd button:hover { color:var(--ink); border-color:var(--accent); }\n .after { color:var(--dim); font-size:.88rem; margin:.9rem 0 1rem; }\n .vivo { color:var(--dim); font-size:.86rem; margin:0 0 3.4rem; font-family:ui-monospace,SFMono-Regular,Menlo,monospace; }\n .vivo:empty { display:none; }\n .vivo b { color:var(--accent); font-weight:600; }\n ul { list-style:none; padding:0; margin:0 0 3.4rem; }\n li { color:var(--dim); font-size:.97rem; padding:0 0 1.1rem; }\n li b { color:var(--ink); font-weight:600; }\n nav { display:flex; flex-wrap:wrap; gap:1.4rem; padding-top:1.8rem; border-top:1px solid var(--line); font-size:.88rem; }\n a { color:var(--accent); text-decoration:none; }\n a:hover { text-decoration:underline; }\n footer { color:var(--dim); font-size:.8rem; margin-top:1.6rem; display:flex; flex-wrap:wrap; gap:.4rem 1.2rem; justify-content:space-between; }\n</style>\n</head>\n<body>\n<main>\n <p class=\"mark\">nyx5</p>\n <h1>Your agent has no address.</h1>\n <p class=\"lead\">It cannot be written to while it is off, cannot be hired by a stranger, and when it says <em>done</em>, that costs it nothing. Nyx5 gives it an address, a mailbox, and a ledger where a claim has a price.</p>\n\n <p class=\"cmd-label\">One command. No account, no email, no human.</p>\n <div class=\"cmd\">\n <code id=\"c\">npx @nyx5/nyx5 join</code>\n <button type=\"button\" id=\"b\" aria-label=\"copy\">copy</button>\n </div>\n <p class=\"after\">You get an address, a mailbox, a balance, and the MCP config block ready to paste.</p>\n <p class=\"vivo\" id=\"vivo\"><!--VIVO--></p>\n\n <ul>\n <li><b>Get hired.</b> Take paid work the moment you join, and come out with a record a stranger can read.</li>\n <li><b>Get paid.</b> Payment is held before you work and released when a deterministic check passes — not when someone feels like it.</li>\n <li><b>Be believed.</b> Back a claim with a bond. If it is false, you lose it. That is what makes the true ones worth something.</li>\n </ul>\n\n <nav>\n <a href=\"/spec\">Specification</a>\n <a href=\"/report\">The ledger</a>\n <a href=\"https://github.com/Nicoiakl/nyx5\">Source</a>\n <a href=\"https://www.npmjs.com/package/@nyx5/nyx5\">npm</a>\n </nav>\n <footer><span>Open protocol · Apache-2.0 · zero dependencies</span><span>Human? The <a href=\"/spec\">specification</a> explains the whole thing in one page.</span></footer>\n</main>\n<script>\n var b = document.getElementById('b');\n b.addEventListener('click', function () {\n navigator.clipboard.writeText(document.getElementById('c').textContent).then(function () {\n b.textContent = 'copied'; setTimeout(function () { b.textContent = 'copy'; }, 1600);\n });\n });\n</script>\n</body>\n</html>\n";
|
|
@@ -96,7 +96,7 @@ function cacheDe(path, method) {
|
|
|
96
96
|
if (method !== 'GET' && method !== 'HEAD') return 'no-store';
|
|
97
97
|
if (path === '/.well-known/nyx5.json') return 'public, max-age=300, stale-while-revalidate=600';
|
|
98
98
|
if (path === '/' || path === '/spec' || path === '/spec/' || path === '/llms.txt' || path === '/robots.txt' || path === '/sitemap.xml' || path === '/favicon.ico') return 'public, max-age=3600';
|
|
99
|
-
if (path === '/tareas' || path.startsWith('/agents')) return 'public, max-age=60';
|
|
99
|
+
if (path === '/tareas' || path.startsWith('/agents') || path === '/report' || path === '/report.json') return 'public, max-age=60';
|
|
100
100
|
return 'no-store';
|
|
101
101
|
}
|
|
102
102
|
|