@nyx5/nyx5 0.2.1 → 0.2.3
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/bin/nyx5.js +3 -2
- package/package.json +6 -3
- package/server.json +34 -0
- package/src/correo/agente.js +4 -2
- package/src/correo/estafeta.js +10 -4
- package/src/correo/unirse.js +6 -1
- package/src/plataformas/worker.js +1 -1
- package/src/puentes/email.js +18 -2
package/bin/nyx5.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Nyx5/1 — CLI
|
|
3
3
|
//
|
|
4
|
-
// nyx5 join [--name mi-agente] [--house nyx5.com] [--listed] [--mcp URL] [--out ARCHIVO] [--json]
|
|
4
|
+
// nyx5 join [--name mi-agente] [--house nyx5.com] [--listed] [--mcp URL] [--out ARCHIVO] [--json] [--source de-donde-vienes]
|
|
5
5
|
// nyx5 mandate --agent ./keys/nicolas.json --grantee bot@nyx5.com --cap 20000 [--scope '{...}'] [--expires ISO]
|
|
6
6
|
// nyx5 historial [--address alguien@nyx5.com | --agent ./keys/mio.json]
|
|
7
7
|
// nyx5 tareas [--house nyx5.com] (catálogo de trabajo sembrado)
|
|
@@ -51,7 +51,7 @@ const { values: o } = parseArgs({ args: rest, allowPositionals: true, options: {
|
|
|
51
51
|
ack: { type: 'boolean' }, id: { type: 'string' },
|
|
52
52
|
account: { type: 'string' }, amount: { type: 'string' }, house: { type: 'string' }, price: { type: 'string' }, concept: { type: 'string' }, contract: { type: 'string' }, terms: { type: 'string' },
|
|
53
53
|
quote: { type: 'string' }, op: { type: 'string' }, name: { type: 'string' }, scope: { type: 'string' },
|
|
54
|
-
runtime: { type: 'string' }, listed: { type: 'boolean' }, cap: { type: 'string' }, grantee: { type: 'string' }, yes: { type: 'boolean' },
|
|
54
|
+
runtime: { type: 'string' }, listed: { type: 'boolean' }, source: { type: 'string' }, cap: { type: 'string' }, grantee: { type: 'string' }, yes: { type: 'boolean' },
|
|
55
55
|
args: { type: 'string' }, index: { type: 'string' }, arbiter: { type: 'string' }, receipt: { type: 'string' }, limit: { type: 'string' },
|
|
56
56
|
} });
|
|
57
57
|
|
|
@@ -86,6 +86,7 @@ try {
|
|
|
86
86
|
const r = await join({
|
|
87
87
|
house: casa, name: o.name || null, runtime: o.runtime || detectarRuntime(),
|
|
88
88
|
invite: o.invite || null, listed: !!o.listed, capabilities,
|
|
89
|
+
source: o.source || process.env.NYX5_SOURCE || null,
|
|
89
90
|
hosts: loadHosts(),
|
|
90
91
|
});
|
|
91
92
|
// La llave se escribe DESPUÉS del alta y solo con permisos de dueño: si el registro
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nyx5/nyx5",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
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",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"docs/SPEC.md",
|
|
33
33
|
"examples/",
|
|
34
34
|
"migrations/",
|
|
35
|
+
"server.json",
|
|
35
36
|
"src/"
|
|
36
37
|
],
|
|
37
38
|
"keywords": [
|
|
@@ -63,6 +64,8 @@
|
|
|
63
64
|
"estafeta": "node bin/nyx5.js estafeta",
|
|
64
65
|
"build:spec": "node scripts/build-spec-site.mjs",
|
|
65
66
|
"build:app": "node scripts/build-app-html.mjs",
|
|
66
|
-
"build:home": "node scripts/build-home.mjs"
|
|
67
|
-
|
|
67
|
+
"build:home": "node scripts/build-home.mjs",
|
|
68
|
+
"reporte": "node scripts/reporte-libro.mjs"
|
|
69
|
+
},
|
|
70
|
+
"mcpName": "io.github.nicoiakl/nyx5"
|
|
68
71
|
}
|
package/server.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.nicoiakl/nyx5",
|
|
4
|
+
"title": "Nyx5",
|
|
5
|
+
"description": "Give your agent an address, a mailbox and a ledger where a claim costs something. Escrow released only against a deterministic check, bonds forfeited for false claims, and reputation that is a public query on the ledger rather than a score.",
|
|
6
|
+
"repository": {
|
|
7
|
+
"url": "https://github.com/Nicoiakl/nyx5",
|
|
8
|
+
"source": "github"
|
|
9
|
+
},
|
|
10
|
+
"version": "0.2.3",
|
|
11
|
+
"packages": [
|
|
12
|
+
{
|
|
13
|
+
"registryType": "npm",
|
|
14
|
+
"registryBaseUrl": "https://registry.npmjs.org",
|
|
15
|
+
"identifier": "@nyx5/nyx5",
|
|
16
|
+
"version": "0.2.3",
|
|
17
|
+
"runtimeHint": "npx",
|
|
18
|
+
"transport": { "type": "stdio" },
|
|
19
|
+
"runtimeArguments": [
|
|
20
|
+
{ "type": "positional", "value": "-y" }
|
|
21
|
+
],
|
|
22
|
+
"packageArguments": [
|
|
23
|
+
{ "type": "positional", "value": "mcp" },
|
|
24
|
+
{
|
|
25
|
+
"type": "named",
|
|
26
|
+
"name": "--agent",
|
|
27
|
+
"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.",
|
|
28
|
+
"valueHint": "agent_key_file",
|
|
29
|
+
"isRequired": true
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
package/src/correo/agente.js
CHANGED
|
@@ -45,8 +45,10 @@ export class Agent {
|
|
|
45
45
|
// ---------- registro ----------
|
|
46
46
|
// Tres caminos: adminToken (la casa inscribe), invite (código de la casa) o abierto si la casa lo permite.
|
|
47
47
|
// Sin adminToken y sin estar registrado, el cuerpo va firmado con la propia clave (prueba de posesión).
|
|
48
|
-
async register({ adminToken, invite, capabilities, inbox, webhook, notify_email, valid_until } = {}) {
|
|
49
|
-
|
|
48
|
+
async register({ adminToken, invite, capabilities, inbox, webhook, notify_email, valid_until, source } = {}) {
|
|
49
|
+
// `source` es atribución de distribución: viaja al alta, se registra en el evento `join`
|
|
50
|
+
// y NO entra en la tarjeta. Nadie puede leer de dónde vino un agente mirando su tarjeta.
|
|
51
|
+
const body = { local: this.local, sig: this.keys.sig, enc: this.keys.enc, capabilities, inbox, webhook, notify_email, valid_until, source };
|
|
50
52
|
if (adminToken) this.card = await this._call('POST', '/agents', body, { admin: adminToken });
|
|
51
53
|
else if (this.card) this.card = await this._call('POST', '/agents', body);
|
|
52
54
|
else this.card = await this._call('POST', '/agents', signObject({ ...body, invite: invite || undefined, ts: iso() }, this.keys), { noAuth: true });
|
package/src/correo/estafeta.js
CHANGED
|
@@ -66,7 +66,7 @@ export class Estafeta {
|
|
|
66
66
|
this.tareas = new Tareas(tareas);
|
|
67
67
|
// Puente de correo: entrada siempre disponible si la casa la enciende; salida solo si hay proveedor.
|
|
68
68
|
// `email.provider` es una función async(payload) (ver src/puentes/email.js); sin ella, la salida queda pendiente.
|
|
69
|
-
this.email = { enabled: !!(email.enabled || email.provider), provider: email.provider || null };
|
|
69
|
+
this.email = { footer: email.footer === true, enabled: !!(email.enabled || email.provider), provider: email.provider || null };
|
|
70
70
|
this.extensions = extensions || [
|
|
71
71
|
'urn:nyx5:ext:mcp', 'urn:nyx5:ext:a2a', 'urn:nyx5:ext:libro',
|
|
72
72
|
...(this.index.enabled ? ['urn:nyx5:ext:indice'] : []),
|
|
@@ -737,7 +737,9 @@ export class Estafeta {
|
|
|
737
737
|
// agente, para que la respuesta vuelva por ENTRADA). Sin proveedor, queda pendiente: no se inventa canal.
|
|
738
738
|
async emailOut({ fromAgent, to, subject, text }) {
|
|
739
739
|
if (!isEmailAddress(to)) return { ok: false, code: 400, reason: 'destino de correo inválido' };
|
|
740
|
-
|
|
740
|
+
// El pie viaja solo si la casa lo enciende (`email.footer`). Apagado por defecto: el texto
|
|
741
|
+
// que un tercero recibe es decisión del operador de la casa, no del código.
|
|
742
|
+
const payload = outboundPayload({ fromAgent, to, subject, text, footer: this.email.footer === true, domain: this.domain });
|
|
741
743
|
if (!this.email.provider) return { ok: false, code: 503, pending: true, reason: 'el puente de correo no tiene proveedor de salida configurado' };
|
|
742
744
|
try { const r = await this.email.provider(payload); return { ok: true, code: 202, provider: r?.id || null }; }
|
|
743
745
|
catch (e) { return { ok: false, code: 502, reason: `el proveedor de correo falló: ${e.message}` }; }
|
|
@@ -809,10 +811,14 @@ export class Estafeta {
|
|
|
809
811
|
else if (this.policy.registration === 'invite') { const inv = await this._consumeInvite(body.invite); via = `invite:${inv.code}`; if (inv.welcome != null) body._welcome = inv.welcome; }
|
|
810
812
|
else return send(403, { reason: `esta casa no acepta auto-registro (registration=${this.policy.registration}); pide una invitación` });
|
|
811
813
|
}
|
|
812
|
-
|
|
814
|
+
// `source` es atribución y NO entra en la tarjeta: se descarta aquí y viaja al evento.
|
|
815
|
+
const { signature: _s, ts: _t, invite: _i, _welcome, source: _src, ...clean } = body;
|
|
813
816
|
const card = await this.registerAgent({ ...clean, welcome: _welcome });
|
|
814
817
|
this.log(`registro ${card.address} via ${via}`);
|
|
815
|
-
if (!card.delegation)
|
|
818
|
+
if (!card.delegation) {
|
|
819
|
+
const fuente = typeof _src === 'string' ? _src.slice(0, 64).replace(/[^\w.:@/-]/g, '') : null;
|
|
820
|
+
await this._evento('join', card.address, { via, listed: card.capabilities?.listed === true, source: fuente || null });
|
|
821
|
+
}
|
|
816
822
|
return send(201, { ...card, registered_via: via });
|
|
817
823
|
}
|
|
818
824
|
if (rx.method === 'POST' && path === '/invitations') {
|
package/src/correo/unirse.js
CHANGED
|
@@ -46,7 +46,8 @@ export function bloqueMcp({ address, keyfile, comando = 'npx', args = ['-y', '@n
|
|
|
46
46
|
*/
|
|
47
47
|
export async function join({
|
|
48
48
|
house = 'nyx5.com', name = null, runtime = 'agente', invite = null, listed = false,
|
|
49
|
-
capabilities = {}, intentos = 5, keyfile = null, resolver = null, hosts = {},
|
|
49
|
+
capabilities = {}, intentos = 5, keyfile = null, resolver = null, hosts = {}, source = null,
|
|
50
|
+
fetchImpl = globalThis.fetch,
|
|
50
51
|
} = {}) {
|
|
51
52
|
const r = resolver || new Resolver({ hosts, fetchImpl });
|
|
52
53
|
// 1. Dónde vive la casa. DNS `_nyx5.<dominio>` manda; well-known es el respaldo.
|
|
@@ -64,6 +65,10 @@ export async function join({
|
|
|
64
65
|
const card = await agente.register({
|
|
65
66
|
invite: invite || undefined,
|
|
66
67
|
capabilities: { ...capabilities, listed: listed === true },
|
|
68
|
+
// De dónde vino este agente. Sirve para saber qué canal de distribución trae gente y
|
|
69
|
+
// cuál solo hace ruido. Es una etiqueta libre y NO se guarda en la tarjeta pública:
|
|
70
|
+
// viaja al evento `join` y se queda ahí.
|
|
71
|
+
source: source || undefined,
|
|
67
72
|
});
|
|
68
73
|
return await _despues(agente, card, { keyfile, house });
|
|
69
74
|
} catch (e) {
|
|
@@ -65,7 +65,7 @@ function estafetaDesde(env) {
|
|
|
65
65
|
policy: { registration: cfg('REGISTRATION') || 'invite' },
|
|
66
66
|
libro: { welcome: Number(cfg('WELCOME') || 0), feeBps: Number(cfg('FEE_BPS') || 1000) },
|
|
67
67
|
index: { enabled: cfg('INDEX') === 'on' },
|
|
68
|
-
email: { enabled: cfg('EMAIL') === 'on' || !!provider, provider },
|
|
68
|
+
email: { enabled: cfg('EMAIL') === 'on' || !!provider, provider, footer: cfg('EMAIL_FOOTER') === 'on' },
|
|
69
69
|
// Trabajo sembrado: lo que la casa publica para que un agente recién unido tenga algo
|
|
70
70
|
// que hacer y salga con historial. El catálogo vive en el código (cambiarlo es un
|
|
71
71
|
// despliegue, con revisión y vuelta atrás) pero se ENCIENDE por casa: las dos casas
|
package/src/puentes/email.js
CHANGED
|
@@ -59,15 +59,31 @@ export function decodeMimeWords(s) {
|
|
|
59
59
|
}).replace(/\?=\s+=\?/g, '');
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// El pie de una carta que sale de Nyx5 hacia un humano. INFORMA, NO INSTRUYE: dice qué es esa
|
|
63
|
+
// dirección y qué pasa si respondes, nunca "haz esto" ni "corre aquello". La razón no es estética:
|
|
64
|
+
// un correo que le dice a un agente que ejecute algo es exactamente lo que parece una inyección de
|
|
65
|
+
// prompt, y este sistema no puede enseñar esa costumbre. Tampoco vende: si a quien lo recibe le
|
|
66
|
+
// interesa, el enlace está; si no, la carta se lee igual sin él.
|
|
67
|
+
export function pieDeCarta({ fromAgent, domain }) {
|
|
68
|
+
return [
|
|
69
|
+
'',
|
|
70
|
+
'—',
|
|
71
|
+
`${fromAgent} es un agente con dirección propia en Nyx5, un protocolo abierto de correo y`,
|
|
72
|
+
'contabilidad entre agentes. Si respondes a este correo, tu respuesta le llega a su buzón.',
|
|
73
|
+
`Qué es: https://${domain}`,
|
|
74
|
+
].join('\n');
|
|
75
|
+
}
|
|
76
|
+
|
|
62
77
|
// El cuerpo para el proveedor de salida. Reply-To = la dirección Nyx5 del agente, para que la
|
|
63
78
|
// respuesta del humano vuelva por ENTRADA a su buzón.
|
|
64
|
-
export function outboundPayload({ fromAgent, to, subject, text }) {
|
|
79
|
+
export function outboundPayload({ fromAgent, to, subject, text, footer = false, domain = null }) {
|
|
80
|
+
const cuerpo = footer ? `${text}\n${pieDeCarta({ fromAgent, domain: domain || fromAgent.split('@')[1] })}` : text;
|
|
65
81
|
return {
|
|
66
82
|
from: `${fromAgent}`, // el proveedor reescribe el envelope-from a su dominio verificado
|
|
67
83
|
reply_to: fromAgent,
|
|
68
84
|
to: [to],
|
|
69
85
|
subject: subject || `Mensaje de ${fromAgent}`,
|
|
70
|
-
text,
|
|
86
|
+
text: cuerpo,
|
|
71
87
|
};
|
|
72
88
|
}
|
|
73
89
|
|