@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,57 @@
1
+ /**
2
+ * What this CLI promises. **Raw MAX objects never reach output** (REQUIREMENTS §16, §25): the wire
3
+ * shape is undocumented, changes without notice, and carries fields whose meaning nobody knows.
4
+ * Everything above the adapter speaks these types.
5
+ */
6
+ /** Every id is a string, never a number: MAX ids are 64-bit and an id is an identifier, not arithmetic. */
7
+ export type Id = string;
8
+ export type ChatKind = "dialog" | "group" | "channel" | "unknown";
9
+ export interface Chat {
10
+ id: Id;
11
+ title: string | null;
12
+ kind: ChatKind;
13
+ /** `null` means MAX did not say, which is not the same as zero. */
14
+ unreadCount: number | null;
15
+ /** ISO 8601, or `null` when the chat has never had a message. */
16
+ lastMessageAt: string | null;
17
+ participantsCount: number | null;
18
+ }
19
+ export interface Attachment {
20
+ /** Lower-cased MAX type: `photo`, `video`, `file`, `share`, `call`, `control`, `sticker`… */
21
+ kind: string;
22
+ }
23
+ export interface Message {
24
+ id: Id;
25
+ chatId: Id;
26
+ senderId: Id | null;
27
+ senderName: string | null;
28
+ /** ISO 8601. */
29
+ timestamp: string;
30
+ /**
31
+ * When MAX last recorded an edit, or `null` for a message nobody has changed.
32
+ *
33
+ * Measured 2026-09-20: an edited message comes back carrying `status: "EDITED"` and an
34
+ * `updateTime` later than its `time`. Surfaced rather than hidden because a reader has no other
35
+ * way to tell an edited message from the original, and because it is what the cache compares to
36
+ * decide whether the copy it holds is still current.
37
+ */
38
+ editedAt: string | null;
39
+ text: string;
40
+ /** Whether this account sent it. `null` when we do not know who we are. */
41
+ outgoing: boolean | null;
42
+ attachments: Attachment[];
43
+ }
44
+ export interface Contact {
45
+ id: Id;
46
+ name: string | null;
47
+ /** The @link MAX shows for a person, when they have one. */
48
+ username: string | null;
49
+ /** Whatever they wrote about themselves. */
50
+ description: string | null;
51
+ }
52
+ export interface Profile {
53
+ id: Id;
54
+ name: string | null;
55
+ phone: string | null;
56
+ }
57
+ //# sourceMappingURL=models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/domain/models.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,2GAA2G;AAC3G,MAAM,MAAM,EAAE,GAAG,MAAM,CAAA;AAEvB,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAA;AAEjE,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,EAAE,CAAA;IACN,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,IAAI,EAAE,QAAQ,CAAA;IACd,mEAAmE;IACnE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,iEAAiE;IACjE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAED,MAAM,WAAW,UAAU;IACzB,6FAA6F;IAC7F,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,EAAE,CAAA;IACN,MAAM,EAAE,EAAE,CAAA;IACV,QAAQ,EAAE,EAAE,GAAG,IAAI,CAAA;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,gBAAgB;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;;;OAOG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,2EAA2E;IAC3E,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAA;IACxB,WAAW,EAAE,UAAU,EAAE,CAAA;CAC1B;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,EAAE,CAAA;IACN,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,4CAA4C;IAC5C,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,EAAE,CAAA;IACN,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * What this CLI promises. **Raw MAX objects never reach output** (REQUIREMENTS §16, §25): the wire
3
+ * shape is undocumented, changes without notice, and carries fields whose meaning nobody knows.
4
+ * Everything above the adapter speaks these types.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/domain/models.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,30 @@
1
+ import type { Payload } from "../protocol/frame.js";
2
+ import type { Operation, RequestOf } from "../spec/define.js";
3
+ import { OPERATIONS } from "./operations.generated.js";
4
+ export type Invoke = <TOperation extends Operation>(operation: TOperation, request: RequestOf<TOperation>) => Promise<Payload>;
5
+ /**
6
+ * The wire surface: one typed call per operation, and nothing else.
7
+ *
8
+ * **It is internal.** Everything it returns is a raw MAX payload, with none of the rules that make
9
+ * this tool safe to point at a real account — the resend policy, the name filling, the conversion
10
+ * that keeps a 64-bit id from being read as a number. `MaxClient` is the only public door
11
+ * (`NEED-34`).
12
+ */
13
+ export declare const wireClient: (invoke: Invoke) => {
14
+ session: {
15
+ init: (request: RequestOf<(typeof OPERATIONS)["session.init"]>) => Promise<Payload>;
16
+ login: (request: RequestOf<(typeof OPERATIONS)["session.login"]>) => Promise<Payload>;
17
+ };
18
+ contacts: {
19
+ info: (request: RequestOf<(typeof OPERATIONS)["contacts.info"]>) => Promise<Payload>;
20
+ };
21
+ chats: {
22
+ history: (request: RequestOf<(typeof OPERATIONS)["chats.history"]>) => Promise<Payload>;
23
+ list: (request: RequestOf<(typeof OPERATIONS)["chats.list"]>) => Promise<Payload>;
24
+ };
25
+ messages: {
26
+ send: (request: RequestOf<(typeof OPERATIONS)["messages.send"]>) => Promise<Payload>;
27
+ };
28
+ };
29
+ export type WireClient = ReturnType<typeof wireClient>;
30
+ //# sourceMappingURL=client.generated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.generated.d.ts","sourceRoot":"","sources":["../../src/generated/client.generated.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAEtD,MAAM,MAAM,MAAM,GAAG,CAAC,UAAU,SAAS,SAAS,EAChD,SAAS,EAAE,UAAU,EACrB,OAAO,EAAE,SAAS,CAAC,UAAU,CAAC,KAC3B,OAAO,CAAC,OAAO,CAAC,CAAA;AAErB;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM;;wBAErB,SAAS,CAAC,CAAC,OAAO,UAAU,EAAE,cAAc,CAAC,CAAC;yBAC7C,SAAS,CAAC,CAAC,OAAO,UAAU,EAAE,eAAe,CAAC,CAAC;;;wBAGhD,SAAS,CAAC,CAAC,OAAO,UAAU,EAAE,eAAe,CAAC,CAAC;;;2BAG5C,SAAS,CAAC,CAAC,OAAO,UAAU,EAAE,eAAe,CAAC,CAAC;wBAClD,SAAS,CAAC,CAAC,OAAO,UAAU,EAAE,YAAY,CAAC,CAAC;;;wBAG5C,SAAS,CAAC,CAAC,OAAO,UAAU,EAAE,eAAe,CAAC,CAAC;;CAEjE,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAA"}
@@ -0,0 +1,26 @@
1
+ import { OPERATIONS } from "./operations.generated.js";
2
+ /**
3
+ * The wire surface: one typed call per operation, and nothing else.
4
+ *
5
+ * **It is internal.** Everything it returns is a raw MAX payload, with none of the rules that make
6
+ * this tool safe to point at a real account — the resend policy, the name filling, the conversion
7
+ * that keeps a 64-bit id from being read as a number. `MaxClient` is the only public door
8
+ * (`NEED-34`).
9
+ */
10
+ export const wireClient = (invoke) => ({
11
+ session: {
12
+ init: (request) => invoke(OPERATIONS["session.init"], request),
13
+ login: (request) => invoke(OPERATIONS["session.login"], request),
14
+ },
15
+ contacts: {
16
+ info: (request) => invoke(OPERATIONS["contacts.info"], request),
17
+ },
18
+ chats: {
19
+ history: (request) => invoke(OPERATIONS["chats.history"], request),
20
+ list: (request) => invoke(OPERATIONS["chats.list"], request),
21
+ },
22
+ messages: {
23
+ send: (request) => invoke(OPERATIONS["messages.send"], request),
24
+ },
25
+ });
26
+ //# sourceMappingURL=client.generated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.generated.js","sourceRoot":"","sources":["../../src/generated/client.generated.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAOtD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,CAAC;IAC7C,OAAO,EAAE;QACP,IAAI,EAAE,CAAC,OAAuD,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;QAC9G,KAAK,EAAE,CAAC,OAAwD,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;KAClH;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,CAAC,OAAwD,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;KACjH;IACD,KAAK,EAAE;QACL,OAAO,EAAE,CAAC,OAAwD,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;QACnH,IAAI,EAAE,CAAC,OAAqD,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;KAC3G;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,CAAC,OAAwD,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;KACjH;CACF,CAAC,CAAA"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Every opcode this project knows. **A number being here is not permission to send it** — the ones
3
+ * marked "declared, never sent" exist so the reason they are never sent has a home, and so a test
4
+ * asserting their absence has something real to compare against.
5
+ */
6
+ export declare const Opcode: {
7
+ readonly SESSION_INIT: 6;
8
+ /**
9
+ * **Declared, never sent.** It does not read a profile, it updates one, and it refuses an empty payload. Your own
10
+ * profile arrives with the login response, so nothing needs to send this. It is declared here so that fact keeps a
11
+ * home.
12
+ */
13
+ readonly PROFILE: 16;
14
+ readonly LOGIN: 19;
15
+ /**
16
+ * **Declared, never sent.** `max session end` forgets the token locally and tells MAX nothing. Ending the session
17
+ * server-side would also end it for the browser tab the token came from, which is not what the command promises.
18
+ */
19
+ readonly LOGOUT: 20;
20
+ readonly CONTACT_INFO: 32;
21
+ /**
22
+ * **Declared, never sent.** Nobody agrees what it is: tsmax and PyMax call it `CONTACT_LIST`; the protocol
23
+ * documentation calls it `GET_BLOCKED`. Sent once with the owner's permission on 2026-09-20 and it exists — but it
24
+ * refuses every payload we can guess, and one guess closed the connection. It stays unsent until somebody watches a
25
+ * real client send it (`PROTO-1`).
26
+ */
27
+ readonly UNIDENTIFIED_36: 36;
28
+ readonly CHAT_HISTORY: 49;
29
+ /**
30
+ * **Declared, never sent.** Reading is observational by construction. Marking a conversation read is a change to
31
+ * somebody's account that no read command asked for, so 50 is declared here and never sent — and
32
+ * `src/client.test.ts` asserts its absence from everything the client sent.
33
+ */
34
+ readonly CHAT_MARK: 50;
35
+ readonly CHATS_LIST: 53;
36
+ readonly MSG_SEND: 64;
37
+ /**
38
+ * **Declared, never sent.** Deliberately never called. Giving this tool the ability to destroy somebody's messages,
39
+ * in order to tidy up after a test, is a poor trade (`NEED-32`).
40
+ */
41
+ readonly MSG_DELETE: 66;
42
+ };
43
+ export type Opcode = (typeof Opcode)[keyof typeof Opcode];
44
+ //# sourceMappingURL=opcodes.generated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opcodes.generated.d.ts","sourceRoot":"","sources":["../../src/generated/opcodes.generated.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,MAAM;;IAEjB;;;;OAIG;;;IAGH;;;OAGG;;;IAGH;;;;;OAKG;;;IAGH;;;;OAIG;;;;IAIH;;;OAGG;;CAEK,CAAA;AAEV,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,OAAO,MAAM,CAAC,CAAA"}
@@ -0,0 +1,44 @@
1
+ // Generated from src/spec/ by scripts/generate.ts. Do not edit; run `pnpm generate`.
2
+ /**
3
+ * Every opcode this project knows. **A number being here is not permission to send it** — the ones
4
+ * marked "declared, never sent" exist so the reason they are never sent has a home, and so a test
5
+ * asserting their absence has something real to compare against.
6
+ */
7
+ export const Opcode = {
8
+ SESSION_INIT: 6,
9
+ /**
10
+ * **Declared, never sent.** It does not read a profile, it updates one, and it refuses an empty payload. Your own
11
+ * profile arrives with the login response, so nothing needs to send this. It is declared here so that fact keeps a
12
+ * home.
13
+ */
14
+ PROFILE: 16,
15
+ LOGIN: 19,
16
+ /**
17
+ * **Declared, never sent.** `max session end` forgets the token locally and tells MAX nothing. Ending the session
18
+ * server-side would also end it for the browser tab the token came from, which is not what the command promises.
19
+ */
20
+ LOGOUT: 20,
21
+ CONTACT_INFO: 32,
22
+ /**
23
+ * **Declared, never sent.** Nobody agrees what it is: tsmax and PyMax call it `CONTACT_LIST`; the protocol
24
+ * documentation calls it `GET_BLOCKED`. Sent once with the owner's permission on 2026-09-20 and it exists — but it
25
+ * refuses every payload we can guess, and one guess closed the connection. It stays unsent until somebody watches a
26
+ * real client send it (`PROTO-1`).
27
+ */
28
+ UNIDENTIFIED_36: 36,
29
+ CHAT_HISTORY: 49,
30
+ /**
31
+ * **Declared, never sent.** Reading is observational by construction. Marking a conversation read is a change to
32
+ * somebody's account that no read command asked for, so 50 is declared here and never sent — and
33
+ * `src/client.test.ts` asserts its absence from everything the client sent.
34
+ */
35
+ CHAT_MARK: 50,
36
+ CHATS_LIST: 53,
37
+ MSG_SEND: 64,
38
+ /**
39
+ * **Declared, never sent.** Deliberately never called. Giving this tool the ability to destroy somebody's messages,
40
+ * in order to tidy up after a test, is a poor trade (`NEED-32`).
41
+ */
42
+ MSG_DELETE: 66,
43
+ };
44
+ //# sourceMappingURL=opcodes.generated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opcodes.generated.js","sourceRoot":"","sources":["../../src/generated/opcodes.generated.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAErF;;;;GAIG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,YAAY,EAAE,CAAC;IACf;;;;OAIG;IACH,OAAO,EAAE,EAAE;IACX,KAAK,EAAE,EAAE;IACT;;;OAGG;IACH,MAAM,EAAE,EAAE;IACV,YAAY,EAAE,EAAE;IAChB;;;;;OAKG;IACH,eAAe,EAAE,EAAE;IACnB,YAAY,EAAE,EAAE;IAChB;;;;OAIG;IACH,SAAS,EAAE,EAAE;IACb,UAAU,EAAE,EAAE;IACd,QAAQ,EAAE,EAAE;IACZ;;;OAGG;IACH,UAAU,EAAE,EAAE;CACN,CAAA"}
@@ -0,0 +1,76 @@
1
+ /** Every operation that may be sent, by the name the client calls it. */
2
+ export declare const OPERATIONS: {
3
+ readonly "session.init": import("../spec/define.js").Operation<import("valibot").StrictObjectSchema<{
4
+ readonly userAgent: import("valibot").StrictObjectSchema<{
5
+ readonly deviceType: import("valibot").LiteralSchema<"WEB", undefined>;
6
+ readonly pushDeviceType: import("valibot").LiteralSchema<"WEBPUSH", undefined>;
7
+ readonly locale: import("valibot").StringSchema<undefined>;
8
+ readonly deviceLocale: import("valibot").StringSchema<undefined>;
9
+ readonly osVersion: import("valibot").StringSchema<undefined>;
10
+ readonly deviceName: import("valibot").StringSchema<undefined>;
11
+ readonly headerUserAgent: import("valibot").StringSchema<undefined>;
12
+ readonly appVersion: import("valibot").StringSchema<undefined>;
13
+ readonly screen: import("valibot").StringSchema<undefined>;
14
+ readonly timezone: import("valibot").StringSchema<undefined>;
15
+ }, undefined>;
16
+ readonly deviceId: import("valibot").StringSchema<undefined>;
17
+ }, undefined>, import("valibot").LooseObjectSchema<{}, undefined>>;
18
+ readonly "session.login": import("../spec/define.js").Operation<import("valibot").StrictObjectSchema<{
19
+ readonly token: import("valibot").StringSchema<undefined>;
20
+ readonly interactive: import("valibot").LiteralSchema<false, undefined>;
21
+ readonly chatsCount: import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<undefined>, import("valibot").IntegerAction<number, undefined>, import("valibot").MinValueAction<number, 1, undefined>, import("valibot").MaxValueAction<number, 100, undefined>]>;
22
+ readonly chatsSync: import("valibot").NumberSchema<undefined>;
23
+ readonly contactsSync: import("valibot").NumberSchema<undefined>;
24
+ readonly presenceSync: import("valibot").NumberSchema<undefined>;
25
+ readonly draftsSync: import("valibot").NumberSchema<undefined>;
26
+ }, undefined>, import("valibot").LooseObjectSchema<{
27
+ readonly profile: import("valibot").OptionalSchema<import("valibot").LooseObjectSchema<{}, undefined>, undefined>;
28
+ readonly chats: import("valibot").OptionalSchema<import("valibot").ArraySchema<import("valibot").LooseObjectSchema<{}, undefined>, undefined>, undefined>;
29
+ readonly contacts: import("valibot").OptionalSchema<import("valibot").ArraySchema<import("valibot").LooseObjectSchema<{}, undefined>, undefined>, undefined>;
30
+ readonly messages: import("valibot").OptionalSchema<import("valibot").UnknownSchema, undefined>;
31
+ readonly presence: import("valibot").OptionalSchema<import("valibot").UnknownSchema, undefined>;
32
+ readonly time: import("valibot").OptionalSchema<import("valibot").NumberSchema<undefined>, undefined>;
33
+ readonly chatMarker: import("valibot").OptionalSchema<import("valibot").UnknownSchema, undefined>;
34
+ readonly config: import("valibot").OptionalSchema<import("valibot").UnknownSchema, undefined>;
35
+ readonly videoChatHistory: import("valibot").OptionalSchema<import("valibot").UnknownSchema, undefined>;
36
+ }, undefined>>;
37
+ readonly "contacts.info": import("../spec/define.js").Operation<import("valibot").StrictObjectSchema<{
38
+ readonly contactIds: import("valibot").ArraySchema<import("valibot").SchemaWithPipe<readonly [import("valibot").StringSchema<undefined>, import("valibot").RegexAction<string, "an id is a whole number written as a string">, import("valibot").TransformAction<string, bigint>]>, undefined>;
39
+ }, undefined>, import("valibot").LooseObjectSchema<{
40
+ readonly contacts: import("valibot").OptionalSchema<import("valibot").ArraySchema<import("valibot").LooseObjectSchema<{}, undefined>, undefined>, undefined>;
41
+ }, undefined>>;
42
+ readonly "chats.history": import("../spec/define.js").Operation<import("valibot").StrictObjectSchema<{
43
+ readonly chatId: import("valibot").SchemaWithPipe<readonly [import("valibot").StringSchema<undefined>, import("valibot").RegexAction<string, "an id is a whole number written as a string">, import("valibot").TransformAction<string, bigint>]>;
44
+ readonly from: import("valibot").NumberSchema<undefined>;
45
+ readonly forward: import("valibot").NumberSchema<undefined>;
46
+ readonly backward: import("valibot").NumberSchema<undefined>;
47
+ readonly forwardTime: import("valibot").NumberSchema<undefined>;
48
+ readonly backwardTime: import("valibot").NumberSchema<undefined>;
49
+ readonly itemType: import("valibot").LiteralSchema<"REGULAR", undefined>;
50
+ readonly getChat: import("valibot").BooleanSchema<undefined>;
51
+ readonly getMessages: import("valibot").BooleanSchema<undefined>;
52
+ readonly interactive: import("valibot").LiteralSchema<false, undefined>;
53
+ }, undefined>, import("valibot").LooseObjectSchema<{
54
+ readonly messages: import("valibot").OptionalSchema<import("valibot").ArraySchema<import("valibot").LooseObjectSchema<{}, undefined>, undefined>, undefined>;
55
+ }, undefined>>;
56
+ readonly "chats.list": import("../spec/define.js").Operation<import("valibot").StrictObjectSchema<{
57
+ readonly marker: import("valibot").NumberSchema<undefined>;
58
+ readonly count: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<undefined>, import("valibot").IntegerAction<number, undefined>, import("valibot").MinValueAction<number, 1, undefined>]>, undefined>;
59
+ }, undefined>, import("valibot").LooseObjectSchema<{
60
+ readonly chats: import("valibot").OptionalSchema<import("valibot").ArraySchema<import("valibot").LooseObjectSchema<{}, undefined>, undefined>, undefined>;
61
+ }, undefined>>;
62
+ readonly "messages.send": import("../spec/define.js").Operation<import("valibot").StrictObjectSchema<{
63
+ readonly chatId: import("valibot").SchemaWithPipe<readonly [import("valibot").StringSchema<undefined>, import("valibot").RegexAction<string, "an id is a whole number written as a string">, import("valibot").TransformAction<string, bigint>]>;
64
+ readonly message: import("valibot").StrictObjectSchema<{
65
+ readonly text: import("valibot").StringSchema<undefined>;
66
+ readonly cid: import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<undefined>, import("valibot").IntegerAction<number, undefined>]>;
67
+ readonly elements: import("valibot").ArraySchema<import("valibot").UnknownSchema, undefined>;
68
+ readonly attaches: import("valibot").ArraySchema<import("valibot").UnknownSchema, undefined>;
69
+ }, undefined>;
70
+ readonly notify: import("valibot").BooleanSchema<undefined>;
71
+ }, undefined>, import("valibot").LooseObjectSchema<{
72
+ readonly message: import("valibot").OptionalSchema<import("valibot").LooseObjectSchema<{}, undefined>, undefined>;
73
+ }, undefined>>;
74
+ };
75
+ export type OperationName = keyof typeof OPERATIONS;
76
+ //# sourceMappingURL=operations.generated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations.generated.d.ts","sourceRoot":"","sources":["../../src/generated/operations.generated.ts"],"names":[],"mappings":"AAOA,yEAAyE;AACzE,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAOb,CAAA;AAEV,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,UAAU,CAAA"}
@@ -0,0 +1,15 @@
1
+ // Generated from src/spec/ by scripts/generate.ts. Do not edit; run `pnpm generate`.
2
+ import { chatsHistory, chatsList } from "../spec/operations/chats.js";
3
+ import { contactsInfo } from "../spec/operations/contacts.js";
4
+ import { messagesSend } from "../spec/operations/messages.js";
5
+ import { sessionInit, sessionLogin } from "../spec/operations/session.js";
6
+ /** Every operation that may be sent, by the name the client calls it. */
7
+ export const OPERATIONS = {
8
+ "session.init": sessionInit,
9
+ "session.login": sessionLogin,
10
+ "contacts.info": contactsInfo,
11
+ "chats.history": chatsHistory,
12
+ "chats.list": chatsList,
13
+ "messages.send": messagesSend,
14
+ };
15
+ //# sourceMappingURL=operations.generated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations.generated.js","sourceRoot":"","sources":["../../src/generated/operations.generated.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAErF,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAA;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAEzE,yEAAyE;AACzE,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,cAAc,EAAE,WAAW;IAC3B,eAAe,EAAE,YAAY;IAC7B,eAAe,EAAE,YAAY;IAC7B,eAAe,EAAE,YAAY;IAC7B,YAAY,EAAE,SAAS;IACvB,eAAe,EAAE,YAAY;CACrB,CAAA"}
@@ -0,0 +1,22 @@
1
+ import { type Renderer, type Streams } from "@leemour/cli-core";
2
+ export interface OutputOptions {
3
+ json?: boolean;
4
+ quiet?: boolean;
5
+ streams?: Streams;
6
+ /** Whether a person is looking. Defaults to whether stdout is a terminal. */
7
+ tty?: boolean;
8
+ color?: boolean;
9
+ }
10
+ /**
11
+ * One place decides the mode, so no command can disagree with another.
12
+ *
13
+ * `--json` forces machine output; otherwise a terminal gets the pretty renderer and a pipe gets
14
+ * JSON, because a pipe is a script until proven otherwise. In every machine mode **stdout carries
15
+ * one JSON value and nothing else** — diagnostics are on stderr in all modes, which is what makes
16
+ * that contract hold by construction rather than by remembering.
17
+ */
18
+ export declare const resolveOutput: ({ json, quiet, streams, tty, color }?: OutputOptions) => {
19
+ format: "json" | "pretty";
20
+ renderer: Renderer;
21
+ };
22
+ //# sourceMappingURL=output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,QAAQ,EAAqB,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAElG,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,6EAA6E;IAC7E,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,GAAI,uCAAsC,aAAkB;;;CAUrF,CAAA"}
package/dist/output.js ADDED
@@ -0,0 +1,34 @@
1
+ import { createRenderer } from "@leemour/cli-core";
2
+ /**
3
+ * One place decides the mode, so no command can disagree with another.
4
+ *
5
+ * `--json` forces machine output; otherwise a terminal gets the pretty renderer and a pipe gets
6
+ * JSON, because a pipe is a script until proven otherwise. In every machine mode **stdout carries
7
+ * one JSON value and nothing else** — diagnostics are on stderr in all modes, which is what makes
8
+ * that contract hold by construction rather than by remembering.
9
+ */
10
+ export const resolveOutput = ({ json, quiet, streams, tty, color } = {}) => {
11
+ const interactive = tty ?? process.stdout.isTTY === true;
12
+ const format = json || !interactive ? "json" : "pretty";
13
+ const renderer = createRenderer({
14
+ format,
15
+ color: color ?? (format === "pretty" && process.env.NO_COLOR === undefined),
16
+ ...(streams ? { streams } : {}),
17
+ });
18
+ return { format, renderer: quiet ? silence(renderer) : renderer };
19
+ };
20
+ /**
21
+ * Everything diagnostic, and **not** `failure`: an exit code says which kind of thing went wrong
22
+ * and nothing about which chat or how long to wait, so silencing the sentence too would leave a
23
+ * script with a number and a person with nothing.
24
+ *
25
+ * The protocol note reaches `note` from the client (`commands/context.ts`), which is what makes
26
+ * one rule cover it rather than two.
27
+ */
28
+ const silence = (renderer) => ({
29
+ ...renderer,
30
+ note: () => { },
31
+ success: () => { },
32
+ warn: () => { },
33
+ });
34
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAkD,MAAM,mBAAmB,CAAA;AAWlG;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,KAAoB,EAAE,EAAE,EAAE;IACxF,MAAM,WAAW,GAAG,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,CAAA;IACxD,MAAM,MAAM,GAAiB,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAA;IACrE,MAAM,QAAQ,GAAG,cAAc,CAAC;QAC9B,MAAM;QACN,KAAK,EAAE,KAAK,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC;QAC3E,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChC,CAAC,CAAA;IAEF,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;AACnE,CAAC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,GAAG,CAAC,QAAkB,EAAY,EAAE,CAAC,CAAC;IACjD,GAAG,QAAQ;IACX,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC;IACjB,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;CACf,CAAC,CAAA"}
@@ -0,0 +1,33 @@
1
+ import type { Command } from "commander";
2
+ export declare const DEFAULT_PROFILE = "default";
3
+ /** How the command line names this profile: the first word, or nothing at all for the default. */
4
+ export declare const asFirstWord: (profile: string) => string;
5
+ export declare const usableProfileName: (profile: string) => string;
6
+ /**
7
+ * Every word that means a command at the top level.
8
+ *
9
+ * Taken off the built program rather than written out, because a list written out goes stale the
10
+ * day a command is added — and `help` is in it without being in `program.commands`: commander
11
+ * answers `max help chats` from an implicit command that is not in that array.
12
+ */
13
+ export declare const commandWords: (program: Command) => Set<string>;
14
+ /**
15
+ * **The first word is the profile unless it names a command.** `max personal chats list`, and
16
+ * `max chats list` on the default profile, with no flag in either.
17
+ *
18
+ * An option is never a profile, so `max --json chats list` still works.
19
+ */
20
+ export declare const liftProfile: (argv: string[], words: Set<string>) => {
21
+ profile?: string;
22
+ rest: string[];
23
+ };
24
+ /**
25
+ * Refused at creation, which is the only moment it can still be explained.
26
+ *
27
+ * A profile named after a command breaks silently and in the other direction: `max chats` would
28
+ * have to mean the command, so the profile called `chats` could never be selected at all.
29
+ */
30
+ export declare const refuseCommandName: (profile: string, words: Set<string>) => void;
31
+ /** The root of the command tree, from anywhere inside it. */
32
+ export declare const rootOf: (command: Command) => Command;
33
+ //# sourceMappingURL=profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAExC,eAAO,MAAM,eAAe,YAAY,CAAA;AAExC,kGAAkG;AAClG,eAAO,MAAM,WAAW,GAAI,SAAS,MAAM,KAAG,MAA4D,CAAA;AAW1G,eAAO,MAAM,iBAAiB,GAAI,SAAS,MAAM,KAAG,MASnD,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GAAI,SAAS,OAAO,KAAG,GAAG,CAAC,MAAM,CAC2C,CAAA;AAErG;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,EAAE,EAAE,OAAO,GAAG,CAAC,MAAM,CAAC,KAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAIlG,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,SAAS,MAAM,EAAE,OAAO,GAAG,CAAC,MAAM,CAAC,KAAG,IASvE,CAAA;AAED,6DAA6D;AAC7D,eAAO,MAAM,MAAM,GAAI,SAAS,OAAO,KAAG,OAIzC,CAAA"}
@@ -0,0 +1,60 @@
1
+ import { CliError } from "@leemour/cli-core";
2
+ export const DEFAULT_PROFILE = "default";
3
+ /** How the command line names this profile: the first word, or nothing at all for the default. */
4
+ export const asFirstWord = (profile) => (profile === DEFAULT_PROFILE ? "" : `${profile} `);
5
+ /**
6
+ * A profile name becomes a file name — `<state>/profiles/<name>.json` (`session/store.ts:103`) —
7
+ * and the keyring account is the same string.
8
+ *
9
+ * The first word on the command line is now the profile, so a mistyped path arrives here as a
10
+ * name, and `a/../b` would write that file somewhere nobody would think to look.
11
+ */
12
+ const PLAIN_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
13
+ export const usableProfileName = (profile) => {
14
+ if (!PLAIN_NAME.test(profile)) {
15
+ throw new CliError("validation_error", `"${profile}" cannot be a profile name — letters, digits, dot, dash and underscore only, ` +
16
+ "starting with a letter or a digit");
17
+ }
18
+ return profile;
19
+ };
20
+ /**
21
+ * Every word that means a command at the top level.
22
+ *
23
+ * Taken off the built program rather than written out, because a list written out goes stale the
24
+ * day a command is added — and `help` is in it without being in `program.commands`: commander
25
+ * answers `max help chats` from an implicit command that is not in that array.
26
+ */
27
+ export const commandWords = (program) => new Set(["help", ...program.commands.flatMap((command) => [command.name(), ...command.aliases()])]);
28
+ /**
29
+ * **The first word is the profile unless it names a command.** `max personal chats list`, and
30
+ * `max chats list` on the default profile, with no flag in either.
31
+ *
32
+ * An option is never a profile, so `max --json chats list` still works.
33
+ */
34
+ export const liftProfile = (argv, words) => {
35
+ const [first, ...rest] = argv;
36
+ if (first === undefined || first.startsWith("-") || words.has(first))
37
+ return { rest: argv };
38
+ return { profile: first, rest };
39
+ };
40
+ /**
41
+ * Refused at creation, which is the only moment it can still be explained.
42
+ *
43
+ * A profile named after a command breaks silently and in the other direction: `max chats` would
44
+ * have to mean the command, so the profile called `chats` could never be selected at all.
45
+ */
46
+ export const refuseCommandName = (profile, words) => {
47
+ if (words.has(profile)) {
48
+ throw new CliError("validation_error", `"${profile}" is a command, so \`max ${profile} …\` would always mean the command and never ` +
49
+ "this profile. Pick a name that is not one of: " +
50
+ [...words].sort().join(", "));
51
+ }
52
+ };
53
+ /** The root of the command tree, from anywhere inside it. */
54
+ export const rootOf = (command) => {
55
+ let root = command;
56
+ while (root.parent)
57
+ root = root.parent;
58
+ return root;
59
+ };
60
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAG5C,MAAM,CAAC,MAAM,eAAe,GAAG,SAAS,CAAA;AAExC,kGAAkG;AAClG,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,OAAe,EAAU,EAAE,CAAC,CAAC,OAAO,KAAK,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC,CAAA;AAE1G;;;;;;GAMG;AACH,MAAM,UAAU,GAAG,8BAA8B,CAAA;AAEjD,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,OAAe,EAAU,EAAE;IAC3D,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,QAAQ,CAChB,kBAAkB,EAClB,IAAI,OAAO,+EAA+E;YACxF,mCAAmC,CACtC,CAAA;IACH,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAgB,EAAe,EAAE,CAC5D,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;AAErG;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAc,EAAE,KAAkB,EAAwC,EAAE;IACtG,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAA;IAC7B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IAC3F,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;AACjC,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,OAAe,EAAE,KAAkB,EAAQ,EAAE;IAC7E,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,QAAQ,CAChB,kBAAkB,EAClB,IAAI,OAAO,4BAA4B,OAAO,+CAA+C;YAC3F,gDAAgD;YAChD,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAC/B,CAAA;IACH,CAAC;AACH,CAAC,CAAA;AAED,6DAA6D;AAC7D,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAgB,EAAW,EAAE;IAClD,IAAI,IAAI,GAAG,OAAO,CAAA;IAClB,OAAO,IAAI,CAAC,MAAM;QAAE,IAAI,GAAG,IAAI,CAAC,MAAM,CAAA;IACtC,OAAO,IAAI,CAAA;AACb,CAAC,CAAA"}
@@ -0,0 +1,29 @@
1
+ import { type Streams } from "@leemour/cli-core";
2
+ import { Command } from "commander";
3
+ export interface RunOptions {
4
+ streams?: Streams;
5
+ /** Whether a person is looking. Defaults to whether stdout is a terminal. */
6
+ tty?: boolean;
7
+ }
8
+ export interface ProgramOptions {
9
+ /** Where `--version` and `--help` write. Injected so a test reads them instead of the terminal. */
10
+ out?: (text: string) => void;
11
+ err?: (text: string) => void;
12
+ }
13
+ /**
14
+ * The command tree, built fresh on each call.
15
+ *
16
+ * Commander is global state by default — `exitOverride` and the write hooks below turn it into a
17
+ * value a test can drive. A command that calls `process.exit` cannot be tested, and a CLI whose
18
+ * argument parsing is untested is a CLI that breaks on the day someone adds an option.
19
+ */
20
+ export declare const createProgram: ({ out, err }?: ProgramOptions) => Command;
21
+ /**
22
+ * Runs the program and **returns an exit code instead of throwing**.
23
+ *
24
+ * A stack trace is not an error message: it puts Node internals on stderr, tells a script nothing
25
+ * it can branch on, and exits 1 for every kind of failure alike. Here a failure becomes a code an
26
+ * agent can switch on, and a sentence a person can act on.
27
+ */
28
+ export declare const run: (argv: string[], options?: RunOptions) => Promise<number>;
29
+ //# sourceMappingURL=program.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0D,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAA;AACxG,OAAO,EAAE,OAAO,EAAkB,MAAM,WAAW,CAAA;AAWnD,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,6EAA6E;IAC7E,GAAG,CAAC,EAAE,OAAO,CAAA;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,mGAAmG;IACnG,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GAAI,eAAc,cAAmB,KAAG,OA4CjE,CAAA;AAOD;;;;;;GAMG;AACH,eAAO,MAAM,GAAG,GAAU,MAAM,MAAM,EAAE,EAAE,UAAS,UAAe,KAAG,OAAO,CAAC,MAAM,CA2DlF,CAAA"}