@unscientificjszhai/howto 1.0.0-alpha.3 → 1.0.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/README.CN.md +5 -2
- package/README.md +5 -2
- package/THIRD_PARTY_NOTICES.md +17 -0
- package/dist/ai/command-schema.js +2 -0
- package/dist/index.js +4 -5
- package/dist/prompt.js +1 -0
- package/dist/ui/App.js +26 -22
- package/dist/ui/ConfirmView.js +12 -3
- package/dist/ui/ResolvePlaceholdersView.js +37 -58
- package/dist/ui/placeholder-logic.js +89 -0
- package/dist/validation/generated-commands.js +19 -0
- package/package.json +4 -2
package/README.CN.md
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
_在终端中用 AI 快速找到可执行命令。_
|
|
4
4
|
|
|
5
|
-

|
|
5
|
+
[](https://www.npmjs.com/package/@unscientificjszhai/howto)
|
|
6
|
+
[](https://github.com/UnscientificJsZhai/HowTo/actions)
|
|
7
|
+
[](https://github.com/prettier/prettier)
|
|
8
|
+
[](LICENSE)
|
|
6
9
|
|
|
7
10
|
[English](README.md) | 简体中文
|
|
8
11
|
|
|
@@ -148,7 +151,7 @@ howto --print "show current branch"
|
|
|
148
151
|
- 响应包含一到三个候选项;
|
|
149
152
|
- 所有占位符使用 `{{name}}` 语法,并且声明与引用一致;
|
|
150
153
|
- `use <command>` 候选项在保守处理前缀后,明确以指定工具开头;
|
|
151
|
-
- 明显危险的命令需要输入 `EXECUTE`
|
|
154
|
+
- 明显危险的命令需要输入 `EXECUTE` 才能继续执行;大小写不敏感。
|
|
152
155
|
|
|
153
156
|
危险命令检测当前覆盖递归破坏性 `rm`、磁盘和文件系统操作、大范围递归权限变更、下载脚本后直接交给 shell 执行、高影响包管理器操作以及服务变更等高风险模式。
|
|
154
157
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
_Use AI to quickly find commands within the terminal._
|
|
4
4
|
|
|
5
|
-

|
|
5
|
+
[](https://www.npmjs.com/package/@unscientificjszhai/howto)
|
|
6
|
+
[](https://github.com/UnscientificJsZhai/HowTo/actions)
|
|
7
|
+
[](https://github.com/prettier/prettier)
|
|
8
|
+
[](LICENSE)
|
|
6
9
|
|
|
7
10
|
English | [简体中文](README.CN.md)
|
|
8
11
|
|
|
@@ -148,7 +151,7 @@ howto --print "show current branch"
|
|
|
148
151
|
- the response contains between one and three candidates;
|
|
149
152
|
- all placeholders use `{{name}}` syntax and are declared consistently;
|
|
150
153
|
- `use <command>` candidates clearly start with the requested tool after conservative prefix handling;
|
|
151
|
-
- obvious dangerous patterns require typing `EXECUTE` before they can run.
|
|
154
|
+
- obvious dangerous patterns require typing `EXECUTE` before they can run; matching is case-insensitive.
|
|
152
155
|
|
|
153
156
|
Dangerous-command detection currently covers high-risk patterns such as recursive destructive `rm`, disk and filesystem operations, broad recursive permission changes, downloaded scripts piped into a shell, high-impact package manager operations, and service changes.
|
|
154
157
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Third-Party Notices
|
|
2
|
+
|
|
3
|
+
This project depends on third-party packages that are installed by npm as separate packages.
|
|
4
|
+
Each dependency is distributed under its own license.
|
|
5
|
+
|
|
6
|
+
## Runtime Dependencies
|
|
7
|
+
|
|
8
|
+
| Package | Version | License |
|
|
9
|
+
| --------------- | -------- | ---------- |
|
|
10
|
+
| `@google/genai` | `2.6.0` | Apache-2.0 |
|
|
11
|
+
| `ink` | `7.0.4` | MIT |
|
|
12
|
+
| `openai` | `6.39.0` | Apache-2.0 |
|
|
13
|
+
| `react` | `19.2.6` | MIT |
|
|
14
|
+
|
|
15
|
+
The package versions above reflect the installed dependency set at the time this notice
|
|
16
|
+
was prepared. See each package's published npm artifact for its complete license text and
|
|
17
|
+
any package-specific notices.
|
package/dist/index.js
CHANGED
|
@@ -17,8 +17,8 @@ import { ConfigError, hasExplicitAiProvider, loadConfig } from "./config.js";
|
|
|
17
17
|
import { readUserConfigFile } from "./config-file.js";
|
|
18
18
|
import { createCommandProvider } from "./ai/index.js";
|
|
19
19
|
import { buildCommandGenerationPrompt, createProviderPromptRequest } from "./prompt.js";
|
|
20
|
-
import { parseAndValidateAiResponse } from "./validation/ai-response.js";
|
|
21
20
|
import { checkCommandInPath } from "./validation/command-tool.js";
|
|
21
|
+
import { generateValidatedCommandCandidates } from "./validation/generated-commands.js";
|
|
22
22
|
import { ensureInteractiveTty } from "./ui/tty.js";
|
|
23
23
|
import { executeCommand } from "./execute.js";
|
|
24
24
|
import { toAppError } from "./errors.js";
|
|
@@ -71,10 +71,9 @@ function run(argv) {
|
|
|
71
71
|
const { systemPrompt, userPrompt } = buildCommandGenerationPrompt(promptRequest);
|
|
72
72
|
const provider = createCommandProvider(config);
|
|
73
73
|
if (parsedCli.options.print) {
|
|
74
|
-
const
|
|
74
|
+
const candidates = yield generateValidatedCommandCandidates(provider, Object.assign(Object.assign({}, promptRequest), { systemPrompt,
|
|
75
75
|
userPrompt }));
|
|
76
|
-
|
|
77
|
-
aiResponse.commands.forEach((candidate) => {
|
|
76
|
+
candidates.forEach((candidate) => {
|
|
78
77
|
console.log(candidate.command);
|
|
79
78
|
});
|
|
80
79
|
return { exitCode: 0 };
|
|
@@ -84,7 +83,7 @@ function run(argv) {
|
|
|
84
83
|
}
|
|
85
84
|
let finalCommand;
|
|
86
85
|
let appError;
|
|
87
|
-
const { waitUntilExit, unmount, clear } = render(React.createElement(App, { provider: provider, request: Object.assign(Object.assign({}, promptRequest), { systemPrompt, userPrompt }),
|
|
86
|
+
const { waitUntilExit, unmount, clear } = render(React.createElement(App, { provider: provider, request: Object.assign(Object.assign({}, promptRequest), { systemPrompt, userPrompt }), onSuccess: (command) => {
|
|
88
87
|
finalCommand = command;
|
|
89
88
|
clear();
|
|
90
89
|
unmount();
|
package/dist/prompt.js
CHANGED
|
@@ -19,6 +19,7 @@ The commands array must contain 1 to 3 items. Do not return more than 3 candidat
|
|
|
19
19
|
Detect the primary natural language of the user's question. Return title, description, and placeholders[].description in that language. Keep placeholder name values English-compatible ASCII using only letters, numbers, underscores, or hyphens.
|
|
20
20
|
Use placeholders in commands only as {{name}}, and declare every placeholder in the placeholders array. User may provide argument. If the user's intent is clear, try to use the provided arguments as parameters in the generated commands instead of placeholders. If the intent is unclear, do not fill them.`;
|
|
21
21
|
export const STRUCTURED_OUTPUT_CONTRACT = `Return only the JSON object requested by the response schema. Do not include a natural-language body, markdown, or code fences.
|
|
22
|
+
The commands array must contain 1 to 3 items. Do not return more than 3 candidate commands.
|
|
22
23
|
Detect the primary natural language of the user's question. Return title, description, and placeholders[].description in that language. Keep placeholder name values English-compatible ASCII using only letters, numbers, underscores, or hyphens.
|
|
23
24
|
Use placeholders in commands only as {{name}}, and declare every placeholder in the placeholders array. User may provide argument. If the user's intent is clear, try to use the provided arguments as parameters in the generated commands instead of placeholders. If the intent is unclear, do not fill them.`;
|
|
24
25
|
export const SAFETY_CONSTRAINTS = `Prefer read-only, reversible, and low-risk commands.
|
package/dist/ui/App.js
CHANGED
|
@@ -4,12 +4,11 @@ import { LoadingView } from "./LoadingView.js";
|
|
|
4
4
|
import { SelectCommandView } from "./SelectCommandView.js";
|
|
5
5
|
import { ResolvePlaceholdersView } from "./ResolvePlaceholdersView.js";
|
|
6
6
|
import { ConfirmView } from "./ConfirmView.js";
|
|
7
|
-
import {
|
|
8
|
-
import { validateUseCommandCandidates } from "../validation/command-tool.js";
|
|
7
|
+
import { generateValidatedCommandCandidates } from "../validation/generated-commands.js";
|
|
9
8
|
import { detectDangerousCommand } from "../safety/dangerous-command.js";
|
|
10
|
-
import {
|
|
9
|
+
import { resolveCandidatePlaceholders } from "./placeholder-logic.js";
|
|
11
10
|
import { InteractionCancelledError } from "./tty.js";
|
|
12
|
-
export const App = ({ provider, request,
|
|
11
|
+
export const App = ({ provider, request, onSuccess, onError }) => {
|
|
13
12
|
const { rows } = useWindowSize();
|
|
14
13
|
const [status, setStatus] = useState("loading");
|
|
15
14
|
const [candidates, setCandidates] = useState([]);
|
|
@@ -20,12 +19,9 @@ export const App = ({ provider, request, useCommand, onSuccess, onError }) => {
|
|
|
20
19
|
const [errorMessage, setErrorMessage] = useState("");
|
|
21
20
|
useEffect(() => {
|
|
22
21
|
if (status === "loading") {
|
|
23
|
-
provider
|
|
24
|
-
.
|
|
25
|
-
|
|
26
|
-
const aiResponse = parseAndValidateAiResponse(result.rawText);
|
|
27
|
-
validateUseCommandCandidates(aiResponse, useCommand);
|
|
28
|
-
setCandidates(aiResponse.commands);
|
|
22
|
+
generateValidatedCommandCandidates(provider, request)
|
|
23
|
+
.then((generatedCandidates) => {
|
|
24
|
+
setCandidates(generatedCandidates);
|
|
29
25
|
setStatus("selecting");
|
|
30
26
|
})
|
|
31
27
|
.catch((error) => {
|
|
@@ -35,7 +31,7 @@ export const App = ({ provider, request, useCommand, onSuccess, onError }) => {
|
|
|
35
31
|
onError(appError);
|
|
36
32
|
});
|
|
37
33
|
}
|
|
38
|
-
}, [status, provider, request,
|
|
34
|
+
}, [status, provider, request, onError]);
|
|
39
35
|
const handleSelect = (candidate) => {
|
|
40
36
|
setSelectedCandidate(candidate);
|
|
41
37
|
setResolvedValues(new Map());
|
|
@@ -43,19 +39,27 @@ export const App = ({ provider, request, useCommand, onSuccess, onError }) => {
|
|
|
43
39
|
setStatus("resolving");
|
|
44
40
|
}
|
|
45
41
|
else {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
try {
|
|
43
|
+
const resolved = resolveCandidatePlaceholders(candidate, new Map());
|
|
44
|
+
setFinalCommand(resolved.command);
|
|
45
|
+
setResolvedValues(resolved.values);
|
|
46
|
+
setDanger(detectDangerousCommand(resolved.command));
|
|
47
|
+
setStatus("confirming");
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const appError = normalizeError(error);
|
|
51
|
+
setErrorMessage(appError.message);
|
|
52
|
+
setStatus("error");
|
|
53
|
+
onError(appError);
|
|
54
|
+
}
|
|
50
55
|
}
|
|
51
56
|
};
|
|
52
|
-
const handleResolve = (
|
|
57
|
+
const handleResolve = (resolved) => {
|
|
53
58
|
if (!selectedCandidate)
|
|
54
59
|
return;
|
|
55
|
-
setResolvedValues(values);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
setDanger(detectDangerousCommand(command));
|
|
60
|
+
setResolvedValues(resolved.values);
|
|
61
|
+
setFinalCommand(resolved.command);
|
|
62
|
+
setDanger(detectDangerousCommand(resolved.command));
|
|
59
63
|
setStatus("confirming");
|
|
60
64
|
};
|
|
61
65
|
useEffect(() => {
|
|
@@ -76,8 +80,8 @@ export const App = ({ provider, request, useCommand, onSuccess, onError }) => {
|
|
|
76
80
|
return (React.createElement(Box, { flexDirection: "column", maxHeight: maxFrameRows, overflowY: "hidden" },
|
|
77
81
|
status === "loading" && React.createElement(LoadingView, null),
|
|
78
82
|
status === "selecting" && (React.createElement(SelectCommandView, { candidates: candidates, onSelect: handleSelect, onCancel: handleCancel })),
|
|
79
|
-
status === "resolving" && selectedCandidate && (React.createElement(ResolvePlaceholdersView, { candidate: selectedCandidate,
|
|
80
|
-
(status === "confirming" || status === "done") && selectedCandidate && (React.createElement(ConfirmView, { candidate: selectedCandidate, resolvedValues: resolvedValues, danger: danger, onConfirm: handleConfirm, onCancel: handleCancel, isDone: status === "done" })),
|
|
83
|
+
status === "resolving" && selectedCandidate && (React.createElement(ResolvePlaceholdersView, { candidate: selectedCandidate, onResolve: handleResolve, onBack: handleBackToSelection, onCancel: handleCancel })),
|
|
84
|
+
(status === "confirming" || status === "done") && selectedCandidate && (React.createElement(ConfirmView, { candidate: selectedCandidate, command: finalCommand, resolvedValues: resolvedValues, danger: danger, onConfirm: handleConfirm, onCancel: handleCancel, isDone: status === "done" })),
|
|
81
85
|
status === "error" && React.createElement(Text, { color: "red" },
|
|
82
86
|
"Error: ",
|
|
83
87
|
errorMessage)));
|
package/dist/ui/ConfirmView.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
3
|
import { SelectedCommandDisplay } from "./SelectedCommandDisplay.js";
|
|
4
|
-
export
|
|
4
|
+
export function isDangerConfirmationInput(input) {
|
|
5
|
+
return input.toUpperCase() === "EXECUTE";
|
|
6
|
+
}
|
|
7
|
+
export const ConfirmView = ({ candidate, command, resolvedValues, danger, onConfirm, onCancel, isDone = false, }) => {
|
|
5
8
|
const [buffer, setBuffer] = useState("");
|
|
6
9
|
useInput((input, key) => {
|
|
7
10
|
if (isDone)
|
|
@@ -16,7 +19,7 @@ export const ConfirmView = ({ candidate, resolvedValues, danger, onConfirm, onCa
|
|
|
16
19
|
}
|
|
17
20
|
if (danger) {
|
|
18
21
|
if (key.return) {
|
|
19
|
-
if (buffer
|
|
22
|
+
if (isDangerConfirmationInput(buffer)) {
|
|
20
23
|
onConfirm();
|
|
21
24
|
}
|
|
22
25
|
else {
|
|
@@ -48,7 +51,10 @@ export const ConfirmView = ({ candidate, resolvedValues, danger, onConfirm, onCa
|
|
|
48
51
|
"Risk: ",
|
|
49
52
|
danger.reason),
|
|
50
53
|
React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|
|
51
|
-
React.createElement(SelectedCommandDisplay, { candidate: candidate, resolvedValues: resolvedValues })
|
|
54
|
+
React.createElement(SelectedCommandDisplay, { candidate: candidate, resolvedValues: resolvedValues }),
|
|
55
|
+
React.createElement(Text, null,
|
|
56
|
+
"Final command: ",
|
|
57
|
+
React.createElement(Text, { color: "yellow" }, command))),
|
|
52
58
|
!isDone && (React.createElement(React.Fragment, null,
|
|
53
59
|
React.createElement(Box, { marginTop: 1 },
|
|
54
60
|
React.createElement(Text, null,
|
|
@@ -62,6 +68,9 @@ export const ConfirmView = ({ candidate, resolvedValues, danger, onConfirm, onCa
|
|
|
62
68
|
}
|
|
63
69
|
return (React.createElement(Box, { flexDirection: "column", marginY: 1 },
|
|
64
70
|
React.createElement(SelectedCommandDisplay, { candidate: candidate, resolvedValues: resolvedValues }),
|
|
71
|
+
React.createElement(Text, null,
|
|
72
|
+
"Final command: ",
|
|
73
|
+
React.createElement(Text, { color: "yellow" }, command)),
|
|
65
74
|
!isDone && (React.createElement(Box, { marginTop: 1 },
|
|
66
75
|
React.createElement(Text, null,
|
|
67
76
|
"Press ",
|
|
@@ -1,80 +1,59 @@
|
|
|
1
|
-
import React, { useState
|
|
1
|
+
import React, { useState } from "react";
|
|
2
2
|
import { Box, Text, useInput } from "ink";
|
|
3
3
|
import { SelectedCommandDisplay } from "./SelectedCommandDisplay.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const [
|
|
7
|
-
const
|
|
8
|
-
const resolvedValuesMap = useMemo(() => {
|
|
9
|
-
const map = new Map();
|
|
10
|
-
placeholders.forEach((p, i) => {
|
|
11
|
-
if (i < activeIndex) {
|
|
12
|
-
map.set(p.name, values[i]);
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
return map;
|
|
16
|
-
}, [placeholders, activeIndex, values]);
|
|
17
|
-
const currentBuffer = useMemo(() => ({
|
|
18
|
-
name: placeholders[activeIndex].name,
|
|
19
|
-
value: buffer,
|
|
20
|
-
}), [placeholders, activeIndex, buffer]);
|
|
4
|
+
import { applyPlaceholderResolutionInput, createPlaceholderResolution, getPlaceholderResolutionView, } from "./placeholder-logic.js";
|
|
5
|
+
export const ResolvePlaceholdersView = ({ candidate, onResolve, onBack, onCancel, }) => {
|
|
6
|
+
const [resolutionState, setResolutionState] = useState(() => createPlaceholderResolution(candidate));
|
|
7
|
+
const { currentPlaceholder, currentBuffer, resolvedValues } = getPlaceholderResolutionView(resolutionState);
|
|
21
8
|
useInput((input, key) => {
|
|
22
9
|
if (key.ctrl && input === "c") {
|
|
23
10
|
onCancel();
|
|
24
11
|
return;
|
|
25
12
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
onBack();
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
const newValues = [...values];
|
|
32
|
-
newValues[activeIndex] = "";
|
|
33
|
-
setValues(newValues);
|
|
34
|
-
setActiveIndex((prev) => prev - 1);
|
|
35
|
-
setBuffer(values[activeIndex - 1]);
|
|
36
|
-
}
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
if (key.return) {
|
|
40
|
-
const newValues = [...values];
|
|
41
|
-
newValues[activeIndex] = buffer;
|
|
42
|
-
setValues(newValues);
|
|
43
|
-
if (activeIndex === placeholders.length - 1) {
|
|
44
|
-
const resultMap = new Map();
|
|
45
|
-
placeholders.forEach((p, i) => {
|
|
46
|
-
resultMap.set(p.name, newValues[i]);
|
|
47
|
-
});
|
|
48
|
-
onResolve(resultMap);
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
setActiveIndex((prev) => prev + 1);
|
|
52
|
-
setBuffer(values[activeIndex + 1]);
|
|
53
|
-
}
|
|
13
|
+
const resolutionInput = toPlaceholderResolutionInput(input, key);
|
|
14
|
+
if (!resolutionInput) {
|
|
54
15
|
return;
|
|
55
16
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
17
|
+
const transition = applyPlaceholderResolutionInput(resolutionState, resolutionInput);
|
|
18
|
+
switch (transition.type) {
|
|
19
|
+
case "editing":
|
|
20
|
+
setResolutionState(transition.state);
|
|
21
|
+
return;
|
|
22
|
+
case "back-to-selection":
|
|
23
|
+
onBack();
|
|
24
|
+
return;
|
|
25
|
+
case "complete":
|
|
26
|
+
onResolve(transition.resolved);
|
|
27
|
+
return;
|
|
62
28
|
}
|
|
63
|
-
// Handle space and other printable characters that might be in key but not in input if needed
|
|
64
|
-
// However ink's useInput input is usually the string.
|
|
65
29
|
});
|
|
66
30
|
return (React.createElement(Box, { flexDirection: "column", marginY: 1 },
|
|
67
|
-
React.createElement(SelectedCommandDisplay, { candidate: candidate, resolvedValues:
|
|
31
|
+
React.createElement(SelectedCommandDisplay, { candidate: candidate, resolvedValues: resolvedValues, currentBuffer: currentBuffer }),
|
|
68
32
|
React.createElement(Text, { color: "green" }, "? Fill command placeholders"),
|
|
69
33
|
React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
70
34
|
React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
71
35
|
React.createElement(Text, null,
|
|
72
|
-
|
|
36
|
+
currentPlaceholder.name,
|
|
73
37
|
": ",
|
|
74
|
-
|
|
38
|
+
currentPlaceholder.description),
|
|
75
39
|
React.createElement(Box, null,
|
|
76
40
|
React.createElement(Text, { color: "cyan" }, "> "),
|
|
77
|
-
React.createElement(Text, null,
|
|
41
|
+
React.createElement(Text, null, currentBuffer.value),
|
|
78
42
|
React.createElement(Text, { backgroundColor: "white" }, " ")))),
|
|
79
43
|
React.createElement(Text, { dimColor: true }, "Press Enter for next value, Esc to go back, Ctrl+C to cancel.")));
|
|
80
44
|
};
|
|
45
|
+
function toPlaceholderResolutionInput(input, key) {
|
|
46
|
+
if (key.escape) {
|
|
47
|
+
return { type: "escape" };
|
|
48
|
+
}
|
|
49
|
+
if (key.return) {
|
|
50
|
+
return { type: "commit" };
|
|
51
|
+
}
|
|
52
|
+
if (key.backspace || key.delete) {
|
|
53
|
+
return { type: "delete" };
|
|
54
|
+
}
|
|
55
|
+
if (input && !Object.values(key).some(Boolean)) {
|
|
56
|
+
return { type: "append", value: input };
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
@@ -5,6 +5,64 @@ export class PlaceholderResolutionError extends Error {
|
|
|
5
5
|
this.name = "PlaceholderResolutionError";
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
|
+
export function createPlaceholderResolution(candidate) {
|
|
9
|
+
if (candidate.placeholders.length === 0) {
|
|
10
|
+
throw new PlaceholderResolutionError("cannot resolve command without placeholders");
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
candidate,
|
|
14
|
+
activeIndex: 0,
|
|
15
|
+
values: candidate.placeholders.map(() => ""),
|
|
16
|
+
buffer: "",
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function getPlaceholderResolutionView(state) {
|
|
20
|
+
const currentPlaceholder = state.candidate.placeholders[state.activeIndex];
|
|
21
|
+
if (!currentPlaceholder) {
|
|
22
|
+
throw new PlaceholderResolutionError("placeholder resolution state is out of bounds");
|
|
23
|
+
}
|
|
24
|
+
const resolvedValues = new Map();
|
|
25
|
+
state.candidate.placeholders.forEach((placeholder, index) => {
|
|
26
|
+
if (index < state.activeIndex) {
|
|
27
|
+
resolvedValues.set(placeholder.name, state.values[index]);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return {
|
|
31
|
+
currentPlaceholder,
|
|
32
|
+
currentBuffer: {
|
|
33
|
+
name: currentPlaceholder.name,
|
|
34
|
+
value: state.buffer,
|
|
35
|
+
},
|
|
36
|
+
resolvedValues,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function applyPlaceholderResolutionInput(state, input) {
|
|
40
|
+
switch (input.type) {
|
|
41
|
+
case "append":
|
|
42
|
+
return {
|
|
43
|
+
type: "editing",
|
|
44
|
+
state: Object.assign(Object.assign({}, state), { buffer: state.buffer + input.value }),
|
|
45
|
+
};
|
|
46
|
+
case "delete":
|
|
47
|
+
return {
|
|
48
|
+
type: "editing",
|
|
49
|
+
state: Object.assign(Object.assign({}, state), { buffer: state.buffer.slice(0, -1) }),
|
|
50
|
+
};
|
|
51
|
+
case "escape":
|
|
52
|
+
return applyEscape(state);
|
|
53
|
+
case "commit":
|
|
54
|
+
return applyCommit(state);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function resolveCandidatePlaceholders(candidate, values) {
|
|
58
|
+
const command = replaceCommandPlaceholders(candidate.command, values);
|
|
59
|
+
assertNoUnresolvedPlaceholders(command);
|
|
60
|
+
return {
|
|
61
|
+
candidate,
|
|
62
|
+
values,
|
|
63
|
+
command,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
8
66
|
export function replaceCommandPlaceholders(command, values) {
|
|
9
67
|
let resolvedCommand = command;
|
|
10
68
|
for (const [name, value] of values) {
|
|
@@ -17,3 +75,34 @@ export function assertNoUnresolvedPlaceholders(command) {
|
|
|
17
75
|
throw new PlaceholderResolutionError("final command contains unresolved placeholders");
|
|
18
76
|
}
|
|
19
77
|
}
|
|
78
|
+
function applyEscape(state) {
|
|
79
|
+
if (state.activeIndex === 0) {
|
|
80
|
+
return { type: "back-to-selection" };
|
|
81
|
+
}
|
|
82
|
+
const previousIndex = state.activeIndex - 1;
|
|
83
|
+
const values = [...state.values];
|
|
84
|
+
values[state.activeIndex] = "";
|
|
85
|
+
return {
|
|
86
|
+
type: "editing",
|
|
87
|
+
state: Object.assign(Object.assign({}, state), { activeIndex: previousIndex, values, buffer: values[previousIndex] }),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function applyCommit(state) {
|
|
91
|
+
const values = [...state.values];
|
|
92
|
+
values[state.activeIndex] = state.buffer;
|
|
93
|
+
if (state.activeIndex === state.candidate.placeholders.length - 1) {
|
|
94
|
+
const resolvedValues = new Map();
|
|
95
|
+
state.candidate.placeholders.forEach((placeholder, index) => {
|
|
96
|
+
resolvedValues.set(placeholder.name, values[index]);
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
type: "complete",
|
|
100
|
+
resolved: resolveCandidatePlaceholders(state.candidate, resolvedValues),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const nextIndex = state.activeIndex + 1;
|
|
104
|
+
return {
|
|
105
|
+
type: "editing",
|
|
106
|
+
state: Object.assign(Object.assign({}, state), { activeIndex: nextIndex, values, buffer: values[nextIndex] }),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { parseAndValidateAiResponse } from "./ai-response.js";
|
|
11
|
+
import { validateUseCommandCandidates } from "./command-tool.js";
|
|
12
|
+
export function generateValidatedCommandCandidates(provider, request) {
|
|
13
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
14
|
+
const result = yield provider.generateCommands(request);
|
|
15
|
+
const response = parseAndValidateAiResponse(result.rawText);
|
|
16
|
+
validateUseCommandCandidates(response, request.useCommand);
|
|
17
|
+
return response.commands;
|
|
18
|
+
});
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unscientificjszhai/howto",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Use AI to quickly find commands within the terminal.",
|
|
6
|
+
"license": "MIT",
|
|
6
7
|
"main": "dist/index.js",
|
|
7
8
|
"repository": {
|
|
8
9
|
"type": "git",
|
|
@@ -16,7 +17,8 @@
|
|
|
16
17
|
"howto": "dist/index.js"
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
19
|
-
"dist"
|
|
20
|
+
"dist",
|
|
21
|
+
"THIRD_PARTY_NOTICES.md"
|
|
20
22
|
],
|
|
21
23
|
"scripts": {
|
|
22
24
|
"build": "tsc",
|