@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.
Files changed (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +176 -0
  3. package/dist/bin/max.d.ts +3 -0
  4. package/dist/bin/max.d.ts.map +1 -0
  5. package/dist/bin/max.js +14 -0
  6. package/dist/bin/max.js.map +1 -0
  7. package/dist/cache/driver.d.ts +38 -0
  8. package/dist/cache/driver.d.ts.map +1 -0
  9. package/dist/cache/driver.js +26 -0
  10. package/dist/cache/driver.js.map +1 -0
  11. package/dist/cache/drivers/bun-sqlite.d.ts +8 -0
  12. package/dist/cache/drivers/bun-sqlite.d.ts.map +1 -0
  13. package/dist/cache/drivers/bun-sqlite.js +23 -0
  14. package/dist/cache/drivers/bun-sqlite.js.map +1 -0
  15. package/dist/cache/drivers/node-sqlite.d.ts +8 -0
  16. package/dist/cache/drivers/node-sqlite.d.ts.map +1 -0
  17. package/dist/cache/drivers/node-sqlite.js +22 -0
  18. package/dist/cache/drivers/node-sqlite.js.map +1 -0
  19. package/dist/cache/index.d.ts +22 -0
  20. package/dist/cache/index.d.ts.map +1 -0
  21. package/dist/cache/index.js +33 -0
  22. package/dist/cache/index.js.map +1 -0
  23. package/dist/cache/open.d.ts +14 -0
  24. package/dist/cache/open.d.ts.map +1 -0
  25. package/dist/cache/open.js +26 -0
  26. package/dist/cache/open.js.map +1 -0
  27. package/dist/cache/schema.d.ts +14 -0
  28. package/dist/cache/schema.d.ts.map +1 -0
  29. package/dist/cache/schema.js +95 -0
  30. package/dist/cache/schema.js.map +1 -0
  31. package/dist/cache/store.d.ts +32 -0
  32. package/dist/cache/store.d.ts.map +1 -0
  33. package/dist/cache/store.js +153 -0
  34. package/dist/cache/store.js.map +1 -0
  35. package/dist/client.d.ts +103 -0
  36. package/dist/client.d.ts.map +1 -0
  37. package/dist/client.js +383 -0
  38. package/dist/client.js.map +1 -0
  39. package/dist/commands/account.d.ts +3 -0
  40. package/dist/commands/account.d.ts.map +1 -0
  41. package/dist/commands/account.js +24 -0
  42. package/dist/commands/account.js.map +1 -0
  43. package/dist/commands/cache.d.ts +10 -0
  44. package/dist/commands/cache.d.ts.map +1 -0
  45. package/dist/commands/cache.js +36 -0
  46. package/dist/commands/cache.js.map +1 -0
  47. package/dist/commands/chats.d.ts +3 -0
  48. package/dist/commands/chats.d.ts.map +1 -0
  49. package/dist/commands/chats.js +28 -0
  50. package/dist/commands/chats.js.map +1 -0
  51. package/dist/commands/contacts.d.ts +10 -0
  52. package/dist/commands/contacts.d.ts.map +1 -0
  53. package/dist/commands/contacts.js +33 -0
  54. package/dist/commands/contacts.js.map +1 -0
  55. package/dist/commands/context.d.ts +34 -0
  56. package/dist/commands/context.d.ts.map +1 -0
  57. package/dist/commands/context.js +40 -0
  58. package/dist/commands/context.js.map +1 -0
  59. package/dist/commands/messages.d.ts +3 -0
  60. package/dist/commands/messages.d.ts.map +1 -0
  61. package/dist/commands/messages.js +62 -0
  62. package/dist/commands/messages.js.map +1 -0
  63. package/dist/commands/runs.d.ts +9 -0
  64. package/dist/commands/runs.d.ts.map +1 -0
  65. package/dist/commands/runs.js +68 -0
  66. package/dist/commands/runs.js.map +1 -0
  67. package/dist/commands/session.d.ts +3 -0
  68. package/dist/commands/session.d.ts.map +1 -0
  69. package/dist/commands/session.js +74 -0
  70. package/dist/commands/session.js.map +1 -0
  71. package/dist/config.d.ts +53 -0
  72. package/dist/config.d.ts.map +1 -0
  73. package/dist/config.js +83 -0
  74. package/dist/config.js.map +1 -0
  75. package/dist/domain/map.d.ts +22 -0
  76. package/dist/domain/map.d.ts.map +1 -0
  77. package/dist/domain/map.js +107 -0
  78. package/dist/domain/map.js.map +1 -0
  79. package/dist/domain/models.d.ts +57 -0
  80. package/dist/domain/models.d.ts.map +1 -0
  81. package/dist/domain/models.js +7 -0
  82. package/dist/domain/models.js.map +1 -0
  83. package/dist/generated/client.generated.d.ts +30 -0
  84. package/dist/generated/client.generated.d.ts.map +1 -0
  85. package/dist/generated/client.generated.js +26 -0
  86. package/dist/generated/client.generated.js.map +1 -0
  87. package/dist/generated/opcodes.generated.d.ts +44 -0
  88. package/dist/generated/opcodes.generated.d.ts.map +1 -0
  89. package/dist/generated/opcodes.generated.js +44 -0
  90. package/dist/generated/opcodes.generated.js.map +1 -0
  91. package/dist/generated/operations.generated.d.ts +76 -0
  92. package/dist/generated/operations.generated.d.ts.map +1 -0
  93. package/dist/generated/operations.generated.js +15 -0
  94. package/dist/generated/operations.generated.js.map +1 -0
  95. package/dist/output.d.ts +22 -0
  96. package/dist/output.d.ts.map +1 -0
  97. package/dist/output.js +34 -0
  98. package/dist/output.js.map +1 -0
  99. package/dist/profile.d.ts +33 -0
  100. package/dist/profile.d.ts.map +1 -0
  101. package/dist/profile.js +60 -0
  102. package/dist/profile.js.map +1 -0
  103. package/dist/program.d.ts +29 -0
  104. package/dist/program.d.ts.map +1 -0
  105. package/dist/program.js +130 -0
  106. package/dist/program.js.map +1 -0
  107. package/dist/protocol/connection.d.ts +60 -0
  108. package/dist/protocol/connection.d.ts.map +1 -0
  109. package/dist/protocol/connection.js +142 -0
  110. package/dist/protocol/connection.js.map +1 -0
  111. package/dist/protocol/frame.d.ts +44 -0
  112. package/dist/protocol/frame.d.ts.map +1 -0
  113. package/dist/protocol/frame.js +68 -0
  114. package/dist/protocol/frame.js.map +1 -0
  115. package/dist/runs/events.d.ts +92 -0
  116. package/dist/runs/events.d.ts.map +1 -0
  117. package/dist/runs/events.js +85 -0
  118. package/dist/runs/events.js.map +1 -0
  119. package/dist/runs/recording.d.ts +35 -0
  120. package/dist/runs/recording.d.ts.map +1 -0
  121. package/dist/runs/recording.js +53 -0
  122. package/dist/runs/recording.js.map +1 -0
  123. package/dist/runs/run.d.ts +81 -0
  124. package/dist/runs/run.d.ts.map +1 -0
  125. package/dist/runs/run.js +175 -0
  126. package/dist/runs/run.js.map +1 -0
  127. package/dist/session/handshake.d.ts +26 -0
  128. package/dist/session/handshake.d.ts.map +1 -0
  129. package/dist/session/handshake.js +32 -0
  130. package/dist/session/handshake.js.map +1 -0
  131. package/dist/session/prompt.d.ts +18 -0
  132. package/dist/session/prompt.d.ts.map +1 -0
  133. package/dist/session/prompt.js +41 -0
  134. package/dist/session/prompt.js.map +1 -0
  135. package/dist/session/store.d.ts +46 -0
  136. package/dist/session/store.d.ts.map +1 -0
  137. package/dist/session/store.js +80 -0
  138. package/dist/session/store.js.map +1 -0
  139. package/dist/spec/check.d.ts +16 -0
  140. package/dist/spec/check.d.ts.map +1 -0
  141. package/dist/spec/check.js +44 -0
  142. package/dist/spec/check.js.map +1 -0
  143. package/dist/spec/define.d.ts +95 -0
  144. package/dist/spec/define.d.ts.map +1 -0
  145. package/dist/spec/define.js +34 -0
  146. package/dist/spec/define.js.map +1 -0
  147. package/dist/spec/identity.d.ts +23 -0
  148. package/dist/spec/identity.d.ts.map +1 -0
  149. package/dist/spec/identity.js +23 -0
  150. package/dist/spec/identity.js.map +1 -0
  151. package/dist/spec/index.d.ts +15 -0
  152. package/dist/spec/index.d.ts.map +1 -0
  153. package/dist/spec/index.js +30 -0
  154. package/dist/spec/index.js.map +1 -0
  155. package/dist/spec/operations/chats.d.ts +25 -0
  156. package/dist/spec/operations/chats.d.ts.map +1 -0
  157. package/dist/spec/operations/chats.js +49 -0
  158. package/dist/spec/operations/chats.js.map +1 -0
  159. package/dist/spec/operations/contacts.d.ts +9 -0
  160. package/dist/spec/operations/contacts.d.ts.map +1 -0
  161. package/dist/spec/operations/contacts.js +39 -0
  162. package/dist/spec/operations/contacts.js.map +1 -0
  163. package/dist/spec/operations/messages.d.ts +19 -0
  164. package/dist/spec/operations/messages.d.ts.map +1 -0
  165. package/dist/spec/operations/messages.js +37 -0
  166. package/dist/spec/operations/messages.js.map +1 -0
  167. package/dist/spec/operations/session.d.ts +69 -0
  168. package/dist/spec/operations/session.d.ts.map +1 -0
  169. package/dist/spec/operations/session.js +106 -0
  170. package/dist/spec/operations/session.js.map +1 -0
  171. package/dist/spec/scalars.d.ts +16 -0
  172. package/dist/spec/scalars.d.ts.map +1 -0
  173. package/dist/spec/scalars.js +28 -0
  174. package/dist/spec/scalars.js.map +1 -0
  175. package/dist/testing/mock-max.d.ts +29 -0
  176. package/dist/testing/mock-max.d.ts.map +1 -0
  177. package/dist/testing/mock-max.js +46 -0
  178. package/dist/testing/mock-max.js.map +1 -0
  179. package/dist/version.d.ts +3 -0
  180. package/dist/version.d.ts.map +1 -0
  181. package/dist/version.js +3 -0
  182. package/dist/version.js.map +1 -0
  183. package/package.json +58 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/session/store.ts"],"names":[],"mappings":"AAGA,OAAO,EAAe,KAAK,YAAY,EAAmD,MAAM,mBAAmB,CAAA;AAEnH;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oGAAoG;IACpG,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAID,qBAAa,YAAY;;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;gBAIZ,EAAE,OAAmB,EAAE,GAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAE,mBAAwB;IAe9G,8DAA8D;IAC9D,SAAS,IAAI,MAAM,GAAG,SAAS;IAI/B,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI/B;;;;;;;OAOG;IACH,SAAS,IAAI,YAAY;IAezB,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAIrC,0FAA0F;IAC1F,MAAM,IAAI,OAAO;CAclB"}
@@ -0,0 +1,80 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { Credentials, pathsAreOverridden, resolvePaths, writeSecurely } from "@leemour/cli-core";
5
+ const APP = "max-cli";
6
+ export class SessionStore {
7
+ profile;
8
+ #stateDir;
9
+ #credentials;
10
+ constructor({ profile = "default", env = process.env, keyring, configDir, stateDir } = {}) {
11
+ const paths = resolvePaths({ appName: APP, prefix: "MAX", env });
12
+ this.profile = profile;
13
+ this.#stateDir = stateDir ?? paths.state;
14
+ this.#credentials = new Credentials({
15
+ configDir: configDir ?? paths.config,
16
+ service: APP,
17
+ envVar: "MAX_TOKEN",
18
+ isolated: pathsAreOverridden({ appName: APP, prefix: "MAX", env }),
19
+ ...(keyring ? { keyring } : {}),
20
+ env,
21
+ warn: (message) => process.stderr.write(`${message}\n`),
22
+ });
23
+ }
24
+ /** `undefined` means nobody has logged in on this profile. */
25
+ readToken() {
26
+ return this.#credentials.read(this.profile)?.secret;
27
+ }
28
+ writeToken(token) {
29
+ this.#credentials.write(this.profile, token);
30
+ }
31
+ /**
32
+ * Reads the state, inventing a device identity the first time and keeping it thereafter.
33
+ *
34
+ * **A new identity is written out immediately, before it is used for anything.** Returning one
35
+ * without saving it looks harmless and is not: a login that fails, or simply a second call,
36
+ * would then present MAX with a different device every time — which is the "new device on every
37
+ * command" that §34 exists to prevent. Found by a test, not by a user.
38
+ */
39
+ readState() {
40
+ try {
41
+ const parsed = JSON.parse(readFileSync(this.#statePath(), "utf8"));
42
+ if (typeof parsed.deviceId === "string" && parsed.deviceId !== "") {
43
+ return { deviceId: parsed.deviceId, logins: parsed.logins ?? 0, ...pick(parsed) };
44
+ }
45
+ }
46
+ catch {
47
+ // No state yet, or a file we cannot read: either way the answer is a fresh identity.
48
+ }
49
+ const fresh = { deviceId: randomUUID(), logins: 0 };
50
+ this.writeState(fresh);
51
+ return fresh;
52
+ }
53
+ writeState(state) {
54
+ writeSecurely(this.#statePath(), `${JSON.stringify(state, null, 2)}\n`, 0o600);
55
+ }
56
+ /** Forgets everything about this profile. Returns whether there was a token to forget. */
57
+ forget() {
58
+ const had = this.readToken() !== undefined;
59
+ this.#credentials.remove(this.profile);
60
+ try {
61
+ writeSecurely(this.#statePath(), `${JSON.stringify({ deviceId: randomUUID(), logins: 0 }, null, 2)}\n`, 0o600);
62
+ }
63
+ catch {
64
+ // Nothing to reset.
65
+ }
66
+ return had;
67
+ }
68
+ #statePath() {
69
+ return join(this.#stateDir, "profiles", `${this.profile}.json`);
70
+ }
71
+ }
72
+ const pick = (state) => {
73
+ const extra = {};
74
+ if (typeof state.viewerId === "string")
75
+ extra.viewerId = state.viewerId;
76
+ if (typeof state.lastLoginAt === "string")
77
+ extra.lastLoginAt = state.lastLoginAt;
78
+ return extra;
79
+ };
80
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/session/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { Payload } from "../protocol/frame.js";
2
+ import { type Operation } from "./define.js";
3
+ /**
4
+ * Compares MAX's answer with what we declared, and **reports without deciding**.
5
+ *
6
+ * Checking is not enforcing. An unofficial protocol changes without telling anyone, so a response
7
+ * never fails a command, never changes an exit code and never alters what the caller gets back
8
+ * (`NEED-35`). Unknown fields are not mismatches at all — response shapes are loose, and a field
9
+ * MAX adds passes straight through.
10
+ *
11
+ * What is left is the case worth hearing about: a field we said we rely on stopped being what it
12
+ * was. The failure this prevents is the quiet one — `max chats` printing a column of blanks
13
+ * because something was renamed, and nobody learning why for a week.
14
+ */
15
+ export declare const checkResponse: (operation: Operation, payload: Payload) => string | undefined;
16
+ //# sourceMappingURL=check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/spec/check.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,aAAa,CAAA;AAEvD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa,GAAI,WAAW,SAAS,EAAE,SAAS,OAAO,KAAG,MAAM,GAAG,SAgB/E,CAAA"}
@@ -0,0 +1,44 @@
1
+ import * as v from "valibot";
2
+ import { fieldPath } from "./define.js";
3
+ /**
4
+ * Compares MAX's answer with what we declared, and **reports without deciding**.
5
+ *
6
+ * Checking is not enforcing. An unofficial protocol changes without telling anyone, so a response
7
+ * never fails a command, never changes an exit code and never alters what the caller gets back
8
+ * (`NEED-35`). Unknown fields are not mismatches at all — response shapes are loose, and a field
9
+ * MAX adds passes straight through.
10
+ *
11
+ * What is left is the case worth hearing about: a field we said we rely on stopped being what it
12
+ * was. The failure this prevents is the quiet one — `max chats` printing a column of blanks
13
+ * because something was renamed, and nobody learning why for a week.
14
+ */
15
+ export const checkResponse = (operation, payload) => {
16
+ try {
17
+ const result = v.safeParse(operation.response, payload);
18
+ if (result.success)
19
+ return undefined;
20
+ const shown = result.issues.slice(0, MOST);
21
+ const rest = result.issues.length - shown.length;
22
+ const detail = shown.map(describe).join("; ");
23
+ return `${operation.name}: MAX answered with something we did not expect — ${detail}${rest > 0 ? ` (and ${rest} more)` : ""}`;
24
+ }
25
+ catch {
26
+ // A diagnostic that breaks the command it was describing is worse than no diagnostic.
27
+ return undefined;
28
+ }
29
+ };
30
+ const MOST = 3;
31
+ /**
32
+ * ⚠ **Built by hand, never from `issue.message`.** Valibot's own text quotes the value it saw —
33
+ * `Invalid type: Expected number but received "…"` — and the value may be somebody's message. Only
34
+ * the field path, the expected type and the *kind* of what arrived leave this function.
35
+ */
36
+ const describe = (issue) => `${fieldPath(issue)} — expected ${issue.expected ?? "something else"}, got ${kindOf(issue.input)}`;
37
+ const kindOf = (value) => {
38
+ if (value === null)
39
+ return "null";
40
+ if (Array.isArray(value))
41
+ return "a list";
42
+ return typeof value;
43
+ };
44
+ //# sourceMappingURL=check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/spec/check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAE5B,OAAO,EAAE,SAAS,EAAkB,MAAM,aAAa,CAAA;AAEvD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,SAAoB,EAAE,OAAgB,EAAsB,EAAE;IAC1F,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QACvD,IAAI,MAAM,CAAC,OAAO;YAAE,OAAO,SAAS,CAAA;QAEpC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;QAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAA;QAChD,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAE7C,OAAO,GAAG,SAAS,CAAC,IAAI,qDAAqD,MAAM,GACjF,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,QAAQ,CAAC,CAAC,CAAC,EACrC,EAAE,CAAA;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,sFAAsF;QACtF,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC,CAAA;AAED,MAAM,IAAI,GAAG,CAAC,CAAA;AAEd;;;;GAIG;AACH,MAAM,QAAQ,GAAG,CAAC,KAAoE,EAAU,EAAE,CAChG,GAAG,SAAS,CAAC,KAAK,CAAC,eAAe,KAAK,CAAC,QAAQ,IAAI,gBAAgB,SAAS,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAA;AAEpG,MAAM,MAAM,GAAG,CAAC,KAAc,EAAU,EAAE;IACxC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAA;IACjC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAA;IACzC,OAAO,OAAO,KAAK,CAAA;AACrB,CAAC,CAAA"}
@@ -0,0 +1,95 @@
1
+ import * as v from "valibot";
2
+ import type { Payload } from "../protocol/frame.js";
3
+ /**
4
+ * How much we actually know about a shape.
5
+ *
6
+ * `REQUIREMENTS.md` §10 asks for four levels. There are five, because most of what this CLI sends
7
+ * is stronger than all four of them: we watched MAX answer it. A claim in a README, a line in
8
+ * somebody's source and a frame we captured are three different things, and flattening the third
9
+ * into "confirmed" loses the one distinction the field exists for.
10
+ *
11
+ * The case this has to express: opcode 36 is `CONTACT_LIST` in two clients and `GET_BLOCKED` in
12
+ * the documentation. "Two sources agree" and "one client does this" must not be the same value.
13
+ */
14
+ export type Confidence =
15
+ /** We sent it and read the answer. The source says when. */
16
+ "measured"
17
+ /** Two or more independent implementations agree, and we have not run it. */
18
+ | "confirmed"
19
+ /** One implementation does this. */
20
+ | "observed"
21
+ /** Nobody states it; it follows from something next to it. */
22
+ | "inferred"
23
+ /** Named here so that a gap is recorded rather than implied. */
24
+ | "unknown";
25
+ export interface Provenance {
26
+ confidence: Confidence;
27
+ /** Where it came from: a file in another project, a document, or a dated measurement. */
28
+ sources: [string, ...string[]];
29
+ /** Anything a reader needs that the sources do not say. */
30
+ notes?: string;
31
+ }
32
+ export interface Operation<TRequest extends v.GenericSchema = v.GenericSchema, TResponse extends v.GenericSchema = v.GenericSchema> {
33
+ readonly kind: "operation";
34
+ /** Dotted and grouped by subject: `chats.list`, `messages.send`. The generator splits on the dot. */
35
+ readonly name: string;
36
+ /**
37
+ * MAX's own name for the number, as captured traffic and other clients spell it — `MSG_SEND`,
38
+ * not `MESSAGES_SEND`. Written out rather than derived from `name`, because the two vocabularies
39
+ * are not the same one: ours groups by subject, MAX's has to match what a frame is called
40
+ * everywhere else (`CONVENTIONS.md`, wire names below the adapter).
41
+ */
42
+ readonly constant: string;
43
+ readonly opcode: number;
44
+ /** Whether it may only be sent after LOGIN. */
45
+ readonly auth: boolean;
46
+ /** Strict: a request must not carry a field nobody has seen. */
47
+ readonly request: TRequest;
48
+ /** Loose: a response that gained a field must not break a command (§29). */
49
+ readonly response: TResponse;
50
+ readonly provenance: Provenance;
51
+ }
52
+ /**
53
+ * A number we know and will not send.
54
+ *
55
+ * Not a `callable: false` flag on an operation, because a reserved opcode has no request shape and
56
+ * half an empty operation invites somebody to fill it in. The generator gives these a registry
57
+ * entry and no method, which is what turns "never call an opcode because it is in the enum" from a
58
+ * rule somebody has to remember into a property of the generated code.
59
+ */
60
+ export interface Reservation {
61
+ readonly kind: "reservation";
62
+ readonly name: string;
63
+ readonly constant: string;
64
+ readonly opcode: number;
65
+ /** Why it is known and still never sent. This is the whole value of the entry. */
66
+ readonly reason: string;
67
+ readonly provenance: Provenance;
68
+ }
69
+ export type Entry = Operation | Reservation;
70
+ export declare const defineOperation: <TRequest extends v.GenericSchema, TResponse extends v.GenericSchema>(operation: Omit<Operation<TRequest, TResponse>, "kind">) => Operation<TRequest, TResponse>;
71
+ export declare const reserveOpcode: (reservation: Omit<Reservation, "kind">) => Reservation;
72
+ /** What a caller passes: a domain string for an id, not a `bigint`. */
73
+ export type RequestOf<TOperation extends Operation> = v.InferInput<TOperation["request"]>;
74
+ /** What goes on the wire once the schema has encoded it. */
75
+ export type WireRequestOf<TOperation extends Operation> = v.InferOutput<TOperation["request"]>;
76
+ export type ResponseOf<TOperation extends Operation> = v.InferOutput<TOperation["response"]>;
77
+ /**
78
+ * Checks and encodes in one step, so a conversion cannot be forgotten in a second place.
79
+ *
80
+ * A refusal here is ours, before anything reaches the socket. **The message names the field and
81
+ * never the value** — a request carries message text, and a validation error is not a place for
82
+ * it to appear.
83
+ */
84
+ export declare const buildRequest: <TOperation extends Operation>(operation: TOperation, input: RequestOf<TOperation>) => Payload;
85
+ /**
86
+ * The dotted path of the field an issue is about, and nothing else.
87
+ *
88
+ * ⚠ **Never `issue.message` and never `issue.received`.** Valibot's own text quotes the value it
89
+ * saw — `Invalid type: Expected number but received "…"` — so printing it puts a message body on
90
+ * the screen and into any log that collects the stream (`REQUIREMENTS.md` §14, §24).
91
+ */
92
+ export declare const fieldPath: (issue: {
93
+ path?: unknown;
94
+ }) => string;
95
+ //# sourceMappingURL=define.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../../src/spec/define.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAC5B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAEnD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,UAAU;AACpB,4DAA4D;AAC1D,UAAU;AACZ,6EAA6E;GAC3E,WAAW;AACb,oCAAoC;GAClC,UAAU;AACZ,8DAA8D;GAC5D,UAAU;AACZ,gEAAgE;GAC9D,SAAS,CAAA;AAEb,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,UAAU,CAAA;IACtB,yFAAyF;IACzF,OAAO,EAAE,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAA;IAC9B,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,SAAS,CACxB,QAAQ,SAAS,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,aAAa,EAClD,SAAS,SAAS,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,aAAa;IAEnD,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;IAC1B,qGAAqG;IACrG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAA;IAC1B,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;IAC5B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAA;CAChC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAA;CAChC;AAED,MAAM,MAAM,KAAK,GAAG,SAAS,GAAG,WAAW,CAAA;AAE3C,eAAO,MAAM,eAAe,GAAI,QAAQ,SAAS,CAAC,CAAC,aAAa,EAAE,SAAS,SAAS,CAAC,CAAC,aAAa,EACjG,WAAW,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,KACtD,SAAS,CAAC,QAAQ,EAAE,SAAS,CAA0C,CAAA;AAE1E,eAAO,MAAM,aAAa,GAAI,aAAa,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,KAAG,WAGrE,CAAA;AAEF,uEAAuE;AACvE,MAAM,MAAM,SAAS,CAAC,UAAU,SAAS,SAAS,IAAI,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAA;AAEzF,4DAA4D;AAC5D,MAAM,MAAM,aAAa,CAAC,UAAU,SAAS,SAAS,IAAI,CAAC,CAAC,WAAW,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAA;AAE9F,MAAM,MAAM,UAAU,CAAC,UAAU,SAAS,SAAS,IAAI,CAAC,CAAC,WAAW,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAA;AAE5F;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GAAI,UAAU,SAAS,SAAS,EACvD,WAAW,UAAU,EACrB,OAAO,SAAS,CAAC,UAAU,CAAC,KAC3B,OAMF,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,KAAG,MAIrD,CAAA"}
@@ -0,0 +1,34 @@
1
+ import { CliError } from "@leemour/cli-core";
2
+ import * as v from "valibot";
3
+ export const defineOperation = (operation) => ({ kind: "operation", ...operation });
4
+ export const reserveOpcode = (reservation) => ({
5
+ kind: "reservation",
6
+ ...reservation,
7
+ });
8
+ /**
9
+ * Checks and encodes in one step, so a conversion cannot be forgotten in a second place.
10
+ *
11
+ * A refusal here is ours, before anything reaches the socket. **The message names the field and
12
+ * never the value** — a request carries message text, and a validation error is not a place for
13
+ * it to appear.
14
+ */
15
+ export const buildRequest = (operation, input) => {
16
+ const result = v.safeParse(operation.request, input);
17
+ if (result.success)
18
+ return result.output;
19
+ const fields = result.issues.map((issue) => fieldPath(issue)).join(", ");
20
+ throw new CliError("validation_error", `${operation.name}: cannot send this request — ${fields}`);
21
+ };
22
+ /**
23
+ * The dotted path of the field an issue is about, and nothing else.
24
+ *
25
+ * ⚠ **Never `issue.message` and never `issue.received`.** Valibot's own text quotes the value it
26
+ * saw — `Invalid type: Expected number but received "…"` — so printing it puts a message body on
27
+ * the screen and into any log that collects the stream (`REQUIREMENTS.md` §14, §24).
28
+ */
29
+ export const fieldPath = (issue) => {
30
+ const path = Array.isArray(issue.path) ? issue.path : [];
31
+ const segments = path.map((step) => String(step?.key ?? "?"));
32
+ return segments.length > 0 ? segments.join(".") : "(the payload itself)";
33
+ };
34
+ //# sourceMappingURL=define.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define.js","sourceRoot":"","sources":["../../src/spec/define.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AA8E5B,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,SAAuD,EACvB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,SAAS,EAAE,CAAC,CAAA;AAE1E,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,WAAsC,EAAe,EAAE,CAAC,CAAC;IACrF,IAAI,EAAE,aAAa;IACnB,GAAG,WAAW;CACf,CAAC,CAAA;AAUF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,SAAqB,EACrB,KAA4B,EACnB,EAAE;IACX,MAAM,MAAM,GAAG,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;IACpD,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC,MAAiB,CAAA;IAEnD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACxE,MAAM,IAAI,QAAQ,CAAC,kBAAkB,EAAE,GAAG,SAAS,CAAC,IAAI,gCAAgC,MAAM,EAAE,CAAC,CAAA;AACnG,CAAC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAyB,EAAU,EAAE;IAC7D,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;IACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,IAAuB,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC,CAAA;IAChF,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAA;AAC1E,CAAC,CAAA"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * What we claim to be on the wire.
3
+ *
4
+ * **We imitate MAX's own web client rather than announcing ourselves** (`REQUIREMENTS.md` §34), so
5
+ * none of these values names this tool. Shape and field names from `max-api-docs/protocol/auth.md`,
6
+ * cross-checked against tsmax's `createWebAgent`.
7
+ *
8
+ * The device identity is deliberately not here: it is generated once per profile and stored,
9
+ * because a client presenting a new device on every command is not imitating anything.
10
+ */
11
+ export declare const WEB_USER_AGENT: {
12
+ readonly deviceType: "WEB";
13
+ readonly pushDeviceType: "WEBPUSH";
14
+ readonly locale: "ru";
15
+ readonly deviceLocale: "ru";
16
+ readonly osVersion: "Linux";
17
+ readonly deviceName: "Chrome";
18
+ readonly headerUserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36";
19
+ readonly appVersion: "26.5.5";
20
+ readonly screen: "1080x1920 1.0x";
21
+ readonly timezone: "Europe/Madrid";
22
+ };
23
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/spec/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;CAYjB,CAAA"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * What we claim to be on the wire.
3
+ *
4
+ * **We imitate MAX's own web client rather than announcing ourselves** (`REQUIREMENTS.md` §34), so
5
+ * none of these values names this tool. Shape and field names from `max-api-docs/protocol/auth.md`,
6
+ * cross-checked against tsmax's `createWebAgent`.
7
+ *
8
+ * The device identity is deliberately not here: it is generated once per profile and stored,
9
+ * because a client presenting a new device on every command is not imitating anything.
10
+ */
11
+ export const WEB_USER_AGENT = {
12
+ deviceType: "WEB",
13
+ pushDeviceType: "WEBPUSH",
14
+ locale: "ru",
15
+ deviceLocale: "ru",
16
+ osVersion: "Linux",
17
+ deviceName: "Chrome",
18
+ headerUserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
19
+ appVersion: "26.5.5",
20
+ screen: "1080x1920 1.0x",
21
+ timezone: "Europe/Madrid",
22
+ };
23
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/spec/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,UAAU,EAAE,KAAK;IACjB,cAAc,EAAE,SAAS;IACzB,MAAM,EAAE,IAAI;IACZ,YAAY,EAAE,IAAI;IAClB,SAAS,EAAE,OAAO;IAClB,UAAU,EAAE,QAAQ;IACpB,eAAe,EACb,uGAAuG;IACzG,UAAU,EAAE,QAAQ;IACpB,MAAM,EAAE,gBAAgB;IACxB,QAAQ,EAAE,eAAe;CACjB,CAAA"}
@@ -0,0 +1,15 @@
1
+ import type { Entry, Operation, Reservation } from "./define.js";
2
+ export { checkResponse } from "./check.js";
3
+ export * from "./define.js";
4
+ export * from "./scalars.js";
5
+ /**
6
+ * Every opcode this project knows, whether or not it is ever sent.
7
+ *
8
+ * **The order is the order it is read in**, and the generator preserves it: the registry, the
9
+ * reference table and the coverage list all come out grouped the same way, which is what makes a
10
+ * regenerated file a small diff instead of a reshuffle.
11
+ */
12
+ export declare const spec: readonly Entry[];
13
+ export declare const operations: readonly Operation[];
14
+ export declare const reservations: readonly Reservation[];
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/spec/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAMhE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC1C,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAE5B;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,EAAE,SAAS,KAAK,EAYhC,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,SAAS,SAAS,EAA2E,CAAA;AAEtH,eAAO,MAAM,YAAY,EAAE,SAAS,WAAW,EAE9C,CAAA"}
@@ -0,0 +1,30 @@
1
+ import { chatMark, chatsHistory, chatsList } from "./operations/chats.js";
2
+ import { contactsInfo, profile, unidentified36 } from "./operations/contacts.js";
3
+ import { messageDelete, messagesSend } from "./operations/messages.js";
4
+ import { logout, sessionInit, sessionLogin } from "./operations/session.js";
5
+ export { checkResponse } from "./check.js";
6
+ export * from "./define.js";
7
+ export * from "./scalars.js";
8
+ /**
9
+ * Every opcode this project knows, whether or not it is ever sent.
10
+ *
11
+ * **The order is the order it is read in**, and the generator preserves it: the registry, the
12
+ * reference table and the coverage list all come out grouped the same way, which is what makes a
13
+ * regenerated file a small diff instead of a reshuffle.
14
+ */
15
+ export const spec = [
16
+ sessionInit,
17
+ sessionLogin,
18
+ logout,
19
+ contactsInfo,
20
+ profile,
21
+ unidentified36,
22
+ chatsHistory,
23
+ chatMark,
24
+ chatsList,
25
+ messagesSend,
26
+ messageDelete,
27
+ ];
28
+ export const operations = spec.filter((entry) => entry.kind === "operation");
29
+ export const reservations = spec.filter((entry) => entry.kind === "reservation");
30
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/spec/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACzE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAChF,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACtE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAE3E,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC1C,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAE5B;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,IAAI,GAAqB;IACpC,WAAW;IACX,YAAY;IACZ,MAAM;IACN,YAAY;IACZ,OAAO;IACP,cAAc;IACd,YAAY;IACZ,QAAQ;IACR,SAAS;IACT,YAAY;IACZ,aAAa;CACd,CAAA;AAED,MAAM,CAAC,MAAM,UAAU,GAAyB,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAsB,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,WAAW,CAAC,CAAA;AAEtH,MAAM,CAAC,MAAM,YAAY,GAA2B,IAAI,CAAC,MAAM,CAC7D,CAAC,KAAK,EAAwB,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,aAAa,CAC9D,CAAA"}
@@ -0,0 +1,25 @@
1
+ import * as v from "valibot";
2
+ export declare const chatsList: import("../define.js").Operation<v.StrictObjectSchema<{
3
+ /** A cursor, not a count: MAX pages from a point in time. */
4
+ readonly marker: v.NumberSchema<undefined>;
5
+ readonly count: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, undefined>;
6
+ }, undefined>, v.LooseObjectSchema<{
7
+ readonly chats: v.OptionalSchema<v.ArraySchema<v.LooseObjectSchema<{}, undefined>, undefined>, undefined>;
8
+ }, undefined>>;
9
+ export declare const chatsHistory: import("../define.js").Operation<v.StrictObjectSchema<{
10
+ readonly chatId: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "an id is a whole number written as a string">, v.TransformAction<string, bigint>]>;
11
+ readonly from: v.NumberSchema<undefined>;
12
+ readonly forward: v.NumberSchema<undefined>;
13
+ readonly backward: v.NumberSchema<undefined>;
14
+ readonly forwardTime: v.NumberSchema<undefined>;
15
+ readonly backwardTime: v.NumberSchema<undefined>;
16
+ readonly itemType: v.LiteralSchema<"REGULAR", undefined>;
17
+ readonly getChat: v.BooleanSchema<undefined>;
18
+ readonly getMessages: v.BooleanSchema<undefined>;
19
+ /** False for the same reason as on login: a script reading is not a person looking. */
20
+ readonly interactive: v.LiteralSchema<false, undefined>;
21
+ }, undefined>, v.LooseObjectSchema<{
22
+ readonly messages: v.OptionalSchema<v.ArraySchema<v.LooseObjectSchema<{}, undefined>, undefined>, undefined>;
23
+ }, undefined>>;
24
+ export declare const chatMark: import("../define.js").Reservation;
25
+ //# sourceMappingURL=chats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chats.d.ts","sourceRoot":"","sources":["../../../src/spec/operations/chats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAI5B,eAAO,MAAM,SAAS;IAMlB,6DAA6D;;;;;cAU/D,CAAA;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;IAerB,uFAAuF;;;;cAKzF,CAAA;AAEF,eAAO,MAAM,QAAQ,oCAOnB,CAAA"}
@@ -0,0 +1,49 @@
1
+ import * as v from "valibot";
2
+ import { defineOperation, reserveOpcode } from "../define.js";
3
+ import { id } from "../scalars.js";
4
+ export const chatsList = defineOperation({
5
+ name: "chats.list",
6
+ constant: "CHATS_LIST",
7
+ opcode: 53,
8
+ auth: true,
9
+ request: v.strictObject({
10
+ /** A cursor, not a count: MAX pages from a point in time. */
11
+ marker: v.number(),
12
+ count: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1))),
13
+ }),
14
+ response: v.looseObject({ chats: v.optional(v.array(v.looseObject({}))) }),
15
+ provenance: {
16
+ confidence: "measured",
17
+ sources: ["measured against MAX 2026-09-19", "max-api-docs/protocol/chats.md"],
18
+ notes: "The documentation describes `count`; tsmax sends only `marker`. Optional here for that reason.",
19
+ },
20
+ });
21
+ export const chatsHistory = defineOperation({
22
+ name: "chats.history",
23
+ constant: "CHAT_HISTORY",
24
+ opcode: 49,
25
+ auth: true,
26
+ request: v.strictObject({
27
+ chatId: id(),
28
+ from: v.number(),
29
+ forward: v.number(),
30
+ backward: v.number(),
31
+ forwardTime: v.number(),
32
+ backwardTime: v.number(),
33
+ itemType: v.literal("REGULAR"),
34
+ getChat: v.boolean(),
35
+ getMessages: v.boolean(),
36
+ /** False for the same reason as on login: a script reading is not a person looking. */
37
+ interactive: v.literal(false),
38
+ }),
39
+ response: v.looseObject({ messages: v.optional(v.array(v.looseObject({}))) }),
40
+ provenance: { confidence: "measured", sources: ["measured against MAX 2026-09-19"] },
41
+ });
42
+ export const chatMark = reserveOpcode({
43
+ name: "chats.mark",
44
+ constant: "CHAT_MARK",
45
+ opcode: 50,
46
+ reason: "Reading is observational by construction. Marking a conversation read is a change to somebody's account that no read command asked for, so 50 is declared here and never sent — and `src/client.test.ts` asserts its absence from everything the client sent.",
47
+ provenance: { confidence: "confirmed", sources: ["tsmax", "max-api-docs/protocol/chats.md"] },
48
+ });
49
+ //# sourceMappingURL=chats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chats.js","sourceRoot":"","sources":["../../../src/spec/operations/chats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAC5B,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AAElC,MAAM,CAAC,MAAM,SAAS,GAAG,eAAe,CAAC;IACvC,IAAI,EAAE,YAAY;IAClB,QAAQ,EAAE,YAAY;IACtB,MAAM,EAAE,EAAE;IACV,IAAI,EAAE,IAAI;IACV,OAAO,EAAE,CAAC,CAAC,YAAY,CAAC;QACtB,6DAA6D;QAC7D,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;QAClB,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;KAClE,CAAC;IACF,QAAQ,EAAE,CAAC,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,UAAU,EAAE;QACV,UAAU,EAAE,UAAU;QACtB,OAAO,EAAE,CAAC,iCAAiC,EAAE,gCAAgC,CAAC;QAC9E,KAAK,EAAE,gGAAgG;KACxG;CACF,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,eAAe,CAAC;IAC1C,IAAI,EAAE,eAAe;IACrB,QAAQ,EAAE,cAAc;IACxB,MAAM,EAAE,EAAE;IACV,IAAI,EAAE,IAAI;IACV,OAAO,EAAE,CAAC,CAAC,YAAY,CAAC;QACtB,MAAM,EAAE,EAAE,EAAE;QACZ,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;QACnB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;QACpB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;QACvB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;QACxB,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;QAC9B,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;QACpB,WAAW,EAAE,CAAC,CAAC,OAAO,EAAE;QACxB,uFAAuF;QACvF,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;KAC9B,CAAC;IACF,QAAQ,EAAE,CAAC,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7E,UAAU,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,iCAAiC,CAAC,EAAE;CACrF,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG,aAAa,CAAC;IACpC,IAAI,EAAE,YAAY;IAClB,QAAQ,EAAE,WAAW;IACrB,MAAM,EAAE,EAAE;IACV,MAAM,EACJ,+PAA+P;IACjQ,UAAU,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,gCAAgC,CAAC,EAAE;CAC9F,CAAC,CAAA"}
@@ -0,0 +1,9 @@
1
+ import * as v from "valibot";
2
+ export declare const contactsInfo: import("../define.js").Operation<v.StrictObjectSchema<{
3
+ readonly contactIds: v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "an id is a whole number written as a string">, v.TransformAction<string, bigint>]>, undefined>;
4
+ }, undefined>, v.LooseObjectSchema<{
5
+ readonly contacts: v.OptionalSchema<v.ArraySchema<v.LooseObjectSchema<{}, undefined>, undefined>, undefined>;
6
+ }, undefined>>;
7
+ export declare const profile: import("../define.js").Reservation;
8
+ export declare const unidentified36: import("../define.js").Reservation;
9
+ //# sourceMappingURL=contacts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contacts.d.ts","sourceRoot":"","sources":["../../../src/spec/operations/contacts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAI5B,eAAO,MAAM,YAAY;;;;cAYvB,CAAA;AAEF,eAAO,MAAM,OAAO,oCAOlB,CAAA;AAEF,eAAO,MAAM,cAAc,oCAgBzB,CAAA"}
@@ -0,0 +1,39 @@
1
+ import * as v from "valibot";
2
+ import { defineOperation, reserveOpcode } from "../define.js";
3
+ import { id } from "../scalars.js";
4
+ export const contactsInfo = defineOperation({
5
+ name: "contacts.info",
6
+ constant: "CONTACT_INFO",
7
+ opcode: 32,
8
+ auth: true,
9
+ request: v.strictObject({ contactIds: v.array(id()) }),
10
+ response: v.looseObject({ contacts: v.optional(v.array(v.looseObject({}))) }),
11
+ provenance: {
12
+ confidence: "measured",
13
+ sources: ["measured against MAX 2026-09-19: asked for ten, got ten"],
14
+ notes: "One request for every unknown id at once. A person with forty dialogs should not cost forty round trips.",
15
+ },
16
+ });
17
+ export const profile = reserveOpcode({
18
+ name: "account.update",
19
+ constant: "PROFILE",
20
+ opcode: 16,
21
+ reason: "It does not read a profile, it updates one, and it refuses an empty payload. Your own profile arrives with the login response, so nothing needs to send this. It is declared here so that fact keeps a home.",
22
+ provenance: { confidence: "measured", sources: ["measured against MAX 2026-09-19: refused an empty payload"] },
23
+ });
24
+ export const unidentified36 = reserveOpcode({
25
+ name: "protocol.unidentified36",
26
+ constant: "UNIDENTIFIED_36",
27
+ opcode: 36,
28
+ reason: "Nobody agrees what it is: tsmax and PyMax call it `CONTACT_LIST`; the protocol documentation calls it `GET_BLOCKED`. Sent once with the owner's permission on 2026-09-20 and it exists — but it refuses every payload we can guess, and one guess closed the connection. It stays unsent until somebody watches a real client send it (`PROTO-1`).",
29
+ provenance: {
30
+ confidence: "unknown",
31
+ sources: [
32
+ "tsmax and PyMax call it CONTACT_LIST",
33
+ "max-api-docs calls it GET_BLOCKED",
34
+ "measured against MAX 2026-09-20: `{}` and `{marker}` are refused with `proto.payload`, `{marker, count}` closes the connection",
35
+ ],
36
+ notes: "The refusal names the payload rather than the opcode, so 36 is real and validates its arguments — we simply do not know its shape. Guessing cost a dropped connection, which is why the ladder stopped at three.",
37
+ },
38
+ });
39
+ //# sourceMappingURL=contacts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contacts.js","sourceRoot":"","sources":["../../../src/spec/operations/contacts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAC5B,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AAElC,MAAM,CAAC,MAAM,YAAY,GAAG,eAAe,CAAC;IAC1C,IAAI,EAAE,eAAe;IACrB,QAAQ,EAAE,cAAc;IACxB,MAAM,EAAE,EAAE;IACV,IAAI,EAAE,IAAI;IACV,OAAO,EAAE,CAAC,CAAC,YAAY,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;IACtD,QAAQ,EAAE,CAAC,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7E,UAAU,EAAE;QACV,UAAU,EAAE,UAAU;QACtB,OAAO,EAAE,CAAC,yDAAyD,CAAC;QACpE,KAAK,EAAE,0GAA0G;KAClH;CACF,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,OAAO,GAAG,aAAa,CAAC;IACnC,IAAI,EAAE,gBAAgB;IACtB,QAAQ,EAAE,SAAS;IACnB,MAAM,EAAE,EAAE;IACV,MAAM,EACJ,8MAA8M;IAChN,UAAU,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,2DAA2D,CAAC,EAAE;CAC/G,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,aAAa,CAAC;IAC1C,IAAI,EAAE,yBAAyB;IAC/B,QAAQ,EAAE,iBAAiB;IAC3B,MAAM,EAAE,EAAE;IACV,MAAM,EACJ,oVAAoV;IACtV,UAAU,EAAE;QACV,UAAU,EAAE,SAAS;QACrB,OAAO,EAAE;YACP,sCAAsC;YACtC,mCAAmC;YACnC,gIAAgI;SACjI;QACD,KAAK,EACH,kNAAkN;KACrN;CACF,CAAC,CAAA"}
@@ -0,0 +1,19 @@
1
+ import * as v from "valibot";
2
+ export declare const messagesSend: import("../define.js").Operation<v.StrictObjectSchema<{
3
+ readonly chatId: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "an id is a whole number written as a string">, v.TransformAction<string, bigint>]>;
4
+ readonly message: v.StrictObjectSchema<{
5
+ readonly text: v.StringSchema<undefined>;
6
+ /**
7
+ * The client id MAX deduplicates by — measured, and it is what makes one retry safe. It must
8
+ * never be regenerated on a retry: a fresh one means a second message in somebody's chat.
9
+ */
10
+ readonly cid: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>]>;
11
+ readonly elements: v.ArraySchema<v.UnknownSchema, undefined>;
12
+ readonly attaches: v.ArraySchema<v.UnknownSchema, undefined>;
13
+ }, undefined>;
14
+ readonly notify: v.BooleanSchema<undefined>;
15
+ }, undefined>, v.LooseObjectSchema<{
16
+ readonly message: v.OptionalSchema<v.LooseObjectSchema<{}, undefined>, undefined>;
17
+ }, undefined>>;
18
+ export declare const messageDelete: import("../define.js").Reservation;
19
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/spec/operations/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAI5B,eAAO,MAAM,YAAY;;;;QASnB;;;WAGG;;;;;;;;cAaP,CAAA;AAEF,eAAO,MAAM,aAAa,oCAOxB,CAAA"}
@@ -0,0 +1,37 @@
1
+ import * as v from "valibot";
2
+ import { defineOperation, reserveOpcode } from "../define.js";
3
+ import { id } from "../scalars.js";
4
+ export const messagesSend = defineOperation({
5
+ name: "messages.send",
6
+ constant: "MSG_SEND",
7
+ opcode: 64,
8
+ auth: true,
9
+ request: v.strictObject({
10
+ chatId: id(),
11
+ message: v.strictObject({
12
+ text: v.string(),
13
+ /**
14
+ * The client id MAX deduplicates by — measured, and it is what makes one retry safe. It must
15
+ * never be regenerated on a retry: a fresh one means a second message in somebody's chat.
16
+ */
17
+ cid: v.pipe(v.number(), v.integer()),
18
+ elements: v.array(v.unknown()),
19
+ attaches: v.array(v.unknown()),
20
+ }),
21
+ notify: v.boolean(),
22
+ }),
23
+ response: v.looseObject({ message: v.optional(v.looseObject({})) }),
24
+ provenance: {
25
+ confidence: "measured",
26
+ sources: ["measured against MAX 2026-09-19, including deduplication by `cid` across two connections"],
27
+ notes: "How long MAX remembers a `cid` is still unmeasured (`PROTO-2`); the two probes were seconds apart.",
28
+ },
29
+ });
30
+ export const messageDelete = reserveOpcode({
31
+ name: "messages.delete",
32
+ constant: "MSG_DELETE",
33
+ opcode: 66,
34
+ reason: "Deliberately never called. Giving this tool the ability to destroy somebody's messages, in order to tidy up after a test, is a poor trade (`NEED-32`).",
35
+ provenance: { confidence: "observed", sources: ["tsmax"] },
36
+ });
37
+ //# sourceMappingURL=messages.js.map