@leemour/max-cli 0.1.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/LICENSE +21 -0
- package/README.md +176 -0
- package/dist/bin/max.d.ts +3 -0
- package/dist/bin/max.d.ts.map +1 -0
- package/dist/bin/max.js +14 -0
- package/dist/bin/max.js.map +1 -0
- package/dist/cache/driver.d.ts +38 -0
- package/dist/cache/driver.d.ts.map +1 -0
- package/dist/cache/driver.js +26 -0
- package/dist/cache/driver.js.map +1 -0
- package/dist/cache/drivers/bun-sqlite.d.ts +8 -0
- package/dist/cache/drivers/bun-sqlite.d.ts.map +1 -0
- package/dist/cache/drivers/bun-sqlite.js +23 -0
- package/dist/cache/drivers/bun-sqlite.js.map +1 -0
- package/dist/cache/drivers/node-sqlite.d.ts +8 -0
- package/dist/cache/drivers/node-sqlite.d.ts.map +1 -0
- package/dist/cache/drivers/node-sqlite.js +22 -0
- package/dist/cache/drivers/node-sqlite.js.map +1 -0
- package/dist/cache/index.d.ts +22 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +33 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/open.d.ts +14 -0
- package/dist/cache/open.d.ts.map +1 -0
- package/dist/cache/open.js +26 -0
- package/dist/cache/open.js.map +1 -0
- package/dist/cache/schema.d.ts +14 -0
- package/dist/cache/schema.d.ts.map +1 -0
- package/dist/cache/schema.js +95 -0
- package/dist/cache/schema.js.map +1 -0
- package/dist/cache/store.d.ts +32 -0
- package/dist/cache/store.d.ts.map +1 -0
- package/dist/cache/store.js +153 -0
- package/dist/cache/store.js.map +1 -0
- package/dist/client.d.ts +103 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +383 -0
- package/dist/client.js.map +1 -0
- package/dist/commands/account.d.ts +3 -0
- package/dist/commands/account.d.ts.map +1 -0
- package/dist/commands/account.js +24 -0
- package/dist/commands/account.js.map +1 -0
- package/dist/commands/cache.d.ts +10 -0
- package/dist/commands/cache.d.ts.map +1 -0
- package/dist/commands/cache.js +36 -0
- package/dist/commands/cache.js.map +1 -0
- package/dist/commands/chats.d.ts +3 -0
- package/dist/commands/chats.d.ts.map +1 -0
- package/dist/commands/chats.js +28 -0
- package/dist/commands/chats.js.map +1 -0
- package/dist/commands/contacts.d.ts +10 -0
- package/dist/commands/contacts.d.ts.map +1 -0
- package/dist/commands/contacts.js +33 -0
- package/dist/commands/contacts.js.map +1 -0
- package/dist/commands/context.d.ts +34 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +40 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/messages.d.ts +3 -0
- package/dist/commands/messages.d.ts.map +1 -0
- package/dist/commands/messages.js +62 -0
- package/dist/commands/messages.js.map +1 -0
- package/dist/commands/runs.d.ts +9 -0
- package/dist/commands/runs.d.ts.map +1 -0
- package/dist/commands/runs.js +68 -0
- package/dist/commands/runs.js.map +1 -0
- package/dist/commands/session.d.ts +3 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +74 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/config.d.ts +53 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +83 -0
- package/dist/config.js.map +1 -0
- package/dist/domain/map.d.ts +22 -0
- package/dist/domain/map.d.ts.map +1 -0
- package/dist/domain/map.js +107 -0
- package/dist/domain/map.js.map +1 -0
- package/dist/domain/models.d.ts +57 -0
- package/dist/domain/models.d.ts.map +1 -0
- package/dist/domain/models.js +7 -0
- package/dist/domain/models.js.map +1 -0
- package/dist/generated/client.generated.d.ts +30 -0
- package/dist/generated/client.generated.d.ts.map +1 -0
- package/dist/generated/client.generated.js +26 -0
- package/dist/generated/client.generated.js.map +1 -0
- package/dist/generated/opcodes.generated.d.ts +44 -0
- package/dist/generated/opcodes.generated.d.ts.map +1 -0
- package/dist/generated/opcodes.generated.js +44 -0
- package/dist/generated/opcodes.generated.js.map +1 -0
- package/dist/generated/operations.generated.d.ts +76 -0
- package/dist/generated/operations.generated.d.ts.map +1 -0
- package/dist/generated/operations.generated.js +15 -0
- package/dist/generated/operations.generated.js.map +1 -0
- package/dist/output.d.ts +22 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +34 -0
- package/dist/output.js.map +1 -0
- package/dist/profile.d.ts +33 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +60 -0
- package/dist/profile.js.map +1 -0
- package/dist/program.d.ts +29 -0
- package/dist/program.d.ts.map +1 -0
- package/dist/program.js +130 -0
- package/dist/program.js.map +1 -0
- package/dist/protocol/connection.d.ts +60 -0
- package/dist/protocol/connection.d.ts.map +1 -0
- package/dist/protocol/connection.js +142 -0
- package/dist/protocol/connection.js.map +1 -0
- package/dist/protocol/frame.d.ts +44 -0
- package/dist/protocol/frame.d.ts.map +1 -0
- package/dist/protocol/frame.js +68 -0
- package/dist/protocol/frame.js.map +1 -0
- package/dist/runs/events.d.ts +92 -0
- package/dist/runs/events.d.ts.map +1 -0
- package/dist/runs/events.js +85 -0
- package/dist/runs/events.js.map +1 -0
- package/dist/runs/recording.d.ts +35 -0
- package/dist/runs/recording.d.ts.map +1 -0
- package/dist/runs/recording.js +53 -0
- package/dist/runs/recording.js.map +1 -0
- package/dist/runs/run.d.ts +81 -0
- package/dist/runs/run.d.ts.map +1 -0
- package/dist/runs/run.js +175 -0
- package/dist/runs/run.js.map +1 -0
- package/dist/session/handshake.d.ts +26 -0
- package/dist/session/handshake.d.ts.map +1 -0
- package/dist/session/handshake.js +32 -0
- package/dist/session/handshake.js.map +1 -0
- package/dist/session/prompt.d.ts +18 -0
- package/dist/session/prompt.d.ts.map +1 -0
- package/dist/session/prompt.js +41 -0
- package/dist/session/prompt.js.map +1 -0
- package/dist/session/store.d.ts +46 -0
- package/dist/session/store.d.ts.map +1 -0
- package/dist/session/store.js +80 -0
- package/dist/session/store.js.map +1 -0
- package/dist/spec/check.d.ts +16 -0
- package/dist/spec/check.d.ts.map +1 -0
- package/dist/spec/check.js +44 -0
- package/dist/spec/check.js.map +1 -0
- package/dist/spec/define.d.ts +95 -0
- package/dist/spec/define.d.ts.map +1 -0
- package/dist/spec/define.js +34 -0
- package/dist/spec/define.js.map +1 -0
- package/dist/spec/identity.d.ts +23 -0
- package/dist/spec/identity.d.ts.map +1 -0
- package/dist/spec/identity.js +23 -0
- package/dist/spec/identity.js.map +1 -0
- package/dist/spec/index.d.ts +15 -0
- package/dist/spec/index.d.ts.map +1 -0
- package/dist/spec/index.js +30 -0
- package/dist/spec/index.js.map +1 -0
- package/dist/spec/operations/chats.d.ts +25 -0
- package/dist/spec/operations/chats.d.ts.map +1 -0
- package/dist/spec/operations/chats.js +49 -0
- package/dist/spec/operations/chats.js.map +1 -0
- package/dist/spec/operations/contacts.d.ts +9 -0
- package/dist/spec/operations/contacts.d.ts.map +1 -0
- package/dist/spec/operations/contacts.js +39 -0
- package/dist/spec/operations/contacts.js.map +1 -0
- package/dist/spec/operations/messages.d.ts +19 -0
- package/dist/spec/operations/messages.d.ts.map +1 -0
- package/dist/spec/operations/messages.js +37 -0
- package/dist/spec/operations/messages.js.map +1 -0
- package/dist/spec/operations/session.d.ts +69 -0
- package/dist/spec/operations/session.d.ts.map +1 -0
- package/dist/spec/operations/session.js +106 -0
- package/dist/spec/operations/session.js.map +1 -0
- package/dist/spec/scalars.d.ts +16 -0
- package/dist/spec/scalars.d.ts.map +1 -0
- package/dist/spec/scalars.js +28 -0
- package/dist/spec/scalars.js.map +1 -0
- package/dist/testing/mock-max.d.ts +29 -0
- package/dist/testing/mock-max.d.ts.map +1 -0
- package/dist/testing/mock-max.js +46 -0
- package/dist/testing/mock-max.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/package.json +58 -0
package/dist/program.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { CliError, exitCodeFor, GENERIC_FAILURE, processStreams } from "@leemour/cli-core";
|
|
2
|
+
import { Command, CommanderError } from "commander";
|
|
3
|
+
import { accountCommand } from "./commands/account.js";
|
|
4
|
+
import { cacheCommand } from "./commands/cache.js";
|
|
5
|
+
import { chatsCommand } from "./commands/chats.js";
|
|
6
|
+
import { contactsCommand } from "./commands/contacts.js";
|
|
7
|
+
import { messagesCommand } from "./commands/messages.js";
|
|
8
|
+
import { runsCommand } from "./commands/runs.js";
|
|
9
|
+
import { sessionCommand } from "./commands/session.js";
|
|
10
|
+
import { commandWords, liftProfile } from "./profile.js";
|
|
11
|
+
import { VERSION } from "./version.js";
|
|
12
|
+
/**
|
|
13
|
+
* The command tree, built fresh on each call.
|
|
14
|
+
*
|
|
15
|
+
* Commander is global state by default — `exitOverride` and the write hooks below turn it into a
|
|
16
|
+
* value a test can drive. A command that calls `process.exit` cannot be tested, and a CLI whose
|
|
17
|
+
* argument parsing is untested is a CLI that breaks on the day someone adds an option.
|
|
18
|
+
*/
|
|
19
|
+
export const createProgram = ({ out, err } = {}) => {
|
|
20
|
+
const program = new Command();
|
|
21
|
+
program
|
|
22
|
+
.name("max")
|
|
23
|
+
.usage("[profile] [options] <command>")
|
|
24
|
+
.description("A local command line interface for a personal MAX Messenger account\n\n" +
|
|
25
|
+
"The first word is the profile whenever it is not a command — `max personal chats list`.\n" +
|
|
26
|
+
"`MAX_PROFILE` says the same thing for a whole shell session; without either it is `default`.")
|
|
27
|
+
.version(VERSION, "-v, --version")
|
|
28
|
+
.option("--json", "machine-readable output: one JSON value on stdout, nothing else")
|
|
29
|
+
.option("--quiet", "diagnostics off")
|
|
30
|
+
.option("--verbose", "diagnostics on: ids and timings on stderr, never message content")
|
|
31
|
+
.option("--offline", "answer from what was recorded and never connect; fails if nothing was")
|
|
32
|
+
.option("--record", "keep this run under `max runs` — ids and timings, never message content")
|
|
33
|
+
.option("--no-record", "do not keep it, whatever the configuration says")
|
|
34
|
+
.showHelpAfterError();
|
|
35
|
+
// One resource per command, one action per subcommand — `max chats list`, `max messages send`.
|
|
36
|
+
// The shape `braze-cli` uses, and the reason there is no `max send`: an action is never a
|
|
37
|
+
// top-level command, so there is one rule instead of a list of exceptions.
|
|
38
|
+
program.addCommand(sessionCommand());
|
|
39
|
+
program.addCommand(accountCommand());
|
|
40
|
+
program.addCommand(chatsCommand());
|
|
41
|
+
program.addCommand(contactsCommand());
|
|
42
|
+
program.addCommand(messagesCommand());
|
|
43
|
+
program.addCommand(cacheCommand());
|
|
44
|
+
program.addCommand(runsCommand());
|
|
45
|
+
// Depth-first: Commander does not pass `configureOutput` down to a command added with
|
|
46
|
+
// `addCommand`, so `max messages --help` would write to the real terminal while the top level
|
|
47
|
+
// wrote to the injected streams — and a test reading stdout would see nothing at all.
|
|
48
|
+
if (out || err) {
|
|
49
|
+
forEachCommand(program, (command) => command.configureOutput({
|
|
50
|
+
writeOut: (text) => out?.(text),
|
|
51
|
+
writeErr: (text) => err?.(text),
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
return program;
|
|
55
|
+
};
|
|
56
|
+
const forEachCommand = (command, apply) => {
|
|
57
|
+
apply(command);
|
|
58
|
+
for (const child of command.commands)
|
|
59
|
+
forEachCommand(child, apply);
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Runs the program and **returns an exit code instead of throwing**.
|
|
63
|
+
*
|
|
64
|
+
* A stack trace is not an error message: it puts Node internals on stderr, tells a script nothing
|
|
65
|
+
* it can branch on, and exits 1 for every kind of failure alike. Here a failure becomes a code an
|
|
66
|
+
* agent can switch on, and a sentence a person can act on.
|
|
67
|
+
*/
|
|
68
|
+
export const run = async (argv, options = {}) => {
|
|
69
|
+
const streams = options.streams ?? processStreams;
|
|
70
|
+
const program = createProgram({
|
|
71
|
+
out: (text) => streams.data(text.replace(/\n$/, "")),
|
|
72
|
+
err: (text) => streams.diagnostic(text.replace(/\n$/, "")),
|
|
73
|
+
});
|
|
74
|
+
// Commander calls process.exit for --help and --version. A library that kills the process cannot
|
|
75
|
+
// be tested and cannot be embedded, so it throws instead and `run` decides the exit code.
|
|
76
|
+
//
|
|
77
|
+
// Depth-first, not one level: `max chats list --nonsense` is handled by the subcommand, and a
|
|
78
|
+
// subcommand left with the default behaviour kills the process from inside a test.
|
|
79
|
+
forEachCommand(program, (command) => command.exitOverride());
|
|
80
|
+
// Before commander, not inside it: a custom argument parser would still have to be told that
|
|
81
|
+
// the first word is sometimes a command, and commander has no hook that runs before it decides
|
|
82
|
+
// which subcommand it is looking at.
|
|
83
|
+
const { profile, rest } = liftProfile(argv, commandWords(program));
|
|
84
|
+
if (profile !== undefined)
|
|
85
|
+
program.setOptionValue("profile", profile);
|
|
86
|
+
// `max me` — a command that was renamed away — is now a profile with nothing after it, and
|
|
87
|
+
// commander answers a missing command by printing help **on stdout**. That breaks the one
|
|
88
|
+
// contract this program has, and it tells the person nothing about why their command vanished.
|
|
89
|
+
if (profile !== undefined && rest.length === 0) {
|
|
90
|
+
report(streams, options, {
|
|
91
|
+
code: "validation_error",
|
|
92
|
+
message: `"${profile}" is not a command, so it was read as a profile name — and no command followed it. ` +
|
|
93
|
+
`Run \`max --help\` for the commands, or \`max ${profile} account show\` if "${profile}" is your profile.`,
|
|
94
|
+
});
|
|
95
|
+
return exitCodeFor("validation_error");
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
await program.parseAsync(rest, { from: "user" });
|
|
99
|
+
return process.exitCode === undefined ? 0 : Number(process.exitCode);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error instanceof CommanderError) {
|
|
103
|
+
// `max chat list` — one letter short of `chats` — now reports an unknown command `list`,
|
|
104
|
+
// which is baffling on its own. This is the everyday cost of the first word being a profile.
|
|
105
|
+
if (profile !== undefined && error.code === "commander.unknownCommand") {
|
|
106
|
+
streams.diagnostic(`"${profile}" is not a command, so it was read as a profile name — which left "${rest[0]}" to be one.`);
|
|
107
|
+
}
|
|
108
|
+
return error.exitCode;
|
|
109
|
+
}
|
|
110
|
+
if (error instanceof CliError) {
|
|
111
|
+
report(streams, options, { code: error.code, message: error.message, ...error.details });
|
|
112
|
+
return exitCodeFor(error.code);
|
|
113
|
+
}
|
|
114
|
+
report(streams, options, {
|
|
115
|
+
code: "generic_failure",
|
|
116
|
+
message: error instanceof Error ? error.message : String(error),
|
|
117
|
+
});
|
|
118
|
+
return GENERIC_FAILURE;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* **A failure never reaches stdout.** An agent reading stdout must not be able to mistake a refusal
|
|
123
|
+
* for a result, so the error goes to stderr — as JSON when nobody is watching, because an exit code
|
|
124
|
+
* says which kind of thing went wrong and nothing about which chat or how long to wait.
|
|
125
|
+
*/
|
|
126
|
+
const report = (streams, options, error) => {
|
|
127
|
+
const interactive = options.tty ?? process.stdout.isTTY === true;
|
|
128
|
+
streams.diagnostic(interactive ? `\u2717 ${error.message}` : JSON.stringify({ error }));
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=program.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"program.js","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { WebSocket } from "ws";
|
|
2
|
+
import { type InboundFrame, type Payload } from "./frame.js";
|
|
3
|
+
export declare const MAX_WEBSOCKET_URL = "wss://ws-api.oneme.ru/websocket";
|
|
4
|
+
/** MAX's web client sends this; we send what it sends rather than announcing ourselves (§34). */
|
|
5
|
+
export declare const WEB_ORIGIN = "https://web.max.ru";
|
|
6
|
+
/**
|
|
7
|
+
* What one frame cost, reported as it happens.
|
|
8
|
+
*
|
|
9
|
+
* The transport is the only layer that knows the `seq` it allocated and how many bytes actually
|
|
10
|
+
* went over the socket, and it is the only one that can time the wait. It reports those and
|
|
11
|
+
* nothing else — **who is asking, and why, stays above it** (`ARCHITECTURE.md` §2).
|
|
12
|
+
*/
|
|
13
|
+
export interface WireEvent {
|
|
14
|
+
phase: "sent" | "received";
|
|
15
|
+
seq: number;
|
|
16
|
+
opcode: number;
|
|
17
|
+
bytes: number;
|
|
18
|
+
}
|
|
19
|
+
export interface ConnectionOptions {
|
|
20
|
+
url?: string;
|
|
21
|
+
origin?: string;
|
|
22
|
+
/** Per request, not per connection. */
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
/** Events MAX pushes on its own — messages arriving, presence. Ignored unless a caller cares. */
|
|
25
|
+
onEvent?: (frame: InboundFrame) => void;
|
|
26
|
+
/** Injected in tests; defaults to the real `ws` client. */
|
|
27
|
+
createSocket?: (url: string, origin: string) => WebSocket;
|
|
28
|
+
}
|
|
29
|
+
export declare class ProtocolError extends Error {
|
|
30
|
+
readonly opcode: number;
|
|
31
|
+
readonly payload: Payload | null;
|
|
32
|
+
constructor(message: string, opcode: number, payload: Payload | null);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* One WebSocket, one command's worth of work, then closed.
|
|
36
|
+
*
|
|
37
|
+
* **Everything that could keep the process alive is owned here**: the socket, the per-request
|
|
38
|
+
* timers and the listeners. `close()` clears all three, and a command that opens a connection
|
|
39
|
+
* closes it in a `finally` — a CLI that prints its result and then hangs is a defect, not a rough
|
|
40
|
+
* edge (REQUIREMENTS §18).
|
|
41
|
+
*/
|
|
42
|
+
export declare class Connection {
|
|
43
|
+
#private;
|
|
44
|
+
constructor(options?: ConnectionOptions);
|
|
45
|
+
open(): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Sends one request and waits for the response carrying the same `seq`.
|
|
48
|
+
*
|
|
49
|
+
* MAX interleaves pushed events with responses on one socket, so the `seq` is the only thing
|
|
50
|
+
* tying an answer to its question — a reader that takes the next frame as its answer will
|
|
51
|
+
* eventually read somebody's incoming message instead.
|
|
52
|
+
*
|
|
53
|
+
* `watch` is told what left and what came back, for the caller that is keeping a diagnostic. It
|
|
54
|
+
* is called synchronously on the way out, so a request that never gets an answer is still on
|
|
55
|
+
* record — which is the run somebody actually wants to read.
|
|
56
|
+
*/
|
|
57
|
+
invoke(opcode: number, payload?: Payload, watch?: (event: WireEvent) => void): Promise<Payload>;
|
|
58
|
+
close(): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=connection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/protocol/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAA;AAC9B,OAAO,EAAqC,KAAK,YAAY,EAAE,KAAK,OAAO,EAAE,MAAM,YAAY,CAAA;AAE/F,eAAO,MAAM,iBAAiB,oCAAoC,CAAA;AAClE,iGAAiG;AACjG,eAAO,MAAM,UAAU,uBAAuB,CAAA;AAE9C;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,GAAG,UAAU,CAAA;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iGAAiG;IACjG,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAA;IACvC,2DAA2D;IAC3D,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,SAAS,CAAA;CAC1D;AAED,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;gBAEpB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;CAMrE;AAED;;;;;;;GAOG;AACH,qBAAa,UAAU;;gBAmBT,OAAO,GAAE,iBAAsB;IAQrC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB3B;;;;;;;;;;OAUG;IACG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,OAAY,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAoCnG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAuC7B"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { WebSocket } from "ws";
|
|
2
|
+
import { Command, decodeFrame, encodeFrame } from "./frame.js";
|
|
3
|
+
export const MAX_WEBSOCKET_URL = "wss://ws-api.oneme.ru/websocket";
|
|
4
|
+
/** MAX's web client sends this; we send what it sends rather than announcing ourselves (§34). */
|
|
5
|
+
export const WEB_ORIGIN = "https://web.max.ru";
|
|
6
|
+
export class ProtocolError extends Error {
|
|
7
|
+
opcode;
|
|
8
|
+
payload;
|
|
9
|
+
constructor(message, opcode, payload) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "ProtocolError";
|
|
12
|
+
this.opcode = opcode;
|
|
13
|
+
this.payload = payload;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* One WebSocket, one command's worth of work, then closed.
|
|
18
|
+
*
|
|
19
|
+
* **Everything that could keep the process alive is owned here**: the socket, the per-request
|
|
20
|
+
* timers and the listeners. `close()` clears all three, and a command that opens a connection
|
|
21
|
+
* closes it in a `finally` — a CLI that prints its result and then hangs is a defect, not a rough
|
|
22
|
+
* edge (REQUIREMENTS §18).
|
|
23
|
+
*/
|
|
24
|
+
export class Connection {
|
|
25
|
+
#url;
|
|
26
|
+
#origin;
|
|
27
|
+
#timeoutMs;
|
|
28
|
+
#onEvent;
|
|
29
|
+
#createSocket;
|
|
30
|
+
#pending = new Map();
|
|
31
|
+
#socket;
|
|
32
|
+
#seq = 0;
|
|
33
|
+
#closed = false;
|
|
34
|
+
constructor(options = {}) {
|
|
35
|
+
this.#url = options.url ?? MAX_WEBSOCKET_URL;
|
|
36
|
+
this.#origin = options.origin ?? WEB_ORIGIN;
|
|
37
|
+
this.#timeoutMs = options.timeoutMs ?? 30_000;
|
|
38
|
+
this.#onEvent = options.onEvent;
|
|
39
|
+
this.#createSocket = options.createSocket ?? ((url, origin) => new WebSocket(url, { headers: { Origin: origin } }));
|
|
40
|
+
}
|
|
41
|
+
async open() {
|
|
42
|
+
if (this.#socket)
|
|
43
|
+
return;
|
|
44
|
+
const socket = this.#createSocket(this.#url, this.#origin);
|
|
45
|
+
this.#socket = socket;
|
|
46
|
+
await new Promise((resolve, reject) => {
|
|
47
|
+
const onOpen = () => {
|
|
48
|
+
socket.off("error", onError);
|
|
49
|
+
resolve();
|
|
50
|
+
};
|
|
51
|
+
const onError = (error) => {
|
|
52
|
+
socket.off("open", onOpen);
|
|
53
|
+
reject(error);
|
|
54
|
+
};
|
|
55
|
+
socket.once("open", onOpen);
|
|
56
|
+
socket.once("error", onError);
|
|
57
|
+
});
|
|
58
|
+
socket.on("message", (data) => this.#receive(String(data)));
|
|
59
|
+
socket.on("close", () => this.#failAll(new Error("MAX closed the connection")));
|
|
60
|
+
socket.on("error", (error) => this.#failAll(error));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Sends one request and waits for the response carrying the same `seq`.
|
|
64
|
+
*
|
|
65
|
+
* MAX interleaves pushed events with responses on one socket, so the `seq` is the only thing
|
|
66
|
+
* tying an answer to its question — a reader that takes the next frame as its answer will
|
|
67
|
+
* eventually read somebody's incoming message instead.
|
|
68
|
+
*
|
|
69
|
+
* `watch` is told what left and what came back, for the caller that is keeping a diagnostic. It
|
|
70
|
+
* is called synchronously on the way out, so a request that never gets an answer is still on
|
|
71
|
+
* record — which is the run somebody actually wants to read.
|
|
72
|
+
*/
|
|
73
|
+
async invoke(opcode, payload = {}, watch) {
|
|
74
|
+
if (!Number.isInteger(opcode)) {
|
|
75
|
+
// A typo in an opcode constant is otherwise a round trip to MAX that comes back
|
|
76
|
+
// "неизвестный opcode", which reads as the protocol's fault rather than ours.
|
|
77
|
+
throw new Error(`refusing to send a frame with a non-integer opcode: ${String(opcode)}`);
|
|
78
|
+
}
|
|
79
|
+
if (!this.#socket)
|
|
80
|
+
throw new Error("the connection is not open");
|
|
81
|
+
if (this.#closed)
|
|
82
|
+
throw new Error("the connection is closed");
|
|
83
|
+
this.#seq += 1;
|
|
84
|
+
const seq = this.#seq;
|
|
85
|
+
const socket = this.#socket;
|
|
86
|
+
const answer = await new Promise((resolve, reject) => {
|
|
87
|
+
const timer = setTimeout(() => {
|
|
88
|
+
this.#pending.delete(seq);
|
|
89
|
+
reject(new Error(`MAX did not answer opcode ${opcode} within ${this.#timeoutMs}ms`));
|
|
90
|
+
}, this.#timeoutMs);
|
|
91
|
+
this.#pending.set(seq, { resolve, reject, timer });
|
|
92
|
+
const sent = encodeFrame({ seq, opcode, payload });
|
|
93
|
+
socket.send(sent);
|
|
94
|
+
watch?.({ phase: "sent", seq, opcode, bytes: Buffer.byteLength(sent) });
|
|
95
|
+
});
|
|
96
|
+
const frame = answer.frame;
|
|
97
|
+
watch?.({ phase: "received", seq, opcode, bytes: answer.bytes });
|
|
98
|
+
if (frame.cmd === Command.ERROR) {
|
|
99
|
+
const reason = typeof frame.payload?.error === "string" ? frame.payload.error : "an error with no reason given";
|
|
100
|
+
throw new ProtocolError(`MAX refused opcode ${opcode}: ${reason}`, opcode, frame.payload);
|
|
101
|
+
}
|
|
102
|
+
return frame.payload ?? {};
|
|
103
|
+
}
|
|
104
|
+
async close() {
|
|
105
|
+
this.#closed = true;
|
|
106
|
+
const socket = this.#socket;
|
|
107
|
+
this.#socket = undefined;
|
|
108
|
+
for (const { timer } of this.#pending.values())
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
this.#pending.clear();
|
|
111
|
+
if (!socket)
|
|
112
|
+
return;
|
|
113
|
+
socket.removeAllListeners();
|
|
114
|
+
if (socket.readyState === socket.OPEN || socket.readyState === socket.CONNECTING)
|
|
115
|
+
socket.close();
|
|
116
|
+
}
|
|
117
|
+
#receive(raw) {
|
|
118
|
+
let frame;
|
|
119
|
+
try {
|
|
120
|
+
frame = decodeFrame(raw);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return; // A frame we cannot read is not a reason to fail a request we can.
|
|
124
|
+
}
|
|
125
|
+
const waiting = frame.seq === null ? undefined : this.#pending.get(frame.seq);
|
|
126
|
+
if (!waiting) {
|
|
127
|
+
this.#onEvent?.(frame);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
clearTimeout(waiting.timer);
|
|
131
|
+
this.#pending.delete(frame.seq);
|
|
132
|
+
waiting.resolve({ frame, bytes: Buffer.byteLength(raw) });
|
|
133
|
+
}
|
|
134
|
+
#failAll(error) {
|
|
135
|
+
for (const { reject, timer } of this.#pending.values()) {
|
|
136
|
+
clearTimeout(timer);
|
|
137
|
+
reject(error);
|
|
138
|
+
}
|
|
139
|
+
this.#pending.clear();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/protocol/connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** The protocol version the web client pins. Confirmed in tsmax, maxrs and max-api-docs. */
|
|
2
|
+
export declare const PROTOCOL_VERSION = 11;
|
|
3
|
+
export declare const Command: {
|
|
4
|
+
readonly REQUEST: 0;
|
|
5
|
+
readonly RESPONSE: 1;
|
|
6
|
+
readonly EVENT: 2;
|
|
7
|
+
readonly ERROR: 3;
|
|
8
|
+
};
|
|
9
|
+
export type Command = (typeof Command)[keyof typeof Command];
|
|
10
|
+
export type Payload = Record<string, unknown>;
|
|
11
|
+
export interface OutboundFrame {
|
|
12
|
+
seq: number;
|
|
13
|
+
opcode: number;
|
|
14
|
+
payload: Payload;
|
|
15
|
+
cmd?: Command;
|
|
16
|
+
}
|
|
17
|
+
export interface InboundFrame {
|
|
18
|
+
ver: number;
|
|
19
|
+
cmd: number;
|
|
20
|
+
seq: number | null;
|
|
21
|
+
opcode: number;
|
|
22
|
+
payload: Payload | null;
|
|
23
|
+
}
|
|
24
|
+
export declare const encodeFrame: (frame: OutboundFrame) => string;
|
|
25
|
+
/**
|
|
26
|
+
* **Never `JSON.parse`.** Message ids are 64-bit and past `Number.MAX_SAFE_INTEGER` — 18 digits,
|
|
27
|
+
* measured — so the built-in parser rounds them and two different messages arrive as one id.
|
|
28
|
+
* `lossless-json` hands back a bigint for any integer that would not survive, and every id leaves
|
|
29
|
+
* this layer as a string.
|
|
30
|
+
*
|
|
31
|
+
* ⚠ Measured 2026-09-19: **chat ids on the owner's account reach 14 digits, not 19**, and contact
|
|
32
|
+
* ids reach 9 — all comfortably inside a number. The hazard is real and it is the message ids that
|
|
33
|
+
* carry it; a 19-digit chat id is an illustration, not something seen.
|
|
34
|
+
*/
|
|
35
|
+
export declare const decodeFrame: (raw: string) => InboundFrame;
|
|
36
|
+
export declare class FrameError extends Error {
|
|
37
|
+
constructor(what: string);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Ids arrive as `number` when small and `bigint` when not, and both have to render identically.
|
|
41
|
+
* A domain id is a string from here on — it is an identifier, never arithmetic.
|
|
42
|
+
*/
|
|
43
|
+
export declare const asId: (value: unknown) => string | undefined;
|
|
44
|
+
//# sourceMappingURL=frame.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame.d.ts","sourceRoot":"","sources":["../../src/protocol/frame.ts"],"names":[],"mappings":"AAEA,4FAA4F;AAC5F,eAAO,MAAM,gBAAgB,KAAK,CAAA;AAElC,eAAO,MAAM,OAAO;;;;;CAKV,CAAA;AACV,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,OAAO,OAAO,CAAC,CAAA;AAE5D,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAE7C,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,OAAO,CAAA;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;CACxB;AAED,eAAO,MAAM,WAAW,GAAI,OAAO,aAAa,KAAG,MAOrC,CAAA;AAEd;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,YAczC,CAAA;AAED,qBAAa,UAAW,SAAQ,KAAK;gBACvB,IAAI,EAAE,MAAM;CAIzB;AAED;;;GAGG;AACH,eAAO,MAAM,IAAI,GAAI,OAAO,OAAO,KAAG,MAAM,GAAG,SAK9C,CAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { isInteger, isSafeNumber, parse, stringify } from "lossless-json";
|
|
2
|
+
/** The protocol version the web client pins. Confirmed in tsmax, maxrs and max-api-docs. */
|
|
3
|
+
export const PROTOCOL_VERSION = 11;
|
|
4
|
+
export const Command = {
|
|
5
|
+
REQUEST: 0,
|
|
6
|
+
RESPONSE: 1,
|
|
7
|
+
EVENT: 2,
|
|
8
|
+
ERROR: 3,
|
|
9
|
+
};
|
|
10
|
+
export const encodeFrame = (frame) => stringify({
|
|
11
|
+
ver: PROTOCOL_VERSION,
|
|
12
|
+
cmd: frame.cmd ?? Command.REQUEST,
|
|
13
|
+
seq: frame.seq,
|
|
14
|
+
opcode: frame.opcode,
|
|
15
|
+
payload: frame.payload,
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* **Never `JSON.parse`.** Message ids are 64-bit and past `Number.MAX_SAFE_INTEGER` — 18 digits,
|
|
19
|
+
* measured — so the built-in parser rounds them and two different messages arrive as one id.
|
|
20
|
+
* `lossless-json` hands back a bigint for any integer that would not survive, and every id leaves
|
|
21
|
+
* this layer as a string.
|
|
22
|
+
*
|
|
23
|
+
* ⚠ Measured 2026-09-19: **chat ids on the owner's account reach 14 digits, not 19**, and contact
|
|
24
|
+
* ids reach 9 — all comfortably inside a number. The hazard is real and it is the message ids that
|
|
25
|
+
* carry it; a 19-digit chat id is an illustration, not something seen.
|
|
26
|
+
*/
|
|
27
|
+
export const decodeFrame = (raw) => {
|
|
28
|
+
const value = parse(raw, undefined, {
|
|
29
|
+
parseNumber: (text) => (isInteger(text) && !isSafeNumber(text) ? BigInt(text) : Number(text)),
|
|
30
|
+
});
|
|
31
|
+
if (!isRecord(value))
|
|
32
|
+
throw new FrameError("a frame that is not an object");
|
|
33
|
+
return {
|
|
34
|
+
ver: header(value.ver, "ver"),
|
|
35
|
+
cmd: header(value.cmd ?? Command.RESPONSE, "cmd"),
|
|
36
|
+
seq: value.seq === null || value.seq === undefined ? null : header(value.seq, "seq"),
|
|
37
|
+
opcode: header(value.opcode, "opcode"),
|
|
38
|
+
payload: isRecord(value.payload) ? value.payload : null,
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
export class FrameError extends Error {
|
|
42
|
+
constructor(what) {
|
|
43
|
+
super(`MAX sent ${what}`);
|
|
44
|
+
this.name = "FrameError";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Ids arrive as `number` when small and `bigint` when not, and both have to render identically.
|
|
49
|
+
* A domain id is a string from here on — it is an identifier, never arithmetic.
|
|
50
|
+
*/
|
|
51
|
+
export const asId = (value) => {
|
|
52
|
+
if (typeof value === "bigint")
|
|
53
|
+
return value.toString();
|
|
54
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
55
|
+
return String(value);
|
|
56
|
+
if (typeof value === "string" && value !== "")
|
|
57
|
+
return value;
|
|
58
|
+
return undefined;
|
|
59
|
+
};
|
|
60
|
+
const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
61
|
+
const header = (value, field) => {
|
|
62
|
+
const asNumber = typeof value === "bigint" ? Number(value) : value;
|
|
63
|
+
if (typeof asNumber !== "number" || !Number.isSafeInteger(asNumber)) {
|
|
64
|
+
throw new FrameError(`a frame whose \`${field}\` is not a whole number`);
|
|
65
|
+
}
|
|
66
|
+
return asNumber;
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=frame.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame.js","sourceRoot":"","sources":["../../src/protocol/frame.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Payload } from "../protocol/frame.js";
|
|
2
|
+
/**
|
|
3
|
+
* One request, as both sinks see it: `--verbose` renders it, the run log writes it as a line of
|
|
4
|
+
* JSON. **One object, two sinks** — a second shape for one of them is how the two start disagreeing
|
|
5
|
+
* about what happened.
|
|
6
|
+
*
|
|
7
|
+
* ⚠ **Nothing here may carry content.** The operation, the opcode, the `seq`, the ids a request
|
|
8
|
+
* named, byte counts and durations are safe: an id is opaque and can only be correlated by whoever
|
|
9
|
+
* already has the session. A chat title, a person's name, a message body, a phone number or a token
|
|
10
|
+
* are not — not truncated and not hashed (`REQUIREMENTS.md` §14, §24). The fields below are the
|
|
11
|
+
* whole vocabulary, and `idsOf` builds them from named fields rather than filtering a copy of the
|
|
12
|
+
* payload, so there is no path by which an unforeseen field arrives here.
|
|
13
|
+
*/
|
|
14
|
+
export interface RequestEvent {
|
|
15
|
+
event: "request" | "response";
|
|
16
|
+
/** Ours, not MAX's: `chats.history`, not `CHAT_HISTORY`. */
|
|
17
|
+
operation: string;
|
|
18
|
+
opcode: number;
|
|
19
|
+
seq: number;
|
|
20
|
+
/** The frame that went out, or the one that came back. Absent when nothing came back. */
|
|
21
|
+
bytes?: number;
|
|
22
|
+
/** Which things the request named — `{ chat: "0" }`. Never how they are called. */
|
|
23
|
+
ids?: Record<string, string>;
|
|
24
|
+
/** How many things, per field: `{ messages: 3 }`. A field name and a length carry no content. */
|
|
25
|
+
counts?: Record<string, number>;
|
|
26
|
+
durationMs?: number;
|
|
27
|
+
outcome?: "ok" | "error";
|
|
28
|
+
/** `cli-core`'s code — `timeout`, `provider_error`. Never the message: MAX quotes our payload. */
|
|
29
|
+
errorCode?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A read that never reached MAX.
|
|
33
|
+
*
|
|
34
|
+
* It is not a request and must not be shaped like one — there is no opcode, no `seq` and no frame,
|
|
35
|
+
* and calling a local answer "request 0 bytes" would put a fiction in the record. The call sites
|
|
36
|
+
* belong to the cache (`MAX-7`); what lives here is the vocabulary, so that the two of them cannot
|
|
37
|
+
* drift into two shapes for one diagnostic.
|
|
38
|
+
*/
|
|
39
|
+
export interface CacheEvent {
|
|
40
|
+
event: "cache";
|
|
41
|
+
/** Ours: `chats.list`, `messages.list`. */
|
|
42
|
+
operation: string;
|
|
43
|
+
reason: CacheReason;
|
|
44
|
+
ids?: Record<string, string>;
|
|
45
|
+
counts?: Record<string, number>;
|
|
46
|
+
/** How old the local answer was. The number a person wants when a list looks wrong. */
|
|
47
|
+
ageMs?: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Why MAX was not asked. **"We did not ask" and "there was nothing to ask" are different events**,
|
|
51
|
+
* and only the second is the record doing its job rather than standing in for a connection.
|
|
52
|
+
*
|
|
53
|
+
* Written out as two words rather than a free string so that the vocabulary has one home and a
|
|
54
|
+
* third case has to be added here, deliberately, instead of appearing in a log nobody can group.
|
|
55
|
+
*/
|
|
56
|
+
export type CacheReason =
|
|
57
|
+
/** The caller said never connect, so the record is all there is. */
|
|
58
|
+
"offline"
|
|
59
|
+
/** The window asked for is older than a fetch would return, so the record is authoritative. */
|
|
60
|
+
| "history";
|
|
61
|
+
/** What either sink is handed. One object, two sinks — see `RequestEvent`. */
|
|
62
|
+
export type DiagnosticEvent = RequestEvent | CacheEvent;
|
|
63
|
+
/**
|
|
64
|
+
* The ids a request named, by name, from a list of fields that is written out here.
|
|
65
|
+
*
|
|
66
|
+
* **An allowlist, never a filter.** Copying the request and removing what looks dangerous means a
|
|
67
|
+
* field nobody has seen yet arrives in the log by default; this way it cannot. `token` is the case
|
|
68
|
+
* that matters: it is a field of `session.login` and there is no branch here that could reach it.
|
|
69
|
+
*/
|
|
70
|
+
export declare const idsOf: (request: unknown) => {
|
|
71
|
+
ids?: Record<string, string>;
|
|
72
|
+
counts?: Record<string, number>;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* How many of each list MAX sent back — `{ chats: 25, contacts: 6 }`.
|
|
76
|
+
*
|
|
77
|
+
* Every array at the top of the answer, counted. Generic on purpose: a per-operation table of
|
|
78
|
+
* "which field is the interesting one" is a second registry to keep in step with `src/spec/`, and
|
|
79
|
+
* a field name with a length beside it is content-free whatever MAX adds next.
|
|
80
|
+
*/
|
|
81
|
+
export declare const countsIn: (payload: Payload) => Record<string, number> | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* The line a person reads. `→` is what we asked, `←` what came back, `•` what never left.
|
|
84
|
+
*
|
|
85
|
+
* ```text
|
|
86
|
+
* → chats.history op 49 seq 3 chat 0
|
|
87
|
+
* ← chats.history op 49 seq 3 118ms 4.2 kB 3 messages
|
|
88
|
+
* • chats.list offline cached 42s 25 chats
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export declare const renderEvent: (event: DiagnosticEvent) => string;
|
|
92
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/runs/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAEnD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,SAAS,GAAG,UAAU,CAAA;IAC7B,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,yFAAyF;IACzF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mFAAmF;IACnF,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,iGAAiG;IACjG,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,IAAI,GAAG,OAAO,CAAA;IACxB,kGAAkG;IAClG,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,OAAO,CAAA;IACd,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,WAAW,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,uFAAuF;IACvF,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;;;GAMG;AACH,MAAM,MAAM,WAAW;AACrB,oEAAoE;AAClE,SAAS;AACX,+FAA+F;GAC7F,SAAS,CAAA;AAEb,8EAA8E;AAC9E,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,UAAU,CAAA;AAEvD;;;;;;GAMG;AACH,eAAO,MAAM,KAAK,GAAI,SAAS,OAAO,KAAG;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAqBvG,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,GAAI,SAAS,OAAO,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAIpE,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,eAAe,KAAG,MAqBpD,CAAA"}
|