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.
- package/assets/skills/backend-policy/skill.json +1 -1
- package/assets/skills/ciphera-style-policy/SKILL.md +1 -1
- package/assets/skills/ciphera-style-policy/skill.json +3 -3
- package/assets/skills/code-review-policy/skill.json +1 -1
- package/assets/skills/core-engineering-policy/skill.json +1 -1
- package/assets/skills/database-expert/skill.json +1 -1
- package/assets/skills/debugging-policy/skill.json +1 -1
- package/assets/skills/dependency-policy/skill.json +1 -1
- package/assets/skills/frontend-policy/SKILL.md +16 -2
- package/assets/skills/frontend-policy/skill.json +3 -3
- package/assets/skills/git-policy/skill.json +1 -1
- package/assets/skills/security-policy/skill.json +1 -1
- package/assets/skills/testing-policy/skill.json +1 -1
- package/assets/skills/validation-policy/skill.json +1 -1
- package/dist/enigma.js +380 -88
- package/package.json +1 -1
|
@@ -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.
|
|
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)
|
|
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.
|
|
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.
|
|
7
|
-
"sha": "
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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),
|
|
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.
|
|
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.
|
|
7
|
-
"sha": "
|
|
6
|
+
"cliVersion": "1.2.0",
|
|
7
|
+
"sha": "33fa1e9f667ef26203a3d6c892121efe12b0cddb706c195492fa97e080fba115"
|
|
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.
|
|
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.
|
|
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.
|
|
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: "
|
|
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
|
|
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
|
-
|
|
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 [
|
|
544
|
-
const [
|
|
601
|
+
const [sideIndex, setSideIndex] = useState(0);
|
|
602
|
+
const [focusRight, setFocusRight] = useState(false);
|
|
545
603
|
const [setIndex, setSetIndex] = useState(0);
|
|
546
|
-
const [,
|
|
547
|
-
const
|
|
548
|
-
const
|
|
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 (
|
|
551
|
-
|
|
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
|
-
|
|
729
|
+
if (category) setFocusRight((f) => !f);
|
|
560
730
|
return;
|
|
561
731
|
}
|
|
562
732
|
if (key.leftArrow || input === "h") {
|
|
563
|
-
|
|
733
|
+
setFocusRight(false);
|
|
564
734
|
return;
|
|
565
735
|
}
|
|
566
736
|
if (key.rightArrow || input === "l") {
|
|
567
|
-
|
|
737
|
+
if (category) setFocusRight(true);
|
|
568
738
|
return;
|
|
569
739
|
}
|
|
570
740
|
if (key.upArrow || input === "k") {
|
|
571
|
-
if (
|
|
741
|
+
if (focusRight && category) setSetIndex((i) => Math.max(0, i - 1));
|
|
572
742
|
else {
|
|
573
|
-
|
|
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 (
|
|
750
|
+
if (focusRight && category) setSetIndex((i) => Math.min(category.settings.length - 1, i + 1));
|
|
580
751
|
else {
|
|
581
|
-
|
|
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 (
|
|
588
|
-
|
|
759
|
+
if (current.kind === "action") {
|
|
760
|
+
openAction(current.action);
|
|
589
761
|
return;
|
|
590
762
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
|
771
|
+
const headerRight = confirm4 ? h(Text, { color: "yellow" }, "unsaved changes") : mode === "menu" ? h(
|
|
597
772
|
Box,
|
|
598
|
-
{
|
|
599
|
-
h(Text, {
|
|
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
|
|
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
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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:
|
|
620
|
-
paddingX:
|
|
621
|
-
|
|
846
|
+
borderColor: "yellow",
|
|
847
|
+
paddingX: 2,
|
|
848
|
+
paddingY: 1
|
|
622
849
|
}, [
|
|
623
|
-
h(Text, { key: "
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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,
|
|
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
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
{
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
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.
|
|
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": {
|