@getmarrow/install 0.1.12 → 0.1.13

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/README.md CHANGED
@@ -11,6 +11,20 @@ npx @getmarrow/install --repair
11
11
  npx @getmarrow/install doctor
12
12
  ```
13
13
 
14
+ ## What's New in v0.1.13
15
+
16
+ v0.1.13 turns `npx @getmarrow/install govern` into an interactive terminal setup flow when run in a real TTY.
17
+
18
+ - Select Codex, Claude Code, Cursor, OpenCode, OpenClaw, CI scripts, or a custom command with arrow keys.
19
+ - Choose passive setup, governed pilot mode, or governed enforce mode.
20
+ - Run passive setup + self-test from the TUI after explicit confirmation.
21
+ - Check Marrow status and test the before-action gate from the same screen.
22
+ - Print the exact command for the selected harness/mode so users know what to run next.
23
+ - Exit cleanly with `q`, `Esc`, or `Ctrl+C`.
24
+ - CI/non-TTY usage remains stable with `npx @getmarrow/install govern --no-interactive`.
25
+
26
+ This keeps Marrow passive-first: install once, verify Marrow is active, then let agents use the runtime/gate path automatically for risky work.
27
+
14
28
  ## What's New in v0.1.12
15
29
 
16
30
  v0.1.12 adds the Marrow governed runner for businesses that want agent governance without replacing their existing harness.
@@ -32,6 +46,12 @@ Preview the detected harnesses and protected command examples:
32
46
  npx @getmarrow/install govern
33
47
  ```
34
48
 
49
+ In a real terminal, this opens the interactive setup flow. In CI or scripts, use:
50
+
51
+ ```bash
52
+ npx @getmarrow/install govern --no-interactive
53
+ ```
54
+
35
55
  Run a harmless command through Marrow:
36
56
 
37
57
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmarrow/install",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "Universal installer for Marrow passive agent setup.",
5
5
  "bin": {
6
6
  "marrow-install": "bin/marrow-install.js"
@@ -3,9 +3,11 @@ const crypto = require('node:crypto');
3
3
  const fs = require('node:fs');
4
4
  const os = require('node:os');
5
5
  const path = require('node:path');
6
+ const readline = require('node:readline');
6
7
 
7
8
  const DEFAULT_BASE_URL = 'https://api.getmarrow.ai';
8
9
  const HIGH_RISK_TERMS = /\b(deploy|prod|production|publish|release|merge|migration|migrate|secret|token|key|cloudflare|wrangler|npm publish|gh pr merge|git push|terraform apply|kubectl apply|delete|destroy|drop)\b/i;
10
+ const GOVERN_TUI_ROW_COUNT = 7;
9
11
  function usage() {
10
12
  return `Usage:
11
13
  npx @getmarrow/install run --agent deploy-agent -- npm test
@@ -14,13 +16,14 @@ function usage() {
14
16
  npx @getmarrow/install proof --decision-id <id> --success --summary "smoke passed"
15
17
  npx @getmarrow/install status
16
18
  npx @getmarrow/install govern
19
+ npx @getmarrow/install govern --no-interactive
17
20
 
18
21
  Commands:
19
22
  run Run a command through Marrow pre-action gate and automatic outcome closure
20
23
  gate Check Marrow runtime/gate for an action without running a command
21
24
  proof Commit an outcome/proof for an existing decision
22
25
  status Read /v1/agent/status
23
- govern Print a TUI-style setup panel for configuring governed agent runs
26
+ govern Interactive setup TUI when run in a terminal; text panel in CI/non-TTY
24
27
 
25
28
  Options:
26
29
  --agent <id> Agent identity. Defaults to MARROW_FLEET_AGENT_ID, MARROW_AGENT_ID, or local user
@@ -36,6 +39,8 @@ Options:
36
39
  --base-url <url> Marrow API base URL
37
40
  --key <key> Marrow API key. Prefer MARROW_API_KEY
38
41
  --json Print machine-readable result after completion
42
+ --interactive Force interactive govern TUI when possible
43
+ --no-interactive Print govern panel instead of opening the TUI
39
44
  `;
40
45
  }
41
46
 
@@ -55,13 +60,28 @@ function redact(value) {
55
60
 
56
61
  function shellQuote(value) {
57
62
  const text = String(value || '');
58
- return /^[A-Za-z0-9_./:@%+=,-]+$/.test(text) ? text : JSON.stringify(text);
63
+ if (!text) return "''";
64
+ return /^[A-Za-z0-9_./:@%+=,-]+$/.test(text) ? text : `'${text.replace(/'/g, "'\\''")}'`;
59
65
  }
60
66
 
61
67
  function redactedCommand(command) {
62
68
  return command.map((part) => shellQuote(redact(part))).join(' ');
63
69
  }
64
70
 
71
+ function displayText(value, maxLength = 120) {
72
+ const text = redact(String(value || ''))
73
+ .replace(/\u001b\][^\u0007]*(?:\u0007|\u001b\\)/g, '')
74
+ .replace(/\u009d[^\u0007\u009c]*(?:\u0007|\u009c|\u001b\\)/g, '')
75
+ .replace(/[\u001b\u009b][[\]()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '')
76
+ .replace(/[\t\r\n]+/g, ' ')
77
+ .replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/g, '');
78
+ return text.length > maxLength ? `${text.slice(0, maxLength - 3)}...` : text;
79
+ }
80
+
81
+ function shellQuoteDisplay(value) {
82
+ return shellQuote(displayText(value, 240));
83
+ }
84
+
65
85
  function inferType(text) {
66
86
  const value = String(text || '').toLowerCase();
67
87
  if (/\b(deploy|wrangler|cloudflare|production|prod|release)\b/.test(value)) return 'deploy';
@@ -104,6 +124,7 @@ function parseBaseOptions(argv, startIndex = 0) {
104
124
  proofFile: '',
105
125
  type: '',
106
126
  action: '',
127
+ interactive: null,
107
128
  };
108
129
  let i = startIndex;
109
130
  for (; i < argv.length; i += 1) {
@@ -128,6 +149,8 @@ function parseBaseOptions(argv, startIndex = 0) {
128
149
  options.apiKey = argv[++i] || options.apiKey;
129
150
  options.keyFromArg = true;
130
151
  } else if (arg === '--json') options.json = true;
152
+ else if (arg === '--interactive') options.interactive = true;
153
+ else if (arg === '--no-interactive') options.interactive = false;
131
154
  else if (arg === '--help' || arg === '-h') options.help = true;
132
155
  else if (arg.startsWith('--')) throw new Error(`Unknown option: ${arg}`);
133
156
  else break;
@@ -439,8 +462,10 @@ function detectHarnesses(cwd = process.cwd()) {
439
462
  const candidates = [
440
463
  { name: 'Codex', command: 'codex', detected: fs.existsSync(path.join(cwd, 'AGENTS.md')) || fs.existsSync(path.join(os.homedir(), '.codex')) },
441
464
  { name: 'Claude Code', command: 'claude -p', detected: fs.existsSync(path.join(cwd, 'CLAUDE.md')) || fs.existsSync(path.join(os.homedir(), '.claude.json')) },
465
+ { name: 'Cursor', command: 'cursor', detected: fs.existsSync(path.join(cwd, '.cursor')) || fs.existsSync(path.join(os.homedir(), '.cursor')) },
442
466
  { name: 'OpenCode', command: 'opencode', detected: fs.existsSync(path.join(cwd, 'opencode.json')) || fs.existsSync(path.join(os.homedir(), '.opencode')) },
443
467
  { name: 'OpenClaw', command: 'openclaw agent', detected: fs.existsSync(path.join(os.homedir(), '.openclaw')) },
468
+ { name: 'CI script', command: 'npm test', detected: fs.existsSync(path.join(cwd, 'package.json')) },
444
469
  { name: 'Custom command', command: '<your-agent-command>', detected: true },
445
470
  ];
446
471
  return candidates;
@@ -448,12 +473,15 @@ function detectHarnesses(cwd = process.cwd()) {
448
473
 
449
474
  function governPanel(options) {
450
475
  const rows = detectHarnesses();
476
+ const agentId = displayText(options.agentId, 80);
477
+ const profile = displayText(options.profile, 80);
478
+ const policy = displayText(options.policy, 24);
451
479
  const lines = [
452
480
  'Marrow Governed Runner',
453
481
  '',
454
- `Agent: ${options.agentId}`,
455
- `Profile: ${options.profile}`,
456
- `Policy: ${options.policy}`,
482
+ `Agent: ${agentId}`,
483
+ `Profile: ${profile}`,
484
+ `Policy: ${policy}`,
457
485
  '',
458
486
  'Choose where your agent runs. Marrow governs the action before it executes.',
459
487
  '',
@@ -461,7 +489,7 @@ function governPanel(options) {
461
489
  ...rows.map((row, index) => ` ${index + 1}. ${row.detected ? '[x]' : '[ ]'} ${row.name} ${row.command}`),
462
490
  '',
463
491
  'Recommended first commands:',
464
- ` npx @getmarrow/install run --agent ${options.agentId} --profile production --policy enforce -- codex`,
492
+ ` npx @getmarrow/install run --agent ${shellQuoteDisplay(options.agentId)} --profile production --policy enforce -- codex`,
465
493
  ` npx @getmarrow/install run --agent deploy-agent --type deploy --policy enforce -- wrangler deploy`,
466
494
  ` npx @getmarrow/install gate "deploy production worker after tests pass"`,
467
495
  '',
@@ -470,6 +498,319 @@ function governPanel(options) {
470
498
  return lines.join('\n');
471
499
  }
472
500
 
501
+ function governModes() {
502
+ return [
503
+ {
504
+ id: 'passive',
505
+ label: 'Passive setup',
506
+ description: 'Install passive MCP/SDK/agent instructions, then run the installer self-test.',
507
+ policy: 'warn',
508
+ },
509
+ {
510
+ id: 'warn',
511
+ label: 'Governed pilot',
512
+ description: 'Wrap commands with Marrow, show gates, but do not block execution.',
513
+ policy: 'warn',
514
+ },
515
+ {
516
+ id: 'enforce',
517
+ label: 'Governed enforce',
518
+ description: 'Wrap risky commands and fail closed when Marrow blocks or requires owner approval.',
519
+ policy: 'enforce',
520
+ },
521
+ ];
522
+ }
523
+
524
+ function commandForSelection(state, options) {
525
+ const harness = state.harnesses[state.harnessIndex] || state.harnesses[0];
526
+ const mode = state.modes[state.modeIndex] || state.modes[0];
527
+ if (mode.id === 'passive') {
528
+ return 'MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install --yes';
529
+ }
530
+ const command = harness.command === '<your-agent-command>' ? '<your-command>' : harness.command;
531
+ const renderedCommand = command.split(/\s+/).filter(Boolean).map(shellQuote).join(' ');
532
+ return `MARROW_API_KEY=mrw_live_xxx npx @getmarrow/install run --agent ${shellQuoteDisplay(options.agentId)} --profile ${shellQuoteDisplay(options.profile)} --policy ${shellQuoteDisplay(mode.policy)} -- ${renderedCommand}`;
533
+ }
534
+
535
+ function buildGovernState(options, cwd = process.cwd()) {
536
+ const harnesses = detectHarnesses(cwd);
537
+ const firstDetected = harnesses.findIndex((harness) => harness.detected);
538
+ return {
539
+ cursor: 0,
540
+ harnesses,
541
+ harnessIndex: firstDetected >= 0 ? firstDetected : 0,
542
+ modes: governModes(),
543
+ modeIndex: 0,
544
+ status: '',
545
+ lastResult: '',
546
+ confirmingSetup: false,
547
+ running: false,
548
+ };
549
+ }
550
+
551
+ function renderOptionBox(row, active) {
552
+ const contentWidth = 86;
553
+ const borderWidth = contentWidth + 2;
554
+ const marker = active ? '>' : ' ';
555
+ const borderChar = active ? '=' : '-';
556
+ const labelText = `[${displayText(row.label, 36)}]`;
557
+ const labelVisible = labelText.padEnd(38, ' ');
558
+ const label = `\x1b[47m\x1b[30m${labelText}\x1b[0m${' '.repeat(Math.max(0, 38 - labelText.length))}`;
559
+ const value = displayText(row.value, contentWidth - 39);
560
+ const firstLineVisible = `${labelVisible}${value}`;
561
+ const firstLine = `${label}${value}${' '.repeat(Math.max(0, contentWidth - firstLineVisible.length))}`;
562
+ const hint = displayText(row.hint, contentWidth);
563
+ return [
564
+ `${marker} +${borderChar.repeat(borderWidth)}+`,
565
+ `${marker} | ${firstLine} |`,
566
+ `${marker} | ${hint.padEnd(contentWidth, ' ')} |`,
567
+ `${marker} +${borderChar.repeat(borderWidth)}+`,
568
+ ];
569
+ }
570
+
571
+ function renderGovernTui(state, options) {
572
+ const harness = state.harnesses[state.harnessIndex] || state.harnesses[0];
573
+ const mode = state.modes[state.modeIndex] || state.modes[0];
574
+ const rows = [
575
+ {
576
+ label: 'Harness',
577
+ value: `${harness.name}${harness.detected ? ' detected' : ' not detected'} (${harness.command})`,
578
+ hint: 'Left/right changes the harness.',
579
+ },
580
+ {
581
+ label: 'Mode',
582
+ value: mode.label,
583
+ hint: mode.description,
584
+ },
585
+ {
586
+ label: 'Run passive setup + self-test',
587
+ value: state.confirmingSetup ? 'Press Enter again to run installer --yes' : 'writes local config after confirmation',
588
+ hint: 'Uses the existing installer path and self-test.',
589
+ },
590
+ {
591
+ label: 'Check Marrow status',
592
+ value: options.apiKey ? 'ready' : 'needs MARROW_API_KEY',
593
+ hint: 'Calls GET /v1/agent/status.',
594
+ },
595
+ {
596
+ label: 'Test before-action gate',
597
+ value: options.apiKey ? 'ready' : 'needs MARROW_API_KEY',
598
+ hint: 'Calls POST /v1/agent/runtime for a deploy-like action.',
599
+ },
600
+ {
601
+ label: 'Show command and exit',
602
+ value: 'Print selected command',
603
+ hint: 'Prints the command for this selection.',
604
+ },
605
+ {
606
+ label: 'Exit',
607
+ value: 'Return to shell',
608
+ hint: 'Press Enter, q, Esc, or Ctrl+C to leave setup.',
609
+ },
610
+ ];
611
+ const lines = [
612
+ '\x1b[2J\x1b[H',
613
+ '+------------------------------------------------------------+',
614
+ '| Marrow Governed Setup |',
615
+ '| Passive agent governance for day-one use |',
616
+ '+------------------------------------------------------------+',
617
+ '',
618
+ `Agent: ${displayText(options.agentId, 36)} Profile: ${displayText(options.profile, 24)} API key: ${options.apiKey ? 'present' : 'missing'}`,
619
+ '',
620
+ 'Navigation: Up/Down move Left/Right change Enter select',
621
+ 'Exit: q, Esc, or Ctrl+C',
622
+ '',
623
+ ];
624
+ rows.forEach((row, index) => {
625
+ lines.push(...renderOptionBox(row, index === state.cursor), '');
626
+ });
627
+ lines.push('Recommended command:');
628
+ lines.push(` ${commandForSelection(state, options)}`);
629
+ if (state.status) {
630
+ lines.push('');
631
+ lines.push(`Status: ${displayText(state.status, 120)}`);
632
+ }
633
+ if (state.lastResult) {
634
+ lines.push('');
635
+ lines.push(displayText(state.lastResult, 500));
636
+ }
637
+ return lines.join('\n');
638
+ }
639
+
640
+ function canUseInteractive(options, input = process.stdin, output = process.stdout) {
641
+ if (options.interactive === false) return false;
642
+ if (options.interactive === true) return Boolean(input.isTTY && output.isTTY);
643
+ return Boolean(input.isTTY && output.isTTY);
644
+ }
645
+
646
+ function waitForAnyKey(input = process.stdin) {
647
+ return new Promise((resolve) => {
648
+ const onKey = () => {
649
+ input.off('keypress', onKey);
650
+ resolve();
651
+ };
652
+ input.on('keypress', onKey);
653
+ });
654
+ }
655
+
656
+ async function runSetupSelfTest(options, input, output) {
657
+ if (!options.apiKey) {
658
+ return 'MARROW_API_KEY is missing. Create a key in your Marrow account, export it, then rerun setup.';
659
+ }
660
+ const binPath = path.resolve(__dirname, '..', 'bin', 'marrow-install.js');
661
+ output.write('\x1b[2J\x1b[HRunning Marrow passive setup and self-test...\n\n');
662
+ if (input.setRawMode) input.setRawMode(false);
663
+ const result = await runChild([process.execPath, binPath, '--yes'], {
664
+ ...process.env,
665
+ MARROW_API_KEY: options.apiKey,
666
+ MARROW_BASE_URL: options.baseUrl,
667
+ MARROW_FLEET_AGENT_ID: options.agentId,
668
+ });
669
+ output.write('\nPress any key to return to Marrow Governed Setup.');
670
+ if (input.setRawMode) input.setRawMode(true);
671
+ await waitForAnyKey(input);
672
+ return result.exitCode === 0
673
+ ? 'Marrow passive setup completed. Self-test output above is the source of truth.'
674
+ : `Marrow passive setup exited with code ${result.exitCode}. Review the output above.`;
675
+ }
676
+
677
+ async function runStatusCheck(options) {
678
+ if (!options.apiKey) return 'MARROW_API_KEY is missing. Status check skipped.';
679
+ const status = await statusOnly({ options });
680
+ const coverage = status.capture_coverage || {};
681
+ const closure = status.auto_outcome_closure || {};
682
+ const active = status.enabled ?? status.active ?? true;
683
+ const missed = Array.isArray(status.missed_hooks) && status.missed_hooks.length
684
+ ? ` missed hooks: ${status.missed_hooks.join(', ')}`
685
+ : '';
686
+ return `Marrow status: ${active ? 'active' : 'inactive'}; coverage=${coverage.status || coverage.summary || 'reported'}; outcomes=${closure.status || closure.summary || 'reported'}${missed}`;
687
+ }
688
+
689
+ async function runGateCheck(options) {
690
+ if (!options.apiKey) return 'MARROW_API_KEY is missing. Gate check skipped.';
691
+ const runtime = await preflightRuntime(options, 'deploy production worker after tests pass', 'deploy', 'wrangler deploy');
692
+ const decision = gateDecision(runtime);
693
+ const proof = decision.proofPack?.required
694
+ ? ` Proof required${decision.proofPack.missing?.length ? `; missing ${decision.proofPack.missing.join(', ')}` : ''}.`
695
+ : '';
696
+ return `Gate: ${decision.decision}${decision.required ? ' required' : ''}.${decision.exactNextAction ? ` Next: ${decision.exactNextAction}` : ''}${proof}`;
697
+ }
698
+
699
+ async function runGovernInteractive(options, input = process.stdin, output = process.stdout) {
700
+ if (!canUseInteractive(options, input, output)) {
701
+ output.write(`${governPanel(options)}\n`);
702
+ return;
703
+ }
704
+
705
+ const state = buildGovernState(options);
706
+ readline.emitKeypressEvents(input);
707
+ input.setRawMode(true);
708
+ output.write('\x1b[?25l');
709
+
710
+ let cleaned = false;
711
+ const cleanup = () => {
712
+ if (cleaned) return;
713
+ cleaned = true;
714
+ if (input.setRawMode) input.setRawMode(false);
715
+ output.write('\x1b[?25h');
716
+ };
717
+
718
+ const render = () => {
719
+ output.write(renderGovernTui(state, options));
720
+ };
721
+
722
+ render();
723
+ let keyHandler;
724
+ try {
725
+ await new Promise((resolve) => {
726
+ keyHandler = async (str, key = {}) => {
727
+ if (state.running) return;
728
+ if (key.ctrl && key.name === 'c') {
729
+ cleanup();
730
+ resolve();
731
+ return;
732
+ }
733
+ if (key.name === 'q' || key.name === 'escape' || str === 'q') {
734
+ cleanup();
735
+ resolve();
736
+ return;
737
+ }
738
+ if (key.name === 'up') {
739
+ state.cursor = (state.cursor + GOVERN_TUI_ROW_COUNT - 1) % GOVERN_TUI_ROW_COUNT;
740
+ state.confirmingSetup = false;
741
+ render();
742
+ } else if (key.name === 'down') {
743
+ state.cursor = (state.cursor + 1) % GOVERN_TUI_ROW_COUNT;
744
+ state.confirmingSetup = false;
745
+ render();
746
+ } else if (key.name === 'left' || key.name === 'right') {
747
+ const direction = key.name === 'right' ? 1 : -1;
748
+ if (state.cursor === 0) state.harnessIndex = (state.harnessIndex + direction + state.harnesses.length) % state.harnesses.length;
749
+ if (state.cursor === 1) state.modeIndex = (state.modeIndex + direction + state.modes.length) % state.modes.length;
750
+ state.confirmingSetup = false;
751
+ render();
752
+ } else if (key.name === 'return') {
753
+ state.running = true;
754
+ try {
755
+ if (state.cursor === 0) {
756
+ state.harnessIndex = (state.harnessIndex + 1) % state.harnesses.length;
757
+ state.status = 'Harness selected.';
758
+ state.confirmingSetup = false;
759
+ } else if (state.cursor === 1) {
760
+ state.modeIndex = (state.modeIndex + 1) % state.modes.length;
761
+ state.status = 'Mode selected.';
762
+ state.confirmingSetup = false;
763
+ } else if (state.cursor === 2) {
764
+ if (!state.confirmingSetup) {
765
+ state.confirmingSetup = true;
766
+ state.status = 'Confirm passive setup.';
767
+ } else {
768
+ state.lastResult = await runSetupSelfTest(options, input, output);
769
+ state.status = 'Passive setup attempted.';
770
+ state.confirmingSetup = false;
771
+ }
772
+ } else if (state.cursor === 3) {
773
+ state.status = 'Checking Marrow status...';
774
+ render();
775
+ state.lastResult = await runStatusCheck(options);
776
+ state.status = 'Status check complete.';
777
+ state.confirmingSetup = false;
778
+ } else if (state.cursor === 4) {
779
+ state.status = 'Testing before-action gate...';
780
+ render();
781
+ state.lastResult = await runGateCheck(options);
782
+ state.status = 'Gate check complete.';
783
+ state.confirmingSetup = false;
784
+ } else if (state.cursor === 5) {
785
+ cleanup();
786
+ output.write(`\n${commandForSelection(state, options)}\n`);
787
+ resolve();
788
+ return;
789
+ } else if (state.cursor === 6) {
790
+ cleanup();
791
+ resolve();
792
+ return;
793
+ }
794
+ } catch (error) {
795
+ state.lastResult = `Error: ${error instanceof Error ? error.message : String(error)}`;
796
+ state.status = 'Action failed.';
797
+ state.confirmingSetup = false;
798
+ } finally {
799
+ state.running = false;
800
+ if (!cleaned) render();
801
+ }
802
+ }
803
+ };
804
+ input.on('keypress', keyHandler);
805
+ });
806
+ } finally {
807
+ if (keyHandler) input.off('keypress', keyHandler);
808
+ if (input.pause) input.pause();
809
+ cleanup();
810
+ output.write('\n');
811
+ }
812
+ }
813
+
473
814
  async function runCli(argv) {
474
815
  const parsed = parseArgs(argv);
475
816
  if (parsed.command === 'help') {
@@ -487,7 +828,7 @@ async function runCli(argv) {
487
828
  else if (parsed.command === 'proof') result = await proofOnly(parsed);
488
829
  else if (parsed.command === 'status') result = await statusOnly(parsed);
489
830
  else if (parsed.command === 'govern') {
490
- process.stdout.write(`${governPanel(parsed.options)}\n`);
831
+ await runGovernInteractive(parsed.options);
491
832
  return;
492
833
  }
493
834
 
@@ -504,12 +845,19 @@ module.exports = {
504
845
  redactedCommand,
505
846
  inferType,
506
847
  inferSurfaces,
848
+ commandForSelection,
849
+ buildGovernState,
507
850
  gateDecision,
508
851
  shouldBlock,
509
852
  governPanel,
853
+ renderGovernTui,
854
+ canUseInteractive,
510
855
  runGoverned,
511
856
  gateOnly,
512
857
  proofOnly,
513
858
  statusOnly,
859
+ runStatusCheck,
860
+ runGateCheck,
861
+ runGovernInteractive,
514
862
  runCli,
515
863
  };