enigma-cli 1.1.3 → 1.2.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.
@@ -3,6 +3,6 @@
3
3
  "version": "1.0.0",
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Backend/API architecture: controller-service-repository layering, API and request optimization, server-side caching (Redis), and Zod boundary validation.",
6
- "cliVersion": "1.1.3",
6
+ "cliVersion": "1.2.0",
7
7
  "sha": "c442bc9e39a7710cb709ef2abb8d15ecd8aa16ed4f5c8af92b7af6877401cba4"
8
8
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: ciphera-style-policy
3
- description: Ciphera code style conventions - mandatory formatting and language idioms for source code (TypeScript-first, applies to every language): American-English naming, double quotes, string interpolation, length-sorted imports, 4-space indentation, comment/JSDoc format, compact single-line blocks, and code-level anti-patterns (barrel files, external CDN/hosting dependencies). Use whenever writing, refactoring, or reviewing source code.
3
+ description: Ciphera code style conventions - mandatory formatting and language idioms for source code (TypeScript-first, applies to every language) - American-English naming, double quotes, string interpolation, length-sorted imports, 4-space indentation, comment/JSDoc format, compact single-line blocks, and code-level anti-patterns (barrel files, external CDN/hosting dependencies). Use whenever writing, refactoring, or reviewing source code.
4
4
  ---
5
5
 
6
6
  # Ciphera Code Style Policy
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "ciphera-style-policy",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Ciphera code style conventions (formatting, naming, imports, comments, code-level anti-patterns; TypeScript-first, language-agnostic).",
6
- "cliVersion": "1.1.3",
7
- "sha": "f8602bb79fbbe063ab39fbd59d0b7844a22c3a1583fcd11c1b4f98a2fe8ddc86"
6
+ "cliVersion": "1.2.0",
7
+ "sha": "74f638aec13e8c93257fe1ad604c28b07e9a7c456796a4ceefcc99217d9e7039"
8
8
  }
@@ -3,6 +3,6 @@
3
3
  "version": "1.0.0",
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Pre-delivery self-review gate, prioritized review dimensions, and change-quality criteria.",
6
- "cliVersion": "1.1.3",
6
+ "cliVersion": "1.2.0",
7
7
  "sha": "3d3bbe0602d5bbb4afe37648fe3c2fa39376b1bcbac5d8c441f01fad1e866ed0"
8
8
  }
@@ -3,6 +3,6 @@
3
3
  "version": "1.4.0",
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Core engineering execution policy and harness orchestration (highest-authority rules).",
6
- "cliVersion": "1.1.3",
6
+ "cliVersion": "1.2.0",
7
7
  "sha": "c9c69c59516794311cb7b306ed4d4ad971824de3689a39c2b86c7669c73f2e8b"
8
8
  }
@@ -3,6 +3,6 @@
3
3
  "version": "1.0.0",
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Senior database architecture policy: query optimization, anti-duplication/normalization, scalability, and RGPD/GDPR encryption.",
6
- "cliVersion": "1.1.3",
6
+ "cliVersion": "1.2.0",
7
7
  "sha": "c4617ee8d1a57d9621c81bef3093e94de91f79eec0cc0ead41f6d18dd443e623"
8
8
  }
@@ -3,6 +3,6 @@
3
3
  "version": "1.0.0",
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Reproduce-isolate-fix debugging methodology with root-cause discipline and regression verification.",
6
- "cliVersion": "1.1.3",
6
+ "cliVersion": "1.2.0",
7
7
  "sha": "14b0064c8b33a0dc85e51464b05005cf5801c756b1101789a6924b9548420f6b"
8
8
  }
@@ -3,6 +3,6 @@
3
3
  "version": "1.0.0",
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Dependency and supply-chain security: lockfiles and reproducible installs, version pinning, vulnerability auditing, vetting/minimizing packages, vendoring, and SBOM/provenance.",
6
- "cliVersion": "1.1.3",
6
+ "cliVersion": "1.2.0",
7
7
  "sha": "6375d835c2aef2c9bd31ce116444dc3d796f510f9970a213aa3ac4696d7e21b9"
8
8
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: frontend-policy
3
- description: Frontend architecture - reusable components, abstraction thresholds, state management, client-side caching (localStorage/sessionStorage to avoid redundant server calls and survive rate limits), and optimistic UI with rollback. Use when building or changing UI components, client state, data fetching/caching, or any frontend structure.
3
+ description: Frontend architecture - reusable components, abstraction thresholds, state management, client-side caching (localStorage/sessionStorage to avoid redundant server calls and survive rate limits), optimistic UI with rollback, and periodic React code-health audits (react-doctor). Use when building or changing UI components, client state, data fetching/caching, auditing React code, or any frontend structure.
4
4
  ---
5
5
 
6
6
  # Frontend Architecture Policy
@@ -114,4 +114,18 @@ Cache on the client to avoid redundant server round-trips and to keep the app us
114
114
 
115
115
  - Use semantic markup and accessible interactive elements by default.
116
116
  - Handle loading, empty, and error states explicitly for every async view.
117
- - Validate user input in real time per validation-policy; never rely on the UI as the only validation layer.
117
+ - Validate user input in real time per validation-policy; never rely on the UI as the only validation layer.
118
+
119
+ ---
120
+
121
+ ## React Code Health Audit (react-doctor)
122
+
123
+ - Periodically audit React code with React Doctor, a fast static analyzer that scores the codebase across performance, security, correctness, accessibility, bundle size, and architecture (60+ rules, framework-aware: Next.js, Vite, React Native, Expo, ...). It is purpose-built to catch the bad React that agents tend to write.
124
+ - Run it from the project root; no install needed:
125
+ - `npx -y react-doctor@latest .`
126
+ - When to run it:
127
+ - Occasionally during React work, and as a final sanity check before committing a non-trivial React change.
128
+ - After large refactors, or when touching performance-sensitive components.
129
+ - It is an advisory audit, not a gate: read the findings, fix the high-value issues (real performance, correctness, or accessibility problems), and skip noise that does not apply. Never block delivery on the score alone.
130
+ - Review anything it proposes to auto-fix as a normal diff before keeping it; do not apply changes blindly (treat tool output as untrusted per security-policy).
131
+ - It runs locally and analyzes read-only by default. Rules for wiring it into CI as a deterministic gate live in dependency-policy and testing-policy.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "frontend-policy",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Frontend architecture: reusable components, abstraction thresholds, state management, and optimistic UI with rollback.",
6
- "cliVersion": "1.1.3",
7
- "sha": "b0355b0e15f9f528d32adf19f0722d2727cd64d6b3544307ecc7a3141338f023"
6
+ "cliVersion": "1.2.0",
7
+ "sha": "33fa1e9f667ef26203a3d6c892121efe12b0cddb706c195492fa97e080fba115"
8
8
  }
@@ -3,6 +3,6 @@
3
3
  "version": "1.2.0",
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Git & contribution policy (senior engineering standards).",
6
- "cliVersion": "1.1.3",
6
+ "cliVersion": "1.2.0",
7
7
  "sha": "ada4b7eb5bb7e013429e23703c271c0f34b0d76327c059efa148ea2794f96178"
8
8
  }
@@ -3,6 +3,6 @@
3
3
  "version": "1.0.0",
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Application and AI-agent security: secrets, authn/authz (least privilege), OWASP Top 10, transport/crypto baseline, secure logging, and agent/MCP/tool-use safety.",
6
- "cliVersion": "1.1.3",
6
+ "cliVersion": "1.2.0",
7
7
  "sha": "9971e9d9127397d0152e89d24aad3191e2935e55a8483db7fd15f5d4d7a60e7a"
8
8
  }
@@ -3,6 +3,6 @@
3
3
  "version": "1.0.0",
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Test strategy, coverage gates, deterministic tests, mocking discipline, and regression-first bug fixing.",
6
- "cliVersion": "1.1.3",
6
+ "cliVersion": "1.2.0",
7
7
  "sha": "d19fa8ec7985ed231478be504d3c80360897f555d0bc0624bea19c091f459fb0"
8
8
  }
@@ -3,6 +3,6 @@
3
3
  "version": "1.0.0",
4
4
  "provider": "FJRG2007/enigma",
5
5
  "description": "Strict frontend + backend schema validation, schema consistency, and safe client-facing error handling.",
6
- "cliVersion": "1.1.3",
6
+ "cliVersion": "1.2.0",
7
7
  "sha": "a33622a2f810ee4cea39824cb1a7ca34b355a917d4224025df50d77dd74f0b3a"
8
8
  }
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.
@@ -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
  {
@@ -526,29 +527,198 @@ var init_settings_registry = __esm({
526
527
  // src/tui/settings.ts
527
528
  var settings_exports = {};
528
529
  __export(settings_exports, {
530
+ buildApp: () => buildApp,
531
+ runHomeTui: () => runHomeTui,
529
532
  runSettingsTui: () => runSettingsTui
530
533
  });
534
+ async function runHomeTui(hub) {
535
+ await runTui({ showActions: true, hub });
536
+ }
531
537
  async function runSettingsTui() {
538
+ await runTui({ showActions: false });
539
+ }
540
+ async function runTui(opts) {
532
541
  if (!process.stdout.isTTY) return;
542
+ const fullscreen = readConfig().config.fullscreen;
533
543
  const React = (await import("react")).default;
534
544
  const ink = await import("ink");
535
- const { render, useApp, useInput } = ink;
545
+ const { render } = ink;
546
+ const h = React.createElement;
547
+ const agents = opts.hub?.agents ?? [];
548
+ const protections = opts.hub?.protections ?? [];
549
+ const clear = () => {
550
+ if (fullscreen) try {
551
+ process.stdout.write(CLEAR_SCREEN);
552
+ } catch {
553
+ }
554
+ };
555
+ for (; ; ) {
556
+ const state = { intent: { action: "quit" } };
557
+ const App = buildApp(React, ink, { showActions: opts.showActions, fullscreen, agents, protections, onLeave: (i) => {
558
+ state.intent = i;
559
+ } });
560
+ clear();
561
+ const app = render(h(App), { exitOnCtrlC: true });
562
+ await app.waitUntilExit();
563
+ const { intent } = state;
564
+ if (opts.hub && (intent.action === "skills" || intent.action === "security")) {
565
+ clear();
566
+ await opts.hub.runAction(intent);
567
+ await waitForEnter();
568
+ continue;
569
+ }
570
+ break;
571
+ }
572
+ }
573
+ function waitForEnter() {
574
+ return new Promise((resolve3) => {
575
+ process.stdout.write("\nPress Enter to return to the menu...\n");
576
+ const stdin = process.stdin;
577
+ stdin.resume();
578
+ stdin.once("data", () => {
579
+ stdin.pause();
580
+ resolve3();
581
+ });
582
+ });
583
+ }
584
+ function buildApp(React, ink, opts) {
585
+ const { useApp, useInput, useStdout } = ink;
536
586
  const Box = ink.Box;
537
587
  const Text = ink.Text;
538
- const { useState } = React;
588
+ const { useState, useEffect } = React;
539
589
  const h = React.createElement;
540
- function App() {
590
+ const fill = opts.fullscreen;
591
+ const sideItems = [
592
+ ...CATEGORIES.map((c, i) => ({ kind: "category", catIndex: i, title: c.title })),
593
+ ...opts.showActions ? ACTION_ITEMS.map((a) => ({ kind: "action", ...a })) : []
594
+ ];
595
+ return function App() {
541
596
  const { exit } = useApp();
597
+ const { stdout } = useStdout();
598
+ const [size, setSize] = useState({ columns: stdout.columns || 80, rows: stdout.rows || 24 });
599
+ const [mode, setMode] = useState("menu");
542
600
  const [scope, setScope] = useState("global");
543
- const [focusSettings, setFocusSettings] = useState(false);
544
- const [catIndex, setCatIndex] = useState(0);
601
+ const [sideIndex, setSideIndex] = useState(0);
602
+ const [focusRight, setFocusRight] = useState(false);
545
603
  const [setIndex, setSetIndex] = useState(0);
546
- const [, bump] = useState(0);
547
- const category = CATEGORIES[catIndex];
548
- const settings = category.settings;
604
+ const [pending, setPending] = useState({});
605
+ const [confirm4, setConfirm] = useState(null);
606
+ const [actCursor, setActCursor] = useState(0);
607
+ const [actChecked, setActChecked] = useState({});
608
+ const [actScope, setActScope] = useState("global");
609
+ useEffect(() => {
610
+ const onResize = () => setSize({ columns: stdout.columns || 80, rows: stdout.rows || 24 });
611
+ stdout.on("resize", onResize);
612
+ return () => {
613
+ stdout.off("resize", onResize);
614
+ };
615
+ }, [stdout]);
616
+ const current = sideItems[sideIndex];
617
+ const category = current.kind === "category" ? CATEGORIES[current.catIndex] : null;
618
+ const valueOf = (setting, sc) => {
619
+ const k = stageKey(setting.key, sc);
620
+ return k in pending ? pending[k] : setting.read(sc);
621
+ };
622
+ const isModified = (setting, sc) => {
623
+ const k = stageKey(setting.key, sc);
624
+ return k in pending && pending[k] !== setting.read(sc);
625
+ };
626
+ const dirty = Object.entries(pending).some(([k, v]) => {
627
+ const { key, scope: sc } = parseStageKey(k);
628
+ return SETTING_BY_KEY.get(key)?.read(sc) !== v;
629
+ });
630
+ const persistPending = () => {
631
+ for (const [k, v] of Object.entries(pending)) {
632
+ const { key, scope: sc } = parseStageKey(k);
633
+ const setting = SETTING_BY_KEY.get(key);
634
+ if (setting && setting.read(sc) !== v) setting.write(v, sc);
635
+ }
636
+ };
637
+ const leave = (intent) => {
638
+ opts.onLeave(intent);
639
+ exit();
640
+ };
641
+ const openAction = (action) => {
642
+ setActCursor(0);
643
+ if (action === "security") {
644
+ setActChecked(Object.fromEntries(opts.protections.map((p6) => [p6.value, true])));
645
+ } else {
646
+ const detected = opts.agents.filter((a) => a.installed);
647
+ const preselect = detected.length ? detected : opts.agents;
648
+ setActChecked(Object.fromEntries(opts.agents.map((a) => [a.name, preselect.some((d) => d.name === a.name)])));
649
+ setActScope("global");
650
+ }
651
+ setMode(action);
652
+ };
549
653
  useInput((input, key) => {
550
- if (input === "q" || key.escape || key.ctrl && input === "c") {
551
- exit();
654
+ if (confirm4) {
655
+ if (key.escape) {
656
+ setConfirm(null);
657
+ return;
658
+ }
659
+ if (key.upArrow || input === "k") {
660
+ setConfirm((c) => c && { index: Math.max(0, c.index - 1) });
661
+ return;
662
+ }
663
+ if (key.downArrow || input === "j") {
664
+ setConfirm((c) => c && { index: Math.min(EXIT_OPTIONS.length - 1, c.index + 1) });
665
+ return;
666
+ }
667
+ if (key.return || input === " ") {
668
+ const index = confirm4.index;
669
+ setConfirm(null);
670
+ if (index === 2) return;
671
+ if (index === 0) persistPending();
672
+ setPending({});
673
+ leave({ action: "quit" });
674
+ }
675
+ return;
676
+ }
677
+ if (mode !== "menu") {
678
+ const items = mode === "security" ? opts.protections.map((p6) => p6.value) : opts.agents.map((a) => a.name);
679
+ if (key.escape || input === "q") {
680
+ setMode("menu");
681
+ return;
682
+ }
683
+ if (mode === "skills" && input === "g") {
684
+ setActScope((s) => s === "global" ? "local" : "global");
685
+ return;
686
+ }
687
+ if (key.upArrow || input === "k") {
688
+ setActCursor((i) => Math.max(0, i - 1));
689
+ return;
690
+ }
691
+ if (key.downArrow || input === "j") {
692
+ setActCursor((i) => Math.min(items.length - 1, i + 1));
693
+ return;
694
+ }
695
+ if (input === " ") {
696
+ const k = items[actCursor];
697
+ setActChecked((c) => ({ ...c, [k]: !c[k] }));
698
+ return;
699
+ }
700
+ if (key.return) {
701
+ const chosen = items.filter((k) => actChecked[k]);
702
+ persistPending();
703
+ setPending({});
704
+ if (mode === "security") leave({ action: "security", protections: chosen });
705
+ else leave({ action: "skills", scope: actScope, agents: chosen });
706
+ }
707
+ return;
708
+ }
709
+ if (input === "q" || key.escape) {
710
+ dirty ? setConfirm({ index: 0 }) : leave({ action: "quit" });
711
+ return;
712
+ }
713
+ if (input === "s") {
714
+ persistPending();
715
+ setPending({});
716
+ return;
717
+ }
718
+ if (input === "x") {
719
+ persistPending();
720
+ setPending({});
721
+ leave({ action: "quit" });
552
722
  return;
553
723
  }
554
724
  if (input === "g") {
@@ -556,96 +726,218 @@ async function runSettingsTui() {
556
726
  return;
557
727
  }
558
728
  if (key.tab) {
559
- setFocusSettings((f) => !f);
729
+ if (category) setFocusRight((f) => !f);
560
730
  return;
561
731
  }
562
732
  if (key.leftArrow || input === "h") {
563
- setFocusSettings(false);
733
+ setFocusRight(false);
564
734
  return;
565
735
  }
566
736
  if (key.rightArrow || input === "l") {
567
- setFocusSettings(true);
737
+ if (category) setFocusRight(true);
568
738
  return;
569
739
  }
570
740
  if (key.upArrow || input === "k") {
571
- if (focusSettings) setSetIndex((i) => Math.max(0, i - 1));
741
+ if (focusRight && category) setSetIndex((i) => Math.max(0, i - 1));
572
742
  else {
573
- setCatIndex((i) => Math.max(0, i - 1));
743
+ setSideIndex((i) => Math.max(0, i - 1));
574
744
  setSetIndex(0);
745
+ setFocusRight(false);
575
746
  }
576
747
  return;
577
748
  }
578
749
  if (key.downArrow || input === "j") {
579
- if (focusSettings) setSetIndex((i) => Math.min(settings.length - 1, i + 1));
750
+ if (focusRight && category) setSetIndex((i) => Math.min(category.settings.length - 1, i + 1));
580
751
  else {
581
- setCatIndex((i) => Math.min(CATEGORIES.length - 1, i + 1));
752
+ setSideIndex((i) => Math.min(sideItems.length - 1, i + 1));
582
753
  setSetIndex(0);
754
+ setFocusRight(false);
583
755
  }
584
756
  return;
585
757
  }
586
758
  if (key.return || input === " ") {
587
- if (!focusSettings) {
588
- setFocusSettings(true);
759
+ if (current.kind === "action") {
760
+ openAction(current.action);
589
761
  return;
590
762
  }
591
- const setting = settings[setIndex];
592
- setting.write(!setting.read(scope), scope);
593
- bump((n) => n + 1);
763
+ if (!focusRight) {
764
+ setFocusRight(true);
765
+ return;
766
+ }
767
+ const setting = category.settings[setIndex];
768
+ setPending((p6) => ({ ...p6, [stageKey(setting.key, scope)]: !valueOf(setting, scope) }));
594
769
  }
595
770
  });
596
- const header = h(
771
+ const headerRight = confirm4 ? h(Text, { color: "yellow" }, "unsaved changes") : mode === "menu" ? h(
597
772
  Box,
598
- { marginBottom: 1 },
599
- h(Text, { bold: true, color: "cyan" }, "enigma settings"),
600
- h(Text, { dimColor: true }, " scope: "),
773
+ {},
774
+ h(Text, { dimColor: true }, "scope "),
601
775
  h(Text, { bold: true, color: scope === "global" ? "green" : "yellow" }, scope),
602
- h(Text, { dimColor: true }, " (g to change)")
776
+ h(Text, { dimColor: true }, " (g)"),
777
+ dirty ? h(Text, { color: "yellow" }, " * unsaved") : null
778
+ ) : h(Text, { dimColor: true }, mode === "skills" ? "install" : "security");
779
+ const titleBar = h(
780
+ Box,
781
+ { width: size.columns, paddingX: 1, justifyContent: "space-between" },
782
+ h(Text, { bold: true, color: "cyan" }, "enigma"),
783
+ headerRight
603
784
  );
604
- const left = h(Box, {
605
- flexDirection: "column",
606
- borderStyle: "round",
607
- borderColor: focusSettings ? "gray" : "cyan",
608
- paddingX: 1,
609
- width: 26,
610
- marginRight: 1
611
- }, CATEGORIES.map((c, i) => h(Text, {
612
- key: c.title,
613
- color: i === catIndex ? "cyan" : void 0,
614
- inverse: !focusSettings && i === catIndex
615
- }, `${i === catIndex ? ">" : " "} ${c.title}`)));
616
- const right = h(Box, {
785
+ let content;
786
+ if (confirm4) {
787
+ content = renderConfirm(h, Box, Text, confirm4.index, fill);
788
+ } else if (mode === "security") {
789
+ content = renderChecklist(h, Box, Text, {
790
+ title: "Git security hooks",
791
+ blurb: "Choose what the commit guard enforces, then press enter.",
792
+ items: opts.protections.map((p6) => ({ key: p6.value, label: p6.label, hint: p6.hint })),
793
+ cursor: actCursor,
794
+ checked: actChecked,
795
+ fill
796
+ });
797
+ } else if (mode === "skills") {
798
+ content = renderChecklist(h, Box, Text, {
799
+ title: "Install agent skills",
800
+ blurb: `Scope ${actScope} (g to change). Choose agents, then press enter.`,
801
+ items: opts.agents.map((a) => ({ key: a.name, label: a.label, hint: a.installed ? "detected" : "not detected" })),
802
+ cursor: actCursor,
803
+ checked: actChecked,
804
+ fill
805
+ });
806
+ } else {
807
+ const sidebarWidth = Math.min(28, Math.max(20, Math.floor(size.columns * 0.3)));
808
+ const panel = category ? renderCategoryPanel(h, Box, Text, { category, scope, focusRight, setIndex, valueOf, isModified, fill }) : renderActionPanel(h, Box, Text, current);
809
+ content = h(Box, fill ? { flexGrow: 1 } : {}, renderSidebar(h, Box, Text, sideItems, sideIndex, focusRight, sidebarWidth), panel);
810
+ }
811
+ const footerText = confirm4 ? "up/down move enter select esc cancel" : mode === "security" ? "up/down move space toggle enter apply esc back" : mode === "skills" ? "up/down move space toggle g scope enter install esc back" : `up/down move tab switch ${category ? "enter toggle g scope " : "enter open "}s save x save & exit q quit`;
812
+ const footer = h(Box, { width: size.columns, paddingX: 1 }, h(Text, { dimColor: true }, footerText));
813
+ return h(
814
+ Box,
815
+ { width: size.columns, ...fill ? { height: size.rows } : {}, flexDirection: "column" },
816
+ titleBar,
817
+ content,
818
+ footer
819
+ );
820
+ };
821
+ }
822
+ function renderSidebar(h, Box, Text, items, index, focusRight, width) {
823
+ return h(Box, {
824
+ flexDirection: "column",
825
+ borderStyle: "round",
826
+ borderColor: focusRight ? "gray" : "cyan",
827
+ paddingX: 1,
828
+ width,
829
+ marginRight: 1
830
+ }, [
831
+ h(Text, { key: "__t", bold: true, dimColor: true }, "MENU"),
832
+ ...items.map((it, i) => h(Text, {
833
+ key: String(i),
834
+ inverse: !focusRight && i === index,
835
+ color: i === index ? "cyan" : void 0
836
+ }, ` ${it.title} `))
837
+ ]);
838
+ }
839
+ function renderConfirm(h, Box, Text, index, fill) {
840
+ return h(
841
+ Box,
842
+ { justifyContent: "center", ...fill ? { flexGrow: 1, alignItems: "center" } : {} },
843
+ h(Box, {
617
844
  flexDirection: "column",
618
845
  borderStyle: "round",
619
- borderColor: focusSettings ? "cyan" : "gray",
620
- paddingX: 1,
621
- flexGrow: 1
846
+ borderColor: "yellow",
847
+ paddingX: 2,
848
+ paddingY: 1
622
849
  }, [
623
- h(Text, { key: "__blurb", dimColor: true }, category.blurb),
624
- ...settings.map((s, i) => {
625
- const on = s.read(scope);
626
- const selected = focusSettings && i === setIndex;
627
- return h(
628
- Box,
629
- { key: s.key, justifyContent: "space-between" },
630
- h(Text, { inverse: selected }, `${selected ? ">" : " "} ${s.label}${s.globalOnly ? " (global)" : ""}`),
631
- h(Text, { bold: true, color: on ? "green" : "gray" }, ` ${valueLabel(on)}`)
632
- );
633
- })
634
- ]);
635
- const footer = h(Box, { marginTop: 1 }, h(
636
- Text,
637
- { dimColor: true },
638
- "up/down move - tab/left/right switch pane - enter/space toggle - g scope - q quit"
639
- ));
640
- return h(Box, { flexDirection: "column" }, header, h(Box, {}, left, right), footer);
641
- }
642
- const app = render(h(App));
643
- await app.waitUntilExit();
850
+ h(Text, { key: "__t", bold: true, color: "yellow" }, "You have unsaved changes"),
851
+ h(
852
+ Box,
853
+ { key: "__o", marginTop: 1, flexDirection: "column" },
854
+ EXIT_OPTIONS.map((o, i) => h(Text, { key: o, inverse: i === index, bold: i === index }, ` ${o} `))
855
+ )
856
+ ])
857
+ );
644
858
  }
859
+ function renderChecklist(h, Box, Text, s) {
860
+ const rows = s.items.map((it, i) => {
861
+ const on = !!s.checked[it.key];
862
+ const selected = i === s.cursor;
863
+ return h(
864
+ Box,
865
+ { key: it.key, justifyContent: "space-between" },
866
+ h(Text, { inverse: selected, bold: selected }, ` ${on ? "[x]" : "[ ]"} ${it.label} `),
867
+ h(Text, { dimColor: true }, `${it.hint} `)
868
+ );
869
+ });
870
+ return h(Box, {
871
+ flexDirection: "column",
872
+ borderStyle: "round",
873
+ borderColor: "cyan",
874
+ paddingX: 1,
875
+ ...s.fill ? { flexGrow: 1 } : {}
876
+ }, [
877
+ h(Text, { key: "__t", bold: true, color: "cyan" }, s.title),
878
+ h(Text, { key: "__bl", dimColor: true }, s.blurb),
879
+ h(Box, { key: "__rows", marginTop: 1, flexDirection: "column" }, rows),
880
+ ...s.fill ? [h(Box, { key: "__grow", flexGrow: 1 })] : []
881
+ ]);
882
+ }
883
+ function renderCategoryPanel(h, Box, Text, s) {
884
+ const focused = s.category.settings[s.setIndex];
885
+ const rows = s.category.settings.map((setting, i) => {
886
+ const on = s.valueOf(setting, s.scope);
887
+ const modified = s.isModified(setting, s.scope);
888
+ const selected = s.focusRight && i === s.setIndex;
889
+ return h(
890
+ Box,
891
+ { key: setting.key, justifyContent: "space-between" },
892
+ h(Text, { inverse: selected, bold: selected }, ` ${setting.label}${setting.globalOnly ? " (global)" : ""} `),
893
+ h(Text, { bold: true, color: modified ? "yellow" : on ? "green" : "gray" }, `${valueLabel(on)}${modified ? " *" : ""} `)
894
+ );
895
+ });
896
+ return h(Box, {
897
+ flexDirection: "column",
898
+ borderStyle: "round",
899
+ borderColor: s.focusRight ? "cyan" : "gray",
900
+ paddingX: 1,
901
+ flexGrow: 1
902
+ }, [
903
+ h(Text, { key: "__b", bold: true, color: "cyan" }, s.category.title),
904
+ h(Text, { key: "__bl", dimColor: true }, s.category.blurb),
905
+ h(Box, { key: "__rows", marginTop: 1, flexDirection: "column" }, rows),
906
+ ...s.fill ? [h(Box, { key: "__grow", flexGrow: 1 })] : [],
907
+ h(Text, { key: "__hint", marginTop: 1, dimColor: true, wrap: "truncate-end" }, focused.hint)
908
+ ]);
909
+ }
910
+ function renderActionPanel(h, Box, Text, item) {
911
+ return h(Box, {
912
+ flexDirection: "column",
913
+ borderStyle: "round",
914
+ borderColor: "gray",
915
+ paddingX: 1,
916
+ flexGrow: 1
917
+ }, [
918
+ h(Text, { key: "__t", bold: true, color: "cyan" }, item.title),
919
+ h(Text, { key: "__bl", dimColor: true }, item.blurb),
920
+ h(Text, { key: "__h", marginTop: 1, dimColor: true }, "Press enter to open")
921
+ ]);
922
+ }
923
+ var CLEAR_SCREEN, SETTING_BY_KEY, stageKey, parseStageKey, ACTION_ITEMS, EXIT_OPTIONS;
645
924
  var init_settings = __esm({
646
925
  "src/tui/settings.ts"() {
647
926
  "use strict";
927
+ init_config();
648
928
  init_settings_registry();
929
+ CLEAR_SCREEN = "\x1B[2J\x1B[H";
930
+ SETTING_BY_KEY = new Map(ALL_SETTINGS.map((s) => [s.key, s]));
931
+ stageKey = (key, scope) => `${scope}/${key}`;
932
+ parseStageKey = (composite) => {
933
+ const i = composite.indexOf("/");
934
+ return { scope: composite.slice(0, i), key: composite.slice(i + 1) };
935
+ };
936
+ ACTION_ITEMS = [
937
+ { action: "skills", title: "Install agent skills", blurb: "Claude Code, Codex, OpenCode" },
938
+ { action: "security", title: "Git security hooks", blurb: "block secrets, .env, node_modules on commit" }
939
+ ];
940
+ EXIT_OPTIONS = ["Save & exit", "Exit without saving", "Cancel"];
649
941
  }
650
942
  });
651
943
 
@@ -1185,6 +1477,9 @@ async function installSkills(opts, interactive) {
1185
1477
  }
1186
1478
  }
1187
1479
 
1480
+ // src/cli.ts
1481
+ init_agents();
1482
+
1188
1483
  // src/guard.ts
1189
1484
  import { readFileSync as readFileSync4, statSync as statSync2 } from "fs";
1190
1485
  import { execFileSync as execFileSync3 } from "child_process";
@@ -1590,7 +1885,7 @@ Usage:
1590
1885
 
1591
1886
  Commands:
1592
1887
  (none) Interactive hub: configure settings or set up features
1593
- install Install/update agent skills (Claude Code, Codex, opencode)
1888
+ install Install/update agent skills (Claude Code, Codex, OpenCode)
1594
1889
  security Set up git security hooks in the current repo
1595
1890
  guard [--all] Run the commit guard (staged files, or --all for every tracked file)
1596
1891
  config [key val] Configure settings: no args opens the interactive menu;
@@ -1599,7 +1894,7 @@ Commands:
1599
1894
  check Integrity gate: verify skills are well-formed and sealed
1600
1895
  help, version
1601
1896
 
1602
- Config keys: commit-emoji, update-notifier, claude-attribution,
1897
+ Config keys: commit-emoji, update-notifier, fullscreen, claude-attribution,
1603
1898
  bypass-claude, bypass-codex, bypass-opencode
1604
1899
 
1605
1900
  Install options:
@@ -1671,25 +1966,22 @@ async function run(argv) {
1671
1966
  await notifyUpdate(version, interactive);
1672
1967
  return;
1673
1968
  }
1674
- p5.intro("enigma");
1675
- for (; ; ) {
1676
- const action = await p5.select({
1677
- message: "What would you like to do?",
1678
- options: [
1679
- { value: "config", label: "Configure settings", hint: "emoji, attribution, permission bypass, ..." },
1680
- { value: "skills", label: "Install agent skills", hint: "Claude Code, Codex, opencode" },
1681
- { value: "security", label: "Git security hooks", hint: "block secrets, .env, node_modules on commit" },
1682
- { value: "exit", label: "Exit" }
1683
- ]
1684
- });
1685
- if (p5.isCancel(action) || action === "exit") break;
1686
- if (action === "config") {
1687
- const { runSettingsTui: runSettingsTui2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
1688
- await runSettingsTui2();
1689
- } else if (action === "skills") await installSkills(opts, interactive);
1690
- else if (action === "security") await setupGitHooks(opts, interactive);
1691
- }
1692
- p5.outro("Done.");
1969
+ const { runHomeTui: runHomeTui2 } = await Promise.resolve().then(() => (init_settings(), settings_exports));
1970
+ await runHomeTui2({
1971
+ agents: discoverAgents().map((a) => ({ name: a.name, label: a.label, installed: a.installed })),
1972
+ protections: GUARD_PROTECTIONS,
1973
+ runAction: async (intent) => {
1974
+ if (intent.action === "skills") {
1975
+ p5.intro("enigma - install agent skills");
1976
+ await installSkills({ ...opts, scope: intent.scope ?? opts.scope, agents: intent.agents ?? [], allAgents: !(intent.agents && intent.agents.length) }, false);
1977
+ p5.outro("Done.");
1978
+ } else if (intent.action === "security") {
1979
+ p5.intro("enigma - git security hooks");
1980
+ const done = await setupGitHooks({ ...opts, protections: intent.protections, force: true }, false);
1981
+ p5.outro(done ? "Git hooks configured." : "No changes made.");
1982
+ }
1983
+ }
1984
+ });
1693
1985
  await notifyUpdate(version, interactive);
1694
1986
  }
1695
1987
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "enigma-cli",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "Everything you need to work with a coding agent: install shared policy skills for Claude Code, OpenAI Codex and opencode, and set up portable git security hooks.",
5
5
  "type": "module",
6
6
  "bin": {