@mnemahq/cli 0.10.0 → 0.11.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mnemahq/cli",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Mnema CLI — connect a repo to your Mnema workspace: install session capture, sweep past sessions, and search from the terminal.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.mjs CHANGED
@@ -510,6 +510,9 @@ Commands:
510
510
  uninstall Remove hooks and stored secrets
511
511
  tui Open the interactive briefing explicitly (Node 20+, a terminal)
512
512
 
513
+ On a terminal, the read commands below open a NAVIGABLE view — arrow keys to move,
514
+ enter to open, esc to go back. Piped or with --json they print as they always have.
515
+
513
516
  Read your workspace:
514
517
  tasks List tasks [--status --project --limit]
515
518
  next The next task to pick up, with a ready-made branch name
@@ -520,7 +523,7 @@ Read your workspace:
520
523
 
521
524
  Ask the knowledge graph (paid feature):
522
525
  ask "q" A cited answer, with the confidence it deserves
523
- graph Neighbourhood of a node, or the path between two
526
+ graph Walk a node's neighbours; graph <a> <b> prints the path between two
524
527
 
525
528
  Examples:
526
529
  mnema the interactive briefing
@@ -565,6 +568,49 @@ writes plain text.
565
568
  * because `node --check` parses without resolving and would never notice a stray
566
569
  * `import { Box } from 'ink'` in read-commands.mjs.
567
570
  */
571
+ /**
572
+ * ⭐ THE COMMAND YOU TYPED SHOULD DO THE THING YOU NAMED.
573
+ *
574
+ * `mnema graph "Workspace Security & Management"` printed 25 rows and "… 176
575
+ * more" and stopped. The navigable walker existed, but it lived behind bare
576
+ * `mnema` and a `g` keybinding — so the obvious command gave a static dump and
577
+ * the answer was "run a different command and press a key". Nobody does that.
578
+ * They type the thing they want.
579
+ *
580
+ * ⚠️ THE ONE-SHOT PATH IS UNCHANGED. --json, a pipe, no TTY, --no-tui, CI: all
581
+ * still print exactly what they printed before, byte for byte. Scripts are the
582
+ * contract. This only changes what a HUMAN AT A TERMINAL gets.
583
+ *
584
+ * @returns true if the interactive view took over.
585
+ */
586
+ /**
587
+ * Which screen a read command should open on, or null to stay one-shot.
588
+ *
589
+ * Exported so the ROUTING is testable without spawning ink — the decision is the
590
+ * part that was wrong, and a test that re-implements it in the test file proves
591
+ * nothing about this function.
592
+ */
593
+ export function screenFor(cmd, flags, rest = []) {
594
+ if (flags.json) return null;
595
+ if (cmd === 'graph') {
596
+ // `graph <a> <b>` is a path query — one answer, not a place to walk.
597
+ if (rest.length > 1) return null;
598
+ return rest[0] ? { name: 'node', target: { label: rest[0] } } : { name: 'graph' };
599
+ }
600
+ if (cmd === 'tasks' || cmd === 'docs' || cmd === 'briefing') return { name: cmd };
601
+ return null;
602
+ }
603
+
604
+ async function maybeInteractive(flags, screen) {
605
+ if (!screen) return false;
606
+ if (flags.json) return false;
607
+ if (!tuiEligibility(flags).ok) return false;
608
+ const { startTui } = await import('./tui/launch.mjs');
609
+ const ctx = resolveContext(flags);
610
+ await startTui({ ...ctx, version: VERSION, call: (fn) => call(ctx, fn) }, screen);
611
+ return true;
612
+ }
613
+
568
614
  async function openTuiOrHelp(flags) {
569
615
  const gate = tuiEligibility(flags);
570
616
  if (gate.ok) {
@@ -612,14 +658,25 @@ export async function run(argv) {
612
658
 
613
659
  // Reads. Each resolves context once and hands the SDK client to a wrapper;
614
660
  // none of them knows a URL or an envelope key (§A2, t-623).
615
- case 'tasks': return cmdTasks(flags, resolveContext(flags));
661
+ case 'tasks':
662
+ if (await maybeInteractive(flags, screenFor('tasks', flags))) return 0;
663
+ return cmdTasks(flags, resolveContext(flags));
616
664
  case 'next': return cmdNext(flags, resolveContext(flags));
617
- case 'docs': return cmdDocs(flags, resolveContext(flags));
665
+ case 'docs':
666
+ if (await maybeInteractive(flags, screenFor('docs', flags))) return 0;
667
+ return cmdDocs(flags, resolveContext(flags));
618
668
  case 'doc': return cmdDoc(flags, resolveContext(flags), rest);
619
669
  case 'projects': return cmdProjects(flags, resolveContext(flags));
620
670
  case 'ask': return cmdAsk(flags, resolveContext(flags), rest);
621
- case 'graph': return cmdGraph(flags, resolveContext(flags), rest);
622
- case 'briefing': return cmdBriefing(flags, resolveContext(flags));
671
+ case 'graph':
672
+ // With a node named, open the walker AT it. With no argument, open the hub
673
+ // list. `graph <a> <b>` is a path query — a single answer, not a place to
674
+ // walk — so that one stays one-shot.
675
+ if (await maybeInteractive(flags, screenFor('graph', flags, rest))) return 0;
676
+ return cmdGraph(flags, resolveContext(flags), rest);
677
+ case 'briefing':
678
+ if (await maybeInteractive(flags, screenFor('briefing', flags))) return 0;
679
+ return cmdBriefing(flags, resolveContext(flags));
623
680
  case 'tui':
624
681
  // An EXPLICIT request that cannot be honoured fails loudly (exit 1); the
625
682
  // implicit one below degrades to help and exits 0. That asymmetry is
package/src/tui/app.mjs CHANGED
@@ -26,9 +26,9 @@ import { Docs, Doc } from './screens/docs.mjs';
26
26
 
27
27
  const HOME = { name: 'briefing' };
28
28
 
29
- export function App({ ctx }) {
29
+ export function App({ ctx, initial }) {
30
30
  const { exit } = useApp();
31
- const [stack, setStack] = useState([HOME]);
31
+ const [stack, setStack] = useState([initial ?? HOME]);
32
32
  const [fatal, setFatal] = useState(null);
33
33
 
34
34
  // ⭐ THE AUTH TAKEOVER HAS TO BE WIRED, not merely rendered. Every screen fetches
@@ -28,8 +28,14 @@ import { App } from './app.mjs';
28
28
 
29
29
  const SHOW_CURSOR = '\x1b[?25h';
30
30
 
31
- export async function startTui(ctx) {
32
- const instance = render(html`<${App} ctx=${ctx} />`, {
31
+ /**
32
+ * @param initial the screen to open on. `mnema graph "X"` opens the walker AT X
33
+ * rather than at the briefing, because the command someone typed
34
+ * should do the thing they named — not drop them at a home screen
35
+ * with a keybinding to hunt for.
36
+ */
37
+ export async function startTui(ctx, initial) {
38
+ const instance = render(html`<${App} ctx=${ctx} initial=${initial} />`, {
33
39
  // Ink's own exitOnCtrlC would leave our own key handling out of the loop.
34
40
  exitOnCtrlC: true,
35
41
  });