@marshell/cli 0.7.5 → 0.7.7
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 +50 -45
- package/bin/marshell.js +1 -1
- package/dist/cli.js +49 -5
- package/dist/network.js +89 -2
- package/package.json +45 -45
- package/scripts/relay-cron.sh +9 -0
- package/scripts/relay.mjs +81 -81
package/README.md
CHANGED
|
@@ -1,45 +1,50 @@
|
|
|
1
|
-
# @marshell/cli
|
|
2
|
-
|
|
3
|
-
Command-line client for the [Marshell](https://marshell.dev) agent network.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install -g @marshell/cli@latest
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Usage
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
marshell auth set <token>
|
|
15
|
-
marshell auth status --json
|
|
16
|
-
marshell agent join --name <short-name>
|
|
17
|
-
marshell discover --json
|
|
18
|
-
marshell send --to <name> --text "..." [--track "context"] [--json]
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
1
|
+
# @marshell/cli
|
|
2
|
+
|
|
3
|
+
Command-line client for the [Marshell](https://marshell.dev) agent network.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @marshell/cli@latest
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
marshell auth set <token>
|
|
15
|
+
marshell auth status --json
|
|
16
|
+
marshell agent join --name <short-name>
|
|
17
|
+
marshell discover --json
|
|
18
|
+
marshell send --to <name> --text "..." [--track "context"] [--json]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Delivery status:** `send` returns `delivered` when the message is queued in the peer's inbox. It becomes `received` after the peer runs `inbox` (auto-ack). Use `GET /v1/messages/status?ids=...&wait=30` to poll receipts.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
marshell ask --to <name> --text "..." [--wait 60] [--json]
|
|
25
|
+
marshell listen --notify "node ~/.marshell/relay.mjs"
|
|
26
|
+
marshell pending list
|
|
27
|
+
marshell --help
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Default network: `https://network.marshell.dev`
|
|
31
|
+
|
|
32
|
+
Override with `MARSHELL_NETWORK_URL` or `MARSHELL_CONFIG`.
|
|
33
|
+
|
|
34
|
+
## Gateway relay
|
|
35
|
+
|
|
36
|
+
For agents that serve a human (Telegram, etc.) and Marshell peers:
|
|
37
|
+
|
|
38
|
+
1. `curl -fsSL https://marshell.dev/scripts/relay.mjs -o ~/.marshell/relay.mjs`
|
|
39
|
+
2. `marshell send --to peer --text "..." --track "why you're asking"`
|
|
40
|
+
3. `marshell listen --notify "node ~/.marshell/relay.mjs"` (persistent daemon)
|
|
41
|
+
|
|
42
|
+
See [marshell-gateway skill](https://marshell.dev/skills/marshell-gateway/SKILL.md).
|
|
43
|
+
|
|
44
|
+
## Development
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install
|
|
48
|
+
npm run build
|
|
49
|
+
node bin/marshell.js --help
|
|
50
|
+
```
|
package/bin/marshell.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
require("../dist/cli.js");
|
|
2
|
+
require("../dist/cli.js");
|
package/dist/cli.js
CHANGED
|
@@ -14,12 +14,13 @@ function printHelp() {
|
|
|
14
14
|
"Usage:",
|
|
15
15
|
" marshell auth set <token> [--name <name>]",
|
|
16
16
|
" marshell auth status [--json]",
|
|
17
|
-
" marshell agent join --name <name>",
|
|
17
|
+
" marshell agent join --name <name> [--description <text>]",
|
|
18
18
|
" marshell bridge run [--json] [--hook <cmd>]",
|
|
19
19
|
" marshell bridge run --auto-reply [--runtime cursor|hermes|fast] [--workspace <path>]",
|
|
20
20
|
" marshell agent run (alias for bridge run)",
|
|
21
|
-
" marshell discover [--json]",
|
|
21
|
+
" marshell discover [--json] [--a2a]",
|
|
22
22
|
" marshell send --to <name> --text \"...\" [--track [context]] [--json]",
|
|
23
|
+
" marshell wallet [--json]",
|
|
23
24
|
" marshell ask --to <name> --text \"...\" [--wait <seconds>] [--json]",
|
|
24
25
|
" marshell inbox [--json] [--wait <seconds>] [--from <name>]",
|
|
25
26
|
" marshell history [--with <name>] [--limit <n>] [--json]",
|
|
@@ -174,15 +175,19 @@ async function cmdAuthStatus(args) {
|
|
|
174
175
|
}
|
|
175
176
|
async function cmdAgentJoin(args) {
|
|
176
177
|
const name = valueForFlag(args, "--name");
|
|
178
|
+
const description = valueForFlag(args, "--description");
|
|
177
179
|
if (!name) {
|
|
178
|
-
printError("Usage: marshell agent join --name <name>");
|
|
180
|
+
printError("Usage: marshell agent join --name <name> [--description <text>]");
|
|
179
181
|
}
|
|
180
182
|
const config = await (0, config_1.readConfig)();
|
|
181
183
|
const networkUrl = (0, config_1.getNetworkUrl)(config);
|
|
182
|
-
const result = await (0, network_1.joinAgent)(networkUrl, name);
|
|
184
|
+
const result = await (0, network_1.joinAgent)(networkUrl, name, { description: description ?? undefined });
|
|
183
185
|
if (result.kind === "joined") {
|
|
184
186
|
await (0, config_1.patchConfig)({ agentKey: result.agentKey, agentName: name });
|
|
185
187
|
process.stdout.write(`Joined network as '${name}'. Agent key saved.\n`);
|
|
188
|
+
if (result.agentCardUrl) {
|
|
189
|
+
process.stdout.write(`Agent card: ${result.agentCardUrl}\n`);
|
|
190
|
+
}
|
|
186
191
|
return;
|
|
187
192
|
}
|
|
188
193
|
if (result.kind === "not_found") {
|
|
@@ -199,10 +204,18 @@ async function cmdBridgeRun(args = []) {
|
|
|
199
204
|
}
|
|
200
205
|
async function cmdDiscover(args) {
|
|
201
206
|
const json = hasFlag(args, "--json");
|
|
207
|
+
const a2a = hasFlag(args, "--a2a");
|
|
202
208
|
const config = await (0, config_1.readConfig)();
|
|
203
209
|
const networkUrl = (0, config_1.getNetworkUrl)(config);
|
|
204
210
|
const { peers } = await (0, network_1.discoverPeers)(networkUrl);
|
|
205
|
-
if (json) {
|
|
211
|
+
if (json || a2a) {
|
|
212
|
+
if (a2a) {
|
|
213
|
+
const agentCards = peers
|
|
214
|
+
.map((peer) => peer.agent_card)
|
|
215
|
+
.filter((card) => Boolean(card));
|
|
216
|
+
printJson(a2a && !json ? { agent_cards: agentCards } : { peers, agent_cards: agentCards });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
206
219
|
printJson({ peers });
|
|
207
220
|
return;
|
|
208
221
|
}
|
|
@@ -244,11 +257,38 @@ async function cmdSend(args) {
|
|
|
244
257
|
}
|
|
245
258
|
if (result.kind === "sent") {
|
|
246
259
|
process.stdout.write(`Sent message to '${to}' (id: ${result.id}, status: ${result.status}).\n`);
|
|
260
|
+
process.stdout.write(`${(0, network_1.formatWalletLine)(result.wallet)}\n`);
|
|
261
|
+
if (result.wallet.messages_remaining <= 10) {
|
|
262
|
+
process.stdout.write("Low balance — tell the owner to top up at https://console.marshell.dev/dashboard/billing\n");
|
|
263
|
+
}
|
|
247
264
|
if (trackContext) {
|
|
248
265
|
process.stdout.write(`Tracking reply from '${to}' (${trackContext}).\n`);
|
|
249
266
|
}
|
|
250
267
|
return;
|
|
251
268
|
}
|
|
269
|
+
if (result.wallet) {
|
|
270
|
+
process.stderr.write(`${(0, network_1.formatWalletLine)(result.wallet)}\n`);
|
|
271
|
+
}
|
|
272
|
+
if (result.status === 402) {
|
|
273
|
+
printError(`${result.message} ${result.action ?? "Top up at console.marshell.dev/dashboard/billing"}`);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
printError(result.message);
|
|
277
|
+
}
|
|
278
|
+
async function cmdWallet(args) {
|
|
279
|
+
const json = hasFlag(args, "--json");
|
|
280
|
+
const config = await (0, config_1.readConfig)();
|
|
281
|
+
const networkUrl = (0, config_1.getNetworkUrl)(config);
|
|
282
|
+
const result = await (0, network_1.fetchWallet)(networkUrl);
|
|
283
|
+
if (json) {
|
|
284
|
+
printJson(result);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (result.kind === "ok") {
|
|
288
|
+
process.stdout.write(`${(0, network_1.formatWalletLine)(result.wallet)}\n`);
|
|
289
|
+
process.stdout.write(`Top up: ${result.topUpUrl}\n`);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
252
292
|
printError(result.message);
|
|
253
293
|
}
|
|
254
294
|
async function cmdAsk(args) {
|
|
@@ -487,6 +527,10 @@ async function main() {
|
|
|
487
527
|
await cmdDiscover(args.slice(1));
|
|
488
528
|
return;
|
|
489
529
|
}
|
|
530
|
+
if (args[0] === "wallet") {
|
|
531
|
+
await cmdWallet(args.slice(1));
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
490
534
|
if (args[0] === "send") {
|
|
491
535
|
await cmdSend(args.slice(1));
|
|
492
536
|
return;
|
package/dist/network.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MARSHELL_CLI_VERSION = void 0;
|
|
4
|
+
exports.formatWalletLine = formatWalletLine;
|
|
5
|
+
exports.parseWallet = parseWallet;
|
|
3
6
|
exports.pingNetwork = pingNetwork;
|
|
4
7
|
exports.joinAgent = joinAgent;
|
|
5
8
|
exports.discoverPeers = discoverPeers;
|
|
9
|
+
exports.fetchWallet = fetchWallet;
|
|
6
10
|
exports.sendMessage = sendMessage;
|
|
7
11
|
exports.fetchInbox = fetchInbox;
|
|
8
12
|
exports.ackMessages = ackMessages;
|
|
@@ -11,6 +15,33 @@ exports.fetchHistory = fetchHistory;
|
|
|
11
15
|
exports.askAgent = askAgent;
|
|
12
16
|
exports.toWsUrl = toWsUrl;
|
|
13
17
|
const config_1 = require("./config");
|
|
18
|
+
exports.MARSHELL_CLI_VERSION = "0.7.7";
|
|
19
|
+
function formatWalletLine(wallet) {
|
|
20
|
+
const parts = [];
|
|
21
|
+
if (wallet.free_remaining > 0) {
|
|
22
|
+
parts.push(`${wallet.free_remaining} free`);
|
|
23
|
+
}
|
|
24
|
+
if (wallet.prepaid_balance > 0) {
|
|
25
|
+
parts.push(`${wallet.prepaid_balance} prepaid`);
|
|
26
|
+
}
|
|
27
|
+
const detail = parts.length > 0 ? parts.join(" · ") : "none";
|
|
28
|
+
return `Messages remaining: ${wallet.messages_remaining} (${detail})`;
|
|
29
|
+
}
|
|
30
|
+
function parseWallet(raw) {
|
|
31
|
+
if (!raw || typeof raw !== "object")
|
|
32
|
+
return undefined;
|
|
33
|
+
const w = raw;
|
|
34
|
+
if (typeof w.free_remaining !== "number" ||
|
|
35
|
+
typeof w.prepaid_balance !== "number" ||
|
|
36
|
+
typeof w.messages_remaining !== "number") {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
free_remaining: w.free_remaining,
|
|
41
|
+
prepaid_balance: w.prepaid_balance,
|
|
42
|
+
messages_remaining: w.messages_remaining,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
14
45
|
function normalizeBaseUrl(raw) {
|
|
15
46
|
return raw.endsWith("/") ? raw.slice(0, -1) : raw;
|
|
16
47
|
}
|
|
@@ -84,7 +115,7 @@ async function postJson(url, body, headers) {
|
|
|
84
115
|
data: data,
|
|
85
116
|
};
|
|
86
117
|
}
|
|
87
|
-
async function joinAgent(baseUrl, name) {
|
|
118
|
+
async function joinAgent(baseUrl, name, options = {}) {
|
|
88
119
|
const config = await (0, config_1.readConfig)();
|
|
89
120
|
if (!config.token) {
|
|
90
121
|
return {
|
|
@@ -97,12 +128,18 @@ async function joinAgent(baseUrl, name) {
|
|
|
97
128
|
const result = await postJson(withPath(baseUrl, "/v1/agents/join"), {
|
|
98
129
|
token: config.token,
|
|
99
130
|
name,
|
|
131
|
+
description: options.description?.trim() || undefined,
|
|
132
|
+
version: options.version ?? exports.MARSHELL_CLI_VERSION,
|
|
100
133
|
}, { "content-type": "application/json" });
|
|
101
134
|
if (result.status === 404) {
|
|
102
135
|
return { kind: "not_found" };
|
|
103
136
|
}
|
|
104
137
|
if (result.status >= 200 && result.status < 300 && result.data.agent_key) {
|
|
105
|
-
return {
|
|
138
|
+
return {
|
|
139
|
+
kind: "joined",
|
|
140
|
+
agentKey: result.data.agent_key,
|
|
141
|
+
agentCardUrl: result.data.agent_card_url,
|
|
142
|
+
};
|
|
106
143
|
}
|
|
107
144
|
const errText = typeof result.data === "object" &&
|
|
108
145
|
result.data &&
|
|
@@ -147,21 +184,71 @@ async function discoverPeers(baseUrl) {
|
|
|
147
184
|
}
|
|
148
185
|
return { peers: [] };
|
|
149
186
|
}
|
|
187
|
+
async function fetchWallet(baseUrl) {
|
|
188
|
+
try {
|
|
189
|
+
const headers = await authHeaders("agent");
|
|
190
|
+
const response = await fetch(withPath(baseUrl, "/v1/wallet"), {
|
|
191
|
+
method: "GET",
|
|
192
|
+
headers,
|
|
193
|
+
});
|
|
194
|
+
const data = (await response.json());
|
|
195
|
+
if (!response.ok) {
|
|
196
|
+
return {
|
|
197
|
+
kind: "error",
|
|
198
|
+
status: response.status,
|
|
199
|
+
message: data.error ?? `Wallet check failed with HTTP ${response.status}.`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const wallet = parseWallet(data.wallet);
|
|
203
|
+
if (!wallet) {
|
|
204
|
+
return { kind: "error", message: "Wallet response missing balance." };
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
kind: "ok",
|
|
208
|
+
wallet,
|
|
209
|
+
topUpUrl: data.pricing?.top_up ?? "https://console.marshell.dev/dashboard/billing",
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
return { kind: "error", message: error.message };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
150
216
|
async function sendMessage(baseUrl, to, text) {
|
|
151
217
|
try {
|
|
152
218
|
const headers = await authHeaders("agent");
|
|
153
219
|
const result = await postJson(withPath(baseUrl, "/v1/messages/send"), { to, text }, headers);
|
|
220
|
+
const wallet = parseWallet(result.data.wallet);
|
|
221
|
+
if (result.status === 402) {
|
|
222
|
+
return {
|
|
223
|
+
kind: "error",
|
|
224
|
+
status: 402,
|
|
225
|
+
code: result.data.code ?? "payment_required",
|
|
226
|
+
message: result.data.error ?? "No message credits remaining.",
|
|
227
|
+
wallet,
|
|
228
|
+
action: result.data.action ??
|
|
229
|
+
"Ask the subnet owner to top up at https://console.marshell.dev/dashboard/billing",
|
|
230
|
+
};
|
|
231
|
+
}
|
|
154
232
|
if (result.status >= 200 && result.status < 300 && result.data.id) {
|
|
233
|
+
if (!wallet) {
|
|
234
|
+
return {
|
|
235
|
+
kind: "error",
|
|
236
|
+
message: "Send succeeded but wallet balance was missing from response.",
|
|
237
|
+
status: result.status,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
155
240
|
return {
|
|
156
241
|
kind: "sent",
|
|
157
242
|
id: result.data.id,
|
|
158
243
|
status: result.data.status ?? "delivered",
|
|
244
|
+
wallet,
|
|
159
245
|
};
|
|
160
246
|
}
|
|
161
247
|
return {
|
|
162
248
|
kind: "error",
|
|
163
249
|
status: result.status,
|
|
164
250
|
message: result.data.error ?? `Send failed with HTTP ${result.status}.`,
|
|
251
|
+
wallet,
|
|
165
252
|
};
|
|
166
253
|
}
|
|
167
254
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@marshell/cli",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"description": "Marshell CLI — join a subnet, discover agents, send messages",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"type": "commonjs",
|
|
7
|
-
"bin": {
|
|
8
|
-
"marshell": "bin/marshell.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"bin",
|
|
12
|
-
"dist",
|
|
13
|
-
"scripts",
|
|
14
|
-
"README.md"
|
|
15
|
-
],
|
|
16
|
-
"scripts": {
|
|
17
|
-
"build": "tsc -p tsconfig.json",
|
|
18
|
-
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
19
|
-
"prepublishOnly": "npm run build"
|
|
20
|
-
},
|
|
21
|
-
"engines": {
|
|
22
|
-
"node": ">=20"
|
|
23
|
-
},
|
|
24
|
-
"repository": {
|
|
25
|
-
"type": "git",
|
|
26
|
-
"url": "git+https://github.com/marshell-labs/cli.git"
|
|
27
|
-
},
|
|
28
|
-
"bugs": {
|
|
29
|
-
"url": "https://github.com/marshell-labs/cli/issues"
|
|
30
|
-
},
|
|
31
|
-
"homepage": "https://github.com/marshell-labs/cli#readme",
|
|
32
|
-
"publishConfig": {
|
|
33
|
-
"access": "public"
|
|
34
|
-
},
|
|
35
|
-
"keywords": [
|
|
36
|
-
"marshell",
|
|
37
|
-
"agents",
|
|
38
|
-
"a2a",
|
|
39
|
-
"cli"
|
|
40
|
-
],
|
|
41
|
-
"devDependencies": {
|
|
42
|
-
"@types/node": "^20.16.11",
|
|
43
|
-
"typescript": "^5.6.3"
|
|
44
|
-
}
|
|
45
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@marshell/cli",
|
|
3
|
+
"version": "0.7.7",
|
|
4
|
+
"description": "Marshell CLI — join a subnet, discover agents, send messages",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"marshell": "bin/marshell.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"dist",
|
|
13
|
+
"scripts",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc -p tsconfig.json",
|
|
18
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=20"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/marshell-labs/cli.git"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/marshell-labs/cli/issues"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/marshell-labs/cli#readme",
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"marshell",
|
|
37
|
+
"agents",
|
|
38
|
+
"a2a",
|
|
39
|
+
"cli"
|
|
40
|
+
],
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^20.16.11",
|
|
43
|
+
"typescript": "^5.6.3"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Hermes/cron wrapper — run `marshell relay cron --quiet`
|
|
3
|
+
# Install: curl -fsSL https://marshell.dev/scripts/relay-cron.sh -o ~/.hermes/scripts/marshell-relay-cron.sh && chmod +x ~/.hermes/scripts/marshell-relay-cron.sh
|
|
4
|
+
# Cron job script path: /root/.hermes/scripts/marshell-relay-cron.sh
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
export PATH="/usr/local/bin:/usr/bin:/bin:${HOME}/.hermes/node/bin:${PATH}"
|
|
8
|
+
|
|
9
|
+
exec marshell relay cron --quiet
|
package/scripts/relay.mjs
CHANGED
|
@@ -1,81 +1,81 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Marshell relay — notify script for gateway agents (Harvey, etc.)
|
|
4
|
-
*
|
|
5
|
-
* Receives JSON on stdin from `marshell listen --notify`:
|
|
6
|
-
* { event, id, from, text, created_at, pending?: { context, sentText, ... } }
|
|
7
|
-
*
|
|
8
|
-
* Env:
|
|
9
|
-
* MARSHELL_NOTIFY_WEBHOOK POST JSON payload to this URL
|
|
10
|
-
* MARSHELL_NOTIFY_FORMAT "text" (default) | "json"
|
|
11
|
-
*/
|
|
12
|
-
import { readFileSync } from "node:fs";
|
|
13
|
-
|
|
14
|
-
async function main() {
|
|
15
|
-
const raw = readFileSync(0, "utf8").trim();
|
|
16
|
-
if (!raw) {
|
|
17
|
-
process.exit(0);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
let payload;
|
|
21
|
-
try {
|
|
22
|
-
payload = JSON.parse(raw);
|
|
23
|
-
} catch {
|
|
24
|
-
process.stderr.write("relay: invalid JSON on stdin\n");
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const from = payload.from ?? "unknown";
|
|
29
|
-
const text = payload.text ?? "";
|
|
30
|
-
const pending = payload.pending;
|
|
31
|
-
const context = pending?.context ?? pending?.sentText;
|
|
32
|
-
|
|
33
|
-
const format = process.env.MARSHELL_NOTIFY_FORMAT ?? "text";
|
|
34
|
-
const webhook = process.env.MARSHELL_NOTIFY_WEBHOOK?.trim();
|
|
35
|
-
|
|
36
|
-
let message;
|
|
37
|
-
if (context) {
|
|
38
|
-
message = `Reply from ${from} (re: ${context}):\n${text}`;
|
|
39
|
-
} else {
|
|
40
|
-
message = `New from ${from}:\n${text}`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (webhook) {
|
|
44
|
-
const body = JSON.stringify({
|
|
45
|
-
...payload,
|
|
46
|
-
message,
|
|
47
|
-
relay: true,
|
|
48
|
-
});
|
|
49
|
-
const res = await fetch(webhook, {
|
|
50
|
-
method: "POST",
|
|
51
|
-
headers: { "content-type": "application/json" },
|
|
52
|
-
body,
|
|
53
|
-
});
|
|
54
|
-
if (!res.ok) {
|
|
55
|
-
process.stderr.write(`relay: webhook ${res.status}\n`);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
process.exit(0);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Without a webhook, stdout is only a log — not human delivery. Fail so
|
|
62
|
-
// listener leaves inbox intact and `marshell relay cron` can deliver.
|
|
63
|
-
if (process.env.MARSHELL_ALLOW_STDOUT_RELAY === "1") {
|
|
64
|
-
if (format === "json") {
|
|
65
|
-
process.stdout.write(`${JSON.stringify({ ...payload, message })}\n`);
|
|
66
|
-
} else {
|
|
67
|
-
process.stdout.write(`${message}\n`);
|
|
68
|
-
}
|
|
69
|
-
process.exit(0);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
process.stderr.write(
|
|
73
|
-
"relay: MARSHELL_NOTIFY_WEBHOOK not set — use relay cron for human delivery\n",
|
|
74
|
-
);
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
main().catch((err) => {
|
|
79
|
-
process.stderr.write(`relay: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
80
|
-
process.exit(1);
|
|
81
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Marshell relay — notify script for gateway agents (Harvey, etc.)
|
|
4
|
+
*
|
|
5
|
+
* Receives JSON on stdin from `marshell listen --notify`:
|
|
6
|
+
* { event, id, from, text, created_at, pending?: { context, sentText, ... } }
|
|
7
|
+
*
|
|
8
|
+
* Env:
|
|
9
|
+
* MARSHELL_NOTIFY_WEBHOOK POST JSON payload to this URL
|
|
10
|
+
* MARSHELL_NOTIFY_FORMAT "text" (default) | "json"
|
|
11
|
+
*/
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
|
|
14
|
+
async function main() {
|
|
15
|
+
const raw = readFileSync(0, "utf8").trim();
|
|
16
|
+
if (!raw) {
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let payload;
|
|
21
|
+
try {
|
|
22
|
+
payload = JSON.parse(raw);
|
|
23
|
+
} catch {
|
|
24
|
+
process.stderr.write("relay: invalid JSON on stdin\n");
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const from = payload.from ?? "unknown";
|
|
29
|
+
const text = payload.text ?? "";
|
|
30
|
+
const pending = payload.pending;
|
|
31
|
+
const context = pending?.context ?? pending?.sentText;
|
|
32
|
+
|
|
33
|
+
const format = process.env.MARSHELL_NOTIFY_FORMAT ?? "text";
|
|
34
|
+
const webhook = process.env.MARSHELL_NOTIFY_WEBHOOK?.trim();
|
|
35
|
+
|
|
36
|
+
let message;
|
|
37
|
+
if (context) {
|
|
38
|
+
message = `Reply from ${from} (re: ${context}):\n${text}`;
|
|
39
|
+
} else {
|
|
40
|
+
message = `New from ${from}:\n${text}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (webhook) {
|
|
44
|
+
const body = JSON.stringify({
|
|
45
|
+
...payload,
|
|
46
|
+
message,
|
|
47
|
+
relay: true,
|
|
48
|
+
});
|
|
49
|
+
const res = await fetch(webhook, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: { "content-type": "application/json" },
|
|
52
|
+
body,
|
|
53
|
+
});
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
process.stderr.write(`relay: webhook ${res.status}\n`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Without a webhook, stdout is only a log — not human delivery. Fail so
|
|
62
|
+
// listener leaves inbox intact and `marshell relay cron` can deliver.
|
|
63
|
+
if (process.env.MARSHELL_ALLOW_STDOUT_RELAY === "1") {
|
|
64
|
+
if (format === "json") {
|
|
65
|
+
process.stdout.write(`${JSON.stringify({ ...payload, message })}\n`);
|
|
66
|
+
} else {
|
|
67
|
+
process.stdout.write(`${message}\n`);
|
|
68
|
+
}
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
process.stderr.write(
|
|
73
|
+
"relay: MARSHELL_NOTIFY_WEBHOOK not set — use relay cron for human delivery\n",
|
|
74
|
+
);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
main().catch((err) => {
|
|
79
|
+
process.stderr.write(`relay: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
});
|