enigma-cli 1.1.4 → 1.3.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/dist/enigma.js CHANGED
@@ -106,7 +106,7 @@ var init_agents = __esm({
106
106
  }
107
107
  },
108
108
  opencode: {
109
- label: "opencode",
109
+ label: "OpenCode",
110
110
  memoryFile: "AGENTS.md",
111
111
  // opencode reads AGENTS.md from ~/.config/opencode (global) or the project
112
112
  // root (local); skills from ~/.config/opencode/skills and .opencode/skills.
@@ -217,7 +217,7 @@ var init_claude = __esm({
217
217
  import { homedir as homedir3 } from "os";
218
218
  import { join as join5 } from "path";
219
219
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
220
- import * as p2 from "@clack/prompts";
220
+ import * as p3 from "@clack/prompts";
221
221
  async function resolveBypassSelection(candidates, opts, interactive) {
222
222
  const supported = candidates.filter((a) => BYPASS_SUPPORTED.includes(a.name));
223
223
  if (!supported.length || opts.noBypass) return [];
@@ -229,7 +229,7 @@ async function resolveBypassSelection(candidates, opts, interactive) {
229
229
  return names.filter((n) => req.includes(n));
230
230
  }
231
231
  if (!interactive) return [];
232
- const r = await p2.multiselect({
232
+ const r = await p3.multiselect({
233
233
  message: "Bypass approval prompts for which agents? (security trade-off: the agent stops asking before acting)",
234
234
  options: supported.map((a) => ({
235
235
  value: a.name,
@@ -239,7 +239,7 @@ async function resolveBypassSelection(candidates, opts, interactive) {
239
239
  initialValues: names.filter((n) => BYPASS_DEFAULT_ON.has(n)),
240
240
  required: false
241
241
  });
242
- if (p2.isCancel(r)) return [];
242
+ if (p3.isCancel(r)) return [];
243
243
  return r;
244
244
  }
245
245
  function applyBypass(agentNames, scope, dryRun) {
@@ -247,9 +247,9 @@ function applyBypass(agentNames, scope, dryRun) {
247
247
  const res = enableFor(name, scope, dryRun);
248
248
  if (!res) continue;
249
249
  const label = AGENTS[name]?.label || name;
250
- if (dryRun) p2.log.info(`Bypass (dry run): would enable for ${label} -> ${res.path}.`);
251
- else if (res.changed) p2.log.warn(`Bypass: enabled for ${label} (${res.path}) - approval prompts are now OFF.`);
252
- else p2.log.info(`Bypass: already enabled for ${label}.`);
250
+ if (dryRun) p3.log.info(`Bypass (dry run): would enable for ${label} -> ${res.path}.`);
251
+ else if (res.changed) p3.log.warn(`Bypass: enabled for ${label} (${res.path}) - approval prompts are now OFF.`);
252
+ else p3.log.info(`Bypass: already enabled for ${label}.`);
253
253
  }
254
254
  }
255
255
  function enableFor(name, scope, dryRun) {
@@ -453,7 +453,7 @@ var init_config = __esm({
453
453
  "use strict";
454
454
  init_util();
455
455
  CONFIG_FILE = ".enigma.json";
456
- CONFIG_DEFAULTS = { commitEmoji: true, updateNotifier: true };
456
+ CONFIG_DEFAULTS = { commitEmoji: true, updateNotifier: true, fullscreen: true };
457
457
  }
458
458
  });
459
459
 
@@ -490,7 +490,8 @@ var init_settings_registry = __esm({
490
490
  blurb: "enigma runtime toggles (.enigma.json)",
491
491
  settings: [
492
492
  enigmaToggle("commit-emoji", "commitEmoji", "Commit subject emoji", "leading gitmoji on commit subjects"),
493
- enigmaToggle("update-notifier", "updateNotifier", "Update notifications", "notify when a newer enigma-cli is published")
493
+ enigmaToggle("update-notifier", "updateNotifier", "Update notifications", "notify when a newer enigma-cli is published"),
494
+ enigmaToggle("fullscreen", "fullscreen", "Full-screen TUI", "clear the screen for a clean TUI view; off renders inline among existing output")
494
495
  ]
495
496
  },
496
497
  {
@@ -530,44 +531,29 @@ __export(settings_exports, {
530
531
  runHomeTui: () => runHomeTui,
531
532
  runSettingsTui: () => runSettingsTui
532
533
  });
533
- async function runHomeTui(runAction) {
534
- await runTui("home", runAction);
534
+ async function runHomeTui(hub) {
535
+ await runTui({ showActions: true, hub });
535
536
  }
536
537
  async function runSettingsTui() {
537
- await runTui("settings");
538
+ await runTui({ showActions: false });
538
539
  }
539
- async function runTui(initialView, runAction) {
540
+ async function runTui(opts) {
540
541
  if (!process.stdout.isTTY) return;
542
+ const fullscreen = readConfig().config.fullscreen;
541
543
  const React = (await import("react")).default;
542
544
  const ink = await import("ink");
543
545
  const { render } = ink;
544
546
  const h = React.createElement;
545
- const restore = () => {
546
- try {
547
- process.stdout.write(LEAVE_ALT);
548
- } catch {
549
- }
550
- };
551
- for (; ; ) {
552
- const state = { leave: "quit" };
553
- const App = buildApp(React, ink, { initialView, onLeave: (a) => {
554
- state.leave = a;
555
- } });
556
- process.stdout.write(ENTER_ALT);
557
- process.on("exit", restore);
558
- try {
559
- const app = render(h(App), { exitOnCtrlC: true });
560
- await app.waitUntilExit();
561
- } finally {
562
- process.removeListener("exit", restore);
563
- restore();
564
- }
565
- if (runAction && (state.leave === "skills" || state.leave === "security")) {
566
- await runAction(state.leave);
567
- continue;
568
- }
569
- break;
547
+ const agents = opts.hub?.agents ?? [];
548
+ const protections = opts.hub?.protections ?? [];
549
+ const runAction = opts.hub?.runAction ?? (async () => ({ ok: false, title: "", lines: [] }));
550
+ if (fullscreen) try {
551
+ process.stdout.write(CLEAR_SCREEN);
552
+ } catch {
570
553
  }
554
+ const App = buildApp(React, ink, { showActions: opts.showActions, fullscreen, agents, protections, runAction });
555
+ const app = render(h(App), { exitOnCtrlC: true });
556
+ await app.waitUntilExit();
571
557
  }
572
558
  function buildApp(React, ink, opts) {
573
559
  const { useApp, useInput, useStdout } = ink;
@@ -575,18 +561,28 @@ function buildApp(React, ink, opts) {
575
561
  const Text = ink.Text;
576
562
  const { useState, useEffect } = React;
577
563
  const h = React.createElement;
578
- const canGoHome = opts.initialView === "home";
564
+ const fill = opts.fullscreen;
565
+ const sideItems = [
566
+ ...CATEGORIES.map((c, i) => ({ kind: "category", catIndex: i, title: c.title })),
567
+ ...opts.showActions ? ACTION_ITEMS.map((a) => ({ kind: "action", ...a })) : []
568
+ ];
569
+ const actionItemKeys = (action) => action === "security" ? opts.protections.map((pr) => pr.value) : opts.agents.map((a) => a.name);
579
570
  return function App() {
580
571
  const { exit } = useApp();
581
572
  const { stdout } = useStdout();
582
573
  const [size, setSize] = useState({ columns: stdout.columns || 80, rows: stdout.rows || 24 });
583
- const [view, setView] = useState(opts.initialView);
584
- const [homeIndex, setHomeIndex] = useState(0);
574
+ const [mode, setMode] = useState("menu");
585
575
  const [scope, setScope] = useState("global");
586
- const [focusSettings, setFocusSettings] = useState(false);
587
- const [catIndex, setCatIndex] = useState(0);
576
+ const [sideIndex, setSideIndex] = useState(0);
577
+ const [focusRight, setFocusRight] = useState(false);
588
578
  const [setIndex, setSetIndex] = useState(0);
589
- const [, redraw] = useState(0);
579
+ const [pending, setPending] = useState({});
580
+ const [confirm4, setConfirm] = useState(null);
581
+ const [actCursor, setActCursor] = useState(0);
582
+ const [actChecked, setActChecked] = useState({});
583
+ const [busyTitle, setBusyTitle] = useState("");
584
+ const [result, setResult] = useState(null);
585
+ const [resultScroll, setResultScroll] = useState(0);
590
586
  useEffect(() => {
591
587
  const onResize = () => setSize({ columns: stdout.columns || 80, rows: stdout.rows || 24 });
592
588
  stdout.on("resize", onResize);
@@ -594,45 +590,112 @@ function buildApp(React, ink, opts) {
594
590
  stdout.off("resize", onResize);
595
591
  };
596
592
  }, [stdout]);
597
- const back = () => {
598
- if (canGoHome) setView("home");
599
- else {
600
- opts.onLeave("quit");
601
- exit();
593
+ const current = sideItems[sideIndex];
594
+ const category = current.kind === "category" ? CATEGORIES[current.catIndex] : null;
595
+ const action = current.kind === "action" ? current.action : null;
596
+ useEffect(() => {
597
+ if (!action) return;
598
+ setActCursor(0);
599
+ if (action === "security") {
600
+ setActChecked(Object.fromEntries(opts.protections.map((pr) => [pr.value, true])));
601
+ } else {
602
+ const detected = opts.agents.filter((a) => a.installed);
603
+ const preselect = detected.length ? detected : opts.agents;
604
+ setActChecked(Object.fromEntries(opts.agents.map((a) => [a.name, preselect.some((d) => d.name === a.name)])));
602
605
  }
606
+ }, [action]);
607
+ const resultRows = fill ? Math.max(3, size.rows - 7) : result?.lines.length ?? 0;
608
+ const maxResultScroll = Math.max(0, (result?.lines.length ?? 0) - resultRows);
609
+ const valueOf = (setting, sc) => {
610
+ const k = stageKey(setting.key, sc);
611
+ return k in pending ? pending[k] : setting.read(sc);
612
+ };
613
+ const isModified = (setting, sc) => {
614
+ const k = stageKey(setting.key, sc);
615
+ return k in pending && pending[k] !== setting.read(sc);
616
+ };
617
+ const dirty = Object.entries(pending).some(([k, v]) => {
618
+ const { key, scope: sc } = parseStageKey(k);
619
+ return SETTING_BY_KEY.get(key)?.read(sc) !== v;
620
+ });
621
+ const persistPending = () => {
622
+ for (const [k, v] of Object.entries(pending)) {
623
+ const { key, scope: sc } = parseStageKey(k);
624
+ const setting = SETTING_BY_KEY.get(key);
625
+ if (setting && setting.read(sc) !== v) setting.write(v, sc);
626
+ }
627
+ };
628
+ const runChosen = (act) => {
629
+ const keys = actionItemKeys(act);
630
+ const chosen = keys.filter((k) => actChecked[k]);
631
+ const req = act === "security" ? { action: act, protections: chosen } : { action: act, scope, agents: chosen };
632
+ persistPending();
633
+ setPending({});
634
+ setBusyTitle(actionTitle(act));
635
+ setResult(null);
636
+ setResultScroll(0);
637
+ setMode("running");
638
+ opts.runAction(req).then((res) => {
639
+ setResult(res);
640
+ setMode("result");
641
+ }).catch((err) => {
642
+ setResult({ ok: false, title: actionTitle(act), lines: [`Error: ${err.message}`] });
643
+ setMode("result");
644
+ });
603
645
  };
604
646
  useInput((input, key) => {
605
- if (view === "home") {
606
- if (input === "q" || key.escape) {
607
- opts.onLeave("quit");
608
- exit();
647
+ if (confirm4) {
648
+ if (key.escape) {
649
+ setConfirm(null);
609
650
  return;
610
651
  }
611
652
  if (key.upArrow || input === "k") {
612
- setHomeIndex((i) => Math.max(0, i - 1));
653
+ setConfirm((c) => c && { index: Math.max(0, c.index - 1) });
613
654
  return;
614
655
  }
615
656
  if (key.downArrow || input === "j") {
616
- setHomeIndex((i) => Math.min(HOME_ITEMS.length - 1, i + 1));
657
+ setConfirm((c) => c && { index: Math.min(EXIT_OPTIONS.length - 1, c.index + 1) });
617
658
  return;
618
659
  }
619
660
  if (key.return || input === " ") {
620
- const item = HOME_ITEMS[homeIndex];
621
- if (item.key === "settings") {
622
- setView("settings");
623
- setFocusSettings(false);
624
- } else if (item.key === "exit") {
625
- opts.onLeave("quit");
626
- exit();
627
- } else {
628
- opts.onLeave(item.key);
629
- exit();
630
- }
661
+ const index = confirm4.index;
662
+ setConfirm(null);
663
+ if (index === 2) return;
664
+ if (index === 0) persistPending();
665
+ setPending({});
666
+ exit();
667
+ }
668
+ return;
669
+ }
670
+ if (mode === "running") return;
671
+ if (mode === "result") {
672
+ if (key.return || key.escape || input === " " || input === "q") {
673
+ setMode("menu");
674
+ return;
675
+ }
676
+ if (key.upArrow || input === "k") {
677
+ setResultScroll((s) => Math.max(0, s - 1));
678
+ return;
679
+ }
680
+ if (key.downArrow || input === "j") {
681
+ setResultScroll((s) => Math.min(maxResultScroll, s + 1));
682
+ return;
631
683
  }
632
684
  return;
633
685
  }
634
686
  if (input === "q" || key.escape) {
635
- back();
687
+ dirty ? setConfirm({ index: 0 }) : exit();
688
+ return;
689
+ }
690
+ if (input === "s") {
691
+ persistPending();
692
+ setPending({});
693
+ return;
694
+ }
695
+ if (input === "x") {
696
+ persistPending();
697
+ setPending({});
698
+ exit();
636
699
  return;
637
700
  }
638
701
  if (input === "g") {
@@ -640,147 +703,632 @@ function buildApp(React, ink, opts) {
640
703
  return;
641
704
  }
642
705
  if (key.tab) {
643
- setFocusSettings((f) => !f);
706
+ setFocusRight((f) => !f);
644
707
  return;
645
708
  }
646
709
  if (key.leftArrow || input === "h") {
647
- setFocusSettings(false);
710
+ setFocusRight(false);
648
711
  return;
649
712
  }
650
713
  if (key.rightArrow || input === "l") {
651
- setFocusSettings(true);
714
+ setFocusRight(true);
652
715
  return;
653
716
  }
654
717
  if (key.upArrow || input === "k") {
655
- if (focusSettings) setSetIndex((i) => Math.max(0, i - 1));
718
+ if (focusRight && category) setSetIndex((i) => Math.max(0, i - 1));
719
+ else if (focusRight && action) setActCursor((i) => Math.max(0, i - 1));
656
720
  else {
657
- setCatIndex((i) => Math.max(0, i - 1));
721
+ setSideIndex((i) => Math.max(0, i - 1));
658
722
  setSetIndex(0);
723
+ setFocusRight(false);
659
724
  }
660
725
  return;
661
726
  }
662
727
  if (key.downArrow || input === "j") {
663
- if (focusSettings) setSetIndex((i) => Math.min(CATEGORIES[catIndex].settings.length - 1, i + 1));
728
+ if (focusRight && category) setSetIndex((i) => Math.min(category.settings.length - 1, i + 1));
729
+ else if (focusRight && action) setActCursor((i) => Math.min(actionItemKeys(action).length - 1, i + 1));
664
730
  else {
665
- setCatIndex((i) => Math.min(CATEGORIES.length - 1, i + 1));
731
+ setSideIndex((i) => Math.min(sideItems.length - 1, i + 1));
666
732
  setSetIndex(0);
733
+ setFocusRight(false);
667
734
  }
668
735
  return;
669
736
  }
737
+ if (input === " " && focusRight && action) {
738
+ const k = actionItemKeys(action)[actCursor];
739
+ setActChecked((c) => ({ ...c, [k]: !c[k] }));
740
+ return;
741
+ }
670
742
  if (key.return || input === " ") {
671
- if (!focusSettings) {
672
- setFocusSettings(true);
743
+ if (!focusRight) {
744
+ setFocusRight(true);
745
+ return;
746
+ }
747
+ if (action) {
748
+ runChosen(action);
673
749
  return;
674
750
  }
675
- const setting = CATEGORIES[catIndex].settings[setIndex];
676
- setting.write(!setting.read(scope), scope);
677
- redraw((n) => n + 1);
751
+ const setting = category.settings[setIndex];
752
+ setPending((pr) => ({ ...pr, [stageKey(setting.key, scope)]: !valueOf(setting, scope) }));
678
753
  }
679
754
  });
680
- const titleBar = (right2) => h(
681
- Box,
682
- { width: size.columns, paddingX: 1, justifyContent: "space-between" },
683
- h(Text, { bold: true, color: "cyan" }, "enigma"),
684
- right2
685
- );
686
- const body = view === "home" ? renderHome(h, Box, Text, homeIndex) : renderSettings(h, Box, Text, { size, scope, focusSettings, catIndex, setIndex });
687
- const footer = h(Box, { width: size.columns, paddingX: 1 }, h(
688
- Text,
689
- { dimColor: true },
690
- view === "home" ? "up/down move enter select q quit" : `up/down move tab/left/right switch enter toggle g scope ${canGoHome ? "esc back" : "q quit"}`
691
- ));
692
- const right = view === "home" ? h(Text, { dimColor: true }, "main menu") : h(
755
+ const headerRight = confirm4 ? h(Text, { color: "yellow" }, "unsaved changes") : mode === "running" ? h(Text, { dimColor: true }, "working") : mode === "result" ? h(Text, { dimColor: true }, "result") : h(
693
756
  Box,
694
757
  {},
695
758
  h(Text, { dimColor: true }, "scope "),
696
759
  h(Text, { bold: true, color: scope === "global" ? "green" : "yellow" }, scope),
697
- h(Text, { dimColor: true }, " (g to change)")
760
+ h(Text, { dimColor: true }, " (g)"),
761
+ dirty ? h(Text, { color: "yellow" }, " * unsaved") : null
698
762
  );
763
+ const titleBar = h(
764
+ Box,
765
+ { width: size.columns, paddingX: 1, justifyContent: "space-between" },
766
+ h(Text, { bold: true, color: "cyan" }, "enigma"),
767
+ headerRight
768
+ );
769
+ let content;
770
+ if (confirm4) {
771
+ content = renderConfirm(h, Box, Text, confirm4.index, fill);
772
+ } else if (mode === "running") {
773
+ content = renderRunning(h, Box, Text, busyTitle, fill);
774
+ } else if (mode === "result" && result) {
775
+ content = renderResult(h, Box, Text, { res: result, scroll: Math.min(resultScroll, maxResultScroll), maxRows: resultRows, fill });
776
+ } else {
777
+ const sidebarWidth = Math.min(28, Math.max(20, Math.floor(size.columns * 0.3)));
778
+ const panel = category ? renderCategoryPanel(h, Box, Text, { category, scope, focusRight, setIndex, valueOf, isModified, fill }) : renderChecklist(h, Box, Text, {
779
+ title: actionTitle(action),
780
+ blurb: action === "skills" ? `Scope ${scope} (g to change). Choose agents, then enter to install.` : "Choose what the commit guard enforces, then enter to apply.",
781
+ items: action === "security" ? opts.protections.map((pr) => ({ key: pr.value, label: pr.label, hint: pr.hint })) : opts.agents.map((a) => ({ key: a.name, label: a.label, hint: a.installed ? "detected" : "not detected" })),
782
+ cursor: actCursor,
783
+ checked: actChecked,
784
+ focused: focusRight,
785
+ fill
786
+ });
787
+ content = h(Box, fill ? { flexGrow: 1 } : {}, renderSidebar(h, Box, Text, sideItems, sideIndex, focusRight, sidebarWidth), panel);
788
+ }
789
+ const menuFooter = focusRight && action ? action === "skills" ? "up/down move space toggle g scope enter install tab back" : "up/down move space toggle enter apply tab back" : focusRight && category ? "up/down move enter toggle g scope tab back" : `up/down move tab switch enter ${action ? "edit" : "focus"} s save x save & exit q quit`;
790
+ const footerText = confirm4 ? "up/down move enter select esc cancel" : mode === "running" ? "working..." : mode === "result" ? `${maxResultScroll > 0 ? "up/down scroll " : ""}enter / esc back to menu` : menuFooter;
791
+ const footer = h(Box, { width: size.columns, paddingX: 1 }, h(Text, { dimColor: true }, footerText));
699
792
  return h(
700
793
  Box,
701
- { width: size.columns, height: size.rows, flexDirection: "column" },
702
- titleBar(right),
703
- h(Box, { flexGrow: 1 }, body),
794
+ { width: size.columns, ...fill ? { height: size.rows } : {}, flexDirection: "column" },
795
+ titleBar,
796
+ content,
704
797
  footer
705
798
  );
706
799
  };
707
800
  }
708
- function renderHome(h, Box, Text, homeIndex) {
709
- const items = HOME_ITEMS.map((item, i) => h(
801
+ function renderSidebar(h, Box, Text, items, index, focusRight, width) {
802
+ return h(Box, {
803
+ flexDirection: "column",
804
+ borderStyle: "round",
805
+ borderColor: focusRight ? "gray" : "cyan",
806
+ paddingX: 1,
807
+ width,
808
+ marginRight: 1
809
+ }, [
810
+ h(Text, { key: "__t", bold: true, dimColor: true }, "MENU"),
811
+ ...items.map((it, i) => h(Text, {
812
+ key: String(i),
813
+ inverse: !focusRight && i === index,
814
+ color: i === index ? "cyan" : void 0
815
+ }, ` ${it.title} `))
816
+ ]);
817
+ }
818
+ function renderConfirm(h, Box, Text, index, fill) {
819
+ return h(
710
820
  Box,
711
- { key: item.key, flexDirection: "column" },
712
- h(Text, { inverse: i === homeIndex, bold: i === homeIndex }, ` ${item.label} `),
713
- i === homeIndex && item.hint ? h(Text, { key: "h", dimColor: true }, ` ${item.hint}`) : null
714
- ));
821
+ { justifyContent: "center", ...fill ? { flexGrow: 1, alignItems: "center" } : {} },
822
+ h(Box, {
823
+ flexDirection: "column",
824
+ borderStyle: "round",
825
+ borderColor: "yellow",
826
+ paddingX: 2,
827
+ paddingY: 1
828
+ }, [
829
+ h(Text, { key: "__t", bold: true, color: "yellow" }, "You have unsaved changes"),
830
+ h(
831
+ Box,
832
+ { key: "__o", marginTop: 1, flexDirection: "column" },
833
+ EXIT_OPTIONS.map((o, i) => h(Text, { key: o, inverse: i === index, bold: i === index }, ` ${o} `))
834
+ )
835
+ ])
836
+ );
837
+ }
838
+ function renderRunning(h, Box, Text, title, fill) {
715
839
  return h(Box, {
716
840
  flexDirection: "column",
717
841
  borderStyle: "round",
718
842
  borderColor: "cyan",
719
843
  paddingX: 1,
720
- flexGrow: 1
844
+ ...fill ? { flexGrow: 1 } : {}
721
845
  }, [
722
- h(Text, { key: "__t", bold: true, color: "cyan" }, "What would you like to do?"),
723
- h(Box, { key: "__sp", marginTop: 1, flexDirection: "column" }, items)
846
+ h(Text, { key: "__t", bold: true, color: "cyan" }, title || "Working"),
847
+ h(Text, { key: "__w", marginTop: 1, dimColor: true }, "Working..."),
848
+ ...fill ? [h(Box, { key: "__grow", flexGrow: 1 })] : []
724
849
  ]);
725
850
  }
726
- function renderSettings(h, Box, Text, s) {
727
- const category = CATEGORIES[s.catIndex];
728
- const settings = category.settings;
729
- const focused = settings[s.setIndex];
730
- const sidebarWidth = Math.min(28, Math.max(18, Math.floor(s.size.columns * 0.3)));
731
- const sidebar = h(Box, {
851
+ function renderResult(h, Box, Text, s) {
852
+ const { res, maxRows } = s;
853
+ const windowed = maxRows > 0 && res.lines.length > maxRows;
854
+ const start = windowed ? Math.max(0, Math.min(s.scroll, res.lines.length - maxRows)) : 0;
855
+ const slice = windowed ? res.lines.slice(start, start + maxRows) : res.lines;
856
+ const rows = slice.length ? slice.map((line, i) => h(Text, { key: String(start + i), wrap: "truncate-end" }, ` ${line} `)) : [h(Text, { key: "__none", dimColor: true }, " (no output) ")];
857
+ const above = windowed && start > 0;
858
+ const below = windowed && start + maxRows < res.lines.length;
859
+ return h(Box, {
732
860
  flexDirection: "column",
733
861
  borderStyle: "round",
734
- borderColor: s.focusSettings ? "gray" : "cyan",
862
+ borderColor: res.ok ? "green" : "red",
735
863
  paddingX: 1,
736
- width: sidebarWidth,
737
- marginRight: 1
864
+ ...s.fill ? { flexGrow: 1 } : {}
738
865
  }, [
739
- h(Text, { key: "__t", bold: true, dimColor: true }, "CATEGORIES"),
740
- ...CATEGORIES.map((c, i) => h(Text, {
741
- key: c.title,
742
- inverse: !s.focusSettings && i === s.catIndex,
743
- color: i === s.catIndex ? "cyan" : void 0
744
- }, ` ${c.title} `))
866
+ h(Text, { key: "__t", bold: true, color: res.ok ? "green" : "red" }, res.title),
867
+ h(Text, { key: "__up", dimColor: true }, above ? ` ... ${start} more above ` : " "),
868
+ h(Box, { key: "__rows", flexDirection: "column" }, rows),
869
+ h(Text, { key: "__dn", dimColor: true }, below ? ` ... ${res.lines.length - start - maxRows} more below ` : " "),
870
+ ...s.fill ? [h(Box, { key: "__grow", flexGrow: 1 })] : []
745
871
  ]);
746
- const rows = settings.map((setting, i) => {
747
- const on = setting.read(s.scope);
748
- const selected = s.focusSettings && i === s.setIndex;
872
+ }
873
+ function renderChecklist(h, Box, Text, s) {
874
+ const rows = s.items.map((it, i) => {
875
+ const on = !!s.checked[it.key];
876
+ const selected = s.focused && i === s.cursor;
877
+ return h(
878
+ Box,
879
+ { key: it.key, justifyContent: "space-between" },
880
+ h(Text, { inverse: selected, bold: selected }, ` ${on ? "[x]" : "[ ]"} ${it.label} `),
881
+ h(Text, { dimColor: true }, `${it.hint} `)
882
+ );
883
+ });
884
+ return h(Box, {
885
+ flexDirection: "column",
886
+ borderStyle: "round",
887
+ borderColor: s.focused ? "cyan" : "gray",
888
+ paddingX: 1,
889
+ flexGrow: 1
890
+ }, [
891
+ h(Text, { key: "__t", bold: true, color: "cyan" }, s.title),
892
+ h(Text, { key: "__bl", dimColor: true }, s.blurb),
893
+ h(Box, { key: "__rows", marginTop: 1, flexDirection: "column" }, rows),
894
+ ...s.fill ? [h(Box, { key: "__grow", flexGrow: 1 })] : []
895
+ ]);
896
+ }
897
+ function renderCategoryPanel(h, Box, Text, s) {
898
+ const focused = s.category.settings[s.setIndex];
899
+ const rows = s.category.settings.map((setting, i) => {
900
+ const on = s.valueOf(setting, s.scope);
901
+ const modified = s.isModified(setting, s.scope);
902
+ const selected = s.focusRight && i === s.setIndex;
749
903
  return h(
750
904
  Box,
751
905
  { key: setting.key, justifyContent: "space-between" },
752
906
  h(Text, { inverse: selected, bold: selected }, ` ${setting.label}${setting.globalOnly ? " (global)" : ""} `),
753
- h(Text, { bold: true, color: on ? "green" : "gray" }, `${valueLabel(on)} `)
907
+ h(Text, { bold: true, color: modified ? "yellow" : on ? "green" : "gray" }, `${valueLabel(on)}${modified ? " *" : ""} `)
754
908
  );
755
909
  });
756
- const panel = h(Box, {
910
+ return h(Box, {
757
911
  flexDirection: "column",
758
912
  borderStyle: "round",
759
- borderColor: s.focusSettings ? "cyan" : "gray",
913
+ borderColor: s.focusRight ? "cyan" : "gray",
760
914
  paddingX: 1,
761
915
  flexGrow: 1
762
916
  }, [
763
- h(Text, { key: "__b", bold: true, color: "cyan" }, category.title),
764
- h(Text, { key: "__bl", dimColor: true }, category.blurb),
917
+ h(Text, { key: "__b", bold: true, color: "cyan" }, s.category.title),
918
+ h(Text, { key: "__bl", dimColor: true }, s.category.blurb),
765
919
  h(Box, { key: "__rows", marginTop: 1, flexDirection: "column" }, rows),
766
- h(Box, { key: "__grow", flexGrow: 1 }),
767
- h(Text, { key: "__hint", dimColor: true, wrap: "truncate-end" }, focused.hint)
920
+ ...s.fill ? [h(Box, { key: "__grow", flexGrow: 1 })] : [],
921
+ h(Text, { key: "__hint", marginTop: 1, dimColor: true, wrap: "truncate-end" }, focused.hint)
768
922
  ]);
769
- return h(Box, { flexGrow: 1 }, sidebar, panel);
770
923
  }
771
- var ENTER_ALT, LEAVE_ALT, HOME_ITEMS;
924
+ var CLEAR_SCREEN, SETTING_BY_KEY, stageKey, parseStageKey, ACTION_ITEMS, actionTitle, EXIT_OPTIONS;
772
925
  var init_settings = __esm({
773
926
  "src/tui/settings.ts"() {
927
+ "use strict";
928
+ init_config();
929
+ init_settings_registry();
930
+ CLEAR_SCREEN = "\x1B[2J\x1B[H";
931
+ SETTING_BY_KEY = new Map(ALL_SETTINGS.map((s) => [s.key, s]));
932
+ stageKey = (key, scope) => `${scope}/${key}`;
933
+ parseStageKey = (composite) => {
934
+ const i = composite.indexOf("/");
935
+ return { scope: composite.slice(0, i), key: composite.slice(i + 1) };
936
+ };
937
+ ACTION_ITEMS = [
938
+ { action: "skills", title: "Install agent skills", blurb: "Claude Code, Codex, OpenCode" },
939
+ { action: "security", title: "Git security hooks", blurb: "block secrets, .env, node_modules on commit" }
940
+ ];
941
+ actionTitle = (action) => ACTION_ITEMS.find((a) => a.action === action).title;
942
+ EXIT_OPTIONS = ["Save & exit", "Exit without saving", "Cancel"];
943
+ }
944
+ });
945
+
946
+ // src/tui/opentui.ts
947
+ var opentui_exports = {};
948
+ __export(opentui_exports, {
949
+ runHomeTui: () => runHomeTui2
950
+ });
951
+ async function runHomeTui2(hub) {
952
+ if (!process.stdout.isTTY) return;
953
+ const React = (await import("react")).default;
954
+ const { createCliRenderer, TextAttributes } = await import("@opentui/core");
955
+ const { createRoot, useKeyboard, useTerminalDimensions } = await import("@opentui/react");
956
+ const h = React.createElement;
957
+ const { useState, useEffect } = React;
958
+ const box = "box";
959
+ const text = "text";
960
+ const BOLD = TextAttributes.BOLD;
961
+ const DIM = TextAttributes.DIM;
962
+ const agents = hub.agents;
963
+ const protections = hub.protections;
964
+ const txt = (content, props = {}) => h(text, props, content);
965
+ const selStyle = (selected, normal = {}) => selected ? { bg: SEL_BG, fg: SEL_FG, attributes: BOLD } : normal;
966
+ const panelBox = (borderColor, children, extra = {}) => h(box, { border: true, borderStyle: "rounded", borderColor, flexDirection: "column", paddingLeft: 1, paddingRight: 1, flexGrow: 1, ...extra }, ...children);
967
+ const renderSidebar2 = (items, index, focusRight, width) => h(
968
+ box,
969
+ { border: true, borderStyle: "rounded", borderColor: focusRight ? COL.gray : COL.cyan, flexDirection: "column", paddingLeft: 1, paddingRight: 1, width, marginRight: 1 },
970
+ txt("MENU", { fg: COL.gray, attributes: BOLD }),
971
+ ...items.map((it, i) => txt(
972
+ ` ${it.title} `,
973
+ !focusRight && i === index ? { bg: SEL_BG, fg: SEL_FG, attributes: BOLD } : { fg: i === index ? COL.cyan : void 0 }
974
+ ))
975
+ );
976
+ const renderChecklist2 = (s) => panelBox(s.focused ? COL.cyan : COL.gray, [
977
+ txt(s.title, { fg: COL.cyan, attributes: BOLD }),
978
+ txt(s.blurb, { fg: COL.gray }),
979
+ h(
980
+ box,
981
+ { flexDirection: "column", marginTop: 1 },
982
+ ...s.items.map((it, i) => {
983
+ const on = !!s.checked[it.key];
984
+ const selected = s.focused && i === s.cursor;
985
+ return h(
986
+ box,
987
+ { flexDirection: "row", justifyContent: "space-between" },
988
+ txt(` ${on ? "[x]" : "[ ]"} ${it.label} `, selStyle(selected)),
989
+ txt(`${it.hint} `, { fg: COL.gray })
990
+ );
991
+ })
992
+ )
993
+ ]);
994
+ const renderCategoryPanel2 = (s) => {
995
+ const focusedHint = s.category.settings[s.setIndex].hint;
996
+ return panelBox(s.focusRight ? COL.cyan : COL.gray, [
997
+ txt(s.category.title, { fg: COL.cyan, attributes: BOLD }),
998
+ txt(s.category.blurb, { fg: COL.gray }),
999
+ h(
1000
+ box,
1001
+ { flexDirection: "column", marginTop: 1 },
1002
+ ...s.category.settings.map((setting, i) => {
1003
+ const on = s.valueOf(setting, s.scope);
1004
+ const modified = s.isModified(setting, s.scope);
1005
+ const selected = s.focusRight && i === s.setIndex;
1006
+ return h(
1007
+ box,
1008
+ { flexDirection: "row", justifyContent: "space-between" },
1009
+ txt(` ${setting.label}${setting.globalOnly ? " (global)" : ""} `, selStyle(selected)),
1010
+ txt(`${valueLabel(on)}${modified ? " *" : ""} `, { attributes: BOLD, fg: modified ? COL.yellow : on ? COL.green : COL.gray })
1011
+ );
1012
+ })
1013
+ ),
1014
+ h(box, { flexGrow: 1 }),
1015
+ txt(focusedHint, { fg: COL.gray, marginTop: 1, truncate: true })
1016
+ ]);
1017
+ };
1018
+ const renderConfirm2 = (index) => h(
1019
+ box,
1020
+ { flexGrow: 1, justifyContent: "center", alignItems: "center" },
1021
+ h(
1022
+ box,
1023
+ { border: true, borderStyle: "rounded", borderColor: COL.yellow, flexDirection: "column", paddingLeft: 2, paddingRight: 2, paddingTop: 1, paddingBottom: 1 },
1024
+ txt("You have unsaved changes", { fg: COL.yellow, attributes: BOLD }),
1025
+ h(
1026
+ box,
1027
+ { flexDirection: "column", marginTop: 1 },
1028
+ ...EXIT_OPTIONS2.map((o, i) => txt(` ${o} `, selStyle(i === index)))
1029
+ )
1030
+ )
1031
+ );
1032
+ const renderRunning2 = (title) => panelBox(COL.cyan, [
1033
+ txt(title || "Working", { fg: COL.cyan, attributes: BOLD }),
1034
+ txt("Working...", { fg: COL.gray, marginTop: 1 }),
1035
+ h(box, { flexGrow: 1 })
1036
+ ]);
1037
+ const renderResult2 = (res, scroll, maxRows) => {
1038
+ const windowed = maxRows > 0 && res.lines.length > maxRows;
1039
+ const start = windowed ? Math.max(0, Math.min(scroll, res.lines.length - maxRows)) : 0;
1040
+ const slice = windowed ? res.lines.slice(start, start + maxRows) : res.lines;
1041
+ const above = windowed && start > 0;
1042
+ const below = windowed && start + maxRows < res.lines.length;
1043
+ const rows = slice.length ? slice.map((line, i) => txt(` ${line} `, { key: String(start + i), truncate: true })) : [txt(" (no output) ", { fg: COL.gray })];
1044
+ return panelBox(res.ok ? COL.green : COL.red, [
1045
+ txt(res.title, { fg: res.ok ? COL.green : COL.red, attributes: BOLD }),
1046
+ txt(above ? ` ... ${start} more above ` : " ", { fg: COL.gray }),
1047
+ h(box, { flexDirection: "column" }, ...rows),
1048
+ txt(below ? ` ... ${res.lines.length - start - maxRows} more below ` : " ", { fg: COL.gray }),
1049
+ h(box, { flexGrow: 1 })
1050
+ ]);
1051
+ };
1052
+ const sideItems = [
1053
+ ...CATEGORIES.map((c, i) => ({ kind: "category", catIndex: i, title: c.title })),
1054
+ ...ACTION_ITEMS2.map((a) => ({ kind: "action", ...a }))
1055
+ ];
1056
+ const actionItemKeys = (action) => action === "security" ? protections.map((p7) => p7.value) : agents.map((a) => a.name);
1057
+ function App({ onExit }) {
1058
+ const dims = useTerminalDimensions();
1059
+ const size = { columns: dims.width || 80, rows: dims.height || 24 };
1060
+ const [mode, setMode] = useState("menu");
1061
+ const [scope, setScope] = useState("global");
1062
+ const [sideIndex, setSideIndex] = useState(0);
1063
+ const [focusRight, setFocusRight] = useState(false);
1064
+ const [setIndex, setSetIndex] = useState(0);
1065
+ const [pending, setPending] = useState({});
1066
+ const [confirm4, setConfirm] = useState(null);
1067
+ const [actCursor, setActCursor] = useState(0);
1068
+ const [actChecked, setActChecked] = useState({});
1069
+ const [busyTitle, setBusyTitle] = useState("");
1070
+ const [result, setResult] = useState(null);
1071
+ const [resultScroll, setResultScroll] = useState(0);
1072
+ const current = sideItems[sideIndex];
1073
+ const category = current.kind === "category" ? CATEGORIES[current.catIndex] : null;
1074
+ const action = current.kind === "action" ? current.action : null;
1075
+ useEffect(() => {
1076
+ if (!action) return;
1077
+ setActCursor(0);
1078
+ if (action === "security") {
1079
+ setActChecked(Object.fromEntries(protections.map((p7) => [p7.value, true])));
1080
+ } else {
1081
+ const detected = agents.filter((a) => a.installed);
1082
+ const preselect = detected.length ? detected : agents;
1083
+ setActChecked(Object.fromEntries(agents.map((a) => [a.name, preselect.some((d) => d.name === a.name)])));
1084
+ }
1085
+ }, [action]);
1086
+ const resultRows = Math.max(3, size.rows - 7);
1087
+ const maxResultScroll = Math.max(0, (result?.lines.length ?? 0) - resultRows);
1088
+ const valueOf = (setting, sc) => {
1089
+ const k = stageKey2(setting.key, sc);
1090
+ return k in pending ? pending[k] : setting.read(sc);
1091
+ };
1092
+ const isModified = (setting, sc) => {
1093
+ const k = stageKey2(setting.key, sc);
1094
+ return k in pending && pending[k] !== setting.read(sc);
1095
+ };
1096
+ const dirty = Object.entries(pending).some(([k, v]) => {
1097
+ const { key, scope: sc } = parseStageKey2(k);
1098
+ return SETTING_BY_KEY2.get(key)?.read(sc) !== v;
1099
+ });
1100
+ const persistPending = () => {
1101
+ for (const [k, v] of Object.entries(pending)) {
1102
+ const { key, scope: sc } = parseStageKey2(k);
1103
+ const setting = SETTING_BY_KEY2.get(key);
1104
+ if (setting && setting.read(sc) !== v) setting.write(v, sc);
1105
+ }
1106
+ };
1107
+ const runChosen = (act) => {
1108
+ const chosen = actionItemKeys(act).filter((k) => actChecked[k]);
1109
+ const req = act === "security" ? { action: act, protections: chosen } : { action: act, scope, agents: chosen };
1110
+ persistPending();
1111
+ setPending({});
1112
+ setBusyTitle(actionTitle2(act));
1113
+ setResult(null);
1114
+ setResultScroll(0);
1115
+ setMode("running");
1116
+ hub.runAction(req).then((res) => {
1117
+ setResult(res);
1118
+ setMode("result");
1119
+ }).catch((err) => {
1120
+ setResult({ ok: false, title: actionTitle2(act), lines: [`Error: ${err.message}`] });
1121
+ setMode("result");
1122
+ });
1123
+ };
1124
+ useKeyboard((key) => {
1125
+ const name = key.name;
1126
+ const up = name === "up", down = name === "down", left = name === "left", right = name === "right";
1127
+ const enter = name === "return", esc = name === "escape", tab = name === "tab", space = name === "space";
1128
+ const ch = name && name.length === 1 ? name : "";
1129
+ if (confirm4) {
1130
+ if (esc) {
1131
+ setConfirm(null);
1132
+ return;
1133
+ }
1134
+ if (up || ch === "k") {
1135
+ setConfirm((c) => c && { index: Math.max(0, c.index - 1) });
1136
+ return;
1137
+ }
1138
+ if (down || ch === "j") {
1139
+ setConfirm((c) => c && { index: Math.min(EXIT_OPTIONS2.length - 1, c.index + 1) });
1140
+ return;
1141
+ }
1142
+ if (enter || space) {
1143
+ const index = confirm4.index;
1144
+ setConfirm(null);
1145
+ if (index === 2) return;
1146
+ if (index === 0) persistPending();
1147
+ setPending({});
1148
+ onExit();
1149
+ }
1150
+ return;
1151
+ }
1152
+ if (mode === "running") return;
1153
+ if (mode === "result") {
1154
+ if (enter || esc || space || ch === "q") {
1155
+ setMode("menu");
1156
+ return;
1157
+ }
1158
+ if (up || ch === "k") {
1159
+ setResultScroll((s) => Math.max(0, s - 1));
1160
+ return;
1161
+ }
1162
+ if (down || ch === "j") {
1163
+ setResultScroll((s) => Math.min(maxResultScroll, s + 1));
1164
+ return;
1165
+ }
1166
+ return;
1167
+ }
1168
+ if (ch === "q" || esc) {
1169
+ dirty ? setConfirm({ index: 0 }) : onExit();
1170
+ return;
1171
+ }
1172
+ if (ch === "s") {
1173
+ persistPending();
1174
+ setPending({});
1175
+ return;
1176
+ }
1177
+ if (ch === "x") {
1178
+ persistPending();
1179
+ setPending({});
1180
+ onExit();
1181
+ return;
1182
+ }
1183
+ if (ch === "g") {
1184
+ setScope((s) => s === "global" ? "local" : "global");
1185
+ return;
1186
+ }
1187
+ if (tab) {
1188
+ setFocusRight((f) => !f);
1189
+ return;
1190
+ }
1191
+ if (left || ch === "h") {
1192
+ setFocusRight(false);
1193
+ return;
1194
+ }
1195
+ if (right || ch === "l") {
1196
+ setFocusRight(true);
1197
+ return;
1198
+ }
1199
+ if (up || ch === "k") {
1200
+ if (focusRight && category) setSetIndex((i) => Math.max(0, i - 1));
1201
+ else if (focusRight && action) setActCursor((i) => Math.max(0, i - 1));
1202
+ else {
1203
+ setSideIndex((i) => Math.max(0, i - 1));
1204
+ setSetIndex(0);
1205
+ setFocusRight(false);
1206
+ }
1207
+ return;
1208
+ }
1209
+ if (down || ch === "j") {
1210
+ if (focusRight && category) setSetIndex((i) => Math.min(category.settings.length - 1, i + 1));
1211
+ else if (focusRight && action) setActCursor((i) => Math.min(actionItemKeys(action).length - 1, i + 1));
1212
+ else {
1213
+ setSideIndex((i) => Math.min(sideItems.length - 1, i + 1));
1214
+ setSetIndex(0);
1215
+ setFocusRight(false);
1216
+ }
1217
+ return;
1218
+ }
1219
+ if (space && focusRight && action) {
1220
+ const k = actionItemKeys(action)[actCursor];
1221
+ setActChecked((c) => ({ ...c, [k]: !c[k] }));
1222
+ return;
1223
+ }
1224
+ if (enter || space) {
1225
+ if (!focusRight) {
1226
+ setFocusRight(true);
1227
+ return;
1228
+ }
1229
+ if (action) {
1230
+ runChosen(action);
1231
+ return;
1232
+ }
1233
+ const setting = category.settings[setIndex];
1234
+ setPending((p7) => ({ ...p7, [stageKey2(setting.key, scope)]: !valueOf(setting, scope) }));
1235
+ }
1236
+ });
1237
+ const headerRight = confirm4 ? txt("unsaved changes", { fg: COL.yellow }) : mode === "running" ? txt("working", { fg: COL.gray }) : mode === "result" ? txt("result", { fg: COL.gray }) : h(
1238
+ box,
1239
+ { flexDirection: "row" },
1240
+ txt("scope ", { fg: COL.gray }),
1241
+ txt(scope, { fg: scope === "global" ? COL.green : COL.yellow, attributes: BOLD }),
1242
+ txt(" (g)", { fg: COL.gray }),
1243
+ dirty ? txt(" * unsaved", { fg: COL.yellow }) : null
1244
+ );
1245
+ const titleBar = h(
1246
+ box,
1247
+ { width: size.columns, flexDirection: "row", paddingLeft: 1, paddingRight: 1, justifyContent: "space-between" },
1248
+ txt("enigma", { fg: COL.cyan, attributes: BOLD }),
1249
+ headerRight
1250
+ );
1251
+ let content;
1252
+ if (confirm4) {
1253
+ content = renderConfirm2(confirm4.index);
1254
+ } else if (mode === "running") {
1255
+ content = renderRunning2(busyTitle);
1256
+ } else if (mode === "result" && result) {
1257
+ content = renderResult2(result, Math.min(resultScroll, maxResultScroll), resultRows);
1258
+ } else {
1259
+ const sidebarWidth = Math.min(28, Math.max(20, Math.floor(size.columns * 0.3)));
1260
+ const panel = category ? renderCategoryPanel2({ category, scope, focusRight, setIndex, valueOf, isModified }) : renderChecklist2({
1261
+ title: actionTitle2(action),
1262
+ blurb: action === "skills" ? `Scope ${scope} (g to change). Choose agents, then enter to install.` : "Choose what the commit guard enforces, then enter to apply.",
1263
+ items: action === "security" ? protections.map((p7) => ({ key: p7.value, label: p7.label, hint: p7.hint })) : agents.map((a) => ({ key: a.name, label: a.label, hint: a.installed ? "detected" : "not detected" })),
1264
+ cursor: actCursor,
1265
+ checked: actChecked,
1266
+ focused: focusRight
1267
+ });
1268
+ content = h(box, { flexGrow: 1, flexDirection: "row" }, renderSidebar2(sideItems, sideIndex, focusRight, sidebarWidth), panel);
1269
+ }
1270
+ const footerLine = (s) => h(box, { width: size.columns, paddingLeft: 1, paddingRight: 1 }, txt(s, { fg: COL.gray, attributes: DIM }));
1271
+ const menuNav = focusRight && action ? action === "skills" ? "up/down move space toggle g scope enter install tab back" : "up/down move space toggle enter apply tab back" : focusRight && category ? "up/down move enter toggle g scope tab back" : `up/down move tab switch enter ${action ? "edit" : "focus"}`;
1272
+ let footer;
1273
+ if (confirm4) {
1274
+ footer = footerLine("up/down move enter select esc cancel");
1275
+ } else if (mode === "running") {
1276
+ footer = footerLine("working...");
1277
+ } else if (mode === "result") {
1278
+ footer = footerLine(`${maxResultScroll > 0 ? "up/down scroll " : ""}enter / esc back to menu`);
1279
+ } else {
1280
+ footer = h(
1281
+ box,
1282
+ { width: size.columns, flexDirection: "row", justifyContent: "space-between", paddingLeft: 1, paddingRight: 1 },
1283
+ txt(menuNav, { fg: COL.gray, attributes: DIM }),
1284
+ txt("s save x save & exit q quit", { fg: COL.gray, attributes: DIM })
1285
+ );
1286
+ }
1287
+ return h(box, { width: size.columns, height: size.rows, flexDirection: "column" }, titleBar, content, footer);
1288
+ }
1289
+ const renderer = await createCliRenderer({ exitOnCtrlC: true });
1290
+ await new Promise((resolve3) => {
1291
+ const root = createRoot(renderer);
1292
+ const onExit = () => {
1293
+ try {
1294
+ root.unmount();
1295
+ } catch {
1296
+ }
1297
+ try {
1298
+ renderer.destroy();
1299
+ } catch {
1300
+ }
1301
+ resolve3();
1302
+ };
1303
+ root.render(h(App, { onExit }));
1304
+ });
1305
+ }
1306
+ var COL, SEL_BG, SEL_FG, SETTING_BY_KEY2, stageKey2, parseStageKey2, ACTION_ITEMS2, actionTitle2, EXIT_OPTIONS2;
1307
+ var init_opentui = __esm({
1308
+ "src/tui/opentui.ts"() {
774
1309
  "use strict";
775
1310
  init_settings_registry();
776
- ENTER_ALT = "\x1B[?1049h\x1B[?25l";
777
- LEAVE_ALT = "\x1B[?25h\x1B[?1049l";
778
- HOME_ITEMS = [
779
- { key: "settings", label: "Configure settings", hint: "emoji, attribution, permission bypass, ..." },
780
- { key: "skills", label: "Install agent skills", hint: "Claude Code, Codex, opencode" },
781
- { key: "security", label: "Git security hooks", hint: "block secrets, .env, node_modules on commit" },
782
- { key: "exit", label: "Exit", hint: "leave enigma" }
1311
+ COL = {
1312
+ cyan: "#22d3ee",
1313
+ green: "#22c55e",
1314
+ yellow: "#eab308",
1315
+ red: "#ef4444",
1316
+ gray: "#6b7280"
1317
+ };
1318
+ SEL_BG = "#155e75";
1319
+ SEL_FG = "#ffffff";
1320
+ SETTING_BY_KEY2 = new Map(ALL_SETTINGS.map((s) => [s.key, s]));
1321
+ stageKey2 = (key, scope) => `${scope}/${key}`;
1322
+ parseStageKey2 = (composite) => {
1323
+ const i = composite.indexOf("/");
1324
+ return { scope: composite.slice(0, i), key: composite.slice(i + 1) };
1325
+ };
1326
+ ACTION_ITEMS2 = [
1327
+ { action: "skills", title: "Install agent skills", blurb: "Claude Code, Codex, OpenCode" },
1328
+ { action: "security", title: "Git security hooks", blurb: "block secrets, .env, node_modules on commit" }
783
1329
  ];
1330
+ actionTitle2 = (action) => ACTION_ITEMS2.find((a) => a.action === action).title;
1331
+ EXIT_OPTIONS2 = ["Save & exit", "Exit without saving", "Cancel"];
784
1332
  }
785
1333
  });
786
1334
 
@@ -788,7 +1336,52 @@ var init_settings = __esm({
788
1336
  init_util();
789
1337
  import { dirname as dirname4, join as join10 } from "path";
790
1338
  import { fileURLToPath as fileURLToPath4 } from "url";
791
- import * as p5 from "@clack/prompts";
1339
+ import * as p6 from "@clack/prompts";
1340
+
1341
+ // src/runtime.ts
1342
+ var isBun = () => typeof process.versions.bun === "string";
1343
+
1344
+ // src/reporter.ts
1345
+ import * as p from "@clack/prompts";
1346
+ function clackReporter() {
1347
+ return {
1348
+ info: (m) => p.log.info(m),
1349
+ success: (m) => p.log.success(m),
1350
+ warn: (m) => p.log.warn(m),
1351
+ error: (m) => p.log.error(m),
1352
+ note: (body, title) => p.note(body, title),
1353
+ spinner: () => {
1354
+ const s = p.spinner();
1355
+ return { start: (m) => s.start(m), stop: (m) => s.stop(m) };
1356
+ },
1357
+ fatal: (m) => {
1358
+ p.cancel(m);
1359
+ process.exit(1);
1360
+ }
1361
+ };
1362
+ }
1363
+ function collectReporter() {
1364
+ const lines = [];
1365
+ const push = (prefix, message) => {
1366
+ for (const line of message.split("\n")) lines.push(prefix ? `${prefix} ${line}` : line);
1367
+ };
1368
+ return {
1369
+ lines,
1370
+ info: (m) => push("", m),
1371
+ success: (m) => push("", m),
1372
+ warn: (m) => push("!", m),
1373
+ error: (m) => push("x", m),
1374
+ note: (body, title) => {
1375
+ if (title) lines.push(title);
1376
+ push(" ", body);
1377
+ },
1378
+ spinner: () => ({ start: () => {
1379
+ }, stop: (m) => push("", m) }),
1380
+ fatal: (m) => {
1381
+ throw new Error(m);
1382
+ }
1383
+ };
1384
+ }
792
1385
 
793
1386
  // src/skills.ts
794
1387
  init_util();
@@ -797,7 +1390,7 @@ import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3,
797
1390
  import { dirname as dirname2, join as join6, resolve as resolve2, relative as relative2, sep as sep2 } from "path";
798
1391
  import { fileURLToPath as fileURLToPath2 } from "url";
799
1392
  import { createHash } from "crypto";
800
- import * as p3 from "@clack/prompts";
1393
+ import * as p4 from "@clack/prompts";
801
1394
 
802
1395
  // src/security.ts
803
1396
  init_util();
@@ -805,7 +1398,7 @@ import { existsSync as existsSync3, mkdirSync, cpSync, writeFileSync, chmodSync
805
1398
  import { dirname, join as join3, resolve, relative } from "path";
806
1399
  import { fileURLToPath } from "url";
807
1400
  import { execFileSync as execFileSync2 } from "child_process";
808
- import * as p from "@clack/prompts";
1401
+ import * as p2 from "@clack/prompts";
809
1402
  var __dirname = dirname(fileURLToPath(import.meta.url));
810
1403
  function findGuardSrc() {
811
1404
  const candidates = [
@@ -838,41 +1431,41 @@ function currentHooksPath(root) {
838
1431
  return "";
839
1432
  }
840
1433
  }
841
- async function setupGitHooks(opts, interactive) {
1434
+ async function setupGitHooks(opts, interactive, reporter = clackReporter()) {
842
1435
  const root = findGitRoot(process.cwd());
843
1436
  if (!root) {
844
- p.log.error("Not inside a git repository (no .git found). Run this from your project root.");
1437
+ reporter.error("Not inside a git repository (no .git found). Run this from your project root.");
845
1438
  return false;
846
1439
  }
847
1440
  const guardSrc = findGuardSrc();
848
1441
  if (!guardSrc) {
849
- p.log.error("Cannot find the built guard (dist/guard.js). Run 'npm run build' first.");
1442
+ reporter.error("Cannot find the built guard (dist/guard.js). Run 'npm run build' first.");
850
1443
  return false;
851
1444
  }
852
1445
  const current = currentHooksPath(root);
853
1446
  if (current && current !== ".githooks" && !opts.force) {
854
- p.log.warn(`core.hooksPath is already set to '${current}'.`);
1447
+ reporter.warn(`core.hooksPath is already set to '${current}'.`);
855
1448
  if (interactive) {
856
- const ok = await p.confirm({ message: `Override existing core.hooksPath '${current}' with '.githooks'?` });
857
- if (p.isCancel(ok) || !ok) {
858
- p.log.info("Left git hooks unchanged.");
1449
+ const ok = await p2.confirm({ message: `Override existing core.hooksPath '${current}' with '.githooks'?` });
1450
+ if (p2.isCancel(ok) || !ok) {
1451
+ reporter.info("Left git hooks unchanged.");
859
1452
  return false;
860
1453
  }
861
1454
  } else {
862
- p.log.info("Re-run with --force to override.");
1455
+ reporter.info("Re-run with --force to override.");
863
1456
  return false;
864
1457
  }
865
1458
  }
866
1459
  let enabled = opts.protections;
867
1460
  if (!enabled && interactive) {
868
- const r = await p.multiselect({
1461
+ const r = await p2.multiselect({
869
1462
  message: "Which protections should the commit guard enforce?",
870
1463
  options: GUARD_PROTECTIONS,
871
1464
  initialValues: GUARD_PROTECTIONS.map((o) => o.value),
872
1465
  required: true
873
1466
  });
874
- if (p.isCancel(r)) {
875
- p.log.info("Left git hooks unchanged.");
1467
+ if (p2.isCancel(r)) {
1468
+ reporter.info("Left git hooks unchanged.");
876
1469
  return false;
877
1470
  }
878
1471
  enabled = r;
@@ -903,14 +1496,14 @@ async function setupGitHooks(opts, interactive) {
903
1496
  try {
904
1497
  execFileSync2("git", ["-C", root, "config", "core.hooksPath", ".githooks"]);
905
1498
  } catch (err) {
906
- p.log.error(`Failed to set core.hooksPath: ${err.message}`);
1499
+ reporter.error(`Failed to set core.hooksPath: ${err.message}`);
907
1500
  return false;
908
1501
  }
909
1502
  const on = Object.entries(config).filter(([, v]) => v).map(([k]) => k);
910
- p.log.success(`Git security hooks installed in ${relative(process.cwd(), hooksDir) || ".githooks"} (core.hooksPath set).`);
911
- p.log.info(`Enforcing: ${on.join(", ") || "nothing"}. Commit .githooks/ so your team inherits it.`);
1503
+ reporter.success(`Git security hooks installed in ${relative(process.cwd(), hooksDir) || ".githooks"} (core.hooksPath set).`);
1504
+ reporter.info(`Enforcing: ${on.join(", ") || "nothing"}. Commit .githooks/ so your team inherits it.`);
912
1505
  if (isOnPath("gh")) {
913
- p.log.info("GitHub CLI (gh) detected: these hooks also run for commits made via gh, since gh uses git underneath.");
1506
+ reporter.info("GitHub CLI (gh) detected: these hooks also run for commits made via gh, since gh uses git underneath.");
914
1507
  }
915
1508
  return true;
916
1509
  }
@@ -919,8 +1512,8 @@ async function maybeOfferGitHooks(interactive, opts) {
919
1512
  const root = findGitRoot(process.cwd());
920
1513
  if (!root) return;
921
1514
  if (currentHooksPath(root) === ".githooks") return;
922
- const ok = await p.confirm({ message: "Set up git security hooks here too (block secrets, .env, node_modules)?" });
923
- if (!p.isCancel(ok) && ok) await setupGitHooks({ ...opts, protections: void 0 }, interactive);
1515
+ const ok = await p2.confirm({ message: "Set up git security hooks here too (block secrets, .env, node_modules)?" });
1516
+ if (!p2.isCancel(ok) && ok) await setupGitHooks({ ...opts, protections: void 0 }, interactive);
924
1517
  }
925
1518
 
926
1519
  // src/skills.ts
@@ -1081,25 +1674,22 @@ function checkSources() {
1081
1674
  }
1082
1675
  console.log(`Integrity check passed: ${checked} skill(s) well-formed and sealed.`);
1083
1676
  }
1084
- async function installSkills(opts, interactive) {
1677
+ async function installSkills(opts, interactive, reporter = clackReporter()) {
1085
1678
  const available = discoverAgents();
1086
- if (available.length === 0) {
1087
- p3.cancel("No installable agents known.");
1088
- process.exit(1);
1089
- }
1679
+ if (available.length === 0) reporter.fatal("No installable agents known.");
1090
1680
  let scope;
1091
1681
  if (opts.scope) {
1092
1682
  scope = opts.scope;
1093
1683
  } else if (interactive) {
1094
- const r = await p3.select({
1684
+ const r = await p4.select({
1095
1685
  message: "Where should skills be installed?",
1096
1686
  options: [
1097
1687
  { value: "global", label: "Global (user)", hint: "~/.claude, ~/.codex, ~/.config/opencode" },
1098
1688
  { value: "local", label: "Local (this project)", hint: process.cwd() }
1099
1689
  ]
1100
1690
  });
1101
- if (p3.isCancel(r)) {
1102
- p3.cancel("Aborted.");
1691
+ if (p4.isCancel(r)) {
1692
+ p4.cancel("Aborted.");
1103
1693
  return;
1104
1694
  }
1105
1695
  scope = r;
@@ -1111,19 +1701,19 @@ async function installSkills(opts, interactive) {
1111
1701
  if (opts.agents.length) {
1112
1702
  chosenAgents = available.filter((a) => opts.agents.includes(a.name));
1113
1703
  const unknown = opts.agents.filter((n) => !available.some((a) => a.name === n));
1114
- if (unknown.length) p3.log.warn(`Skipping unknown/absent agents: ${unknown.join(", ")}`);
1704
+ if (unknown.length) reporter.warn(`Skipping unknown/absent agents: ${unknown.join(", ")}`);
1115
1705
  } else if (opts.allAgents) {
1116
1706
  chosenAgents = available;
1117
1707
  } else if (interactive && available.length > 1) {
1118
1708
  const preselect = (detected.length ? detected : available).map((a) => a.name);
1119
- const r = await p3.multiselect({
1709
+ const r = await p4.multiselect({
1120
1710
  message: "Which agents? (detected on this system are preselected)",
1121
1711
  options: available.map((a) => ({ value: a.name, label: a.label, hint: a.installed ? "detected" : "not detected" })),
1122
1712
  initialValues: preselect,
1123
1713
  required: true
1124
1714
  });
1125
- if (p3.isCancel(r)) {
1126
- p3.cancel("Aborted.");
1715
+ if (p4.isCancel(r)) {
1716
+ p4.cancel("Aborted.");
1127
1717
  return;
1128
1718
  }
1129
1719
  chosenAgents = available.filter((a) => r.includes(a.name));
@@ -1131,17 +1721,14 @@ async function installSkills(opts, interactive) {
1131
1721
  chosenAgents = detected;
1132
1722
  } else {
1133
1723
  chosenAgents = available;
1134
- p3.log.warn("No installed agents detected; defaulting to all supported agents.");
1135
- }
1136
- if (chosenAgents.length === 0) {
1137
- p3.cancel("No matching agents selected.");
1138
- process.exit(1);
1724
+ reporter.warn("No installed agents detected; defaulting to all supported agents.");
1139
1725
  }
1726
+ if (chosenAgents.length === 0) reporter.fatal("No matching agents selected.");
1140
1727
  const claudeScope = chosenAgents.some((a) => a.name === "claude") ? scope : null;
1141
1728
  const applyClaudeConfig = () => {
1142
1729
  if (!claudeScope || opts.dryRun) return;
1143
1730
  if (disableClaudeAttribution(claudeScope)) {
1144
- p3.log.info("Claude Code: disabled Co-Authored-By and PR attribution in settings.json.");
1731
+ reporter.info("Claude Code: disabled Co-Authored-By and PR attribution in settings.json.");
1145
1732
  }
1146
1733
  };
1147
1734
  const bypassAgents = await resolveBypassSelection(chosenAgents, opts, interactive);
@@ -1150,7 +1737,7 @@ async function installSkills(opts, interactive) {
1150
1737
  for (const agent of chosenAgents) {
1151
1738
  const target = agent.targets[scope];
1152
1739
  if (!target) {
1153
- p3.log.warn(`${agent.label} has no '${scope}' target - skipping.`);
1740
+ reporter.warn(`${agent.label} has no '${scope}' target - skipping.`);
1154
1741
  continue;
1155
1742
  }
1156
1743
  const skills = inspectSkills();
@@ -1159,7 +1746,7 @@ async function installSkills(opts, interactive) {
1159
1746
  if (!opts.memoryOnly && opts.skills.length) {
1160
1747
  chosenSkills = skills.filter((s2) => opts.skills.includes(s2.name));
1161
1748
  } else if (!opts.memoryOnly && interactive && skills.length > 1) {
1162
- const r = await p3.multiselect({
1749
+ const r = await p4.multiselect({
1163
1750
  message: `Skills for ${agent.label} - all selected; deselect any you don't want`,
1164
1751
  options: skills.map((s2) => {
1165
1752
  const st = skillStatus(join6(target.skills, s2.name), s2.meta);
@@ -1169,8 +1756,8 @@ async function installSkills(opts, interactive) {
1169
1756
  initialValues: skills.map((s2) => s2.name),
1170
1757
  required: false
1171
1758
  });
1172
- if (p3.isCancel(r)) {
1173
- p3.cancel("Aborted.");
1759
+ if (p4.isCancel(r)) {
1760
+ p4.cancel("Aborted.");
1174
1761
  return;
1175
1762
  }
1176
1763
  chosenSkills = skills.filter((s2) => r.includes(s2.name));
@@ -1187,16 +1774,16 @@ async function installSkills(opts, interactive) {
1187
1774
  if (tampered.length) {
1188
1775
  if (opts.keepModified) {
1189
1776
  for (const s2 of tampered) s2.overwrite = false;
1190
- p3.log.warn(`${tampered.length} locally-modified skill(s) will be kept (--keep-modified).`);
1777
+ reporter.warn(`${tampered.length} locally-modified skill(s) will be kept (--keep-modified).`);
1191
1778
  } else if (interactive && !opts.dryRun) {
1192
- const sel = await p3.multiselect({
1779
+ const sel = await p4.multiselect({
1193
1780
  message: `${tampered.length} skill(s) were modified locally since install. Select which to OVERWRITE`,
1194
1781
  options: tampered.map((s2, i) => ({ value: i, label: s2.name, hint: s2.meta.version ? `v${s2.meta.version}` : "modified" })),
1195
1782
  initialValues: tampered.map((_, i) => i),
1196
1783
  required: false
1197
1784
  });
1198
- if (p3.isCancel(sel)) {
1199
- p3.cancel("Aborted.");
1785
+ if (p4.isCancel(sel)) {
1786
+ p4.cancel("Aborted.");
1200
1787
  return;
1201
1788
  }
1202
1789
  tampered.forEach((s2, i) => {
@@ -1250,14 +1837,14 @@ async function installSkills(opts, interactive) {
1250
1837
  }
1251
1838
  }
1252
1839
  if (nInstall + nUpdate + nRemove === 0) {
1253
- p3.note(lines.join("\n"), "Nothing to do");
1840
+ reporter.note(lines.join("\n"), "Nothing to do");
1254
1841
  applyClaudeConfig();
1255
1842
  applyBypassConfig();
1256
1843
  await maybeOfferGitHooks(interactive, opts);
1257
- p3.log.success(`Everything up-to-date - ${nSkip} item(s) unchanged${nKept ? `, ${nKept} kept modified` : ""} (${scope}).`);
1844
+ reporter.success(`Everything up-to-date - ${nSkip} item(s) unchanged${nKept ? `, ${nKept} kept modified` : ""} (${scope}).`);
1258
1845
  return;
1259
1846
  }
1260
- p3.note(lines.join("\n"), opts.dryRun ? "Dry run - planned changes" : "Planned changes");
1847
+ reporter.note(lines.join("\n"), opts.dryRun ? "Dry run - planned changes" : "Planned changes");
1261
1848
  if (interactive && !opts.dryRun) {
1262
1849
  const summary = [
1263
1850
  nInstall && `${nInstall} to install`,
@@ -1265,21 +1852,21 @@ async function installSkills(opts, interactive) {
1265
1852
  nRemove && `${nRemove} to remove`,
1266
1853
  nSkip && `${nSkip} unchanged`
1267
1854
  ].filter(Boolean).join(", ");
1268
- const ok = await p3.confirm({ message: `Apply: ${summary}?` });
1269
- if (p3.isCancel(ok) || !ok) {
1270
- p3.cancel("Aborted.");
1855
+ const ok = await p4.confirm({ message: `Apply: ${summary}?` });
1856
+ if (p4.isCancel(ok) || !ok) {
1857
+ p4.cancel("Aborted.");
1271
1858
  return;
1272
1859
  }
1273
1860
  }
1274
1861
  if (opts.dryRun) {
1275
1862
  applyBypassConfig();
1276
- p3.log.info("Dry run complete - no files written.");
1863
+ reporter.info("Dry run complete - no files written.");
1277
1864
  return;
1278
1865
  }
1279
1866
  const changedAgents = plan.filter(
1280
1867
  (x) => x.skills.some(willCopy) || x.memory.some((m) => memoryStatus(m.src, join6(x.target.memory, m.name)) !== "identical") || x.prune.length > 0
1281
1868
  );
1282
- const s = p3.spinner();
1869
+ const s = reporter.spinner();
1283
1870
  s.start("Installing...");
1284
1871
  let copied = 0;
1285
1872
  try {
@@ -1300,26 +1887,28 @@ async function installSkills(opts, interactive) {
1300
1887
  }
1301
1888
  } catch (err) {
1302
1889
  s.stop("Failed.");
1303
- p3.cancel(`Error while installing: ${err.message}`);
1304
- process.exit(1);
1890
+ reporter.fatal(`Error while installing: ${err.message}`);
1305
1891
  }
1306
1892
  s.stop(`Wrote ${copied} item(s)${nRemove ? `, removed ${nRemove}` : ""}.`);
1307
1893
  applyClaudeConfig();
1308
1894
  applyBypassConfig();
1309
1895
  await maybeOfferGitHooks(interactive, opts);
1310
- p3.log.success(`${nInstall} installed, ${nUpdate} updated/overwritten` + (nRemove ? `, ${nRemove} removed` : "") + (nSkip ? `, ${nSkip} unchanged` : "") + (nKept ? `, ${nKept} kept modified` : "") + ` (${scope}).`);
1896
+ reporter.success(`${nInstall} installed, ${nUpdate} updated/overwritten` + (nRemove ? `, ${nRemove} removed` : "") + (nSkip ? `, ${nSkip} unchanged` : "") + (nKept ? `, ${nKept} kept modified` : "") + ` (${scope}).`);
1311
1897
  if (changedAgents.length) {
1312
1898
  const { known, running } = runningStatus(changedAgents.map((x) => x.agent));
1313
1899
  if (running.size) {
1314
1900
  const names = changedAgents.filter((x) => running.has(x.agent.name)).map((x) => x.agent.label);
1315
- p3.log.warn(`Restart ${names.join(", ")} to apply the changes (running now).`);
1901
+ reporter.warn(`Restart ${names.join(", ")} to apply the changes (running now).`);
1316
1902
  } else if (!known) {
1317
1903
  const names = changedAgents.map((x) => x.agent.label);
1318
- p3.log.info(`If any of these agents are running, restart them to apply the changes: ${names.join(", ")}.`);
1904
+ reporter.info(`If any of these agents are running, restart them to apply the changes: ${names.join(", ")}.`);
1319
1905
  }
1320
1906
  }
1321
1907
  }
1322
1908
 
1909
+ // src/cli.ts
1910
+ init_agents();
1911
+
1323
1912
  // src/guard.ts
1324
1913
  import { readFileSync as readFileSync4, statSync as statSync2 } from "fs";
1325
1914
  import { execFileSync as execFileSync3 } from "child_process";
@@ -1521,7 +2110,7 @@ import { homedir as homedir5 } from "os";
1521
2110
  import { join as join9 } from "path";
1522
2111
  import { writeFileSync as writeFileSync6 } from "fs";
1523
2112
  import { spawn, spawnSync } from "child_process";
1524
- import * as p4 from "@clack/prompts";
2113
+ import * as p5 from "@clack/prompts";
1525
2114
  var REGISTRY_URL = "https://registry.npmjs.org/enigma-cli/latest";
1526
2115
  var UPDATE_COMMAND = "npm i -g enigma-cli@latest";
1527
2116
  var CACHE_FILE = join9(homedir5(), ".enigma-update-check.json");
@@ -1606,8 +2195,8 @@ async function notifyUpdate(current, interactive) {
1606
2195
  ${renderUpdateBox(current, latest)}
1607
2196
  `);
1608
2197
  if (!interactive) return;
1609
- const ok = await p4.confirm({ message: `Update now with ${UPDATE_COMMAND}?`, initialValue: true });
1610
- if (p4.isCancel(ok) || !ok) return;
2198
+ const ok = await p5.confirm({ message: `Update now with ${UPDATE_COMMAND}?`, initialValue: true });
2199
+ if (p5.isCancel(ok) || !ok) return;
1611
2200
  runUpdate();
1612
2201
  } catch {
1613
2202
  }
@@ -1725,7 +2314,7 @@ Usage:
1725
2314
 
1726
2315
  Commands:
1727
2316
  (none) Interactive hub: configure settings or set up features
1728
- install Install/update agent skills (Claude Code, Codex, opencode)
2317
+ install Install/update agent skills (Claude Code, Codex, OpenCode)
1729
2318
  security Set up git security hooks in the current repo
1730
2319
  guard [--all] Run the commit guard (staged files, or --all for every tracked file)
1731
2320
  config [key val] Configure settings: no args opens the interactive menu;
@@ -1734,7 +2323,7 @@ Commands:
1734
2323
  check Integrity gate: verify skills are well-formed and sealed
1735
2324
  help, version
1736
2325
 
1737
- Config keys: commit-emoji, update-notifier, claude-attribution,
2326
+ Config keys: commit-emoji, update-notifier, fullscreen, claude-attribution,
1738
2327
  bypass-claude, bypass-codex, bypass-opencode
1739
2328
 
1740
2329
  Install options:
@@ -1788,16 +2377,16 @@ async function run(argv) {
1788
2377
  process.exit(await runConfigCli(opts.positionals, opts.scope, interactive));
1789
2378
  }
1790
2379
  if (opts.command === "install") {
1791
- p5.intro("enigma - install agent skills");
2380
+ p6.intro("enigma - install agent skills");
1792
2381
  await installSkills(opts, interactive);
1793
- p5.outro("Done.");
2382
+ p6.outro("Done.");
1794
2383
  await notifyUpdate(version, interactive);
1795
2384
  return;
1796
2385
  }
1797
2386
  if (opts.command === "security") {
1798
- p5.intro("enigma - git security hooks");
2387
+ p6.intro("enigma - git security hooks");
1799
2388
  const done = await setupGitHooks(opts, interactive);
1800
- p5.outro(done ? "Git hooks configured." : "No changes made.");
2389
+ p6.outro(done ? "Git hooks configured." : "No changes made.");
1801
2390
  await notifyUpdate(version, interactive);
1802
2391
  return;
1803
2392
  }
@@ -1806,16 +2395,24 @@ async function run(argv) {
1806
2395
  await notifyUpdate(version, interactive);
1807
2396
  return;
1808
2397
  }
1809
- const { runHomeTui: runHomeTui2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
1810
- await runHomeTui2(async (action) => {
1811
- if (action === "skills") {
1812
- p5.intro("enigma - install agent skills");
1813
- await installSkills(opts, interactive);
1814
- p5.outro("Done.");
1815
- } else if (action === "security") {
1816
- p5.intro("enigma - git security hooks");
1817
- const done = await setupGitHooks(opts, interactive);
1818
- p5.outro(done ? "Git hooks configured." : "No changes made.");
2398
+ const { runHomeTui: runHomeTui3 } = isBun() ? await Promise.resolve().then(() => (init_opentui(), opentui_exports)) : await Promise.resolve().then(() => (init_settings(), settings_exports));
2399
+ await runHomeTui3({
2400
+ agents: discoverAgents().map((a) => ({ name: a.name, label: a.label, installed: a.installed })),
2401
+ protections: GUARD_PROTECTIONS,
2402
+ runAction: async (req) => {
2403
+ const reporter = collectReporter();
2404
+ const title = req.action === "skills" ? "Install agent skills" : "Git security hooks";
2405
+ try {
2406
+ if (req.action === "skills") {
2407
+ await installSkills({ ...opts, scope: req.scope ?? opts.scope, agents: req.agents ?? [], allAgents: !(req.agents && req.agents.length) }, false, reporter);
2408
+ return { ok: true, title, lines: reporter.lines };
2409
+ }
2410
+ const done = await setupGitHooks({ ...opts, protections: req.protections, force: true }, false, reporter);
2411
+ return { ok: done, title, lines: reporter.lines };
2412
+ } catch (err) {
2413
+ reporter.error(`Error: ${err.message}`);
2414
+ return { ok: false, title, lines: reporter.lines };
2415
+ }
1819
2416
  }
1820
2417
  });
1821
2418
  await notifyUpdate(version, interactive);