@rookdaemon/agora 0.5.8 → 0.6.1
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 +36 -4
- package/dist/{chunk-P5EN45ZV.js → chunk-UDRIP62M.js} +200 -14
- package/dist/chunk-UDRIP62M.js.map +1 -0
- package/dist/cli.js +238 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +50 -2
- package/dist/index.js +24 -98
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-P5EN45ZV.js.map +0 -1
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ Agent A <---direct HTTP---> Agent B
|
|
|
41
41
|
|
|
42
42
|
2. **Peer Registry + Config**
|
|
43
43
|
- Local config (`~/.config/agora/config.json`) stores identity, peers, and optional relay settings.
|
|
44
|
+
- Named profiles live under `~/.config/agora/profiles/<name>/config.json`.
|
|
44
45
|
- Peer identity is public key; names are convenience labels.
|
|
45
46
|
|
|
46
47
|
3. **Transport Layer**
|
|
@@ -82,15 +83,17 @@ Agent A <---direct HTTP---> Agent B
|
|
|
82
83
|
- Automatic global pub/sub or DHT-style discovery.
|
|
83
84
|
- Protocol-level consensus/governance execution.
|
|
84
85
|
- CLI commands for reputation revocation/listing (message types exist in code, CLI workflow is not exposed).
|
|
85
|
-
-
|
|
86
|
+
- Multi-identity in a single config (email-client style "send as"). Use named profiles for now.
|
|
86
87
|
|
|
87
88
|
## CLI (Current Surface)
|
|
88
89
|
|
|
90
|
+
All commands accept `--profile <name>` (or `--as <name>`) to target a named profile instead of the default config.
|
|
91
|
+
|
|
89
92
|
### Identity
|
|
90
93
|
|
|
91
|
-
- `agora init`
|
|
92
|
-
- `agora whoami`
|
|
93
|
-
- `agora status`
|
|
94
|
+
- `agora init [--profile <name>]`
|
|
95
|
+
- `agora whoami [--profile <name>]`
|
|
96
|
+
- `agora status [--profile <name>]`
|
|
94
97
|
|
|
95
98
|
### Peers
|
|
96
99
|
|
|
@@ -98,6 +101,13 @@ Agent A <---direct HTTP---> Agent B
|
|
|
98
101
|
- `agora peers add <name> --pubkey <pubkey> [--url <url> --token <token>]`
|
|
99
102
|
- `agora peers remove <name|pubkey>`
|
|
100
103
|
- `agora peers discover [--relay <url>] [--relay-pubkey <pubkey>] [--limit <n>] [--active-within <ms>] [--save]`
|
|
104
|
+
- `agora peers copy <name|pubkey> --from <profile> --to <profile>`
|
|
105
|
+
|
|
106
|
+
### Config Transfer
|
|
107
|
+
|
|
108
|
+
- `agora config profiles` — list available profiles (default + named)
|
|
109
|
+
- `agora config export [--include-identity] [--output <file>]` — export peers/relay (and optionally identity) as portable JSON
|
|
110
|
+
- `agora config import <file> [--overwrite-identity] [--overwrite-relay] [--dry-run]` — merge exported config into current profile
|
|
101
111
|
|
|
102
112
|
### Messaging
|
|
103
113
|
|
|
@@ -159,6 +169,28 @@ Agent A <---direct HTTP---> Agent B
|
|
|
159
169
|
}
|
|
160
170
|
```
|
|
161
171
|
|
|
172
|
+
### Profiles
|
|
173
|
+
|
|
174
|
+
Run multiple identities on the same machine using named profiles:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Default profile: ~/.config/agora/config.json
|
|
178
|
+
agora init
|
|
179
|
+
|
|
180
|
+
# Named profile: ~/.config/agora/profiles/stefan/config.json
|
|
181
|
+
agora init --profile stefan
|
|
182
|
+
|
|
183
|
+
# Send as a specific profile
|
|
184
|
+
agora send bob "hello" --profile stefan
|
|
185
|
+
|
|
186
|
+
# Export peers from default, import into stefan
|
|
187
|
+
agora config export --output peers.json
|
|
188
|
+
agora config import peers.json --profile stefan
|
|
189
|
+
|
|
190
|
+
# Or copy a single peer between profiles
|
|
191
|
+
agora peers copy bob --from default --to stefan
|
|
192
|
+
```
|
|
193
|
+
|
|
162
194
|
## Relay + REST Mode (Library API)
|
|
163
195
|
|
|
164
196
|
`agora relay` starts WebSocket relay only. For WebSocket + REST together, use `runRelay()`:
|
|
@@ -28,7 +28,7 @@ function initPeerConfig(path) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// src/transport/http.ts
|
|
31
|
-
async function sendToPeer(config, peerPublicKey, type, payload, inReplyTo) {
|
|
31
|
+
async function sendToPeer(config, peerPublicKey, type, payload, inReplyTo, allRecipients) {
|
|
32
32
|
const peer = config.peers.get(peerPublicKey);
|
|
33
33
|
if (!peer) {
|
|
34
34
|
return { ok: false, status: 0, error: "Unknown peer" };
|
|
@@ -43,7 +43,7 @@ async function sendToPeer(config, peerPublicKey, type, payload, inReplyTo) {
|
|
|
43
43
|
payload,
|
|
44
44
|
Date.now(),
|
|
45
45
|
inReplyTo,
|
|
46
|
-
[peerPublicKey]
|
|
46
|
+
allRecipients ?? [peerPublicKey]
|
|
47
47
|
);
|
|
48
48
|
const envelopeJson = JSON.stringify(envelope);
|
|
49
49
|
const envelopeBase64 = Buffer.from(envelopeJson).toString("base64url");
|
|
@@ -122,7 +122,7 @@ function decodeInboundEnvelope(message, knownPeers) {
|
|
|
122
122
|
|
|
123
123
|
// src/transport/relay.ts
|
|
124
124
|
import WebSocket from "ws";
|
|
125
|
-
async function sendViaRelay(config, peerPublicKey, type, payload, inReplyTo) {
|
|
125
|
+
async function sendViaRelay(config, peerPublicKey, type, payload, inReplyTo, allRecipients) {
|
|
126
126
|
if (config.relayClient && config.relayClient.connected()) {
|
|
127
127
|
const envelope = createEnvelope(
|
|
128
128
|
type,
|
|
@@ -131,11 +131,11 @@ async function sendViaRelay(config, peerPublicKey, type, payload, inReplyTo) {
|
|
|
131
131
|
payload,
|
|
132
132
|
Date.now(),
|
|
133
133
|
inReplyTo,
|
|
134
|
-
[peerPublicKey]
|
|
134
|
+
allRecipients ?? [peerPublicKey]
|
|
135
135
|
);
|
|
136
136
|
return config.relayClient.send(peerPublicKey, envelope);
|
|
137
137
|
}
|
|
138
|
-
return new Promise((
|
|
138
|
+
return new Promise((resolve2) => {
|
|
139
139
|
const ws = new WebSocket(config.relayUrl);
|
|
140
140
|
let registered = false;
|
|
141
141
|
let messageSent = false;
|
|
@@ -144,7 +144,7 @@ async function sendViaRelay(config, peerPublicKey, type, payload, inReplyTo) {
|
|
|
144
144
|
if (!resolved) {
|
|
145
145
|
resolved = true;
|
|
146
146
|
clearTimeout(timeout);
|
|
147
|
-
|
|
147
|
+
resolve2(result);
|
|
148
148
|
}
|
|
149
149
|
};
|
|
150
150
|
const timeout = setTimeout(() => {
|
|
@@ -172,7 +172,7 @@ async function sendViaRelay(config, peerPublicKey, type, payload, inReplyTo) {
|
|
|
172
172
|
payload,
|
|
173
173
|
Date.now(),
|
|
174
174
|
inReplyTo,
|
|
175
|
-
[peerPublicKey]
|
|
175
|
+
allRecipients ?? [peerPublicKey]
|
|
176
176
|
);
|
|
177
177
|
const relayMsg = {
|
|
178
178
|
type: "message",
|
|
@@ -206,6 +206,183 @@ async function sendViaRelay(config, peerPublicKey, type, payload, inReplyTo) {
|
|
|
206
206
|
});
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
// src/config.ts
|
|
210
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2, readdirSync, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
211
|
+
import { readFile } from "fs/promises";
|
|
212
|
+
import { resolve, join, dirname } from "path";
|
|
213
|
+
import { homedir } from "os";
|
|
214
|
+
function getDefaultConfigPath() {
|
|
215
|
+
if (process.env.AGORA_CONFIG) {
|
|
216
|
+
return resolve(process.env.AGORA_CONFIG);
|
|
217
|
+
}
|
|
218
|
+
return resolve(homedir(), ".config", "agora", "config.json");
|
|
219
|
+
}
|
|
220
|
+
function parseConfig(config) {
|
|
221
|
+
const rawIdentity = config.identity;
|
|
222
|
+
if (!rawIdentity?.publicKey || !rawIdentity?.privateKey) {
|
|
223
|
+
throw new Error("Invalid config: missing identity.publicKey or identity.privateKey");
|
|
224
|
+
}
|
|
225
|
+
const identity = {
|
|
226
|
+
publicKey: rawIdentity.publicKey,
|
|
227
|
+
privateKey: rawIdentity.privateKey,
|
|
228
|
+
name: typeof rawIdentity.name === "string" ? rawIdentity.name : void 0
|
|
229
|
+
};
|
|
230
|
+
const peers = {};
|
|
231
|
+
if (config.peers && typeof config.peers === "object") {
|
|
232
|
+
for (const entry of Object.values(config.peers)) {
|
|
233
|
+
const peer = entry;
|
|
234
|
+
if (peer && typeof peer.publicKey === "string") {
|
|
235
|
+
peers[peer.publicKey] = {
|
|
236
|
+
publicKey: peer.publicKey,
|
|
237
|
+
url: typeof peer.url === "string" ? peer.url : void 0,
|
|
238
|
+
token: typeof peer.token === "string" ? peer.token : void 0,
|
|
239
|
+
name: typeof peer.name === "string" ? peer.name : void 0
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
let relay;
|
|
245
|
+
const rawRelay = config.relay;
|
|
246
|
+
if (typeof rawRelay === "string") {
|
|
247
|
+
relay = { url: rawRelay, autoConnect: true };
|
|
248
|
+
} else if (rawRelay && typeof rawRelay === "object") {
|
|
249
|
+
const r = rawRelay;
|
|
250
|
+
if (typeof r.url === "string") {
|
|
251
|
+
relay = {
|
|
252
|
+
url: r.url,
|
|
253
|
+
autoConnect: typeof r.autoConnect === "boolean" ? r.autoConnect : true,
|
|
254
|
+
name: typeof r.name === "string" ? r.name : void 0,
|
|
255
|
+
reconnectMaxMs: typeof r.reconnectMaxMs === "number" ? r.reconnectMaxMs : void 0
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
identity,
|
|
261
|
+
peers,
|
|
262
|
+
...relay ? { relay } : {}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function loadAgoraConfig(path) {
|
|
266
|
+
const configPath = path ?? getDefaultConfigPath();
|
|
267
|
+
if (!existsSync2(configPath)) {
|
|
268
|
+
throw new Error(`Config file not found at ${configPath}. Run 'npx @rookdaemon/agora init' first.`);
|
|
269
|
+
}
|
|
270
|
+
const content = readFileSync2(configPath, "utf-8");
|
|
271
|
+
let config;
|
|
272
|
+
try {
|
|
273
|
+
config = JSON.parse(content);
|
|
274
|
+
} catch {
|
|
275
|
+
throw new Error(`Invalid JSON in config file: ${configPath}`);
|
|
276
|
+
}
|
|
277
|
+
return parseConfig(config);
|
|
278
|
+
}
|
|
279
|
+
async function loadAgoraConfigAsync(path) {
|
|
280
|
+
const configPath = path ?? getDefaultConfigPath();
|
|
281
|
+
let content;
|
|
282
|
+
try {
|
|
283
|
+
content = await readFile(configPath, "utf-8");
|
|
284
|
+
} catch (err) {
|
|
285
|
+
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
286
|
+
if (code === "ENOENT") {
|
|
287
|
+
throw new Error(`Config file not found at ${configPath}. Run 'npx @rookdaemon/agora init' first.`);
|
|
288
|
+
}
|
|
289
|
+
throw err;
|
|
290
|
+
}
|
|
291
|
+
let config;
|
|
292
|
+
try {
|
|
293
|
+
config = JSON.parse(content);
|
|
294
|
+
} catch {
|
|
295
|
+
throw new Error(`Invalid JSON in config file: ${configPath}`);
|
|
296
|
+
}
|
|
297
|
+
return parseConfig(config);
|
|
298
|
+
}
|
|
299
|
+
function getConfigDir() {
|
|
300
|
+
if (process.env.AGORA_CONFIG_DIR) {
|
|
301
|
+
return resolve(process.env.AGORA_CONFIG_DIR);
|
|
302
|
+
}
|
|
303
|
+
return resolve(homedir(), ".config", "agora");
|
|
304
|
+
}
|
|
305
|
+
function getProfileConfigPath(profile) {
|
|
306
|
+
if (process.env.AGORA_CONFIG) {
|
|
307
|
+
return resolve(process.env.AGORA_CONFIG);
|
|
308
|
+
}
|
|
309
|
+
const base = getConfigDir();
|
|
310
|
+
if (!profile || profile === "default") {
|
|
311
|
+
return join(base, "config.json");
|
|
312
|
+
}
|
|
313
|
+
return join(base, "profiles", profile, "config.json");
|
|
314
|
+
}
|
|
315
|
+
function listProfiles() {
|
|
316
|
+
const base = getConfigDir();
|
|
317
|
+
const profiles = [];
|
|
318
|
+
if (existsSync2(join(base, "config.json"))) {
|
|
319
|
+
profiles.push("default");
|
|
320
|
+
}
|
|
321
|
+
const profilesDir = join(base, "profiles");
|
|
322
|
+
if (existsSync2(profilesDir)) {
|
|
323
|
+
for (const entry of readdirSync(profilesDir, { withFileTypes: true })) {
|
|
324
|
+
if (entry.isDirectory() && existsSync2(join(profilesDir, entry.name, "config.json"))) {
|
|
325
|
+
profiles.push(entry.name);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return profiles;
|
|
330
|
+
}
|
|
331
|
+
function exportConfig(config, opts = {}) {
|
|
332
|
+
const exported = {
|
|
333
|
+
version: 1,
|
|
334
|
+
peers: Object.fromEntries(
|
|
335
|
+
Object.entries(config.peers).map(([k, v]) => [k, { ...v }])
|
|
336
|
+
)
|
|
337
|
+
};
|
|
338
|
+
if (opts.includeIdentity) {
|
|
339
|
+
exported.identity = { ...config.identity };
|
|
340
|
+
}
|
|
341
|
+
if (config.relay) {
|
|
342
|
+
exported.relay = { ...config.relay };
|
|
343
|
+
}
|
|
344
|
+
return exported;
|
|
345
|
+
}
|
|
346
|
+
function importConfig(target, incoming, opts = {}) {
|
|
347
|
+
const result = {
|
|
348
|
+
peersAdded: [],
|
|
349
|
+
peersSkipped: [],
|
|
350
|
+
identityImported: false,
|
|
351
|
+
relayImported: false
|
|
352
|
+
};
|
|
353
|
+
for (const [key, peer] of Object.entries(incoming.peers)) {
|
|
354
|
+
if (target.peers[key]) {
|
|
355
|
+
result.peersSkipped.push(key);
|
|
356
|
+
} else {
|
|
357
|
+
target.peers[key] = { ...peer };
|
|
358
|
+
result.peersAdded.push(key);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (opts.overwriteIdentity && incoming.identity) {
|
|
362
|
+
target.identity = { ...incoming.identity };
|
|
363
|
+
result.identityImported = true;
|
|
364
|
+
}
|
|
365
|
+
if (opts.overwriteRelay && incoming.relay) {
|
|
366
|
+
target.relay = { ...incoming.relay };
|
|
367
|
+
result.relayImported = true;
|
|
368
|
+
}
|
|
369
|
+
return result;
|
|
370
|
+
}
|
|
371
|
+
function saveAgoraConfig(path, config) {
|
|
372
|
+
const dir = dirname(path);
|
|
373
|
+
if (!existsSync2(dir)) {
|
|
374
|
+
mkdirSync(dir, { recursive: true });
|
|
375
|
+
}
|
|
376
|
+
const raw = {
|
|
377
|
+
identity: config.identity,
|
|
378
|
+
peers: config.peers
|
|
379
|
+
};
|
|
380
|
+
if (config.relay) {
|
|
381
|
+
raw.relay = config.relay;
|
|
382
|
+
}
|
|
383
|
+
writeFileSync2(path, JSON.stringify(raw, null, 2) + "\n", "utf-8");
|
|
384
|
+
}
|
|
385
|
+
|
|
209
386
|
// src/relay/client.ts
|
|
210
387
|
import { EventEmitter } from "events";
|
|
211
388
|
import WebSocket2 from "ws";
|
|
@@ -317,7 +494,7 @@ var RelayClient = class extends EventEmitter {
|
|
|
317
494
|
* Internal: Perform connection
|
|
318
495
|
*/
|
|
319
496
|
async doConnect() {
|
|
320
|
-
return new Promise((
|
|
497
|
+
return new Promise((resolve2, reject) => {
|
|
321
498
|
try {
|
|
322
499
|
this.ws = new WebSocket2(this.config.relayUrl);
|
|
323
500
|
let resolved = false;
|
|
@@ -343,7 +520,7 @@ var RelayClient = class extends EventEmitter {
|
|
|
343
520
|
const msg = JSON.parse(data.toString());
|
|
344
521
|
this.handleMessage(msg);
|
|
345
522
|
if (msg.type === "registered" && !resolved) {
|
|
346
|
-
resolveOnce(() =>
|
|
523
|
+
resolveOnce(() => resolve2());
|
|
347
524
|
}
|
|
348
525
|
} catch (err) {
|
|
349
526
|
this.emit("error", new Error(`Failed to parse message: ${err instanceof Error ? err.message : String(err)}`));
|
|
@@ -523,7 +700,7 @@ var PeerDiscoveryService = class extends EventEmitter2 {
|
|
|
523
700
|
if (!result.ok) {
|
|
524
701
|
throw new Error(`Failed to send peer list request: ${result.error}`);
|
|
525
702
|
}
|
|
526
|
-
return new Promise((
|
|
703
|
+
return new Promise((resolve2, reject) => {
|
|
527
704
|
const timeout = setTimeout(() => {
|
|
528
705
|
cleanup();
|
|
529
706
|
reject(new Error("Peer list request timed out"));
|
|
@@ -531,7 +708,7 @@ var PeerDiscoveryService = class extends EventEmitter2 {
|
|
|
531
708
|
const messageHandler = (responseEnvelope, from) => {
|
|
532
709
|
if (responseEnvelope.type === "peer_list_response" && responseEnvelope.inReplyTo === envelope.id && from === this.config.relayPublicKey) {
|
|
533
710
|
cleanup();
|
|
534
|
-
|
|
711
|
+
resolve2(responseEnvelope.payload);
|
|
535
712
|
}
|
|
536
713
|
};
|
|
537
714
|
const cleanup = () => {
|
|
@@ -858,7 +1035,7 @@ function validateRevealRecord(record) {
|
|
|
858
1035
|
|
|
859
1036
|
// src/reputation/store.ts
|
|
860
1037
|
import { promises as fs } from "fs";
|
|
861
|
-
import { dirname } from "path";
|
|
1038
|
+
import { dirname as dirname2 } from "path";
|
|
862
1039
|
var ReputationStore = class {
|
|
863
1040
|
filePath;
|
|
864
1041
|
verifications = /* @__PURE__ */ new Map();
|
|
@@ -929,7 +1106,7 @@ var ReputationStore = class {
|
|
|
929
1106
|
* Append a record to the JSONL file
|
|
930
1107
|
*/
|
|
931
1108
|
async appendToFile(record) {
|
|
932
|
-
await fs.mkdir(
|
|
1109
|
+
await fs.mkdir(dirname2(this.filePath), { recursive: true });
|
|
933
1110
|
const line = JSON.stringify(record) + "\n";
|
|
934
1111
|
await fs.appendFile(this.filePath, line, "utf-8");
|
|
935
1112
|
}
|
|
@@ -1272,6 +1449,15 @@ export {
|
|
|
1272
1449
|
sendToPeer,
|
|
1273
1450
|
decodeInboundEnvelope,
|
|
1274
1451
|
sendViaRelay,
|
|
1452
|
+
getDefaultConfigPath,
|
|
1453
|
+
loadAgoraConfig,
|
|
1454
|
+
loadAgoraConfigAsync,
|
|
1455
|
+
getConfigDir,
|
|
1456
|
+
getProfileConfigPath,
|
|
1457
|
+
listProfiles,
|
|
1458
|
+
exportConfig,
|
|
1459
|
+
importConfig,
|
|
1460
|
+
saveAgoraConfig,
|
|
1275
1461
|
RelayClient,
|
|
1276
1462
|
PeerDiscoveryService,
|
|
1277
1463
|
DEFAULT_BOOTSTRAP_RELAYS,
|
|
@@ -1305,4 +1491,4 @@ export {
|
|
|
1305
1491
|
computeTrustScores,
|
|
1306
1492
|
computeAllTrustScores
|
|
1307
1493
|
};
|
|
1308
|
-
//# sourceMappingURL=chunk-
|
|
1494
|
+
//# sourceMappingURL=chunk-UDRIP62M.js.map
|