@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 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
- - `agora config set ...` style config mutation command.
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((resolve) => {
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
- resolve(result);
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((resolve, reject) => {
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(() => resolve());
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((resolve, reject) => {
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
- resolve(responseEnvelope.payload);
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(dirname(this.filePath), { recursive: true });
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-P5EN45ZV.js.map
1494
+ //# sourceMappingURL=chunk-UDRIP62M.js.map