@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":"messages.js","sourceRoot":"","sources":["../../../src/spec/operations/messages.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,UAAU;IACpB,MAAM,EAAE,EAAE;IACV,IAAI,EAAE,IAAI;IACV,OAAO,EAAE,CAAC,CAAC,YAAY,CAAC;QACtB,MAAM,EAAE,EAAE,EAAE;QACZ,OAAO,EAAE,CAAC,CAAC,YAAY,CAAC;YACtB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;YAChB;;;eAGG;YACH,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;YACpC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;YAC9B,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;SAC/B,CAAC;QACF,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE;KACpB,CAAC;IACF,QAAQ,EAAE,CAAC,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;IACnE,UAAU,EAAE;QACV,UAAU,EAAE,UAAU;QACtB,OAAO,EAAE,CAAC,0FAA0F,CAAC;QACrG,KAAK,EAAE,oGAAoG;KAC5G;CACF,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAC;IACzC,IAAI,EAAE,iBAAiB;IACvB,QAAQ,EAAE,YAAY;IACtB,MAAM,EAAE,EAAE;IACV,MAAM,EACJ,wJAAwJ;IAC1J,UAAU,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE;CAC3D,CAAC,CAAA"}
@@ -0,0 +1,69 @@
1
+ import * as v from "valibot";
2
+ export declare const sessionInit: import("../define.js").Operation<v.StrictObjectSchema<{
3
+ readonly userAgent: v.StrictObjectSchema<{
4
+ readonly deviceType: v.LiteralSchema<"WEB", undefined>;
5
+ readonly pushDeviceType: v.LiteralSchema<"WEBPUSH", undefined>;
6
+ readonly locale: v.StringSchema<undefined>;
7
+ readonly deviceLocale: v.StringSchema<undefined>;
8
+ readonly osVersion: v.StringSchema<undefined>;
9
+ readonly deviceName: v.StringSchema<undefined>;
10
+ readonly headerUserAgent: v.StringSchema<undefined>;
11
+ readonly appVersion: v.StringSchema<undefined>;
12
+ readonly screen: v.StringSchema<undefined>;
13
+ readonly timezone: v.StringSchema<undefined>;
14
+ }, undefined>;
15
+ readonly deviceId: v.StringSchema<undefined>;
16
+ }, undefined>, v.LooseObjectSchema<{}, undefined>>;
17
+ export declare const sessionLogin: import("../define.js").Operation<v.StrictObjectSchema<{
18
+ readonly token: v.StringSchema<undefined>;
19
+ /**
20
+ * False, always. The web client sends true because a person is looking at it; we are a script
21
+ * reading, and claiming a human is present is the small lie that becomes a side effect nobody
22
+ * can trace (`RES-5`).
23
+ */
24
+ readonly interactive: v.LiteralSchema<false, undefined>;
25
+ readonly chatsCount: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>, v.MaxValueAction<number, 100, undefined>]>;
26
+ /**
27
+ * **Delta markers, not flags** — measured 2026-09-20. Each is a moment in time, and MAX
28
+ * returns only what changed in that collection since it. `0` means "everything", which is what
29
+ * `src/session/handshake.ts` sends and is why every command re-fetches the whole list.
30
+ *
31
+ * The measurement, on a real account: `0` returned 6 contacts and 25 chats; the `time` the
32
+ * previous login answered with returned 0 and 0; a week earlier returned 1 and 11. The last of
33
+ * those is the one that matters — without it, "nothing came back" would equally have meant
34
+ * "any non-zero value suppresses the collection".
35
+ *
36
+ * Feeding them back is `MAX-10`, and it needs somewhere to keep the marker per profile.
37
+ */
38
+ readonly chatsSync: v.NumberSchema<undefined>;
39
+ readonly contactsSync: v.NumberSchema<undefined>;
40
+ readonly presenceSync: v.NumberSchema<undefined>;
41
+ readonly draftsSync: v.NumberSchema<undefined>;
42
+ }, undefined>, v.LooseObjectSchema<{
43
+ readonly profile: v.OptionalSchema<v.LooseObjectSchema<{}, undefined>, undefined>;
44
+ readonly chats: v.OptionalSchema<v.ArraySchema<v.LooseObjectSchema<{}, undefined>, undefined>, undefined>;
45
+ readonly contacts: v.OptionalSchema<v.ArraySchema<v.LooseObjectSchema<{}, undefined>, undefined>, undefined>;
46
+ /**
47
+ * ⚠ **Shape unknown, and this field is why the response check exists.**
48
+ *
49
+ * It was declared as an array here and MAX sends an **object** — found 2026-09-20 by routing
50
+ * the handshake through the checked path. Nothing broke, because the reader is tolerant; what
51
+ * happened instead is that every message the login carries has been silently discarded since
52
+ * the specification was written, after we paid a request for them.
53
+ *
54
+ * The reference documents no top-level `messages` on this response at all — each chat carries
55
+ * its own `lastMessage` (`max-api-docs/protocol/chats.md:61`). So the array was **invented**,
56
+ * not misread, which is the failure `REQUIREMENTS.md` §10 exists to prevent: an unsourced
57
+ * shape stated with the same confidence as a measured one.
58
+ *
59
+ * Left as `unknown` until somebody looks (`PROTO-6`). `pnpm probe:ids` reports its type and key count.
60
+ */
61
+ readonly messages: v.OptionalSchema<v.UnknownSchema, undefined>;
62
+ readonly presence: v.OptionalSchema<v.UnknownSchema, undefined>;
63
+ readonly time: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
64
+ readonly chatMarker: v.OptionalSchema<v.UnknownSchema, undefined>;
65
+ readonly config: v.OptionalSchema<v.UnknownSchema, undefined>;
66
+ readonly videoChatHistory: v.OptionalSchema<v.UnknownSchema, undefined>;
67
+ }, undefined>>;
68
+ export declare const logout: import("../define.js").Reservation;
69
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/spec/operations/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAsB5B,eAAO,MAAM,WAAW;;;;;;;;;;;;;;kDAYtB,CAAA;AAEF,eAAO,MAAM,YAAY;;IAOrB;;;;OAIG;;;IAKH;;;;;;;;;;;OAWG;;;;;;;;;IAUH;;;;;;;;;;;;;;OAcG;;;;;;;cAcL,CAAA;AAEF,eAAO,MAAM,MAAM,oCAOjB,CAAA"}
@@ -0,0 +1,106 @@
1
+ import * as v from "valibot";
2
+ import { defineOperation, reserveOpcode } from "../define.js";
3
+ /**
4
+ * What we claim to be, as a shape rather than a constant.
5
+ *
6
+ * Declaring it strictly is the point: every field here is one the official web client sends
7
+ * (`REQUIREMENTS.md` §34), and a field we invented would be refused before it reached the socket.
8
+ */
9
+ const WebUserAgent = v.strictObject({
10
+ deviceType: v.literal("WEB"),
11
+ pushDeviceType: v.literal("WEBPUSH"),
12
+ locale: v.string(),
13
+ deviceLocale: v.string(),
14
+ osVersion: v.string(),
15
+ deviceName: v.string(),
16
+ headerUserAgent: v.string(),
17
+ appVersion: v.string(),
18
+ screen: v.string(),
19
+ timezone: v.string(),
20
+ });
21
+ export const sessionInit = defineOperation({
22
+ name: "session.init",
23
+ constant: "SESSION_INIT",
24
+ opcode: 6,
25
+ auth: false,
26
+ request: v.strictObject({ userAgent: WebUserAgent, deviceId: v.string() }),
27
+ response: v.looseObject({}),
28
+ provenance: {
29
+ confidence: "measured",
30
+ sources: ["measured against MAX 2026-09-19", "max-api-docs/protocol/auth.md", "tsmax createWebAgent"],
31
+ notes: "MAX answers nothing before this and LOGIN, in that order.",
32
+ },
33
+ });
34
+ export const sessionLogin = defineOperation({
35
+ name: "session.login",
36
+ constant: "LOGIN",
37
+ opcode: 19,
38
+ auth: false,
39
+ request: v.strictObject({
40
+ token: v.string(),
41
+ /**
42
+ * False, always. The web client sends true because a person is looking at it; we are a script
43
+ * reading, and claiming a human is present is the small lie that becomes a side effect nobody
44
+ * can trace (`RES-5`).
45
+ */
46
+ interactive: v.literal(false),
47
+ // 100 works and 200 comes back `'chatsCount' out of range`; refusing locally beats a round
48
+ // trip that only tells us what we already know. The real boundary is `PROTO-3`.
49
+ chatsCount: v.pipe(v.number(), v.integer(), v.minValue(1), v.maxValue(100)),
50
+ /**
51
+ * **Delta markers, not flags** — measured 2026-09-20. Each is a moment in time, and MAX
52
+ * returns only what changed in that collection since it. `0` means "everything", which is what
53
+ * `src/session/handshake.ts` sends and is why every command re-fetches the whole list.
54
+ *
55
+ * The measurement, on a real account: `0` returned 6 contacts and 25 chats; the `time` the
56
+ * previous login answered with returned 0 and 0; a week earlier returned 1 and 11. The last of
57
+ * those is the one that matters — without it, "nothing came back" would equally have meant
58
+ * "any non-zero value suppresses the collection".
59
+ *
60
+ * Feeding them back is `MAX-10`, and it needs somewhere to keep the marker per profile.
61
+ */
62
+ chatsSync: v.number(),
63
+ contactsSync: v.number(),
64
+ presenceSync: v.number(),
65
+ draftsSync: v.number(),
66
+ }),
67
+ response: v.looseObject({
68
+ profile: v.optional(v.looseObject({})),
69
+ chats: v.optional(v.array(v.looseObject({}))),
70
+ contacts: v.optional(v.array(v.looseObject({}))),
71
+ /**
72
+ * ⚠ **Shape unknown, and this field is why the response check exists.**
73
+ *
74
+ * It was declared as an array here and MAX sends an **object** — found 2026-09-20 by routing
75
+ * the handshake through the checked path. Nothing broke, because the reader is tolerant; what
76
+ * happened instead is that every message the login carries has been silently discarded since
77
+ * the specification was written, after we paid a request for them.
78
+ *
79
+ * The reference documents no top-level `messages` on this response at all — each chat carries
80
+ * its own `lastMessage` (`max-api-docs/protocol/chats.md:61`). So the array was **invented**,
81
+ * not misread, which is the failure `REQUIREMENTS.md` §10 exists to prevent: an unsourced
82
+ * shape stated with the same confidence as a measured one.
83
+ *
84
+ * Left as `unknown` until somebody looks (`PROTO-6`). `pnpm probe:ids` reports its type and key count.
85
+ */
86
+ messages: v.optional(v.unknown()),
87
+ presence: v.optional(v.unknown()),
88
+ time: v.optional(v.number()),
89
+ chatMarker: v.optional(v.unknown()),
90
+ config: v.optional(v.unknown()),
91
+ videoChatHistory: v.optional(v.unknown()),
92
+ }),
93
+ provenance: {
94
+ confidence: "measured",
95
+ sources: ["measured against MAX 2026-09-19", "measured against MAX 2026-09-20 (`messages` is an object)"],
96
+ notes: "Unusually generous: the answer carries the profile, chats, contacts and recent messages, so `me` and `chats` need no further request.",
97
+ },
98
+ });
99
+ export const logout = reserveOpcode({
100
+ name: "session.logout",
101
+ constant: "LOGOUT",
102
+ opcode: 20,
103
+ reason: "`max session end` forgets the token locally and tells MAX nothing. Ending the session server-side would also end it for the browser tab the token came from, which is not what the command promises.",
104
+ provenance: { confidence: "observed", sources: ["max-api-docs/protocol/auth.md"] },
105
+ });
106
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/spec/operations/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import * as v from "valibot";
2
+ export declare const toWireId: (value: string) => bigint;
3
+ /**
4
+ * An id in a request we build: a domain string in, the wire form out.
5
+ *
6
+ * The schema is the encoder as well as the check, so there is no second place where a conversion
7
+ * could be forgotten — `v.parse(request, { chatId: "0" })` yields `{ chatId: 0n }`.
8
+ */
9
+ export declare const id: () => v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "an id is a whole number written as a string">, v.TransformAction<string, bigint>]>;
10
+ /**
11
+ * An id in an answer we read. **Not the same as `id()` and not a transform**: a response schema
12
+ * reports, it does not convert (§4.6 of the plan), and the conversion to our own string form is
13
+ * the domain mapper's job.
14
+ */
15
+ export declare const wireId: () => v.UnionSchema<[v.StringSchema<undefined>, v.NumberSchema<undefined>, v.BigintSchema<undefined>], undefined>;
16
+ //# sourceMappingURL=scalars.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scalars.d.ts","sourceRoot":"","sources":["../../src/spec/scalars.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAgB5B,eAAO,MAAM,QAAQ,GAAI,OAAO,MAAM,KAAG,MAAuB,CAAA;AAEhE;;;;;GAKG;AACH,eAAO,MAAM,EAAE,uKACkG,CAAA;AAEjH;;;;GAIG;AACH,eAAO,MAAM,MAAM,mHAAsD,CAAA"}
@@ -0,0 +1,28 @@
1
+ import * as v from "valibot";
2
+ /**
3
+ * The two directions an id travels, and why they are not the same schema.
4
+ *
5
+ * MAX ids are 64-bit. `asId` in `../protocol/frame.ts` is the read direction: whatever arrived —
6
+ * a number when small, a bigint when not — leaves as a string, because an id is an identifier and
7
+ * never arithmetic. The write direction did not exist, and `Number(id)` was standing in for it:
8
+ * `Number("7268926000000000001")` is `7268926000000000000`, which is a different chat.
9
+ *
10
+ * `lossless-json` serialises a bigint as a bare number literal, so a `bigint` in a payload reaches
11
+ * MAX with every digit and an id below 2^53 produces the identical bytes it does today.
12
+ */
13
+ const WHOLE_NUMBER = /^-?\d+$/;
14
+ export const toWireId = (value) => BigInt(value);
15
+ /**
16
+ * An id in a request we build: a domain string in, the wire form out.
17
+ *
18
+ * The schema is the encoder as well as the check, so there is no second place where a conversion
19
+ * could be forgotten — `v.parse(request, { chatId: "0" })` yields `{ chatId: 0n }`.
20
+ */
21
+ export const id = () => v.pipe(v.string(), v.regex(WHOLE_NUMBER, "an id is a whole number written as a string"), v.transform(toWireId));
22
+ /**
23
+ * An id in an answer we read. **Not the same as `id()` and not a transform**: a response schema
24
+ * reports, it does not convert (§4.6 of the plan), and the conversion to our own string form is
25
+ * the domain mapper's job.
26
+ */
27
+ export const wireId = () => v.union([v.string(), v.number(), v.bigint()]);
28
+ //# sourceMappingURL=scalars.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scalars.js","sourceRoot":"","sources":["../../src/spec/scalars.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAE5B;;;;;;;;;;GAUG;AAEH,MAAM,YAAY,GAAG,SAAS,CAAA;AAE9B,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAEhE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,EAAE,GAAG,GAAG,EAAE,CACrB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,YAAY,EAAE,6CAA6C,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAA;AAEjH;;;;GAIG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA"}
@@ -0,0 +1,29 @@
1
+ import type { WebSocket } from "ws";
2
+ import { type Payload } from "../protocol/frame.js";
3
+ export interface MockMaxOptions {
4
+ /** One answer per opcode. An opcode with no answer is an unexpected call, and the test fails. */
5
+ /** A function may return `undefined` to stay silent, which is how a timeout is scripted. */
6
+ answers: Record<number, Payload | (() => Payload | undefined)>;
7
+ /** Opcodes to refuse, as MAX does: `cmd=3` with an `error` in the payload. */
8
+ refuse?: Record<number, string>;
9
+ }
10
+ export interface MockMax {
11
+ createSocket: () => WebSocket;
12
+ /** Every request sent, in order — what the code under test actually asked MAX for. */
13
+ sent: {
14
+ opcode: number;
15
+ payload: Payload;
16
+ }[];
17
+ /** Calls with no scripted answer. A test that ignores these can pass for the wrong reason. */
18
+ unexpected: number[];
19
+ closed: boolean;
20
+ }
21
+ /**
22
+ * MAX, scripted. **No timers, no network, no waiting**: an answer is delivered on the next tick,
23
+ * so a timeout test costs nothing and a suite cannot reach the real service.
24
+ *
25
+ * It records what was sent, which is how "reading history never marks anything read" is a test
26
+ * rather than a hope: assert that opcode 50 is absent from `sent`.
27
+ */
28
+ export declare const mockMax: ({ answers, refuse }: MockMaxOptions) => MockMax;
29
+ //# sourceMappingURL=mock-max.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-max.d.ts","sourceRoot":"","sources":["../../src/testing/mock-max.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAA;AACnC,OAAO,EAAqC,KAAK,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAEtF,MAAM,WAAW,cAAc;IAC7B,iGAAiG;IACjG,4FAA4F;IAC5F,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAA;IAC9D,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,OAAO;IACtB,YAAY,EAAE,MAAM,SAAS,CAAA;IAC7B,sFAAsF;IACtF,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;IAC5C,8FAA8F;IAC9F,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,MAAM,EAAE,OAAO,CAAA;CAChB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,GAAI,qBAA0B,cAAc,KAAG,OA2ClE,CAAA"}
@@ -0,0 +1,46 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { Command, decodeFrame, encodeFrame } from "../protocol/frame.js";
3
+ /**
4
+ * MAX, scripted. **No timers, no network, no waiting**: an answer is delivered on the next tick,
5
+ * so a timeout test costs nothing and a suite cannot reach the real service.
6
+ *
7
+ * It records what was sent, which is how "reading history never marks anything read" is a test
8
+ * rather than a hope: assert that opcode 50 is absent from `sent`.
9
+ */
10
+ export const mockMax = ({ answers, refuse = {} }) => {
11
+ const state = { createSocket: () => socket, sent: [], unexpected: [], closed: false };
12
+ const socket = new (class extends EventEmitter {
13
+ readyState = 1;
14
+ OPEN = 1;
15
+ CONNECTING = 0;
16
+ CLOSING = 2;
17
+ send(raw) {
18
+ const frame = decodeFrame(raw);
19
+ state.sent.push({ opcode: frame.opcode, payload: frame.payload ?? {} });
20
+ const refusal = refuse[frame.opcode];
21
+ if (refusal !== undefined) {
22
+ this.answer({ seq: frame.seq ?? 0, opcode: frame.opcode, payload: { error: refusal }, cmd: Command.ERROR });
23
+ return;
24
+ }
25
+ const answer = answers[frame.opcode];
26
+ if (answer === undefined) {
27
+ state.unexpected.push(frame.opcode);
28
+ return;
29
+ }
30
+ const payload = typeof answer === "function" ? answer() : answer;
31
+ if (payload === undefined)
32
+ return; // Silence: the caller will time out, as MAX sometimes does.
33
+ this.answer({ seq: frame.seq ?? 0, opcode: frame.opcode, payload, cmd: Command.RESPONSE });
34
+ }
35
+ answer(frame) {
36
+ queueMicrotask(() => this.emit("message", encodeFrame(frame)));
37
+ }
38
+ close() {
39
+ state.closed = true;
40
+ this.readyState = 3;
41
+ }
42
+ })();
43
+ queueMicrotask(() => socket.emit("open"));
44
+ return state;
45
+ };
46
+ //# sourceMappingURL=mock-max.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-max.js","sourceRoot":"","sources":["../../src/testing/mock-max.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1C,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAgB,MAAM,sBAAsB,CAAA;AAmBtF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,EAAE,EAAkB,EAAW,EAAE;IAC3E,MAAM,KAAK,GAAY,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,MAA8B,EAAE,IAAI,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IAEtH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAM,SAAQ,YAAY;QAC5C,UAAU,GAAG,CAAC,CAAA;QACL,IAAI,GAAG,CAAC,CAAA;QACR,UAAU,GAAG,CAAC,CAAA;QACd,OAAO,GAAG,CAAC,CAAA;QAEpB,IAAI,CAAC,GAAW;YACd,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAA;YAC9B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC,CAAA;YAEvE,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;YACpC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC1B,IAAI,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAA;gBAC3G,OAAM;YACR,CAAC;YAED,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;YACpC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;gBACnC,OAAM;YACR,CAAC;YAED,MAAM,OAAO,GAAG,OAAO,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAA;YAChE,IAAI,OAAO,KAAK,SAAS;gBAAE,OAAM,CAAC,4DAA4D;YAE9F,IAAI,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAA;QAC5F,CAAC;QAED,MAAM,CAAC,KAAsE;YAC3E,cAAc,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAChE,CAAC;QAED,KAAK;YACH,KAAK,CAAC,MAAM,GAAG,IAAI,CAAA;YACnB,IAAI,CAAC,UAAU,GAAG,CAAC,CAAA;QACrB,CAAC;KACF,CAAC,EAAE,CAAA;IAEJ,cAAc,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;IACzC,OAAO,KAAK,CAAA;AACd,CAAC,CAAA"}
@@ -0,0 +1,3 @@
1
+ /** The one the running command reports. `pnpm version:check` fails when it and package.json disagree. */
2
+ export declare const VERSION = "0.1.0";
3
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,yGAAyG;AACzG,eAAO,MAAM,OAAO,UAAU,CAAA"}
@@ -0,0 +1,3 @@
1
+ /** The one the running command reports. `pnpm version:check` fails when it and package.json disagree. */
2
+ export const VERSION = "0.1.0";
3
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,yGAAyG;AACzG,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAA"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@leemour/max-cli",
3
+ "version": "0.1.0",
4
+ "description": "A local command line interface for a personal MAX Messenger account, built for agents and scripts",
5
+ "license": "MIT",
6
+ "author": "Viacheslav Ptsarev",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/leemour/max-cli.git"
10
+ },
11
+ "type": "module",
12
+ "engines": {
13
+ "node": ">=22"
14
+ },
15
+ "bin": {
16
+ "max": "dist/bin/max.js"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "dependencies": {
24
+ "@leemour/cli-core": "^0.1.0",
25
+ "commander": "^15.0.0",
26
+ "lossless-json": "^4.3.1",
27
+ "valibot": "^1.5.0",
28
+ "ws": "^8.19.0"
29
+ },
30
+ "devDependencies": {
31
+ "@biomejs/biome": "^2.3.14",
32
+ "@types/node": "^22.10.2",
33
+ "@types/ws": "^8.18.1",
34
+ "typescript": "^5.9.3",
35
+ "vitest": "^3.2.4",
36
+ "lefthook": "^2.1.8"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc --build",
43
+ "generate": "pnpm build && node --experimental-strip-types scripts/generate.ts && node --experimental-strip-types scripts/commands.ts",
44
+ "typecheck": "tsc --build --force --emitDeclarationOnly && tsc -p tsconfig.test.json",
45
+ "lint": "biome check .",
46
+ "lint:fix": "biome check --write .",
47
+ "test": "vitest run",
48
+ "test:watch": "vitest",
49
+ "smoke:bun": "bun run scripts/smoke.ts",
50
+ "version:check": "node --experimental-strip-types scripts/version.ts",
51
+ "version:sync": "node --experimental-strip-types scripts/version.ts --sync",
52
+ "probe:ids": "pnpm build && node --experimental-strip-types scripts/id-shape.ts",
53
+ "probe:edits": "pnpm build && node --experimental-strip-types scripts/probe-edits.ts",
54
+ "verify:live": "pnpm build && node --experimental-strip-types scripts/verify-commands.ts",
55
+ "secrets:scan": "gitleaks git --no-banner --redact .",
56
+ "probe:contacts": "pnpm build && node --experimental-strip-types scripts/probe-contacts.ts"
57
+ }
58
+ }