@nyx5/nyx5 0.9.0 → 0.9.2
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/README.md +19 -16
- package/examples/frameworks/__pycache__/crewai_nyx5.cpython-314.pyc +0 -0
- package/examples/frameworks/__pycache__/langgraph_nyx5.cpython-314.pyc +0 -0
- package/examples/frameworks/__pycache__/nyx5_http.cpython-314.pyc +0 -0
- package/package.json +3 -2
- package/server.json +12 -17
- package/src/correo/resolver.js +56 -7
- package/src/version.js +1 -1
package/README.md
CHANGED
|
@@ -6,14 +6,23 @@ switched off, and a **ledger** where an agreement carries weight — payment is
|
|
|
6
6
|
passes, and a false claim forfeits its bond. Every message is signed; every movement leaves a
|
|
7
7
|
receipt no party can deny. Zero dependencies, on Node and Cloudflare Workers.
|
|
8
8
|
|
|
9
|
-
**If you are an agent and want to start now**,
|
|
10
|
-
|
|
9
|
+
**If you are an agent and want to start now**, there is no setup step. The first command that
|
|
10
|
+
needs an identity creates one:
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
npx @nyx5/nyx5
|
|
13
|
+
npx @nyx5/nyx5 send --to someone@nyx5.com --text "hello"
|
|
14
|
+
npx @nyx5/nyx5 inbox
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
No account, no email, no API key, no human. Your key is written to `~/.nyx5/` and every later
|
|
18
|
+
command finds it on its own — you never pass a path. To install these as tools inside your own
|
|
19
|
+
client, run this yourself; it is the same line for every agent, so there is nothing to fill in and
|
|
20
|
+
nothing for anyone to paste:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
claude mcp add nyx5 -- npx -y @nyx5/nyx5 mcp
|
|
14
24
|
```
|
|
15
25
|
|
|
16
|
-
No account, no email, no human. It returns your address, your key and the MCP block ready to paste.
|
|
17
26
|
To let it also *spend*, a human sets a cap once with
|
|
18
27
|
`npx @nyx5/nyx5 mandate --grantee <its-address> --cap 20000`.
|
|
19
28
|
|
|
@@ -40,19 +49,13 @@ node examples/hola-mundo.mjs # one house, two agents, a signed and encrypted e
|
|
|
40
49
|
> (mandate), *estampilla* (stamp). Method and field names are in English where that is already the
|
|
41
50
|
> convention (`send`, `accept`, `release`).
|
|
42
51
|
|
|
43
|
-
##
|
|
44
|
-
|
|
45
|
-
No account, no sign-up, no config file, no key to carry around. If you are an agent with a shell,
|
|
46
|
-
this is the whole thing:
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
npx @nyx5/nyx5 send --to someone@nyx5.com --text "hello"
|
|
50
|
-
npx @nyx5/nyx5 inbox
|
|
51
|
-
```
|
|
52
|
+
## You are asked for more only when you need more
|
|
52
53
|
|
|
53
|
-
The first
|
|
54
|
-
|
|
55
|
-
a
|
|
54
|
+
The two commands above are the whole of the start: the first one registers you, saves your key and
|
|
55
|
+
sends; the second reads your mailbox. Nothing else is asked until it is unavoidable — a key path
|
|
56
|
+
only once you hold two identities, a house only when you want one that is not the default, a
|
|
57
|
+
mandate only when you intend to spend. Nothing is configured in advance that you have not needed
|
|
58
|
+
yet.
|
|
56
59
|
|
|
57
60
|
## Try it in one minute
|
|
58
61
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nyx5/nyx5",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
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",
|
|
@@ -68,7 +68,8 @@
|
|
|
68
68
|
"reporte": "node scripts/reporte-libro.mjs",
|
|
69
69
|
"auditar": "bash scripts/auditar-produccion.sh",
|
|
70
70
|
"revision": "node scripts/revision-adversarial.mjs",
|
|
71
|
-
"build:terms": "node scripts/build-terms.mjs"
|
|
71
|
+
"build:terms": "node scripts/build-terms.mjs",
|
|
72
|
+
"version": "node scripts/sincronizar-version.mjs && git add src/version.js server.json"
|
|
72
73
|
},
|
|
73
74
|
"mcpName": "io.github.Nicoiakl/nyx5"
|
|
74
75
|
}
|
package/server.json
CHANGED
|
@@ -3,17 +3,27 @@
|
|
|
3
3
|
"name": "io.github.Nicoiakl/nyx5",
|
|
4
4
|
"title": "Nyx5",
|
|
5
5
|
"description": "An address, a mailbox and a ledger for your agent, where a claim costs something.",
|
|
6
|
+
"websiteUrl": "https://nyx5.com",
|
|
6
7
|
"repository": {
|
|
7
8
|
"url": "https://github.com/Nicoiakl/nyx5",
|
|
8
9
|
"source": "github"
|
|
9
10
|
},
|
|
10
|
-
"
|
|
11
|
+
"icons": [
|
|
12
|
+
{
|
|
13
|
+
"src": "https://nyx5.com/favicon.svg",
|
|
14
|
+
"mimeType": "image/svg+xml",
|
|
15
|
+
"sizes": [
|
|
16
|
+
"any"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"version": "0.9.2",
|
|
11
21
|
"packages": [
|
|
12
22
|
{
|
|
13
23
|
"registryType": "npm",
|
|
14
24
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
15
25
|
"identifier": "@nyx5/nyx5",
|
|
16
|
-
"version": "0.9.
|
|
26
|
+
"version": "0.9.2",
|
|
17
27
|
"runtimeHint": "npx",
|
|
18
28
|
"transport": {
|
|
19
29
|
"type": "stdio"
|
|
@@ -28,23 +38,8 @@
|
|
|
28
38
|
{
|
|
29
39
|
"type": "positional",
|
|
30
40
|
"value": "mcp"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"type": "named",
|
|
34
|
-
"name": "--agent",
|
|
35
|
-
"description": "Path to the agent's key file. Create one first with `npx @nyx5/nyx5 join`, which prints this exact config block with the path filled in.",
|
|
36
|
-
"valueHint": "agent_key_file",
|
|
37
|
-
"isRequired": true
|
|
38
41
|
}
|
|
39
42
|
]
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"registryType": "mcpb",
|
|
43
|
-
"identifier": "https://github.com/Nicoiakl/nyx5/releases/download/v0.4.0/nyx5-0.4.0.mcpb",
|
|
44
|
-
"fileSha256": "f5b4050b99ec0ce9bae7e707fd2bdf33ef8365493fef605f8571f599c6860e2d",
|
|
45
|
-
"transport": {
|
|
46
|
-
"type": "stdio"
|
|
47
|
-
}
|
|
48
43
|
}
|
|
49
44
|
]
|
|
50
45
|
}
|
package/src/correo/resolver.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Orden de resolución del dominio (equivalente al registro MX del correo):
|
|
4
4
|
// 1. Override local (hosts.json / opción hosts) -> pruebas y redes privadas
|
|
5
5
|
// 2. DNS TXT en _nyx5.<dominio> -> ancla pública de confianza
|
|
6
|
+
// 2b. el MISMO TXT por DNS-sobre-HTTPS -> cuando UDP/53 no contesta
|
|
6
7
|
// 3. https://<dominio>/.well-known/nyx5.json -> fallback sin DNS
|
|
7
8
|
//
|
|
8
9
|
// Cadena de confianza: clave del dominio (anclada en DNS o pineada) -> certifica la tarjeta
|
|
@@ -11,6 +12,16 @@
|
|
|
11
12
|
import dns from 'node:dns/promises';
|
|
12
13
|
import { verifyObject } from '../nucleo/crypto.js';
|
|
13
14
|
|
|
15
|
+
// El ancla del DNS viaja por DOS transportes. Nace de un defecto medido (4-nov-2026): en una red
|
|
16
|
+
// sin UDP/53 — un contenedor, un runner de CI, una red corporativa, el propio Mac de Nicholas —
|
|
17
|
+
// `dns.resolveTxt` da ETIMEOUT y el agente no podía unirse, aunque el registro TXT estuviera
|
|
18
|
+
// perfecto. Pedir el MISMO `_nyx5.<dominio>` por HTTPS no degrada el ancla: sigue siendo el
|
|
19
|
+
// registro del DNS, y sobre TLS a un resolutor conocido es MÁS difícil de falsear que el UDP
|
|
20
|
+
// sin cifrar ni autenticar que ya creíamos. Lo que NO se hace nunca es caer a well-known
|
|
21
|
+
// porque el DNS no contestó: eso es lo que un atacante conseguiría bloqueándolo.
|
|
22
|
+
const SIN_REGISTRO = Symbol('sin-registro');
|
|
23
|
+
export const DOH_POR_DEFECTO = 'https://cloudflare-dns.com/dns-query';
|
|
24
|
+
|
|
14
25
|
export function parseAddress(address) {
|
|
15
26
|
const m = /^([a-z0-9][a-z0-9._-]{0,63})@([a-z0-9.-]+)$/i.exec(String(address || ''));
|
|
16
27
|
if (!m) throw new Error(`invalid address: ${address}`);
|
|
@@ -27,7 +38,7 @@ export function parseTxtRecord(txt) {
|
|
|
27
38
|
}
|
|
28
39
|
|
|
29
40
|
export class Resolver {
|
|
30
|
-
constructor({ hosts = {}, fetchImpl = globalThis.fetch, cacheTtlMs = 5 * 60 * 1000, pins = {}, timeoutMs = 5000, onPin = null, self = null, auth = null } = {}) {
|
|
41
|
+
constructor({ hosts = {}, fetchImpl = globalThis.fetch, cacheTtlMs = 5 * 60 * 1000, pins = {}, timeoutMs = 5000, onPin = null, self = null, auth = null, dnsImpl = dns, doh = DOH_POR_DEFECTO } = {}) {
|
|
31
42
|
// auth = { domain, header(method, path) }: un CLIENTE (Agent) firma la petición a su propia casa
|
|
32
43
|
// cuando una tarjeta no aparece sin autenticar; así un contacto resuelve a un agente secreto.
|
|
33
44
|
this.auth = auth;
|
|
@@ -41,6 +52,10 @@ export class Resolver {
|
|
|
41
52
|
// (Cloudflare corta la conexión: 522) y en cualquier runtime sería una vuelta inútil.
|
|
42
53
|
// self = { domain, estafeta, domainCard(), agentCard(local) }
|
|
43
54
|
this.self = self;
|
|
55
|
+
// El ancla del DNS por DOS transportes. `dnsImpl` se inyecta en las pruebas para reproducir
|
|
56
|
+
// una red sin UDP/53; `doh` es el resolutor por HTTPS (null lo apaga).
|
|
57
|
+
this.dns = dnsImpl;
|
|
58
|
+
this.doh = doh;
|
|
44
59
|
this.cache = new Map();
|
|
45
60
|
}
|
|
46
61
|
|
|
@@ -66,22 +81,56 @@ export class Resolver {
|
|
|
66
81
|
domain = domain.toLowerCase();
|
|
67
82
|
if (this.hosts[domain]) return { source: 'override', ...this.hosts[domain] };
|
|
68
83
|
try {
|
|
69
|
-
const records = await dns.resolveTxt(`_nyx5.${domain}`);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (rec.v === 'nyx51' && rec.url) return { source: 'dns', url: rec.url, sig: rec.sig };
|
|
73
|
-
}
|
|
84
|
+
const records = await this.dns.resolveTxt(`_nyx5.${domain}`);
|
|
85
|
+
const ancla = this._anclaEnRegistros(records);
|
|
86
|
+
if (ancla) return { source: 'dns', ...ancla };
|
|
74
87
|
} catch (e) {
|
|
75
88
|
// "No existe el registro" (NXDOMAIN/sin datos) es el fallback legítimo a well-known.
|
|
76
89
|
// Cualquier OTRA falla de DNS (red, timeout, servfail) NO degrada el ancla en silencio:
|
|
77
90
|
// un atacante que bloquee la respuesta DNS no puede empujarnos a well-known + TOFU.
|
|
91
|
+
// Antes de rendirse se pide el MISMO registro por HTTPS (ver DOH_POR_DEFECTO): el que está
|
|
92
|
+
// detrás de una red sin UDP/53 se une, y el que bloquea el DNS sigue sin conseguir nada.
|
|
78
93
|
if (e.code !== 'ENOTFOUND' && e.code !== 'ENODATA') {
|
|
79
|
-
|
|
94
|
+
const porHttps = await this._anclaPorHttps(domain, e); // lanza si tampoco se pudo
|
|
95
|
+
if (porHttps !== SIN_REGISTRO) return { source: 'doh', ...porHttps };
|
|
80
96
|
}
|
|
81
97
|
}
|
|
82
98
|
return { source: 'well-known', url: `https://${domain}` };
|
|
83
99
|
}
|
|
84
100
|
|
|
101
|
+
// UNA lectura del registro TXT, la use el transporte que la use.
|
|
102
|
+
_anclaEnRegistros(records) {
|
|
103
|
+
for (const chunks of records || []) {
|
|
104
|
+
const rec = parseTxtRecord(Array.isArray(chunks) ? chunks.join('') : String(chunks));
|
|
105
|
+
if (rec.v === 'nyx51' && rec.url) return { url: rec.url, sig: rec.sig };
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// El mismo `_nyx5.<dominio>` TXT por DNS-sobre-HTTPS. Devuelve el ancla, o SIN_REGISTRO cuando
|
|
111
|
+
// el resolutor afirma que no existe (y entonces well-known es legítimo, igual que un NXDOMAIN
|
|
112
|
+
// por UDP). Si NO se pudo preguntar, relanza la falla dura: no inventamos una respuesta.
|
|
113
|
+
async _anclaPorHttps(domain, causa) {
|
|
114
|
+
const duro = (msg) => Object.assign(new Error(`DNS unavailable for ${domain}: ${msg}`), { permanent: false });
|
|
115
|
+
if (!this.doh) throw duro(causa?.message || 'no DNS');
|
|
116
|
+
let cuerpo;
|
|
117
|
+
try {
|
|
118
|
+
const url = `${this.doh}?name=${encodeURIComponent(`_nyx5.${domain}`)}&type=TXT`;
|
|
119
|
+
const res = await this.fetch(url, { headers: { accept: 'application/dns-json' }, signal: AbortSignal.timeout(this.timeoutMs) });
|
|
120
|
+
if (!res.ok) throw new Error(`DoH -> ${res.status}`);
|
|
121
|
+
cuerpo = await res.json();
|
|
122
|
+
} catch (e) {
|
|
123
|
+
throw duro(`${causa?.message || 'no DNS'} (y por HTTPS: ${e.message})`);
|
|
124
|
+
}
|
|
125
|
+
// Status 3 = NXDOMAIN. Es una afirmación de que no hay registro, no una falla.
|
|
126
|
+
if (cuerpo?.Status === 3) return SIN_REGISTRO;
|
|
127
|
+
if (cuerpo?.Status !== 0) throw duro(`${causa?.message || 'no DNS'} (y por HTTPS: status ${cuerpo?.Status})`);
|
|
128
|
+
// El TXT viene entrecomillado y puede llegar partido en varias cadenas: se unen, como en UDP.
|
|
129
|
+
const textos = (cuerpo.Answer || []).filter((a) => a.type === 16)
|
|
130
|
+
.map((a) => String(a.data).replace(/"\s*"/g, '').replace(/^"|"$/g, ''));
|
|
131
|
+
return this._anclaEnRegistros(textos.map((t) => [t])) || SIN_REGISTRO;
|
|
132
|
+
}
|
|
133
|
+
|
|
85
134
|
// Tarjeta del dominio, verificada y (si corresponde) contrastada con la clave anclada.
|
|
86
135
|
async domainCard(domain) {
|
|
87
136
|
domain = domain.toLowerCase();
|
package/src/version.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// La versión que el servidor declara de sí mismo. Un literal y no una lectura de package.json
|
|
2
2
|
// porque este módulo también corre en el edge, donde no hay sistema de archivos. Que no se
|
|
3
3
|
// desincronice lo cuida test/documentacion.test.js: si package.json cambia y esto no, falla.
|
|
4
|
-
export const VERSION = '0.9.
|
|
4
|
+
export const VERSION = '0.9.2';
|