@marshell/cli 0.7.1 → 0.7.2
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/dist/bridge.js +2 -0
- package/dist/cli.js +36 -0
- package/dist/relay-cron.js +79 -0
- package/dist/relay-state.js +44 -0
- package/package.json +1 -1
- package/scripts/relay.mjs +1 -1
package/dist/bridge.js
CHANGED
|
@@ -8,6 +8,7 @@ const node_fs_1 = require("node:fs");
|
|
|
8
8
|
const node_path_1 = require("node:path");
|
|
9
9
|
const network_1 = require("./network");
|
|
10
10
|
const pending_1 = require("./pending");
|
|
11
|
+
const relay_state_1 = require("./relay-state");
|
|
11
12
|
const DEFAULT_REPLY_TIMEOUT_MS = 120_000;
|
|
12
13
|
const GREETING_COOLDOWN_MS = 120_000;
|
|
13
14
|
const ECHO_WINDOW_MS = 60_000;
|
|
@@ -408,6 +409,7 @@ async function handleInbound(networkUrl, msg, options) {
|
|
|
408
409
|
if (pending) {
|
|
409
410
|
await (0, pending_1.clearPending)(msg.from);
|
|
410
411
|
}
|
|
412
|
+
await (0, relay_state_1.markRelayed)([msg.id]);
|
|
411
413
|
}
|
|
412
414
|
catch (error) {
|
|
413
415
|
const message = error instanceof Error ? error.message : String(error);
|
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ const bridge_1 = require("./bridge");
|
|
|
6
6
|
const config_1 = require("./config");
|
|
7
7
|
const network_1 = require("./network");
|
|
8
8
|
const pending_1 = require("./pending");
|
|
9
|
+
const relay_cron_1 = require("./relay-cron");
|
|
9
10
|
function printHelp() {
|
|
10
11
|
const message = [
|
|
11
12
|
"marshell - agent messaging bridge",
|
|
@@ -23,6 +24,7 @@ function printHelp() {
|
|
|
23
24
|
" marshell inbox [--json] [--wait <seconds>] [--from <name>]",
|
|
24
25
|
" marshell history [--with <name>] [--limit <n>] [--json]",
|
|
25
26
|
" marshell listen [--json] [--notify <cmd>] (deliver-only listener)",
|
|
27
|
+
" marshell relay cron [--include-untracked] [--json]",
|
|
26
28
|
" marshell pending list|clear [--peer <name>] [--json]",
|
|
27
29
|
" marshell --help",
|
|
28
30
|
"",
|
|
@@ -365,6 +367,36 @@ async function cmdInbox(args) {
|
|
|
365
367
|
process.stdout.write(`[${msg.from}] ${msg.text}\n`);
|
|
366
368
|
}
|
|
367
369
|
}
|
|
370
|
+
async function cmdRelay(args) {
|
|
371
|
+
const sub = args[0];
|
|
372
|
+
if (sub !== "cron") {
|
|
373
|
+
printError("Usage: marshell relay cron [--include-untracked] [--json]");
|
|
374
|
+
}
|
|
375
|
+
const json = hasFlag(args, "--json");
|
|
376
|
+
const pendingOnly = !hasFlag(args, "--include-untracked");
|
|
377
|
+
const config = await (0, config_1.readConfig)();
|
|
378
|
+
const networkUrl = (0, config_1.getNetworkUrl)(config);
|
|
379
|
+
const result = await (0, relay_cron_1.runRelayCron)({ networkUrl, pendingOnly });
|
|
380
|
+
if (json) {
|
|
381
|
+
printJson({
|
|
382
|
+
...result,
|
|
383
|
+
formatted: (0, relay_cron_1.formatRelayOutput)(result.relayed),
|
|
384
|
+
});
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (result.message && result.relayed.length === 0) {
|
|
388
|
+
process.stdout.write(`${result.message}\n`);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (result.relayed.length === 0) {
|
|
392
|
+
process.stdout.write("Nothing new to relay.\n");
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (result.pending_count === 0 && !pendingOnly) {
|
|
396
|
+
process.stdout.write(`${result.relayed.length} new inbound message(s):\n\n`);
|
|
397
|
+
}
|
|
398
|
+
process.stdout.write(`${(0, relay_cron_1.formatRelayOutput)(result.relayed)}\n`);
|
|
399
|
+
}
|
|
368
400
|
async function cmdPending(args) {
|
|
369
401
|
const json = hasFlag(args, "--json");
|
|
370
402
|
const sub = args[0];
|
|
@@ -473,6 +505,10 @@ async function main() {
|
|
|
473
505
|
await cmdListen(args.slice(1));
|
|
474
506
|
return;
|
|
475
507
|
}
|
|
508
|
+
if (args[0] === "relay") {
|
|
509
|
+
await cmdRelay(args.slice(1));
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
476
512
|
if (args[0] === "pending") {
|
|
477
513
|
await cmdPending(args.slice(1));
|
|
478
514
|
return;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatRelayOutput = formatRelayOutput;
|
|
4
|
+
exports.runRelayCron = runRelayCron;
|
|
5
|
+
const pending_1 = require("./pending");
|
|
6
|
+
const network_1 = require("./network");
|
|
7
|
+
const relay_state_1 = require("./relay-state");
|
|
8
|
+
function formatItem(item) {
|
|
9
|
+
if (item.kind === "reply" && item.context) {
|
|
10
|
+
return `Reply from ${item.from} (re: ${item.context}):\n${item.text}`;
|
|
11
|
+
}
|
|
12
|
+
return `New from ${item.from}:\n${item.text}`;
|
|
13
|
+
}
|
|
14
|
+
function formatRelayOutput(items) {
|
|
15
|
+
if (items.length === 0)
|
|
16
|
+
return "";
|
|
17
|
+
return items.map(formatItem).join("\n\n---\n\n");
|
|
18
|
+
}
|
|
19
|
+
async function runRelayCron(options) {
|
|
20
|
+
const pending = await (0, pending_1.listPending)();
|
|
21
|
+
const pendingMap = new Map(pending.map((p) => [p.peer.toLowerCase(), p]));
|
|
22
|
+
if (options.pendingOnly && pending.length === 0) {
|
|
23
|
+
return {
|
|
24
|
+
pending_count: 0,
|
|
25
|
+
relayed: [],
|
|
26
|
+
skipped_relayed: 0,
|
|
27
|
+
message: "No pending tracked sends — list is empty.",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const inbox = await (0, network_1.fetchInbox)(options.networkUrl, { peek: true });
|
|
31
|
+
if (inbox.kind === "error") {
|
|
32
|
+
throw new Error(inbox.message);
|
|
33
|
+
}
|
|
34
|
+
const unrelayed = await (0, relay_state_1.filterUnrelayed)(inbox.messages);
|
|
35
|
+
const skippedRelayed = inbox.messages.length - unrelayed.length;
|
|
36
|
+
const items = [];
|
|
37
|
+
const toAck = [];
|
|
38
|
+
const peersToClear = new Set();
|
|
39
|
+
for (const msg of unrelayed) {
|
|
40
|
+
const peer = msg.from.toLowerCase();
|
|
41
|
+
const tracked = pendingMap.get(peer);
|
|
42
|
+
if (tracked) {
|
|
43
|
+
items.push(buildItem(msg, tracked, "reply"));
|
|
44
|
+
toAck.push(msg.id);
|
|
45
|
+
peersToClear.add(peer);
|
|
46
|
+
}
|
|
47
|
+
else if (!options.pendingOnly) {
|
|
48
|
+
items.push(buildItem(msg, null, "new"));
|
|
49
|
+
toAck.push(msg.id);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (toAck.length > 0) {
|
|
53
|
+
await (0, network_1.ackMessages)(options.networkUrl, toAck);
|
|
54
|
+
await (0, relay_state_1.markRelayed)(toAck);
|
|
55
|
+
}
|
|
56
|
+
for (const peer of peersToClear) {
|
|
57
|
+
await (0, pending_1.clearPending)(peer);
|
|
58
|
+
}
|
|
59
|
+
let message;
|
|
60
|
+
if (items.length === 0 && pending.length > 0) {
|
|
61
|
+
const names = pending.map((p) => p.peer).join(", ");
|
|
62
|
+
message = `Waiting for replies from: ${names}`;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
pending_count: pending.length,
|
|
66
|
+
relayed: items,
|
|
67
|
+
skipped_relayed: skippedRelayed,
|
|
68
|
+
message,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function buildItem(msg, pending, kind) {
|
|
72
|
+
return {
|
|
73
|
+
kind: pending ? "reply" : kind,
|
|
74
|
+
from: msg.from,
|
|
75
|
+
text: msg.text,
|
|
76
|
+
id: msg.id,
|
|
77
|
+
context: pending?.context,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.filterUnrelayed = filterUnrelayed;
|
|
4
|
+
exports.markRelayed = markRelayed;
|
|
5
|
+
const promises_1 = require("node:fs/promises");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
const node_os_1 = require("node:os");
|
|
8
|
+
const MAX_IDS = 500;
|
|
9
|
+
function statePath() {
|
|
10
|
+
return (0, node_path_1.join)((0, node_os_1.homedir)(), ".marshell", "relay-state.json");
|
|
11
|
+
}
|
|
12
|
+
async function readState() {
|
|
13
|
+
try {
|
|
14
|
+
const raw = await (0, promises_1.readFile)(statePath(), "utf8");
|
|
15
|
+
return JSON.parse(raw);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const maybeCode = error.code;
|
|
19
|
+
if (maybeCode === "ENOENT") {
|
|
20
|
+
return { relayedIds: [], updatedAt: new Date().toISOString() };
|
|
21
|
+
}
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function writeState(state) {
|
|
26
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)((0, node_os_1.homedir)(), ".marshell"), { recursive: true });
|
|
27
|
+
await (0, promises_1.writeFile)(statePath(), `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
28
|
+
}
|
|
29
|
+
async function filterUnrelayed(messages) {
|
|
30
|
+
const state = await readState();
|
|
31
|
+
const seen = new Set(state.relayedIds);
|
|
32
|
+
return messages.filter((m) => !seen.has(m.id));
|
|
33
|
+
}
|
|
34
|
+
async function markRelayed(ids) {
|
|
35
|
+
if (ids.length === 0)
|
|
36
|
+
return;
|
|
37
|
+
const state = await readState();
|
|
38
|
+
const merged = [...new Set([...state.relayedIds, ...ids])];
|
|
39
|
+
const trimmed = merged.length > MAX_IDS ? merged.slice(merged.length - MAX_IDS) : merged;
|
|
40
|
+
await writeState({
|
|
41
|
+
relayedIds: trimmed,
|
|
42
|
+
updatedAt: new Date().toISOString(),
|
|
43
|
+
});
|
|
44
|
+
}
|
package/package.json
CHANGED
package/scripts/relay.mjs
CHANGED