@a-company/paradigm 3.44.0 → 3.46.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.
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+
3
+ // ../paradigm-mcp/src/utils/symphony-peers.ts
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ import * as os from "os";
7
+ import * as crypto from "crypto";
8
+ var SCORE_DIR = path.join(os.homedir(), ".paradigm", "score");
9
+ var PEERS_FILE = path.join(SCORE_DIR, "peers.json");
10
+ var PAIRING_TTL_MS = 5 * 60 * 1e3;
11
+ function loadPeers() {
12
+ try {
13
+ if (!fs.existsSync(PEERS_FILE)) return [];
14
+ const content = fs.readFileSync(PEERS_FILE, "utf-8");
15
+ const parsed = JSON.parse(content);
16
+ if (!Array.isArray(parsed)) return [];
17
+ return parsed;
18
+ } catch {
19
+ return [];
20
+ }
21
+ }
22
+ function savePeers(peers) {
23
+ if (!fs.existsSync(SCORE_DIR)) {
24
+ fs.mkdirSync(SCORE_DIR, { recursive: true });
25
+ }
26
+ fs.writeFileSync(PEERS_FILE, JSON.stringify(peers, null, 2), { mode: 384 });
27
+ }
28
+ function findPeer(id) {
29
+ const peers = loadPeers();
30
+ return peers.find((p) => p.id === id) ?? null;
31
+ }
32
+ function addPeer(peer) {
33
+ const peers = loadPeers();
34
+ const idx = peers.findIndex((p) => p.id === peer.id);
35
+ if (idx >= 0) {
36
+ peers[idx] = peer;
37
+ } else {
38
+ peers.push(peer);
39
+ }
40
+ savePeers(peers);
41
+ }
42
+ function revokePeer(id) {
43
+ const peers = loadPeers();
44
+ const peer = peers.find((p) => p.id === id);
45
+ if (!peer) return false;
46
+ peer.revoked = true;
47
+ savePeers(peers);
48
+ return true;
49
+ }
50
+ function forgetAllPeers() {
51
+ const peers = loadPeers();
52
+ const count = peers.length;
53
+ if (fs.existsSync(PEERS_FILE)) {
54
+ fs.unlinkSync(PEERS_FILE);
55
+ }
56
+ return count;
57
+ }
58
+ function updatePeerLastSeen(id) {
59
+ const peers = loadPeers();
60
+ const peer = peers.find((p) => p.id === id);
61
+ if (!peer) return;
62
+ peer.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
63
+ savePeers(peers);
64
+ }
65
+ function updatePeerAgents(id, agents) {
66
+ const peers = loadPeers();
67
+ const peer = peers.find((p) => p.id === id);
68
+ if (!peer) return;
69
+ peer.agents = agents;
70
+ savePeers(peers);
71
+ }
72
+ function generatePairing() {
73
+ const sharedSecret = crypto.randomBytes(32).toString("hex");
74
+ const code = (parseInt(crypto.randomBytes(3).toString("hex"), 16) % 1e6).toString().padStart(6, "0");
75
+ const codeHash = crypto.createHash("sha256").update(code).digest("hex");
76
+ return {
77
+ code,
78
+ codeHash,
79
+ sharedSecret,
80
+ createdAt: Date.now()
81
+ };
82
+ }
83
+ function verifyPairingCode(state, code) {
84
+ if (Date.now() - state.createdAt > PAIRING_TTL_MS) {
85
+ return false;
86
+ }
87
+ const hash = crypto.createHash("sha256").update(code).digest("hex");
88
+ return hash === state.codeHash;
89
+ }
90
+ function computeHmacProof(challenge, codeHash) {
91
+ return crypto.createHmac("sha256", codeHash).update(challenge).digest("hex");
92
+ }
93
+ function verifyHmacProof(challenge, codeHash, proof) {
94
+ const expected = computeHmacProof(challenge, codeHash);
95
+ if (expected.length !== proof.length) return false;
96
+ try {
97
+ return crypto.timingSafeEqual(
98
+ Buffer.from(expected, "hex"),
99
+ Buffer.from(proof, "hex")
100
+ );
101
+ } catch {
102
+ return false;
103
+ }
104
+ }
105
+
106
+ export {
107
+ PEERS_FILE,
108
+ PAIRING_TTL_MS,
109
+ loadPeers,
110
+ savePeers,
111
+ findPeer,
112
+ addPeer,
113
+ revokePeer,
114
+ forgetAllPeers,
115
+ updatePeerLastSeen,
116
+ updatePeerAgents,
117
+ generatePairing,
118
+ verifyPairingCode,
119
+ computeHmacProof,
120
+ verifyHmacProof
121
+ };
@@ -246,6 +246,9 @@ function appendToOutbox(agentId, message) {
246
246
  ensureAgentDir(agentId);
247
247
  appendJsonlLine(outboxPath(agentId), message);
248
248
  }
249
+ function readOutbox(agentId) {
250
+ return readJsonlFile(outboxPath(agentId));
251
+ }
249
252
  function acknowledgeMessages(agentId, lastMessageId) {
250
253
  const filePath = ackPath(agentId);
251
254
  ensureAgentDir(agentId);
@@ -596,7 +599,9 @@ export {
596
599
  markAgentPollTime,
597
600
  isAgentAsleep,
598
601
  discoverClaudeCodeSessions,
602
+ appendToInbox,
599
603
  readInbox,
604
+ readOutbox,
600
605
  acknowledgeMessages,
601
606
  garbageCollect,
602
607
  createThread,
package/dist/index.js CHANGED
@@ -710,7 +710,7 @@ loreCmd.option("-p, --port <port>", "Port to run on", "3840").option("--no-open"
710
710
  await loreServeCommand(void 0, options);
711
711
  });
712
712
  program.command("serve").description("Launch Paradigm Platform \u2014 unified development management UI").option("-p, --port <port>", "Port to run on", "3850").option("--no-open", "Don't open browser automatically").option("--sections <list>", "Comma-separated sections to enable (e.g., lore,graph,git)").action(async (options) => {
713
- const { serveCommand } = await import("./serve-JVXSRSUB.js");
713
+ const { serveCommand } = await import("./serve-KKEHE44G.js");
714
714
  await serveCommand(options);
715
715
  });
716
716
  var graphCmd = program.command("graph").description("Interactive symbol relationship graph").argument("[path]", "Project directory", void 0).option("-p, --port <port>", "Port to run on", "3841").option("--no-open", "Don't open browser automatically").action(async (path2, options) => {
@@ -835,71 +835,92 @@ pipelineCmd.action(async () => {
835
835
  });
836
836
  var symphonyCmd = program.command("symphony").description("Symphony \u2014 agent-to-agent messaging for multi-session collaboration");
837
837
  symphonyCmd.command("join").description("Join this session to the Symphony network").option("--remote <ip>", "Connect to remote Symphony server").action(async (options) => {
838
- const { symphonyJoinCommand } = await import("./symphony-EYRGGVNE.js");
838
+ const { symphonyJoinCommand } = await import("./symphony-6K3HD7AW.js");
839
839
  await symphonyJoinCommand(options);
840
840
  });
841
841
  symphonyCmd.command("leave").description("Remove this session from the Symphony network").action(async () => {
842
- const { symphonyLeaveCommand } = await import("./symphony-EYRGGVNE.js");
842
+ const { symphonyLeaveCommand } = await import("./symphony-6K3HD7AW.js");
843
843
  await symphonyLeaveCommand();
844
844
  });
845
845
  symphonyCmd.command("whoami").description("Show this agent's identity and linked peers").action(async () => {
846
- const { symphonyWhoamiCommand } = await import("./symphony-EYRGGVNE.js");
846
+ const { symphonyWhoamiCommand } = await import("./symphony-6K3HD7AW.js");
847
847
  await symphonyWhoamiCommand();
848
848
  });
849
849
  symphonyCmd.command("list").alias("ls").description("List all joined agents").option("--json", "Output as JSON").action(async (options) => {
850
- const { symphonyListCommand } = await import("./symphony-EYRGGVNE.js");
850
+ const { symphonyListCommand } = await import("./symphony-6K3HD7AW.js");
851
851
  await symphonyListCommand(options);
852
852
  });
853
853
  symphonyCmd.command("send <message>").description("Send a note to agents").option("--to <agent>", "Send to specific agent (omit for broadcast)").option("--thread <id>", "Reply to existing thread").action(async (message, options) => {
854
- const { symphonySendCommand } = await import("./symphony-EYRGGVNE.js");
854
+ const { symphonySendCommand } = await import("./symphony-6K3HD7AW.js");
855
855
  await symphonySendCommand(message, options);
856
856
  });
857
857
  symphonyCmd.command("read").description("Show unread notes").action(async () => {
858
- const { symphonyReadCommand } = await import("./symphony-EYRGGVNE.js");
858
+ const { symphonyReadCommand } = await import("./symphony-6K3HD7AW.js");
859
859
  await symphonyReadCommand();
860
860
  });
861
861
  symphonyCmd.command("inbox").description("Show unread notes (alias for read)").action(async () => {
862
- const { symphonyReadCommand } = await import("./symphony-EYRGGVNE.js");
862
+ const { symphonyReadCommand } = await import("./symphony-6K3HD7AW.js");
863
863
  await symphonyReadCommand();
864
864
  });
865
865
  symphonyCmd.command("threads").description("List all threads").option("--json", "Output as JSON").action(async (options) => {
866
- const { symphonyThreadsCommand } = await import("./symphony-EYRGGVNE.js");
866
+ const { symphonyThreadsCommand } = await import("./symphony-6K3HD7AW.js");
867
867
  await symphonyThreadsCommand(options);
868
868
  });
869
869
  symphonyCmd.command("thread <id>").description("Show full thread conversation").action(async (id) => {
870
- const { symphonyThreadCommand } = await import("./symphony-EYRGGVNE.js");
870
+ const { symphonyThreadCommand } = await import("./symphony-6K3HD7AW.js");
871
871
  await symphonyThreadCommand(id);
872
872
  });
873
873
  symphonyCmd.command("resolve <id>").description("Mark a thread as resolved").option("--decision <text>", "Decision text to record").action(async (id, options) => {
874
- const { symphonyResolveCommand } = await import("./symphony-EYRGGVNE.js");
874
+ const { symphonyResolveCommand } = await import("./symphony-6K3HD7AW.js");
875
875
  await symphonyResolveCommand(id, options);
876
876
  });
877
877
  symphonyCmd.command("status").description("Show Symphony network status").option("--json", "Output as JSON").action(async (options) => {
878
- const { symphonyStatusCommand } = await import("./symphony-EYRGGVNE.js");
878
+ const { symphonyStatusCommand } = await import("./symphony-6K3HD7AW.js");
879
879
  await symphonyStatusCommand(options);
880
880
  });
881
- symphonyCmd.command("serve").description("Start TCP server for remote Symphony linking").option("--port <port>", "Port to listen on", "3939").action(async (options) => {
882
- const { symphonyServeCommand } = await import("./symphony-EYRGGVNE.js");
881
+ symphonyCmd.command("serve").description("Start Symphony relay server for cross-machine networking").option("--port <port>", "Port to listen on", "3939").option("--public", "Show connection string for internet access").action(async (options) => {
882
+ const { symphonyServeCommand } = await import("./symphony-6K3HD7AW.js");
883
883
  await symphonyServeCommand(options);
884
884
  });
885
+ var peersCmd = symphonyCmd.command("peers").description("Manage trusted remote peers");
886
+ peersCmd.command("list", { isDefault: true }).description("List trusted peers and their agents").option("--json", "Output as JSON").action(async (options) => {
887
+ const { symphonyPeersCommand } = await import("./peers-RFQCWVLV.js");
888
+ await symphonyPeersCommand(options);
889
+ });
890
+ peersCmd.command("revoke <id>").description("Revoke trust for a peer (disconnects immediately)").action(async (id) => {
891
+ const { symphonyPeersRevokeCommand } = await import("./peers-RFQCWVLV.js");
892
+ await symphonyPeersRevokeCommand(id);
893
+ });
894
+ peersCmd.command("forget").description("Clear all peer trust records").option("--force", "Skip confirmation").action(async (options) => {
895
+ const { symphonyPeersForgetCommand } = await import("./peers-RFQCWVLV.js");
896
+ await symphonyPeersForgetCommand(options);
897
+ });
898
+ peersCmd.action(async () => {
899
+ const { symphonyPeersCommand } = await import("./peers-RFQCWVLV.js");
900
+ await symphonyPeersCommand({});
901
+ });
885
902
  symphonyCmd.command("request <file>").description("Request a file from another agent").option("--from <agent>", "Agent to request from").option("--reason <text>", "Why this file is needed").action(async (file, options) => {
886
- const { symphonyRequestCommand } = await import("./symphony-EYRGGVNE.js");
903
+ const { symphonyRequestCommand } = await import("./symphony-6K3HD7AW.js");
887
904
  await symphonyRequestCommand(file, options);
888
905
  });
889
906
  symphonyCmd.command("requests").description("List pending file requests").action(async () => {
890
- const { symphonyRequestsCommand } = await import("./symphony-EYRGGVNE.js");
907
+ const { symphonyRequestsCommand } = await import("./symphony-6K3HD7AW.js");
891
908
  await symphonyRequestsCommand();
892
909
  });
893
910
  symphonyCmd.command("approve <id>").description("Approve a file request").option("--redact", "Strip sensitive lines before sending").action(async (id, options) => {
894
- const { symphonyApproveCommand } = await import("./symphony-EYRGGVNE.js");
911
+ const { symphonyApproveCommand } = await import("./symphony-6K3HD7AW.js");
895
912
  await symphonyApproveCommand(id, options);
896
913
  });
897
914
  symphonyCmd.command("deny <id>").description("Deny a file request").option("--reason <text>", "Reason for denial").action(async (id, options) => {
898
- const { symphonyDenyCommand } = await import("./symphony-EYRGGVNE.js");
915
+ const { symphonyDenyCommand } = await import("./symphony-6K3HD7AW.js");
899
916
  await symphonyDenyCommand(id, options);
900
917
  });
918
+ symphonyCmd.command("watch").description("Watch inbox in real-time \u2014 zero AI tokens, pure file monitoring").option("--interval <ms>", "Poll interval in milliseconds (default: 2000)").option("--thread <id>", "Only show messages from this thread").option("--quiet", "Minimal output \u2014 messages only, no header").action(async (options) => {
919
+ const { symphonyWatchCommand } = await import("./symphony-6K3HD7AW.js");
920
+ await symphonyWatchCommand(options);
921
+ });
901
922
  symphonyCmd.action(async () => {
902
- const { symphonyStatusCommand } = await import("./symphony-EYRGGVNE.js");
923
+ const { symphonyStatusCommand } = await import("./symphony-6K3HD7AW.js");
903
924
  await symphonyStatusCommand({});
904
925
  });
905
926
  program.parse();
package/dist/mcp.js CHANGED
@@ -14584,6 +14584,35 @@ function outboxPath(agentId) {
14584
14584
  function ackPath(agentId) {
14585
14585
  return path28.join(getAgentDir(agentId), "ack.json");
14586
14586
  }
14587
+ function peekInbox(agentId) {
14588
+ const filePath = inboxPath(agentId);
14589
+ if (!fs26.existsSync(filePath)) return { hasNew: false, inboxSize: 0 };
14590
+ const stat = fs26.statSync(filePath);
14591
+ const inboxSize = stat.size;
14592
+ const ack = readAck(agentId);
14593
+ if (!ack) {
14594
+ return { hasNew: inboxSize > 0, inboxSize };
14595
+ }
14596
+ const ackSizePath = path28.join(getAgentDir(agentId), "ack-size.json");
14597
+ if (fs26.existsSync(ackSizePath)) {
14598
+ try {
14599
+ const ackSize = JSON.parse(fs26.readFileSync(ackSizePath, "utf-8"));
14600
+ return { hasNew: inboxSize > (ackSize.size || 0), inboxSize };
14601
+ } catch {
14602
+ }
14603
+ }
14604
+ return { hasNew: inboxSize > 0, inboxSize };
14605
+ }
14606
+ function recordAckSize(agentId) {
14607
+ const filePath = inboxPath(agentId);
14608
+ const ackSizePath = path28.join(getAgentDir(agentId), "ack-size.json");
14609
+ try {
14610
+ const size = fs26.existsSync(filePath) ? fs26.statSync(filePath).size : 0;
14611
+ ensureAgentDir(agentId);
14612
+ fs26.writeFileSync(ackSizePath, JSON.stringify({ size }), "utf-8");
14613
+ } catch {
14614
+ }
14615
+ }
14587
14616
  function appendToInbox(agentId, message) {
14588
14617
  ensureAgentDir(agentId);
14589
14618
  appendJsonlLine(inboxPath(agentId), message);
@@ -14942,9 +14971,26 @@ function isProcessAlive2(pid) {
14942
14971
  // ../paradigm-mcp/src/tools/symphony.ts
14943
14972
  function getSymphonyToolsList() {
14944
14973
  return [
14974
+ {
14975
+ name: "paradigm_symphony_peek",
14976
+ description: "Ultra-cheap inbox check \u2014 file stat only, no parsing. Returns { hasNew: true/false }. Use with /loop 10s for near-free monitoring. When hasNew is true, call paradigm_symphony_poll to read messages. ~15 tokens.",
14977
+ inputSchema: {
14978
+ type: "object",
14979
+ properties: {
14980
+ status: {
14981
+ type: "string",
14982
+ description: "Short status blurb (same as poll). Updates heartbeat."
14983
+ }
14984
+ }
14985
+ },
14986
+ annotations: {
14987
+ readOnlyHint: true,
14988
+ destructiveHint: false
14989
+ }
14990
+ },
14945
14991
  {
14946
14992
  name: "paradigm_symphony_poll",
14947
- description: "Poll inbox for new notes. Call via /loop for continuous agent messaging. Returns unread notes formatted as markdown with thread context and suggested actions. Updates heartbeat and optional status blurb. ~200 tokens.",
14993
+ description: "Read inbox notes and process them. Call when paradigm_symphony_peek returns hasNew: true, or directly via /loop for continuous messaging. Returns unread notes formatted as markdown with thread context and suggested actions. Updates heartbeat and optional status blurb. ~200 tokens.",
14948
14994
  inputSchema: {
14949
14995
  type: "object",
14950
14996
  properties: {
@@ -15121,6 +15167,16 @@ async function handleSymphonyTool(name, args, ctx) {
15121
15167
  identity = registerAgent(ctx.rootDir);
15122
15168
  }
15123
15169
  switch (name) {
15170
+ case "paradigm_symphony_peek": {
15171
+ const peekStatus = args.status;
15172
+ markAgentPollTime(identity.id, peekStatus);
15173
+ const { hasNew } = peekInbox(identity.id);
15174
+ const pendingRequests = hasNew ? listFileRequests("pending").length : 0;
15175
+ return {
15176
+ handled: true,
15177
+ text: JSON.stringify(hasNew ? { hasNew: true, pendingFileRequests: pendingRequests, action: "call paradigm_symphony_poll to read" } : { hasNew: false })
15178
+ };
15179
+ }
15124
15180
  case "paradigm_symphony_poll": {
15125
15181
  cleanStaleAgents();
15126
15182
  expireOldRequests();
@@ -15130,6 +15186,7 @@ async function handleSymphonyTool(name, args, ctx) {
15130
15186
  if (messages.length > 0) {
15131
15187
  const lastId = messages[messages.length - 1].id;
15132
15188
  acknowledgeMessages(identity.id, lastId);
15189
+ recordAckSize(identity.id);
15133
15190
  }
15134
15191
  garbageCollect(identity.id);
15135
15192
  const pendingRequests = listFileRequests("pending");
@@ -15236,6 +15293,18 @@ async function handleSymphonyTool(name, args, ctx) {
15236
15293
  const threads = listThreads("active");
15237
15294
  const unread = readInbox(identity.id);
15238
15295
  const pendingRequests = listFileRequests("pending");
15296
+ let peers = [];
15297
+ try {
15298
+ const { loadPeers } = await import("./symphony-peers-APOGJPF4.js");
15299
+ const allPeers = loadPeers();
15300
+ peers = allPeers.filter((p) => !p.revoked).map((p) => ({
15301
+ id: p.id,
15302
+ address: p.address,
15303
+ agents: p.agents?.length ?? 0,
15304
+ lastSeen: p.lastSeen
15305
+ }));
15306
+ } catch {
15307
+ }
15239
15308
  return {
15240
15309
  handled: true,
15241
15310
  text: JSON.stringify({
@@ -15253,6 +15322,7 @@ async function handleSymphonyTool(name, args, ctx) {
15253
15322
  lastPoll: a.lastPoll,
15254
15323
  statusBlurb: a.statusBlurb
15255
15324
  })),
15325
+ peers,
15256
15326
  activeThreads: threads.map((t) => ({
15257
15327
  id: t.id,
15258
15328
  topic: t.topic,
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ forgetAllPeers,
4
+ loadPeers,
5
+ revokePeer
6
+ } from "./chunk-KVDYJLTC.js";
7
+ import "./chunk-ZXMDA7VB.js";
8
+
9
+ // src/commands/symphony/peers.ts
10
+ import chalk from "chalk";
11
+ async function symphonyPeersCommand(options) {
12
+ const peers = loadPeers();
13
+ if (options.json) {
14
+ console.log(JSON.stringify(peers, null, 2));
15
+ return;
16
+ }
17
+ if (peers.length === 0) {
18
+ console.log(chalk.yellow('No trusted peers. Run "paradigm symphony serve" to accept connections.'));
19
+ return;
20
+ }
21
+ console.log(chalk.cyan(`
22
+ Trusted Peers (${peers.length})
23
+ `));
24
+ console.log(chalk.gray(` ${"PEER ID".padEnd(20)} ${"ADDRESS".padEnd(22)} ${"STATUS".padEnd(10)} ${"AGENTS".padEnd(8)} LAST SEEN`));
25
+ console.log(chalk.gray(` ${"\u2500".repeat(20)} ${"\u2500".repeat(22)} ${"\u2500".repeat(10)} ${"\u2500".repeat(8)} ${"\u2500".repeat(20)}`));
26
+ for (const peer of peers) {
27
+ const status = peer.revoked ? chalk.red("revoked") : chalk.green("trusted");
28
+ const agentCount = (peer.agents || []).length.toString();
29
+ const lastSeen = peer.lastSeen ? formatRelativeTime(peer.lastSeen) : chalk.gray("never");
30
+ console.log(` ${chalk.white(peer.id.padEnd(20))} ${peer.address.padEnd(22)} ${status.padEnd(10)} ${agentCount.padEnd(8)} ${lastSeen}`);
31
+ if (peer.agents && peer.agents.length > 0) {
32
+ for (const agent of peer.agents) {
33
+ const agentStatus = agent.status === "awake" ? chalk.green("awake") : chalk.yellow("asleep");
34
+ console.log(chalk.gray(` \u2514 ${agent.id} [${agentStatus}]`));
35
+ }
36
+ }
37
+ }
38
+ console.log();
39
+ }
40
+ async function symphonyPeersRevokeCommand(peerId) {
41
+ const success = revokePeer(peerId);
42
+ if (success) {
43
+ console.log(chalk.green(`\u2713 Revoked peer ${chalk.bold(peerId)}`));
44
+ console.log(chalk.gray(" Peer will be disconnected and cannot reconnect until re-paired."));
45
+ } else {
46
+ console.log(chalk.red(`Peer "${peerId}" not found.`));
47
+ const peers = loadPeers();
48
+ if (peers.length > 0) {
49
+ console.log(chalk.gray("\n Available peers:"));
50
+ for (const p of peers) {
51
+ console.log(chalk.gray(` ${p.id} (${p.address})`));
52
+ }
53
+ }
54
+ }
55
+ }
56
+ async function symphonyPeersForgetCommand(options) {
57
+ const peers = loadPeers();
58
+ if (peers.length === 0) {
59
+ console.log(chalk.yellow("No peers to forget."));
60
+ return;
61
+ }
62
+ if (!options.force) {
63
+ console.log(chalk.yellow(`This will remove all ${peers.length} trusted peer(s). Use --force to confirm.`));
64
+ return;
65
+ }
66
+ const count = forgetAllPeers();
67
+ console.log(chalk.green(`\u2713 Forgot ${count} peer${count !== 1 ? "s" : ""}`));
68
+ console.log(chalk.gray(" All peer trust records deleted. Re-pairing required for remote connections."));
69
+ }
70
+ function formatRelativeTime(isoDate) {
71
+ const diff = Date.now() - new Date(isoDate).getTime();
72
+ const seconds = Math.floor(diff / 1e3);
73
+ if (seconds < 60) return "just now";
74
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
75
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
76
+ return `${Math.floor(seconds / 86400)}d ago`;
77
+ }
78
+ export {
79
+ symphonyPeersCommand,
80
+ symphonyPeersForgetCommand,
81
+ symphonyPeersRevokeCommand
82
+ };
@@ -857,7 +857,7 @@ async function startPlatformServer(options) {
857
857
  }
858
858
  if (sections.has("symphony")) {
859
859
  try {
860
- const { createSymphonyRouter } = await import("./symphony-QWOEKZMC.js");
860
+ const { createSymphonyRouter } = await import("./symphony-YCHBYN3E.js");
861
861
  app.use("/api/symphony", createSymphonyRouter(options.projectDir, wsContext.broadcast));
862
862
  log.component("platform-server").success("Symphony routes mounted");
863
863
  } catch (err) {
@@ -10,7 +10,7 @@ async function serveCommand(options) {
10
10
  const sections = options.sections ? options.sections.split(",").map((s) => s.trim()) : void 0;
11
11
  console.log(chalk.cyan("\n Starting Paradigm Platform...\n"));
12
12
  try {
13
- const { startPlatformServer } = await import("./platform-server-KHL6ZPPN.js");
13
+ const { startPlatformServer } = await import("./platform-server-H7Y6Q7O4.js");
14
14
  await startPlatformServer({ port, projectDir, open: shouldOpen, sections });
15
15
  console.log(chalk.green(` Platform running at ${chalk.bold(`http://localhost:${port}`)}`));
16
16
  console.log(chalk.gray(" Press Ctrl+C to stop\n"));