mini-coder 0.5.6 → 0.5.8
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.md +5 -2
- package/bun.lock +13 -13
- package/package.json +3 -3
- package/src/agent.ts +299 -41
- package/src/cli.ts +11 -2
- package/src/headless.ts +185 -47
- package/src/index.ts +105 -12
- package/src/input.ts +1 -0
- package/src/prompt.ts +86 -78
- package/src/session.ts +56 -8
- package/src/submit.ts +53 -11
- package/src/tools.ts +401 -44
- package/src/ui/agent.ts +16 -2
- package/src/ui/commands.test.ts +72 -0
- package/src/ui/commands.ts +13 -0
- package/src/ui/conversation.test.ts +91 -0
- package/src/ui/conversation.ts +122 -3
- package/src/ui/help.test.ts +1 -0
- package/src/ui/help.ts +1 -0
- package/src/ui/input.test.ts +29 -1
- package/src/ui/input.ts +45 -51
- package/src/ui/render-performance.test.ts +1 -0
- package/src/ui/status.test.ts +1 -0
- package/src/ui.ts +85 -18
package/src/headless.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import type { AssistantMessage, UserMessage } from "@mariozechner/pi-ai";
|
|
8
|
+
import type { AgentEvent } from "./agent.ts";
|
|
7
9
|
import type { AppState } from "./index.ts";
|
|
8
10
|
import {
|
|
9
11
|
resolveRawInput,
|
|
@@ -15,18 +17,37 @@ import {
|
|
|
15
17
|
// Types
|
|
16
18
|
// ---------------------------------------------------------------------------
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
type HeadlessStopReason = "stop" | "length" | "error" | "aborted";
|
|
21
|
+
|
|
22
|
+
/** Options for a headless NDJSON run. */
|
|
19
23
|
export interface HeadlessRunOptions {
|
|
20
|
-
/** Optional line writer for NDJSON event output. */
|
|
24
|
+
/** Optional line writer for completed NDJSON event output. */
|
|
21
25
|
writeLine?: (line: string) => void;
|
|
22
26
|
}
|
|
23
27
|
|
|
28
|
+
/** Options for a headless final-text run. */
|
|
29
|
+
export interface HeadlessTextRunOptions {
|
|
30
|
+
/** Optional writer for the final assistant text output. */
|
|
31
|
+
writeText?: (text: string) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface HeadlessOutputController {
|
|
35
|
+
/** Write text to stdout with broken-pipe handling. */
|
|
36
|
+
write(text: string): void;
|
|
37
|
+
/** Attach SIGINT/stdout error handlers for the active run. */
|
|
38
|
+
attach(): void;
|
|
39
|
+
/** Remove SIGINT/stdout error handlers after the run. */
|
|
40
|
+
detach(): void;
|
|
41
|
+
/** Resolve the final stop reason, converting broken pipes into quiet shutdowns. */
|
|
42
|
+
finalize(stopReason: HeadlessStopReason): HeadlessStopReason;
|
|
43
|
+
}
|
|
44
|
+
|
|
24
45
|
// ---------------------------------------------------------------------------
|
|
25
46
|
// Helpers
|
|
26
47
|
// ---------------------------------------------------------------------------
|
|
27
48
|
|
|
28
|
-
function
|
|
29
|
-
process.stdout.write(
|
|
49
|
+
function defaultWrite(text: string): void {
|
|
50
|
+
process.stdout.write(text);
|
|
30
51
|
}
|
|
31
52
|
|
|
32
53
|
function isBrokenPipeError(error: unknown): boolean {
|
|
@@ -52,23 +73,10 @@ function buildCommandError(command: string): Error {
|
|
|
52
73
|
);
|
|
53
74
|
}
|
|
54
75
|
|
|
55
|
-
|
|
56
|
-
* Run a single headless prompt to completion and stream NDJSON events.
|
|
57
|
-
*
|
|
58
|
-
* The raw input is parsed with the same rules as interactive input. Slash
|
|
59
|
-
* commands are rejected in headless mode. Assistant/tool events are written as
|
|
60
|
-
* one JSON object per line.
|
|
61
|
-
*
|
|
62
|
-
* @param state - Mutable application state for the run.
|
|
63
|
-
* @param rawInput - Exact raw prompt text supplied by the user.
|
|
64
|
-
* @param options - Optional event-output overrides.
|
|
65
|
-
* @returns The terminal stop reason for the agent loop.
|
|
66
|
-
*/
|
|
67
|
-
export async function runHeadlessPrompt(
|
|
76
|
+
function resolveHeadlessContent(
|
|
68
77
|
state: AppState,
|
|
69
78
|
rawInput: string,
|
|
70
|
-
|
|
71
|
-
): Promise<"stop" | "length" | "error" | "aborted"> {
|
|
79
|
+
): UserMessage["content"] {
|
|
72
80
|
const resolved = resolveRawInput(rawInput, state);
|
|
73
81
|
switch (resolved.type) {
|
|
74
82
|
case "empty":
|
|
@@ -78,11 +86,52 @@ export async function runHeadlessPrompt(
|
|
|
78
86
|
case "command":
|
|
79
87
|
throw buildCommandError(resolved.command);
|
|
80
88
|
case "message":
|
|
81
|
-
|
|
89
|
+
return resolved.content;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function extractAssistantText(message: AssistantMessage | null): string {
|
|
94
|
+
if (!message) {
|
|
95
|
+
return "";
|
|
82
96
|
}
|
|
83
97
|
|
|
98
|
+
return message.content
|
|
99
|
+
.filter(
|
|
100
|
+
(
|
|
101
|
+
block,
|
|
102
|
+
): block is Extract<
|
|
103
|
+
AssistantMessage["content"][number],
|
|
104
|
+
{ type: "text" }
|
|
105
|
+
> => {
|
|
106
|
+
return block.type === "text";
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
.map((block) => block.text)
|
|
110
|
+
.join("");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function shouldWriteHeadlessJsonEvent(event: AgentEvent): boolean {
|
|
114
|
+
switch (event.type) {
|
|
115
|
+
case "user_message":
|
|
116
|
+
case "assistant_message":
|
|
117
|
+
case "tool_result":
|
|
118
|
+
case "done":
|
|
119
|
+
case "error":
|
|
120
|
+
case "aborted":
|
|
121
|
+
return true;
|
|
122
|
+
default:
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function createHeadlessOutputController(
|
|
128
|
+
state: AppState,
|
|
129
|
+
writeImpl: (text: string) => void,
|
|
130
|
+
): HeadlessOutputController {
|
|
84
131
|
let brokenPipe = false;
|
|
85
132
|
let outputError: unknown = null;
|
|
133
|
+
const sigintHandler = createSigintHandler(state);
|
|
134
|
+
|
|
86
135
|
const stopForBrokenPipe = (): void => {
|
|
87
136
|
if (brokenPipe) {
|
|
88
137
|
return;
|
|
@@ -90,51 +139,140 @@ export async function runHeadlessPrompt(
|
|
|
90
139
|
brokenPipe = true;
|
|
91
140
|
state.abortController?.abort();
|
|
92
141
|
};
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
142
|
+
|
|
143
|
+
const stdoutErrorHandler = (error: unknown): void => {
|
|
144
|
+
if (isBrokenPipeError(error)) {
|
|
145
|
+
stopForBrokenPipe();
|
|
96
146
|
return;
|
|
97
147
|
}
|
|
98
148
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
149
|
+
outputError = error;
|
|
150
|
+
state.abortController?.abort();
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
write(text) {
|
|
155
|
+
if (brokenPipe) {
|
|
156
|
+
return;
|
|
104
157
|
}
|
|
105
|
-
|
|
106
|
-
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
writeImpl(text);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (!isBrokenPipeError(error)) {
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
stopForBrokenPipe();
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
attach() {
|
|
169
|
+
process.stdout.on("error", stdoutErrorHandler);
|
|
170
|
+
process.on("SIGINT", sigintHandler);
|
|
171
|
+
},
|
|
172
|
+
detach() {
|
|
173
|
+
process.stdout.off("error", stdoutErrorHandler);
|
|
174
|
+
process.off("SIGINT", sigintHandler);
|
|
175
|
+
},
|
|
176
|
+
finalize(stopReason) {
|
|
177
|
+
if (outputError) {
|
|
178
|
+
throw outputError;
|
|
179
|
+
}
|
|
180
|
+
return brokenPipe ? "stop" : stopReason;
|
|
181
|
+
},
|
|
107
182
|
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Run a single headless prompt to completion and stream completed NDJSON events.
|
|
187
|
+
*
|
|
188
|
+
* The raw input is parsed with the same rules as interactive input. Slash
|
|
189
|
+
* commands are rejected in headless mode. Persisted messages and terminal
|
|
190
|
+
* events are written as one JSON object per line; streaming delta/progress
|
|
191
|
+
* events are omitted.
|
|
192
|
+
*
|
|
193
|
+
* @param state - Mutable application state for the run.
|
|
194
|
+
* @param rawInput - Exact raw prompt text supplied by the user.
|
|
195
|
+
* @param options - Optional event-output overrides.
|
|
196
|
+
* @returns The terminal stop reason for the agent loop.
|
|
197
|
+
*/
|
|
198
|
+
export async function runHeadlessPrompt(
|
|
199
|
+
state: AppState,
|
|
200
|
+
rawInput: string,
|
|
201
|
+
options?: HeadlessRunOptions,
|
|
202
|
+
): Promise<HeadlessStopReason> {
|
|
203
|
+
const content = resolveHeadlessContent(state, rawInput);
|
|
204
|
+
const output = createHeadlessOutputController(
|
|
205
|
+
state,
|
|
206
|
+
options?.writeLine ?? ((line) => defaultWrite(`${line}\n`)),
|
|
207
|
+
);
|
|
108
208
|
const hooks: SubmitTurnHooks = {
|
|
109
209
|
onEvent: (event) => {
|
|
110
|
-
|
|
210
|
+
if (!shouldWriteHeadlessJsonEvent(event)) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
output.write(JSON.stringify(event));
|
|
111
214
|
},
|
|
112
215
|
};
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
216
|
+
|
|
217
|
+
output.attach();
|
|
218
|
+
try {
|
|
219
|
+
const stopReason = await submitResolvedInput(
|
|
220
|
+
rawInput,
|
|
221
|
+
content,
|
|
222
|
+
state,
|
|
223
|
+
hooks,
|
|
224
|
+
);
|
|
225
|
+
return output.finalize(stopReason);
|
|
226
|
+
} finally {
|
|
227
|
+
output.detach();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Run a single headless prompt to completion and write only the final assistant text.
|
|
233
|
+
*
|
|
234
|
+
* The raw input is parsed with the same rules as interactive input. Slash
|
|
235
|
+
* commands are rejected in headless mode. Only the final persisted assistant
|
|
236
|
+
* message's text content is written to stdout.
|
|
237
|
+
*
|
|
238
|
+
* @param state - Mutable application state for the run.
|
|
239
|
+
* @param rawInput - Exact raw prompt text supplied by the user.
|
|
240
|
+
* @param options - Optional final-text output overrides.
|
|
241
|
+
* @returns The terminal stop reason for the agent loop.
|
|
242
|
+
*/
|
|
243
|
+
export async function runHeadlessPromptText(
|
|
244
|
+
state: AppState,
|
|
245
|
+
rawInput: string,
|
|
246
|
+
options?: HeadlessTextRunOptions,
|
|
247
|
+
): Promise<HeadlessStopReason> {
|
|
248
|
+
const content = resolveHeadlessContent(state, rawInput);
|
|
249
|
+
const output = createHeadlessOutputController(
|
|
250
|
+
state,
|
|
251
|
+
options?.writeText ?? defaultWrite,
|
|
252
|
+
);
|
|
253
|
+
let finalAssistantMessage: AssistantMessage | null = null;
|
|
254
|
+
const hooks: SubmitTurnHooks = {
|
|
255
|
+
onEvent: (event) => {
|
|
256
|
+
if (event.type === "assistant_message") {
|
|
257
|
+
finalAssistantMessage = event.message;
|
|
258
|
+
}
|
|
259
|
+
},
|
|
121
260
|
};
|
|
122
261
|
|
|
123
|
-
|
|
124
|
-
process.on("SIGINT", sigintHandler);
|
|
262
|
+
output.attach();
|
|
125
263
|
try {
|
|
126
264
|
const stopReason = await submitResolvedInput(
|
|
127
265
|
rawInput,
|
|
128
|
-
|
|
266
|
+
content,
|
|
129
267
|
state,
|
|
130
268
|
hooks,
|
|
131
269
|
);
|
|
132
|
-
|
|
133
|
-
|
|
270
|
+
const finalText = extractAssistantText(finalAssistantMessage);
|
|
271
|
+
if (finalText.length > 0) {
|
|
272
|
+
output.write(finalText);
|
|
134
273
|
}
|
|
135
|
-
return
|
|
274
|
+
return output.finalize(stopReason);
|
|
136
275
|
} finally {
|
|
137
|
-
|
|
138
|
-
process.off("SIGINT", sigintHandler);
|
|
276
|
+
output.detach();
|
|
139
277
|
}
|
|
140
278
|
}
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { homedir } from "node:os";
|
|
13
|
-
import { dirname, join } from "node:path";
|
|
13
|
+
import { basename, dirname, join } from "node:path";
|
|
14
14
|
import { isDeepStrictEqual } from "node:util";
|
|
15
15
|
import type {
|
|
16
16
|
KnownProvider,
|
|
@@ -19,14 +19,17 @@ import type {
|
|
|
19
19
|
OAuthCredentials,
|
|
20
20
|
ThinkingLevel,
|
|
21
21
|
Tool,
|
|
22
|
+
UserMessage,
|
|
22
23
|
} from "@mariozechner/pi-ai";
|
|
23
24
|
import { getEnvApiKey, getModels, getProviders } from "@mariozechner/pi-ai";
|
|
24
25
|
import { getOAuthApiKey, getOAuthProviders } from "@mariozechner/pi-ai/oauth";
|
|
25
26
|
import type { ToolHandler } from "./agent.ts";
|
|
26
27
|
import {
|
|
28
|
+
type CliOptions,
|
|
27
29
|
parseCliArgs,
|
|
28
30
|
resolveHeadlessPrompt,
|
|
29
31
|
shouldUseHeadlessMode,
|
|
32
|
+
type TtyState,
|
|
30
33
|
} from "./cli.ts";
|
|
31
34
|
import { getErrorMessage } from "./errors.ts";
|
|
32
35
|
import { type GitState, getGitState } from "./git.ts";
|
|
@@ -70,8 +73,12 @@ import {
|
|
|
70
73
|
executeEdit,
|
|
71
74
|
executeReadImage,
|
|
72
75
|
executeShell,
|
|
76
|
+
executeTodoRead,
|
|
77
|
+
executeTodoWrite,
|
|
73
78
|
readImageTool,
|
|
74
79
|
shellTool,
|
|
80
|
+
todoReadTool,
|
|
81
|
+
todoWriteTool,
|
|
75
82
|
} from "./tools.ts";
|
|
76
83
|
import { resolveAppVersionLabel } from "./version.ts";
|
|
77
84
|
|
|
@@ -381,11 +388,28 @@ const BUILTIN_HANDLERS: Record<string, ToolHandler> = {
|
|
|
381
388
|
function buildTools(
|
|
382
389
|
model: Model<string>,
|
|
383
390
|
plugins: LoadedPlugin[],
|
|
391
|
+
messages: AppState["messages"],
|
|
384
392
|
): { tools: Tool[]; toolHandlers: Map<string, ToolHandler> } {
|
|
385
|
-
const tools: Tool[] = [editTool, shellTool];
|
|
393
|
+
const tools: Tool[] = [editTool, shellTool, todoWriteTool, todoReadTool];
|
|
386
394
|
const toolHandlers = new Map<string, ToolHandler>([
|
|
387
395
|
[editTool.name, BUILTIN_HANDLERS.edit!],
|
|
388
396
|
[shellTool.name, BUILTIN_HANDLERS.shell!],
|
|
397
|
+
[
|
|
398
|
+
todoWriteTool.name,
|
|
399
|
+
(args) =>
|
|
400
|
+
executeTodoWrite(
|
|
401
|
+
{
|
|
402
|
+
todos: Array.isArray(args.todos)
|
|
403
|
+
? (args.todos as Array<{
|
|
404
|
+
content: string;
|
|
405
|
+
status: "pending" | "in_progress" | "completed" | "cancelled";
|
|
406
|
+
}>)
|
|
407
|
+
: [],
|
|
408
|
+
},
|
|
409
|
+
messages,
|
|
410
|
+
),
|
|
411
|
+
],
|
|
412
|
+
[todoReadTool.name, () => executeTodoRead(messages)],
|
|
389
413
|
]);
|
|
390
414
|
|
|
391
415
|
// Conditionally register readImage for vision-capable models
|
|
@@ -541,8 +565,6 @@ export interface AppState {
|
|
|
541
565
|
versionLabel: string;
|
|
542
566
|
/** Current git state (null if not in a repo). */
|
|
543
567
|
git: GitState | null;
|
|
544
|
-
/** Optional git state loader override used by tests. */
|
|
545
|
-
loadGitState?: (cwd: string) => Promise<GitState | null>;
|
|
546
568
|
/** Available provider credentials (provider → API key). */
|
|
547
569
|
providers: Map<string, string>;
|
|
548
570
|
/** OAuth credentials on disk. */
|
|
@@ -561,6 +583,8 @@ export interface AppState {
|
|
|
561
583
|
abortController: AbortController | null;
|
|
562
584
|
/** Promise for the active conversational turn, used to serialize cleanup like `/undo`. */
|
|
563
585
|
activeTurnPromise: Promise<void> | null;
|
|
586
|
+
/** Resolved user messages queued while the current run is still active. */
|
|
587
|
+
queuedUserMessages: UserMessage[];
|
|
564
588
|
/** Whether to show thinking content. */
|
|
565
589
|
showReasoning: boolean;
|
|
566
590
|
/** Whether to show full (un-truncated) tool output. */
|
|
@@ -641,6 +665,7 @@ export async function init(): Promise<AppState> {
|
|
|
641
665
|
running: false,
|
|
642
666
|
abortController: null,
|
|
643
667
|
activeTurnPromise: null,
|
|
668
|
+
queuedUserMessages: [],
|
|
644
669
|
showReasoning: startup.showReasoning,
|
|
645
670
|
verbose: startup.verbose,
|
|
646
671
|
customModels: customResult.models,
|
|
@@ -648,16 +673,37 @@ export async function init(): Promise<AppState> {
|
|
|
648
673
|
};
|
|
649
674
|
}
|
|
650
675
|
|
|
676
|
+
/** Resolve the shell label shown in the system prompt. */
|
|
677
|
+
function resolvePromptShell(): string {
|
|
678
|
+
return basename(process.env.SHELL || "/bin/sh");
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/** Resolve the normalized OS label shown in the system prompt. */
|
|
682
|
+
function resolvePromptOs(): "linux" | "mac" | "docker" {
|
|
683
|
+
if (process.platform === "darwin") {
|
|
684
|
+
return "mac";
|
|
685
|
+
}
|
|
686
|
+
if (existsSync("/.dockerenv") || existsSync("/run/.containerenv")) {
|
|
687
|
+
return "docker";
|
|
688
|
+
}
|
|
689
|
+
return "linux";
|
|
690
|
+
}
|
|
691
|
+
|
|
651
692
|
/**
|
|
652
693
|
* Build the system prompt for the current state.
|
|
653
694
|
*
|
|
654
|
-
* Separated from `init` because
|
|
655
|
-
*
|
|
695
|
+
* Separated from `init` because turns still rebuild the assembled prompt
|
|
696
|
+
* from the session-stable prompt context plus the current runtime state.
|
|
656
697
|
*/
|
|
657
698
|
export function buildPrompt(state: AppState): string {
|
|
658
699
|
return buildSystemPrompt({
|
|
659
700
|
cwd: state.cwd,
|
|
660
|
-
|
|
701
|
+
modelLabel: state.model
|
|
702
|
+
? `${state.model.provider}/${state.model.id}`
|
|
703
|
+
: "unknown",
|
|
704
|
+
os: resolvePromptOs(),
|
|
705
|
+
shell: resolvePromptShell(),
|
|
706
|
+
supportsImages: state.model?.input.includes("image") ?? false,
|
|
661
707
|
git: state.git,
|
|
662
708
|
agentsMd: state.agentsMd,
|
|
663
709
|
skills: state.skills,
|
|
@@ -673,7 +719,7 @@ export function buildToolList(state: AppState): {
|
|
|
673
719
|
toolHandlers: Map<string, ToolHandler>;
|
|
674
720
|
} {
|
|
675
721
|
if (!state.model) return { tools: [], toolHandlers: new Map() };
|
|
676
|
-
return buildTools(state.model, state.plugins);
|
|
722
|
+
return buildTools(state.model, state.plugins, state.messages);
|
|
677
723
|
}
|
|
678
724
|
|
|
679
725
|
/**
|
|
@@ -740,6 +786,47 @@ export {
|
|
|
740
786
|
saveOAuthCredentials,
|
|
741
787
|
};
|
|
742
788
|
|
|
789
|
+
// ---------------------------------------------------------------------------
|
|
790
|
+
// Headless CLI
|
|
791
|
+
// ---------------------------------------------------------------------------
|
|
792
|
+
|
|
793
|
+
type HeadlessCliStopReason = "stop" | "length" | "error" | "aborted";
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Run one headless CLI prompt using the output mode selected by the parsed CLI flags.
|
|
797
|
+
*
|
|
798
|
+
* Non-TTY detection only decides whether headless mode should run at all.
|
|
799
|
+
* Once headless mode is selected, `--json` is the only switch that chooses
|
|
800
|
+
* NDJSON streaming versus final-text output.
|
|
801
|
+
*
|
|
802
|
+
* @param state - Initialized application state for the run.
|
|
803
|
+
* @param cli - Parsed CLI options.
|
|
804
|
+
* @param tty - Current TTY availability.
|
|
805
|
+
* @param deps - Injected I/O and runner callbacks.
|
|
806
|
+
* @returns The terminal stop reason for the headless run.
|
|
807
|
+
*/
|
|
808
|
+
export async function runHeadlessCli(
|
|
809
|
+
state: AppState,
|
|
810
|
+
cli: CliOptions,
|
|
811
|
+
tty: TtyState,
|
|
812
|
+
deps: {
|
|
813
|
+
readStdin: () => Promise<string>;
|
|
814
|
+
runJson: (
|
|
815
|
+
state: AppState,
|
|
816
|
+
rawPrompt: string,
|
|
817
|
+
) => Promise<HeadlessCliStopReason>;
|
|
818
|
+
runText: (
|
|
819
|
+
state: AppState,
|
|
820
|
+
rawPrompt: string,
|
|
821
|
+
) => Promise<HeadlessCliStopReason>;
|
|
822
|
+
},
|
|
823
|
+
): Promise<HeadlessCliStopReason> {
|
|
824
|
+
const rawPrompt = await resolveHeadlessPrompt(cli, tty, deps.readStdin);
|
|
825
|
+
return cli.json
|
|
826
|
+
? deps.runJson(state, rawPrompt)
|
|
827
|
+
: deps.runText(state, rawPrompt);
|
|
828
|
+
}
|
|
829
|
+
|
|
743
830
|
// ---------------------------------------------------------------------------
|
|
744
831
|
// Main
|
|
745
832
|
// ---------------------------------------------------------------------------
|
|
@@ -762,11 +849,17 @@ export async function main(): Promise<void> {
|
|
|
762
849
|
|
|
763
850
|
if (shouldUseHeadlessMode(cli, tty)) {
|
|
764
851
|
try {
|
|
765
|
-
const
|
|
766
|
-
|
|
852
|
+
const stopReason = await runHeadlessCli(state, cli, tty, {
|
|
853
|
+
readStdin: async () => Bun.stdin.text(),
|
|
854
|
+
runJson: async (headlessState, rawPrompt) => {
|
|
855
|
+
const { runHeadlessPrompt } = await import("./headless.ts");
|
|
856
|
+
return runHeadlessPrompt(headlessState, rawPrompt);
|
|
857
|
+
},
|
|
858
|
+
runText: async (headlessState, rawPrompt) => {
|
|
859
|
+
const { runHeadlessPromptText } = await import("./headless.ts");
|
|
860
|
+
return runHeadlessPromptText(headlessState, rawPrompt);
|
|
861
|
+
},
|
|
767
862
|
});
|
|
768
|
-
const { runHeadlessPrompt } = await import("./headless.ts");
|
|
769
|
-
const stopReason = await runHeadlessPrompt(state, rawPrompt);
|
|
770
863
|
if (stopReason === "aborted") {
|
|
771
864
|
process.exitCode = 130;
|
|
772
865
|
} else if (stopReason === "error") {
|