@mnemahq/cli 0.4.0 → 0.7.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.
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Whether to open the interactive UI, and why not when not (t-632).
3
+ *
4
+ * ⚠️ THIS FILE MUST NEVER IMPORT INK. It is loaded on every single invocation of
5
+ * `mnema`, including `mnema --version` from a shell prompt and the capture hook.
6
+ * `import('ink')` costs ~140ms measured, against a ~60ms `mnema --version`, so a
7
+ * static import here would make every one-shot several times slower for a feature
8
+ * almost no invocation uses. The decision has to be cheap; only the answer "yes"
9
+ * is allowed to be expensive.
10
+ *
11
+ * ⭐ AND `node --check` WILL NOT CATCH A MISTAKE HERE. It parses without resolving,
12
+ * so `import { Box } from 'ink'` at the top of this file is syntactically fine and
13
+ * nothing goes red until a user's `mnema status` crashes or crawls. That is what
14
+ * test/no-ink-in-oneshot.test.mjs is for.
15
+ */
16
+
17
+ /** ink 6 requires Node 20 — the same floor @mnemahq/sdk already declares. */
18
+ export const MIN_NODE = 20;
19
+
20
+ const nodeMajor = () => Number(process.versions.node.split('.')[0]);
21
+
22
+ /**
23
+ * @returns {{ok: boolean, reason?: string, detail?: string}}
24
+ */
25
+ export function tuiEligibility(flags = {}, env = process.env, out = process.stdout, inp = process.stdin) {
26
+ // An explicit no wins over everything, including MNEMA_TUI=always.
27
+ if (flags['no-tui'] || env.MNEMA_TUI === 'never' || env.MNEMA_NO_TUI) {
28
+ return { ok: false, reason: 'opted-out' };
29
+ }
30
+
31
+ // ⚠️ THE NODE CHECK IS NOT OVERRIDABLE. MNEMA_TUI=always exists so tests can
32
+ // exercise the positive path without a pty; it must not be able to force a
33
+ // runtime that cannot load the library into trying.
34
+ if (nodeMajor() < MIN_NODE) {
35
+ return { ok: false, reason: 'node', detail: `v${process.versions.node}` };
36
+ }
37
+
38
+ // ⚠️ RAW MODE IS A HARD REQUIREMENT OF INK, so this is not overridable either.
39
+ // Found by running `MNEMA_TUI=always mnema` through a pipe: Ink starts, cannot
40
+ // put stdin in raw mode, and renders its own internal fallback — which emits a
41
+ // React "two children with the same key" warning straight into the user's output
42
+ // and leaves a half-drawn screen that responds to nothing. I spent a while
43
+ // hunting that key in MY components before proving it came from Ink's internals:
44
+ // with a raw-mode-capable stdin the same tree renders clean.
45
+ //
46
+ // A UI that cannot receive a keypress is not a UI, so refuse rather than start.
47
+ if (typeof inp.setRawMode !== 'function') {
48
+ return { ok: false, reason: 'no-raw-mode' };
49
+ }
50
+
51
+ if (env.MNEMA_TUI === 'always') return { ok: true };
52
+
53
+ if (!out.isTTY || !inp.isTTY) return { ok: false, reason: 'not-a-tty' };
54
+ if (env.TERM === 'dumb') return { ok: false, reason: 'dumb-terminal' };
55
+ // Ink degrades to line-by-line under CI, but we would rather not start at all.
56
+ if (env.CI) return { ok: false, reason: 'ci' };
57
+
58
+ return { ok: true };
59
+ }
60
+
61
+ /**
62
+ * What to tell the user, and on which stream.
63
+ *
64
+ * ⚠️ `not-a-tty` SAYS NOTHING AT ALL. Piping `mnema` to read its help is a
65
+ * perfectly good thing to do, and a nag in that case is noise in someone's data.
66
+ * The node case does explain itself, because "I ran mnema and got help" deserves
67
+ * a reason — but on stderr, so `mnema | cat` still gets clean help on stdout.
68
+ */
69
+ export function explainUnavailable(reason, detail) {
70
+ if (reason === 'node') {
71
+ return [
72
+ `The interactive UI needs Node ${MIN_NODE} or newer — you are on ${detail}.`,
73
+ 'Everything below works on your version. For the UI: nvm install 22',
74
+ ];
75
+ }
76
+ if (reason === 'dumb-terminal') return ['This terminal reports TERM=dumb, so the interactive UI is off.'];
77
+ if (reason === 'no-raw-mode') return ['This terminal cannot enter raw mode, so the interactive UI cannot read keys.'];
78
+ return [];
79
+ }