centaurus-cli 3.1.3 → 3.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-adapter.js +685 -153
- package/dist/cli-adapter.js.map +1 -1
- package/dist/config/defaultConfig.js +1 -4
- package/dist/config/defaultConfig.js.map +1 -1
- package/dist/config/models.js +4 -0
- package/dist/config/models.js.map +1 -1
- package/dist/config/slash-commands.js +66 -2
- package/dist/config/slash-commands.js.map +1 -1
- package/dist/config/types.js +4 -4
- package/dist/config/types.js.map +1 -1
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -1
- package/dist/services/ai-context-injector.js +109 -0
- package/dist/services/ai-context-injector.js.map +1 -1
- package/dist/services/api-client.js.map +1 -1
- package/dist/services/background-task-manager.js +59 -0
- package/dist/services/background-task-manager.js.map +1 -1
- package/dist/services/local-chat-storage.js +2 -0
- package/dist/services/local-chat-storage.js.map +1 -1
- package/dist/services/skill-storage.js +141 -0
- package/dist/services/skill-storage.js.map +1 -0
- package/dist/services/sub-agent-manager.js +49 -8
- package/dist/services/sub-agent-manager.js.map +1 -1
- package/dist/services/warpify-detector.js +17 -5
- package/dist/services/warpify-detector.js.map +1 -1
- package/dist/tools/background-command.js +5 -2
- package/dist/tools/background-command.js.map +1 -1
- package/dist/tools/command.js +367 -109
- package/dist/tools/command.js.map +1 -1
- package/dist/tools/file-ops.js +23 -6
- package/dist/tools/file-ops.js.map +1 -1
- package/dist/tools/plan-mode.js +184 -336
- package/dist/tools/plan-mode.js.map +1 -1
- package/dist/tools/sub-agent.js +24 -5
- package/dist/tools/sub-agent.js.map +1 -1
- package/dist/tools/todo-list.js +157 -0
- package/dist/tools/todo-list.js.map +1 -0
- package/dist/types/skill.js +30 -0
- package/dist/types/skill.js.map +1 -0
- package/dist/ui/components/App.js +956 -162
- package/dist/ui/components/App.js.map +1 -1
- package/dist/ui/components/AuthScreen.js +3 -1
- package/dist/ui/components/AuthScreen.js.map +1 -1
- package/dist/ui/components/AuthWelcomeScreen.js +3 -1
- package/dist/ui/components/AuthWelcomeScreen.js.map +1 -1
- package/dist/ui/components/CodeBlock.js +3 -1
- package/dist/ui/components/CodeBlock.js.map +1 -1
- package/dist/ui/components/CompactShellPreview.js +44 -0
- package/dist/ui/components/CompactShellPreview.js.map +1 -0
- package/dist/ui/components/ConfigViewer.js +3 -1
- package/dist/ui/components/ConfigViewer.js.map +1 -1
- package/dist/ui/components/ConfirmPrompt.js +3 -1
- package/dist/ui/components/ConfirmPrompt.js.map +1 -1
- package/dist/ui/components/ConnectionStatusMessage.js +3 -1
- package/dist/ui/components/ConnectionStatusMessage.js.map +1 -1
- package/dist/ui/components/DetailedPlanReviewScreen.js +84 -74
- package/dist/ui/components/DetailedPlanReviewScreen.js.map +1 -1
- package/dist/ui/components/DiffViewer.js +6 -3
- package/dist/ui/components/DiffViewer.js.map +1 -1
- package/dist/ui/components/FileCreationPreview.js.map +1 -1
- package/dist/ui/components/FileTagAutocomplete.js +4 -2
- package/dist/ui/components/FileTagAutocomplete.js.map +1 -1
- package/dist/ui/components/InputBox.js +243 -40
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/InteractiveShell.js +5 -3
- package/dist/ui/components/InteractiveShell.js.map +1 -1
- package/dist/ui/components/KeyboardHelp.js +4 -1
- package/dist/ui/components/KeyboardHelp.js.map +1 -1
- package/dist/ui/components/LoadingIndicator.js +3 -1
- package/dist/ui/components/LoadingIndicator.js.map +1 -1
- package/dist/ui/components/MCPAddScreen.js +63 -13
- package/dist/ui/components/MCPAddScreen.js.map +1 -1
- package/dist/ui/components/MarkdownRenderer.js +3 -1
- package/dist/ui/components/MarkdownRenderer.js.map +1 -1
- package/dist/ui/components/MessageDisplay.js +9 -7
- package/dist/ui/components/MessageDisplay.js.map +1 -1
- package/dist/ui/components/ModelPicker.js +170 -0
- package/dist/ui/components/ModelPicker.js.map +1 -0
- package/dist/ui/components/MonitorModeAIPanel.js +3 -1
- package/dist/ui/components/MonitorModeAIPanel.js.map +1 -1
- package/dist/ui/components/PlanAcceptedMessage.js +12 -6
- package/dist/ui/components/PlanAcceptedMessage.js.map +1 -1
- package/dist/ui/components/PlanQuestionMessage.js +37 -0
- package/dist/ui/components/PlanQuestionMessage.js.map +1 -0
- package/dist/ui/components/PlanQuestionScreen.js +138 -0
- package/dist/ui/components/PlanQuestionScreen.js.map +1 -0
- package/dist/ui/components/PlanReviewScreen.js +7 -9
- package/dist/ui/components/PlanReviewScreen.js.map +1 -1
- package/dist/ui/components/RulesEditorScreen.js +65 -28
- package/dist/ui/components/RulesEditorScreen.js.map +1 -1
- package/dist/ui/components/SelectPrompt.js +3 -1
- package/dist/ui/components/SelectPrompt.js.map +1 -1
- package/dist/ui/components/SkillCreatorScreen.js +217 -0
- package/dist/ui/components/SkillCreatorScreen.js.map +1 -0
- package/dist/ui/components/SlashCommandAutocomplete.js +4 -2
- package/dist/ui/components/SlashCommandAutocomplete.js.map +1 -1
- package/dist/ui/components/StatusBar.js +4 -2
- package/dist/ui/components/StatusBar.js.map +1 -1
- package/dist/ui/components/StreamingMessageDisplay.js +5 -3
- package/dist/ui/components/StreamingMessageDisplay.js.map +1 -1
- package/dist/ui/components/SubAgentListScreen.js +65 -0
- package/dist/ui/components/SubAgentListScreen.js.map +1 -0
- package/dist/ui/components/SubAgentViewScreen.js +123 -0
- package/dist/ui/components/SubAgentViewScreen.js.map +1 -0
- package/dist/ui/components/TaskCompletedMessage.js +40 -8
- package/dist/ui/components/TaskCompletedMessage.js.map +1 -1
- package/dist/ui/components/TaskProgressIndicator.js +6 -4
- package/dist/ui/components/TaskProgressIndicator.js.map +1 -1
- package/dist/ui/components/TextEditor.js +297 -0
- package/dist/ui/components/TextEditor.js.map +1 -0
- package/dist/ui/components/TodoListMessage.js +59 -0
- package/dist/ui/components/TodoListMessage.js.map +1 -0
- package/dist/ui/components/ToolExecutionMessage.js +134 -84
- package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
- package/dist/ui/components/ToolExecutionStatus.js +3 -1
- package/dist/ui/components/ToolExecutionStatus.js.map +1 -1
- package/dist/ui/components/WelcomeBanner.js +33 -33
- package/dist/ui/components/WelcomeBanner.js.map +1 -1
- package/dist/ui/components/WorkflowCreatorScreen.js +5 -3
- package/dist/ui/components/WorkflowCreatorScreen.js.map +1 -1
- package/dist/ui/theme.js +97 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/ui/utils/chat-history-limit.js +247 -0
- package/dist/ui/utils/chat-history-limit.js.map +1 -0
- package/dist/utils/chat-formatter.js +22 -9
- package/dist/utils/chat-formatter.js.map +1 -1
- package/dist/utils/input-classifier.js +11 -1
- package/dist/utils/input-classifier.js.map +1 -1
- package/dist/utils/output-truncation.js +175 -0
- package/dist/utils/output-truncation.js.map +1 -0
- package/dist/utils/rule-reference-resolver.js +3 -3
- package/dist/utils/rule-reference-resolver.js.map +1 -1
- package/dist/utils/tunnel-commands-manager.js +134 -0
- package/dist/utils/tunnel-commands-manager.js.map +1 -0
- package/package.json +91 -90
- package/postinstall.js +4 -11
- package/dist/ui/components/MultiLineInput.js +0 -255
- package/dist/ui/components/MultiLineInput.js.map +0 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import { useTheme } from "../theme.js";
|
|
3
4
|
const PlanReviewScreen = ({
|
|
4
5
|
plan,
|
|
5
6
|
onApprove,
|
|
6
7
|
onEdit
|
|
7
8
|
}) => {
|
|
9
|
+
const theme = useTheme();
|
|
8
10
|
useInput((input, key) => {
|
|
9
11
|
if (key.return || input.toLowerCase() === "y") {
|
|
10
12
|
onApprove();
|
|
@@ -21,8 +23,8 @@ const PlanReviewScreen = ({
|
|
|
21
23
|
paddingY: 1,
|
|
22
24
|
marginBottom: 1
|
|
23
25
|
},
|
|
24
|
-
/* @__PURE__ */ React.createElement(Text, { color: "#ffaa00", bold: true }, "
|
|
25
|
-
), /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color:
|
|
26
|
+
/* @__PURE__ */ React.createElement(Text, { color: "#ffaa00", bold: true }, "PLAN REVIEW")
|
|
27
|
+
), /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: theme.accent, bold: true }, plan.title)), /* @__PURE__ */ React.createElement(
|
|
26
28
|
Box,
|
|
27
29
|
{
|
|
28
30
|
flexDirection: "column",
|
|
@@ -32,13 +34,9 @@ const PlanReviewScreen = ({
|
|
|
32
34
|
paddingY: 1,
|
|
33
35
|
marginBottom: 1
|
|
34
36
|
},
|
|
35
|
-
/* @__PURE__ */ React.createElement(Text, { color: "#888888", dimColor: true }, "
|
|
36
|
-
/* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginTop: 1 }, plan.
|
|
37
|
-
|
|
38
|
-
const complexityLabel = step.estimatedComplexity ? ` [${step.estimatedComplexity}]` : "";
|
|
39
|
-
return /* @__PURE__ */ React.createElement(Box, { key: step.id }, /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, index + 1, ". "), /* @__PURE__ */ React.createElement(Text, null, step.description), step.estimatedComplexity && /* @__PURE__ */ React.createElement(Text, { color: complexityColor }, complexityLabel));
|
|
40
|
-
}))
|
|
41
|
-
), /* @__PURE__ */ React.createElement(Box, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, "Press "), /* @__PURE__ */ React.createElement(Text, { color: "#00cc66", bold: true }, "Enter"), /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, " or "), /* @__PURE__ */ React.createElement(Text, { color: "#00cc66", bold: true }, "Y"), /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, " to proceed with plan")), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, "Press "), /* @__PURE__ */ React.createElement(Text, { color: "#ffaa00", bold: true }, "E"), /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, " or "), /* @__PURE__ */ React.createElement(Text, { color: "#ffaa00", bold: true }, "N"), /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, " to edit plan (provide feedback)"))));
|
|
37
|
+
/* @__PURE__ */ React.createElement(Text, { color: "#888888", dimColor: true }, "Implementation steps:"),
|
|
38
|
+
/* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginTop: 1 }, plan.implementationSteps.map((step) => /* @__PURE__ */ React.createElement(Box, { key: step.id }, /* @__PURE__ */ React.createElement(Text, null, step.description))))
|
|
39
|
+
), /* @__PURE__ */ React.createElement(Box, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, "Press "), /* @__PURE__ */ React.createElement(Text, { color: "#00cc66", bold: true }, "Enter"), /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, " or "), /* @__PURE__ */ React.createElement(Text, { color: "#00cc66", bold: true }, "Y"), /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, " to implement")), /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, "Press "), /* @__PURE__ */ React.createElement(Text, { color: "#ffaa00", bold: true }, "E"), /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, " or "), /* @__PURE__ */ React.createElement(Text, { color: "#ffaa00", bold: true }, "N"), /* @__PURE__ */ React.createElement(Text, { color: "#888888" }, " to cancel"))));
|
|
42
40
|
};
|
|
43
41
|
var PlanReviewScreen_default = PlanReviewScreen;
|
|
44
42
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/ui/components/PlanReviewScreen.tsx"],"sourcesContent":["/**\r\n * PlanReviewScreen Component\r\n *
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/components/PlanReviewScreen.tsx"],"sourcesContent":["/**\r\n * PlanReviewScreen Component\r\n * Simple plan review screen (legacy - DetailedPlanReviewScreen is the primary screen)\r\n */\r\n\r\nimport React from 'react';\r\nimport { Box, Text, useInput } from 'ink';\r\nimport { Plan } from '../../tools/plan-mode.js';\r\nimport { useTheme } from '../theme.js';\r\n\r\ninterface PlanReviewScreenProps {\r\n plan: Plan;\r\n onApprove: () => void;\r\n onEdit: () => void;\r\n}\r\n\r\nexport const PlanReviewScreen: React.FC<PlanReviewScreenProps> = ({\r\n plan,\r\n onApprove,\r\n onEdit\r\n}) => {\r\n const theme = useTheme();\r\n\r\n // Handle keyboard input\r\n useInput((input, key) => {\r\n if (key.return || input.toLowerCase() === 'y') {\r\n onApprove();\r\n } else if (input.toLowerCase() === 'e' || input.toLowerCase() === 'n') {\r\n onEdit();\r\n }\r\n });\r\n\r\n return (\r\n <Box flexDirection=\"column\" paddingX={2} paddingY={1}>\r\n {/* Header */}\r\n <Box\r\n borderStyle=\"double\"\r\n borderColor=\"#ffaa00\"\r\n paddingX={2}\r\n paddingY={1}\r\n marginBottom={1}\r\n >\r\n <Text color=\"#ffaa00\" bold>PLAN REVIEW</Text>\r\n </Box>\r\n\r\n {/* Plan Title */}\r\n <Box marginBottom={1}>\r\n <Text color={theme.accent} bold>{plan.title}</Text>\r\n </Box>\r\n\r\n {/* Steps List */}\r\n <Box\r\n flexDirection=\"column\"\r\n borderStyle=\"round\"\r\n borderColor=\"#666666\"\r\n paddingX={1}\r\n paddingY={1}\r\n marginBottom={1}\r\n >\r\n <Text color=\"#888888\" dimColor>Implementation steps:</Text>\r\n <Box flexDirection=\"column\" marginTop={1}>\r\n {plan.implementationSteps.map((step) => (\r\n <Box key={step.id}>\r\n <Text>{step.description}</Text>\r\n </Box>\r\n ))}\r\n </Box>\r\n </Box>\r\n\r\n {/* Action Buttons */}\r\n <Box marginTop={1} flexDirection=\"column\">\r\n <Box>\r\n <Text color=\"#888888\">Press </Text>\r\n <Text color=\"#00cc66\" bold>Enter</Text>\r\n <Text color=\"#888888\"> or </Text>\r\n <Text color=\"#00cc66\" bold>Y</Text>\r\n <Text color=\"#888888\"> to implement</Text>\r\n </Box>\r\n <Box>\r\n <Text color=\"#888888\">Press </Text>\r\n <Text color=\"#ffaa00\" bold>E</Text>\r\n <Text color=\"#888888\"> or </Text>\r\n <Text color=\"#ffaa00\" bold>N</Text>\r\n <Text color=\"#888888\"> to cancel</Text>\r\n </Box>\r\n </Box>\r\n </Box>\r\n );\r\n};\r\n\r\nexport default PlanReviewScreen;\r\n"],"mappings":"AAKA,OAAO,WAAW;AAClB,SAAS,KAAK,MAAM,gBAAgB;AAEpC,SAAS,gBAAgB;AAQlB,MAAM,mBAAoD,CAAC;AAAA,EAC9D;AAAA,EACA;AAAA,EACA;AACJ,MAAM;AACF,QAAM,QAAQ,SAAS;AAGvB,WAAS,CAAC,OAAO,QAAQ;AACrB,QAAI,IAAI,UAAU,MAAM,YAAY,MAAM,KAAK;AAC3C,gBAAU;AAAA,IACd,WAAW,MAAM,YAAY,MAAM,OAAO,MAAM,YAAY,MAAM,KAAK;AACnE,aAAO;AAAA,IACX;AAAA,EACJ,CAAC;AAED,SACI,oCAAC,OAAI,eAAc,UAAS,UAAU,GAAG,UAAU,KAE/C;AAAA,IAAC;AAAA;AAAA,MACG,aAAY;AAAA,MACZ,aAAY;AAAA,MACZ,UAAU;AAAA,MACV,UAAU;AAAA,MACV,cAAc;AAAA;AAAA,IAEd,oCAAC,QAAK,OAAM,WAAU,MAAI,QAAC,aAAW;AAAA,EAC1C,GAGA,oCAAC,OAAI,cAAc,KACf,oCAAC,QAAK,OAAO,MAAM,QAAQ,MAAI,QAAE,KAAK,KAAM,CAChD,GAGA;AAAA,IAAC;AAAA;AAAA,MACG,eAAc;AAAA,MACd,aAAY;AAAA,MACZ,aAAY;AAAA,MACZ,UAAU;AAAA,MACV,UAAU;AAAA,MACV,cAAc;AAAA;AAAA,IAEd,oCAAC,QAAK,OAAM,WAAU,UAAQ,QAAC,uBAAqB;AAAA,IACpD,oCAAC,OAAI,eAAc,UAAS,WAAW,KAClC,KAAK,oBAAoB,IAAI,CAAC,SAC3B,oCAAC,OAAI,KAAK,KAAK,MACX,oCAAC,YAAM,KAAK,WAAY,CAC5B,CACH,CACL;AAAA,EACJ,GAGA,oCAAC,OAAI,WAAW,GAAG,eAAc,YAC7B,oCAAC,WACG,oCAAC,QAAK,OAAM,aAAU,QAAM,GAC5B,oCAAC,QAAK,OAAM,WAAU,MAAI,QAAC,OAAK,GAChC,oCAAC,QAAK,OAAM,aAAU,MAAI,GAC1B,oCAAC,QAAK,OAAM,WAAU,MAAI,QAAC,GAAC,GAC5B,oCAAC,QAAK,OAAM,aAAU,eAAa,CACvC,GACA,oCAAC,WACG,oCAAC,QAAK,OAAM,aAAU,QAAM,GAC5B,oCAAC,QAAK,OAAM,WAAU,MAAI,QAAC,GAAC,GAC5B,oCAAC,QAAK,OAAM,aAAU,MAAI,GAC1B,oCAAC,QAAK,OAAM,WAAU,MAAI,QAAC,GAAC,GAC5B,oCAAC,QAAK,OAAM,aAAU,YAAU,CACpC,CACJ,CACJ;AAER;AAEA,IAAO,2BAAQ;","names":[]}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import React, { useCallback, useState } from "react";
|
|
2
|
-
import { Box, Text,
|
|
1
|
+
import React, { useCallback, useState, useEffect, useRef } from "react";
|
|
2
|
+
import { Box, Text, useStdin } from "ink";
|
|
3
3
|
import TextInput from "ink-text-input";
|
|
4
|
-
import {
|
|
4
|
+
import { TextEditor } from "./TextEditor.js";
|
|
5
|
+
import { useTheme } from "../theme.js";
|
|
6
|
+
import * as readline from "readline";
|
|
5
7
|
const RulesEditorScreen = ({
|
|
6
8
|
mode,
|
|
7
9
|
initialName,
|
|
@@ -9,63 +11,98 @@ const RulesEditorScreen = ({
|
|
|
9
11
|
onSave,
|
|
10
12
|
onCancel
|
|
11
13
|
}) => {
|
|
14
|
+
const theme = useTheme();
|
|
15
|
+
const { stdin, setRawMode } = useStdin();
|
|
12
16
|
const [editorState, setEditorState] = useState("editing");
|
|
13
17
|
const [content, setContent] = useState(initialContent || "");
|
|
14
18
|
const [ruleName, setRuleName] = useState(initialName || "");
|
|
15
19
|
const [error, setError] = useState(null);
|
|
20
|
+
const emitInitRef = useRef(false);
|
|
21
|
+
const escTimerRef = useRef(null);
|
|
22
|
+
const stateRef = useRef(editorState);
|
|
23
|
+
const contentRef = useRef(content);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
stateRef.current = editorState;
|
|
26
|
+
}, [editorState]);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
contentRef.current = content;
|
|
29
|
+
}, [content]);
|
|
16
30
|
const screenTitle = mode === "edit" ? "Edit Rule" : "Create Rule";
|
|
17
31
|
const editorViewportHeight = 12;
|
|
18
32
|
const showTransientError = useCallback((message) => {
|
|
19
33
|
setError(message);
|
|
20
34
|
setTimeout(() => setError(null), 3e3);
|
|
21
35
|
}, []);
|
|
22
|
-
useInput((input, key) => {
|
|
23
|
-
if (key.escape) {
|
|
24
|
-
onCancel();
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
if (editorState === "editing" && key.ctrl && input.toLowerCase() === "s") {
|
|
28
|
-
if (!content.trim()) {
|
|
29
|
-
showTransientError("Rule content cannot be empty.");
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
setEditorState("naming");
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
36
|
const handleNameSubmit = useCallback((name) => {
|
|
36
37
|
if (!name.trim()) {
|
|
37
38
|
showTransientError("Rule name is required.");
|
|
38
39
|
return;
|
|
39
40
|
}
|
|
40
|
-
const result = onSave(name.trim(),
|
|
41
|
+
const result = onSave(name.trim(), contentRef.current, initialName);
|
|
41
42
|
if (!result.success) {
|
|
42
43
|
showTransientError(result.error || "Failed to save rule.");
|
|
43
44
|
}
|
|
44
|
-
}, [
|
|
45
|
-
|
|
45
|
+
}, [initialName, onSave, showTransientError]);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!stdin) return;
|
|
48
|
+
setRawMode(true);
|
|
49
|
+
if (!emitInitRef.current) {
|
|
50
|
+
readline.emitKeypressEvents(stdin);
|
|
51
|
+
emitInitRef.current = true;
|
|
52
|
+
}
|
|
53
|
+
const handler = (_str, key) => {
|
|
54
|
+
if (!key) return;
|
|
55
|
+
const name = key.name || "";
|
|
56
|
+
const ctrl = !!key.ctrl;
|
|
57
|
+
if (name !== "escape" && escTimerRef.current) {
|
|
58
|
+
clearTimeout(escTimerRef.current);
|
|
59
|
+
escTimerRef.current = null;
|
|
60
|
+
}
|
|
61
|
+
if (name === "escape") {
|
|
62
|
+
if (escTimerRef.current) clearTimeout(escTimerRef.current);
|
|
63
|
+
escTimerRef.current = setTimeout(() => {
|
|
64
|
+
escTimerRef.current = null;
|
|
65
|
+
onCancel();
|
|
66
|
+
}, 50);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (stateRef.current === "editing" && ctrl && name === "s") {
|
|
70
|
+
if (!contentRef.current.trim()) {
|
|
71
|
+
showTransientError("Rule content cannot be empty.");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
setEditorState("naming");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
stdin.on("keypress", handler);
|
|
79
|
+
return () => {
|
|
80
|
+
stdin.off("keypress", handler);
|
|
81
|
+
if (escTimerRef.current) clearTimeout(escTimerRef.current);
|
|
82
|
+
};
|
|
83
|
+
}, [stdin, setRawMode, onCancel, showTransientError]);
|
|
84
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React.createElement(Box, { borderStyle: "round", borderColor: theme.accent, paddingX: 2, paddingY: 1, marginBottom: 1 }, /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: theme.accent, bold: true }, screenTitle), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Write reusable instructions here. Save them, then reference them inside prompts with", /* @__PURE__ */ React.createElement(Text, { color: "#ffd700" }, " @rules:<name>"), ".")), /* @__PURE__ */ React.createElement(Box, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, /* @__PURE__ */ React.createElement(Text, { color: "#ffd700" }, "Enter"), " New line ", /* @__PURE__ */ React.createElement(Text, { color: "#ffd700" }, "Ctrl+S"), " Save ", /* @__PURE__ */ React.createElement(Text, { color: "#ffd700" }, "ESC"), " Cancel"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, /* @__PURE__ */ React.createElement(Text, { color: "#ffd700" }, "Ctrl+\u2190/\u2192"), " Word jump ", /* @__PURE__ */ React.createElement(Text, { color: "#ffd700" }, "Ctrl+A/E"), " Home/End ", /* @__PURE__ */ React.createElement(Text, { color: "#ffd700" }, "Opt+\u2190/\u2192"), " Word jump (macOS)"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, /* @__PURE__ */ React.createElement(Text, { color: "#ffd700" }, "Ctrl+K"), " Kill to EOL ", /* @__PURE__ */ React.createElement(Text, { color: "#ffd700" }, "Ctrl+U"), " Kill to BOL ", /* @__PURE__ */ React.createElement(Text, { color: "#ffd700" }, "Ctrl+W"), " Del word ", /* @__PURE__ */ React.createElement(Text, { color: "#ffd700" }, "Ctrl+Z"), " Undo")))), error && /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "red" }, "\u26A0\uFE0F ", error)), editorState === "editing" && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, initialName && /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Editing rule: ", /* @__PURE__ */ React.createElement(Text, { color: theme.accent, bold: true }, initialName))), /* @__PURE__ */ React.createElement(
|
|
46
85
|
Box,
|
|
47
86
|
{
|
|
48
|
-
borderStyle: "
|
|
49
|
-
borderColor:
|
|
87
|
+
borderStyle: "single",
|
|
88
|
+
borderColor: theme.border,
|
|
50
89
|
paddingX: 1,
|
|
51
90
|
paddingY: 0,
|
|
52
|
-
width: "100%",
|
|
53
91
|
minHeight: editorViewportHeight + 2
|
|
54
92
|
},
|
|
55
93
|
/* @__PURE__ */ React.createElement(
|
|
56
|
-
|
|
94
|
+
TextEditor,
|
|
57
95
|
{
|
|
58
96
|
value: content,
|
|
59
97
|
onChange: setContent,
|
|
60
|
-
onSubmit: () => {
|
|
61
|
-
},
|
|
62
|
-
submitOnEnter: false,
|
|
63
98
|
minHeight: editorViewportHeight,
|
|
64
99
|
maxHeight: editorViewportHeight,
|
|
65
|
-
placeholder: "Type reusable instructions here..."
|
|
100
|
+
placeholder: "Type reusable instructions here...",
|
|
101
|
+
tabHandledExternally: false,
|
|
102
|
+
width: Math.max(20, (process.stdout.columns || 80) - 10)
|
|
66
103
|
}
|
|
67
104
|
)
|
|
68
|
-
)), editorState === "naming" && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { bold: true, color:
|
|
105
|
+
)), editorState === "naming" && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: theme.accent }, "Save Rule"), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Rule name: "), /* @__PURE__ */ React.createElement(
|
|
69
106
|
TextInput,
|
|
70
107
|
{
|
|
71
108
|
value: ruleName,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/ui/components/RulesEditorScreen.tsx"],"sourcesContent":["/**\r\n * Rules Editor Screen\r\n *\r\n * A multiline editor for reusable prompt rules referenced via @rules:<name>.\r\n */\r\n\r\nimport React, { useCallback, useState } from 'react';\r\nimport { Box, Text, useInput } from 'ink';\r\nimport TextInput from 'ink-text-input';\r\nimport { MultiLineInput } from './MultiLineInput.js';\r\nimport { SaveRuleResult } from '../../types/rule.js';\r\n\r\ninterface RulesEditorScreenProps {\r\n mode: 'add' | 'edit';\r\n initialName?: string;\r\n initialContent?: string;\r\n onSave: (name: string, content: string, previousName?: string) => SaveRuleResult;\r\n onCancel: () => void;\r\n}\r\n\r\ntype EditorState = 'editing' | 'naming';\r\n\r\nexport const RulesEditorScreen: React.FC<RulesEditorScreenProps> = ({\r\n mode,\r\n initialName,\r\n initialContent,\r\n onSave,\r\n onCancel\r\n}) => {\r\n const [editorState, setEditorState] = useState<EditorState>('editing');\r\n const [content, setContent] = useState(initialContent || '');\r\n const [ruleName, setRuleName] = useState(initialName || '');\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const screenTitle = mode === 'edit' ? 'Edit Rule' : 'Create Rule';\r\n const editorViewportHeight = 12;\r\n\r\n const showTransientError = useCallback((message: string) => {\r\n setError(message);\r\n setTimeout(() => setError(null), 3000);\r\n }, []);\r\n\r\n useInput((input, key) => {\r\n if (key.escape) {\r\n onCancel();\r\n return;\r\n }\r\n\r\n if (editorState === 'editing' && key.ctrl && input.toLowerCase() === 's') {\r\n if (!content.trim()) {\r\n showTransientError('Rule content cannot be empty.');\r\n return;\r\n }\r\n\r\n setEditorState('naming');\r\n }\r\n });\r\n\r\n const handleNameSubmit = useCallback((name: string) => {\r\n if (!name.trim()) {\r\n showTransientError('Rule name is required.');\r\n return;\r\n }\r\n\r\n const result = onSave(name.trim(), content, initialName);\r\n if (!result.success) {\r\n showTransientError(result.error || 'Failed to save rule.');\r\n }\r\n }, [content, initialName, onSave, showTransientError]);\r\n\r\n return (\r\n <Box flexDirection=\"column\" paddingX={1}>\r\n <Box borderStyle=\"round\" borderColor=\"#00ccff\" paddingX={2} paddingY={1} marginBottom={1}>\r\n <Box flexDirection=\"column\">\r\n <Text color=\"#00ccff\" bold>{screenTitle}</Text>\r\n <Box marginTop={1}>\r\n <Text dimColor>\r\n Write reusable instructions here. Save them, then reference them inside prompts with\r\n <Text color=\"#ffd700\"> @rules:<name></Text>.\r\n </Text>\r\n </Box>\r\n <Box marginTop={1} flexDirection=\"column\">\r\n <Text dimColor>\r\n <Text color=\"#ffd700\">Enter</Text> New line\r\n </Text>\r\n <Text dimColor>\r\n <Text color=\"#ffd700\">Ctrl+S</Text> Save and name this rule\r\n </Text>\r\n <Text dimColor>\r\n <Text color=\"#ffd700\">Ctrl/Cmd+Arrow</Text> Move by word\r\n </Text>\r\n <Text dimColor>\r\n <Text color=\"#ffd700\">Home / End</Text> Jump within the current line\r\n </Text>\r\n <Text dimColor>\r\n <Text color=\"#ffd700\">ESC</Text> Cancel\r\n </Text>\r\n </Box>\r\n </Box>\r\n </Box>\r\n\r\n {error && (\r\n <Box marginBottom={1}>\r\n <Text color=\"red\">⚠️ {error}</Text>\r\n </Box>\r\n )}\r\n\r\n {editorState === 'editing' && (\r\n <Box flexDirection=\"column\">\r\n {initialName && (\r\n <Box marginBottom={1}>\r\n <Text dimColor>\r\n Editing rule: <Text color=\"#00ccff\" bold>{initialName}</Text>\r\n </Text>\r\n </Box>\r\n )}\r\n <Box\r\n borderStyle=\"round\"\r\n borderColor=\"#257aa5ff\"\r\n paddingX={1}\r\n paddingY={0}\r\n width=\"100%\"\r\n minHeight={editorViewportHeight + 2}\r\n >\r\n <MultiLineInput\r\n value={content}\r\n onChange={setContent}\r\n onSubmit={() => {\r\n // The rules editor saves via Ctrl+S so Enter always inserts a newline.\r\n }}\r\n submitOnEnter={false}\r\n minHeight={editorViewportHeight}\r\n maxHeight={editorViewportHeight}\r\n placeholder=\"Type reusable instructions here...\"\r\n />\r\n </Box>\r\n </Box>\r\n )}\r\n\r\n {editorState === 'naming' && (\r\n <Box flexDirection=\"column\">\r\n <Text bold color=\"#00ccff\">Save Rule</Text>\r\n <Box marginTop={1}>\r\n <Text>Rule name: </Text>\r\n <TextInput\r\n value={ruleName}\r\n onChange={setRuleName}\r\n onSubmit={handleNameSubmit}\r\n placeholder=\"frontend-review\"\r\n />\r\n </Box>\r\n <Box marginTop={1}>\r\n <Text dimColor>\r\n Names are normalized for mentions, for example <Text color=\"#ffd700\">frontend review</Text>\r\n becomes <Text color=\"#ffd700\">frontend-review</Text>.\r\n </Text>\r\n </Box>\r\n </Box>\r\n )}\r\n </Box>\r\n );\r\n};\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/components/RulesEditorScreen.tsx"],"sourcesContent":["/**\r\n * Rules Editor Screen\r\n *\r\n * A multiline editor for reusable prompt rules referenced via @rules:<name>.\r\n * Uses raw stdin for key handling (same approach as TextEditor).\r\n */\r\n\r\nimport React, { useCallback, useState, useEffect, useRef } from 'react';\r\nimport { Box, Text, useStdin } from 'ink';\r\nimport TextInput from 'ink-text-input';\r\nimport { TextEditor } from './TextEditor.js';\r\nimport { SaveRuleResult } from '../../types/rule.js';\r\nimport { useTheme } from '../theme.js';\r\nimport * as readline from 'readline';\r\n\r\ninterface RulesEditorScreenProps {\r\n mode: 'add' | 'edit';\r\n initialName?: string;\r\n initialContent?: string;\r\n onSave: (name: string, content: string, previousName?: string) => SaveRuleResult;\r\n onCancel: () => void;\r\n}\r\n\r\ntype EditorState = 'editing' | 'naming';\r\n\r\nexport const RulesEditorScreen: React.FC<RulesEditorScreenProps> = ({\r\n mode,\r\n initialName,\r\n initialContent,\r\n onSave,\r\n onCancel\r\n}) => {\r\n const theme = useTheme();\r\n const { stdin, setRawMode } = useStdin();\r\n const [editorState, setEditorState] = useState<EditorState>('editing');\r\n const [content, setContent] = useState(initialContent || '');\r\n const [ruleName, setRuleName] = useState(initialName || '');\r\n const [error, setError] = useState<string | null>(null);\r\n const emitInitRef = useRef(false);\r\n const escTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n\r\n // Keep refs for stale closure avoidance\r\n const stateRef = useRef(editorState);\r\n const contentRef = useRef(content);\r\n useEffect(() => { stateRef.current = editorState; }, [editorState]);\r\n useEffect(() => { contentRef.current = content; }, [content]);\r\n\r\n const screenTitle = mode === 'edit' ? 'Edit Rule' : 'Create Rule';\r\n const editorViewportHeight = 12;\r\n\r\n const showTransientError = useCallback((message: string) => {\r\n setError(message);\r\n setTimeout(() => setError(null), 3000);\r\n }, []);\r\n\r\n const handleNameSubmit = useCallback((name: string) => {\r\n if (!name.trim()) {\r\n showTransientError('Rule name is required.');\r\n return;\r\n }\r\n\r\n const result = onSave(name.trim(), contentRef.current, initialName);\r\n if (!result.success) {\r\n showTransientError(result.error || 'Failed to save rule.');\r\n }\r\n }, [initialName, onSave, showTransientError]);\r\n\r\n // Raw stdin handler for ESC and Ctrl+S\r\n useEffect(() => {\r\n if (!stdin) return;\r\n setRawMode(true);\r\n\r\n if (!emitInitRef.current) {\r\n readline.emitKeypressEvents(stdin);\r\n emitInitRef.current = true;\r\n }\r\n\r\n const handler = (_str: string | undefined, key: any) => {\r\n if (!key) return;\r\n const name = key.name || '';\r\n const ctrl = !!key.ctrl;\r\n\r\n // Any non-escape keypress cancels a pending ESC (it was a meta combo like Option+Backspace)\r\n if (name !== 'escape' && escTimerRef.current) {\r\n clearTimeout(escTimerRef.current);\r\n escTimerRef.current = null;\r\n }\r\n\r\n // ESC — debounce to distinguish standalone ESC from meta sequences\r\n if (name === 'escape') {\r\n if (escTimerRef.current) clearTimeout(escTimerRef.current);\r\n escTimerRef.current = setTimeout(() => {\r\n escTimerRef.current = null;\r\n onCancel();\r\n }, 50);\r\n return;\r\n }\r\n\r\n if (stateRef.current === 'editing' && ctrl && name === 's') {\r\n if (!contentRef.current.trim()) {\r\n showTransientError('Rule content cannot be empty.');\r\n return;\r\n }\r\n setEditorState('naming');\r\n return;\r\n }\r\n };\r\n\r\n stdin.on('keypress', handler);\r\n return () => {\r\n stdin.off('keypress', handler);\r\n if (escTimerRef.current) clearTimeout(escTimerRef.current);\r\n };\r\n }, [stdin, setRawMode, onCancel, showTransientError]);\r\n\r\n return (\r\n <Box flexDirection=\"column\" paddingX={1}>\r\n <Box borderStyle=\"round\" borderColor={theme.accent} paddingX={2} paddingY={1} marginBottom={1}>\r\n <Box flexDirection=\"column\">\r\n <Text color={theme.accent} bold>{screenTitle}</Text>\r\n <Box marginTop={1}>\r\n <Text dimColor>\r\n Write reusable instructions here. Save them, then reference them inside prompts with\r\n <Text color=\"#ffd700\"> @rules:<name></Text>.\r\n </Text>\r\n </Box>\r\n <Box marginTop={1} flexDirection=\"column\">\r\n <Text dimColor>\r\n <Text color=\"#ffd700\">Enter</Text> New line <Text color=\"#ffd700\">Ctrl+S</Text> Save <Text color=\"#ffd700\">ESC</Text> Cancel\r\n </Text>\r\n <Text dimColor>\r\n <Text color=\"#ffd700\">Ctrl+←/→</Text> Word jump <Text color=\"#ffd700\">Ctrl+A/E</Text> Home/End <Text color=\"#ffd700\">Opt+←/→</Text> Word jump (macOS)\r\n </Text>\r\n <Text dimColor>\r\n <Text color=\"#ffd700\">Ctrl+K</Text> Kill to EOL <Text color=\"#ffd700\">Ctrl+U</Text> Kill to BOL <Text color=\"#ffd700\">Ctrl+W</Text> Del word <Text color=\"#ffd700\">Ctrl+Z</Text> Undo\r\n </Text>\r\n </Box>\r\n </Box>\r\n </Box>\r\n\r\n {error && (\r\n <Box marginBottom={1}>\r\n <Text color=\"red\">⚠️ {error}</Text>\r\n </Box>\r\n )}\r\n\r\n {editorState === 'editing' && (\r\n <Box flexDirection=\"column\">\r\n {initialName && (\r\n <Box marginBottom={1}>\r\n <Text dimColor>\r\n Editing rule: <Text color={theme.accent} bold>{initialName}</Text>\r\n </Text>\r\n </Box>\r\n )}\r\n <Box\r\n borderStyle=\"single\"\r\n borderColor={theme.border}\r\n paddingX={1}\r\n paddingY={0}\r\n minHeight={editorViewportHeight + 2}\r\n >\r\n <TextEditor\r\n value={content}\r\n onChange={setContent}\r\n minHeight={editorViewportHeight}\r\n maxHeight={editorViewportHeight}\r\n placeholder=\"Type reusable instructions here...\"\r\n tabHandledExternally={false}\r\n width={Math.max(20, (process.stdout.columns || 80) - 10)}\r\n />\r\n </Box>\r\n </Box>\r\n )}\r\n\r\n {editorState === 'naming' && (\r\n <Box flexDirection=\"column\">\r\n <Text bold color={theme.accent}>Save Rule</Text>\r\n <Box marginTop={1}>\r\n <Text>Rule name: </Text>\r\n <TextInput\r\n value={ruleName}\r\n onChange={setRuleName}\r\n onSubmit={handleNameSubmit}\r\n placeholder=\"frontend-review\"\r\n />\r\n </Box>\r\n <Box marginTop={1}>\r\n <Text dimColor>\r\n Names are normalized for mentions, for example <Text color=\"#ffd700\">frontend review</Text>\r\n becomes <Text color=\"#ffd700\">frontend-review</Text>.\r\n </Text>\r\n </Box>\r\n </Box>\r\n )}\r\n </Box>\r\n );\r\n};\r\n"],"mappings":"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","names":[]}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import SelectInput from "ink-select-input";
|
|
4
|
+
import { useTheme } from "../theme.js";
|
|
4
5
|
const SelectPrompt = ({
|
|
5
6
|
message,
|
|
6
7
|
choices,
|
|
7
8
|
onSelect,
|
|
8
9
|
width
|
|
9
10
|
}) => {
|
|
10
|
-
|
|
11
|
+
const theme = useTheme();
|
|
12
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, width }, /* @__PURE__ */ React.createElement(Text, { color: theme.accent, bold: true }, message), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(
|
|
11
13
|
SelectInput,
|
|
12
14
|
{
|
|
13
15
|
items: choices,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/ui/components/SelectPrompt.tsx"],"sourcesContent":["import React from 'react';\r\nimport { Box, Text } from 'ink';\r\nimport SelectInput from 'ink-select-input';\r\n\r\ninterface SelectPromptProps {\r\n message: string;\r\n choices: Array<{ label: string; value: string }>;\r\n onSelect: (value: string) => void;\r\n width?: number;\r\n}\r\n\r\nexport const SelectPrompt: React.FC<SelectPromptProps> = ({\r\n message,\r\n choices,\r\n onSelect,\r\n width\r\n}) => {\r\n return (\r\n <Box flexDirection=\"column\" borderStyle=\"round\" borderColor
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/components/SelectPrompt.tsx"],"sourcesContent":["import React from 'react';\r\nimport { Box, Text } from 'ink';\r\nimport SelectInput from 'ink-select-input';\r\nimport { useTheme } from '../theme.js';\r\n\r\ninterface SelectPromptProps {\r\n message: string;\r\n choices: Array<{ label: string; value: string }>;\r\n onSelect: (value: string) => void;\r\n width?: number;\r\n}\r\n\r\nexport const SelectPrompt: React.FC<SelectPromptProps> = ({\r\n message,\r\n choices,\r\n onSelect,\r\n width\r\n}) => {\r\n const theme = useTheme();\r\n return (\r\n <Box flexDirection=\"column\" borderStyle=\"round\" borderColor={theme.accent} paddingX={1} width={width}>\r\n <Text color={theme.accent} bold>{message}</Text>\r\n <Box marginTop={1}>\r\n <SelectInput\r\n items={choices}\r\n onSelect={(item) => onSelect(item.value)}\r\n />\r\n </Box>\r\n </Box>\r\n );\r\n};\r\n"],"mappings":"AAAA,OAAO,WAAW;AAClB,SAAS,KAAK,YAAY;AAC1B,OAAO,iBAAiB;AACxB,SAAS,gBAAgB;AASlB,MAAM,eAA4C,CAAC;AAAA,EACxD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,QAAQ,SAAS;AACvB,SACE,oCAAC,OAAI,eAAc,UAAS,aAAY,SAAQ,aAAa,MAAM,QAAQ,UAAU,GAAG,SACtF,oCAAC,QAAK,OAAO,MAAM,QAAQ,MAAI,QAAE,OAAQ,GACzC,oCAAC,OAAI,WAAW,KACd;AAAA,IAAC;AAAA;AAAA,MACC,OAAO;AAAA,MACP,UAAU,CAAC,SAAS,SAAS,KAAK,KAAK;AAAA;AAAA,EACzC,CACF,CACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import React, { useState, useCallback, useEffect, useRef } from "react";
|
|
2
|
+
import { Box, Text, useStdin } from "ink";
|
|
3
|
+
import TextInput from "ink-text-input";
|
|
4
|
+
import { TextEditor } from "./TextEditor.js";
|
|
5
|
+
import { useTheme } from "../theme.js";
|
|
6
|
+
import { SKILL_TOOL_PRESETS } from "../../types/skill.js";
|
|
7
|
+
import { fetchModelsConfig } from "../../config/models.js";
|
|
8
|
+
import * as readline from "readline";
|
|
9
|
+
const ACCESS_OPTIONS = ["read", "read-write"];
|
|
10
|
+
const SECTION_COUNT = 4;
|
|
11
|
+
const SkillCreatorScreen = ({
|
|
12
|
+
mode,
|
|
13
|
+
initialSkill,
|
|
14
|
+
onSave,
|
|
15
|
+
onCancel
|
|
16
|
+
}) => {
|
|
17
|
+
const theme = useTheme();
|
|
18
|
+
const { stdin, setRawMode } = useStdin();
|
|
19
|
+
const [section, setSection] = useState(0);
|
|
20
|
+
const [name, setName] = useState(initialSkill?.name || "");
|
|
21
|
+
const [prompt, setPrompt] = useState(initialSkill?.prompt || "");
|
|
22
|
+
const [accessLevel, setAccessLevel] = useState(initialSkill?.accessLevel || "read-write");
|
|
23
|
+
const [modelOptions, setModelOptions] = useState([{ id: "", label: "(Default \u2014 uses sub-agent model)" }]);
|
|
24
|
+
const [modelIdx, setModelIdx] = useState(0);
|
|
25
|
+
const [error, setError] = useState(null);
|
|
26
|
+
const [nameFlash, setNameFlash] = useState(false);
|
|
27
|
+
const emitInitRef = useRef(false);
|
|
28
|
+
const escTimerRef = useRef(null);
|
|
29
|
+
const sectionRef = useRef(section);
|
|
30
|
+
const nameRef = useRef(name);
|
|
31
|
+
const promptRef = useRef(prompt);
|
|
32
|
+
const accessRef = useRef(accessLevel);
|
|
33
|
+
const modelIdxRef = useRef(modelIdx);
|
|
34
|
+
const modelOptionsRef = useRef(modelOptions);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
sectionRef.current = section;
|
|
37
|
+
}, [section]);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
nameRef.current = name;
|
|
40
|
+
}, [name]);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
promptRef.current = prompt;
|
|
43
|
+
}, [prompt]);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
accessRef.current = accessLevel;
|
|
46
|
+
}, [accessLevel]);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
modelIdxRef.current = modelIdx;
|
|
49
|
+
}, [modelIdx]);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
modelOptionsRef.current = modelOptions;
|
|
52
|
+
}, [modelOptions]);
|
|
53
|
+
const isValidName = (n) => {
|
|
54
|
+
const trimmed = n.trim();
|
|
55
|
+
if (!trimmed) return { valid: false, reason: "Skill name is required." };
|
|
56
|
+
if (!/^[a-zA-Z]/.test(trimmed)) return { valid: false, reason: "Skill name must start with a letter." };
|
|
57
|
+
if (/\s/.test(trimmed)) return { valid: false, reason: "Skill name must be a single word (no spaces)." };
|
|
58
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(trimmed)) return { valid: false, reason: "Skill name can only contain letters, digits, hyphens and underscores." };
|
|
59
|
+
return { valid: true };
|
|
60
|
+
};
|
|
61
|
+
const flashNameSection = useCallback(() => {
|
|
62
|
+
setNameFlash(true);
|
|
63
|
+
setSection(0);
|
|
64
|
+
setTimeout(() => setNameFlash(false), 1500);
|
|
65
|
+
}, []);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
(async () => {
|
|
68
|
+
try {
|
|
69
|
+
const config = await fetchModelsConfig();
|
|
70
|
+
const options = [{ id: "", label: "(Default \u2014 uses sub-agent model)" }];
|
|
71
|
+
for (const m of config.models) {
|
|
72
|
+
options.push({ id: m.id || m.uid, label: m.name });
|
|
73
|
+
}
|
|
74
|
+
setModelOptions(options);
|
|
75
|
+
if (initialSkill?.model) {
|
|
76
|
+
const idx = options.findIndex((o) => o.id === initialSkill.model);
|
|
77
|
+
if (idx >= 0) setModelIdx(idx);
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
})();
|
|
82
|
+
}, []);
|
|
83
|
+
const showTransientError = useCallback((msg) => {
|
|
84
|
+
setError(msg);
|
|
85
|
+
setTimeout(() => setError(null), 3e3);
|
|
86
|
+
}, []);
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (!stdin) return;
|
|
89
|
+
setRawMode(true);
|
|
90
|
+
if (!emitInitRef.current) {
|
|
91
|
+
readline.emitKeypressEvents(stdin);
|
|
92
|
+
emitInitRef.current = true;
|
|
93
|
+
}
|
|
94
|
+
const handler = (_str, key) => {
|
|
95
|
+
if (!key) return;
|
|
96
|
+
const kname = key.name || "";
|
|
97
|
+
const ctrl = !!key.ctrl;
|
|
98
|
+
const sec = sectionRef.current;
|
|
99
|
+
if (kname !== "escape" && escTimerRef.current) {
|
|
100
|
+
clearTimeout(escTimerRef.current);
|
|
101
|
+
escTimerRef.current = null;
|
|
102
|
+
}
|
|
103
|
+
if (kname === "escape") {
|
|
104
|
+
if (escTimerRef.current) clearTimeout(escTimerRef.current);
|
|
105
|
+
escTimerRef.current = setTimeout(() => {
|
|
106
|
+
escTimerRef.current = null;
|
|
107
|
+
onCancel();
|
|
108
|
+
}, 50);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (kname === "tab") {
|
|
112
|
+
setSection((prev) => (prev + 1) % SECTION_COUNT);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (kname === "return" && sec !== 1) {
|
|
116
|
+
setSection((prev) => (prev + 1) % SECTION_COUNT);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (ctrl && kname === "s") {
|
|
120
|
+
const nameCheck = isValidName(nameRef.current);
|
|
121
|
+
if (!nameCheck.valid) {
|
|
122
|
+
showTransientError(nameCheck.reason || "Invalid skill name.");
|
|
123
|
+
flashNameSection();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!promptRef.current.trim()) {
|
|
127
|
+
showTransientError("Skill prompt is required.");
|
|
128
|
+
setSection(1);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
132
|
+
const opts = modelOptionsRef.current;
|
|
133
|
+
const mIdx = modelIdxRef.current;
|
|
134
|
+
const selectedModel = opts[mIdx];
|
|
135
|
+
const skill = {
|
|
136
|
+
name: nameRef.current.trim(),
|
|
137
|
+
prompt: promptRef.current.trim(),
|
|
138
|
+
accessLevel: accessRef.current,
|
|
139
|
+
allowedTools: SKILL_TOOL_PRESETS[accessRef.current],
|
|
140
|
+
model: selectedModel?.id || void 0,
|
|
141
|
+
createdAt: initialSkill?.createdAt || now,
|
|
142
|
+
updatedAt: now
|
|
143
|
+
};
|
|
144
|
+
const result = onSave(skill, initialSkill?.name);
|
|
145
|
+
if (!result.success) {
|
|
146
|
+
showTransientError(result.error || "Failed to save skill.");
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (sec === 2) {
|
|
151
|
+
if (kname === "up" || kname === "down" || _str === " " && !ctrl) {
|
|
152
|
+
setAccessLevel((prev) => {
|
|
153
|
+
const idx = ACCESS_OPTIONS.indexOf(prev);
|
|
154
|
+
return ACCESS_OPTIONS[(idx + 1) % ACCESS_OPTIONS.length];
|
|
155
|
+
});
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (sec === 3) {
|
|
160
|
+
if (kname === "down" || _str === " " && !ctrl) {
|
|
161
|
+
setModelIdx((prev) => (prev + 1) % modelOptionsRef.current.length);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (kname === "up") {
|
|
165
|
+
setModelIdx((prev) => (prev - 1 + modelOptionsRef.current.length) % modelOptionsRef.current.length);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
stdin.on("keypress", handler);
|
|
171
|
+
return () => {
|
|
172
|
+
stdin.off("keypress", handler);
|
|
173
|
+
if (escTimerRef.current) clearTimeout(escTimerRef.current);
|
|
174
|
+
};
|
|
175
|
+
}, [stdin, setRawMode, onCancel, onSave, initialSkill, showTransientError, flashNameSection]);
|
|
176
|
+
const title = mode === "edit" ? "Edit Skill" : "Create Skill";
|
|
177
|
+
const isFocused = (idx) => idx === section;
|
|
178
|
+
return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React.createElement(Box, { borderStyle: "round", borderColor: theme.accent, paddingX: 2, paddingY: 0, marginBottom: 1 }, /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: theme.accent, bold: true }, title), /* @__PURE__ */ React.createElement(Box, { marginTop: 1, gap: 3 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, /* @__PURE__ */ React.createElement(Text, { color: "#ffd700" }, "Tab"), " Next field"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, /* @__PURE__ */ React.createElement(Text, { color: "#ffd700" }, "Ctrl+S"), " Save"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, /* @__PURE__ */ React.createElement(Text, { color: "#ffd700" }, "ESC"), " Cancel")))), error && /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "red" }, "\u26A0\uFE0F ", error)), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: nameFlash ? "#ff3366" : isFocused(0) ? theme.accent : "#666" }, isFocused(0) ? "\u25B6 " : " ", "Skill Name", nameFlash && /* @__PURE__ */ React.createElement(Text, { color: "#ff3366" }, " \u26A0")), isFocused(0) ? /* @__PURE__ */ React.createElement(Box, { marginLeft: 4 }, /* @__PURE__ */ React.createElement(
|
|
179
|
+
TextInput,
|
|
180
|
+
{
|
|
181
|
+
value: name,
|
|
182
|
+
onChange: setName,
|
|
183
|
+
placeholder: "my-skill"
|
|
184
|
+
}
|
|
185
|
+
)) : /* @__PURE__ */ React.createElement(Box, { marginLeft: 4 }, /* @__PURE__ */ React.createElement(Text, { color: nameFlash ? "#ff3366" : "#888" }, name || "(not set)")), name && /* @__PURE__ */ React.createElement(Box, { marginLeft: 4 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Command: #", name))), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: isFocused(1) ? theme.accent : "#666" }, isFocused(1) ? "\u25B6 " : " ", "Skill Prompt"), isFocused(1) ? /* @__PURE__ */ React.createElement(
|
|
186
|
+
Box,
|
|
187
|
+
{
|
|
188
|
+
borderStyle: "single",
|
|
189
|
+
borderColor: theme.border,
|
|
190
|
+
paddingX: 1,
|
|
191
|
+
minHeight: 8
|
|
192
|
+
},
|
|
193
|
+
/* @__PURE__ */ React.createElement(
|
|
194
|
+
TextEditor,
|
|
195
|
+
{
|
|
196
|
+
value: prompt,
|
|
197
|
+
onChange: setPrompt,
|
|
198
|
+
minHeight: 6,
|
|
199
|
+
maxHeight: 6,
|
|
200
|
+
placeholder: "Describe what this skill should do...",
|
|
201
|
+
tabHandledExternally: true,
|
|
202
|
+
isActive: isFocused(1),
|
|
203
|
+
width: Math.max(20, (process.stdout.columns || 80) - 10)
|
|
204
|
+
}
|
|
205
|
+
)
|
|
206
|
+
) : /* @__PURE__ */ React.createElement(Box, { marginLeft: 4 }, /* @__PURE__ */ React.createElement(Text, { color: "#888", wrap: "truncate-end" }, prompt ? prompt.length > 80 ? prompt.slice(0, 77) + "..." : prompt.split("\n")[0] : "(not set)"))), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: isFocused(2) ? theme.accent : "#666" }, isFocused(2) ? "\u25B6 " : " ", "Access Level"), isFocused(2) ? /* @__PURE__ */ React.createElement(Box, { marginLeft: 4, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u2191/\u2193 or Space to toggle"), ACCESS_OPTIONS.map((opt) => {
|
|
207
|
+
const isSelected = opt === accessLevel;
|
|
208
|
+
return /* @__PURE__ */ React.createElement(Box, { key: opt, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: isSelected ? theme.accent : "#555", bold: isSelected }, isSelected ? " \u25CF " : " \u25CB ", opt === "read" ? "Read Only" : "Read & Write"));
|
|
209
|
+
}), /* @__PURE__ */ React.createElement(Box, { marginTop: 1, marginLeft: 2 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true, wrap: "wrap" }, "Tools: ", SKILL_TOOL_PRESETS[accessLevel].join(", ")))) : /* @__PURE__ */ React.createElement(Box, { marginLeft: 4 }, /* @__PURE__ */ React.createElement(Text, { color: "#888" }, accessLevel === "read" ? "Read Only" : "Read & Write"))), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: isFocused(3) ? theme.accent : "#666" }, isFocused(3) ? "\u25B6 " : " ", "Model"), isFocused(3) ? /* @__PURE__ */ React.createElement(Box, { marginLeft: 4, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u2191/\u2193 or Space to select"), modelOptions.map((opt, i) => {
|
|
210
|
+
const isSelected = i === modelIdx;
|
|
211
|
+
return /* @__PURE__ */ React.createElement(Text, { key: `model-${i}`, color: isSelected ? theme.accent : "#555", bold: isSelected }, isSelected ? " \u25CF " : " \u25CB ", opt.label);
|
|
212
|
+
})) : /* @__PURE__ */ React.createElement(Box, { marginLeft: 4 }, /* @__PURE__ */ React.createElement(Text, { color: "#888" }, modelOptions[modelIdx]?.label || "(default)"))));
|
|
213
|
+
};
|
|
214
|
+
export {
|
|
215
|
+
SkillCreatorScreen
|
|
216
|
+
};
|
|
217
|
+
//# sourceMappingURL=SkillCreatorScreen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/components/SkillCreatorScreen.tsx"],"sourcesContent":["/**\r\n * SkillCreatorScreen — All sections visible, stacked vertically.\r\n *\r\n * Tab / Shift+Tab to move focus between fields.\r\n * Ctrl+S to save. ESC to cancel.\r\n */\r\n\r\nimport React, { useState, useCallback, useEffect, useRef } from 'react';\r\nimport { Box, Text, useStdin } from 'ink';\r\nimport TextInput from 'ink-text-input';\r\nimport { TextEditor } from './TextEditor.js';\r\nimport { useTheme } from '../theme.js';\r\nimport { SavedSkill, SkillAccessLevel, SKILL_TOOL_PRESETS, SaveSkillResult } from '../../types/skill.js';\r\nimport { fetchModelsConfig } from '../../config/models.js';\r\nimport * as readline from 'readline';\r\n\r\ninterface SkillCreatorScreenProps {\r\n mode: 'add' | 'edit';\r\n initialSkill?: SavedSkill;\r\n onSave: (skill: SavedSkill, previousName?: string) => SaveSkillResult;\r\n onCancel: () => void;\r\n}\r\n\r\nconst ACCESS_OPTIONS: SkillAccessLevel[] = ['read', 'read-write'];\r\nconst SECTION_COUNT = 4; // name, prompt, access, model\r\n\r\ninterface ModelOption {\r\n id: string;\r\n label: string;\r\n}\r\n\r\nexport const SkillCreatorScreen: React.FC<SkillCreatorScreenProps> = ({\r\n mode,\r\n initialSkill,\r\n onSave,\r\n onCancel,\r\n}) => {\r\n const theme = useTheme();\r\n const { stdin, setRawMode } = useStdin();\r\n const [section, setSection] = useState(0);\r\n const [name, setName] = useState(initialSkill?.name || '');\r\n const [prompt, setPrompt] = useState(initialSkill?.prompt || '');\r\n const [accessLevel, setAccessLevel] = useState<SkillAccessLevel>(initialSkill?.accessLevel || 'read-write');\r\n const [modelOptions, setModelOptions] = useState<ModelOption[]>([{ id: '', label: '(Default — uses sub-agent model)' }]);\r\n const [modelIdx, setModelIdx] = useState(0);\r\n const [error, setError] = useState<string | null>(null);\r\n const [nameFlash, setNameFlash] = useState(false);\r\n const emitInitRef = useRef(false);\r\n const escTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n\r\n // Refs for stale closure avoidance\r\n const sectionRef = useRef(section);\r\n const nameRef = useRef(name);\r\n const promptRef = useRef(prompt);\r\n const accessRef = useRef(accessLevel);\r\n const modelIdxRef = useRef(modelIdx);\r\n const modelOptionsRef = useRef(modelOptions);\r\n useEffect(() => { sectionRef.current = section; }, [section]);\r\n useEffect(() => { nameRef.current = name; }, [name]);\r\n useEffect(() => { promptRef.current = prompt; }, [prompt]);\r\n useEffect(() => { accessRef.current = accessLevel; }, [accessLevel]);\r\n useEffect(() => { modelIdxRef.current = modelIdx; }, [modelIdx]);\r\n useEffect(() => { modelOptionsRef.current = modelOptions; }, [modelOptions]);\r\n\r\n // Validate skill name: must start with a letter, single word (letters, digits, hyphens, underscores only)\r\n const isValidName = (n: string): { valid: boolean; reason?: string } => {\r\n const trimmed = n.trim();\r\n if (!trimmed) return { valid: false, reason: 'Skill name is required.' };\r\n if (!/^[a-zA-Z]/.test(trimmed)) return { valid: false, reason: 'Skill name must start with a letter.' };\r\n if (/\\s/.test(trimmed)) return { valid: false, reason: 'Skill name must be a single word (no spaces).' };\r\n if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(trimmed)) return { valid: false, reason: 'Skill name can only contain letters, digits, hyphens and underscores.' };\r\n return { valid: true };\r\n };\r\n\r\n const flashNameSection = useCallback(() => {\r\n setNameFlash(true);\r\n setSection(0);\r\n setTimeout(() => setNameFlash(false), 1500);\r\n }, []);\r\n\r\n // Fetch models from backend\r\n useEffect(() => {\r\n (async () => {\r\n try {\r\n const config = await fetchModelsConfig();\r\n const options: ModelOption[] = [{ id: '', label: '(Default — uses sub-agent model)' }];\r\n for (const m of config.models) {\r\n options.push({ id: m.id || m.uid, label: m.name });\r\n }\r\n setModelOptions(options);\r\n // Restore selection if editing\r\n if (initialSkill?.model) {\r\n const idx = options.findIndex(o => o.id === initialSkill.model);\r\n if (idx >= 0) setModelIdx(idx);\r\n }\r\n } catch {\r\n // Keep fallback\r\n }\r\n })();\r\n }, []);\r\n\r\n const showTransientError = useCallback((msg: string) => {\r\n setError(msg);\r\n setTimeout(() => setError(null), 3000);\r\n }, []);\r\n\r\n // Raw stdin handler for navigation and commands\r\n useEffect(() => {\r\n if (!stdin) return;\r\n setRawMode(true);\r\n\r\n if (!emitInitRef.current) {\r\n readline.emitKeypressEvents(stdin);\r\n emitInitRef.current = true;\r\n }\r\n\r\n const handler = (_str: string | undefined, key: any) => {\r\n if (!key) return;\r\n const kname = key.name || '';\r\n const ctrl = !!key.ctrl;\r\n const sec = sectionRef.current;\r\n\r\n // Any non-escape keypress cancels a pending ESC (it was a meta combo like Option+Backspace)\r\n if (kname !== 'escape' && escTimerRef.current) {\r\n clearTimeout(escTimerRef.current);\r\n escTimerRef.current = null;\r\n }\r\n\r\n // ESC — debounce to distinguish standalone ESC from meta sequences\r\n if (kname === 'escape') {\r\n if (escTimerRef.current) clearTimeout(escTimerRef.current);\r\n escTimerRef.current = setTimeout(() => {\r\n escTimerRef.current = null;\r\n onCancel();\r\n }, 50);\r\n return;\r\n }\r\n\r\n // Tab — next section\r\n if (kname === 'tab') {\r\n setSection(prev => (prev + 1) % SECTION_COUNT);\r\n return;\r\n }\r\n\r\n // Enter — advance section (NOT in Prompt section where TextEditor handles Enter)\r\n if (kname === 'return' && sec !== 1) {\r\n setSection(prev => (prev + 1) % SECTION_COUNT);\r\n return;\r\n }\r\n\r\n // Ctrl+S — save\r\n if (ctrl && kname === 's') {\r\n const nameCheck = isValidName(nameRef.current);\r\n if (!nameCheck.valid) {\r\n showTransientError(nameCheck.reason || 'Invalid skill name.');\r\n flashNameSection();\r\n return;\r\n }\r\n if (!promptRef.current.trim()) {\r\n showTransientError('Skill prompt is required.');\r\n setSection(1);\r\n return;\r\n }\r\n\r\n const now = new Date().toISOString();\r\n const opts = modelOptionsRef.current;\r\n const mIdx = modelIdxRef.current;\r\n const selectedModel = opts[mIdx];\r\n const skill: SavedSkill = {\r\n name: nameRef.current.trim(),\r\n prompt: promptRef.current.trim(),\r\n accessLevel: accessRef.current,\r\n allowedTools: SKILL_TOOL_PRESETS[accessRef.current],\r\n model: selectedModel?.id || undefined,\r\n createdAt: initialSkill?.createdAt || now,\r\n updatedAt: now,\r\n };\r\n\r\n const result = onSave(skill, initialSkill?.name);\r\n if (!result.success) {\r\n showTransientError(result.error || 'Failed to save skill.');\r\n }\r\n return;\r\n }\r\n\r\n // Access level section — up/down/space cycles options\r\n if (sec === 2) {\r\n if (kname === 'up' || kname === 'down' || (_str === ' ' && !ctrl)) {\r\n setAccessLevel(prev => {\r\n const idx = ACCESS_OPTIONS.indexOf(prev);\r\n return ACCESS_OPTIONS[(idx + 1) % ACCESS_OPTIONS.length];\r\n });\r\n return;\r\n }\r\n }\r\n\r\n // Model section — up/down/space cycles models\r\n if (sec === 3) {\r\n if (kname === 'down' || (_str === ' ' && !ctrl)) {\r\n setModelIdx(prev => (prev + 1) % modelOptionsRef.current.length);\r\n return;\r\n }\r\n if (kname === 'up') {\r\n setModelIdx(prev => (prev - 1 + modelOptionsRef.current.length) % modelOptionsRef.current.length);\r\n return;\r\n }\r\n }\r\n };\r\n\r\n stdin.on('keypress', handler);\r\n return () => {\r\n stdin.off('keypress', handler);\r\n if (escTimerRef.current) clearTimeout(escTimerRef.current);\r\n };\r\n }, [stdin, setRawMode, onCancel, onSave, initialSkill, showTransientError, flashNameSection]);\r\n\r\n const title = mode === 'edit' ? 'Edit Skill' : 'Create Skill';\r\n const isFocused = (idx: number) => idx === section;\r\n\r\n return (\r\n <Box flexDirection=\"column\" paddingX={1}>\r\n {/* Header */}\r\n <Box borderStyle=\"round\" borderColor={theme.accent} paddingX={2} paddingY={0} marginBottom={1}>\r\n <Box flexDirection=\"column\">\r\n <Text color={theme.accent} bold>{title}</Text>\r\n <Box marginTop={1} gap={3}>\r\n <Text dimColor><Text color=\"#ffd700\">Tab</Text> Next field</Text>\r\n <Text dimColor><Text color=\"#ffd700\">Ctrl+S</Text> Save</Text>\r\n <Text dimColor><Text color=\"#ffd700\">ESC</Text> Cancel</Text>\r\n </Box>\r\n </Box>\r\n </Box>\r\n\r\n {/* Error */}\r\n {error && (\r\n <Box marginBottom={1}>\r\n <Text color=\"red\">⚠️ {error}</Text>\r\n </Box>\r\n )}\r\n\r\n {/* Section 0 — Name */}\r\n <Box flexDirection=\"column\" marginBottom={1}>\r\n <Text bold color={nameFlash ? '#ff3366' : isFocused(0) ? theme.accent : '#666'}>\r\n {isFocused(0) ? '▶ ' : ' '}Skill Name\r\n {nameFlash && <Text color=\"#ff3366\"> ⚠</Text>}\r\n </Text>\r\n {isFocused(0) ? (\r\n <Box marginLeft={4}>\r\n <TextInput\r\n value={name}\r\n onChange={setName}\r\n placeholder=\"my-skill\"\r\n />\r\n </Box>\r\n ) : (\r\n <Box marginLeft={4}>\r\n <Text color={nameFlash ? '#ff3366' : '#888'}>{name || '(not set)'}</Text>\r\n </Box>\r\n )}\r\n {name && (\r\n <Box marginLeft={4}>\r\n <Text dimColor>Command: #{name}</Text>\r\n </Box>\r\n )}\r\n </Box>\r\n\r\n {/* Section 1 — Prompt */}\r\n <Box flexDirection=\"column\" marginBottom={1}>\r\n <Text bold color={isFocused(1) ? theme.accent : '#666'}>\r\n {isFocused(1) ? '▶ ' : ' '}Skill Prompt\r\n </Text>\r\n {isFocused(1) ? (\r\n <Box\r\n borderStyle=\"single\"\r\n borderColor={theme.border}\r\n paddingX={1}\r\n minHeight={8}\r\n >\r\n <TextEditor\r\n value={prompt}\r\n onChange={setPrompt}\r\n minHeight={6}\r\n maxHeight={6}\r\n placeholder=\"Describe what this skill should do...\"\r\n tabHandledExternally={true}\r\n isActive={isFocused(1)}\r\n width={Math.max(20, (process.stdout.columns || 80) - 10)}\r\n />\r\n </Box>\r\n ) : (\r\n <Box marginLeft={4}>\r\n <Text color=\"#888\" wrap=\"truncate-end\">\r\n {prompt ? (prompt.length > 80 ? prompt.slice(0, 77) + '...' : prompt.split('\\n')[0]) : '(not set)'}\r\n </Text>\r\n </Box>\r\n )}\r\n </Box>\r\n\r\n {/* Section 2 — Access Level */}\r\n <Box flexDirection=\"column\" marginBottom={1}>\r\n <Text bold color={isFocused(2) ? theme.accent : '#666'}>\r\n {isFocused(2) ? '▶ ' : ' '}Access Level\r\n </Text>\r\n {isFocused(2) ? (\r\n <Box marginLeft={4} flexDirection=\"column\">\r\n <Text dimColor>↑/↓ or Space to toggle</Text>\r\n {ACCESS_OPTIONS.map((opt) => {\r\n const isSelected = opt === accessLevel;\r\n return (\r\n <Box key={opt} flexDirection=\"column\">\r\n <Text color={isSelected ? theme.accent : '#555'} bold={isSelected}>\r\n {isSelected ? ' ● ' : ' ○ '}{opt === 'read' ? 'Read Only' : 'Read & Write'}\r\n </Text>\r\n </Box>\r\n );\r\n })}\r\n <Box marginTop={1} marginLeft={2}>\r\n <Text dimColor wrap=\"wrap\">\r\n Tools: {SKILL_TOOL_PRESETS[accessLevel].join(', ')}\r\n </Text>\r\n </Box>\r\n </Box>\r\n ) : (\r\n <Box marginLeft={4}>\r\n <Text color=\"#888\">{accessLevel === 'read' ? 'Read Only' : 'Read & Write'}</Text>\r\n </Box>\r\n )}\r\n </Box>\r\n\r\n {/* Section 3 — Model */}\r\n <Box flexDirection=\"column\" marginBottom={1}>\r\n <Text bold color={isFocused(3) ? theme.accent : '#666'}>\r\n {isFocused(3) ? '▶ ' : ' '}Model\r\n </Text>\r\n {isFocused(3) ? (\r\n <Box marginLeft={4} flexDirection=\"column\">\r\n <Text dimColor>↑/↓ or Space to select</Text>\r\n {modelOptions.map((opt, i) => {\r\n const isSelected = i === modelIdx;\r\n return (\r\n <Text key={`model-${i}`} color={isSelected ? theme.accent : '#555'} bold={isSelected}>\r\n {isSelected ? ' ● ' : ' ○ '}{opt.label}\r\n </Text>\r\n );\r\n })}\r\n </Box>\r\n ) : (\r\n <Box marginLeft={4}>\r\n <Text color=\"#888\">{modelOptions[modelIdx]?.label || '(default)'}</Text>\r\n </Box>\r\n )}\r\n </Box>\r\n </Box>\r\n );\r\n};\r\n"],"mappings":"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","names":[]}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
+
import { useTheme } from "../theme.js";
|
|
3
4
|
const SlashCommandAutocomplete = ({
|
|
4
5
|
commands,
|
|
5
6
|
selectedIndex,
|
|
6
7
|
maxVisibleItems,
|
|
7
8
|
scrollOffset
|
|
8
9
|
}) => {
|
|
10
|
+
const theme = useTheme();
|
|
9
11
|
if (commands.length === 0 || maxVisibleItems === 0) return null;
|
|
10
12
|
const visibleCommands = commands.slice(scrollOffset, scrollOffset + maxVisibleItems);
|
|
11
13
|
const hasMoreAbove = scrollOffset > 0;
|
|
@@ -33,13 +35,13 @@ const SlashCommandAutocomplete = ({
|
|
|
33
35
|
/* @__PURE__ */ React.createElement(
|
|
34
36
|
Text,
|
|
35
37
|
{
|
|
36
|
-
color: isSelected ?
|
|
38
|
+
color: isSelected ? theme.accent : "#666666",
|
|
37
39
|
bold: isSelected,
|
|
38
40
|
inverse: isSelected
|
|
39
41
|
},
|
|
40
42
|
cmd.name
|
|
41
43
|
),
|
|
42
|
-
/* @__PURE__ */ React.createElement(Text, { color: "#666666" }, " ", cmd.description)
|
|
44
|
+
cmd.swatchColor ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Text, { color: cmd.swatchColor }, " \u2588\u2588\u2588\u2588"), /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, " ", cmd.description)) : /* @__PURE__ */ React.createElement(Text, { color: "#666666" }, " ", cmd.description)
|
|
43
45
|
);
|
|
44
46
|
}),
|
|
45
47
|
hasMoreBelow && /* @__PURE__ */ React.createElement(Text, { color: "#666666", dimColor: true }, "\u2193 more")
|