mini-coder 0.4.1 → 0.5.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.
Files changed (51) hide show
  1. package/README.md +87 -48
  2. package/assets/icon-1-minimal.svg +31 -0
  3. package/assets/icon-2-dark-terminal.svg +48 -0
  4. package/assets/icon-3-gradient-modern.svg +45 -0
  5. package/assets/icon-4-filled-bold.svg +54 -0
  6. package/assets/icon-5-community-badge.svg +63 -0
  7. package/assets/preview-0-5-0.png +0 -0
  8. package/assets/preview.gif +0 -0
  9. package/bin/mc.ts +14 -0
  10. package/bun.lock +438 -0
  11. package/package.json +12 -29
  12. package/src/agent.ts +592 -0
  13. package/src/cli.ts +124 -0
  14. package/src/git.ts +164 -0
  15. package/src/headless.ts +140 -0
  16. package/src/index.ts +645 -0
  17. package/src/input.ts +155 -0
  18. package/src/paths.ts +37 -0
  19. package/src/plugins.ts +183 -0
  20. package/src/prompt.ts +294 -0
  21. package/src/session.ts +838 -0
  22. package/src/settings.ts +184 -0
  23. package/src/skills.ts +258 -0
  24. package/src/submit.ts +323 -0
  25. package/src/theme.ts +147 -0
  26. package/src/tools.ts +636 -0
  27. package/src/ui/agent.test.ts +49 -0
  28. package/src/ui/agent.ts +210 -0
  29. package/src/ui/commands.test.ts +610 -0
  30. package/src/ui/commands.ts +638 -0
  31. package/src/ui/conversation.test.ts +892 -0
  32. package/src/ui/conversation.ts +926 -0
  33. package/src/ui/help.test.ts +26 -0
  34. package/src/ui/help.ts +119 -0
  35. package/src/ui/input.test.ts +74 -0
  36. package/src/ui/input.ts +138 -0
  37. package/src/ui/overlay.test.ts +42 -0
  38. package/src/ui/overlay.ts +59 -0
  39. package/src/ui/status.test.ts +450 -0
  40. package/src/ui/status.ts +357 -0
  41. package/src/ui.ts +615 -0
  42. package/.claude/settings.local.json +0 -54
  43. package/.prettierignore +0 -7
  44. package/dist/mc-edit.js +0 -275
  45. package/dist/mc.js +0 -7355
  46. package/docs/KNOWN_ISSUES.md +0 -13
  47. package/docs/design-decisions.md +0 -31
  48. package/docs/mini-coder.1.md +0 -227
  49. package/docs/superpowers/plans/2026-03-30-anthropic-oauth-removal.md +0 -61
  50. package/docs/superpowers/specs/2026-03-30-anthropic-oauth-removal-design.md +0 -47
  51. package/lefthook.yml +0 -4
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Input parsing, message submission, and streaming agent-event handling for the terminal UI.
3
+ *
4
+ * This module owns the transient streaming render state for the in-progress
5
+ * assistant response. Persistent session and message state remain on
6
+ * {@link AppState}; transient UI concerns are exposed through runtime hooks so
7
+ * `ui.ts` can stay a small orchestrator.
8
+ *
9
+ * @module
10
+ */
11
+
12
+ import type { AssistantMessage } from "@mariozechner/pi-ai";
13
+ import type { AgentEvent } from "../agent.ts";
14
+ import type { AppState } from "../index.ts";
15
+ import { resolveRawInput, submitResolvedInput } from "../submit.ts";
16
+ import type {
17
+ PendingToolResult,
18
+ StreamingConversationState,
19
+ } from "./conversation.ts";
20
+
21
+ export { isEmptyUserContent, stripSkillFrontmatter } from "../submit.ts";
22
+
23
+ /** Streaming assistant content for the current response. */
24
+ let streamingContent: AssistantMessage["content"] = [];
25
+
26
+ /** Whether a response is currently streaming. */
27
+ let isStreaming = false;
28
+
29
+ /** Tool results collected during the current streaming turn. */
30
+ let pendingToolResults: PendingToolResult[] = [];
31
+
32
+ /** Hooks implemented by `ui.ts` to bridge controller logic with runtime UI state. */
33
+ interface UiAgentRuntime {
34
+ /** Append a UI-only informational message to the conversation log. */
35
+ appendInfoMessage: (text: string, state: AppState) => void;
36
+ /** Dispatch a parsed slash command. */
37
+ handleCommand: (command: string, state: AppState) => boolean;
38
+ /** Trigger a UI render. */
39
+ render: () => void;
40
+ /** Re-enable stick-to-bottom behavior for the conversation log. */
41
+ scrollConversationToBottom: () => void;
42
+ /** Start the active-turn divider animation. */
43
+ startDividerAnimation: () => void;
44
+ /** Stop the active-turn divider animation. */
45
+ stopDividerAnimation: () => void;
46
+ }
47
+
48
+ /** Public controller API for raw input dispatch and streaming state. */
49
+ interface UiAgentController {
50
+ /** Route raw user input through parseInput and dispatch accordingly. */
51
+ handleInput: (raw: string, state: AppState) => void;
52
+ }
53
+
54
+ /**
55
+ * Reset the transient streaming UI state owned by this module.
56
+ *
57
+ * Used by `resetUiState()` and tests to keep state isolated.
58
+ */
59
+ export function resetUiAgentState(): void {
60
+ streamingContent = [];
61
+ isStreaming = false;
62
+ pendingToolResults = [];
63
+ }
64
+
65
+ /**
66
+ * Read the current streaming tail state for conversation rendering.
67
+ *
68
+ * @returns The in-progress assistant content and pending tool results.
69
+ */
70
+ export function getStreamingConversationState(): StreamingConversationState {
71
+ return {
72
+ isStreaming,
73
+ content: streamingContent,
74
+ pendingToolResults,
75
+ };
76
+ }
77
+
78
+ function getErrorMessage(error: unknown): string {
79
+ return error instanceof Error ? error.message : String(error);
80
+ }
81
+
82
+ /**
83
+ * Create the UI agent controller bound to runtime hooks supplied by `ui.ts`.
84
+ *
85
+ * @param runtime - Runtime hooks for rendering, scrolling, and command feedback.
86
+ * @returns The controller used by the input layer and tests.
87
+ */
88
+ export function createUiAgentController(
89
+ runtime: UiAgentRuntime,
90
+ ): UiAgentController {
91
+ const submitMessageAsync = (rawInput: string, state: AppState): void => {
92
+ const resolved = resolveRawInput(rawInput, state);
93
+
94
+ switch (resolved.type) {
95
+ case "empty":
96
+ return;
97
+ case "error":
98
+ runtime.appendInfoMessage(resolved.message, state);
99
+ return;
100
+ case "command":
101
+ if (!runtime.handleCommand(resolved.command, state)) {
102
+ // Unimplemented command — ignore for now
103
+ }
104
+ return;
105
+ case "message":
106
+ break;
107
+ }
108
+
109
+ if (!state.model || state.running) {
110
+ return;
111
+ }
112
+
113
+ let submitPromise: Promise<void>;
114
+ submitPromise = submitResolvedInput(rawInput, resolved.content, state, {
115
+ onUserMessage: () => {
116
+ runtime.scrollConversationToBottom();
117
+ runtime.render();
118
+ },
119
+ onTurnStart: () => {
120
+ isStreaming = true;
121
+ streamingContent = [];
122
+ pendingToolResults = [];
123
+ runtime.startDividerAnimation();
124
+ runtime.render();
125
+ },
126
+ onEvent: (event, currentState) => handleAgentEvent(event, currentState),
127
+ onTurnEnd: () => {
128
+ resetUiAgentState();
129
+ runtime.stopDividerAnimation();
130
+ runtime.render();
131
+ },
132
+ })
133
+ .then(() => undefined)
134
+ .catch((err) => {
135
+ runtime.appendInfoMessage(
136
+ `Submit failed: ${getErrorMessage(err)}`,
137
+ state,
138
+ );
139
+ })
140
+ .finally(() => {
141
+ if (state.activeTurnPromise === submitPromise) {
142
+ state.activeTurnPromise = null;
143
+ }
144
+ });
145
+
146
+ state.activeTurnPromise = submitPromise;
147
+ };
148
+
149
+ const handleAgentEvent = (event: AgentEvent, _state: AppState): void => {
150
+ switch (event.type) {
151
+ case "text_delta":
152
+ case "thinking_delta":
153
+ case "toolcall_start":
154
+ case "toolcall_delta":
155
+ case "toolcall_end":
156
+ streamingContent = event.content;
157
+ runtime.render();
158
+ break;
159
+
160
+ case "assistant_message":
161
+ streamingContent = [];
162
+ runtime.render();
163
+ break;
164
+
165
+ case "tool_start":
166
+ break;
167
+
168
+ case "tool_delta":
169
+ case "tool_end": {
170
+ const pending = pendingToolResults.find(
171
+ (toolResult) => toolResult.toolCallId === event.toolCallId,
172
+ );
173
+ if (pending) {
174
+ pending.toolName = event.name;
175
+ pending.content = event.result.content;
176
+ pending.isError = event.result.isError;
177
+ } else {
178
+ pendingToolResults.push({
179
+ toolCallId: event.toolCallId,
180
+ toolName: event.name,
181
+ content: event.result.content,
182
+ isError: event.result.isError,
183
+ });
184
+ }
185
+ runtime.render();
186
+ break;
187
+ }
188
+
189
+ case "tool_result":
190
+ pendingToolResults = pendingToolResults.filter(
191
+ (toolResult) => toolResult.toolCallId !== event.message.toolCallId,
192
+ );
193
+ runtime.render();
194
+ break;
195
+
196
+ case "done":
197
+ case "error":
198
+ case "aborted":
199
+ resetUiAgentState();
200
+ runtime.render();
201
+ break;
202
+ }
203
+ };
204
+
205
+ const handleInput = (raw: string, state: AppState): void => {
206
+ submitMessageAsync(raw, state);
207
+ };
208
+
209
+ return { handleInput };
210
+ }