mini-coder 0.5.11 → 0.5.13
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/BENCHMARK.md +107 -0
- package/PROGRESS.md +4 -0
- package/README.md +66 -21
- package/assets/mc-claude-smart.png +0 -0
- package/assets/mc-gpt-smart.png +0 -0
- package/benchmark-baseline.sh +15 -0
- package/benchmark-loop.sh +19 -0
- package/bun.lock +265 -90
- package/package.json +8 -7
- package/skills-lock.json +15 -0
- package/src/agent.ts +20 -0
- package/src/cli.ts +2 -1
- package/src/headless.ts +152 -38
- package/src/index.ts +107 -143
- package/src/input.ts +13 -1
- package/src/mcp.ts +609 -0
- package/src/prompt.ts +10 -12
- package/src/session-message.ts +393 -0
- package/src/session.ts +102 -396
- package/src/settings.ts +218 -22
- package/src/shared.ts +39 -0
- package/src/skills.ts +12 -3
- package/src/submit.ts +20 -25
- package/src/text.ts +71 -0
- package/src/theme.ts +186 -3
- package/src/tool-common.ts +93 -0
- package/src/tool-grep.ts +606 -0
- package/src/tool-read.ts +313 -0
- package/src/tool-shell.ts +1001 -0
- package/src/tools.ts +190 -995
- package/src/ui/agent.ts +206 -110
- package/src/ui/commands.test.ts +489 -17
- package/src/ui/commands.ts +231 -55
- package/src/ui/conversation.test.ts +585 -0
- package/src/ui/conversation.ts +878 -455
- package/src/ui/help.ts +27 -11
- package/src/ui/input.test.ts +1 -43
- package/src/ui/runtime.ts +69 -0
- package/src/ui.ts +422 -185
- package/src/plugins.ts +0 -183
package/src/ui/help.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { AppState } from "../index.ts";
|
|
8
|
-
import { COMMANDS } from "../input.ts";
|
|
8
|
+
import { COMMANDS, SKILL_COMMAND } from "../input.ts";
|
|
9
9
|
import { abbreviatePath } from "./status.ts";
|
|
10
10
|
|
|
11
11
|
/** Help text inputs derived from application state. */
|
|
@@ -18,12 +18,12 @@ export interface HelpRenderState {
|
|
|
18
18
|
agentsMd: AppState["agentsMd"];
|
|
19
19
|
/** Discovered skills. */
|
|
20
20
|
skills: AppState["skills"];
|
|
21
|
-
/** Active plugins. */
|
|
22
|
-
plugins: AppState["plugins"];
|
|
23
21
|
/** Whether reasoning blocks are shown in the log. */
|
|
24
22
|
showReasoning: AppState["showReasoning"];
|
|
25
23
|
/** Whether verbose tool rendering is enabled in the log. */
|
|
26
24
|
verbose: AppState["verbose"];
|
|
25
|
+
/** Configured MCP servers and their current on/off state. */
|
|
26
|
+
mcpServers: AppState["mcpServers"];
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/** Command descriptions for `/help` and command autocomplete. */
|
|
@@ -36,12 +36,20 @@ export const COMMAND_DESCRIPTIONS: Record<string, string> = {
|
|
|
36
36
|
undo: "Undo last turn",
|
|
37
37
|
reasoning: "Toggle thinking display",
|
|
38
38
|
verbose: "Toggle verbose tool rendering",
|
|
39
|
+
mcp: "Toggle configured MCP servers",
|
|
39
40
|
todo: "Show the current todo list",
|
|
40
41
|
login: "OAuth login",
|
|
41
42
|
logout: "OAuth logout",
|
|
42
43
|
help: "Show help",
|
|
43
44
|
};
|
|
44
45
|
|
|
46
|
+
/** Slash-skill label shown in `/help` and command autocomplete. */
|
|
47
|
+
export const SKILL_REFERENCE_LABEL = `/${SKILL_COMMAND}:name`;
|
|
48
|
+
|
|
49
|
+
/** Description for the slash-skill input helper. */
|
|
50
|
+
export const SKILL_REFERENCE_DESCRIPTION =
|
|
51
|
+
"Insert a discovered skill into the next message";
|
|
52
|
+
|
|
45
53
|
/**
|
|
46
54
|
* Get the `/help` description for a command, including current state when relevant.
|
|
47
55
|
*
|
|
@@ -71,6 +79,12 @@ function formatInlineCodeList(items: readonly string[]): string {
|
|
|
71
79
|
return items.map((item) => formatInlineCode(item)).join(", ");
|
|
72
80
|
}
|
|
73
81
|
|
|
82
|
+
function formatMcpServerState(
|
|
83
|
+
server: HelpRenderState["mcpServers"][number],
|
|
84
|
+
): string {
|
|
85
|
+
return `${formatInlineCode(server.name)} (${server.enabled ? "on" : "off"})`;
|
|
86
|
+
}
|
|
87
|
+
|
|
74
88
|
/**
|
|
75
89
|
* Build the `/help` text shown in the conversation log.
|
|
76
90
|
*
|
|
@@ -85,15 +99,21 @@ export function buildHelpText(state: HelpRenderState): string {
|
|
|
85
99
|
`- ${formatInlineCode(`/${command}`)} — ${getHelpCommandDescription(command, state)}`,
|
|
86
100
|
);
|
|
87
101
|
}
|
|
102
|
+
lines.push(
|
|
103
|
+
`- ${formatInlineCode(SKILL_REFERENCE_LABEL)} — ${SKILL_REFERENCE_DESCRIPTION}; submit ${formatInlineCode(`/${SKILL_COMMAND}`)} to open the skill picker.`,
|
|
104
|
+
);
|
|
88
105
|
|
|
89
106
|
const providerNames = Array.from(state.providers.keys());
|
|
107
|
+
const mcpServerStates = state.mcpServers.map((server) =>
|
|
108
|
+
formatMcpServerState(server),
|
|
109
|
+
);
|
|
90
110
|
lines.push(
|
|
91
111
|
"",
|
|
92
112
|
"## Keyboard",
|
|
93
113
|
"",
|
|
94
114
|
"- `Enter` submits the current draft.",
|
|
95
115
|
"- `Shift+Enter` inserts a newline.",
|
|
96
|
-
"- `Tab` opens command autocomplete when the draft starts with
|
|
116
|
+
"- `Tab` opens command autocomplete when the draft starts with `/`, preserving the current draft.",
|
|
97
117
|
"- Otherwise, `Tab` autocompletes file paths and can open a path picker when there are multiple matches.",
|
|
98
118
|
"- `Ctrl+R` opens global input history search.",
|
|
99
119
|
"- `Escape` closes the current overlay and returns focus to the input.",
|
|
@@ -111,6 +131,9 @@ export function buildHelpText(state: HelpRenderState): string {
|
|
|
111
131
|
state.model
|
|
112
132
|
? `- Model: ${formatInlineCode(`${state.model.provider}/${state.model.id}`)}`
|
|
113
133
|
: "- Model: none — use `/model`",
|
|
134
|
+
mcpServerStates.length > 0
|
|
135
|
+
? `- MCP servers: ${mcpServerStates.join(", ")}`
|
|
136
|
+
: "- MCP servers: none — configure them in `settings.json`",
|
|
114
137
|
);
|
|
115
138
|
|
|
116
139
|
if (state.agentsMd.length > 0) {
|
|
@@ -131,12 +154,5 @@ export function buildHelpText(state: HelpRenderState): string {
|
|
|
131
154
|
}
|
|
132
155
|
}
|
|
133
156
|
|
|
134
|
-
if (state.plugins.length > 0) {
|
|
135
|
-
lines.push("", "## Plugins", "");
|
|
136
|
-
for (const plugin of state.plugins) {
|
|
137
|
-
lines.push(`- ${formatInlineCode(plugin.entry.name)}`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
157
|
return lines.join("\n");
|
|
142
158
|
}
|
package/src/ui/input.test.ts
CHANGED
|
@@ -2,13 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
autocompleteInputPath,
|
|
8
|
-
findInputPathMatches,
|
|
9
|
-
type InputController,
|
|
10
|
-
renderInputArea,
|
|
11
|
-
} from "./input.ts";
|
|
5
|
+
import { autocompleteInputPath, findInputPathMatches } from "./input.ts";
|
|
12
6
|
|
|
13
7
|
const tempDirs: string[] = [];
|
|
14
8
|
|
|
@@ -24,43 +18,7 @@ afterEach(() => {
|
|
|
24
18
|
}
|
|
25
19
|
});
|
|
26
20
|
|
|
27
|
-
function expectTextInput(node: ReturnType<typeof renderInputArea>) {
|
|
28
|
-
if (node.type !== "textinput") {
|
|
29
|
-
throw new Error("Expected a TextInput node");
|
|
30
|
-
}
|
|
31
|
-
return node;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
21
|
describe("ui/input", () => {
|
|
35
|
-
test("renderInputArea shows the current draft with the configured placeholder and size limits", () => {
|
|
36
|
-
const controller: InputController = {
|
|
37
|
-
onChange: () => {},
|
|
38
|
-
onFocus: () => {},
|
|
39
|
-
onBlur: () => {},
|
|
40
|
-
onKeyPress: () => undefined,
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const input = expectTextInput(
|
|
44
|
-
renderInputArea(DEFAULT_THEME, controller, "draft", true),
|
|
45
|
-
);
|
|
46
|
-
const placeholder = input.props.placeholder;
|
|
47
|
-
|
|
48
|
-
expect(input.props.value).toBe("draft");
|
|
49
|
-
expect(input.props.focused).toBe(true);
|
|
50
|
-
expect(input.props.minHeight).toBe(2);
|
|
51
|
-
expect(input.props.maxHeight).toBe(10);
|
|
52
|
-
expect(input.props.padding).toEqual({ x: 1 });
|
|
53
|
-
expect(placeholder?.type).toBe("text");
|
|
54
|
-
if (!placeholder || placeholder.type !== "text") {
|
|
55
|
-
throw new Error("Expected a text placeholder");
|
|
56
|
-
}
|
|
57
|
-
expect(placeholder.content).toBe(
|
|
58
|
-
"`Ctrl+R` for input history, `/` + `Tab` for interactive menu, or type a message…",
|
|
59
|
-
);
|
|
60
|
-
expect(placeholder.props.fgColor).toBe(DEFAULT_THEME.mutedText);
|
|
61
|
-
expect(placeholder.props.italic).toBe(true);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
22
|
test("autocompleteInputPath completes the last file path token when exactly one match is available", () => {
|
|
65
23
|
const cwd = createTempDir();
|
|
66
24
|
mkdirSync(join(cwd, "src"), { recursive: true });
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-only helper bindings for the terminal UI.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { AppState } from "../index.ts";
|
|
8
|
+
import {
|
|
9
|
+
appendConversationMessage,
|
|
10
|
+
appendMessage,
|
|
11
|
+
createUiMessage,
|
|
12
|
+
createUiTodoMessage,
|
|
13
|
+
type UiInfoFormat,
|
|
14
|
+
type UiMessage,
|
|
15
|
+
} from "../session.ts";
|
|
16
|
+
import type { TodoItem } from "../tools.ts";
|
|
17
|
+
|
|
18
|
+
/** Render scheduling priorities used by the terminal UI. */
|
|
19
|
+
export type UiRenderPriority = "immediate" | "normal" | "stream" | "animation";
|
|
20
|
+
|
|
21
|
+
interface UiRuntimeHooks {
|
|
22
|
+
/** Schedule a UI render. */
|
|
23
|
+
requestRender: (priority?: UiRenderPriority) => void;
|
|
24
|
+
/** Re-enable stick-to-bottom behavior for the conversation log. */
|
|
25
|
+
scrollConversationToBottom: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface UiRuntimeHelpers {
|
|
29
|
+
/** Append a UI-only info message to the conversation log. */
|
|
30
|
+
appendInfoMessage: (
|
|
31
|
+
text: string,
|
|
32
|
+
state: AppState,
|
|
33
|
+
format?: UiInfoFormat,
|
|
34
|
+
) => void;
|
|
35
|
+
/** Append a UI-only todo snapshot to the conversation log. */
|
|
36
|
+
appendTodoMessage: (todos: readonly TodoItem[], state: AppState) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function appendUiMessage(
|
|
40
|
+
message: UiMessage,
|
|
41
|
+
state: AppState,
|
|
42
|
+
runtime: UiRuntimeHooks,
|
|
43
|
+
): void {
|
|
44
|
+
if (state.session) {
|
|
45
|
+
appendMessage(state.db, state.session.id, message);
|
|
46
|
+
}
|
|
47
|
+
appendConversationMessage(state, message);
|
|
48
|
+
runtime.scrollConversationToBottom();
|
|
49
|
+
runtime.requestRender("normal");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create runtime-bound helpers that append UI-only conversation messages.
|
|
54
|
+
*
|
|
55
|
+
* @param runtime - Render and scroll hooks owned by `ui.ts`.
|
|
56
|
+
* @returns Helpers shared by the command and agent controllers.
|
|
57
|
+
*/
|
|
58
|
+
export function createUiRuntimeHelpers(
|
|
59
|
+
runtime: UiRuntimeHooks,
|
|
60
|
+
): UiRuntimeHelpers {
|
|
61
|
+
return {
|
|
62
|
+
appendInfoMessage: (text, state, format) => {
|
|
63
|
+
appendUiMessage(createUiMessage(text, format), state, runtime);
|
|
64
|
+
},
|
|
65
|
+
appendTodoMessage: (todos, state) => {
|
|
66
|
+
appendUiMessage(createUiTodoMessage(todos), state, runtime);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|