@leemour/max-cli 0.7.0 → 0.8.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 (160) hide show
  1. package/README.md +198 -79
  2. package/dist/cache/schema.d.ts +13 -8
  3. package/dist/cache/schema.d.ts.map +1 -1
  4. package/dist/cache/schema.js +84 -23
  5. package/dist/cache/schema.js.map +1 -1
  6. package/dist/cache/store.d.ts +15 -0
  7. package/dist/cache/store.d.ts.map +1 -1
  8. package/dist/cache/store.js +32 -3
  9. package/dist/cache/store.js.map +1 -1
  10. package/dist/client.d.ts +28 -1
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +110 -5
  13. package/dist/client.js.map +1 -1
  14. package/dist/commands/chats.d.ts.map +1 -1
  15. package/dist/commands/chats.js +8 -0
  16. package/dist/commands/chats.js.map +1 -1
  17. package/dist/commands/config.d.ts.map +1 -1
  18. package/dist/commands/config.js +6 -2
  19. package/dist/commands/config.js.map +1 -1
  20. package/dist/commands/context.d.ts +14 -2
  21. package/dist/commands/context.d.ts.map +1 -1
  22. package/dist/commands/context.js +18 -19
  23. package/dist/commands/context.js.map +1 -1
  24. package/dist/commands/export.d.ts +3 -0
  25. package/dist/commands/export.d.ts.map +1 -0
  26. package/dist/commands/export.js +83 -0
  27. package/dist/commands/export.js.map +1 -0
  28. package/dist/commands/mcp.d.ts.map +1 -1
  29. package/dist/commands/mcp.js +9 -2
  30. package/dist/commands/mcp.js.map +1 -1
  31. package/dist/commands/messages.d.ts.map +1 -1
  32. package/dist/commands/messages.js +126 -3
  33. package/dist/commands/messages.js.map +1 -1
  34. package/dist/commands/models.d.ts +7 -0
  35. package/dist/commands/models.d.ts.map +1 -0
  36. package/dist/commands/models.js +47 -0
  37. package/dist/commands/models.js.map +1 -0
  38. package/dist/commands/serve.d.ts.map +1 -1
  39. package/dist/commands/serve.js +50 -2
  40. package/dist/commands/serve.js.map +1 -1
  41. package/dist/commands/session.d.ts.map +1 -1
  42. package/dist/commands/session.js +13 -3
  43. package/dist/commands/session.js.map +1 -1
  44. package/dist/commands/watch.d.ts.map +1 -1
  45. package/dist/commands/watch.js +12 -16
  46. package/dist/commands/watch.js.map +1 -1
  47. package/dist/config.d.ts +18 -2
  48. package/dist/config.d.ts.map +1 -1
  49. package/dist/config.js +58 -4
  50. package/dist/config.js.map +1 -1
  51. package/dist/domain/map.d.ts.map +1 -1
  52. package/dist/domain/map.js +5 -0
  53. package/dist/domain/map.js.map +1 -1
  54. package/dist/domain/models.d.ts +14 -0
  55. package/dist/domain/models.d.ts.map +1 -1
  56. package/dist/domain/models.js.map +1 -1
  57. package/dist/download.d.ts +2 -0
  58. package/dist/download.d.ts.map +1 -1
  59. package/dist/download.js +11 -0
  60. package/dist/download.js.map +1 -1
  61. package/dist/export.d.ts +26 -0
  62. package/dist/export.d.ts.map +1 -0
  63. package/dist/export.js +75 -0
  64. package/dist/export.js.map +1 -0
  65. package/dist/generated/client.generated.d.ts +2 -0
  66. package/dist/generated/client.generated.d.ts.map +1 -1
  67. package/dist/generated/client.generated.js +2 -0
  68. package/dist/generated/client.generated.js.map +1 -1
  69. package/dist/generated/opcodes.generated.d.ts +0 -9
  70. package/dist/generated/opcodes.generated.d.ts.map +1 -1
  71. package/dist/generated/opcodes.generated.js +0 -9
  72. package/dist/generated/opcodes.generated.js.map +1 -1
  73. package/dist/generated/operations.generated.d.ts +18 -1
  74. package/dist/generated/operations.generated.d.ts.map +1 -1
  75. package/dist/generated/operations.generated.js +4 -2
  76. package/dist/generated/operations.generated.js.map +1 -1
  77. package/dist/mcp/confirm.d.ts +7 -0
  78. package/dist/mcp/confirm.d.ts.map +1 -1
  79. package/dist/mcp/confirm.js +16 -7
  80. package/dist/mcp/confirm.js.map +1 -1
  81. package/dist/mcp/instructions.d.ts +5 -1
  82. package/dist/mcp/instructions.d.ts.map +1 -1
  83. package/dist/mcp/instructions.js +16 -2
  84. package/dist/mcp/instructions.js.map +1 -1
  85. package/dist/mcp/server.d.ts +3 -1
  86. package/dist/mcp/server.d.ts.map +1 -1
  87. package/dist/mcp/server.js +22 -3
  88. package/dist/mcp/server.js.map +1 -1
  89. package/dist/mcp/session.d.ts +1 -1
  90. package/dist/mcp/session.d.ts.map +1 -1
  91. package/dist/mcp/session.js +1 -1
  92. package/dist/mcp/session.js.map +1 -1
  93. package/dist/mcp/tools.d.ts +8 -1
  94. package/dist/mcp/tools.d.ts.map +1 -1
  95. package/dist/mcp/tools.js +87 -10
  96. package/dist/mcp/tools.js.map +1 -1
  97. package/dist/program.d.ts.map +1 -1
  98. package/dist/program.js +6 -2
  99. package/dist/program.js.map +1 -1
  100. package/dist/sends/guard.d.ts +7 -3
  101. package/dist/sends/guard.d.ts.map +1 -1
  102. package/dist/sends/guard.js +32 -11
  103. package/dist/sends/guard.js.map +1 -1
  104. package/dist/sends/journal.d.ts +6 -1
  105. package/dist/sends/journal.d.ts.map +1 -1
  106. package/dist/sends/journal.js.map +1 -1
  107. package/dist/sends/permissions.d.ts +11 -0
  108. package/dist/sends/permissions.d.ts.map +1 -0
  109. package/dist/sends/permissions.js +47 -0
  110. package/dist/sends/permissions.js.map +1 -0
  111. package/dist/server/server-connection.d.ts +25 -15
  112. package/dist/server/server-connection.d.ts.map +1 -1
  113. package/dist/server/server-connection.js +69 -63
  114. package/dist/server/server-connection.js.map +1 -1
  115. package/dist/server/server.d.ts +7 -0
  116. package/dist/server/server.d.ts.map +1 -1
  117. package/dist/server/server.js +82 -19
  118. package/dist/server/server.js.map +1 -1
  119. package/dist/server/start.d.ts +17 -1
  120. package/dist/server/start.d.ts.map +1 -1
  121. package/dist/server/start.js +46 -7
  122. package/dist/server/start.js.map +1 -1
  123. package/dist/session/adopt.d.ts.map +1 -1
  124. package/dist/session/adopt.js +4 -0
  125. package/dist/session/adopt.js.map +1 -1
  126. package/dist/session/handshake.d.ts +1 -1
  127. package/dist/spec/index.js +4 -4
  128. package/dist/spec/index.js.map +1 -1
  129. package/dist/spec/operations/chats.d.ts +12 -2
  130. package/dist/spec/operations/chats.d.ts.map +1 -1
  131. package/dist/spec/operations/chats.js +32 -5
  132. package/dist/spec/operations/chats.js.map +1 -1
  133. package/dist/spec/operations/messages.d.ts +13 -1
  134. package/dist/spec/operations/messages.d.ts.map +1 -1
  135. package/dist/spec/operations/messages.js +18 -4
  136. package/dist/spec/operations/messages.js.map +1 -1
  137. package/dist/transcribe/index.d.ts +34 -0
  138. package/dist/transcribe/index.d.ts.map +1 -0
  139. package/dist/transcribe/index.js +43 -0
  140. package/dist/transcribe/index.js.map +1 -0
  141. package/dist/transcribe/install.d.ts +18 -0
  142. package/dist/transcribe/install.d.ts.map +1 -0
  143. package/dist/transcribe/install.js +67 -0
  144. package/dist/transcribe/install.js.map +1 -0
  145. package/dist/transcribe/models.d.ts +32 -0
  146. package/dist/transcribe/models.d.ts.map +1 -0
  147. package/dist/transcribe/models.js +124 -0
  148. package/dist/transcribe/models.js.map +1 -0
  149. package/dist/transcribe/speech.d.ts +25 -0
  150. package/dist/transcribe/speech.d.ts.map +1 -0
  151. package/dist/transcribe/speech.js +105 -0
  152. package/dist/transcribe/speech.js.map +1 -0
  153. package/dist/update.d.ts +1 -11
  154. package/dist/update.d.ts.map +1 -1
  155. package/dist/update.js +14 -42
  156. package/dist/update.js.map +1 -1
  157. package/dist/version.d.ts +1 -1
  158. package/dist/version.js +1 -1
  159. package/package.json +8 -3
  160. package/skills/max-cli/SKILL.md +19 -3
@@ -0,0 +1,47 @@
1
+ /**
2
+ * What a profile may be allowed to do (`CLI-37`, `NEED-251`). One name per thing the owner would
3
+ * recognise, so `sessions` — which logs the owner out of their phone — never rides along with
4
+ * adding a contact. There is no `*`: "everything" is not setting `allow` at all, and a wildcard
5
+ * would switch on `delete` without anyone naming it.
6
+ */
7
+ export const PERMISSIONS = [
8
+ "send",
9
+ "forward",
10
+ "reaction",
11
+ "edit",
12
+ "pin",
13
+ "read",
14
+ "delete",
15
+ "groups",
16
+ "contacts",
17
+ "profile",
18
+ "folders",
19
+ "sessions",
20
+ ];
21
+ const ACCOUNT = {
22
+ "contact-add": "contacts",
23
+ "contact-remove": "contacts",
24
+ "contact-import": "contacts",
25
+ profile: "profile",
26
+ "folder-create": "folders",
27
+ "folder-update": "folders",
28
+ "folder-delete": "folders",
29
+ "sessions-end": "sessions",
30
+ };
31
+ export const permissionFor = (kind, action) => {
32
+ switch (kind) {
33
+ case "message":
34
+ return "send";
35
+ case "chat":
36
+ return "groups";
37
+ case "account": {
38
+ const permission = ACCOUNT[action];
39
+ if (!permission)
40
+ throw new Error(`an account change without a known action: ${action}`);
41
+ return permission;
42
+ }
43
+ default:
44
+ return kind;
45
+ }
46
+ };
47
+ //# sourceMappingURL=permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/sends/permissions.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,MAAM;IACN,SAAS;IACT,UAAU;IACV,MAAM;IACN,KAAK;IACL,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,SAAS;IACT,SAAS;IACT,UAAU;CACF,CAAA;AAIV,MAAM,OAAO,GAAsC;IACjD,aAAa,EAAE,UAAU;IACzB,gBAAgB,EAAE,UAAU;IAC5B,gBAAgB,EAAE,UAAU;IAC5B,OAAO,EAAE,SAAS;IAClB,eAAe,EAAE,SAAS;IAC1B,eAAe,EAAE,SAAS;IAC1B,eAAe,EAAE,SAAS;IAC1B,cAAc,EAAE,UAAU;CAC3B,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAc,EAAE,MAAe,EAAc,EAAE;IAC3E,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,SAAS;YACZ,OAAO,MAAM,CAAA;QACf,KAAK,MAAM;YACT,OAAO,QAAQ,CAAA;QACjB,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,MAAM,UAAU,GAAG,OAAO,CAAC,MAAuB,CAAC,CAAA;YACnD,IAAI,CAAC,UAAU;gBAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,MAAM,EAAE,CAAC,CAAA;YACvF,OAAO,UAAU,CAAA;QACnB,CAAC;QACD;YACE,OAAO,IAAI,CAAA;IACf,CAAC;AACH,CAAC,CAAA"}
@@ -2,34 +2,44 @@ import { Connection, type Wire, type WireEvent } from "../protocol/connection.js
2
2
  import { type Payload } from "../protocol/frame.js";
3
3
  import type { SessionStore } from "../session/store.js";
4
4
  /**
5
- * **A command's connection that goes through `max serve`** when one is running (`MAX-16`).
5
+ * **A command's connection: `max serve`'s, shared** (`MAX-35`, `NEED-229`).
6
6
  *
7
- * INIT and LOGIN are answered by the server's own login, so the command logs in without logging
8
- * in. Reads go through the server's connection. **Anything else — a send, a reaction — makes this
9
- * open a connection of its own and log in on it**, so it passes the send guards exactly as it does
10
- * without a server (`NEED-185`). No server, a stale login, or a server that stops answering: the
11
- * whole command falls back to its own connection before it has sent anything.
7
+ * One connection to MAX per profile, whoever asks — CLI commands, `max mcp`, `max watch`. INIT
8
+ * and LOGIN are answered by the server's own login; **every other request goes over its
9
+ * connection, sends included**. The send guards still apply: they run in the command's client,
10
+ * before the request reaches this wire.
11
+ *
12
+ * No server answering: `ensure` starts one and waits for it. A connection of the command's own
13
+ * is opened only when there is no `ensure` (the owner said `serve: false`) and no server, or for
14
+ * a token being tried out (`max session start`), which is by definition not the server's.
12
15
  */
13
16
  export declare class ServerConnection implements Wire {
14
17
  #private;
15
- constructor({ path, store, timeoutMs, direct, onUnreachable, }: {
18
+ constructor({ path, store, timeoutMs, direct, ensure, }: {
16
19
  path: string;
17
20
  store: SessionStore;
18
21
  timeoutMs?: number;
19
22
  direct?: () => Connection;
20
- /** The socket file is there and nobody answers — a server that died; start another. */
21
- onUnreachable?: () => void;
23
+ /** Starts a server when none answers and waits for it; `false` if it did not come up. */
24
+ ensure?: () => Promise<boolean>;
22
25
  });
23
26
  open(): Promise<void>;
24
27
  invoke(opcode: number, payload?: Payload, watch?: (event: WireEvent) => void): Promise<Payload>;
25
28
  close(): Promise<void>;
26
29
  }
27
30
  /**
28
- * What a command may ask for through the server — the server allows exactly these. **Nothing here
29
- * changes anything**: sending, reacting, marking read and deleting never come this way, so no
30
- * guard can be walked around.
31
+ * What the server does **not** pass on: logging in is the server's own business, and a login by
32
+ * QR or SMS is `max session start` on a connection of its own. Everything else the specification
33
+ * lets a client send goes through the one connection.
34
+ */
35
+ export declare const forwarded: (opcode: number) => boolean;
36
+ /**
37
+ * Asks a running server to stop. `"refused"` is a server started by hand, which only Ctrl-C
38
+ * stops; `"none"` is nobody listening.
31
39
  */
32
- export declare const READS: Set<number>;
33
- /** Asks a running server to stop. Resolves whether or not one was there. */
34
- export declare const stopServer: (path: string) => Promise<boolean>;
40
+ export declare const stopServer: (path: string, { force }?: {
41
+ force?: boolean | undefined;
42
+ }) => Promise<"stopped" | "refused" | "none">;
43
+ /** What a running server says about itself, or `undefined` when nobody answers. */
44
+ export declare const serverStatus: (path: string) => Promise<Record<string, unknown> | undefined>;
35
45
  //# sourceMappingURL=server-connection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server-connection.d.ts","sourceRoot":"","sources":["../../src/server/server-connection.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAiB,KAAK,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAChG,OAAO,EAAQ,KAAK,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAEzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAUvD;;;;;;;;GAQG;AACH,qBAAa,gBAAiB,YAAW,IAAI;;gBAe/B,EACV,IAAI,EACJ,KAAK,EACL,SAAkB,EAClB,MAAM,EACN,aAAa,GACd,EAAE;QACD,IAAI,EAAE,MAAM,CAAA;QACZ,KAAK,EAAE,YAAY,CAAA;QACnB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,MAAM,CAAC,EAAE,MAAM,UAAU,CAAA;QACzB,uFAAuF;QACvF,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;KAC3B;IAQK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAErB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,OAAY,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAkCnG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CA4F7B;AAED;;;;GAIG;AACH,eAAO,MAAM,KAAK,aAOhB,CAAA;AAEF,4EAA4E;AAC5E,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,OAAO,CAAC,OAAO,CAkBrD,CAAA"}
1
+ {"version":3,"file":"server-connection.d.ts","sourceRoot":"","sources":["../../src/server/server-connection.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAiB,KAAK,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAChG,OAAO,EAAQ,KAAK,OAAO,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AASvD;;;;;;;;;;;GAWG;AACH,qBAAa,gBAAiB,YAAW,IAAI;;gBAc/B,EACV,IAAI,EACJ,KAAK,EACL,SAAkB,EAClB,MAAM,EACN,MAAM,GACP,EAAE;QACD,IAAI,EAAE,MAAM,CAAA;QACZ,KAAK,EAAE,YAAY,CAAA;QACnB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,MAAM,CAAC,EAAE,MAAM,UAAU,CAAA;QACzB,yFAAyF;QACzF,MAAM,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;KAChC;IAQK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAErB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,OAAY,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAkDnG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAwE7B;AAED;;;;GAIG;AACH,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,KAAG,OAIxC,CAAA;AAEH;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,EAAE;;CAAsB,KAAG,OAAO,CAAC,SAAS,GAAG,SAAS,GAAG,MAAM,CAyBpG,CAAA;AAEJ,mFAAmF;AACnF,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAenF,CAAA"}
@@ -1,25 +1,28 @@
1
1
  import { connect } from "node:net";
2
+ import { CliError } from "@leemour/cli-core";
2
3
  import { Opcode } from "../generated/opcodes.generated.js";
4
+ import { OPERATIONS } from "../generated/operations.generated.js";
3
5
  import { Connection, ProtocolError } from "../protocol/connection.js";
4
6
  import { asId } from "../protocol/frame.js";
5
- import { startSession } from "../session/handshake.js";
6
- import { buildRequest } from "../spec/index.js";
7
7
  import { fromLine, lineReader, toLine } from "./lines.js";
8
8
  /**
9
- * **A command's connection that goes through `max serve`** when one is running (`MAX-16`).
9
+ * **A command's connection: `max serve`'s, shared** (`MAX-35`, `NEED-229`).
10
10
  *
11
- * INIT and LOGIN are answered by the server's own login, so the command logs in without logging
12
- * in. Reads go through the server's connection. **Anything else — a send, a reaction — makes this
13
- * open a connection of its own and log in on it**, so it passes the send guards exactly as it does
14
- * without a server (`NEED-185`). No server, a stale login, or a server that stops answering: the
15
- * whole command falls back to its own connection before it has sent anything.
11
+ * One connection to MAX per profile, whoever asks — CLI commands, `max mcp`, `max watch`. INIT
12
+ * and LOGIN are answered by the server's own login; **every other request goes over its
13
+ * connection, sends included**. The send guards still apply: they run in the command's client,
14
+ * before the request reaches this wire.
15
+ *
16
+ * No server answering: `ensure` starts one and waits for it. A connection of the command's own
17
+ * is opened only when there is no `ensure` (the owner said `serve: false`) and no server, or for
18
+ * a token being tried out (`max session start`), which is by definition not the server's.
16
19
  */
17
20
  export class ServerConnection {
18
21
  #path;
19
22
  #store;
20
23
  #timeoutMs;
21
24
  #direct;
22
- #onUnreachable;
25
+ #ensure;
23
26
  #pending = new Map();
24
27
  #socket;
25
28
  #id = 0;
@@ -27,9 +30,8 @@ export class ServerConnection {
27
30
  /** What the client sent as INIT; a fallback at LOGIN has to send it first, or MAX refuses. */
28
31
  #init;
29
32
  #own;
30
- #ownLoggedIn = false;
31
- constructor({ path, store, timeoutMs = 30_000, direct, onUnreachable, }) {
32
- this.#onUnreachable = onUnreachable;
33
+ constructor({ path, store, timeoutMs = 30_000, direct, ensure, }) {
34
+ this.#ensure = ensure;
33
35
  this.#path = path;
34
36
  this.#store = store;
35
37
  this.#timeoutMs = timeoutMs;
@@ -38,17 +40,11 @@ export class ServerConnection {
38
40
  async open() { }
39
41
  async invoke(opcode, payload = {}, watch) {
40
42
  if (this.#own)
41
- return this.#ownRequest(opcode, payload, watch);
43
+ return this.#own.invoke(opcode, payload, watch);
42
44
  if (opcode === Opcode.SESSION_INIT) {
43
45
  this.#init = payload;
44
- this.#login = await this.#ask({ login: true }).catch((error) => {
45
- if (error.code === "ECONNREFUSED" || error.code === "ENOENT")
46
- this.#onUnreachable?.();
47
- return undefined;
48
- });
49
- if (!this.#login)
50
- return this.#fallBack(opcode, payload, watch);
51
- return {};
46
+ this.#login = await this.#askServer({ login: true });
47
+ return this.#login ? {} : this.#fallBack(opcode, payload, watch);
52
48
  }
53
49
  if (opcode === Opcode.LOGIN) {
54
50
  // A token being tried out (`max session start`) is not the one the server logged in with, and
@@ -58,13 +54,30 @@ export class ServerConnection {
58
54
  }
59
55
  return this.#login;
60
56
  }
61
- if (!READS.has(opcode))
62
- return this.#ownRequest(opcode, payload, watch);
63
57
  watch?.({ phase: "sent", seq: this.#id + 1, opcode, bytes: 0 });
64
58
  const answer = await this.#ask({ opcode, payload });
65
59
  watch?.({ phase: "received", seq: this.#id, opcode, bytes: 0 });
66
60
  return answer;
67
61
  }
62
+ /** The server's answer, starting one if `ensure` may; `undefined` when there is none to be had. */
63
+ async #askServer(request) {
64
+ try {
65
+ return await this.#ask(request);
66
+ }
67
+ catch (error) {
68
+ const code = error.code;
69
+ if (code !== "ECONNREFUSED" && code !== "ENOENT")
70
+ throw error;
71
+ if (!this.#ensure || !(await this.#ensure())) {
72
+ if (this.#ensure) {
73
+ throw new CliError("network_error", "`max serve` did not start, and this profile shares one connection (`serve: true`) — " +
74
+ "its log is beside the profile's state; `--no-serve` runs this command on its own");
75
+ }
76
+ return undefined;
77
+ }
78
+ return this.#ask(request);
79
+ }
80
+ }
68
81
  #sameAccount(login) {
69
82
  const profile = login.profile;
70
83
  const viewerId = this.#store.readState().viewerId;
@@ -80,7 +93,7 @@ export class ServerConnection {
80
93
  this.#socket = undefined;
81
94
  await this.#own?.close();
82
95
  }
83
- /** Before anything was sent: the command simply runs as it would with no server. */
96
+ /** Before anything was sent: the command runs on its own connection, as with no server at all. */
84
97
  async #fallBack(opcode, payload, watch) {
85
98
  this.#socket?.destroy();
86
99
  this.#socket = undefined;
@@ -88,28 +101,8 @@ export class ServerConnection {
88
101
  await this.#own.open();
89
102
  if (opcode === Opcode.LOGIN && this.#init)
90
103
  await this.#own.invoke(Opcode.SESSION_INIT, this.#init);
91
- this.#ownLoggedIn = opcode === Opcode.LOGIN;
92
104
  return this.#own.invoke(opcode, payload, watch);
93
105
  }
94
- /** A write mid-command: a connection of its own, logged in, then the request. */
95
- async #ownRequest(opcode, payload, watch) {
96
- if (!this.#own) {
97
- this.#own = this.#direct();
98
- await this.#own.open();
99
- }
100
- const own = this.#own;
101
- if (!this.#ownLoggedIn && opcode !== Opcode.SESSION_INIT && opcode !== Opcode.LOGIN) {
102
- const token = this.#store.readToken() ?? "";
103
- await startSession((operation, request) => own.invoke(operation.opcode, buildRequest(operation, request)), {
104
- token,
105
- deviceId: this.#store.readState().deviceId,
106
- });
107
- this.#ownLoggedIn = true;
108
- }
109
- if (opcode === Opcode.LOGIN)
110
- this.#ownLoggedIn = true;
111
- return own.invoke(opcode, payload, watch);
112
- }
113
106
  async #ask(request) {
114
107
  const socket = await this.#connected();
115
108
  this.#id += 1;
@@ -161,35 +154,48 @@ export class ServerConnection {
161
154
  }
162
155
  }
163
156
  /**
164
- * What a command may ask for through the server — the server allows exactly these. **Nothing here
165
- * changes anything**: sending, reacting, marking read and deleting never come this way, so no
166
- * guard can be walked around.
157
+ * What the server does **not** pass on: logging in is the server's own business, and a login by
158
+ * QR or SMS is `max session start` on a connection of its own. Everything else the specification
159
+ * lets a client send goes through the one connection.
160
+ */
161
+ export const forwarded = (opcode) => Object.values(OPERATIONS).some((operation) => operation.opcode === opcode && !operation.name.startsWith("session.") && !operation.name.startsWith("login."));
162
+ /**
163
+ * Asks a running server to stop. `"refused"` is a server started by hand, which only Ctrl-C
164
+ * stops; `"none"` is nobody listening.
167
165
  */
168
- export const READS = new Set([
169
- Opcode.CHAT_HISTORY,
170
- Opcode.CHATS_LIST,
171
- Opcode.CONTACT_INFO,
172
- Opcode.FILE_DOWNLOAD,
173
- Opcode.VIDEO_PLAY,
174
- Opcode.MSG_GET_REACTIONS,
175
- ]);
176
- /** Asks a running server to stop. Resolves whether or not one was there. */
177
- export const stopServer = (path) => new Promise((resolve) => {
166
+ export const stopServer = (path, { force = false } = {}) => new Promise((resolve) => {
178
167
  const socket = connect(path);
168
+ let answer = "none";
179
169
  const timer = setTimeout(() => {
180
170
  socket.destroy();
181
- resolve(false);
171
+ resolve(answer);
182
172
  }, 2000);
183
- socket.once("connect", () => socket.write(toLine({ id: 1, stop: true })));
184
- // Nobody reads the answer, and a socket nobody reads never sees its end, so never closes.
185
- socket.resume();
173
+ // `force` is a person's own `max serve --stop`, which a server started by hand obeys too.
174
+ socket.once("connect", () => socket.write(toLine({ id: 1, stop: true, ...(force ? { force } : {}) })));
175
+ socket.on("data", lineReader((line) => {
176
+ answer = fromLine(line).stopped === true ? "stopped" : "refused";
177
+ }));
186
178
  socket.once("error", () => {
187
179
  clearTimeout(timer);
188
- resolve(false);
180
+ resolve("none");
189
181
  });
182
+ // After the answer, the server closes its end: stopped, it is gone once this fires.
190
183
  socket.once("close", () => {
191
184
  clearTimeout(timer);
192
- resolve(true);
185
+ resolve(answer);
193
186
  });
194
187
  });
188
+ /** What a running server says about itself, or `undefined` when nobody answers. */
189
+ export const serverStatus = (path) => new Promise((resolve) => {
190
+ const socket = connect(path);
191
+ const done = (status) => {
192
+ clearTimeout(timer);
193
+ socket.destroy();
194
+ resolve(status);
195
+ };
196
+ const timer = setTimeout(() => done(undefined), 2000);
197
+ socket.once("connect", () => socket.write(toLine({ status: true })));
198
+ socket.on("data", lineReader((line) => done(fromLine(line))));
199
+ socket.once("error", () => done(undefined));
200
+ });
195
201
  //# sourceMappingURL=server-connection.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server-connection.js","sourceRoot":"","sources":["../../src/server/server-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAe,MAAM,UAAU,CAAA;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAA;AAC1D,OAAO,EAAE,UAAU,EAAE,aAAa,EAA6B,MAAM,2BAA2B,CAAA;AAChG,OAAO,EAAE,IAAI,EAAgB,MAAM,sBAAsB,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAEtD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAQzD;;;;;;;;GAQG;AACH,MAAM,OAAO,gBAAgB;IAClB,KAAK,CAAQ;IACb,MAAM,CAAc;IACpB,UAAU,CAAQ;IAClB,OAAO,CAAkB;IACzB,cAAc,CAA0B;IACxC,QAAQ,GAAG,IAAI,GAAG,EAAmB,CAAA;IAC9C,OAAO,CAAoB;IAC3B,GAAG,GAAG,CAAC,CAAA;IACP,MAAM,CAAqB;IAC3B,8FAA8F;IAC9F,KAAK,CAAqB;IAC1B,IAAI,CAAwB;IAC5B,YAAY,GAAG,KAAK,CAAA;IAEpB,YAAY,EACV,IAAI,EACJ,KAAK,EACL,SAAS,GAAG,MAAM,EAClB,MAAM,EACN,aAAa,GAQd;QACC,IAAI,CAAC,cAAc,GAAG,aAAa,CAAA;QACnC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;QACnB,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;QAC3B,IAAI,CAAC,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;IAChE,CAAC;IAED,KAAK,CAAC,IAAI,KAAmB,CAAC;IAE9B,KAAK,CAAC,MAAM,CAAC,MAAc,EAAE,UAAmB,EAAE,EAAE,KAAkC;QACpF,IAAI,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;QAE9D,IAAI,MAAM,KAAK,MAAM,CAAC,YAAY,EAAE,CAAC;YACnC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAA;YACpB,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAA4B,EAAE,EAAE;gBACpF,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;oBAAE,IAAI,CAAC,cAAc,EAAE,EAAE,CAAA;gBACrF,OAAO,SAAS,CAAA;YAClB,CAAC,CAAC,CAAA;YACF,IAAI,CAAC,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;YAC/D,OAAO,EAAE,CAAA;QACX,CAAC;QACD,IAAI,MAAM,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC;YAC5B,8FAA8F;YAC9F,yFAAyF;YACzF,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBACjG,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;YAC/C,CAAC;YACD,OAAO,IAAI,CAAC,MAAM,CAAA;QACpB,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;QAEvE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAA;QAC/D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAA;QACnD,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAA;QAC/D,OAAO,MAAM,CAAA;IACf,CAAC;IAED,YAAY,CAAC,KAAc;QACzB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAqD,CAAA;QAC3E,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,QAAQ,CAAA;QACjD,OAAO,QAAQ,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,KAAK,QAAQ,CAAA;IAC1E,CAAC;IAED,KAAK,CAAC,KAAK;QACT,KAAK,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YACvD,YAAY,CAAC,KAAK,CAAC,CAAA;YACnB,MAAM,CAAC,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC,CAAA;QAC1E,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;QACrB,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,CAAA;QACvB,IAAI,CAAC,OAAO,GAAG,SAAS,CAAA;QACxB,MAAM,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAA;IAC1B,CAAC;IAED,oFAAoF;IACpF,KAAK,CAAC,SAAS,CAAC,MAAc,EAAE,OAAgB,EAAE,KAAkC;QAClF,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,CAAA;QACvB,IAAI,CAAC,OAAO,GAAG,SAAS,CAAA;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA;QAC1B,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;QACtB,IAAI,MAAM,KAAK,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;QAClG,IAAI,CAAC,YAAY,GAAG,MAAM,KAAK,MAAM,CAAC,KAAK,CAAA;QAC3C,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;IACjD,CAAC;IAED,iFAAiF;IACjF,KAAK,CAAC,WAAW,CAAC,MAAc,EAAE,OAAgB,EAAE,KAAkC;QACpF,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA;YAC1B,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;QACxB,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAA;QACrB,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,MAAM,KAAK,MAAM,CAAC,YAAY,IAAI,MAAM,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC;YACpF,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,EAAE,CAAA;YAC3C,MAAM,YAAY,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,EAAE;gBACzG,KAAK;gBACL,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,QAAQ;aAC3C,CAAC,CAAA;YACF,IAAI,CAAC,YAAY,GAAG,IAAI,CAAA;QAC1B,CAAC;QACD,IAAI,MAAM,KAAK,MAAM,CAAC,KAAK;YAAE,IAAI,CAAC,YAAY,GAAG,IAAI,CAAA;QACrD,OAAO,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;IAC3C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAgC;QACzC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAA;QACtC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;QACb,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAA;QACnB,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,CAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC5E,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;gBACxB,MAAM,CAAC,IAAI,KAAK,CAAC,mCAAmC,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC,CAAA;YAC3E,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;YACnB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;YACjD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAA;QAC1C,CAAC,CAAC,CAAA;QAEF,MAAM,KAAK,GAAG,MAAM,CAAC,KAA2E,CAAA;QAChG,IAAI,KAAK,EAAE,IAAI,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,IAAI,aAAa,CAAC,KAAK,CAAC,OAAO,IAAI,gBAAgB,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,CAAA;QAC3G,CAAC;QACD,IAAI,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,IAAI,2BAA2B,CAAC,CAAA;QACxE,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAY,CAAA;IAC1C,CAAC;IAED,KAAK,CAAC,UAAU;QACd,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC,OAAO,CAAA;QACrC,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAClC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA;YACvC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAC9B,CAAC,CAAC,CAAA;QACF,MAAM,CAAC,EAAE,CACP,MAAM,EACN,UAAU,CAAC,CAAC,IAAI,EAAE,EAAE;YAClB,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;YAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;YACpD,IAAI,CAAC,OAAO;gBAAE,OAAM;YACpB,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;YAC3B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;YACvC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QACzB,CAAC,CAAC,CACH,CAAA;QACD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QAC5B,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACtB,KAAK,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;gBACvD,YAAY,CAAC,KAAK,CAAC,CAAA;gBACnB,MAAM,CAAC,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAAA;YAC1C,CAAC;YACD,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;YACrB,IAAI,CAAC,OAAO,GAAG,SAAS,CAAA;QAC1B,CAAC,CAAC,CAAA;QACF,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QACrB,OAAO,MAAM,CAAA;IACf,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAS;IACnC,MAAM,CAAC,YAAY;IACnB,MAAM,CAAC,UAAU;IACjB,MAAM,CAAC,YAAY;IACnB,MAAM,CAAC,aAAa;IACpB,MAAM,CAAC,UAAU;IACjB,MAAM,CAAC,iBAAiB;CACzB,CAAC,CAAA;AAEF,4EAA4E;AAC5E,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAY,EAAoB,EAAE,CAC3D,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;IACtB,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;QAC5B,MAAM,CAAC,OAAO,EAAE,CAAA;QAChB,OAAO,CAAC,KAAK,CAAC,CAAA;IAChB,CAAC,EAAE,IAAI,CAAC,CAAA;IACR,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;IACzE,0FAA0F;IAC1F,MAAM,CAAC,MAAM,EAAE,CAAA;IACf,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;QACxB,YAAY,CAAC,KAAK,CAAC,CAAA;QACnB,OAAO,CAAC,KAAK,CAAC,CAAA;IAChB,CAAC,CAAC,CAAA;IACF,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;QACxB,YAAY,CAAC,KAAK,CAAC,CAAA;QACnB,OAAO,CAAC,IAAI,CAAC,CAAA;IACf,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"server-connection.js","sourceRoot":"","sources":["../../src/server/server-connection.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,8 @@ export type ServerEvent = {
10
10
  event: "status";
11
11
  connected: boolean;
12
12
  at: string;
13
+ byHand?: boolean;
14
+ pid?: number;
13
15
  };
14
16
  export interface MaxServerOptions {
15
17
  store: SessionStore;
@@ -23,6 +25,11 @@ export interface MaxServerOptions {
23
25
  /** The least time between two background logins after the snapshot went stale. */
24
26
  refreshEveryMs?: number;
25
27
  retryAfterMs?: (attempt: number) => number;
28
+ /**
29
+ * **A command started it**, not a person. Only such a server stops when asked over its socket;
30
+ * one started by hand runs until Ctrl-C, whoever asks (the owner's rule, 2026-09-24).
31
+ */
32
+ startedByCommand?: boolean;
26
33
  /**
27
34
  * Stop after this long with nobody using it — no request, no `max watch`. Absent, it runs until
28
35
  * stopped; a server started by a command gets one, or it would outlive every reason it had.
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAa,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAiB,MAAM,2BAA2B,CAAA;AAC7F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAIvD,MAAM,MAAM,WAAW,GACnB;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,UAAU,CAAA;CAAE,GACzC;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,YAAY,CAAA;IACnB,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wCAAwC;IACxC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,6DAA6D;IAC7D,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,GAAG,SAAS,CAAC,KAAK,UAAU,CAAA;IAClF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kFAAkF;IAClF,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;IAC1C;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,6EAA6E;IAC7E,MAAM,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAA;CACpC;AAQD;;;;;;;;;;;;;GAaG;AACH,qBAAa,SAAS;;IAgBpB,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;gBAEhB,OAAO,EAAE,gBAAgB;IAOrC,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,2FAA2F;IACrF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAqBtB,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;CAkNzC;AAKD,mGAAmG;AACnG,eAAO,MAAM,YAAY,GAAI,OAAO,YAAY,KAAG,MAA0C,CAAA;AAE7F,yFAAyF;AACzF,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,KAAG,OAAO,CAAC,OAAO,CAQlD,CAAA"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAa,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAErD,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAiB,MAAM,2BAA2B,CAAA;AAC7F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAIvD,MAAM,MAAM,WAAW,GACnB;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,UAAU,CAAA;CAAE,GACzC;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvF,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,YAAY,CAAA;IACnB,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wCAAwC;IACxC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,6DAA6D;IAC7D,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,GAAG,SAAS,CAAC,KAAK,UAAU,CAAA;IAClF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kFAAkF;IAClF,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;IAC1C;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,6EAA6E;IAC7E,MAAM,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAA;CACpC;AAWD;;;;;;;;;;;;;GAaG;AACH,qBAAa,SAAS;;IAqBpB,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;gBAEhB,OAAO,EAAE,gBAAgB;IAUrC,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,2FAA2F;IACrF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BtB,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;CA6PzC;AAKD,mGAAmG;AACnG,eAAO,MAAM,YAAY,GAAI,OAAO,YAAY,KAAG,MAA0C,CAAA;AAE7F,yFAAyF;AACzF,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,KAAG,OAAO,CAAC,OAAO,CAQlD,CAAA"}
@@ -3,11 +3,14 @@ import { connect, createServer } from "node:net";
3
3
  import { dirname } from "node:path";
4
4
  import { CliError } from "@leemour/cli-core";
5
5
  import { MaxClient } from "../client.js";
6
+ import { Opcode } from "../generated/opcodes.generated.js";
6
7
  import { Connection, ProtocolError } from "../protocol/connection.js";
7
8
  import { fromLine, lineReader, toLine } from "./lines.js";
8
- import { READS } from "./server-connection.js";
9
+ import { forwarded, stopServer } from "./server-connection.js";
9
10
  /** The web client's keep-alive interval (web.max.ru bundle, 2026-09-24). A different one is a fingerprint. */
10
11
  const PING_EVERY_MS = 30_000;
12
+ /** A message arrived — what MAX pushes, and what a send through this server is handed on as. */
13
+ const NEW_MESSAGE = 128;
11
14
  /** A login MAX counts; after one, the next background login waits at least this long. */
12
15
  const REFRESH_EVERY_MS = 60_000;
13
16
  /**
@@ -33,8 +36,13 @@ export class MaxServer {
33
36
  #retry;
34
37
  #attempt = 0;
35
38
  #stopped = false;
36
- /** A push touched the chats in a way the login snapshot cannot follow; it is not handed out. */
37
- #stale = false;
39
+ /** Every client on the socket — a command, `max mcp`, `max watch`. Idle means none at all. */
40
+ #open = new Set();
41
+ /** Message ids already handed to watchers, newest last. */
42
+ #seen = new Set();
43
+ /** Settles when a connection is up; replaced while the server reconnects. */
44
+ #up;
45
+ #markUp = () => { };
38
46
  #refresh;
39
47
  #lastRefresh = 0;
40
48
  #lastUse = Date.now();
@@ -44,6 +52,9 @@ export class MaxServer {
44
52
  done;
45
53
  constructor(options) {
46
54
  this.#options = options;
55
+ this.#up = new Promise((resolve) => {
56
+ this.#markUp = resolve;
57
+ });
47
58
  this.done = new Promise((resolve, reject) => {
48
59
  this.#finish = (error) => (error ? reject(error) : resolve());
49
60
  });
@@ -53,9 +64,15 @@ export class MaxServer {
53
64
  }
54
65
  /** Logs in first: a token that does not work should fail here, before anything listens. */
55
66
  async start() {
67
+ // The socket first: whoever binds it is the profile's one server, and a second one learns it
68
+ // before it has logged in — not after, with a login MAX has already counted.
56
69
  try {
57
- await this.#connect();
58
70
  await this.#listen();
71
+ await this.#connect();
72
+ }
73
+ catch (error) {
74
+ await this.stop();
75
+ throw error;
59
76
  }
60
77
  finally {
61
78
  rmSync(startingPath(this.#options.store), { force: true });
@@ -63,7 +80,7 @@ export class MaxServer {
63
80
  const { idleMs } = this.#options;
64
81
  if (idleMs !== undefined) {
65
82
  this.#idle = setInterval(() => {
66
- if (this.#subscribers.size === 0 && Date.now() - this.#lastUse >= idleMs) {
83
+ if (this.#open.size === 0 && Date.now() - this.#lastUse >= idleMs) {
67
84
  this.#options.note(`nobody has used it for ${Math.round(idleMs / 60_000)} min — stopping`);
68
85
  void this.stop();
69
86
  }
@@ -74,15 +91,21 @@ export class MaxServer {
74
91
  if (this.#stopped)
75
92
  return;
76
93
  this.#stopped = true;
94
+ // Anyone still waiting for a connection gets "not connected" rather than waiting forever.
95
+ this.#markUp();
77
96
  clearInterval(this.#ping);
78
97
  clearTimeout(this.#retry);
79
98
  clearTimeout(this.#refresh);
80
99
  clearInterval(this.#idle);
81
- for (const socket of this.#subscribers)
100
+ for (const socket of this.#open)
82
101
  socket.destroy();
83
102
  this.#subscribers.clear();
84
- await new Promise((resolve) => (this.#listener ? this.#listener.close(() => resolve()) : resolve()));
85
- rmSync(this.#options.store.socketPath(), { force: true });
103
+ // Only the server that bound the socket removes it: one refused as "already running" would
104
+ // otherwise delete the running server's socket and let a third start beside it.
105
+ if (this.#listener) {
106
+ await new Promise((resolve) => this.#listener?.close(() => resolve()));
107
+ rmSync(this.#options.store.socketPath(), { force: true });
108
+ }
86
109
  await this.#client?.close();
87
110
  this.#client = undefined;
88
111
  this.#finish?.(error);
@@ -128,8 +151,8 @@ export class MaxServer {
128
151
  const replaced = this.#client;
129
152
  mine = client;
130
153
  this.#client = client;
154
+ this.#markUp();
131
155
  this.#attempt = 0;
132
- this.#stale = false;
133
156
  this.#lastRefresh = Date.now();
134
157
  clearInterval(this.#ping);
135
158
  this.#ping = setInterval(() => {
@@ -155,7 +178,6 @@ export class MaxServer {
155
178
  * in again in the background — at most once a minute, since each is a login MAX counts.
156
179
  */
157
180
  #goneStale() {
158
- this.#stale = true;
159
181
  if (this.#refresh || this.#stopped)
160
182
  return;
161
183
  const every = this.#options.refreshEveryMs ?? REFRESH_EVERY_MS;
@@ -172,6 +194,9 @@ export class MaxServer {
172
194
  clearInterval(this.#ping);
173
195
  const client = this.#client;
174
196
  this.#client = undefined;
197
+ this.#up = new Promise((resolve) => {
198
+ this.#markUp = resolve;
199
+ });
175
200
  client?.close().catch(() => { });
176
201
  if (this.#stopped)
177
202
  return;
@@ -193,13 +218,22 @@ export class MaxServer {
193
218
  }, wait);
194
219
  }
195
220
  #broadcast(event) {
221
+ if (event.event === "message") {
222
+ // A retried send answers with the same message, and MAX repeats pushes after a hiccup.
223
+ if (this.#seen.has(event.message.id))
224
+ return;
225
+ this.#seen.add(event.message.id);
226
+ if (this.#seen.size > 500)
227
+ this.#seen.delete(this.#seen.values().next().value);
228
+ }
196
229
  const line = toLine(event);
197
230
  for (const socket of this.#subscribers)
198
231
  socket.write(line);
199
232
  }
200
233
  async #listen() {
201
234
  const path = this.#options.store.socketPath();
202
- if (await answers(path)) {
235
+ // A server a command started gives way to one started by hand; one started by hand does not.
236
+ if ((await answers(path)) && !(!this.#options.startedByCommand && (await stopServer(path)) === "stopped")) {
203
237
  throw new CliError("validation_error", `a server is already running for profile "${this.#options.store.profile}"`);
204
238
  }
205
239
  // Nobody answers on it, so it is what a crashed server left behind.
@@ -214,9 +248,11 @@ export class MaxServer {
214
248
  this.#listener = listener;
215
249
  }
216
250
  #serve(socket) {
251
+ this.#open.add(socket);
217
252
  socket.on("error", () => this.#subscribers.delete(socket));
218
253
  socket.on("close", () => {
219
254
  this.#subscribers.delete(socket);
255
+ this.#open.delete(socket);
220
256
  this.#lastUse = Date.now();
221
257
  });
222
258
  socket.on("data", lineReader((line) => {
@@ -232,7 +268,13 @@ export class MaxServer {
232
268
  socket.write(toLine({ error: { code: "bad_request", message: "one JSON object per line" } }));
233
269
  return;
234
270
  }
235
- const status = { event: "status", connected: this.connected, at: new Date().toISOString() };
271
+ const status = {
272
+ event: "status",
273
+ connected: this.connected,
274
+ at: new Date().toISOString(),
275
+ byHand: !this.#options.startedByCommand,
276
+ pid: process.pid,
277
+ };
236
278
  const { id } = request;
237
279
  this.#lastUse = Date.now();
238
280
  if (request.subscribe === true) {
@@ -243,16 +285,24 @@ export class MaxServer {
243
285
  socket.write(toLine(status));
244
286
  }
245
287
  else if (request.stop === true) {
246
- // Only the owner can reach this socket (mode 600); `max session end` asks, so a forgotten
247
- // session is not kept alive by a server still logged in with it.
288
+ // Only the owner can reach this socket (mode 600). `max session end` asks, so a forgotten
289
+ // session is not kept alive by a server a command started — one started by hand stays.
290
+ if (!this.#options.startedByCommand && request.force !== true) {
291
+ socket.end(toLine({ id, stopped: false, reason: "started by hand; Ctrl-C, or `max serve --stop`" }));
292
+ return;
293
+ }
248
294
  socket.end(toLine({ id, stopped: true }));
249
295
  await this.stop();
250
296
  }
251
297
  else if (request.login === true) {
298
+ // A request that arrives while the server logs in, or logs in again, waits for it: the
299
+ // command's own timeout is what gives up, not this. A login a deletion made stale is still
300
+ // handed out — only a deleted last message can be wrong in it — while a fresh one is fetched.
301
+ await this.#up;
252
302
  const client = this.#client;
253
- socket.write(toLine(client && !this.#stale
303
+ socket.write(toLine(client
254
304
  ? { id, payload: client.live.snapshot() }
255
- : { id, error: { code: "unavailable", message: this.#stale ? "the login went stale" : "not connected" } }));
305
+ : { id, error: { code: "unavailable", message: "not connected" } }));
256
306
  }
257
307
  else if (typeof request.opcode === "number") {
258
308
  socket.write(toLine({ id, ...(await this.#forward(request.opcode, request.payload)) }));
@@ -262,16 +312,29 @@ export class MaxServer {
262
312
  }
263
313
  }
264
314
  async #forward(opcode, payload) {
315
+ await this.#up;
265
316
  const client = this.#client;
266
- if (!READS.has(opcode)) {
317
+ if (!forwarded(opcode)) {
267
318
  return {
268
- error: { code: "not_allowed", message: `opcode ${opcode} is not a read; send it on your own connection` },
319
+ error: { code: "not_allowed", message: `opcode ${opcode} is a login; the server does its own` },
269
320
  };
270
321
  }
271
322
  if (!client)
272
323
  return { error: { code: "unavailable", message: "not connected" } };
273
324
  try {
274
- return { payload: await client.live.forward(opcode, (payload ?? {})) };
325
+ const request = (payload ?? {});
326
+ const answer = await client.live.forward(opcode, request);
327
+ // MAX does not push a message back to the connection that sent it, and every send now comes
328
+ // through this one — so `max watch` would never see what `max` itself sent. The answer
329
+ // carries the message; hand it on as if it had been pushed. A scheduled one is not sent yet.
330
+ const scheduled = request.message?.delayedAttributes;
331
+ if (opcode === Opcode.MSG_SEND && answer.message && !scheduled) {
332
+ this.#pushed(client, NEW_MESSAGE, { chatId: request.chatId, message: answer.message });
333
+ }
334
+ // Nor does it push our own deletion back; the chat's last message may be the one deleted.
335
+ if (opcode === Opcode.MSG_DELETE)
336
+ this.#goneStale();
337
+ return { payload: answer };
275
338
  }
276
339
  catch (error) {
277
340
  if (error instanceof ProtocolError) {