mini-coder 0.7.4 → 0.8.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/AGENTS.md +114 -0
- package/README.md +53 -66
- package/bin/mini-coder.ts +2 -0
- package/demo.gif +0 -0
- package/package.json +17 -20
- package/src/agent.ts +181 -274
- package/src/cli.ts +101 -0
- package/src/config.ts +150 -0
- package/src/prompt.ts +54 -207
- package/src/session.ts +124 -69
- package/src/tools/bash.ts +89 -0
- package/src/tools/common.ts +32 -0
- package/src/tools/edit.ts +41 -0
- package/src/tools/index.ts +47 -0
- package/src/tools/read.ts +64 -0
- package/src/tui/commands.ts +63 -0
- package/src/tui/editor.ts +291 -0
- package/src/tui/highlight.ts +189 -0
- package/src/tui/stream.ts +142 -0
- package/src/tui/styles.ts +42 -0
- package/src/tui/term.ts +436 -0
- package/src/tui/theme.ts +121 -0
- package/src/tui/tui.ts +595 -0
- package/src/tui/usage.ts +67 -0
- package/tsconfig.json +8 -8
- package/bin/mc.ts +0 -11
- package/bun.lock +0 -350
- package/nono-mini-coder.json +0 -42
- package/src/args.ts +0 -252
- package/src/error-handling.test.ts +0 -163
- package/src/git.ts +0 -23
- package/src/headless.ts +0 -66
- package/src/index.ts +0 -43
- package/src/models.ts +0 -191
- package/src/oauth.ts +0 -147
- package/src/shared.ts +0 -119
- package/src/themes.ts +0 -234
- package/src/tool-bash.ts +0 -77
- package/src/tool-edit.ts +0 -121
- package/src/tool-read.ts +0 -100
- package/src/tui-components.ts +0 -127
- package/src/tui-conversation.ts +0 -218
- package/src/tui-editor.ts +0 -29
- package/src/tui-overlay.ts +0 -604
- package/src/tui.ts +0 -314
- package/src/types.ts +0 -194
- package/src/update.ts +0 -171
package/src/args.ts
DELETED
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
import { mkdir } from "node:fs/promises";
|
|
2
|
-
import type { ThinkingLevel } from "@earendil-works/pi-ai";
|
|
3
|
-
import { Value } from "typebox/value";
|
|
4
|
-
import {
|
|
5
|
-
findModelConfig,
|
|
6
|
-
getFallbackModel,
|
|
7
|
-
getFirstModelConfig,
|
|
8
|
-
} from "./models.ts";
|
|
9
|
-
import { getAvailableProviders, isOAuthProvider, loginOAuth } from "./oauth";
|
|
10
|
-
import { DATA_DIR, SETTINGS_PATH } from "./shared.ts";
|
|
11
|
-
import { DEFAULT_TUI_THEME_ID } from "./themes.ts";
|
|
12
|
-
import {
|
|
13
|
-
type CliOptions,
|
|
14
|
-
CliOptionsSchema,
|
|
15
|
-
type Settings,
|
|
16
|
-
SettingsSchema,
|
|
17
|
-
} from "./types.ts";
|
|
18
|
-
|
|
19
|
-
const DEFAULT_PROVIDER = "openai-codex";
|
|
20
|
-
const DEFAULT_MODEL_ID = "gpt-5.5";
|
|
21
|
-
const DEFAULT_EFFORT: ThinkingLevel = "xhigh";
|
|
22
|
-
|
|
23
|
-
function formatValidationError(
|
|
24
|
-
label: string,
|
|
25
|
-
error: ReturnType<typeof Value.Errors>[number] | undefined,
|
|
26
|
-
) {
|
|
27
|
-
if (!error) {
|
|
28
|
-
return `Invalid ${label}`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const path = error.instancePath || "/";
|
|
32
|
-
return `Invalid ${label}: ${path} ${error.message}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function parseSettings(value: unknown, label: string): Settings {
|
|
36
|
-
if (Value.Check(SettingsSchema, value)) {
|
|
37
|
-
return value;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
throw new Error(
|
|
41
|
-
formatValidationError(label, Value.Errors(SettingsSchema, value)[0]),
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function parseCliOptions(value: unknown): CliOptions {
|
|
46
|
-
if (Value.Check(CliOptionsSchema, value)) {
|
|
47
|
-
return value;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
throw new Error(
|
|
51
|
-
formatValidationError(
|
|
52
|
-
"CLI options",
|
|
53
|
-
Value.Errors(CliOptionsSchema, value)[0],
|
|
54
|
-
),
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async function getSettings(): Promise<Settings | undefined> {
|
|
59
|
-
const file = Bun.file(SETTINGS_PATH);
|
|
60
|
-
|
|
61
|
-
if (!(await file.exists())) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const jsonText = await file.text();
|
|
66
|
-
let settings: unknown;
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
settings = JSON.parse(jsonText) as unknown;
|
|
70
|
-
} catch (err) {
|
|
71
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
72
|
-
throw new Error(`Invalid settings JSON: ${message}`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return parseSettings(settings, "settings");
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export async function saveSettings(s: Settings) {
|
|
79
|
-
await mkdir(DATA_DIR, { recursive: true });
|
|
80
|
-
const file = Bun.file(SETTINGS_PATH);
|
|
81
|
-
await Bun.write(file, JSON.stringify(s, null, 4));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function requireValue(argv: string[], index: number, flag: string) {
|
|
85
|
-
const value = argv[index + 1];
|
|
86
|
-
|
|
87
|
-
if (!value || value.startsWith("-")) {
|
|
88
|
-
throw new Error(`${flag} requires a value`);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return value;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export async function handleArgv(argv: string[]): Promise<CliOptions> {
|
|
95
|
-
const settings = await getSettings();
|
|
96
|
-
const customProviders = settings?.customProviders ?? [];
|
|
97
|
-
const builtInProviders = await getAvailableProviders();
|
|
98
|
-
const availableProviders = [
|
|
99
|
-
...new Set([
|
|
100
|
-
...builtInProviders,
|
|
101
|
-
...customProviders.map((cp) => cp.provider),
|
|
102
|
-
]),
|
|
103
|
-
];
|
|
104
|
-
const defaultProvider = availableProviders[0] ?? DEFAULT_PROVIDER;
|
|
105
|
-
const settingsProvider = settings?.provider ?? defaultProvider;
|
|
106
|
-
const settingsModelId =
|
|
107
|
-
settings?.model ??
|
|
108
|
-
(settingsProvider === DEFAULT_PROVIDER
|
|
109
|
-
? DEFAULT_MODEL_ID
|
|
110
|
-
: getFirstModelConfig(settingsProvider, customProviders).id);
|
|
111
|
-
const settingsEffort = settings?.effort ?? DEFAULT_EFFORT;
|
|
112
|
-
const settingsTheme = settings?.theme ?? DEFAULT_TUI_THEME_ID;
|
|
113
|
-
let provider: string = settingsProvider;
|
|
114
|
-
let modelId = settingsModelId;
|
|
115
|
-
let effort: string = settingsEffort;
|
|
116
|
-
let prompt: string | undefined;
|
|
117
|
-
let providerChanged = false;
|
|
118
|
-
let explicitProvider = false;
|
|
119
|
-
let explicitModel = false;
|
|
120
|
-
|
|
121
|
-
for (let i = 0; i < argv.length; i++) {
|
|
122
|
-
const flag = argv[i];
|
|
123
|
-
|
|
124
|
-
// Settings
|
|
125
|
-
if (flag === "--provider") {
|
|
126
|
-
provider = parseSettings(
|
|
127
|
-
{
|
|
128
|
-
provider: requireValue(argv, i, flag),
|
|
129
|
-
model: modelId,
|
|
130
|
-
effort,
|
|
131
|
-
},
|
|
132
|
-
"CLI settings",
|
|
133
|
-
).provider;
|
|
134
|
-
providerChanged = provider !== settingsProvider;
|
|
135
|
-
explicitProvider = true;
|
|
136
|
-
i++;
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (flag === "--model") {
|
|
141
|
-
modelId = requireValue(argv, i, flag);
|
|
142
|
-
explicitModel = true;
|
|
143
|
-
i++;
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (flag === "--effort") {
|
|
148
|
-
effort = parseSettings(
|
|
149
|
-
{
|
|
150
|
-
provider,
|
|
151
|
-
model: modelId,
|
|
152
|
-
effort: requireValue(argv, i, flag),
|
|
153
|
-
},
|
|
154
|
-
"CLI settings",
|
|
155
|
-
).effort;
|
|
156
|
-
i++;
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// headless api
|
|
161
|
-
if (flag === "--prompt" || flag === "-p") {
|
|
162
|
-
prompt = requireValue(argv, i, flag);
|
|
163
|
-
i++;
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (flag === "--login" || flag === "-l") {
|
|
168
|
-
await loginOAuth(requireValue(argv, i, flag));
|
|
169
|
-
// Rebuild available providers after login
|
|
170
|
-
const refreshedProviders = await getAvailableProviders();
|
|
171
|
-
availableProviders.splice(
|
|
172
|
-
0,
|
|
173
|
-
availableProviders.length,
|
|
174
|
-
...new Set([
|
|
175
|
-
...refreshedProviders,
|
|
176
|
-
...customProviders.map((cp) => cp.provider),
|
|
177
|
-
]),
|
|
178
|
-
);
|
|
179
|
-
i++;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
let cliSettings = parseSettings(
|
|
184
|
-
{
|
|
185
|
-
provider,
|
|
186
|
-
model: modelId,
|
|
187
|
-
effort,
|
|
188
|
-
},
|
|
189
|
-
"CLI settings",
|
|
190
|
-
);
|
|
191
|
-
const providerAvailable = availableProviders.includes(cliSettings.provider);
|
|
192
|
-
|
|
193
|
-
if (explicitProvider) {
|
|
194
|
-
if (!providerAvailable && !isOAuthProvider(cliSettings.provider)) {
|
|
195
|
-
throw new Error(
|
|
196
|
-
`Provider "${cliSettings.provider}" is not logged in and no API key was found`,
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
} else if (!providerAvailable && availableProviders.length) {
|
|
200
|
-
const fallbackProvider = availableProviders[0];
|
|
201
|
-
provider = fallbackProvider;
|
|
202
|
-
providerChanged = provider !== settingsProvider;
|
|
203
|
-
if (!explicitModel) {
|
|
204
|
-
modelId = getFirstModelConfig(fallbackProvider, customProviders).id;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
cliSettings = parseSettings(
|
|
208
|
-
{
|
|
209
|
-
provider,
|
|
210
|
-
model: modelId,
|
|
211
|
-
effort,
|
|
212
|
-
},
|
|
213
|
-
"CLI settings",
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const selectedModel = findModelConfig(
|
|
218
|
-
cliSettings.model,
|
|
219
|
-
cliSettings.provider,
|
|
220
|
-
customProviders,
|
|
221
|
-
);
|
|
222
|
-
const model =
|
|
223
|
-
selectedModel ??
|
|
224
|
-
getFallbackModel(
|
|
225
|
-
cliSettings.provider,
|
|
226
|
-
explicitModel,
|
|
227
|
-
providerChanged,
|
|
228
|
-
customProviders,
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
const options = parseCliOptions({
|
|
232
|
-
provider: cliSettings.provider,
|
|
233
|
-
model,
|
|
234
|
-
effort: cliSettings.effort,
|
|
235
|
-
prompt,
|
|
236
|
-
customProviders,
|
|
237
|
-
theme: settingsTheme,
|
|
238
|
-
});
|
|
239
|
-
const nextSettings = parseSettings(
|
|
240
|
-
{
|
|
241
|
-
provider: options.provider,
|
|
242
|
-
model: options.model.id,
|
|
243
|
-
effort: options.effort,
|
|
244
|
-
customProviders,
|
|
245
|
-
theme: options.theme,
|
|
246
|
-
},
|
|
247
|
-
"settings",
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
await saveSettings(nextSettings);
|
|
251
|
-
return options;
|
|
252
|
-
}
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { afterAll, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { runBashTool } from "./tool-bash.ts";
|
|
6
|
-
import { runEditTool } from "./tool-edit.ts";
|
|
7
|
-
import { runReadTool } from "./tool-read.ts";
|
|
8
|
-
import type { ToolRunnerEvent } from "./types.ts";
|
|
9
|
-
|
|
10
|
-
const originalDataDir = Bun.env.MINI_CODER_DATA_DIR;
|
|
11
|
-
const testHome = await mkdtemp(join(tmpdir(), "mini-coder-errors-"));
|
|
12
|
-
const dataDir = join(testHome, "data");
|
|
13
|
-
Bun.env.MINI_CODER_DATA_DIR = dataDir;
|
|
14
|
-
|
|
15
|
-
const sessionsDir = join(dataDir, "sessions");
|
|
16
|
-
const { handleArgv } = await import("./args.ts");
|
|
17
|
-
const { getAvailableProviders } = await import("./oauth.ts");
|
|
18
|
-
const { getSession, listSessionsForCwd } = await import("./session.ts");
|
|
19
|
-
|
|
20
|
-
type ResultEvent = Extract<ToolRunnerEvent, { type: "result" }>;
|
|
21
|
-
|
|
22
|
-
async function collectToolEvents(
|
|
23
|
-
events: AsyncGenerator<ToolRunnerEvent>,
|
|
24
|
-
): Promise<ToolRunnerEvent[]> {
|
|
25
|
-
const collected: ToolRunnerEvent[] = [];
|
|
26
|
-
|
|
27
|
-
for await (const event of events) {
|
|
28
|
-
collected.push(event);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return collected;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function resultEvent(events: ToolRunnerEvent[]): ResultEvent {
|
|
35
|
-
const result = events.findLast(
|
|
36
|
-
(event): event is ResultEvent => event.type === "result",
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
if (!result) {
|
|
40
|
-
throw new Error("Tool did not yield a result");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return result;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function toolResult(
|
|
47
|
-
events: AsyncGenerator<ToolRunnerEvent>,
|
|
48
|
-
): Promise<ResultEvent> {
|
|
49
|
-
return resultEvent(await collectToolEvents(events));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
beforeEach(async () => {
|
|
53
|
-
await rm(dataDir, { recursive: true, force: true });
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
afterAll(async () => {
|
|
57
|
-
if (originalDataDir === undefined) {
|
|
58
|
-
delete Bun.env.MINI_CODER_DATA_DIR;
|
|
59
|
-
} else {
|
|
60
|
-
Bun.env.MINI_CODER_DATA_DIR = originalDataDir;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
await rm(testHome, { recursive: true, force: true });
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe("tool runner error handling", () => {
|
|
67
|
-
test("read and edit report missing files as tool results", async () => {
|
|
68
|
-
const missingPath = join(testHome, "missing.txt");
|
|
69
|
-
|
|
70
|
-
const readResult = await toolResult(runReadTool({ path: missingPath }));
|
|
71
|
-
expect(readResult.text).toBe(`File not found: ${missingPath}`);
|
|
72
|
-
|
|
73
|
-
const editResult = await toolResult(
|
|
74
|
-
runEditTool({ path: missingPath, oldText: "old", newText: "new" }),
|
|
75
|
-
);
|
|
76
|
-
expect(editResult.text).toBe(`File not found: ${missingPath}`);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("edit refuses unsafe writes and preserves the file", async () => {
|
|
80
|
-
const path = join(testHome, "edit.txt");
|
|
81
|
-
await writeFile(path, "one\none\n");
|
|
82
|
-
|
|
83
|
-
const ambiguous = await toolResult(
|
|
84
|
-
runEditTool({ path, oldText: "one", newText: "two" }),
|
|
85
|
-
);
|
|
86
|
-
expect(ambiguous.text).toContain("Multiple matches found");
|
|
87
|
-
expect(await Bun.file(path).text()).toBe("one\none\n");
|
|
88
|
-
|
|
89
|
-
const missing = await toolResult(
|
|
90
|
-
runEditTool({ path, oldText: "missing", newText: "two" }),
|
|
91
|
-
);
|
|
92
|
-
expect(missing.text).toBe(`Old text not found in: ${path}`);
|
|
93
|
-
expect(await Bun.file(path).text()).toBe("one\none\n");
|
|
94
|
-
|
|
95
|
-
const abortController = new AbortController();
|
|
96
|
-
abortController.abort();
|
|
97
|
-
await writeFile(path, "hello");
|
|
98
|
-
|
|
99
|
-
const aborted = await toolResult(
|
|
100
|
-
runEditTool(
|
|
101
|
-
{ path, oldText: "hello", newText: "bye" },
|
|
102
|
-
abortController.signal,
|
|
103
|
-
),
|
|
104
|
-
);
|
|
105
|
-
expect(aborted.text).toBe("Aborted before write.");
|
|
106
|
-
expect(await Bun.file(path).text()).toBe("hello");
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("bash reports non-zero exits instead of throwing", async () => {
|
|
110
|
-
const result = await toolResult(
|
|
111
|
-
runBashTool({ command: 'printf "bad"; exit 7' }),
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
expect(result.text).toBe("bad\n\nExit code: 7");
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
describe("startup file error handling", () => {
|
|
119
|
-
test("settings JSON parse failures are labelled", async () => {
|
|
120
|
-
await mkdir(dataDir, { recursive: true });
|
|
121
|
-
await writeFile(join(dataDir, "settings.json"), "{");
|
|
122
|
-
|
|
123
|
-
await expect(handleArgv([])).rejects.toThrow("Invalid settings JSON:");
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("auth JSON parse failures are labelled", async () => {
|
|
127
|
-
await mkdir(dataDir, { recursive: true });
|
|
128
|
-
await writeFile(join(dataDir, "auth.json"), "{");
|
|
129
|
-
|
|
130
|
-
await expect(getAvailableProviders()).rejects.toThrow("Invalid auth JSON:");
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
describe("session file error handling", () => {
|
|
135
|
-
test("listSessionsForCwd ignores missing dirs and bad session files", async () => {
|
|
136
|
-
expect(await listSessionsForCwd()).toEqual([]);
|
|
137
|
-
|
|
138
|
-
await mkdir(sessionsDir, { recursive: true });
|
|
139
|
-
await writeFile(join(sessionsDir, "broken.json"), "{");
|
|
140
|
-
await writeFile(
|
|
141
|
-
join(sessionsDir, "elsewhere.json"),
|
|
142
|
-
JSON.stringify({ id: "elsewhere", cwd: "/elsewhere", messages: [] }),
|
|
143
|
-
);
|
|
144
|
-
await writeFile(
|
|
145
|
-
join(sessionsDir, "ok.json"),
|
|
146
|
-
JSON.stringify({
|
|
147
|
-
id: "ok",
|
|
148
|
-
cwd: process.cwd(),
|
|
149
|
-
messages: [{ role: "user", content: "hi", timestamp: 2 }],
|
|
150
|
-
}),
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
const sessions = await listSessionsForCwd();
|
|
154
|
-
expect(sessions.map((session) => session.id)).toEqual(["ok"]);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("getSession returns undefined for bad session files", async () => {
|
|
158
|
-
await mkdir(sessionsDir, { recursive: true });
|
|
159
|
-
await writeFile(join(sessionsDir, "broken.json"), "{");
|
|
160
|
-
|
|
161
|
-
expect(await getSession("broken")).toBeUndefined();
|
|
162
|
-
});
|
|
163
|
-
});
|
package/src/git.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import simpleGit from "simple-git";
|
|
2
|
-
|
|
3
|
-
const git = simpleGit();
|
|
4
|
-
|
|
5
|
-
export async function getGitStatus() {
|
|
6
|
-
try {
|
|
7
|
-
const status = await git.status();
|
|
8
|
-
return status;
|
|
9
|
-
} catch (_) {
|
|
10
|
-
return { nogit: "No git repo in this folder." };
|
|
11
|
-
} // No git
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export async function getBranchLabel() {
|
|
15
|
-
try {
|
|
16
|
-
const status = await git.status();
|
|
17
|
-
const isClean = status.isClean();
|
|
18
|
-
|
|
19
|
-
return `${status.current}${isClean ? "" : "*"}`;
|
|
20
|
-
} catch (_) {
|
|
21
|
-
return "No git";
|
|
22
|
-
} // No git
|
|
23
|
-
}
|
package/src/headless.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type { Message } from "@earendil-works/pi-ai";
|
|
2
|
-
import { streamAgent } from "./agent";
|
|
3
|
-
import {
|
|
4
|
-
buildSystemPrompt,
|
|
5
|
-
injectEnvReminder,
|
|
6
|
-
insertToolUsageReminder,
|
|
7
|
-
MAIN_PROMPT,
|
|
8
|
-
} from "./prompt";
|
|
9
|
-
import { bash, runBashTool } from "./tool-bash";
|
|
10
|
-
import { edit, runEditTool } from "./tool-edit";
|
|
11
|
-
import { read, runReadTool } from "./tool-read";
|
|
12
|
-
import type { AgentContex, CliOptions, ToolAndRunner } from "./types";
|
|
13
|
-
|
|
14
|
-
export async function streamHeadless(
|
|
15
|
-
options: CliOptions,
|
|
16
|
-
leave: (s?: string) => void,
|
|
17
|
-
) {
|
|
18
|
-
const tools: ToolAndRunner[] = [
|
|
19
|
-
{ tool: bash, runner: runBashTool },
|
|
20
|
-
{ tool: edit, runner: runEditTool },
|
|
21
|
-
{ tool: read, runner: runReadTool },
|
|
22
|
-
];
|
|
23
|
-
const envReminder = await injectEnvReminder();
|
|
24
|
-
const messages: Message[] = [
|
|
25
|
-
{
|
|
26
|
-
role: "user",
|
|
27
|
-
content: `${envReminder}\n\n${options.prompt || ""}`,
|
|
28
|
-
timestamp: Date.now(),
|
|
29
|
-
},
|
|
30
|
-
];
|
|
31
|
-
console.log(JSON.stringify(messages[0]));
|
|
32
|
-
|
|
33
|
-
const systemPrompt = await buildSystemPrompt(MAIN_PROMPT);
|
|
34
|
-
const ctx: AgentContex = {
|
|
35
|
-
systemPrompt,
|
|
36
|
-
tools,
|
|
37
|
-
messages,
|
|
38
|
-
options,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const agent = streamAgent(ctx);
|
|
42
|
-
|
|
43
|
-
for await (const ev of agent) {
|
|
44
|
-
switch (ev.type) {
|
|
45
|
-
case "message_end":
|
|
46
|
-
console.log(JSON.stringify(ev.message));
|
|
47
|
-
break;
|
|
48
|
-
|
|
49
|
-
case "tool_message_end": {
|
|
50
|
-
const withReminder = insertToolUsageReminder(messages, ev.message);
|
|
51
|
-
const idx = messages.findIndex(
|
|
52
|
-
(m) =>
|
|
53
|
-
m.role === "toolResult" && m.toolCallId === withReminder.toolCallId,
|
|
54
|
-
);
|
|
55
|
-
if (idx >= 0) {
|
|
56
|
-
messages[idx] = withReminder;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
console.log(JSON.stringify(withReminder));
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
leave();
|
|
66
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { basename } from "node:path";
|
|
2
|
-
import { handleArgv } from "./args.ts";
|
|
3
|
-
import { getBranchLabel } from "./git.ts";
|
|
4
|
-
import { streamHeadless } from "./headless.ts";
|
|
5
|
-
import { initTUI } from "./tui.ts";
|
|
6
|
-
import type { TUIState } from "./types.ts";
|
|
7
|
-
import { updateMiniCoder } from "./update.ts";
|
|
8
|
-
|
|
9
|
-
export async function main(): Promise<void> {
|
|
10
|
-
const argv = process.argv.slice(2);
|
|
11
|
-
|
|
12
|
-
if (argv.includes("--update")) {
|
|
13
|
-
await updateMiniCoder();
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const cwd = basename(process.cwd());
|
|
18
|
-
const options = await handleArgv(argv);
|
|
19
|
-
|
|
20
|
-
function leave(msg?: string) {
|
|
21
|
-
if (msg) console.log(msg);
|
|
22
|
-
process.exit(0);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (options.prompt) {
|
|
26
|
-
await streamHeadless(options, leave);
|
|
27
|
-
process.exit(0);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const state: TUIState = {
|
|
31
|
-
options,
|
|
32
|
-
prompt: "",
|
|
33
|
-
messages: [],
|
|
34
|
-
tuiMessages: [],
|
|
35
|
-
streaming: false,
|
|
36
|
-
stickToBottom: true,
|
|
37
|
-
scrollOffset: 0,
|
|
38
|
-
cwd,
|
|
39
|
-
gitBranch: await getBranchLabel(),
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
initTUI(state, leave);
|
|
43
|
-
}
|