@chatlychat/cli 0.2.0 → 0.4.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-harness.d.ts +80 -0
- package/dist/commands/listen-relay-harness.d.ts.map +1 -0
- package/dist/commands/listen-relay-harness.js +163 -0
- package/dist/commands/listen-relay-harness.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 +2 -39
- package/dist/commands/listen.d.ts.map +1 -1
- package/dist/commands/listen.js +73 -208
- package/dist/commands/listen.js.map +1 -1
- package/dist/help.d.ts +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +19 -3
- package/dist/help.js.map +1 -1
- package/dist/run.js +1 -1
- package/package.json +3 -4
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chatly listen --relay` — receive Chatly's SYNCHRONOUS calls on localhost.
|
|
3
|
+
*
|
|
4
|
+
* ═══ WHY A LEDGER TAIL CANNOT DO THIS ═════════════════════════════════════
|
|
5
|
+
*
|
|
6
|
+
* `listen` and `listen --bot` tail delivery LOGS: the platform has already
|
|
7
|
+
* made the request, and the CLI replays a copy. Three surfaces have no such
|
|
8
|
+
* log and could not use one if they did — an AI tool call, a customer-card
|
|
9
|
+
* fetch and a canvas step. Each is made INSIDE a visitor's request: Chatly
|
|
10
|
+
* calls the configured URL and waits for the answer, which is the point of
|
|
11
|
+
* the call (the tool result, the card's content, the next step). A copy that
|
|
12
|
+
* arrives afterwards is useless, because by then the visitor has been shown
|
|
13
|
+
* the configured URL's answer, or its timeout.
|
|
14
|
+
*
|
|
15
|
+
* So `--relay` opens an outbound session and, while it lives, the server
|
|
16
|
+
* sends calls for the ids named on the command line to this process instead
|
|
17
|
+
* of to the configured URL. The CLI forwards each to `--forward-to`, re-signed
|
|
18
|
+
* (`listen-relay-sign.ts` says why the server does not sign), and posts the
|
|
19
|
+
* answer back, and the server hands that answer to the waiting caller as if
|
|
20
|
+
* the configured URL had given it.
|
|
21
|
+
*
|
|
22
|
+
* ═══ WHICH HALF OF THIS IS REAL ═══════════════════════════════════════════
|
|
23
|
+
*
|
|
24
|
+
* BOTH. The server half is `apps/api/src/modules/relay/` (shipped 2026-10-15),
|
|
25
|
+
* built against `apps/cli/RELAY-SERVER-SPEC.md`, which stays the contract: if
|
|
26
|
+
* the two disagree, the spec is right and this is the bug. The wire types are
|
|
27
|
+
* `@livechat/shared/relay`, and `scripts/check-relay-protocol-parity.mjs`
|
|
28
|
+
* checks the fields this file reads against them.
|
|
29
|
+
*
|
|
30
|
+
* `explainMissingServer` stays anyway. A CLI release reaches users before an
|
|
31
|
+
* API deploy does, and a 404 on the first request is then the route being
|
|
32
|
+
* absent, not a typo — it needs a sentence that says nothing was redirected,
|
|
33
|
+
* rather than a bare "Not Found". (Chatly is a managed service, so there is no
|
|
34
|
+
* older self-hosted API to blame; the window is the deploy ordering.)
|
|
35
|
+
*
|
|
36
|
+
* ═══ WHY LONG-POLL AND NOT A WEBSOCKET ════════════════════════════════════
|
|
37
|
+
*
|
|
38
|
+
* The CLI declares one dependency, the published SDK, and runs on Node 20,
|
|
39
|
+
* which has no WebSocket client without a package. Server-Sent Events would
|
|
40
|
+
* need a second channel for the answers anyway. A long-poll goes through the
|
|
41
|
+
* SDK's own transport — auth, retries, typed errors — and its lease semantics
|
|
42
|
+
* are the teardown rule: a session nobody has polled for 30 seconds is dead.
|
|
43
|
+
* The poll waits 10 seconds because the SDK's request timeout is 15.
|
|
44
|
+
*
|
|
45
|
+
* ═══ THE TWO SAFETY RULES, AND WHERE THEY LIVE ════════════════════════════
|
|
46
|
+
*
|
|
47
|
+
* 1. **Explicit ids.** `--tool`, `--card`, `--canvas` — there is no
|
|
48
|
+
* "everything" form, because a relay takes PRODUCTION traffic. A frame
|
|
49
|
+
* for an id that was not asked for is answered with an error rather than
|
|
50
|
+
* forwarded: that would be a server bug routing a stranger's visitor to a
|
|
51
|
+
* developer's laptop, and the laptop is the last place it can be stopped.
|
|
52
|
+
* 2. **10 minutes idle.** Enforced by the server (authoritative) AND here,
|
|
53
|
+
* so a forgotten terminal detaches itself and says so. It is not a flag:
|
|
54
|
+
* a longer one is how a relay gets forgotten.
|
|
55
|
+
*/
|
|
56
|
+
import { hostname, userInfo } from 'node:os';
|
|
57
|
+
import { ChatlyApiError } from '@chatlychat/sdk-node';
|
|
58
|
+
import { secretFor, selectedFor, relayTargetUrl, readFrames, } from './listen-relay-options.js';
|
|
59
|
+
import { RELAY_OWNED_HEADERS, signRelayedRequest } from './listen-relay-sign.js';
|
|
60
|
+
import { printJson } from '../output.js';
|
|
61
|
+
export { parseRelayOptions, readFrames, relayOnlyFlagWithoutRelay, relayTargetUrl, } from './listen-relay-options.js';
|
|
62
|
+
/** Detach after this long with no frame. See the header, rule 2. */
|
|
63
|
+
export const RELAY_IDLE_TIMEOUT_MS = 10 * 60_000;
|
|
64
|
+
/** Long-poll wait. Under the SDK transport's 15s request timeout. */
|
|
65
|
+
export const RELAY_POLL_WAIT_SECONDS = 10;
|
|
66
|
+
/** A local answer larger than this is refused rather than relayed. */
|
|
67
|
+
export const RELAY_MAX_RESPONSE_BYTES = 1_048_576;
|
|
68
|
+
/** The protocol version this CLI speaks, sent on attach. */
|
|
69
|
+
export const RELAY_PROTOCOL_VERSION = 1;
|
|
70
|
+
/**
|
|
71
|
+
* `relayAnswerSchema` caps an error answer's `message` at 2,000 characters.
|
|
72
|
+
* An `unreachable` message embeds the OS error and the URL, so a long one is
|
|
73
|
+
* possible — and over the cap the server answers 422, the answer is never
|
|
74
|
+
* recorded, and the visitor waits out the whole deadline instead of failing
|
|
75
|
+
* fast. Clamped here rather than trusting the message to be short.
|
|
76
|
+
*/
|
|
77
|
+
const MAX_ERROR_MESSAGE_CHARS = 2_000;
|
|
78
|
+
/** Backoff ceiling for a poll that failed to reach the server. */
|
|
79
|
+
const MAX_BACKOFF_MS = 8_000;
|
|
80
|
+
/** The forward leg of `answerFrame`, before the message cap is applied. */
|
|
81
|
+
async function answerFrameUnclamped(opts, deps, frame, signal) {
|
|
82
|
+
const started = deps.now();
|
|
83
|
+
const elapsed = () => Math.max(0, deps.now() - started);
|
|
84
|
+
if (!selectedFor(opts, frame.kind).includes(frame.targetId)) {
|
|
85
|
+
return {
|
|
86
|
+
error: 'not_selected',
|
|
87
|
+
message: `this relay did not ask for ${frame.kind} ${frame.targetId}`,
|
|
88
|
+
durationMs: 0,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const url = relayTargetUrl(opts.forwardTo, frame.path);
|
|
92
|
+
const headers = {};
|
|
93
|
+
for (const [name, value] of Object.entries(frame.headers)) {
|
|
94
|
+
if (!RELAY_OWNED_HEADERS.has(name.toLowerCase())) {
|
|
95
|
+
headers[name.toLowerCase()] = value;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Not a header Chatly sends; see listen-core.ts.
|
|
99
|
+
headers['x-chatly-cli-forwarded'] = '1';
|
|
100
|
+
const secret = secretFor(opts, frame.targetId);
|
|
101
|
+
if (secret !== null) {
|
|
102
|
+
const signed = signRelayedRequest({
|
|
103
|
+
kind: frame.kind,
|
|
104
|
+
method: frame.method,
|
|
105
|
+
targetUrl: url,
|
|
106
|
+
body: frame.body,
|
|
107
|
+
secret,
|
|
108
|
+
nowMs: deps.now(),
|
|
109
|
+
});
|
|
110
|
+
if (!signed.ok) {
|
|
111
|
+
return {
|
|
112
|
+
error: 'signing_failed',
|
|
113
|
+
message: `the signature chatly listen minted does not verify (${signed.message})`,
|
|
114
|
+
durationMs: 0,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
Object.assign(headers, signed.headers);
|
|
118
|
+
}
|
|
119
|
+
// Abort at the caller's deadline: after it, nobody is waiting for this.
|
|
120
|
+
const budget = Date.parse(frame.deadlineAt) - deps.now();
|
|
121
|
+
const ctrl = new AbortController();
|
|
122
|
+
const onAbort = () => {
|
|
123
|
+
ctrl.abort();
|
|
124
|
+
};
|
|
125
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
126
|
+
let timedOut = false;
|
|
127
|
+
const timer = setTimeout(() => {
|
|
128
|
+
timedOut = true;
|
|
129
|
+
ctrl.abort();
|
|
130
|
+
}, Math.max(0, budget));
|
|
131
|
+
try {
|
|
132
|
+
const method = frame.method.toUpperCase();
|
|
133
|
+
const res = await deps.fetchFn(url, {
|
|
134
|
+
method,
|
|
135
|
+
headers,
|
|
136
|
+
...(method === 'GET' || method === 'HEAD' ? {} : { body: frame.body }),
|
|
137
|
+
signal: ctrl.signal,
|
|
138
|
+
});
|
|
139
|
+
const text = await res.text();
|
|
140
|
+
if (Buffer.byteLength(text, 'utf8') > RELAY_MAX_RESPONSE_BYTES) {
|
|
141
|
+
return {
|
|
142
|
+
error: 'too_large',
|
|
143
|
+
message: `your handler answered more than ${String(RELAY_MAX_RESPONSE_BYTES)} bytes`,
|
|
144
|
+
durationMs: elapsed(),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const contentType = res.headers.get('content-type');
|
|
148
|
+
return {
|
|
149
|
+
status: res.status,
|
|
150
|
+
headers: contentType === null ? {} : { 'content-type': contentType },
|
|
151
|
+
body: text,
|
|
152
|
+
durationMs: elapsed(),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
if (timedOut) {
|
|
157
|
+
return {
|
|
158
|
+
error: 'timeout',
|
|
159
|
+
message: `${url} did not answer before the caller's deadline (${frame.deadlineAt})`,
|
|
160
|
+
durationMs: elapsed(),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (signal.aborted) {
|
|
164
|
+
return { error: 'interrupted', message: 'the relay was stopped', durationMs: elapsed() };
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
error: 'unreachable',
|
|
168
|
+
message: `${url} unreachable: ${err instanceof Error ? err.message : String(err)}`,
|
|
169
|
+
durationMs: elapsed(),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
clearTimeout(timer);
|
|
174
|
+
signal.removeEventListener('abort', onAbort);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Forward one frame to localhost and build the answer. Never throws: every
|
|
179
|
+
* failure is an answer, because the server is holding a visitor's request
|
|
180
|
+
* open until it gets one.
|
|
181
|
+
*/
|
|
182
|
+
export async function answerFrame(opts, deps, frame, signal) {
|
|
183
|
+
const answer = await answerFrameUnclamped(opts, deps, frame, signal);
|
|
184
|
+
return 'error' in answer && answer.message.length > MAX_ERROR_MESSAGE_CHARS
|
|
185
|
+
? { ...answer, message: `${answer.message.slice(0, MAX_ERROR_MESSAGE_CHARS - 1)}…` }
|
|
186
|
+
: answer;
|
|
187
|
+
}
|
|
188
|
+
/** A promise that settles to `null` on abort, so Ctrl-C does not wait out a long-poll. */
|
|
189
|
+
function raceAbort(p, signal) {
|
|
190
|
+
if (signal.aborted) {
|
|
191
|
+
p.catch(() => undefined);
|
|
192
|
+
return Promise.resolve(null);
|
|
193
|
+
}
|
|
194
|
+
return new Promise((resolve, reject) => {
|
|
195
|
+
const onAbort = () => {
|
|
196
|
+
resolve(null);
|
|
197
|
+
};
|
|
198
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
199
|
+
p.then((v) => {
|
|
200
|
+
signal.removeEventListener('abort', onAbort);
|
|
201
|
+
resolve(v);
|
|
202
|
+
}, (e) => {
|
|
203
|
+
signal.removeEventListener('abort', onAbort);
|
|
204
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
/** `relay_ended_<reason>` → `<reason>`, per RELAY-SERVER-SPEC.md §4. */
|
|
209
|
+
function endReasonFromCode(code) {
|
|
210
|
+
return code !== null && code.startsWith('relay_ended_')
|
|
211
|
+
? code.slice('relay_ended_'.length)
|
|
212
|
+
: 'ended';
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* The 404 a server without this feature answers, as a sentence.
|
|
216
|
+
*
|
|
217
|
+
* Exported for the spec. A CLI released ahead of the API deploy gets this, so
|
|
218
|
+
* it has to say plainly that the route is missing rather than that the user
|
|
219
|
+
* typed something wrong.
|
|
220
|
+
*/
|
|
221
|
+
export function explainMissingServer(err) {
|
|
222
|
+
/*
|
|
223
|
+
* 404 can only mean the ROUTE: the spec answers an unknown target id with
|
|
224
|
+
* 422 `relay_target_not_found`, precisely so this branch cannot mistake a
|
|
225
|
+
* typo'd id for a missing feature (RELAY-SERVER-SPEC.md §3.1).
|
|
226
|
+
*/
|
|
227
|
+
if (err instanceof ChatlyApiError && (err.status === 404 || err.status === 405)) {
|
|
228
|
+
return new Error('This Chatly API has no relay route (POST /v1/relay-sessions answered ' +
|
|
229
|
+
`${String(err.status)}). This API has not been deployed with the relay yet, or ` +
|
|
230
|
+
'this CLI is newer than the API it talks to. Tool calls, card fetches and canvas steps still go to their configured ' +
|
|
231
|
+
'URLs — nothing was redirected.');
|
|
232
|
+
}
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
function attachLabel() {
|
|
236
|
+
// `userInfo()` throws when the uid has no passwd entry (some containers).
|
|
237
|
+
let user = 'unknown';
|
|
238
|
+
try {
|
|
239
|
+
user = userInfo().username;
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
user = 'unknown';
|
|
243
|
+
}
|
|
244
|
+
return `chatly-cli ${user}@${hostname()}`;
|
|
245
|
+
}
|
|
246
|
+
function readSession(value) {
|
|
247
|
+
const r = (typeof value === 'object' && value !== null ? value : {});
|
|
248
|
+
if (typeof r['id'] !== 'string') {
|
|
249
|
+
throw new Error('POST /v1/relay-sessions answered without a session id; refusing to poll.');
|
|
250
|
+
}
|
|
251
|
+
const by = (typeof r['attachedBy'] === 'object' && r['attachedBy'] !== null ? r['attachedBy'] : {});
|
|
252
|
+
const who = typeof by['displayName'] === 'string' ? by['displayName'] : 'this API key';
|
|
253
|
+
return { id: r['id'], attachedBy: who };
|
|
254
|
+
}
|
|
255
|
+
function banner(opts, session, deps) {
|
|
256
|
+
deps.out(`Relay ${session.id} attached as ${session.attachedBy}.`);
|
|
257
|
+
deps.out('PRODUCTION calls for these targets now come to this terminal instead of their URLs:');
|
|
258
|
+
for (const id of opts.tools) {
|
|
259
|
+
deps.out(` tool ${id}`);
|
|
260
|
+
}
|
|
261
|
+
for (const id of opts.cards) {
|
|
262
|
+
deps.out(` card ${id}`);
|
|
263
|
+
}
|
|
264
|
+
for (const id of opts.canvases) {
|
|
265
|
+
deps.out(` canvas ${id} (channel)`);
|
|
266
|
+
}
|
|
267
|
+
deps.out(`Forwarding to ${opts.forwardTo} (each call's own path is appended).`);
|
|
268
|
+
const unsigned = [...opts.tools, ...opts.cards, ...opts.canvases].filter((id) => secretFor(opts, id) === null);
|
|
269
|
+
if (unsigned.length > 0) {
|
|
270
|
+
deps.out(`UNSIGNED for ${unsigned.join(', ')}: no --secret, so your handler's verifier will ` +
|
|
271
|
+
'reject those calls and the visitor will see the failure.');
|
|
272
|
+
}
|
|
273
|
+
deps.out('The dashboard shows a banner while this is attached. Detaches after 10 minutes with no ' +
|
|
274
|
+
'call, or on Ctrl-C.\n');
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* The relay loop. Exported for the spec, bounded by `deps.maxPolls` there.
|
|
278
|
+
*
|
|
279
|
+
* Frames are answered CONCURRENTLY: each one has its own caller waiting with
|
|
280
|
+
* its own deadline, and a slow tool call must not make a card fetch behind it
|
|
281
|
+
* time out.
|
|
282
|
+
*/
|
|
283
|
+
export async function relay(chatly, opts, deps) {
|
|
284
|
+
let created;
|
|
285
|
+
try {
|
|
286
|
+
created = await chatly.request('POST', '/v1/relay-sessions', {
|
|
287
|
+
body: {
|
|
288
|
+
protocol: RELAY_PROTOCOL_VERSION,
|
|
289
|
+
targets: { tools: opts.tools, customerCards: opts.cards, canvasChannels: opts.canvases },
|
|
290
|
+
idleTimeoutSeconds: RELAY_IDLE_TIMEOUT_MS / 1000,
|
|
291
|
+
label: attachLabel(),
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
throw explainMissingServer(err) ?? err;
|
|
297
|
+
}
|
|
298
|
+
const session = readSession(created);
|
|
299
|
+
banner(opts, session, deps);
|
|
300
|
+
const base = `/v1/relay-sessions/${encodeURIComponent(session.id)}`;
|
|
301
|
+
const forwards = new AbortController();
|
|
302
|
+
const stopForwards = () => {
|
|
303
|
+
forwards.abort();
|
|
304
|
+
};
|
|
305
|
+
deps.signal.addEventListener('abort', stopForwards, { once: true });
|
|
306
|
+
const inflight = new Set();
|
|
307
|
+
let frames = 0;
|
|
308
|
+
let answered = 0;
|
|
309
|
+
let failed = 0;
|
|
310
|
+
let polls = 0;
|
|
311
|
+
let failures = 0;
|
|
312
|
+
let lastActivity = deps.now();
|
|
313
|
+
let endReason = 'interrupted';
|
|
314
|
+
let endedByServer = false;
|
|
315
|
+
const handle = async (frame) => {
|
|
316
|
+
const answer = await answerFrame(opts, deps, frame, forwards.signal);
|
|
317
|
+
const line = 'error' in answer
|
|
318
|
+
? ` ↳ ${answer.error}: ${answer.message}`
|
|
319
|
+
: ` ↳ ${String(answer.status)} in ${String(answer.durationMs)}ms`;
|
|
320
|
+
if ('error' in answer) {
|
|
321
|
+
failed += 1;
|
|
322
|
+
deps.err(`${line}\n`);
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
// A 4xx/5xx from the handler is still an ANSWER: it is relayed, and the
|
|
326
|
+
// surface treats it exactly as it treats one from the configured url.
|
|
327
|
+
answered += 1;
|
|
328
|
+
if (answer.status >= 400) {
|
|
329
|
+
deps.err(`${line}\n`);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
deps.out(line);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
await chatly.request('POST', `${base}/frames/${encodeURIComponent(frame.id)}/response`, {
|
|
337
|
+
body: answer,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
// 410: the deadline passed or the session ended first. The caller has
|
|
342
|
+
// already been answered with the surface's own timeout.
|
|
343
|
+
const why = err instanceof ChatlyApiError ? `${String(err.status)} ${err.code ?? ''}` : String(err);
|
|
344
|
+
deps.err(` ↳ the answer to ${frame.id} was not accepted (${why.trim()})\n`);
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
try {
|
|
348
|
+
for (;;) {
|
|
349
|
+
if (deps.signal.aborted) {
|
|
350
|
+
endReason = 'interrupted';
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
if (deps.maxPolls !== undefined && polls >= deps.maxPolls) {
|
|
354
|
+
endReason = 'max_polls';
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
if (deps.now() - lastActivity >= RELAY_IDLE_TIMEOUT_MS) {
|
|
358
|
+
endReason = 'idle';
|
|
359
|
+
deps.out('No call for 10 minutes; detaching so this relay cannot be forgotten.');
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
polls += 1;
|
|
363
|
+
let page;
|
|
364
|
+
try {
|
|
365
|
+
page = await raceAbort(chatly.request('GET', `${base}/frames`, {
|
|
366
|
+
query: { waitSeconds: RELAY_POLL_WAIT_SECONDS },
|
|
367
|
+
}), deps.signal);
|
|
368
|
+
}
|
|
369
|
+
catch (err) {
|
|
370
|
+
if (err instanceof ChatlyApiError && (err.status === 410 || err.status === 404)) {
|
|
371
|
+
endedByServer = true;
|
|
372
|
+
endReason = err.status === 404 ? 'not_found' : endReasonFromCode(err.code);
|
|
373
|
+
deps.err(`The server ended this relay (${endReason}).\n`);
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
/*
|
|
377
|
+
* Network or 5xx after the SDK's own retries. Keep trying, with
|
|
378
|
+
* backoff: if this outlasts the server's 30s lease the session is
|
|
379
|
+
* torn down there and the next successful poll answers 410, which is
|
|
380
|
+
* the exit. The CLI does not guess the lease itself.
|
|
381
|
+
*/
|
|
382
|
+
failures += 1;
|
|
383
|
+
deps.err(`poll failed (${err instanceof Error ? err.message : String(err)}); retrying. ` +
|
|
384
|
+
'Calls for these targets fail until it recovers.\n');
|
|
385
|
+
await deps.sleep(Math.min(MAX_BACKOFF_MS, 500 * 2 ** (failures - 1)), deps.signal);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (page === null) {
|
|
389
|
+
continue; // aborted mid-poll; the top of the loop sees it
|
|
390
|
+
}
|
|
391
|
+
failures = 0;
|
|
392
|
+
const { frames: fresh, unreadable } = readFrames(page);
|
|
393
|
+
if (unreadable > 0) {
|
|
394
|
+
deps.err(`${String(unreadable)} frame(s) this CLI could not read and could not answer — a newer ` +
|
|
395
|
+
'server protocol than this CLI speaks? Upgrade @chatlychat/cli.\n');
|
|
396
|
+
}
|
|
397
|
+
for (const frame of fresh) {
|
|
398
|
+
frames += 1;
|
|
399
|
+
lastActivity = deps.now();
|
|
400
|
+
if (opts.json) {
|
|
401
|
+
printJson(frame);
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
deps.out(`${frame.kind} ${frame.targetId} ${frame.method.toUpperCase()} ${frame.path} ${frame.id}`);
|
|
405
|
+
}
|
|
406
|
+
const p = handle(frame).finally(() => {
|
|
407
|
+
inflight.delete(p);
|
|
408
|
+
});
|
|
409
|
+
inflight.add(p);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
finally {
|
|
414
|
+
deps.signal.removeEventListener('abort', stopForwards);
|
|
415
|
+
/*
|
|
416
|
+
* In-flight forwards are NOT aborted on a graceful end (idle, the server
|
|
417
|
+
* ending it): each has a caller waiting with its own deadline, and an
|
|
418
|
+
* answer that arrives is better than an `interrupted` one. Ctrl-C aborts
|
|
419
|
+
* them through `stopForwards`, which is the developer asking.
|
|
420
|
+
*/
|
|
421
|
+
await Promise.allSettled([...inflight]);
|
|
422
|
+
if (!endedByServer) {
|
|
423
|
+
// Best effort: the server's lease tears it down anyway if this fails.
|
|
424
|
+
try {
|
|
425
|
+
await chatly.request('DELETE', base);
|
|
426
|
+
}
|
|
427
|
+
catch {
|
|
428
|
+
deps.err('Could not detach cleanly; the server will drop the relay within 30s.\n');
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return { frames, answered, failed, polls, endReason };
|
|
433
|
+
}
|
|
434
|
+
//# sourceMappingURL=listen-relay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listen-relay.js","sourceRoot":"","sources":["../../src/commands/listen-relay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE7C,OAAO,EAAE,cAAc,EAAqB,MAAM,sBAAsB,CAAC;AAEzE,OAAO,EACL,SAAS,EACT,WAAW,EACX,cAAc,EACd,UAAU,GAIX,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAIzC,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,yBAAyB,EACzB,cAAc,GAKf,MAAM,2BAA2B,CAAC;AAEnC,oEAAoE;AACpE,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,GAAG,MAAM,CAAC;AACjD,qEAAqE;AACrE,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAC1C,sEAAsE;AACtE,MAAM,CAAC,MAAM,wBAAwB,GAAG,SAAS,CAAC;AAClD,4DAA4D;AAC5D,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC;AACxC;;;;;;GAMG;AACH,MAAM,uBAAuB,GAAG,KAAK,CAAC;AACtC,kEAAkE;AAClE,MAAM,cAAc,GAAG,KAAK,CAAC;AAW7B,2EAA2E;AAC3E,KAAK,UAAU,oBAAoB,CACjC,IAAkB,EAClB,IAAgB,EAChB,KAAiB,EACjB,MAAmB;IAEnB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAG,GAAW,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC;IAEhE,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5D,OAAO;YACL,KAAK,EAAE,cAAc;YACrB,OAAO,EAAE,8BAA8B,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,QAAQ,EAAE;YACrE,UAAU,EAAE,CAAC;SACd,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1D,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YACjD,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,GAAG,KAAK,CAAC;QACtC,CAAC;IACH,CAAC;IACD,iDAAiD;IACjD,OAAO,CAAC,wBAAwB,CAAC,GAAG,GAAG,CAAC;IAExC,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC/C,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,kBAAkB,CAAC;YAChC,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,SAAS,EAAE,GAAG;YACd,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM;YACN,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE;SAClB,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,OAAO;gBACL,KAAK,EAAE,gBAAgB;gBACvB,OAAO,EAAE,uDAAuD,MAAM,CAAC,OAAO,GAAG;gBACjF,UAAU,EAAE,CAAC;aACd,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IAED,wEAAwE;IACxE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzD,MAAM,IAAI,GAAG,IAAI,eAAe,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC,CAAC;IACF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,MAAM,KAAK,GAAG,UAAU,CACtB,GAAG,EAAE;QACH,QAAQ,GAAG,IAAI,CAAC;QAChB,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC,EACD,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CACpB,CAAC;IACF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAC1C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE;YAClC,MAAM;YACN,OAAO;YACP,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;YACtE,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,wBAAwB,EAAE,CAAC;YAC/D,OAAO;gBACL,KAAK,EAAE,WAAW;gBAClB,OAAO,EAAE,mCAAmC,MAAM,CAAC,wBAAwB,CAAC,QAAQ;gBACpF,UAAU,EAAE,OAAO,EAAE;aACtB,CAAC;QACJ,CAAC;QACD,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACpD,OAAO;YACL,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,WAAW,EAAE;YACpE,IAAI,EAAE,IAAI;YACV,UAAU,EAAE,OAAO,EAAE;SACtB,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO;gBACL,KAAK,EAAE,SAAS;gBAChB,OAAO,EAAE,GAAG,GAAG,iDAAiD,KAAK,CAAC,UAAU,GAAG;gBACnF,UAAU,EAAE,OAAO,EAAE;aACtB,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QAC3F,CAAC;QACD,OAAO;YACL,KAAK,EAAE,aAAa;YACpB,OAAO,EAAE,GAAG,GAAG,iBAAiB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;YAClF,UAAU,EAAE,OAAO,EAAE;SACtB,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAkB,EAClB,IAAgB,EAChB,KAAiB,EACjB,MAAmB;IAEnB,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACrE,OAAO,OAAO,IAAI,MAAM,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,uBAAuB;QACzE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,uBAAuB,GAAG,CAAC,CAAC,GAAG,EAAE;QACpF,CAAC,CAAC,MAAM,CAAC;AACb,CAAC;AAED,0FAA0F;AAC1F,SAAS,SAAS,CAAI,CAAa,EAAE,MAAmB;IACtD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACzB,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,IAAI,OAAO,CAAW,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC/C,MAAM,OAAO,GAAG,GAAS,EAAE;YACzB,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC,CAAC;QACF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,CAAC,CAAC,IAAI,CACJ,CAAC,CAAC,EAAE,EAAE;YACJ,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,OAAO,CAAC,CAAC,CAAC,CAAC;QACb,CAAC,EACD,CAAC,CAAU,EAAE,EAAE;YACb,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7C,MAAM,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACxD,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,wEAAwE;AACxE,SAAS,iBAAiB,CAAC,IAAmB;IAC5C,OAAO,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC;QACrD,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC;QACnC,CAAC,CAAC,OAAO,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC/C;;;;OAIG;IACH,IAAI,GAAG,YAAY,cAAc,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,EAAE,CAAC;QAChF,OAAO,IAAI,KAAK,CACd,uEAAuE;YACrE,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,2DAA2D;YAChF,qHAAqH;YACrH,gCAAgC,CACnC,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,WAAW;IAClB,0EAA0E;IAC1E,IAAI,IAAI,GAAG,SAAS,CAAC;IACrB,IAAI,CAAC;QACH,IAAI,GAAG,QAAQ,EAAE,CAAC,QAAQ,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,cAAc,IAAI,IAAI,QAAQ,EAAE,EAAE,CAAC;AAC5C,CAAC;AAOD,SAAS,WAAW,CAAC,KAAc;IACjC,MAAM,CAAC,GAAG,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAA4B,CAAC;IAChG,IAAI,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;IAC9F,CAAC;IACD,MAAM,EAAE,GAAG,CACT,OAAO,CAAC,CAAC,YAAY,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EAAE,CAC5D,CAAC;IAC7B,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC,aAAa,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;IACvF,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC;AAC1C,CAAC;AAED,SAAS,MAAM,CAAC,IAAkB,EAAE,OAAmB,EAAE,IAAgB;IACvE,IAAI,CAAC,GAAG,CAAC,SAAS,OAAO,CAAC,EAAE,gBAAgB,OAAO,CAAC,UAAU,GAAG,CAAC,CAAC;IACnE,IAAI,CAAC,GAAG,CAAC,qFAAqF,CAAC,CAAC;IAChG,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC5B,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IAC9B,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC5B,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IAC9B,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IACzC,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,iBAAiB,IAAI,CAAC,SAAS,sCAAsC,CAAC,CAAC;IAChF,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CACtE,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,CACrC,CAAC;IACF,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,IAAI,CAAC,GAAG,CACN,gBAAgB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,iDAAiD;YAClF,0DAA0D,CAC7D,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,GAAG,CACN,yFAAyF;QACvF,uBAAuB,CAC1B,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CACzB,MAAoB,EACpB,IAAkB,EAClB,IAAgB;IAEhB,IAAI,OAAgB,CAAC;IACrB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAU,MAAM,EAAE,oBAAoB,EAAE;YACpE,IAAI,EAAE;gBACJ,QAAQ,EAAE,sBAAsB;gBAChC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,CAAC,QAAQ,EAAE;gBACxF,kBAAkB,EAAE,qBAAqB,GAAG,IAAI;gBAChD,KAAK,EAAE,WAAW,EAAE;aACrB;SACF,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,oBAAoB,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC;IACzC,CAAC;IACD,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAE5B,MAAM,IAAI,GAAG,sBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;IACpE,MAAM,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC;IACvC,MAAM,YAAY,GAAG,GAAS,EAAE;QAC9B,QAAQ,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC,CAAC;IACF,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAEpE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAiB,CAAC;IAC1C,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC9B,IAAI,SAAS,GAAG,aAAa,CAAC;IAC9B,IAAI,aAAa,GAAG,KAAK,CAAC;IAE1B,MAAM,MAAM,GAAG,KAAK,EAAE,KAAiB,EAAiB,EAAE;QACxD,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QACrE,MAAM,IAAI,GACR,OAAO,IAAI,MAAM;YACf,CAAC,CAAC,QAAQ,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,OAAO,EAAE;YAC3C,CAAC,CAAC,QAAQ,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC;QACxE,IAAI,OAAO,IAAI,MAAM,EAAE,CAAC;YACtB,MAAM,IAAI,CAAC,CAAC;YACZ,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;QACxB,CAAC;aAAM,CAAC;YACN,wEAAwE;YACxE,sEAAsE;YACtE,QAAQ,IAAI,CAAC,CAAC;YACd,IAAI,MAAM,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;gBACzB,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;YACxB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACjB,CAAC;QACH,CAAC;QACD,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,WAAW,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,EAAE;gBACtF,IAAI,EAAE,MAAM;aACb,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,sEAAsE;YACtE,wDAAwD;YACxD,MAAM,GAAG,GACP,GAAG,YAAY,cAAc,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC1F,IAAI,CAAC,GAAG,CAAC,sBAAsB,KAAK,CAAC,EAAE,sBAAsB,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAChF,CAAC;IACH,CAAC,CAAC;IAEF,IAAI,CAAC;QACH,SAAS,CAAC;YACR,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACxB,SAAS,GAAG,aAAa,CAAC;gBAC1B,MAAM;YACR,CAAC;YACD,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC1D,SAAS,GAAG,WAAW,CAAC;gBACxB,MAAM;YACR,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,IAAI,qBAAqB,EAAE,CAAC;gBACvD,SAAS,GAAG,MAAM,CAAC;gBACnB,IAAI,CAAC,GAAG,CAAC,sEAAsE,CAAC,CAAC;gBACjF,MAAM;YACR,CAAC;YACD,KAAK,IAAI,CAAC,CAAC;YAEX,IAAI,IAAa,CAAC;YAClB,IAAI,CAAC;gBACH,IAAI,GAAG,MAAM,SAAS,CACpB,MAAM,CAAC,OAAO,CAAU,KAAK,EAAE,GAAG,IAAI,SAAS,EAAE;oBAC/C,KAAK,EAAE,EAAE,WAAW,EAAE,uBAAuB,EAAE;iBAChD,CAAC,EACF,IAAI,CAAC,MAAM,CACZ,CAAC;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,GAAG,YAAY,cAAc,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,EAAE,CAAC;oBAChF,aAAa,GAAG,IAAI,CAAC;oBACrB,SAAS,GAAG,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBAC3E,IAAI,CAAC,GAAG,CAAC,gCAAgC,SAAS,MAAM,CAAC,CAAC;oBAC1D,MAAM;gBACR,CAAC;gBACD;;;;;mBAKG;gBACH,QAAQ,IAAI,CAAC,CAAC;gBACd,IAAI,CAAC,GAAG,CACN,gBAAgB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe;oBAC7E,mDAAmD,CACtD,CAAC;gBACF,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;gBACnF,SAAS;YACX,CAAC;YACD,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBAClB,SAAS,CAAC,gDAAgD;YAC5D,CAAC;YACD,QAAQ,GAAG,CAAC,CAAC;YAEb,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;YACvD,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;gBACnB,IAAI,CAAC,GAAG,CACN,GAAG,MAAM,CAAC,UAAU,CAAC,mEAAmE;oBACtF,kEAAkE,CACrE,CAAC;YACJ,CAAC;YACD,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;gBAC1B,MAAM,IAAI,CAAC,CAAC;gBACZ,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC1B,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;oBACd,SAAS,CAAC,KAAK,CAAC,CAAC;gBACnB,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,GAAG,CACN,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,QAAQ,KAAK,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,EAAE,EAAE,CAC7F,CAAC;gBACJ,CAAC;gBACD,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;oBACnC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBACrB,CAAC,CAAC,CAAC;gBACH,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAI,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QACvD;;;;;WAKG;QACH,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;QACxC,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,sEAAsE;YACtE,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YACvC,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,GAAG,CAAC,wEAAwE,CAAC,CAAC;YACrF,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AACxD,CAAC"}
|
|
@@ -1,44 +1,7 @@
|
|
|
1
|
+
import { type ListenDeps, type ListenOptions, type ListenSummary } from './listen-core.js';
|
|
1
2
|
import { type ParsedArgs } from '../args.js';
|
|
2
3
|
import type { ChatlyClient } from '@chatlychat/sdk-node';
|
|
3
|
-
export
|
|
4
|
-
/** Where to POST each event. `null` prints without forwarding. */
|
|
5
|
-
readonly forwardTo: string | null;
|
|
6
|
-
readonly endpointId: string | null;
|
|
7
|
-
/** Client-side event filter. `null` means every event in the log. */
|
|
8
|
-
readonly events: ReadonlySet<string> | null;
|
|
9
|
-
readonly status: string | null;
|
|
10
|
-
/** The `whsec_…` to re-sign forwarded requests with. `null` sends unsigned. */
|
|
11
|
-
readonly secret: string | null;
|
|
12
|
-
readonly intervalMs: number;
|
|
13
|
-
/** How many deliveries already in the log to forward before tailing. */
|
|
14
|
-
readonly replay: number;
|
|
15
|
-
/** Rows per poll. Also the most this can see between two polls. */
|
|
16
|
-
readonly pageLimit: number;
|
|
17
|
-
readonly json: boolean;
|
|
18
|
-
}
|
|
19
|
-
export interface ListenDeps {
|
|
20
|
-
/** For the FORWARD leg only. API calls go through the SDK's own transport. */
|
|
21
|
-
readonly fetchFn: typeof fetch;
|
|
22
|
-
readonly now: () => number;
|
|
23
|
-
readonly sleep: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
24
|
-
readonly signal: AbortSignal;
|
|
25
|
-
readonly out: (line: string) => void;
|
|
26
|
-
readonly err: (line: string) => void;
|
|
27
|
-
/**
|
|
28
|
-
* Stop after this many polls. Test-only, and the reason it exists rather
|
|
29
|
-
* than a timer the test races: a tail loop with no bound is not a thing a
|
|
30
|
-
* unit test can assert the end of.
|
|
31
|
-
*/
|
|
32
|
-
readonly maxPolls?: number;
|
|
33
|
-
}
|
|
34
|
-
export interface ListenSummary {
|
|
35
|
-
readonly seen: number;
|
|
36
|
-
readonly forwarded: number;
|
|
37
|
-
readonly refused: number;
|
|
38
|
-
readonly polls: number;
|
|
39
|
-
}
|
|
40
|
-
/** Read the flags, refusing anything that cannot work, with the fix named. */
|
|
41
|
-
export declare function parseListenOptions(args: ParsedArgs): ListenOptions;
|
|
4
|
+
export { parseListenOptions, type ListenDeps, type ListenOptions, type ListenSummary, } from './listen-core.js';
|
|
42
5
|
/**
|
|
43
6
|
* The tail loop, abortable, with the process boundary left to the caller.
|
|
44
7
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listen.d.ts","sourceRoot":"","sources":["../../src/commands/listen.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"listen.d.ts","sourceRoot":"","sources":["../../src/commands/listen.ts"],"names":[],"mappings":"AAsFA,OAAO,EAML,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAGnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,OAAO,EACL,kBAAkB,EAClB,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,kBAAkB,CAAC;AA6H1B;;;;;GAKG;AACH,wBAAsB,IAAI,CACxB,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,aAAa,EACnB,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,aAAa,CAAC,CAoIxB;AAED;;;;;;GAMG;AACH,wBAAsB,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgEpF"}
|