@chatlychat/cli 0.1.7 → 0.3.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.
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +29 -3
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/listen-bot.d.ts +52 -0
- package/dist/commands/listen-bot.d.ts.map +1 -0
- package/dist/commands/listen-bot.js +253 -0
- package/dist/commands/listen-bot.js.map +1 -0
- package/dist/commands/listen-core.d.ts +107 -0
- package/dist/commands/listen-core.d.ts.map +1 -0
- package/dist/commands/listen-core.js +334 -0
- package/dist/commands/listen-core.js.map +1 -0
- package/dist/commands/listen-relay-options.d.ts +78 -0
- package/dist/commands/listen-relay-options.d.ts.map +1 -0
- package/dist/commands/listen-relay-options.js +195 -0
- package/dist/commands/listen-relay-options.js.map +1 -0
- package/dist/commands/listen-relay-sign.d.ts +36 -0
- package/dist/commands/listen-relay-sign.d.ts.map +1 -0
- package/dist/commands/listen-relay-sign.js +136 -0
- package/dist/commands/listen-relay-sign.js.map +1 -0
- package/dist/commands/listen-relay.d.ts +43 -0
- package/dist/commands/listen-relay.d.ts.map +1 -0
- package/dist/commands/listen-relay.js +434 -0
- package/dist/commands/listen-relay.js.map +1 -0
- package/dist/commands/listen.d.ts +20 -0
- package/dist/commands/listen.d.ts.map +1 -0
- package/dist/commands/listen.js +355 -0
- package/dist/commands/listen.js.map +1 -0
- package/dist/help.d.ts +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +28 -1
- package/dist/help.js.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +11 -1
- package/dist/run.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `chatly listen` does the same way for every surface it tails.
|
|
3
|
+
*
|
|
4
|
+
* ═══ WHY THIS FILE EXISTS ═════════════════════════════════════════════════
|
|
5
|
+
*
|
|
6
|
+
* `listen` tails two ledgers now — webhook subscriptions
|
|
7
|
+
* (`/v1/webhooks/deliveries`) and an external agent's push deliveries
|
|
8
|
+
* (`/v1/bots/{id}/deliveries`) — and will tail more. The poll loop differs
|
|
9
|
+
* per ledger; the flags, the seen-set, the signing and the forward leg do
|
|
10
|
+
* not. They are here so there is one of each, and so `listen.ts` stays under
|
|
11
|
+
* this repo's 650-line ceiling.
|
|
12
|
+
*
|
|
13
|
+
* ═══ THE SIGNATURE IT MINTS, AND THE SELF-CHECK ═══════════════════════════
|
|
14
|
+
*
|
|
15
|
+
* Neither ledger stores the signature Chatly sent — `webhook_deliveries` and
|
|
16
|
+
* `agent_deliveries` both store the PAYLOAD as `jsonb` and nothing else, and
|
|
17
|
+
* `jsonb` does not preserve key order, so the exact signed bytes are
|
|
18
|
+
* unreconstructable by construction rather than by omission. So a forwarded
|
|
19
|
+
* request is re-signed here, from `--secret`, and every forward carries TWO
|
|
20
|
+
* headers:
|
|
21
|
+
*
|
|
22
|
+
* - the surface's own legacy header (`x-livechat-signature` for an event
|
|
23
|
+
* webhook, `x-chatly-signature` for an agent delivery), `t=…,v1=…` over
|
|
24
|
+
* `<t>.<rawBody>` — what the platform sends today;
|
|
25
|
+
* - `x-chatly-signature-v2`, `t=…,v1=…` over
|
|
26
|
+
* `<t>.<METHOD>.<path>.<rawBody>` — the unified construction every
|
|
27
|
+
* surface is moving to (C-02). The platform does not send this on either
|
|
28
|
+
* surface yet, which is exactly why minting it locally is useful: a
|
|
29
|
+
* developer can write and test the verifier they will need before the
|
|
30
|
+
* sender moves.
|
|
31
|
+
*
|
|
32
|
+
* `path` in that base string is the path of the request being signed — the
|
|
33
|
+
* local `--forward-to` path, because that is the request the receiver
|
|
34
|
+
* verifies. Both headers are verified with the SDK's own
|
|
35
|
+
* `verifyChatlySignature` BEFORE the request goes out, and a forward whose
|
|
36
|
+
* own signature does not verify is refused rather than sent. That self-check
|
|
37
|
+
* is not ceremony: it is the one thing that catches a signer and a verifier
|
|
38
|
+
* drifting on construction or on timestamp unit, which is a bug this product
|
|
39
|
+
* has actually shipped — the scheme once emitted milliseconds in the header
|
|
40
|
+
* while signing a seconds value, and every correct handler rejected us.
|
|
41
|
+
*/
|
|
42
|
+
import { createHmac } from 'node:crypto';
|
|
43
|
+
import { chatlyV2SigningPath, signChatlyV2, verifyChatlySignature } from '@chatlychat/sdk-node';
|
|
44
|
+
import { bool, num, str } from '../args.js';
|
|
45
|
+
export const WEBHOOK_SIGN_PLAN = {
|
|
46
|
+
surface: 'event_webhook',
|
|
47
|
+
legacyHeader: 'x-livechat-signature',
|
|
48
|
+
timestampHeader: 'x-livechat-timestamp',
|
|
49
|
+
};
|
|
50
|
+
export const AGENT_SIGN_PLAN = {
|
|
51
|
+
surface: 'agent_delivery',
|
|
52
|
+
legacyHeader: 'x-chatly-signature',
|
|
53
|
+
timestampHeader: null,
|
|
54
|
+
};
|
|
55
|
+
/** Deliveries remembered before the oldest id is dropped. */
|
|
56
|
+
const SEEN_CAP = 5_000;
|
|
57
|
+
const DEFAULT_INTERVAL_SECONDS = 2;
|
|
58
|
+
const MIN_INTERVAL_SECONDS = 1;
|
|
59
|
+
const DEFAULT_PAGE_LIMIT = 50;
|
|
60
|
+
/**
|
|
61
|
+
* The four words both ledgers use, with the same meanings: `failed` will be
|
|
62
|
+
* retried and `dead` will not. Validated here because the bot ledger answers
|
|
63
|
+
* a **400** on a word outside the four rather than an empty page.
|
|
64
|
+
*/
|
|
65
|
+
export const DELIVERY_STATUSES = ['pending', 'delivered', 'failed', 'dead'];
|
|
66
|
+
/** Drop the oldest ids once the set is at its cap. Sets iterate in insertion order. */
|
|
67
|
+
export function remember(seen, id) {
|
|
68
|
+
seen.add(id);
|
|
69
|
+
if (seen.size > SEEN_CAP) {
|
|
70
|
+
for (const old of seen) {
|
|
71
|
+
seen.delete(old);
|
|
72
|
+
if (seen.size <= SEEN_CAP) {
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** `t=<unix seconds>,v1=<hex>` over `<t>.<body>`, the way the worker signs. */
|
|
79
|
+
function tv1(body, secret, t, prefix = '') {
|
|
80
|
+
const v1 = createHmac('sha256', secret).update(`${t}.${prefix}${body}`).digest('hex');
|
|
81
|
+
return `t=${t},v1=${v1}`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The signature headers for one forwarded request, self-checked.
|
|
85
|
+
*
|
|
86
|
+
* Returns the headers to merge, or a message naming what went wrong — which
|
|
87
|
+
* can only be a bug in this CLI, since it signed the bytes itself moments
|
|
88
|
+
* earlier. See this file's header for why both headers are minted.
|
|
89
|
+
*/
|
|
90
|
+
export function signedForwardHeaders(input) {
|
|
91
|
+
const t = Math.floor(input.nowMs / 1000).toString();
|
|
92
|
+
const { plan, body, secret, nowMs } = input;
|
|
93
|
+
const legacy = tv1(body, secret, t);
|
|
94
|
+
const legacyCheck = verifyChatlySignature({
|
|
95
|
+
surface: plan.surface,
|
|
96
|
+
body,
|
|
97
|
+
headers: { [plan.legacyHeader]: legacy },
|
|
98
|
+
secret,
|
|
99
|
+
nowMs,
|
|
100
|
+
// The v2 header is minted below and lives in the same bag on the wire;
|
|
101
|
+
// this check is specifically of the LEGACY construction, so the policy
|
|
102
|
+
// that would prefer v2 has to be off.
|
|
103
|
+
v2: 'ignore',
|
|
104
|
+
});
|
|
105
|
+
if (!legacyCheck.ok) {
|
|
106
|
+
return { ok: false, message: `${legacyCheck.reason}: ${legacyCheck.message}` };
|
|
107
|
+
}
|
|
108
|
+
const headers = { [plan.legacyHeader]: legacy };
|
|
109
|
+
if (plan.timestampHeader !== null) {
|
|
110
|
+
headers[plan.timestampHeader] = t;
|
|
111
|
+
}
|
|
112
|
+
/*
|
|
113
|
+
* The unified v2 header. `method` is POST because that is what this
|
|
114
|
+
* forwards, and `path` is the target's own path — the receiver verifies
|
|
115
|
+
* the request IT received, so the path in the digest has to be the one on
|
|
116
|
+
* that request line.
|
|
117
|
+
*/
|
|
118
|
+
const path = chatlyV2SigningPath(input.targetUrl);
|
|
119
|
+
if (path === null) {
|
|
120
|
+
return { ok: false, message: `--forward-to "${input.targetUrl}" is not a URL` };
|
|
121
|
+
}
|
|
122
|
+
/*
|
|
123
|
+
* Through the shared signer, so this emits the `b=t` base marker.
|
|
124
|
+
*
|
|
125
|
+
* It used to build the value by hand, and its own docblock said "the
|
|
126
|
+
* platform does not send this on either surface yet" — which is now false
|
|
127
|
+
* for both. Worse, a hand-rolled value carries no marker, and an absent
|
|
128
|
+
* marker means the LEGACY `<t>.<rawBody>` construction: a receiver reading
|
|
129
|
+
* `b=` to decide what to verify would have been told, by the one tool whose
|
|
130
|
+
* job is to rehearse production, that the unified base is the body base.
|
|
131
|
+
*
|
|
132
|
+
* From `@chatlychat/sdk-node`, NOT from `@livechat/shared`. This package
|
|
133
|
+
* builds with plain `tsc` and publishes `dist` declaring only the SDK, so
|
|
134
|
+
* an import of the internal scope would emit a reference to a package that
|
|
135
|
+
* is not on npm and `npm i -g @chatlychat/cli` would fail at run time with
|
|
136
|
+
* MODULE_NOT_FOUND — a break no test here would see, because in the
|
|
137
|
+
* monorepo the import resolves. The SDK's copy is held byte-for-byte
|
|
138
|
+
* against the one the API and worker send with by
|
|
139
|
+
* `packages/shared/src/webhook-signature/v2.parity.spec.ts`, so this still
|
|
140
|
+
* teaches exactly the shape production sends.
|
|
141
|
+
*/
|
|
142
|
+
const v2 = signChatlyV2({
|
|
143
|
+
base: 'target',
|
|
144
|
+
secret,
|
|
145
|
+
timestampSeconds: Number(t),
|
|
146
|
+
method: 'POST',
|
|
147
|
+
path,
|
|
148
|
+
body,
|
|
149
|
+
});
|
|
150
|
+
const v2Check = verifyChatlySignature({
|
|
151
|
+
surface: 'unified_v2',
|
|
152
|
+
body,
|
|
153
|
+
headers: { 'x-chatly-signature-v2': v2 },
|
|
154
|
+
secret,
|
|
155
|
+
method: 'POST',
|
|
156
|
+
path,
|
|
157
|
+
nowMs,
|
|
158
|
+
});
|
|
159
|
+
if (!v2Check.ok) {
|
|
160
|
+
return { ok: false, message: `${v2Check.reason}: ${v2Check.message}` };
|
|
161
|
+
}
|
|
162
|
+
headers['x-chatly-signature-v2'] = v2;
|
|
163
|
+
return { ok: true, headers };
|
|
164
|
+
}
|
|
165
|
+
/** POST one event on, verifying the headers we just minted before sending. */
|
|
166
|
+
export async function forward(opts, deps, req) {
|
|
167
|
+
if (opts.forwardTo === null) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
const headers = {
|
|
171
|
+
'content-type': 'application/json',
|
|
172
|
+
...req.headers,
|
|
173
|
+
// Not a header Chatly sends. A handler that logs it can tell a replayed
|
|
174
|
+
// local forward from a real delivery, which matters the first time
|
|
175
|
+
// somebody points this at a staging environment.
|
|
176
|
+
'x-chatly-cli-forwarded': '1',
|
|
177
|
+
};
|
|
178
|
+
if (opts.secret !== null) {
|
|
179
|
+
const signed = signedForwardHeaders({
|
|
180
|
+
plan: req.plan,
|
|
181
|
+
body: req.body,
|
|
182
|
+
secret: opts.secret,
|
|
183
|
+
nowMs: deps.now(),
|
|
184
|
+
targetUrl: opts.forwardTo,
|
|
185
|
+
});
|
|
186
|
+
if (!signed.ok) {
|
|
187
|
+
deps.err(`refusing to forward ${req.id}: the signature this CLI minted does not verify ` +
|
|
188
|
+
`(${signed.message}). This is a bug in chatly listen, not in your handler — ` +
|
|
189
|
+
'please report it.\n');
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
Object.assign(headers, signed.headers);
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const res = await deps.fetchFn(opts.forwardTo, {
|
|
196
|
+
method: 'POST',
|
|
197
|
+
headers,
|
|
198
|
+
body: req.body,
|
|
199
|
+
signal: deps.signal,
|
|
200
|
+
});
|
|
201
|
+
if (!res.ok) {
|
|
202
|
+
deps.err(` ↳ ${opts.forwardTo} answered ${String(res.status)}. ` +
|
|
203
|
+
(res.status === 401 || res.status === 403
|
|
204
|
+
? 'That is usually signature verification — check --secret matches the endpoint.\n'
|
|
205
|
+
: '\n'));
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
deps.out(` ↳ ${String(res.status)} from ${opts.forwardTo}`);
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
213
|
+
deps.err(` ↳ ${opts.forwardTo} unreachable: ${reason}. Is your server running on that port?\n`);
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function parseEventFilter(args) {
|
|
218
|
+
const raw = args.flags['event'];
|
|
219
|
+
if (raw === undefined) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
const list = (Array.isArray(raw) ? raw : [String(raw)]).flatMap((v) => v
|
|
223
|
+
.split(',')
|
|
224
|
+
.map((s) => s.trim())
|
|
225
|
+
.filter((s) => s.length > 0));
|
|
226
|
+
return list.length === 0 ? null : new Set(list);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Read the flags, refusing anything that cannot work, with the fix named.
|
|
230
|
+
*
|
|
231
|
+
* The two secret prefixes are the one refusal worth reading twice: a webhook
|
|
232
|
+
* endpoint's secret is `whsec_` and an external agent's is `chsec_`, they are
|
|
233
|
+
* minted by different routes, and neither verifies the other's traffic. The
|
|
234
|
+
* symptom of crossing them is a 401 from your own handler, which looks
|
|
235
|
+
* exactly like a verifier bug.
|
|
236
|
+
*/
|
|
237
|
+
export function parseListenOptions(args) {
|
|
238
|
+
const forwardTo = str(args.flags, 'forward-to') ?? null;
|
|
239
|
+
if (forwardTo !== null) {
|
|
240
|
+
let parsed = null;
|
|
241
|
+
try {
|
|
242
|
+
parsed = new URL(forwardTo);
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
parsed = null;
|
|
246
|
+
}
|
|
247
|
+
/*
|
|
248
|
+
* One message for both failures, because `localhost:3000` — the mistake
|
|
249
|
+
* everyone makes — hits the SECOND branch, not the first: the WHATWG
|
|
250
|
+
* parser reads `localhost:` as a SCHEME and succeeds. A test caught this
|
|
251
|
+
* command telling a developer who typed `localhost:3000` that the scheme
|
|
252
|
+
* `"localhost:"` was unsupported, which is true and useless.
|
|
253
|
+
*/
|
|
254
|
+
if (parsed === null || (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')) {
|
|
255
|
+
throw new Error(`--forward-to "${forwardTo}" is not an http(s) URL${parsed === null ? '' : ` (it parsed as the scheme "${parsed.protocol}")`}. Include the scheme:\n` + ' chatly listen --forward-to http://localhost:3000/webhooks');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const botId = str(args.flags, 'bot') ?? null;
|
|
259
|
+
const endpointId = str(args.flags, 'endpoint') ?? null;
|
|
260
|
+
if (botId !== null && endpointId !== null) {
|
|
261
|
+
throw new Error('--bot and --endpoint tail different ledgers and cannot be combined. --bot <id> tails one ' +
|
|
262
|
+
"external agent's push deliveries; --endpoint <id> tails one webhook subscription's.");
|
|
263
|
+
}
|
|
264
|
+
const intervalSeconds = num(args.flags, 'interval') ?? DEFAULT_INTERVAL_SECONDS;
|
|
265
|
+
if (intervalSeconds < MIN_INTERVAL_SECONDS) {
|
|
266
|
+
throw new Error(`--interval ${String(intervalSeconds)} is below the ${String(MIN_INTERVAL_SECONDS)}s floor. ` +
|
|
267
|
+
'This polls a real API on your own rate limit; a tighter loop buys nothing, because the ' +
|
|
268
|
+
'delivery row is written after the attempt completes.');
|
|
269
|
+
}
|
|
270
|
+
const pageLimit = num(args.flags, 'limit') ?? DEFAULT_PAGE_LIMIT;
|
|
271
|
+
if (pageLimit < 1) {
|
|
272
|
+
throw new Error(`--limit ${String(pageLimit)} must be at least 1.`);
|
|
273
|
+
}
|
|
274
|
+
const replay = num(args.flags, 'replay') ?? 0;
|
|
275
|
+
if (replay < 0) {
|
|
276
|
+
throw new Error(`--replay ${String(replay)} cannot be negative.`);
|
|
277
|
+
}
|
|
278
|
+
if (replay > pageLimit) {
|
|
279
|
+
throw new Error(`--replay ${String(replay)} is more than --limit ${String(pageLimit)}, and this command ` +
|
|
280
|
+
'only ever reads one page per poll. Raise --limit to match.');
|
|
281
|
+
}
|
|
282
|
+
const status = str(args.flags, 'status') ?? null;
|
|
283
|
+
if (botId !== null && status !== null && !DELIVERY_STATUSES.includes(status)) {
|
|
284
|
+
// The route validates this against a zod enum and answers 400, not an
|
|
285
|
+
// empty page. Refusing here names the four words.
|
|
286
|
+
throw new Error(`--status "${status}" is not one of ${DELIVERY_STATUSES.join(', ')}. ` +
|
|
287
|
+
'`failed` will be retried; `dead` will not.');
|
|
288
|
+
}
|
|
289
|
+
const secret = str(args.flags, 'secret') ?? null;
|
|
290
|
+
if (secret !== null && botId === null && !secret.startsWith('whsec_')) {
|
|
291
|
+
throw new Error(`--secret should be the endpoint's whsec_ signing secret (got "${secret.slice(0, 6)}…").\n` +
|
|
292
|
+
'It is returned exactly once, by `chatly webhooks create`, and by no other route — if ' +
|
|
293
|
+
'you no longer have it, create another endpoint.\n' +
|
|
294
|
+
"With --bot, pass the AGENT's chsec_ secret instead; the two do not verify each other's " +
|
|
295
|
+
'traffic.');
|
|
296
|
+
}
|
|
297
|
+
if (secret !== null && botId !== null && !secret.startsWith('chsec_')) {
|
|
298
|
+
throw new Error(`--secret should be the external agent's chsec_ signing secret (got "${secret.slice(0, 6)}…").\n` +
|
|
299
|
+
'It is shown once by `chatly bots create` and once by `chatly bots rotate-secret <id>`, ' +
|
|
300
|
+
'and no read route returns it.\n' +
|
|
301
|
+
"A webhook endpoint's whsec_ secret signs a different surface and will not verify here.");
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
forwardTo,
|
|
305
|
+
botId,
|
|
306
|
+
endpointId,
|
|
307
|
+
events: parseEventFilter(args),
|
|
308
|
+
status,
|
|
309
|
+
secret,
|
|
310
|
+
intervalMs: intervalSeconds * 1000,
|
|
311
|
+
replay,
|
|
312
|
+
pageLimit,
|
|
313
|
+
json: bool(args.flags, 'json'),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/** An abortable sleep, so Ctrl-C does not wait out the poll interval. */
|
|
317
|
+
export function sleepUntilAborted(ms, signal) {
|
|
318
|
+
return new Promise((resolve) => {
|
|
319
|
+
if (signal.aborted) {
|
|
320
|
+
resolve();
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const timer = setTimeout(() => {
|
|
324
|
+
signal.removeEventListener('abort', onAbort);
|
|
325
|
+
resolve();
|
|
326
|
+
}, ms);
|
|
327
|
+
function onAbort() {
|
|
328
|
+
clearTimeout(timer);
|
|
329
|
+
resolve();
|
|
330
|
+
}
|
|
331
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
//# sourceMappingURL=listen-core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listen-core.js","sourceRoot":"","sources":["../../src/commands/listen-core.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The flags and the wire shapes of `chatly listen --relay`.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `listen-relay.ts` for the 650-line ceiling. What is parsed
|
|
5
|
+
* here, and why each refusal exists, is documented on each function; the
|
|
6
|
+
* protocol the shapes belong to is `apps/cli/RELAY-SERVER-SPEC.md`.
|
|
7
|
+
*/
|
|
8
|
+
import { type RelayKind } from './listen-relay-sign.js';
|
|
9
|
+
import { type ParsedArgs } from '../args.js';
|
|
10
|
+
export interface RelaySecrets {
|
|
11
|
+
/** `--secret <value>`: for every target without its own. */
|
|
12
|
+
readonly fallback: string | null;
|
|
13
|
+
/** `--secret <targetId>=<value>`. */
|
|
14
|
+
readonly byTarget: ReadonlyMap<string, string>;
|
|
15
|
+
}
|
|
16
|
+
export interface RelayOptions {
|
|
17
|
+
readonly forwardTo: string;
|
|
18
|
+
readonly tools: readonly string[];
|
|
19
|
+
readonly cards: readonly string[];
|
|
20
|
+
readonly canvases: readonly string[];
|
|
21
|
+
readonly secrets: RelaySecrets;
|
|
22
|
+
readonly json: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** One relayed call, as `GET /v1/relay-sessions/{id}/frames` renders it. */
|
|
25
|
+
export interface RelayFrame {
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly kind: RelayKind;
|
|
28
|
+
readonly targetId: string;
|
|
29
|
+
readonly method: string;
|
|
30
|
+
/** `pathname + search` of the CONFIGURED url. */
|
|
31
|
+
readonly path: string;
|
|
32
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
33
|
+
/** Exactly the bytes the configured url would have received. `''` for none. */
|
|
34
|
+
readonly body: string;
|
|
35
|
+
/** When the waiting caller gives up. The CLI aborts the forward then. */
|
|
36
|
+
readonly deadlineAt: string;
|
|
37
|
+
}
|
|
38
|
+
/** What the CLI posts back. Exactly one of the two arms. */
|
|
39
|
+
export type RelayAnswer = {
|
|
40
|
+
readonly status: number;
|
|
41
|
+
readonly headers: Record<string, string>;
|
|
42
|
+
readonly body: string;
|
|
43
|
+
readonly durationMs: number;
|
|
44
|
+
} | {
|
|
45
|
+
readonly error: 'unreachable' | 'timeout' | 'too_large' | 'not_selected' | 'signing_failed' | 'interrupted';
|
|
46
|
+
readonly message: string;
|
|
47
|
+
readonly durationMs: number;
|
|
48
|
+
};
|
|
49
|
+
/** `--tool`, `--card` or `--canvas` on a command that is not `--relay`. */
|
|
50
|
+
export declare function relayOnlyFlagWithoutRelay(args: ParsedArgs): string | null;
|
|
51
|
+
/** Read the relay flags, refusing anything that cannot work, with the fix named. */
|
|
52
|
+
export declare function parseRelayOptions(args: ParsedArgs): RelayOptions;
|
|
53
|
+
/**
|
|
54
|
+
* Where one frame goes: `--forward-to`'s origin and path prefix, then the
|
|
55
|
+
* CONFIGURED url's path and query.
|
|
56
|
+
*
|
|
57
|
+
* The path is preserved because for a tool call it IS the request —
|
|
58
|
+
* `GET /orders/{{order_id}}` renders to `/orders/42`, and a handler that
|
|
59
|
+
* cannot see the 42 cannot answer. Cards and canvases are kept the same way
|
|
60
|
+
* so there is one rule, and a local server mounted under a prefix still works.
|
|
61
|
+
*/
|
|
62
|
+
export declare function relayTargetUrl(forwardTo: string, framePath: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* The frames on one poll, and how many were unreadable.
|
|
65
|
+
*
|
|
66
|
+
* Hand-checked rather than zod, because this package declares the SDK as its
|
|
67
|
+
* only dependency (CLAUDE.md §2: no new dependency without approval). An
|
|
68
|
+
* unreadable frame is COUNTED, not silently dropped — the server is waiting
|
|
69
|
+
* on it, and a relay that loses frames quietly reads as a slow handler.
|
|
70
|
+
*/
|
|
71
|
+
export declare function readFrames(value: unknown): {
|
|
72
|
+
frames: RelayFrame[];
|
|
73
|
+
unreadable: number;
|
|
74
|
+
};
|
|
75
|
+
/** Which selected set a frame's kind is checked against. */
|
|
76
|
+
export declare function selectedFor(opts: RelayOptions, kind: RelayKind): readonly string[];
|
|
77
|
+
export declare function secretFor(opts: RelayOptions, targetId: string): string | null;
|
|
78
|
+
//# sourceMappingURL=listen-relay-options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listen-relay-options.d.ts","sourceRoot":"","sources":["../../src/commands/listen-relay-options.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAIxD,MAAM,WAAW,YAAY;IAC3B,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,qCAAqC;IACrC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,4EAA4E;AAC5E,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,4DAA4D;AAC5D,MAAM,MAAM,WAAW,GACnB;IACE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B,GACD;IACE,QAAQ,CAAC,KAAK,EACV,aAAa,GACb,SAAS,GACT,WAAW,GACX,cAAc,GACd,gBAAgB,GAChB,aAAa,CAAC;IAClB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B,CAAC;AAaN,2EAA2E;AAC3E,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAOzE;AAqDD,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,YAAY,CA6DhE;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAQ3E;AAWD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CA8BvF;AAED,4DAA4D;AAC5D,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,GAAG,SAAS,MAAM,EAAE,CAMlF;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE7E"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The flags and the wire shapes of `chatly listen --relay`.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `listen-relay.ts` for the 650-line ceiling. What is parsed
|
|
5
|
+
* here, and why each refusal exists, is documented on each function; the
|
|
6
|
+
* protocol the shapes belong to is `apps/cli/RELAY-SERVER-SPEC.md`.
|
|
7
|
+
*/
|
|
8
|
+
import { RELAY_KINDS } from './listen-relay-sign.js';
|
|
9
|
+
import { bool, str } from '../args.js';
|
|
10
|
+
const ID_FLAGS = ['tool', 'card', 'canvas'];
|
|
11
|
+
function listFlag(args, name) {
|
|
12
|
+
const raw = args.flags[name];
|
|
13
|
+
if (raw === undefined || raw === true || raw === false) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
return (Array.isArray(raw) ? raw : [String(raw)])
|
|
17
|
+
.flatMap((v) => String(v).split(','))
|
|
18
|
+
.map((s) => s.trim())
|
|
19
|
+
.filter((s) => s.length > 0);
|
|
20
|
+
}
|
|
21
|
+
/** `--tool`, `--card` or `--canvas` on a command that is not `--relay`. */
|
|
22
|
+
export function relayOnlyFlagWithoutRelay(args) {
|
|
23
|
+
for (const name of ID_FLAGS) {
|
|
24
|
+
if (args.flags[name] !== undefined) {
|
|
25
|
+
return name;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
/** The ledger-tail flags, none of which mean anything on a relay. */
|
|
31
|
+
const LEDGER_ONLY_FLAGS = ['bot', 'endpoint', 'event', 'status', 'replay', 'interval', 'limit'];
|
|
32
|
+
function parseSecrets(args, ids) {
|
|
33
|
+
const raw = args.flags['secret'];
|
|
34
|
+
const values = raw === undefined || typeof raw === 'boolean' ? [] : Array.isArray(raw) ? raw : [raw];
|
|
35
|
+
let fallback = null;
|
|
36
|
+
const byTarget = new Map();
|
|
37
|
+
for (const value of values.map(String)) {
|
|
38
|
+
/*
|
|
39
|
+
* `<id>=<secret>` only when the left side IS one of the selected ids. A
|
|
40
|
+
* secret is operator-chosen on a tool, so it may itself contain `=`
|
|
41
|
+
* (standard base64 padding), and splitting every value on the first one
|
|
42
|
+
* would turn that secret into a target id nobody asked for.
|
|
43
|
+
*/
|
|
44
|
+
const eq = value.indexOf('=');
|
|
45
|
+
const left = eq > 0 ? value.slice(0, eq) : '';
|
|
46
|
+
if (left.length > 0 && ids.has(left)) {
|
|
47
|
+
byTarget.set(left, value.slice(eq + 1));
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (fallback !== null) {
|
|
51
|
+
throw new Error('Two bare --secret values. With more than one target, name which secret is whose:\n' +
|
|
52
|
+
' --secret <id>=<secret> (repeatable)');
|
|
53
|
+
}
|
|
54
|
+
fallback = value;
|
|
55
|
+
}
|
|
56
|
+
for (const secret of [fallback, ...byTarget.values()]) {
|
|
57
|
+
if (secret === null) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
/*
|
|
61
|
+
* The two prefixes this product mints for OTHER surfaces. Neither signs
|
|
62
|
+
* a tool call, a card fetch or a canvas step, and the symptom of passing
|
|
63
|
+
* one is a 401 from your own handler that looks like a verifier bug.
|
|
64
|
+
*/
|
|
65
|
+
if (secret.startsWith('whsec_') || secret.startsWith('chsec_')) {
|
|
66
|
+
throw new Error(`--secret "${secret.slice(0, 6)}…" is a ${secret.startsWith('whsec_') ? 'webhook endpoint' : 'external agent'} secret, and it signs a different surface. A relay needs the secret configured on ` +
|
|
67
|
+
"the tool, the customer card (or its endpoint), or the channel's canvas.");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { fallback, byTarget };
|
|
71
|
+
}
|
|
72
|
+
/** Read the relay flags, refusing anything that cannot work, with the fix named. */
|
|
73
|
+
export function parseRelayOptions(args) {
|
|
74
|
+
for (const name of LEDGER_ONLY_FLAGS) {
|
|
75
|
+
if (args.flags[name] !== undefined) {
|
|
76
|
+
throw new Error(`--${name} tails a delivery ledger and means nothing on --relay, which receives live ` +
|
|
77
|
+
'calls instead of replaying logged ones.');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const tools = listFlag(args, 'tool');
|
|
81
|
+
const cards = listFlag(args, 'card');
|
|
82
|
+
const canvases = listFlag(args, 'canvas');
|
|
83
|
+
if (tools.length + cards.length + canvases.length === 0) {
|
|
84
|
+
throw new Error('--relay needs at least one explicit target, because it takes PRODUCTION traffic:\n' +
|
|
85
|
+
' --tool <id> an AI tool (chatly ai tools list)\n' +
|
|
86
|
+
' --card <id> a customer card (chatly customer-cards list)\n' +
|
|
87
|
+
" --canvas <channelId> a channel's canvas endpoint\n" +
|
|
88
|
+
'There is deliberately no form that relays everything.');
|
|
89
|
+
}
|
|
90
|
+
const forwardTo = str(args.flags, 'forward-to') ?? null;
|
|
91
|
+
if (forwardTo === null) {
|
|
92
|
+
throw new Error('--relay needs --forward-to. A relayed call is waiting for an answer, so there is no ' +
|
|
93
|
+
'print-only mode:\n chatly listen --relay --tool <id> --forward-to http://localhost:3000');
|
|
94
|
+
}
|
|
95
|
+
let parsed;
|
|
96
|
+
try {
|
|
97
|
+
parsed = new URL(forwardTo);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
parsed = null;
|
|
101
|
+
}
|
|
102
|
+
// `localhost:3000` parses, as the scheme `localhost:` — see listen-core.ts.
|
|
103
|
+
if (parsed === null || (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')) {
|
|
104
|
+
throw new Error(`--forward-to "${forwardTo}" is not an http(s) URL. Include the scheme:\n` +
|
|
105
|
+
' --forward-to http://localhost:3000');
|
|
106
|
+
}
|
|
107
|
+
if (parsed.search !== '' || parsed.hash !== '') {
|
|
108
|
+
// The configured url's own query is appended (see relayTargetUrl), and
|
|
109
|
+
// merging two query strings would sign one the handler does not receive.
|
|
110
|
+
throw new Error(`--forward-to "${forwardTo}" has a query or fragment. Give an origin, optionally with a ` +
|
|
111
|
+
"path prefix; each call's own path and query are appended to it.");
|
|
112
|
+
}
|
|
113
|
+
const ids = new Set([...tools, ...cards, ...canvases]);
|
|
114
|
+
return {
|
|
115
|
+
forwardTo,
|
|
116
|
+
tools,
|
|
117
|
+
cards,
|
|
118
|
+
canvases,
|
|
119
|
+
secrets: parseSecrets(args, ids),
|
|
120
|
+
json: bool(args.flags, 'json'),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Where one frame goes: `--forward-to`'s origin and path prefix, then the
|
|
125
|
+
* CONFIGURED url's path and query.
|
|
126
|
+
*
|
|
127
|
+
* The path is preserved because for a tool call it IS the request —
|
|
128
|
+
* `GET /orders/{{order_id}}` renders to `/orders/42`, and a handler that
|
|
129
|
+
* cannot see the 42 cannot answer. Cards and canvases are kept the same way
|
|
130
|
+
* so there is one rule, and a local server mounted under a prefix still works.
|
|
131
|
+
*/
|
|
132
|
+
export function relayTargetUrl(forwardTo, framePath) {
|
|
133
|
+
const base = new URL(forwardTo);
|
|
134
|
+
const prefix = base.pathname.replace(/\/+$/, '');
|
|
135
|
+
const rel = framePath.startsWith('/') ? framePath : `/${framePath}`;
|
|
136
|
+
const q = rel.indexOf('?');
|
|
137
|
+
const pathname = q === -1 ? rel : rel.slice(0, q);
|
|
138
|
+
const search = q === -1 ? '' : rel.slice(q);
|
|
139
|
+
return `${base.origin}${prefix}${pathname}${search}`;
|
|
140
|
+
}
|
|
141
|
+
function isStringRecord(value) {
|
|
142
|
+
return (typeof value === 'object' &&
|
|
143
|
+
value !== null &&
|
|
144
|
+
!Array.isArray(value) &&
|
|
145
|
+
Object.values(value).every((v) => typeof v === 'string'));
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The frames on one poll, and how many were unreadable.
|
|
149
|
+
*
|
|
150
|
+
* Hand-checked rather than zod, because this package declares the SDK as its
|
|
151
|
+
* only dependency (CLAUDE.md §2: no new dependency without approval). An
|
|
152
|
+
* unreadable frame is COUNTED, not silently dropped — the server is waiting
|
|
153
|
+
* on it, and a relay that loses frames quietly reads as a slow handler.
|
|
154
|
+
*/
|
|
155
|
+
export function readFrames(value) {
|
|
156
|
+
const list = typeof value === 'object' && value !== null
|
|
157
|
+
? value.frames
|
|
158
|
+
: undefined;
|
|
159
|
+
if (!Array.isArray(list)) {
|
|
160
|
+
return { frames: [], unreadable: 0 };
|
|
161
|
+
}
|
|
162
|
+
const frames = [];
|
|
163
|
+
let unreadable = 0;
|
|
164
|
+
for (const f of list) {
|
|
165
|
+
const r = (typeof f === 'object' && f !== null ? f : {});
|
|
166
|
+
if (typeof r['id'] === 'string' &&
|
|
167
|
+
typeof r['kind'] === 'string' &&
|
|
168
|
+
RELAY_KINDS.includes(r['kind']) &&
|
|
169
|
+
typeof r['targetId'] === 'string' &&
|
|
170
|
+
typeof r['method'] === 'string' &&
|
|
171
|
+
typeof r['path'] === 'string' &&
|
|
172
|
+
isStringRecord(r['headers']) &&
|
|
173
|
+
typeof r['body'] === 'string' &&
|
|
174
|
+
typeof r['deadlineAt'] === 'string' &&
|
|
175
|
+
!Number.isNaN(Date.parse(r['deadlineAt']))) {
|
|
176
|
+
frames.push(r);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
unreadable += 1;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { frames, unreadable };
|
|
183
|
+
}
|
|
184
|
+
/** Which selected set a frame's kind is checked against. */
|
|
185
|
+
export function selectedFor(opts, kind) {
|
|
186
|
+
return kind === 'ai_tool_call'
|
|
187
|
+
? opts.tools
|
|
188
|
+
: kind === 'customer_card'
|
|
189
|
+
? opts.cards
|
|
190
|
+
: opts.canvases;
|
|
191
|
+
}
|
|
192
|
+
export function secretFor(opts, targetId) {
|
|
193
|
+
return opts.secrets.byTarget.get(targetId) ?? opts.secrets.fallback;
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=listen-relay-options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listen-relay-options.js","sourceRoot":"","sources":["../../src/commands/listen-relay-options.ts"],"names":[],"mappings":"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"}
|