@leemour/max-cli 0.3.0 → 0.4.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 (81) hide show
  1. package/README.md +7 -0
  2. package/dist/cache/store.d.ts +4 -7
  3. package/dist/cache/store.d.ts.map +1 -1
  4. package/dist/cache/store.js +18 -3
  5. package/dist/cache/store.js.map +1 -1
  6. package/dist/client.d.ts +18 -9
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +92 -36
  9. package/dist/client.js.map +1 -1
  10. package/dist/commands/account.js +1 -1
  11. package/dist/commands/account.js.map +1 -1
  12. package/dist/commands/body.d.ts +23 -0
  13. package/dist/commands/body.d.ts.map +1 -0
  14. package/dist/commands/body.js +36 -0
  15. package/dist/commands/body.js.map +1 -0
  16. package/dist/commands/cache.js +2 -2
  17. package/dist/commands/cache.js.map +1 -1
  18. package/dist/commands/chats.js +1 -1
  19. package/dist/commands/chats.js.map +1 -1
  20. package/dist/commands/config.d.ts +3 -0
  21. package/dist/commands/config.d.ts.map +1 -0
  22. package/dist/commands/config.js +55 -0
  23. package/dist/commands/config.js.map +1 -0
  24. package/dist/commands/contacts.js +2 -2
  25. package/dist/commands/contacts.js.map +1 -1
  26. package/dist/commands/context.d.ts +23 -2
  27. package/dist/commands/context.d.ts.map +1 -1
  28. package/dist/commands/context.js +42 -7
  29. package/dist/commands/context.js.map +1 -1
  30. package/dist/commands/doctor.d.ts +18 -0
  31. package/dist/commands/doctor.d.ts.map +1 -0
  32. package/dist/commands/doctor.js +72 -0
  33. package/dist/commands/doctor.js.map +1 -0
  34. package/dist/commands/messages.d.ts.map +1 -1
  35. package/dist/commands/messages.js +65 -5
  36. package/dist/commands/messages.js.map +1 -1
  37. package/dist/commands/runs.d.ts.map +1 -1
  38. package/dist/commands/runs.js +4 -6
  39. package/dist/commands/runs.js.map +1 -1
  40. package/dist/commands/session.js +2 -2
  41. package/dist/commands/session.js.map +1 -1
  42. package/dist/commands/skill.d.ts +3 -0
  43. package/dist/commands/skill.d.ts.map +1 -0
  44. package/dist/commands/skill.js +23 -0
  45. package/dist/commands/skill.js.map +1 -0
  46. package/dist/config.d.ts +36 -0
  47. package/dist/config.d.ts.map +1 -1
  48. package/dist/config.js +99 -9
  49. package/dist/config.js.map +1 -1
  50. package/dist/deadline.d.ts +30 -0
  51. package/dist/deadline.d.ts.map +1 -0
  52. package/dist/deadline.js +45 -0
  53. package/dist/deadline.js.map +1 -0
  54. package/dist/diagnose.d.ts +68 -0
  55. package/dist/diagnose.d.ts.map +1 -0
  56. package/dist/diagnose.js +115 -0
  57. package/dist/diagnose.js.map +1 -0
  58. package/dist/domain/models.d.ts +10 -0
  59. package/dist/domain/models.d.ts.map +1 -1
  60. package/dist/domain/models.js +11 -1
  61. package/dist/domain/models.js.map +1 -1
  62. package/dist/program.d.ts +2 -1
  63. package/dist/program.d.ts.map +1 -1
  64. package/dist/program.js +11 -0
  65. package/dist/program.js.map +1 -1
  66. package/dist/protocol/connection.d.ts.map +1 -1
  67. package/dist/protocol/connection.js +9 -0
  68. package/dist/protocol/connection.js.map +1 -1
  69. package/dist/rendering/messages.d.ts +3 -3
  70. package/dist/rendering/messages.d.ts.map +1 -1
  71. package/dist/rendering/messages.js +2 -1
  72. package/dist/rendering/messages.js.map +1 -1
  73. package/dist/testing/mock-max.d.ts.map +1 -1
  74. package/dist/testing/mock-max.js +16 -2
  75. package/dist/testing/mock-max.js.map +1 -1
  76. package/dist/testing/sandbox.js +8 -1
  77. package/dist/testing/sandbox.js.map +1 -1
  78. package/dist/version.d.ts +1 -1
  79. package/dist/version.js +1 -1
  80. package/package.json +2 -1
  81. package/skills/max-cli/SKILL.md +83 -0
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare const skillCommand: () => Command;
3
+ //# sourceMappingURL=skill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../../src/commands/skill.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAMnC,eAAO,MAAM,YAAY,QAAO,OAgB/B,CAAA"}
@@ -0,0 +1,23 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { Command } from "commander";
3
+ import { forCommand } from "./context.js";
4
+ /** Beside `dist/` in the package and in a checkout alike, so the skill is always this version's. */
5
+ const SKILL = new URL("../../skills/max-cli/SKILL.md", import.meta.url);
6
+ export const skillCommand = () => {
7
+ const command = new Command("skill").description("the instructions an agent is given for this tool");
8
+ command
9
+ .command("show")
10
+ .description("print SKILL.md — `max skill show > ~/.claude/skills/max-cli/SKILL.md` installs it")
11
+ .action(function () {
12
+ const { settings, renderer, streams } = forCommand(this);
13
+ const content = readFileSync(SKILL, "utf8").trimEnd();
14
+ // The file itself even into a pipe: installing it *is* a redirect, and a redirect is where
15
+ // every other command switches to JSON. Asked for by name, it is JSON like the rest.
16
+ if (settings.json || settings.jsonl)
17
+ renderer.result({ name: "max-cli", content });
18
+ else
19
+ streams.data(content);
20
+ });
21
+ return command;
22
+ };
23
+ //# sourceMappingURL=skill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill.js","sourceRoot":"","sources":["../../src/commands/skill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzC,oGAAoG;AACpG,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,+BAA+B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAEvE,MAAM,CAAC,MAAM,YAAY,GAAG,GAAY,EAAE;IACxC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,kDAAkD,CAAC,CAAA;IAEpG,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,mFAAmF,CAAC;SAChG,MAAM,CAAC;QACN,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;QACxD,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,EAAE,CAAA;QACrD,2FAA2F;QAC3F,qFAAqF;QACrF,IAAI,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,KAAK;YAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAA;;YAC7E,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC5B,CAAC,CAAC,CAAA;IAEJ,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA"}
package/dist/config.d.ts CHANGED
@@ -24,9 +24,12 @@ export interface GlobalFlags {
24
24
  page?: number;
25
25
  all?: boolean;
26
26
  record?: boolean;
27
+ /** Raw text from `--timeout`, parsed here so the unit rule lives in one place. */
28
+ timeout?: string;
27
29
  }
28
30
  export interface Settings {
29
31
  profile: string;
32
+ /** Where the profile came from, decided here so nothing has to re-derive the order. */
30
33
  json: boolean;
31
34
  jsonl: boolean;
32
35
  quiet: boolean;
@@ -44,11 +47,31 @@ export interface Settings {
44
47
  all: boolean;
45
48
  /** Unset means the transport's own default; the number lives in `protocol/connection.ts`. */
46
49
  timeoutMs: number | undefined;
50
+ /**
51
+ * How long the **whole command** may take, or `undefined` for no bound.
52
+ *
53
+ * ⚠ Not the same thing as `timeoutMs`, which is one request's wait. One read is a connect, an
54
+ * INIT, a LOGIN, a name to resolve and then the request itself, so the wall clock is a multiple
55
+ * of `timeoutMs` and never equal to it. An agent given thirty seconds needs to say *thirty
56
+ * seconds*, and this is the only setting that means that.
57
+ *
58
+ * They are spelled differently on purpose — a number of milliseconds in the file, a duration
59
+ * with a unit on the command line — because two settings called "timeout" that mean different
60
+ * things are otherwise a trap.
61
+ */
62
+ commandTimeoutMs: number | undefined;
47
63
  record: boolean;
48
64
  keepRunsForDays: number;
49
65
  /** Named in errors and in `max --help`, so a person can find the file that decided this. */
50
66
  configPath: string;
67
+ configFound: boolean;
68
+ /** Profiles the configuration file names, whether or not anyone has logged in to them. */
69
+ configuredProfiles: string[];
70
+ /** Where each value came from — `max config show` prints it. */
71
+ sources: Record<SourcedSetting, Source>;
51
72
  }
73
+ export type Source = "first word" | "flag" | "MAX_PROFILE" | "MAX_TIMEOUT" | "config file" | "default";
74
+ export type SourcedSetting = "profile" | "limit" | "timeoutMs" | "commandTimeoutMs" | "color" | "senderColors" | "record" | "keepRunsForDays";
52
75
  export interface ResolveOptions {
53
76
  env?: NodeJS.ProcessEnv;
54
77
  /** Tests pass a temporary directory; nothing else should need this. */
@@ -58,10 +81,23 @@ export interface ResolveOptions {
58
81
  * **Flag, then environment, then the configuration file, then the built-in default.** One place,
59
82
  * so no command can decide the order differently from another.
60
83
  *
84
+ * ⚠ **Only two settings actually have the middle step** — `MAX_PROFILE` and `MAX_TIMEOUT` — and
85
+ * that is deliberate rather than unfinished (`NEED-119`). They are the two an agent sets once for
86
+ * a whole process. A variable for `--json` or `--color` would be worse than missing: one left set
87
+ * in a shell silently changes the output of a command that never asked for it.
88
+ *
61
89
  * Two things already jump this queue and are documented rather than re-litigated here:
62
90
  * `MAX_TOKEN` outranks the keyring (`cli-core`'s `Credentials.read`), and `MAX_CONFIG_DIR`,
63
91
  * `MAX_STATE_DIR` or `MAX_CACHE_DIR` move the whole installation — including which keyring entry
64
92
  * a profile means (`ARCHITECTURE.md` §13).
65
93
  */
66
94
  export declare const resolveSettings: (flags?: GlobalFlags, { env, configDir }?: ResolveOptions) => Settings;
95
+ /**
96
+ * The profile names written in the configuration file.
97
+ *
98
+ * ⚠ **This is not every profile that works.** `max <name> session start` stores a token under any
99
+ * name without writing anything to the file, so a profile can be in daily use and absent here.
100
+ * Whoever prints this has to say so, or it reads as a complete list and quietly is not.
101
+ */
102
+ export declare const configuredProfiles: ({ env, configDir }?: ResolveOptions) => string[];
67
103
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AA8B5B,eAAO,MAAM,YAAY;;;;;;;QALvB,6EAA6E;;;;aAQ7E,CAAA;AAEF,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,YAAY,CAAC,CAAA;AAEvD,gGAAgG;AAChG,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,OAAO,CAAA;IACd,KAAK,EAAE,OAAO,CAAA;IACd,wEAAwE;IACxE,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACjB,KAAK,EAAE,OAAO,CAAA;IACd,8FAA8F;IAC9F,KAAK,EAAE,OAAO,GAAG,SAAS,CAAA;IAC1B,4FAA4F;IAC5F,YAAY,EAAE,OAAO,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,wHAAwH;IACxH,IAAI,EAAE,MAAM,CAAA;IACZ,sDAAsD;IACtD,GAAG,EAAE,OAAO,CAAA;IACZ,6FAA6F;IAC7F,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,MAAM,EAAE,OAAO,CAAA;IACf,eAAe,EAAE,MAAM,CAAA;IACvB,4FAA4F;IAC5F,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,QAAO,WAAgB,EAAE,qBAAkC,cAAmB,aA2C7G,CAAA"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AA8B5B,eAAO,MAAM,YAAY;;;;;;;QALvB,6EAA6E;;;;aAQ7E,CAAA;AAEF,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,YAAY,CAAC,CAAA;AAEvD,gGAAgG;AAChG,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,uFAAuF;IACvF,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,OAAO,CAAA;IACd,KAAK,EAAE,OAAO,CAAA;IACd,wEAAwE;IACxE,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACjB,KAAK,EAAE,OAAO,CAAA;IACd,8FAA8F;IAC9F,KAAK,EAAE,OAAO,GAAG,SAAS,CAAA;IAC1B,4FAA4F;IAC5F,YAAY,EAAE,OAAO,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,wHAAwH;IACxH,IAAI,EAAE,MAAM,CAAA;IACZ,sDAAsD;IACtD,GAAG,EAAE,OAAO,CAAA;IACZ,6FAA6F;IAC7F,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B;;;;;;;;;;;OAWG;IACH,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAA;IACpC,MAAM,EAAE,OAAO,CAAA;IACf,eAAe,EAAE,MAAM,CAAA;IACvB,4FAA4F;IAC5F,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,OAAO,CAAA;IACpB,0FAA0F;IAC1F,kBAAkB,EAAE,MAAM,EAAE,CAAA;IAC5B,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAA;CACxC;AAED,MAAM,MAAM,MAAM,GAAG,YAAY,GAAG,MAAM,GAAG,aAAa,GAAG,aAAa,GAAG,aAAa,GAAG,SAAS,CAAA;AACtG,MAAM,MAAM,cAAc,GACtB,SAAS,GACT,OAAO,GACP,WAAW,GACX,kBAAkB,GAClB,OAAO,GACP,cAAc,GACd,QAAQ,GACR,iBAAiB,CAAA;AAQrB,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,eAAe,GAAI,QAAO,WAAgB,EAAE,qBAAkC,cAAmB,aA+F7G,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,GAAI,qBAAkC,cAAmB,KAAG,MAAM,EAGhG,CAAA"}
package/dist/config.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { existsSync } from "node:fs";
1
2
  import { CliError, configFilePath, loadConfigFile, resolvePaths } from "@leemour/cli-core";
2
3
  import * as v from "valibot";
3
4
  import { DEFAULT_PROFILE, usableProfileName } from "./profile.js";
@@ -29,10 +30,22 @@ export const configSchema = v.strictObject({
29
30
  defaultProfile: v.optional(v.string()),
30
31
  profiles: v.optional(v.record(v.string(), profileSettings), {}),
31
32
  });
33
+ /** The first given value wins, and says which it was. */
34
+ const first = (candidates, fallback) => {
35
+ for (const [from, value] of candidates)
36
+ if (value !== undefined)
37
+ return { value, from };
38
+ return { value: fallback, from: "default" };
39
+ };
32
40
  /**
33
41
  * **Flag, then environment, then the configuration file, then the built-in default.** One place,
34
42
  * so no command can decide the order differently from another.
35
43
  *
44
+ * ⚠ **Only two settings actually have the middle step** — `MAX_PROFILE` and `MAX_TIMEOUT` — and
45
+ * that is deliberate rather than unfinished (`NEED-119`). They are the two an agent sets once for
46
+ * a whole process. A variable for `--json` or `--color` would be worse than missing: one left set
47
+ * in a shell silently changes the output of a command that never asked for it.
48
+ *
36
49
  * Two things already jump this queue and are documented rather than re-litigated here:
37
50
  * `MAX_TOKEN` outranks the keyring (`cli-core`'s `Credentials.read`), and `MAX_CONFIG_DIR`,
38
51
  * `MAX_STATE_DIR` or `MAX_CACHE_DIR` move the whole installation — including which keyring entry
@@ -42,24 +55,62 @@ export const resolveSettings = (flags = {}, { env = process.env, configDir } = {
42
55
  const paths = resolvePaths({ appName: APP, prefix: "MAX", env });
43
56
  const configPath = configFilePath(configDir ?? paths.config);
44
57
  const config = readConfig(configPath);
45
- const profile = usableProfileName(flags.profile ?? given(env.MAX_PROFILE) ?? config.defaultProfile ?? DEFAULT_PROFILE);
46
- const configured = config.profiles[profile] ?? {};
58
+ const profile = first([
59
+ ["first word", flags.profile],
60
+ ["MAX_PROFILE", given(env.MAX_PROFILE)],
61
+ ["config file", config.defaultProfile],
62
+ ], DEFAULT_PROFILE);
63
+ const configured = config.profiles[usableProfileName(profile.value)] ?? {};
64
+ const limit = first([
65
+ ["flag", flags.limit],
66
+ ["config file", configured.limit],
67
+ ], DEFAULT_LIMIT);
68
+ const timeoutMs = first([["config file", configured.timeoutMs]], undefined);
69
+ const color = first([["config file", configured.color]], undefined);
70
+ const senderColors = first([["config file", configured.senderColors]], false);
71
+ const record = first([
72
+ ["flag", flags.record],
73
+ ["config file", configured.record],
74
+ ], false);
75
+ const keepRunsForDays = first([["config file", configured.keepRunsForDays]], DEFAULT_KEEP_RUNS_FOR_DAYS);
76
+ /**
77
+ * ⚠ **The only setting with no `config file` row, on purpose.** A budget for one command is
78
+ * about a particular run, not a habit, and a timeout written into a file is one somebody trips
79
+ * over months later without remembering they set it.
80
+ */
81
+ const timeout = first([
82
+ ["flag", flags.timeout],
83
+ ["MAX_TIMEOUT", given(env.MAX_TIMEOUT)],
84
+ ], undefined);
47
85
  const settings = {
48
- profile,
86
+ profile: usableProfileName(profile.value),
49
87
  json: flags.json === true,
50
88
  jsonl: flags.jsonl === true,
51
89
  quiet: flags.quiet === true,
52
90
  detail: Math.min(2, Math.max(0, flags.verbose ?? 0)),
53
91
  trace: flags.trace === true,
54
- color: configured.color,
55
- senderColors: configured.senderColors ?? false,
56
- limit: flags.limit ?? configured.limit ?? DEFAULT_LIMIT,
92
+ color: color.value,
93
+ senderColors: senderColors.value,
94
+ limit: limit.value,
57
95
  page: flags.page ?? 1,
58
96
  all: flags.all === true,
59
- timeoutMs: configured.timeoutMs,
60
- record: flags.record ?? configured.record ?? false,
61
- keepRunsForDays: configured.keepRunsForDays ?? DEFAULT_KEEP_RUNS_FOR_DAYS,
97
+ timeoutMs: timeoutMs.value,
98
+ commandTimeoutMs: durationMs(timeout.value, timeout.from),
99
+ record: record.value,
100
+ keepRunsForDays: keepRunsForDays.value,
62
101
  configPath,
102
+ configFound: existsSync(configPath),
103
+ configuredProfiles: Object.keys(config.profiles),
104
+ sources: {
105
+ profile: profile.from,
106
+ limit: limit.from,
107
+ timeoutMs: timeoutMs.from,
108
+ commandTimeoutMs: timeout.from,
109
+ color: color.from,
110
+ senderColors: senderColors.from,
111
+ record: record.from,
112
+ keepRunsForDays: keepRunsForDays.from,
113
+ },
63
114
  };
64
115
  // The file was checked by the schema; a flag was not, and `--limit abc` is `NaN` by the time it
65
116
  // gets here, which slices an array to nothing without complaining.
@@ -76,6 +127,45 @@ export const resolveSettings = (flags = {}, { env = process.env, configDir } = {
76
127
  }
77
128
  return settings;
78
129
  };
130
+ /**
131
+ * The profile names written in the configuration file.
132
+ *
133
+ * ⚠ **This is not every profile that works.** `max <name> session start` stores a token under any
134
+ * name without writing anything to the file, so a profile can be in daily use and absent here.
135
+ * Whoever prints this has to say so, or it reads as a complete list and quietly is not.
136
+ */
137
+ export const configuredProfiles = ({ env = process.env, configDir } = {}) => {
138
+ const paths = resolvePaths({ appName: APP, prefix: "MAX", env });
139
+ return Object.keys(readConfig(configFilePath(configDir ?? paths.config)).profiles).sort();
140
+ };
141
+ /**
142
+ * `30s`, `2m`, `500ms` — **the unit is required, and a bare number is refused.**
143
+ *
144
+ * `--timeout 30` is ambiguous in a way that costs real time: the neighbouring setting in the
145
+ * configuration file is called `timeoutMs` and is milliseconds, while every other tool that spells
146
+ * it this way means seconds. Guessing either one is a thirty-fold surprise in one direction or the
147
+ * other, so this asks instead — the same rule as `--kind`, and the reason is the same.
148
+ *
149
+ * The refusal names whichever of the two supplied the value — see `durationMs`.
150
+ */
151
+ const DURATION = /^(\d+)(ms|s|m)$/;
152
+ const UNIT_MS = { ms: 1, s: 1000, m: 60_000 };
153
+ const durationMs = (value, from) => {
154
+ if (value === undefined)
155
+ return undefined;
156
+ // Named by where it came from, so somebody who set `MAX_TIMEOUT` in a shell profile weeks ago is
157
+ // told which thing is wrong rather than shown a flag they never typed.
158
+ const source = from === "MAX_TIMEOUT" ? "MAX_TIMEOUT" : "--timeout";
159
+ const match = DURATION.exec(value.trim());
160
+ if (!match?.[1] || !match[2]) {
161
+ throw new CliError("validation_error", `${source} takes a duration with a unit — 30s, 2m or 500ms — not "${value}"`);
162
+ }
163
+ const ms = Number(match[1]) * (UNIT_MS[match[2]] ?? 0);
164
+ if (ms <= 0) {
165
+ throw new CliError("validation_error", `${source} has to be more than zero, and "${value}" is not`);
166
+ }
167
+ return ms;
168
+ };
79
169
  /**
80
170
  * A missing file is not an error — it is a program nobody has configured. A malformed one is, in
81
171
  * every output mode: an agent reading JSON deserves the same refusal a person gets, and silently
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /** Anything holding something that would keep the process alive. A `MaxClient` is one. */
2
+ export interface Closeable {
3
+ close(): Promise<void>;
4
+ }
5
+ /**
6
+ * Stops the whole command at `ms`, and **closes what it was holding on the way out**.
7
+ *
8
+ * ⚠ The closing is the part that matters, and it is what a plain `Promise.race` does not do.
9
+ * Racing only decides which answer is printed; the losing work carries on, and an open WebSocket
10
+ * keeps Node alive — so a command that reported a timeout would then sit there, which is the
11
+ * defect §18 forbids and worse than the wait it was cutting short. A test covers exactly that, by
12
+ * taking the closing away and watching what is left behind.
13
+ *
14
+ * Closing a connection with a request still in flight used to strand the caller forever; it now
15
+ * rejects it (`BUG-24`), which is what lets the body unwind through its own `finally` rather than
16
+ * hanging somewhere underneath this.
17
+ *
18
+ * ⚠ **It bounds everything, not only what talks to MAX.** `max messages search` never opens a
19
+ * socket at all and still has to fit the budget: an agent handing out thirty seconds does not
20
+ * know which commands are the networked ones, and a bound that quietly did not apply to some of
21
+ * them would be worse than no bound.
22
+ *
23
+ * It lives here rather than under `commands/` because it is about the lifetime of a process, not
24
+ * about any command — and because a command may not reach the transport, while the thing that
25
+ * shuts the transport down has to.
26
+ *
27
+ * Without a bound there is no timer and no race: the ordinary path is exactly what it was.
28
+ */
29
+ export declare const withDeadline: <T>(ms: number | undefined, closeables: Closeable[], body: () => Promise<T>) => Promise<T>;
30
+ //# sourceMappingURL=deadline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deadline.d.ts","sourceRoot":"","sources":["../src/deadline.ts"],"names":[],"mappings":"AAEA,0FAA0F;AAC1F,MAAM,WAAW,SAAS;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,YAAY,GAAU,CAAC,EAClC,IAAI,MAAM,GAAG,SAAS,EACtB,YAAY,SAAS,EAAE,EACvB,MAAM,MAAM,OAAO,CAAC,CAAC,CAAC,KACrB,OAAO,CAAC,CAAC,CAmBX,CAAA"}
@@ -0,0 +1,45 @@
1
+ import { CliError } from "@leemour/cli-core";
2
+ /**
3
+ * Stops the whole command at `ms`, and **closes what it was holding on the way out**.
4
+ *
5
+ * ⚠ The closing is the part that matters, and it is what a plain `Promise.race` does not do.
6
+ * Racing only decides which answer is printed; the losing work carries on, and an open WebSocket
7
+ * keeps Node alive — so a command that reported a timeout would then sit there, which is the
8
+ * defect §18 forbids and worse than the wait it was cutting short. A test covers exactly that, by
9
+ * taking the closing away and watching what is left behind.
10
+ *
11
+ * Closing a connection with a request still in flight used to strand the caller forever; it now
12
+ * rejects it (`BUG-24`), which is what lets the body unwind through its own `finally` rather than
13
+ * hanging somewhere underneath this.
14
+ *
15
+ * ⚠ **It bounds everything, not only what talks to MAX.** `max messages search` never opens a
16
+ * socket at all and still has to fit the budget: an agent handing out thirty seconds does not
17
+ * know which commands are the networked ones, and a bound that quietly did not apply to some of
18
+ * them would be worse than no bound.
19
+ *
20
+ * It lives here rather than under `commands/` because it is about the lifetime of a process, not
21
+ * about any command — and because a command may not reach the transport, while the thing that
22
+ * shuts the transport down has to.
23
+ *
24
+ * Without a bound there is no timer and no race: the ordinary path is exactly what it was.
25
+ */
26
+ export const withDeadline = async (ms, closeables, body) => {
27
+ if (ms === undefined)
28
+ return body();
29
+ let timer;
30
+ const expired = new Promise((_, reject) => {
31
+ timer = setTimeout(() => {
32
+ // Sockets first, then the message: the rejection is what the person reads, and the closing
33
+ // is what lets the process actually end once they have read it.
34
+ void Promise.allSettled(closeables.map((closeable) => closeable.close())).then(() => reject(new CliError("timeout", `the command did not finish within ${ms}ms — \`--timeout\` ended it`)));
35
+ }, ms);
36
+ });
37
+ try {
38
+ return await Promise.race([body(), expired]);
39
+ }
40
+ finally {
41
+ if (timer)
42
+ clearTimeout(timer);
43
+ }
44
+ };
45
+ //# sourceMappingURL=deadline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deadline.js","sourceRoot":"","sources":["../src/deadline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAO5C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAC/B,EAAsB,EACtB,UAAuB,EACvB,IAAsB,EACV,EAAE;IACd,IAAI,EAAE,KAAK,SAAS;QAAE,OAAO,IAAI,EAAE,CAAA;IAEnC,IAAI,KAAiC,CAAA;IACrC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;QAC/C,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YACtB,2FAA2F;YAC3F,gEAAgE;YAChE,KAAK,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAClF,MAAM,CAAC,IAAI,QAAQ,CAAC,SAAS,EAAE,qCAAqC,EAAE,6BAA6B,CAAC,CAAC,CACtG,CAAA;QACH,CAAC,EAAE,EAAE,CAAC,CAAA;IACR,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC,CAAA;IAC9C,CAAC;YAAS,CAAC;QACT,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,CAAA;IAChC,CAAC;AACH,CAAC,CAAA"}
@@ -0,0 +1,68 @@
1
+ export interface DiagnoseOptions {
2
+ profile: string;
3
+ env?: NodeJS.ProcessEnv;
4
+ /** Tests point these at a temporary directory; nothing else should need them. */
5
+ stateDir?: string;
6
+ cacheDir?: string;
7
+ /** Injected so a test needs no SQLite file and no keyring. */
8
+ readSchemaVersion?: (file: string) => Promise<number | undefined>;
9
+ hasKeyringToken?: (profile: string) => boolean;
10
+ }
11
+ export type TokenSource = "environment" | "keyring" | "none";
12
+ export interface Diagnosis {
13
+ /** ⚠ Never the token, and never a prefix or a length of it — only whether one is reachable. */
14
+ token: {
15
+ present: boolean;
16
+ from: TokenSource;
17
+ };
18
+ /**
19
+ * ⚠ **The trap this command exists for.** `MAX_CONFIG_DIR` and its two siblings change the
20
+ * keyring entry a profile means, so a login performed with them set answers "no session"
21
+ * without them — and nothing else in the tool can show that.
22
+ */
23
+ keyring: {
24
+ service: string;
25
+ movedByEnvironment: boolean;
26
+ };
27
+ session: {
28
+ stateFile: string;
29
+ /** `false` on a machine nobody has logged in on. Reading it must not create it. */
30
+ exists: boolean;
31
+ deviceId: boolean;
32
+ viewerId: boolean;
33
+ /** What `RISK-2` wants counted: a token that stops working can then be explained. */
34
+ logins: number | null;
35
+ lastLoginAt: string | null;
36
+ };
37
+ loggedInProfiles: string[];
38
+ cache: {
39
+ file: string;
40
+ exists: boolean;
41
+ /** What is written in the file, which is not always what this build speaks. */
42
+ schemaVersion: number | undefined;
43
+ speaks: number;
44
+ /** A file from a newer `max` is refused rather than written to — and that looks like a bug. */
45
+ readable: boolean;
46
+ };
47
+ runs: {
48
+ directory: string;
49
+ kept: number;
50
+ };
51
+ }
52
+ /**
53
+ * Everything a command depends on, read from disk and **never from MAX**.
54
+ *
55
+ * It is a separate function rather than the body of the command because `forCommand` builds its
56
+ * own renderer over the real streams, so anything left inside a command action cannot be asserted
57
+ * on (`CLI-15`, and `src/session/adopt.ts` moved for the same reason).
58
+ *
59
+ * ⚠ **It must work when everything is broken**, which is the only time anybody runs it: no
60
+ * session, no configuration file, a cache from a newer version, a keyring that will not open. Each
61
+ * of those is a field in the answer, never an exception.
62
+ *
63
+ * ⚠ **It reads the state file directly instead of calling `readState`**, which invents a device
64
+ * identity and writes it out on first read. A diagnosis that creates the thing it is reporting on
65
+ * is not a diagnosis.
66
+ */
67
+ export declare const diagnose: ({ profile, env, stateDir, cacheDir, readSchemaVersion, hasKeyringToken, }: DiagnoseOptions) => Promise<Diagnosis>;
68
+ //# sourceMappingURL=diagnose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnose.d.ts","sourceRoot":"","sources":["../src/diagnose.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACjE,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAA;CAC/C;AAED,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,SAAS,GAAG,MAAM,CAAA;AAE5D,MAAM,WAAW,SAAS;IACxB,+FAA+F;IAC/F,KAAK,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE,CAAA;IAC9C;;;;OAIG;IACH,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,kBAAkB,EAAE,OAAO,CAAA;KAAE,CAAA;IACzD,OAAO,EAAE;QACP,SAAS,EAAE,MAAM,CAAA;QACjB,mFAAmF;QACnF,MAAM,EAAE,OAAO,CAAA;QACf,QAAQ,EAAE,OAAO,CAAA;QACjB,QAAQ,EAAE,OAAO,CAAA;QACjB,qFAAqF;QACrF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;QACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAC3B,CAAA;IACD,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAA;QACZ,MAAM,EAAE,OAAO,CAAA;QACf,+EAA+E;QAC/E,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;QACjC,MAAM,EAAE,MAAM,CAAA;QACd,+FAA+F;QAC/F,QAAQ,EAAE,OAAO,CAAA;KAClB,CAAA;IACD,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAC1C;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,QAAQ,GAAU,2EAO5B,eAAe,KAAG,OAAO,CAAC,SAAS,CA0CrC,CAAA"}
@@ -0,0 +1,115 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { pathsAreOverridden, resolvePaths } from "@leemour/cli-core";
4
+ import { openCache } from "./cache/open.js";
5
+ import { SCHEMA_VERSION } from "./cache/schema.js";
6
+ /**
7
+ * Everything a command depends on, read from disk and **never from MAX**.
8
+ *
9
+ * It is a separate function rather than the body of the command because `forCommand` builds its
10
+ * own renderer over the real streams, so anything left inside a command action cannot be asserted
11
+ * on (`CLI-15`, and `src/session/adopt.ts` moved for the same reason).
12
+ *
13
+ * ⚠ **It must work when everything is broken**, which is the only time anybody runs it: no
14
+ * session, no configuration file, a cache from a newer version, a keyring that will not open. Each
15
+ * of those is a field in the answer, never an exception.
16
+ *
17
+ * ⚠ **It reads the state file directly instead of calling `readState`**, which invents a device
18
+ * identity and writes it out on first read. A diagnosis that creates the thing it is reporting on
19
+ * is not a diagnosis.
20
+ */
21
+ export const diagnose = async ({ profile, env = process.env, stateDir, cacheDir, readSchemaVersion = schemaVersionOf, hasKeyringToken = () => false, }) => {
22
+ const paths = resolvePaths({ appName: "max-cli", prefix: "MAX", env });
23
+ const state = stateDir ?? paths.state;
24
+ const cacheHome = cacheDir ?? paths.cache;
25
+ const moved = pathsAreOverridden({ appName: "max-cli", prefix: "MAX", env });
26
+ const fromEnvironment = (env.MAX_TOKEN ?? "").trim() !== "";
27
+ const inKeyring = fromEnvironment ? false : hasKeyringToken(profile);
28
+ const stateFile = join(state, "profiles", `${profile}.json`);
29
+ const stored = readState(stateFile);
30
+ const cacheFile = join(cacheHome, `${profile}.db`);
31
+ const cacheExists = existsSync(cacheFile);
32
+ const schemaVersion = cacheExists ? await readSchemaVersion(cacheFile) : undefined;
33
+ const runsDirectory = join(state, "runs");
34
+ return {
35
+ token: {
36
+ present: fromEnvironment || inKeyring,
37
+ from: fromEnvironment ? "environment" : inKeyring ? "keyring" : "none",
38
+ },
39
+ keyring: { service: moved ? `max-cli:${paths.config}` : "max-cli", movedByEnvironment: moved },
40
+ session: {
41
+ stateFile,
42
+ exists: stored !== undefined,
43
+ deviceId: typeof stored?.deviceId === "string" && stored.deviceId !== "",
44
+ viewerId: typeof stored?.viewerId === "string" && stored.viewerId !== "",
45
+ logins: typeof stored?.logins === "number" ? stored.logins : null,
46
+ lastLoginAt: typeof stored?.lastLoginAt === "string" ? stored.lastLoginAt : null,
47
+ },
48
+ loggedInProfiles: profilesWithState(join(state, "profiles")),
49
+ cache: {
50
+ file: cacheFile,
51
+ exists: cacheExists,
52
+ schemaVersion,
53
+ speaks: SCHEMA_VERSION,
54
+ readable: !cacheExists || (schemaVersion !== undefined && schemaVersion <= SCHEMA_VERSION),
55
+ },
56
+ runs: { directory: runsDirectory, kept: countEntries(runsDirectory) },
57
+ };
58
+ };
59
+ /** A file that is missing, unreadable or not JSON is "no session", never a thrown error. */
60
+ const readState = (file) => {
61
+ try {
62
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
63
+ return typeof parsed === "object" && parsed !== null ? parsed : undefined;
64
+ }
65
+ catch {
66
+ return undefined;
67
+ }
68
+ };
69
+ /**
70
+ * **Which profiles have been logged in**, which is not the same list as the configured ones —
71
+ * `config show` says so and cannot answer it, because a profile needs no configuration entry.
72
+ */
73
+ const profilesWithState = (directory) => {
74
+ try {
75
+ return readdirSync(directory)
76
+ .filter((name) => name.endsWith(".json"))
77
+ .map((name) => name.slice(0, -".json".length))
78
+ .sort();
79
+ }
80
+ catch {
81
+ return [];
82
+ }
83
+ };
84
+ const countEntries = (directory) => {
85
+ try {
86
+ return readdirSync(directory).filter((name) => statSync(join(directory, name)).isDirectory()).length;
87
+ }
88
+ catch {
89
+ return 0;
90
+ }
91
+ };
92
+ /**
93
+ * The schema version written in the file, read **without migrating it**.
94
+ *
95
+ * It goes through `openCache`, the one seam that knows which SQLite this runtime has — and
96
+ * crucially `openCache` does **not** migrate: `migrate` runs inside `openStore`. So this reads the
97
+ * number the file already carries rather than the number it would carry after being upgraded,
98
+ * which is the whole point. A command asked to describe the state must not change it.
99
+ */
100
+ const schemaVersionOf = async (file) => {
101
+ try {
102
+ const database = await openCache(file);
103
+ try {
104
+ const row = database.prepare("PRAGMA user_version").get();
105
+ return typeof row?.user_version === "number" ? row.user_version : undefined;
106
+ }
107
+ finally {
108
+ database.close();
109
+ }
110
+ }
111
+ catch {
112
+ return undefined;
113
+ }
114
+ };
115
+ //# sourceMappingURL=diagnose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnose.js","sourceRoot":"","sources":["../src/diagnose.ts"],"names":[],"mappings":"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"}
@@ -108,4 +108,14 @@ export interface Profile {
108
108
  name: string | null;
109
109
  phone: string | null;
110
110
  }
111
+ /** One message of a window around another, which carries `anchor: true`. */
112
+ export type WindowedMessage = Message & {
113
+ anchor?: true;
114
+ };
115
+ /**
116
+ * When a message was sent, read from its id: **`id >> 16` is the send time in milliseconds**, the
117
+ * low 16 bits a counter. Measured 2026-09-22 on three messages across two days, exact every time.
118
+ * `undefined` for anything that is not a message id.
119
+ */
120
+ export declare const timeOfMessageId: (id: Id) => number | undefined;
111
121
  //# sourceMappingURL=models.d.ts.map
@@ -1 +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;IACZ;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,8EAA8E;IAC9E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,8GAA8G;AAC9G,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,EAAE,CAAA;IACN,QAAQ,EAAE,EAAE,GAAG,IAAI,CAAA;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,sDAAsD;IACtD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,4EAA4E;IAC5E,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAA;CACzB;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;IACzB,OAAO,EAAE,aAAa,GAAG,IAAI,CAAA;IAC7B,aAAa,EAAE,aAAa,GAAG,IAAI,CAAA;CACpC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAW,SAAQ,OAAO;IACzC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;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;IAC1B;;;;;OAKG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,IAAI,CAAC,CAAC;IACrB,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,EAAE,CAAA;IACN,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB"}
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/domain/models.ts"],"names":[],"mappings":"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"}
@@ -3,5 +3,15 @@
3
3
  * shape is undocumented, changes without notice, and carries fields whose meaning nobody knows.
4
4
  * Everything above the adapter speaks these types.
5
5
  */
6
- export {};
6
+ /**
7
+ * When a message was sent, read from its id: **`id >> 16` is the send time in milliseconds**, the
8
+ * low 16 bits a counter. Measured 2026-09-22 on three messages across two days, exact every time.
9
+ * `undefined` for anything that is not a message id.
10
+ */
11
+ export const timeOfMessageId = (id) => {
12
+ if (!/^\d{10,20}$/.test(id))
13
+ return undefined;
14
+ const time = Number(BigInt(id) >> 16n);
15
+ return Number.isSafeInteger(time) && time > 0 ? time : undefined;
16
+ };
7
17
  //# sourceMappingURL=models.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/domain/models.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/domain/models.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwHH;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,EAAM,EAAsB,EAAE;IAC5D,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,OAAO,SAAS,CAAA;IAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,GAAG,CAAC,CAAA;IACtC,OAAO,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;AAClE,CAAC,CAAA"}
package/dist/program.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type Streams } from "@leemour/cli-core";
2
2
  import { Command } from "commander";
3
- export interface RunOptions {
3
+ import { type Environment } from "./commands/context.js";
4
+ export interface RunOptions extends Environment {
4
5
  streams?: Streams;
5
6
  /** Whether a person is looking. Defaults to whether stdout is a terminal. */
6
7
  tty?: boolean;