@0xmaxma/claude-gateway 1.8.9 → 1.8.11
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 +46 -6
- package/config.template.json +2 -1
- package/dist/agent/runner.d.ts +86 -1
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +396 -9
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +1028 -6
- package/dist/api/router.js.map +1 -1
- package/dist/api/share-router.d.ts.map +1 -1
- package/dist/api/share-router.js +24 -0
- package/dist/api/share-router.js.map +1 -1
- package/dist/api/webhooks-router.d.ts +2 -0
- package/dist/api/webhooks-router.d.ts.map +1 -1
- package/dist/api/webhooks-router.js +2 -0
- package/dist/api/webhooks-router.js.map +1 -1
- package/dist/api/whatsapp-access.d.ts +123 -0
- package/dist/api/whatsapp-access.d.ts.map +1 -0
- package/dist/api/whatsapp-access.js +135 -0
- package/dist/api/whatsapp-access.js.map +1 -0
- package/dist/api/whatsapp-cloud-access.d.ts +48 -0
- package/dist/api/whatsapp-cloud-access.d.ts.map +1 -0
- package/dist/api/whatsapp-cloud-access.js +58 -0
- package/dist/api/whatsapp-cloud-access.js.map +1 -0
- package/dist/api/whatsapp-cloud-client.d.ts +143 -0
- package/dist/api/whatsapp-cloud-client.d.ts.map +1 -0
- package/dist/api/whatsapp-cloud-client.js +298 -0
- package/dist/api/whatsapp-cloud-client.js.map +1 -0
- package/dist/api/whatsapp-cloud-webhook-router.d.ts +132 -0
- package/dist/api/whatsapp-cloud-webhook-router.d.ts.map +1 -0
- package/dist/api/whatsapp-cloud-webhook-router.js +494 -0
- package/dist/api/whatsapp-cloud-webhook-router.js.map +1 -0
- package/dist/cli/args.d.ts +34 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +55 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/commands/app.d.ts +28 -0
- package/dist/cli/commands/app.d.ts.map +1 -0
- package/dist/cli/commands/app.js +411 -0
- package/dist/cli/commands/app.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +77 -16
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/service.d.ts.map +1 -1
- package/dist/cli/commands/service.js +373 -56
- package/dist/cli/commands/service.js.map +1 -1
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +1 -17
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/http-client.d.ts +8 -0
- package/dist/cli/http-client.d.ts.map +1 -1
- package/dist/cli/http-client.js +5 -2
- package/dist/cli/http-client.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +16 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/prompt.d.ts +17 -2
- package/dist/cli/prompt.d.ts.map +1 -1
- package/dist/cli/prompt.js +33 -2
- package/dist/cli/prompt.js.map +1 -1
- package/dist/config/loader.d.ts +8 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +22 -4
- package/dist/config/loader.js.map +1 -1
- package/dist/config/watcher.d.ts.map +1 -1
- package/dist/config/watcher.js +12 -0
- package/dist/config/watcher.js.map +1 -1
- package/dist/config/whatsapp-accounts.d.ts +77 -0
- package/dist/config/whatsapp-accounts.d.ts.map +1 -0
- package/dist/config/whatsapp-accounts.js +218 -0
- package/dist/config/whatsapp-accounts.js.map +1 -0
- package/dist/history/db.d.ts.map +1 -1
- package/dist/history/db.js +43 -11
- package/dist/history/db.js.map +1 -1
- package/dist/history/types.d.ts +18 -1
- package/dist/history/types.d.ts.map +1 -1
- package/dist/history/types.js +1 -1
- package/dist/history/types.js.map +1 -1
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -1
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +37 -0
- package/dist/session/process.js.map +1 -1
- package/dist/session/store.d.ts +3 -2
- package/dist/session/store.d.ts.map +1 -1
- package/dist/session/store.js +1 -1
- package/dist/session/store.js.map +1 -1
- package/dist/share/session-video-catalog.d.ts +19 -0
- package/dist/share/session-video-catalog.d.ts.map +1 -0
- package/dist/share/session-video-catalog.js +119 -0
- package/dist/share/session-video-catalog.js.map +1 -0
- package/dist/shared/image-optimize.d.ts +32 -0
- package/dist/shared/image-optimize.d.ts.map +1 -0
- package/dist/shared/image-optimize.js +197 -0
- package/dist/shared/image-optimize.js.map +1 -0
- package/dist/shared/image-sniff.d.ts +9 -0
- package/dist/shared/image-sniff.d.ts.map +1 -1
- package/dist/shared/image-sniff.js +13 -0
- package/dist/shared/image-sniff.js.map +1 -1
- package/dist/shared/text-chunk.d.ts +37 -0
- package/dist/shared/text-chunk.d.ts.map +1 -0
- package/dist/shared/text-chunk.js +105 -0
- package/dist/shared/text-chunk.js.map +1 -0
- package/dist/shared/whatsapp-ack.d.ts +13 -0
- package/dist/shared/whatsapp-ack.d.ts.map +1 -0
- package/dist/shared/whatsapp-ack.js +16 -0
- package/dist/shared/whatsapp-ack.js.map +1 -0
- package/dist/types.d.ts +183 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/whatsapp/manager.d.ts +173 -0
- package/dist/whatsapp/manager.d.ts.map +1 -0
- package/dist/whatsapp/manager.js +838 -0
- package/dist/whatsapp/manager.js.map +1 -0
- package/mcp/server.ts +4 -0
- package/mcp/tools/image/module.ts +6 -1
- package/mcp/tools/telegram/receiver-server.ts +15 -8
- package/mcp/tools/telegram/reply-attachment.ts +81 -0
- package/mcp/tools/video/module.ts +6 -1
- package/mcp/tools/whatsapp/module.ts +162 -0
- package/mcp/tools/whatsapp-cloud/module.ts +536 -0
- package/package.json +7 -2
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.extractInboundMessages = extractInboundMessages;
|
|
37
|
+
exports.synthesizeVCard = synthesizeVCard;
|
|
38
|
+
exports.normalizeWhatsAppCloudMessage = normalizeWhatsAppCloudMessage;
|
|
39
|
+
exports.verifyMetaSignature = verifyMetaSignature;
|
|
40
|
+
exports.createWhatsAppCloudWebhookHandler = createWhatsAppCloudWebhookHandler;
|
|
41
|
+
const crypto_1 = require("crypto");
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const os = __importStar(require("os"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const logger_1 = require("../logger");
|
|
46
|
+
const whatsapp_cloud_access_1 = require("./whatsapp-cloud-access");
|
|
47
|
+
const pending_senders_1 = require("./pending-senders");
|
|
48
|
+
const whatsapp_cloud_client_1 = require("./whatsapp-cloud-client");
|
|
49
|
+
const media_store_1 = require("../history/media-store");
|
|
50
|
+
const image_sniff_1 = require("../shared/image-sniff");
|
|
51
|
+
const WHATSAPP_CLOUD_API_BASE = 'https://graph.facebook.com/v20.0';
|
|
52
|
+
// Inbound media cap. Sourced from MediaStore (where the downloaded bytes end
|
|
53
|
+
// up) for both images and documents — a router-local literal could drift
|
|
54
|
+
// from what the store then rejects.
|
|
55
|
+
const MAX_MEDIA_BYTES = media_store_1.MediaStore.maxUploadBytes;
|
|
56
|
+
// Meta retries a webhook delivery if our ack is slow/dropped — message.id is
|
|
57
|
+
// the dedup key. A bounded, age-pruned map is enough (mirrors Slack's
|
|
58
|
+
// seenEventIds): it only needs to outlive Meta's short retry window, not forever.
|
|
59
|
+
const WHATSAPP_CLOUD_MESSAGE_ID_TTL_MS = 10 * 60000;
|
|
60
|
+
// The returned media download `url` must resolve to a Meta-owned host before
|
|
61
|
+
// the bearer token is ever attached to the second GET — see
|
|
62
|
+
// downloadWhatsAppCloudMedia's doc comment (mirrors downloadSlackImage's
|
|
63
|
+
// host-check-before-sending-token defense).
|
|
64
|
+
const MEDIA_HOST_ALLOWLIST = [/\.fbcdn\.net$/, /^graph\.facebook\.com$/, /^lookaside\.fbsbx\.com$/];
|
|
65
|
+
/**
|
|
66
|
+
* Extract the inbound message list from a webhook `value` object — returns
|
|
67
|
+
* [] for a status-only batch (our own outbound delivery receipts) or any
|
|
68
|
+
* payload shape without a `messages` array at all, so callers never try to
|
|
69
|
+
* normalize a status update as an inbound message.
|
|
70
|
+
*/
|
|
71
|
+
function extractInboundMessages(value) {
|
|
72
|
+
return value?.messages ?? [];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Build a minimal vCard from Meta's structured contact payload.
|
|
76
|
+
*
|
|
77
|
+
* The Cloud API does NOT hand over a vCard string (Baileys does — see
|
|
78
|
+
* manager.ts's contactMessage branch, which uses `contactMessage.vcard`
|
|
79
|
+
* verbatim), it hands over decomposed JSON. Synthesizing a 3.0 vCard here
|
|
80
|
+
* means BOTH WhatsApp channels put the same kind of value in the same
|
|
81
|
+
* `meta.vcard` key, so the agent never needs channel-specific handling.
|
|
82
|
+
* Returns undefined when there is neither a name nor a phone to put in it.
|
|
83
|
+
*/
|
|
84
|
+
function synthesizeVCard(contact) {
|
|
85
|
+
const name = contact?.name?.formatted_name?.trim();
|
|
86
|
+
const phone = contact?.phones?.find((p) => p.phone)?.phone?.trim();
|
|
87
|
+
if (!name && !phone)
|
|
88
|
+
return undefined;
|
|
89
|
+
const lines = ['BEGIN:VCARD', 'VERSION:3.0'];
|
|
90
|
+
if (name)
|
|
91
|
+
lines.push(`FN:${name}`);
|
|
92
|
+
if (phone)
|
|
93
|
+
lines.push(`TEL:${phone}`);
|
|
94
|
+
lines.push('END:VCARD');
|
|
95
|
+
return lines.join('\n');
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Normalize an inbound Cloud API message into the gateway's {content, meta}
|
|
99
|
+
* intake shape. Returns null when `from` is missing/empty (no reply target).
|
|
100
|
+
* `content` is the text body for `type: 'text'`, the caption (possibly
|
|
101
|
+
* empty) for `type: 'image'`/`'document'`, or a short human summary for
|
|
102
|
+
* `type: 'location'`, or the selected option's title for `type: 'interactive'`
|
|
103
|
+
* (a button tap / list pick) — remaining types (audio, video, ...) are still
|
|
104
|
+
* out of scope and forward with empty content rather than being dropped, same
|
|
105
|
+
* posture as Slack's "no text → empty content, not null".
|
|
106
|
+
*
|
|
107
|
+
* Stays synchronous and pure: an attached image/document/sticker's bytes are
|
|
108
|
+
* fetched by the async handler AFTER this returns, which sets
|
|
109
|
+
* `meta.image_path`/`meta.document_path`/`meta.sticker_path` on the object
|
|
110
|
+
* built here.
|
|
111
|
+
*/
|
|
112
|
+
function normalizeWhatsAppCloudMessage(msg, resolved) {
|
|
113
|
+
const r = resolved ?? (0, whatsapp_cloud_access_1.resolveWhatsAppCloudSource)(msg.from);
|
|
114
|
+
if (r.kind === 'other' || !r.conversationId)
|
|
115
|
+
return null;
|
|
116
|
+
const meta = {
|
|
117
|
+
source: 'whatsapp_cloud',
|
|
118
|
+
chat_id: r.conversationId,
|
|
119
|
+
user_id: r.senderId,
|
|
120
|
+
user: r.senderId,
|
|
121
|
+
message_id: msg.id ?? '',
|
|
122
|
+
};
|
|
123
|
+
// Reply context. Meta's webhook carries ONLY the quoted message's id — the
|
|
124
|
+
// quoted text and its sender are NOT inlined (a real Cloud API limitation,
|
|
125
|
+
// not a gap here; retrieving them would need our own store of previously
|
|
126
|
+
// seen messages). So `replied_user`/`replied_text` are deliberately left
|
|
127
|
+
// unset for this channel, and buildChannelXml emits `<replied
|
|
128
|
+
// message_id=… user="">` with an empty body. Baileys, by contrast, does
|
|
129
|
+
// inline the quote and populates all three.
|
|
130
|
+
if (msg.context?.id)
|
|
131
|
+
meta.replied_message_id = msg.context.id;
|
|
132
|
+
let content = '';
|
|
133
|
+
if (msg.type === 'text')
|
|
134
|
+
content = msg.text?.body ?? '';
|
|
135
|
+
else if (msg.type === 'image')
|
|
136
|
+
content = msg.image?.caption ?? '';
|
|
137
|
+
else if (msg.type === 'document')
|
|
138
|
+
content = msg.document?.caption ?? '';
|
|
139
|
+
else if (msg.type === 'location' && msg.location) {
|
|
140
|
+
const { latitude, longitude, name, address } = msg.location;
|
|
141
|
+
if (typeof latitude === 'number')
|
|
142
|
+
meta.location_lat = String(latitude);
|
|
143
|
+
if (typeof longitude === 'number')
|
|
144
|
+
meta.location_lng = String(longitude);
|
|
145
|
+
// A pinned place carries a name/address; a raw dropped pin carries only
|
|
146
|
+
// coordinates, which already ride along in meta — hence the generic
|
|
147
|
+
// fallback rather than repeating the numbers in the text.
|
|
148
|
+
content = [name, address].filter(Boolean).join(', ') || '[Location shared]';
|
|
149
|
+
}
|
|
150
|
+
else if (msg.type === 'contacts') {
|
|
151
|
+
const vcard = synthesizeVCard(msg.contacts?.[0]);
|
|
152
|
+
if (vcard)
|
|
153
|
+
meta.vcard = vcard;
|
|
154
|
+
}
|
|
155
|
+
else if (msg.type === 'interactive') {
|
|
156
|
+
// A button tap / list pick (Phase 3). `content` becomes the DISPLAY TITLE
|
|
157
|
+
// of whatever the user selected, so the turn reads to the agent exactly as
|
|
158
|
+
// if they had typed that label — no interactive-aware agent-side handling
|
|
159
|
+
// is needed for the common case. The machine-readable `id` rides along in
|
|
160
|
+
// meta.interactive_id for agents whose logic branches on the id instead
|
|
161
|
+
// (ids are stable; titles are what the user reads and may be localized).
|
|
162
|
+
const selected = msg.interactive?.button_reply ?? msg.interactive?.list_reply;
|
|
163
|
+
if (selected) {
|
|
164
|
+
content = selected.title ?? '';
|
|
165
|
+
if (selected.id)
|
|
166
|
+
meta.interactive_id = selected.id;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return { content, meta };
|
|
170
|
+
}
|
|
171
|
+
/** Verify `X-Hub-Signature-256` (HMAC-SHA256 of the raw body — no timestamp component). */
|
|
172
|
+
function verifyMetaSignature(rawBody, appSecret, sigHeader) {
|
|
173
|
+
if (!appSecret || !sigHeader)
|
|
174
|
+
return false;
|
|
175
|
+
const prefix = 'sha256=';
|
|
176
|
+
if (!sigHeader.startsWith(prefix))
|
|
177
|
+
return false;
|
|
178
|
+
const expected = `${prefix}${(0, crypto_1.createHmac)('sha256', appSecret).update(rawBody).digest('hex')}`;
|
|
179
|
+
const expectedBuf = Buffer.from(expected, 'utf8');
|
|
180
|
+
const actualBuf = Buffer.from(sigHeader, 'utf8');
|
|
181
|
+
if (expectedBuf.length !== actualBuf.length)
|
|
182
|
+
return false;
|
|
183
|
+
return (0, crypto_1.timingSafeEqual)(expectedBuf, actualBuf);
|
|
184
|
+
}
|
|
185
|
+
/** Constant-time string equality — same posture as verifyMetaSignature above. */
|
|
186
|
+
function safeStringEqual(a, b) {
|
|
187
|
+
const aBuf = Buffer.from(a, 'utf8');
|
|
188
|
+
const bBuf = Buffer.from(b, 'utf8');
|
|
189
|
+
if (aBuf.length !== bBuf.length)
|
|
190
|
+
return false;
|
|
191
|
+
return (0, crypto_1.timingSafeEqual)(aBuf, bBuf);
|
|
192
|
+
}
|
|
193
|
+
/** Find the agent that has WhatsApp Cloud configured (mirrors resolveSlackAgent). */
|
|
194
|
+
function resolveWhatsAppCloudAgent(agents, agentId) {
|
|
195
|
+
if (agentId) {
|
|
196
|
+
const r = agents.get(agentId);
|
|
197
|
+
return r && r.getAgentConfig().whatsapp_cloud?.appSecret ? r : null;
|
|
198
|
+
}
|
|
199
|
+
for (const runner of agents.values()) {
|
|
200
|
+
if (runner.getAgentConfig().whatsapp_cloud?.appSecret)
|
|
201
|
+
return runner;
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* One-time pairing-code message — same visual-match-code contract as
|
|
207
|
+
* Slack/LINE's pairingMessage. DM-only (no "in this channel/group" phrasing
|
|
208
|
+
* needed — there is no group tier on this channel).
|
|
209
|
+
*/
|
|
210
|
+
function pairingMessage(code) {
|
|
211
|
+
return (`รหัสจับคู่ (pairing code) ของคุณคือ: ${code}\n` +
|
|
212
|
+
`กรุณาแจ้งรหัสนี้ให้แอดมินเพื่อขอเปิดใช้งานบอท (ไม่ต้องพิมพ์รหัสตอบกลับ)\n\n` +
|
|
213
|
+
`Your pairing code: ${code}\n` +
|
|
214
|
+
`Share this code with the admin to get access. (No need to reply with it.)`);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Fetch an inbound media attachment's bytes and return them + the reported
|
|
218
|
+
* mime type. Two-step, per the Cloud API's media contract:
|
|
219
|
+
* 1. `GET /{media-id}?access_token=...` → `{url, mime_type}` — the
|
|
220
|
+
* returned `url` is short-lived and NOT itself bearer-protected the way
|
|
221
|
+
* `url_private` is on Slack, but IS host-restricted to Meta's own CDN.
|
|
222
|
+
* 2. `GET <url>` with the SAME bearer token.
|
|
223
|
+
*
|
|
224
|
+
* Step 2's host is validated against MEDIA_HOST_ALLOWLIST BEFORE the token is
|
|
225
|
+
* attached — defense in depth against a future payload-shape change or bug
|
|
226
|
+
* upstream of this call handing us an attacker-chosen `url` (mirrors
|
|
227
|
+
* downloadSlackImage's identical host-check-before-sending-token structure).
|
|
228
|
+
* Enforces MAX_MEDIA_BYTES against both the declared content-length and the
|
|
229
|
+
* actual streamed byte count. Returns null when the metadata step yields no
|
|
230
|
+
* `url`; throws on HTTP failure, an untrusted host, or an over-cap size — the
|
|
231
|
+
* caller logs and forwards the turn regardless.
|
|
232
|
+
*/
|
|
233
|
+
async function downloadWhatsAppCloudMedia(accessToken, mediaId, apiBase) {
|
|
234
|
+
const metaRes = await fetch(`${apiBase}/${mediaId}?access_token=${encodeURIComponent(accessToken)}`);
|
|
235
|
+
if (!metaRes.ok)
|
|
236
|
+
throw new Error(`HTTP ${metaRes.status} fetching media metadata`);
|
|
237
|
+
const metaJson = (await metaRes.json());
|
|
238
|
+
if (!metaJson.url)
|
|
239
|
+
return null;
|
|
240
|
+
let host;
|
|
241
|
+
try {
|
|
242
|
+
host = new URL(metaJson.url).hostname;
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
throw new Error('invalid media url');
|
|
246
|
+
}
|
|
247
|
+
if (!MEDIA_HOST_ALLOWLIST.some((re) => re.test(host))) {
|
|
248
|
+
throw new Error(`refusing to send bearer token to untrusted media host: ${host}`);
|
|
249
|
+
}
|
|
250
|
+
const res = await fetch(metaJson.url, { headers: { Authorization: `Bearer ${accessToken}` } });
|
|
251
|
+
if (!res.ok)
|
|
252
|
+
throw new Error(`HTTP ${res.status} downloading media`);
|
|
253
|
+
const declared = Number(res.headers.get('content-length') ?? '');
|
|
254
|
+
if (Number.isFinite(declared) && declared > MAX_MEDIA_BYTES) {
|
|
255
|
+
throw new Error(`media exceeds ${MAX_MEDIA_BYTES} byte cap`);
|
|
256
|
+
}
|
|
257
|
+
let buf;
|
|
258
|
+
if (res.body) {
|
|
259
|
+
const chunks = [];
|
|
260
|
+
let total = 0;
|
|
261
|
+
for await (const chunk of res.body) {
|
|
262
|
+
total += chunk.byteLength;
|
|
263
|
+
if (total > MAX_MEDIA_BYTES)
|
|
264
|
+
throw new Error(`media exceeds ${MAX_MEDIA_BYTES} byte cap`);
|
|
265
|
+
chunks.push(Buffer.from(chunk));
|
|
266
|
+
}
|
|
267
|
+
buf = Buffer.concat(chunks);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
buf = Buffer.from(await res.arrayBuffer());
|
|
271
|
+
if (buf.length > MAX_MEDIA_BYTES)
|
|
272
|
+
throw new Error(`media exceeds ${MAX_MEDIA_BYTES} byte cap`);
|
|
273
|
+
}
|
|
274
|
+
return { buf, mimeType: metaJson.mime_type ?? 'application/octet-stream' };
|
|
275
|
+
}
|
|
276
|
+
function writeTempImage(buf, messageId) {
|
|
277
|
+
// messageId is Meta-webhook-supplied and not guaranteed to match wamid.*'s
|
|
278
|
+
// format — sanitize before it reaches a filename (see sanitizeFilenameId).
|
|
279
|
+
const suffix = `${(0, image_sniff_1.sanitizeFilenameId)(messageId)}-${Date.now()}`;
|
|
280
|
+
const dest = path.join(os.tmpdir(), `whatsapp-cloud-img-${suffix}.${(0, image_sniff_1.sniffImageExt)(buf)}`);
|
|
281
|
+
fs.writeFileSync(dest, buf, { mode: 0o600 });
|
|
282
|
+
return dest;
|
|
283
|
+
}
|
|
284
|
+
function writeTempDocument(buf, mimeType, filename, messageId) {
|
|
285
|
+
const suffix = `${(0, image_sniff_1.sanitizeFilenameId)(messageId)}-${Date.now()}`;
|
|
286
|
+
const extFromName = filename ? path.extname(filename) : '';
|
|
287
|
+
const ext = extFromName || (mimeType === 'application/pdf' ? '.pdf' : '');
|
|
288
|
+
const dest = path.join(os.tmpdir(), `whatsapp-cloud-doc-${suffix}${ext}`);
|
|
289
|
+
fs.writeFileSync(dest, buf, { mode: 0o600 });
|
|
290
|
+
return dest;
|
|
291
|
+
}
|
|
292
|
+
function createWhatsAppCloudWebhookHandler(agents, logDir, opts = {}) {
|
|
293
|
+
const logger = (0, logger_1.createLogger)('whatsapp-cloud-webhook', logDir);
|
|
294
|
+
const apiBase = opts.apiBase ?? WHATSAPP_CLOUD_API_BASE;
|
|
295
|
+
const seenMessageIds = new Map();
|
|
296
|
+
const isDuplicateMessage = (id) => {
|
|
297
|
+
const now = Date.now();
|
|
298
|
+
for (const [mid, ts] of seenMessageIds) {
|
|
299
|
+
if (now - ts > WHATSAPP_CLOUD_MESSAGE_ID_TTL_MS)
|
|
300
|
+
seenMessageIds.delete(mid);
|
|
301
|
+
}
|
|
302
|
+
if (seenMessageIds.has(id))
|
|
303
|
+
return true;
|
|
304
|
+
seenMessageIds.set(id, now);
|
|
305
|
+
return false;
|
|
306
|
+
};
|
|
307
|
+
// GET handshake (opposite of Slack's signed-POST url_verification): Meta
|
|
308
|
+
// calls this once when the webhook subscription is configured, comparing
|
|
309
|
+
// our echoed `hub.challenge` against what it sent. Must respond with the
|
|
310
|
+
// RAW challenge string, not JSON-wrapped, on match.
|
|
311
|
+
const verify = (req, res) => {
|
|
312
|
+
const agentId = req.params.agentId;
|
|
313
|
+
const runner = resolveWhatsAppCloudAgent(agents, agentId);
|
|
314
|
+
const mode = req.query['hub.mode'];
|
|
315
|
+
const token = req.query['hub.verify_token'];
|
|
316
|
+
const challenge = req.query['hub.challenge'];
|
|
317
|
+
const expected = runner?.getAgentConfig().whatsapp_cloud?.verifyToken;
|
|
318
|
+
if (mode === 'subscribe' && expected && typeof token === 'string' && safeStringEqual(token, expected)) {
|
|
319
|
+
res.status(200).type('text/plain').send(String(challenge ?? ''));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
logger.warn('WhatsApp Cloud webhook: verify handshake failed', { agentId, mode });
|
|
323
|
+
res.status(403).json({ error: 'verification failed' });
|
|
324
|
+
};
|
|
325
|
+
const handlePost = async (req, res) => {
|
|
326
|
+
const agentId = req.params.agentId;
|
|
327
|
+
const runner = resolveWhatsAppCloudAgent(agents, agentId);
|
|
328
|
+
if (!runner) {
|
|
329
|
+
res.status(404).json({ error: 'no WhatsApp Cloud-enabled agent' });
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const cfg = runner.getAgentConfig().whatsapp_cloud;
|
|
333
|
+
const secret = cfg?.appSecret ?? '';
|
|
334
|
+
const buf = Buffer.isBuffer(req.body) ? req.body : Buffer.from('');
|
|
335
|
+
if (!verifyMetaSignature(buf, secret, req.header('x-hub-signature-256'))) {
|
|
336
|
+
logger.warn('WhatsApp Cloud webhook rejected: bad signature', { agentId: runner.getAgentConfig().id });
|
|
337
|
+
res.status(401).json({ error: 'invalid signature' });
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
let payload;
|
|
341
|
+
try {
|
|
342
|
+
payload = JSON.parse(buf.toString('utf8'));
|
|
343
|
+
}
|
|
344
|
+
catch (err) {
|
|
345
|
+
logger.warn('WhatsApp Cloud webhook: bad JSON', { error: err.message });
|
|
346
|
+
res.status(400).json({ error: 'bad JSON' });
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
// Ack immediately (Meta retries if our ack is slow/dropped) — process after.
|
|
350
|
+
res.status(200).json({ ok: true });
|
|
351
|
+
const deniedAgentId = runner.getAgentConfig().id;
|
|
352
|
+
for (const entry of payload.entry ?? []) {
|
|
353
|
+
for (const change of entry.changes ?? []) {
|
|
354
|
+
const value = change.value;
|
|
355
|
+
const messages = extractInboundMessages(value);
|
|
356
|
+
if (messages.length === 0)
|
|
357
|
+
continue; // status-only batch, or no messages field at all
|
|
358
|
+
// contacts[] carries profile.name synchronously, keyed by wa_id — used
|
|
359
|
+
// for the pending-knock display name with no extra API call needed
|
|
360
|
+
// (unlike Slack's getUserDisplayName round-trip).
|
|
361
|
+
const nameByWaId = new Map();
|
|
362
|
+
for (const c of value?.contacts ?? []) {
|
|
363
|
+
if (c.wa_id && c.profile?.name)
|
|
364
|
+
nameByWaId.set(c.wa_id, c.profile.name);
|
|
365
|
+
}
|
|
366
|
+
for (const msg of messages) {
|
|
367
|
+
if (msg.id && isDuplicateMessage(msg.id)) {
|
|
368
|
+
logger.debug('WhatsApp Cloud webhook: duplicate message id (Meta retry), skipping', { id: msg.id });
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const resolved = (0, whatsapp_cloud_access_1.resolveWhatsAppCloudSource)(msg.from);
|
|
372
|
+
if (resolved.kind === 'other' || !resolved.conversationId)
|
|
373
|
+
continue;
|
|
374
|
+
if (!(0, whatsapp_cloud_access_1.isResolvedWhatsAppCloudSourceAllowed)(cfg, resolved)) {
|
|
375
|
+
logger.debug('WhatsApp Cloud webhook: source not allowed', {
|
|
376
|
+
agentId: deniedAgentId,
|
|
377
|
+
policy: cfg?.dmPolicy ?? '(closed)',
|
|
378
|
+
conversationId: resolved.conversationId,
|
|
379
|
+
});
|
|
380
|
+
const knockId = resolved.senderId;
|
|
381
|
+
if (knockId) {
|
|
382
|
+
const isPairing = cfg?.pairing !== false && cfg?.dmPolicy !== 'open' && cfg?.dmPolicy !== 'disabled';
|
|
383
|
+
const prev = (0, pending_senders_1.getPendingSender)('whatsapp_cloud', deniedAgentId, knockId);
|
|
384
|
+
const code = prev?.code ?? (isPairing ? (0, pending_senders_1.generatePairingCode)() : undefined);
|
|
385
|
+
const displayName = nameByWaId.get(knockId);
|
|
386
|
+
const wasNew = (0, pending_senders_1.recordDeniedSender)('whatsapp_cloud', deniedAgentId, knockId, displayName, Date.now(), code);
|
|
387
|
+
// Send the pairing code exactly once — on first contact only.
|
|
388
|
+
if (isPairing && wasNew && code && cfg?.accessToken && cfg?.phoneNumberId) {
|
|
389
|
+
const client = new whatsapp_cloud_client_1.WhatsAppCloudClient({
|
|
390
|
+
accessToken: cfg.accessToken,
|
|
391
|
+
phoneNumberId: cfg.phoneNumberId,
|
|
392
|
+
logDir,
|
|
393
|
+
apiBase: opts.apiBase,
|
|
394
|
+
});
|
|
395
|
+
void client
|
|
396
|
+
.sendText(knockId, pairingMessage(code))
|
|
397
|
+
.catch((err) => logger.debug('WhatsApp Cloud pairing code reply failed', { error: err.message }));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
const norm = normalizeWhatsAppCloudMessage(msg, resolved);
|
|
403
|
+
if (!norm)
|
|
404
|
+
continue;
|
|
405
|
+
// Receipt signals (Phase 2), both strictly best-effort and both
|
|
406
|
+
// gated on config — mirrors slack-webhook-router.ts's
|
|
407
|
+
// `void client.addReaction(...).catch(() => {})` call site: fired
|
|
408
|
+
// right after the access gate, never awaited, never able to fail
|
|
409
|
+
// the turn. The ack reaction is cleared by the
|
|
410
|
+
// `whatsapp_cloud_reply` MCP tool once the agent's reply lands.
|
|
411
|
+
if (msg.id && cfg?.accessToken && cfg?.phoneNumberId) {
|
|
412
|
+
const receiptClient = new whatsapp_cloud_client_1.WhatsAppCloudClient({
|
|
413
|
+
accessToken: cfg.accessToken,
|
|
414
|
+
phoneNumberId: cfg.phoneNumberId,
|
|
415
|
+
logDir,
|
|
416
|
+
apiBase: opts.apiBase,
|
|
417
|
+
});
|
|
418
|
+
if (cfg.sendReadReceipts !== false) {
|
|
419
|
+
void receiptClient.markAsRead(msg.id).catch(() => { });
|
|
420
|
+
}
|
|
421
|
+
if ((cfg.reactionLevel ?? 'ack') === 'ack') {
|
|
422
|
+
void receiptClient.sendReaction(resolved.conversationId, msg.id).catch(() => { });
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// Inbound media (image AND document, both eager at receipt time):
|
|
426
|
+
// fetch the bytes with the access token and hand the agent an
|
|
427
|
+
// absolute path. Images use the existing meta.image_path contract;
|
|
428
|
+
// documents use the new meta.document_path key, but ONLY when the
|
|
429
|
+
// mime type passes MediaStore.isAllowedMime() (PDF only in
|
|
430
|
+
// practice today) — an unsupported document type is logged and
|
|
431
|
+
// skipped, but the turn still forwards with whatever caption it has.
|
|
432
|
+
//
|
|
433
|
+
// Stickers (Phase 2) ride the SAME download path — they are
|
|
434
|
+
// `image/webp` media, which MediaStore.isAllowedMime already
|
|
435
|
+
// permits (`image/` prefix) and sniffImageExt already recognizes
|
|
436
|
+
// (RIFF/WEBP magic bytes) — but land on their own
|
|
437
|
+
// meta.sticker_path key, never image_path, so the agent can tell a
|
|
438
|
+
// sticker apart from a photo.
|
|
439
|
+
if (cfg?.accessToken && (msg.type === 'image' || msg.type === 'document' || msg.type === 'sticker')) {
|
|
440
|
+
const mediaObj = msg.type === 'image' ? msg.image : msg.type === 'sticker' ? msg.sticker : msg.document;
|
|
441
|
+
if (mediaObj?.id) {
|
|
442
|
+
try {
|
|
443
|
+
const downloaded = await downloadWhatsAppCloudMedia(cfg.accessToken, mediaObj.id, apiBase);
|
|
444
|
+
if (downloaded) {
|
|
445
|
+
if (msg.type === 'image') {
|
|
446
|
+
norm.meta.image_path = writeTempImage(downloaded.buf, msg.id);
|
|
447
|
+
}
|
|
448
|
+
else if (msg.type === 'sticker') {
|
|
449
|
+
if (media_store_1.MediaStore.isAllowedMime(downloaded.mimeType)) {
|
|
450
|
+
norm.meta.sticker_path = writeTempImage(downloaded.buf, msg.id);
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
logger.warn('WhatsApp Cloud webhook: sticker mime type not allowed, skipping download', {
|
|
454
|
+
mimeType: downloaded.mimeType,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
else if (media_store_1.MediaStore.isAllowedMime(downloaded.mimeType)) {
|
|
459
|
+
norm.meta.document_path = writeTempDocument(downloaded.buf, downloaded.mimeType, mediaObj.filename, msg.id);
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
logger.warn('WhatsApp Cloud webhook: document mime type not allowed, skipping download', {
|
|
463
|
+
mimeType: downloaded.mimeType,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
catch (err) {
|
|
469
|
+
logger.warn('WhatsApp Cloud webhook: media download failed', {
|
|
470
|
+
mediaId: mediaObj.id,
|
|
471
|
+
error: err.message,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
try {
|
|
477
|
+
await fetch(`http://127.0.0.1:${runner.getCallbackPort()}/channel`, {
|
|
478
|
+
method: 'POST',
|
|
479
|
+
headers: { 'Content-Type': 'application/json' },
|
|
480
|
+
body: JSON.stringify(norm),
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
catch (err) {
|
|
484
|
+
logger.error('WhatsApp Cloud webhook: failed to forward to callback', {
|
|
485
|
+
error: err.message,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
return { verify, handlePost };
|
|
493
|
+
}
|
|
494
|
+
//# sourceMappingURL=whatsapp-cloud-webhook-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whatsapp-cloud-webhook-router.js","sourceRoot":"","sources":["../../src/api/whatsapp-cloud-webhook-router.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/args.d.ts
CHANGED
|
@@ -13,6 +13,40 @@ export interface ParsedArgs {
|
|
|
13
13
|
* them. `-h` is already honoured as a bare top-level token; without this it
|
|
14
14
|
* fell through to a positional, so `crons -h` reported "Unknown command". */
|
|
15
15
|
export declare const SHORT_ALIASES: Readonly<Record<string, string>>;
|
|
16
|
+
/**
|
|
17
|
+
* Names in `flags` that `known` does not declare.
|
|
18
|
+
*
|
|
19
|
+
* The parser has no schema, so an unrecognised flag parses fine and is then
|
|
20
|
+
* simply ignored — `app install foo --evn KEY=V` sent no env vars at all and
|
|
21
|
+
* exited 0, as though it had worked. Every command that builds its request or
|
|
22
|
+
* its behaviour from named flags therefore has to reject what it does not know
|
|
23
|
+
* rather than drop it; this is the one place that decides what "does not know"
|
|
24
|
+
* means, so the answer cannot drift between commands.
|
|
25
|
+
*/
|
|
26
|
+
export declare function unknownFlagNames(flags: Record<string, string | boolean>, known: ReadonlySet<string>): string[];
|
|
27
|
+
/**
|
|
28
|
+
* Split a `--flag K=V[,K=V...]` value into its pairs, or return null after
|
|
29
|
+
* writing the reason to stderr.
|
|
30
|
+
*
|
|
31
|
+
* Only the list shape is decided here — splitting on commas, ignoring empty
|
|
32
|
+
* entries, and requiring a non-empty name before the first `=`. What a valid
|
|
33
|
+
* name or value *is* differs per flag (`service --env` bans the variables the
|
|
34
|
+
* installer sets, `app --ports` wants a port number), so each caller validates
|
|
35
|
+
* its own pairs; this exists so they cannot disagree about the shape itself, or
|
|
36
|
+
* about how a missing value is reported.
|
|
37
|
+
*
|
|
38
|
+
* `raw` is `boolean` when the flag was passed with nothing after it — the
|
|
39
|
+
* schema-less parser reads `--env` at the end of a line as `true`. That is
|
|
40
|
+
* rejected rather than read as "not passed": silently treating it as omitted
|
|
41
|
+
* installs with none of the environment the caller believes they gave.
|
|
42
|
+
*
|
|
43
|
+
* `pairShape` names the expected form in the error message ("KEY=VALUE",
|
|
44
|
+
* "NAME=PORT").
|
|
45
|
+
*/
|
|
46
|
+
export declare function parseKeyValueList(flagName: string, raw: string | boolean | undefined, pairShape: string): Array<{
|
|
47
|
+
key: string;
|
|
48
|
+
value: string;
|
|
49
|
+
}> | null;
|
|
16
50
|
/**
|
|
17
51
|
* `booleanFlags` names flags that must never consume the next token as a value
|
|
18
52
|
* (e.g. `--force <positional>` should leave `<positional>` alone). Without this,
|
package/dist/cli/args.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;CACzC;AAED;;8EAE8E;AAC9E,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAA+B,CAAC;AAU3F;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,YAAY,GAAE,WAAW,CAAC,MAAM,CAAa,GAAG,UAAU,CA8BxG"}
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;CACzC;AAED;;8EAE8E;AAC9E,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAA+B,CAAC;AAU3F;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAE9G;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,EACjC,SAAS,EAAE,MAAM,GAChB,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,CAkB9C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,YAAY,GAAE,WAAW,CAAC,MAAM,CAAa,GAAG,UAAU,CA8BxG"}
|
package/dist/cli/args.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SHORT_ALIASES = void 0;
|
|
4
|
+
exports.unknownFlagNames = unknownFlagNames;
|
|
5
|
+
exports.parseKeyValueList = parseKeyValueList;
|
|
4
6
|
exports.parseCliArgs = parseCliArgs;
|
|
5
7
|
/** Single-dash aliases, expanded to the long name before anything else sees
|
|
6
8
|
* them. `-h` is already honoured as a bare top-level token; without this it
|
|
@@ -13,6 +15,59 @@ exports.SHORT_ALIASES = { h: 'help', V: 'version' };
|
|
|
13
15
|
function looksLikeFlag(token) {
|
|
14
16
|
return token.startsWith('--') || (token.length === 2 && token[0] === '-' && token[1] in exports.SHORT_ALIASES);
|
|
15
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Names in `flags` that `known` does not declare.
|
|
20
|
+
*
|
|
21
|
+
* The parser has no schema, so an unrecognised flag parses fine and is then
|
|
22
|
+
* simply ignored — `app install foo --evn KEY=V` sent no env vars at all and
|
|
23
|
+
* exited 0, as though it had worked. Every command that builds its request or
|
|
24
|
+
* its behaviour from named flags therefore has to reject what it does not know
|
|
25
|
+
* rather than drop it; this is the one place that decides what "does not know"
|
|
26
|
+
* means, so the answer cannot drift between commands.
|
|
27
|
+
*/
|
|
28
|
+
function unknownFlagNames(flags, known) {
|
|
29
|
+
return Object.keys(flags).filter((name) => !known.has(name));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Split a `--flag K=V[,K=V...]` value into its pairs, or return null after
|
|
33
|
+
* writing the reason to stderr.
|
|
34
|
+
*
|
|
35
|
+
* Only the list shape is decided here — splitting on commas, ignoring empty
|
|
36
|
+
* entries, and requiring a non-empty name before the first `=`. What a valid
|
|
37
|
+
* name or value *is* differs per flag (`service --env` bans the variables the
|
|
38
|
+
* installer sets, `app --ports` wants a port number), so each caller validates
|
|
39
|
+
* its own pairs; this exists so they cannot disagree about the shape itself, or
|
|
40
|
+
* about how a missing value is reported.
|
|
41
|
+
*
|
|
42
|
+
* `raw` is `boolean` when the flag was passed with nothing after it — the
|
|
43
|
+
* schema-less parser reads `--env` at the end of a line as `true`. That is
|
|
44
|
+
* rejected rather than read as "not passed": silently treating it as omitted
|
|
45
|
+
* installs with none of the environment the caller believes they gave.
|
|
46
|
+
*
|
|
47
|
+
* `pairShape` names the expected form in the error message ("KEY=VALUE",
|
|
48
|
+
* "NAME=PORT").
|
|
49
|
+
*/
|
|
50
|
+
function parseKeyValueList(flagName, raw, pairShape) {
|
|
51
|
+
if (raw === undefined)
|
|
52
|
+
return [];
|
|
53
|
+
if (typeof raw !== 'string' || raw.trim() === '') {
|
|
54
|
+
process.stderr.write(`--${flagName} requires a comma-separated list of ${pairShape} pairs.\n`);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const pairs = [];
|
|
58
|
+
for (const entry of raw.split(',')) {
|
|
59
|
+
const trimmed = entry.trim();
|
|
60
|
+
if (!trimmed)
|
|
61
|
+
continue;
|
|
62
|
+
const eq = trimmed.indexOf('=');
|
|
63
|
+
if (eq <= 0) {
|
|
64
|
+
process.stderr.write(`Invalid --${flagName} entry "${trimmed}" — expected ${pairShape}.\n`);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
pairs.push({ key: trimmed.slice(0, eq), value: trimmed.slice(eq + 1) });
|
|
68
|
+
}
|
|
69
|
+
return pairs;
|
|
70
|
+
}
|
|
16
71
|
/**
|
|
17
72
|
* `booleanFlags` names flags that must never consume the next token as a value
|
|
18
73
|
* (e.g. `--force <positional>` should leave `<positional>` alone). Without this,
|
package/dist/cli/args.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"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"}
|