enigma-cli 1.2.0 → 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/assets/skills/backend-policy/skill.json +1 -1
- package/assets/skills/ciphera-style-policy/skill.json +1 -1
- 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.json +1 -1
- 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 +633 -200
- package/package.json +9 -4
package/dist/enigma.js
CHANGED
|
@@ -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
|
|
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
|
|
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 (
|
|
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)
|
|
251
|
-
else if (res.changed)
|
|
252
|
-
else
|
|
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) {
|
|
@@ -546,40 +546,14 @@ async function runTui(opts) {
|
|
|
546
546
|
const h = React.createElement;
|
|
547
547
|
const agents = opts.hub?.agents ?? [];
|
|
548
548
|
const protections = opts.hub?.protections ?? [];
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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;
|
|
549
|
+
const runAction = opts.hub?.runAction ?? (async () => ({ ok: false, title: "", lines: [] }));
|
|
550
|
+
if (fullscreen) try {
|
|
551
|
+
process.stdout.write(CLEAR_SCREEN);
|
|
552
|
+
} catch {
|
|
571
553
|
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
|
|
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
|
-
});
|
|
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();
|
|
583
557
|
}
|
|
584
558
|
function buildApp(React, ink, opts) {
|
|
585
559
|
const { useApp, useInput, useStdout } = ink;
|
|
@@ -592,6 +566,7 @@ function buildApp(React, ink, opts) {
|
|
|
592
566
|
...CATEGORIES.map((c, i) => ({ kind: "category", catIndex: i, title: c.title })),
|
|
593
567
|
...opts.showActions ? ACTION_ITEMS.map((a) => ({ kind: "action", ...a })) : []
|
|
594
568
|
];
|
|
569
|
+
const actionItemKeys = (action) => action === "security" ? opts.protections.map((pr) => pr.value) : opts.agents.map((a) => a.name);
|
|
595
570
|
return function App() {
|
|
596
571
|
const { exit } = useApp();
|
|
597
572
|
const { stdout } = useStdout();
|
|
@@ -605,7 +580,9 @@ function buildApp(React, ink, opts) {
|
|
|
605
580
|
const [confirm4, setConfirm] = useState(null);
|
|
606
581
|
const [actCursor, setActCursor] = useState(0);
|
|
607
582
|
const [actChecked, setActChecked] = useState({});
|
|
608
|
-
const [
|
|
583
|
+
const [busyTitle, setBusyTitle] = useState("");
|
|
584
|
+
const [result, setResult] = useState(null);
|
|
585
|
+
const [resultScroll, setResultScroll] = useState(0);
|
|
609
586
|
useEffect(() => {
|
|
610
587
|
const onResize = () => setSize({ columns: stdout.columns || 80, rows: stdout.rows || 24 });
|
|
611
588
|
stdout.on("resize", onResize);
|
|
@@ -615,6 +592,20 @@ function buildApp(React, ink, opts) {
|
|
|
615
592
|
}, [stdout]);
|
|
616
593
|
const current = sideItems[sideIndex];
|
|
617
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)])));
|
|
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);
|
|
618
609
|
const valueOf = (setting, sc) => {
|
|
619
610
|
const k = stageKey(setting.key, sc);
|
|
620
611
|
return k in pending ? pending[k] : setting.read(sc);
|
|
@@ -634,21 +625,23 @@ function buildApp(React, ink, opts) {
|
|
|
634
625
|
if (setting && setting.read(sc) !== v) setting.write(v, sc);
|
|
635
626
|
}
|
|
636
627
|
};
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
}
|
|
651
|
-
|
|
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
|
+
});
|
|
652
645
|
};
|
|
653
646
|
useInput((input, key) => {
|
|
654
647
|
if (confirm4) {
|
|
@@ -670,44 +663,28 @@ function buildApp(React, ink, opts) {
|
|
|
670
663
|
if (index === 2) return;
|
|
671
664
|
if (index === 0) persistPending();
|
|
672
665
|
setPending({});
|
|
673
|
-
|
|
666
|
+
exit();
|
|
674
667
|
}
|
|
675
668
|
return;
|
|
676
669
|
}
|
|
677
|
-
if (mode
|
|
678
|
-
|
|
679
|
-
if (key.escape || input === "q") {
|
|
670
|
+
if (mode === "running") return;
|
|
671
|
+
if (mode === "result") {
|
|
672
|
+
if (key.return || key.escape || input === " " || input === "q") {
|
|
680
673
|
setMode("menu");
|
|
681
674
|
return;
|
|
682
675
|
}
|
|
683
|
-
if (mode === "skills" && input === "g") {
|
|
684
|
-
setActScope((s) => s === "global" ? "local" : "global");
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
676
|
if (key.upArrow || input === "k") {
|
|
688
|
-
|
|
677
|
+
setResultScroll((s) => Math.max(0, s - 1));
|
|
689
678
|
return;
|
|
690
679
|
}
|
|
691
680
|
if (key.downArrow || input === "j") {
|
|
692
|
-
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
if (input === " ") {
|
|
696
|
-
const k = items[actCursor];
|
|
697
|
-
setActChecked((c) => ({ ...c, [k]: !c[k] }));
|
|
681
|
+
setResultScroll((s) => Math.min(maxResultScroll, s + 1));
|
|
698
682
|
return;
|
|
699
683
|
}
|
|
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
684
|
return;
|
|
708
685
|
}
|
|
709
686
|
if (input === "q" || key.escape) {
|
|
710
|
-
dirty ? setConfirm({ index: 0 }) :
|
|
687
|
+
dirty ? setConfirm({ index: 0 }) : exit();
|
|
711
688
|
return;
|
|
712
689
|
}
|
|
713
690
|
if (input === "s") {
|
|
@@ -718,7 +695,7 @@ function buildApp(React, ink, opts) {
|
|
|
718
695
|
if (input === "x") {
|
|
719
696
|
persistPending();
|
|
720
697
|
setPending({});
|
|
721
|
-
|
|
698
|
+
exit();
|
|
722
699
|
return;
|
|
723
700
|
}
|
|
724
701
|
if (input === "g") {
|
|
@@ -726,7 +703,7 @@ function buildApp(React, ink, opts) {
|
|
|
726
703
|
return;
|
|
727
704
|
}
|
|
728
705
|
if (key.tab) {
|
|
729
|
-
|
|
706
|
+
setFocusRight((f) => !f);
|
|
730
707
|
return;
|
|
731
708
|
}
|
|
732
709
|
if (key.leftArrow || input === "h") {
|
|
@@ -734,11 +711,12 @@ function buildApp(React, ink, opts) {
|
|
|
734
711
|
return;
|
|
735
712
|
}
|
|
736
713
|
if (key.rightArrow || input === "l") {
|
|
737
|
-
|
|
714
|
+
setFocusRight(true);
|
|
738
715
|
return;
|
|
739
716
|
}
|
|
740
717
|
if (key.upArrow || input === "k") {
|
|
741
718
|
if (focusRight && category) setSetIndex((i) => Math.max(0, i - 1));
|
|
719
|
+
else if (focusRight && action) setActCursor((i) => Math.max(0, i - 1));
|
|
742
720
|
else {
|
|
743
721
|
setSideIndex((i) => Math.max(0, i - 1));
|
|
744
722
|
setSetIndex(0);
|
|
@@ -748,6 +726,7 @@ function buildApp(React, ink, opts) {
|
|
|
748
726
|
}
|
|
749
727
|
if (key.downArrow || input === "j") {
|
|
750
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));
|
|
751
730
|
else {
|
|
752
731
|
setSideIndex((i) => Math.min(sideItems.length - 1, i + 1));
|
|
753
732
|
setSetIndex(0);
|
|
@@ -755,27 +734,32 @@ function buildApp(React, ink, opts) {
|
|
|
755
734
|
}
|
|
756
735
|
return;
|
|
757
736
|
}
|
|
737
|
+
if (input === " " && focusRight && action) {
|
|
738
|
+
const k = actionItemKeys(action)[actCursor];
|
|
739
|
+
setActChecked((c) => ({ ...c, [k]: !c[k] }));
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
758
742
|
if (key.return || input === " ") {
|
|
759
|
-
if (current.kind === "action") {
|
|
760
|
-
openAction(current.action);
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
743
|
if (!focusRight) {
|
|
764
744
|
setFocusRight(true);
|
|
765
745
|
return;
|
|
766
746
|
}
|
|
747
|
+
if (action) {
|
|
748
|
+
runChosen(action);
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
767
751
|
const setting = category.settings[setIndex];
|
|
768
|
-
setPending((
|
|
752
|
+
setPending((pr) => ({ ...pr, [stageKey(setting.key, scope)]: !valueOf(setting, scope) }));
|
|
769
753
|
}
|
|
770
754
|
});
|
|
771
|
-
const headerRight = confirm4 ? h(Text, { color: "yellow" }, "unsaved changes") : mode === "
|
|
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(
|
|
772
756
|
Box,
|
|
773
757
|
{},
|
|
774
758
|
h(Text, { dimColor: true }, "scope "),
|
|
775
759
|
h(Text, { bold: true, color: scope === "global" ? "green" : "yellow" }, scope),
|
|
776
760
|
h(Text, { dimColor: true }, " (g)"),
|
|
777
761
|
dirty ? h(Text, { color: "yellow" }, " * unsaved") : null
|
|
778
|
-
)
|
|
762
|
+
);
|
|
779
763
|
const titleBar = h(
|
|
780
764
|
Box,
|
|
781
765
|
{ width: size.columns, paddingX: 1, justifyContent: "space-between" },
|
|
@@ -785,30 +769,25 @@ function buildApp(React, ink, opts) {
|
|
|
785
769
|
let content;
|
|
786
770
|
if (confirm4) {
|
|
787
771
|
content = renderConfirm(h, Box, Text, confirm4.index, fill);
|
|
788
|
-
} else if (mode === "
|
|
789
|
-
content =
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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" })),
|
|
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" })),
|
|
802
782
|
cursor: actCursor,
|
|
803
783
|
checked: actChecked,
|
|
784
|
+
focused: focusRight,
|
|
804
785
|
fill
|
|
805
786
|
});
|
|
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
787
|
content = h(Box, fill ? { flexGrow: 1 } : {}, renderSidebar(h, Box, Text, sideItems, sideIndex, focusRight, sidebarWidth), panel);
|
|
810
788
|
}
|
|
811
|
-
const
|
|
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;
|
|
812
791
|
const footer = h(Box, { width: size.columns, paddingX: 1 }, h(Text, { dimColor: true }, footerText));
|
|
813
792
|
return h(
|
|
814
793
|
Box,
|
|
@@ -856,10 +835,45 @@ function renderConfirm(h, Box, Text, index, fill) {
|
|
|
856
835
|
])
|
|
857
836
|
);
|
|
858
837
|
}
|
|
838
|
+
function renderRunning(h, Box, Text, title, fill) {
|
|
839
|
+
return h(Box, {
|
|
840
|
+
flexDirection: "column",
|
|
841
|
+
borderStyle: "round",
|
|
842
|
+
borderColor: "cyan",
|
|
843
|
+
paddingX: 1,
|
|
844
|
+
...fill ? { flexGrow: 1 } : {}
|
|
845
|
+
}, [
|
|
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 })] : []
|
|
849
|
+
]);
|
|
850
|
+
}
|
|
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, {
|
|
860
|
+
flexDirection: "column",
|
|
861
|
+
borderStyle: "round",
|
|
862
|
+
borderColor: res.ok ? "green" : "red",
|
|
863
|
+
paddingX: 1,
|
|
864
|
+
...s.fill ? { flexGrow: 1 } : {}
|
|
865
|
+
}, [
|
|
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 })] : []
|
|
871
|
+
]);
|
|
872
|
+
}
|
|
859
873
|
function renderChecklist(h, Box, Text, s) {
|
|
860
874
|
const rows = s.items.map((it, i) => {
|
|
861
875
|
const on = !!s.checked[it.key];
|
|
862
|
-
const selected = i === s.cursor;
|
|
876
|
+
const selected = s.focused && i === s.cursor;
|
|
863
877
|
return h(
|
|
864
878
|
Box,
|
|
865
879
|
{ key: it.key, justifyContent: "space-between" },
|
|
@@ -870,9 +884,9 @@ function renderChecklist(h, Box, Text, s) {
|
|
|
870
884
|
return h(Box, {
|
|
871
885
|
flexDirection: "column",
|
|
872
886
|
borderStyle: "round",
|
|
873
|
-
borderColor: "cyan",
|
|
887
|
+
borderColor: s.focused ? "cyan" : "gray",
|
|
874
888
|
paddingX: 1,
|
|
875
|
-
|
|
889
|
+
flexGrow: 1
|
|
876
890
|
}, [
|
|
877
891
|
h(Text, { key: "__t", bold: true, color: "cyan" }, s.title),
|
|
878
892
|
h(Text, { key: "__bl", dimColor: true }, s.blurb),
|
|
@@ -907,20 +921,7 @@ function renderCategoryPanel(h, Box, Text, s) {
|
|
|
907
921
|
h(Text, { key: "__hint", marginTop: 1, dimColor: true, wrap: "truncate-end" }, focused.hint)
|
|
908
922
|
]);
|
|
909
923
|
}
|
|
910
|
-
|
|
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;
|
|
924
|
+
var CLEAR_SCREEN, SETTING_BY_KEY, stageKey, parseStageKey, ACTION_ITEMS, actionTitle, EXIT_OPTIONS;
|
|
924
925
|
var init_settings = __esm({
|
|
925
926
|
"src/tui/settings.ts"() {
|
|
926
927
|
"use strict";
|
|
@@ -937,15 +938,450 @@ var init_settings = __esm({
|
|
|
937
938
|
{ action: "skills", title: "Install agent skills", blurb: "Claude Code, Codex, OpenCode" },
|
|
938
939
|
{ action: "security", title: "Git security hooks", blurb: "block secrets, .env, node_modules on commit" }
|
|
939
940
|
];
|
|
941
|
+
actionTitle = (action) => ACTION_ITEMS.find((a) => a.action === action).title;
|
|
940
942
|
EXIT_OPTIONS = ["Save & exit", "Exit without saving", "Cancel"];
|
|
941
943
|
}
|
|
942
944
|
});
|
|
943
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"() {
|
|
1309
|
+
"use strict";
|
|
1310
|
+
init_settings_registry();
|
|
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" }
|
|
1329
|
+
];
|
|
1330
|
+
actionTitle2 = (action) => ACTION_ITEMS2.find((a) => a.action === action).title;
|
|
1331
|
+
EXIT_OPTIONS2 = ["Save & exit", "Exit without saving", "Cancel"];
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1334
|
+
|
|
944
1335
|
// src/cli.ts
|
|
945
1336
|
init_util();
|
|
946
1337
|
import { dirname as dirname4, join as join10 } from "path";
|
|
947
1338
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
948
|
-
import * as
|
|
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
|
+
}
|
|
949
1385
|
|
|
950
1386
|
// src/skills.ts
|
|
951
1387
|
init_util();
|
|
@@ -954,7 +1390,7 @@ import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3,
|
|
|
954
1390
|
import { dirname as dirname2, join as join6, resolve as resolve2, relative as relative2, sep as sep2 } from "path";
|
|
955
1391
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
956
1392
|
import { createHash } from "crypto";
|
|
957
|
-
import * as
|
|
1393
|
+
import * as p4 from "@clack/prompts";
|
|
958
1394
|
|
|
959
1395
|
// src/security.ts
|
|
960
1396
|
init_util();
|
|
@@ -962,7 +1398,7 @@ import { existsSync as existsSync3, mkdirSync, cpSync, writeFileSync, chmodSync
|
|
|
962
1398
|
import { dirname, join as join3, resolve, relative } from "path";
|
|
963
1399
|
import { fileURLToPath } from "url";
|
|
964
1400
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
965
|
-
import * as
|
|
1401
|
+
import * as p2 from "@clack/prompts";
|
|
966
1402
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
967
1403
|
function findGuardSrc() {
|
|
968
1404
|
const candidates = [
|
|
@@ -995,41 +1431,41 @@ function currentHooksPath(root) {
|
|
|
995
1431
|
return "";
|
|
996
1432
|
}
|
|
997
1433
|
}
|
|
998
|
-
async function setupGitHooks(opts, interactive) {
|
|
1434
|
+
async function setupGitHooks(opts, interactive, reporter = clackReporter()) {
|
|
999
1435
|
const root = findGitRoot(process.cwd());
|
|
1000
1436
|
if (!root) {
|
|
1001
|
-
|
|
1437
|
+
reporter.error("Not inside a git repository (no .git found). Run this from your project root.");
|
|
1002
1438
|
return false;
|
|
1003
1439
|
}
|
|
1004
1440
|
const guardSrc = findGuardSrc();
|
|
1005
1441
|
if (!guardSrc) {
|
|
1006
|
-
|
|
1442
|
+
reporter.error("Cannot find the built guard (dist/guard.js). Run 'npm run build' first.");
|
|
1007
1443
|
return false;
|
|
1008
1444
|
}
|
|
1009
1445
|
const current = currentHooksPath(root);
|
|
1010
1446
|
if (current && current !== ".githooks" && !opts.force) {
|
|
1011
|
-
|
|
1447
|
+
reporter.warn(`core.hooksPath is already set to '${current}'.`);
|
|
1012
1448
|
if (interactive) {
|
|
1013
|
-
const ok = await
|
|
1014
|
-
if (
|
|
1015
|
-
|
|
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.");
|
|
1016
1452
|
return false;
|
|
1017
1453
|
}
|
|
1018
1454
|
} else {
|
|
1019
|
-
|
|
1455
|
+
reporter.info("Re-run with --force to override.");
|
|
1020
1456
|
return false;
|
|
1021
1457
|
}
|
|
1022
1458
|
}
|
|
1023
1459
|
let enabled = opts.protections;
|
|
1024
1460
|
if (!enabled && interactive) {
|
|
1025
|
-
const r = await
|
|
1461
|
+
const r = await p2.multiselect({
|
|
1026
1462
|
message: "Which protections should the commit guard enforce?",
|
|
1027
1463
|
options: GUARD_PROTECTIONS,
|
|
1028
1464
|
initialValues: GUARD_PROTECTIONS.map((o) => o.value),
|
|
1029
1465
|
required: true
|
|
1030
1466
|
});
|
|
1031
|
-
if (
|
|
1032
|
-
|
|
1467
|
+
if (p2.isCancel(r)) {
|
|
1468
|
+
reporter.info("Left git hooks unchanged.");
|
|
1033
1469
|
return false;
|
|
1034
1470
|
}
|
|
1035
1471
|
enabled = r;
|
|
@@ -1060,14 +1496,14 @@ async function setupGitHooks(opts, interactive) {
|
|
|
1060
1496
|
try {
|
|
1061
1497
|
execFileSync2("git", ["-C", root, "config", "core.hooksPath", ".githooks"]);
|
|
1062
1498
|
} catch (err) {
|
|
1063
|
-
|
|
1499
|
+
reporter.error(`Failed to set core.hooksPath: ${err.message}`);
|
|
1064
1500
|
return false;
|
|
1065
1501
|
}
|
|
1066
1502
|
const on = Object.entries(config).filter(([, v]) => v).map(([k]) => k);
|
|
1067
|
-
|
|
1068
|
-
|
|
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.`);
|
|
1069
1505
|
if (isOnPath("gh")) {
|
|
1070
|
-
|
|
1506
|
+
reporter.info("GitHub CLI (gh) detected: these hooks also run for commits made via gh, since gh uses git underneath.");
|
|
1071
1507
|
}
|
|
1072
1508
|
return true;
|
|
1073
1509
|
}
|
|
@@ -1076,8 +1512,8 @@ async function maybeOfferGitHooks(interactive, opts) {
|
|
|
1076
1512
|
const root = findGitRoot(process.cwd());
|
|
1077
1513
|
if (!root) return;
|
|
1078
1514
|
if (currentHooksPath(root) === ".githooks") return;
|
|
1079
|
-
const ok = await
|
|
1080
|
-
if (!
|
|
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);
|
|
1081
1517
|
}
|
|
1082
1518
|
|
|
1083
1519
|
// src/skills.ts
|
|
@@ -1238,25 +1674,22 @@ function checkSources() {
|
|
|
1238
1674
|
}
|
|
1239
1675
|
console.log(`Integrity check passed: ${checked} skill(s) well-formed and sealed.`);
|
|
1240
1676
|
}
|
|
1241
|
-
async function installSkills(opts, interactive) {
|
|
1677
|
+
async function installSkills(opts, interactive, reporter = clackReporter()) {
|
|
1242
1678
|
const available = discoverAgents();
|
|
1243
|
-
if (available.length === 0)
|
|
1244
|
-
p3.cancel("No installable agents known.");
|
|
1245
|
-
process.exit(1);
|
|
1246
|
-
}
|
|
1679
|
+
if (available.length === 0) reporter.fatal("No installable agents known.");
|
|
1247
1680
|
let scope;
|
|
1248
1681
|
if (opts.scope) {
|
|
1249
1682
|
scope = opts.scope;
|
|
1250
1683
|
} else if (interactive) {
|
|
1251
|
-
const r = await
|
|
1684
|
+
const r = await p4.select({
|
|
1252
1685
|
message: "Where should skills be installed?",
|
|
1253
1686
|
options: [
|
|
1254
1687
|
{ value: "global", label: "Global (user)", hint: "~/.claude, ~/.codex, ~/.config/opencode" },
|
|
1255
1688
|
{ value: "local", label: "Local (this project)", hint: process.cwd() }
|
|
1256
1689
|
]
|
|
1257
1690
|
});
|
|
1258
|
-
if (
|
|
1259
|
-
|
|
1691
|
+
if (p4.isCancel(r)) {
|
|
1692
|
+
p4.cancel("Aborted.");
|
|
1260
1693
|
return;
|
|
1261
1694
|
}
|
|
1262
1695
|
scope = r;
|
|
@@ -1268,19 +1701,19 @@ async function installSkills(opts, interactive) {
|
|
|
1268
1701
|
if (opts.agents.length) {
|
|
1269
1702
|
chosenAgents = available.filter((a) => opts.agents.includes(a.name));
|
|
1270
1703
|
const unknown = opts.agents.filter((n) => !available.some((a) => a.name === n));
|
|
1271
|
-
if (unknown.length)
|
|
1704
|
+
if (unknown.length) reporter.warn(`Skipping unknown/absent agents: ${unknown.join(", ")}`);
|
|
1272
1705
|
} else if (opts.allAgents) {
|
|
1273
1706
|
chosenAgents = available;
|
|
1274
1707
|
} else if (interactive && available.length > 1) {
|
|
1275
1708
|
const preselect = (detected.length ? detected : available).map((a) => a.name);
|
|
1276
|
-
const r = await
|
|
1709
|
+
const r = await p4.multiselect({
|
|
1277
1710
|
message: "Which agents? (detected on this system are preselected)",
|
|
1278
1711
|
options: available.map((a) => ({ value: a.name, label: a.label, hint: a.installed ? "detected" : "not detected" })),
|
|
1279
1712
|
initialValues: preselect,
|
|
1280
1713
|
required: true
|
|
1281
1714
|
});
|
|
1282
|
-
if (
|
|
1283
|
-
|
|
1715
|
+
if (p4.isCancel(r)) {
|
|
1716
|
+
p4.cancel("Aborted.");
|
|
1284
1717
|
return;
|
|
1285
1718
|
}
|
|
1286
1719
|
chosenAgents = available.filter((a) => r.includes(a.name));
|
|
@@ -1288,17 +1721,14 @@ async function installSkills(opts, interactive) {
|
|
|
1288
1721
|
chosenAgents = detected;
|
|
1289
1722
|
} else {
|
|
1290
1723
|
chosenAgents = available;
|
|
1291
|
-
|
|
1292
|
-
}
|
|
1293
|
-
if (chosenAgents.length === 0) {
|
|
1294
|
-
p3.cancel("No matching agents selected.");
|
|
1295
|
-
process.exit(1);
|
|
1724
|
+
reporter.warn("No installed agents detected; defaulting to all supported agents.");
|
|
1296
1725
|
}
|
|
1726
|
+
if (chosenAgents.length === 0) reporter.fatal("No matching agents selected.");
|
|
1297
1727
|
const claudeScope = chosenAgents.some((a) => a.name === "claude") ? scope : null;
|
|
1298
1728
|
const applyClaudeConfig = () => {
|
|
1299
1729
|
if (!claudeScope || opts.dryRun) return;
|
|
1300
1730
|
if (disableClaudeAttribution(claudeScope)) {
|
|
1301
|
-
|
|
1731
|
+
reporter.info("Claude Code: disabled Co-Authored-By and PR attribution in settings.json.");
|
|
1302
1732
|
}
|
|
1303
1733
|
};
|
|
1304
1734
|
const bypassAgents = await resolveBypassSelection(chosenAgents, opts, interactive);
|
|
@@ -1307,7 +1737,7 @@ async function installSkills(opts, interactive) {
|
|
|
1307
1737
|
for (const agent of chosenAgents) {
|
|
1308
1738
|
const target = agent.targets[scope];
|
|
1309
1739
|
if (!target) {
|
|
1310
|
-
|
|
1740
|
+
reporter.warn(`${agent.label} has no '${scope}' target - skipping.`);
|
|
1311
1741
|
continue;
|
|
1312
1742
|
}
|
|
1313
1743
|
const skills = inspectSkills();
|
|
@@ -1316,7 +1746,7 @@ async function installSkills(opts, interactive) {
|
|
|
1316
1746
|
if (!opts.memoryOnly && opts.skills.length) {
|
|
1317
1747
|
chosenSkills = skills.filter((s2) => opts.skills.includes(s2.name));
|
|
1318
1748
|
} else if (!opts.memoryOnly && interactive && skills.length > 1) {
|
|
1319
|
-
const r = await
|
|
1749
|
+
const r = await p4.multiselect({
|
|
1320
1750
|
message: `Skills for ${agent.label} - all selected; deselect any you don't want`,
|
|
1321
1751
|
options: skills.map((s2) => {
|
|
1322
1752
|
const st = skillStatus(join6(target.skills, s2.name), s2.meta);
|
|
@@ -1326,8 +1756,8 @@ async function installSkills(opts, interactive) {
|
|
|
1326
1756
|
initialValues: skills.map((s2) => s2.name),
|
|
1327
1757
|
required: false
|
|
1328
1758
|
});
|
|
1329
|
-
if (
|
|
1330
|
-
|
|
1759
|
+
if (p4.isCancel(r)) {
|
|
1760
|
+
p4.cancel("Aborted.");
|
|
1331
1761
|
return;
|
|
1332
1762
|
}
|
|
1333
1763
|
chosenSkills = skills.filter((s2) => r.includes(s2.name));
|
|
@@ -1344,16 +1774,16 @@ async function installSkills(opts, interactive) {
|
|
|
1344
1774
|
if (tampered.length) {
|
|
1345
1775
|
if (opts.keepModified) {
|
|
1346
1776
|
for (const s2 of tampered) s2.overwrite = false;
|
|
1347
|
-
|
|
1777
|
+
reporter.warn(`${tampered.length} locally-modified skill(s) will be kept (--keep-modified).`);
|
|
1348
1778
|
} else if (interactive && !opts.dryRun) {
|
|
1349
|
-
const sel = await
|
|
1779
|
+
const sel = await p4.multiselect({
|
|
1350
1780
|
message: `${tampered.length} skill(s) were modified locally since install. Select which to OVERWRITE`,
|
|
1351
1781
|
options: tampered.map((s2, i) => ({ value: i, label: s2.name, hint: s2.meta.version ? `v${s2.meta.version}` : "modified" })),
|
|
1352
1782
|
initialValues: tampered.map((_, i) => i),
|
|
1353
1783
|
required: false
|
|
1354
1784
|
});
|
|
1355
|
-
if (
|
|
1356
|
-
|
|
1785
|
+
if (p4.isCancel(sel)) {
|
|
1786
|
+
p4.cancel("Aborted.");
|
|
1357
1787
|
return;
|
|
1358
1788
|
}
|
|
1359
1789
|
tampered.forEach((s2, i) => {
|
|
@@ -1407,14 +1837,14 @@ async function installSkills(opts, interactive) {
|
|
|
1407
1837
|
}
|
|
1408
1838
|
}
|
|
1409
1839
|
if (nInstall + nUpdate + nRemove === 0) {
|
|
1410
|
-
|
|
1840
|
+
reporter.note(lines.join("\n"), "Nothing to do");
|
|
1411
1841
|
applyClaudeConfig();
|
|
1412
1842
|
applyBypassConfig();
|
|
1413
1843
|
await maybeOfferGitHooks(interactive, opts);
|
|
1414
|
-
|
|
1844
|
+
reporter.success(`Everything up-to-date - ${nSkip} item(s) unchanged${nKept ? `, ${nKept} kept modified` : ""} (${scope}).`);
|
|
1415
1845
|
return;
|
|
1416
1846
|
}
|
|
1417
|
-
|
|
1847
|
+
reporter.note(lines.join("\n"), opts.dryRun ? "Dry run - planned changes" : "Planned changes");
|
|
1418
1848
|
if (interactive && !opts.dryRun) {
|
|
1419
1849
|
const summary = [
|
|
1420
1850
|
nInstall && `${nInstall} to install`,
|
|
@@ -1422,21 +1852,21 @@ async function installSkills(opts, interactive) {
|
|
|
1422
1852
|
nRemove && `${nRemove} to remove`,
|
|
1423
1853
|
nSkip && `${nSkip} unchanged`
|
|
1424
1854
|
].filter(Boolean).join(", ");
|
|
1425
|
-
const ok = await
|
|
1426
|
-
if (
|
|
1427
|
-
|
|
1855
|
+
const ok = await p4.confirm({ message: `Apply: ${summary}?` });
|
|
1856
|
+
if (p4.isCancel(ok) || !ok) {
|
|
1857
|
+
p4.cancel("Aborted.");
|
|
1428
1858
|
return;
|
|
1429
1859
|
}
|
|
1430
1860
|
}
|
|
1431
1861
|
if (opts.dryRun) {
|
|
1432
1862
|
applyBypassConfig();
|
|
1433
|
-
|
|
1863
|
+
reporter.info("Dry run complete - no files written.");
|
|
1434
1864
|
return;
|
|
1435
1865
|
}
|
|
1436
1866
|
const changedAgents = plan.filter(
|
|
1437
1867
|
(x) => x.skills.some(willCopy) || x.memory.some((m) => memoryStatus(m.src, join6(x.target.memory, m.name)) !== "identical") || x.prune.length > 0
|
|
1438
1868
|
);
|
|
1439
|
-
const s =
|
|
1869
|
+
const s = reporter.spinner();
|
|
1440
1870
|
s.start("Installing...");
|
|
1441
1871
|
let copied = 0;
|
|
1442
1872
|
try {
|
|
@@ -1457,22 +1887,21 @@ async function installSkills(opts, interactive) {
|
|
|
1457
1887
|
}
|
|
1458
1888
|
} catch (err) {
|
|
1459
1889
|
s.stop("Failed.");
|
|
1460
|
-
|
|
1461
|
-
process.exit(1);
|
|
1890
|
+
reporter.fatal(`Error while installing: ${err.message}`);
|
|
1462
1891
|
}
|
|
1463
1892
|
s.stop(`Wrote ${copied} item(s)${nRemove ? `, removed ${nRemove}` : ""}.`);
|
|
1464
1893
|
applyClaudeConfig();
|
|
1465
1894
|
applyBypassConfig();
|
|
1466
1895
|
await maybeOfferGitHooks(interactive, opts);
|
|
1467
|
-
|
|
1896
|
+
reporter.success(`${nInstall} installed, ${nUpdate} updated/overwritten` + (nRemove ? `, ${nRemove} removed` : "") + (nSkip ? `, ${nSkip} unchanged` : "") + (nKept ? `, ${nKept} kept modified` : "") + ` (${scope}).`);
|
|
1468
1897
|
if (changedAgents.length) {
|
|
1469
1898
|
const { known, running } = runningStatus(changedAgents.map((x) => x.agent));
|
|
1470
1899
|
if (running.size) {
|
|
1471
1900
|
const names = changedAgents.filter((x) => running.has(x.agent.name)).map((x) => x.agent.label);
|
|
1472
|
-
|
|
1901
|
+
reporter.warn(`Restart ${names.join(", ")} to apply the changes (running now).`);
|
|
1473
1902
|
} else if (!known) {
|
|
1474
1903
|
const names = changedAgents.map((x) => x.agent.label);
|
|
1475
|
-
|
|
1904
|
+
reporter.info(`If any of these agents are running, restart them to apply the changes: ${names.join(", ")}.`);
|
|
1476
1905
|
}
|
|
1477
1906
|
}
|
|
1478
1907
|
}
|
|
@@ -1681,7 +2110,7 @@ import { homedir as homedir5 } from "os";
|
|
|
1681
2110
|
import { join as join9 } from "path";
|
|
1682
2111
|
import { writeFileSync as writeFileSync6 } from "fs";
|
|
1683
2112
|
import { spawn, spawnSync } from "child_process";
|
|
1684
|
-
import * as
|
|
2113
|
+
import * as p5 from "@clack/prompts";
|
|
1685
2114
|
var REGISTRY_URL = "https://registry.npmjs.org/enigma-cli/latest";
|
|
1686
2115
|
var UPDATE_COMMAND = "npm i -g enigma-cli@latest";
|
|
1687
2116
|
var CACHE_FILE = join9(homedir5(), ".enigma-update-check.json");
|
|
@@ -1766,8 +2195,8 @@ async function notifyUpdate(current, interactive) {
|
|
|
1766
2195
|
${renderUpdateBox(current, latest)}
|
|
1767
2196
|
`);
|
|
1768
2197
|
if (!interactive) return;
|
|
1769
|
-
const ok = await
|
|
1770
|
-
if (
|
|
2198
|
+
const ok = await p5.confirm({ message: `Update now with ${UPDATE_COMMAND}?`, initialValue: true });
|
|
2199
|
+
if (p5.isCancel(ok) || !ok) return;
|
|
1771
2200
|
runUpdate();
|
|
1772
2201
|
} catch {
|
|
1773
2202
|
}
|
|
@@ -1948,16 +2377,16 @@ async function run(argv) {
|
|
|
1948
2377
|
process.exit(await runConfigCli(opts.positionals, opts.scope, interactive));
|
|
1949
2378
|
}
|
|
1950
2379
|
if (opts.command === "install") {
|
|
1951
|
-
|
|
2380
|
+
p6.intro("enigma - install agent skills");
|
|
1952
2381
|
await installSkills(opts, interactive);
|
|
1953
|
-
|
|
2382
|
+
p6.outro("Done.");
|
|
1954
2383
|
await notifyUpdate(version, interactive);
|
|
1955
2384
|
return;
|
|
1956
2385
|
}
|
|
1957
2386
|
if (opts.command === "security") {
|
|
1958
|
-
|
|
2387
|
+
p6.intro("enigma - git security hooks");
|
|
1959
2388
|
const done = await setupGitHooks(opts, interactive);
|
|
1960
|
-
|
|
2389
|
+
p6.outro(done ? "Git hooks configured." : "No changes made.");
|
|
1961
2390
|
await notifyUpdate(version, interactive);
|
|
1962
2391
|
return;
|
|
1963
2392
|
}
|
|
@@ -1966,19 +2395,23 @@ async function run(argv) {
|
|
|
1966
2395
|
await notifyUpdate(version, interactive);
|
|
1967
2396
|
return;
|
|
1968
2397
|
}
|
|
1969
|
-
const { runHomeTui:
|
|
1970
|
-
await
|
|
2398
|
+
const { runHomeTui: runHomeTui3 } = isBun() ? await Promise.resolve().then(() => (init_opentui(), opentui_exports)) : await Promise.resolve().then(() => (init_settings(), settings_exports));
|
|
2399
|
+
await runHomeTui3({
|
|
1971
2400
|
agents: discoverAgents().map((a) => ({ name: a.name, label: a.label, installed: a.installed })),
|
|
1972
2401
|
protections: GUARD_PROTECTIONS,
|
|
1973
|
-
runAction: async (
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
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 };
|
|
1982
2415
|
}
|
|
1983
2416
|
}
|
|
1984
2417
|
});
|