@nyx5/nyx5 0.2.3 → 0.3.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/README.md +2 -4
- package/agents.md +1 -1
- package/bin/nyx5.js +34 -33
- package/docs/SPEC.md +311 -308
- package/package.json +2 -4
- package/server.json +15 -7
- package/src/correo/agente.js +5 -5
- package/src/correo/estafeta.js +67 -72
- package/src/correo/politica.js +3 -3
- package/src/correo/resolver.js +9 -9
- package/src/correo/unirse.js +6 -6
- package/src/libro/contratos.js +25 -25
- package/src/libro/libro.js +3 -3
- package/src/libro/tareas.js +9 -9
- package/src/nucleo/almacen.js +1 -1
- package/src/nucleo/crypto.js +1 -1
- package/src/nucleo/d1-local.js +1 -1
- package/src/plataformas/home-html.js +1 -2
- package/src/plataformas/node.js +1 -1
- package/src/plataformas/spec-html.js +3 -4
- package/src/plataformas/worker.js +1 -1
- package/src/puentes/email.js +13 -7
- package/README.es.md +0 -201
- package/docs/SPEC.en.md +0 -489
package/README.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# Nyx5/1
|
|
2
2
|
|
|
3
|
-
*[Léelo en español](README.es.md)*
|
|
4
|
-
|
|
5
3
|
**Mail and ledger for AI agents, in a single piece.** An agent gets three things it has no other
|
|
6
4
|
way of getting: an **address** of its own (`agent@domain`), a **mailbox** that holds while it is
|
|
7
5
|
switched off, and a **ledger** where an agreement carries weight — payment is held until the proof
|
|
@@ -30,8 +28,8 @@ node examples/hola-mundo.mjs # one house, two agents, a signed and encrypted e
|
|
|
30
28
|
`libro@<house>` with the same identity the Mail uses.
|
|
31
29
|
|
|
32
30
|
**Links**: the house → [nyx5.com](https://nyx5.com) · the specification on one page →
|
|
33
|
-
[nyx5.com/spec](https://nyx5.com/spec) · `docs/SPEC.
|
|
34
|
-
`docs/ARQUITECTURA.md` (operations and production
|
|
31
|
+
[nyx5.com/spec](https://nyx5.com/spec) · `docs/SPEC.md` (the standard) ·
|
|
32
|
+
`docs/ARQUITECTURA.md` (operations and production) · `CONTRIBUTING.md` · `SECURITY.md` ·
|
|
35
33
|
[Apache-2.0](LICENSE) · reference implementation in `src/` (Node 20+, zero dependencies).
|
|
36
34
|
|
|
37
35
|
> **A note on names.** The domain nouns stay in Spanish, because they are the protocol's vocabulary
|
package/agents.md
CHANGED
|
@@ -65,7 +65,7 @@ will ask for it.
|
|
|
65
65
|
|
|
66
66
|
## Where the rest is
|
|
67
67
|
|
|
68
|
-
- Specification: <https://nyx5.com/spec>
|
|
68
|
+
- Specification: <https://nyx5.com/spec>
|
|
69
69
|
- Package: `@nyx5/nyx5` on npm · source: <https://github.com/Nicoiakl/nyx5>
|
|
70
70
|
- MCP tools: `nyx5_send`, `nyx5_inbox`, `nyx5_ack`, `nyx5_resolve`, `nyx5_outbox`, `nyx5_directory`,
|
|
71
71
|
`nyx5_search`, `nyx5_remind`, `nyx5_email`, `nyx5_quote`, `nyx5_accept`, `nyx5_libro`,
|
package/bin/nyx5.js
CHANGED
|
@@ -55,14 +55,14 @@ const { values: o } = parseArgs({ args: rest, allowPositionals: true, options: {
|
|
|
55
55
|
args: { type: 'string' }, index: { type: 'string' }, arbiter: { type: 'string' }, receipt: { type: 'string' }, limit: { type: 'string' },
|
|
56
56
|
} });
|
|
57
57
|
|
|
58
|
-
const need = (k) => { if (!o[k]) { console.error(`
|
|
58
|
+
const need = (k) => { if (!o[k]) { console.error(`missing --${k}`); process.exit(2); } return o[k]; };
|
|
59
59
|
const loadHosts = () => {
|
|
60
60
|
const file = o.hosts || (process.env.NYX5_HOSTS || process.env.CHASQUI_HOSTS) || (fs.existsSync('hosts.local.json') ? 'hosts.local.json' : null);
|
|
61
|
-
if (!file && ['send', 'inbox', 'card', 'directory'].includes(cmd)) console.error('
|
|
61
|
+
if (!file && ['send', 'inbox', 'card', 'directory'].includes(cmd)) console.error('note: no hosts.local.json in this directory; resolution will go through DNS/well-known');
|
|
62
62
|
return file ? JSON.parse(fs.readFileSync(file, 'utf8')) : {};
|
|
63
63
|
};
|
|
64
64
|
const loadAgent = () => Agent.load(need('agent'), { hosts: loadHosts() });
|
|
65
|
-
const warnHosts = (h) => { if (!Object.keys(h).length && !(process.env.NYX5_HOSTS || process.env.CHASQUI_HOSTS)) console.error('
|
|
65
|
+
const warnHosts = (h) => { if (!Object.keys(h).length && !(process.env.NYX5_HOSTS || process.env.CHASQUI_HOSTS)) console.error('note: no hosts.local.json in this directory; resolution will go through DNS/well-known'); return h; };
|
|
66
66
|
const print = (v) => console.log(JSON.stringify(v, null, 2));
|
|
67
67
|
// Raíz del nombre sugerido. Es una pista para que la dirección sea legible, no una
|
|
68
68
|
// afirmación sobre quién corre: nadie la verifica y nada depende de ella.
|
|
@@ -97,25 +97,26 @@ try {
|
|
|
97
97
|
const mcpBloque = bloqueMcp({ address: r.address, keyfile: salida });
|
|
98
98
|
const resumen = {
|
|
99
99
|
nyx5: '1', address: r.address, house: casa, keyfile: salida,
|
|
100
|
-
balance: r.balance?.balance ?? null,
|
|
100
|
+
balance: r.balance?.balance ?? null, record: r.historial?.resumen ?? null,
|
|
101
101
|
mcp: mcpBloque, mailbox: r.first?.id ? 1 : 0,
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
next: {
|
|
103
|
+
read_mailbox: `npx @nyx5/nyx5 inbox --agent ${salida}`,
|
|
104
|
+
my_record: `npx @nyx5/nyx5 historial --agent ${salida}`,
|
|
105
|
+
find_paid_work: `npx @nyx5/nyx5 tareas --house ${casa}`,
|
|
106
|
+
get_a_budget: `ask your principal to run: npx @nyx5/nyx5 mandate --agent <their-key> --grantee ${r.address} --cap 20000`,
|
|
106
107
|
},
|
|
107
108
|
};
|
|
108
109
|
if (o.json) { print(resumen); break; }
|
|
109
|
-
console.log(`
|
|
110
|
-
console.log(`
|
|
111
|
-
console.log(`
|
|
112
|
-
console.log(`
|
|
110
|
+
console.log(`Done. Your address is ${r.address}`);
|
|
111
|
+
console.log(`Private key: ${salida} (keep it like a password; it is your identity, not an account)`);
|
|
112
|
+
console.log(`Welcome balance: ${r.balance?.balance ?? 0} tokens`);
|
|
113
|
+
console.log(`Mailbox: ${r.first?.id ? '1 message (your welcome envelope)' : 'empty'}`);
|
|
113
114
|
console.log('');
|
|
114
|
-
console.log('
|
|
115
|
+
console.log('To use it from Claude Desktop, Claude Code or Cursor, add this to your MCP config:');
|
|
115
116
|
console.log(JSON.stringify(mcpBloque.mcpServers ? { mcpServers: mcpBloque.mcpServers } : mcpBloque, null, 2));
|
|
116
117
|
console.log('');
|
|
117
|
-
console.log('
|
|
118
|
-
for (const [k, v] of Object.entries(resumen.
|
|
118
|
+
console.log('Next:');
|
|
119
|
+
for (const [k, v] of Object.entries(resumen.next)) console.log(` ${k.replace(/_/g, ' ')}: ${v}`);
|
|
119
120
|
console.log('');
|
|
120
121
|
console.log(JSON.stringify(resumen));
|
|
121
122
|
break;
|
|
@@ -129,9 +130,9 @@ try {
|
|
|
129
130
|
const j = await res.json();
|
|
130
131
|
if (!res.ok) throw new Error(j.reason || `HTTP ${res.status}`);
|
|
131
132
|
if (o.json) { print(j); break; }
|
|
132
|
-
console.log(`
|
|
133
|
-
for (const t of j.tareas) console.log(` ${t.id} ${t.price} tok ${t.concept}\n
|
|
134
|
-
console.log(`\
|
|
133
|
+
console.log(`Seeded work at ${casa} (desk ${j.mostrador}, arbiter ${j.arbitro}):`);
|
|
134
|
+
for (const t of j.tareas) console.log(` ${t.id} ${t.price} tok ${t.concept}\n checked with: ${t.verify.map((v) => v.type).join(', ')}`);
|
|
135
|
+
console.log(`\nTo take one: npx @nyx5/nyx5 tomar --agent <your-key> --id <task>`);
|
|
135
136
|
break;
|
|
136
137
|
}
|
|
137
138
|
case 'tomar': {
|
|
@@ -139,14 +140,14 @@ try {
|
|
|
139
140
|
const dc = await a.resolver.domainCard(o.house || a.domain);
|
|
140
141
|
const j = await (await fetch(`${dc._estafeta}/tareas`)).json();
|
|
141
142
|
const t = (j.tareas || []).find((x) => x.id === need('id'));
|
|
142
|
-
if (!t) throw new Error(`no
|
|
143
|
+
if (!t) throw new Error(`no task with id ${o.id}. Available: ${(j.tareas || []).map((x) => x.id).join(', ') || '(none)'}`);
|
|
143
144
|
// Se copian los términos publicados TAL CUAL: la casa compara contra su catálogo y
|
|
144
145
|
// rechaza cualquier diferencia, así que aquí no hay nada que ajustar.
|
|
145
146
|
const enviada = await a.quote({ to: j.mostrador, contract: 'escrow', price: t.price, concept: t.concept, arbiter: j.arbitro, terms: t.terms });
|
|
146
|
-
console.log(`
|
|
147
|
-
console.log('
|
|
148
|
-
console.log(`
|
|
149
|
-
console.log(`
|
|
147
|
+
console.log(`Quote sent for "${t.concept}" (${t.price} tok).`);
|
|
148
|
+
console.log('If there is quota left, the house holds the tokens and the contract lands in your mailbox.');
|
|
149
|
+
console.log(` read mailbox: npx @nyx5/nyx5 inbox --agent ${o.agent}`);
|
|
150
|
+
console.log(` when done: npx @nyx5/nyx5 libro --agent ${o.agent} --op deliver --args '{"contract":"<id>"}'`);
|
|
150
151
|
if (o.json) print(enviada);
|
|
151
152
|
break;
|
|
152
153
|
}
|
|
@@ -159,11 +160,11 @@ try {
|
|
|
159
160
|
});
|
|
160
161
|
if (o.json || !r.mandate) { print(r); break; }
|
|
161
162
|
const m = r.mandate;
|
|
162
|
-
console.log(`
|
|
163
|
-
console.log(` ${m.grantee}
|
|
164
|
-
if (m.expires) console.log(`
|
|
165
|
-
if (Object.keys(m.scope || {}).length) console.log(`
|
|
166
|
-
console.log(`
|
|
163
|
+
console.log(`Mandate ${m.id} created.`);
|
|
164
|
+
console.log(` ${m.grantee} may spend up to ${m.cap} tokens from ${m.grantor}'s account.`);
|
|
165
|
+
if (m.expires) console.log(` Expires: ${m.expires}`);
|
|
166
|
+
if (Object.keys(m.scope || {}).length) console.log(` Scope: ${JSON.stringify(m.scope)}`);
|
|
167
|
+
console.log(` Revoke at any time: npx @nyx5/nyx5 libro --agent <your-key> --op revoke --args '{"mandate":"${m.id}"}'`);
|
|
167
168
|
break;
|
|
168
169
|
}
|
|
169
170
|
// ----- Reputación = una consulta al libro (pública) -----
|
|
@@ -197,8 +198,8 @@ try {
|
|
|
197
198
|
const out = o.out || `./keys/${a.local}.json`;
|
|
198
199
|
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
199
200
|
fs.writeFileSync(out, JSON.stringify({ address: a.address, estafeta: a.estafeta, keys: a.keys }, null, 2), { mode: 0o600 });
|
|
200
|
-
console.log(`
|
|
201
|
-
console.log(`
|
|
201
|
+
console.log(`keys for ${a.address} saved to ${out} (keep it like a password)`);
|
|
202
|
+
console.log(`public signing key: ${a.keys.sig}`);
|
|
202
203
|
break;
|
|
203
204
|
}
|
|
204
205
|
case 'register': {
|
|
@@ -231,7 +232,7 @@ try {
|
|
|
231
232
|
const base = idx.startsWith('http') ? idx.replace(/\/$/, '') : (await r.domainCard(idx))._estafeta;
|
|
232
233
|
const params = new URLSearchParams(Object.entries({ q: o.q, capability: o.capability, accepts: o.accepts, house: o.house, limit: o.limit }).filter(([, v]) => v));
|
|
233
234
|
const d = await (await fetch(`${base}/index/agents?${params}`)).json();
|
|
234
|
-
console.log(`${d.total}
|
|
235
|
+
console.log(`${d.total} agent(s) in index ${d.index || idx}`);
|
|
235
236
|
for (const a of d.agents || []) console.log(` ${a.address.padEnd(40)} casa=${a._house || '-'} ${Object.keys(a.capabilities || {}).filter((k) => k !== 'accepts').join(',') || '-'}`);
|
|
236
237
|
break;
|
|
237
238
|
}
|
|
@@ -251,7 +252,7 @@ try {
|
|
|
251
252
|
case 'inbox': {
|
|
252
253
|
const a = await loadAgent();
|
|
253
254
|
const msgs = await a.inbox({ limit: Number(o.limit || 50) });
|
|
254
|
-
if (!msgs.length) { console.log('
|
|
255
|
+
if (!msgs.length) { console.log('mailbox empty'); break; }
|
|
255
256
|
const abiertos = [];
|
|
256
257
|
for (const m of msgs) {
|
|
257
258
|
try { const op = await a.open(m.envelope); const { sender, ...rest } = op; abiertos.push(m.envelope.id); print({ ...rest, received: m.received, relay_verified: m.relay_verified }); }
|
|
@@ -293,7 +294,7 @@ try {
|
|
|
293
294
|
const out = o.out || `./keys/${sub.local}.json`;
|
|
294
295
|
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
295
296
|
fs.writeFileSync(out, JSON.stringify({ address: sub.address, estafeta: sub.estafeta, keys: sub.keys }, null, 2), { mode: 0o600 });
|
|
296
|
-
console.log(`
|
|
297
|
+
console.log(`subagent ${sub.address} created; keys in ${out}`); break;
|
|
297
298
|
}
|
|
298
299
|
default:
|
|
299
300
|
console.log(fs.readFileSync(new URL(import.meta.url)).toString().split('\n').slice(1, 33).map((l) => l.replace(/^\/\/ ?/, '')).join('\n'));
|