min-agent 0.1.9 → 0.2.1
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/dist/agent.js +6 -423
- package/dist/cli.js +15 -5
- package/dist/compaction.js +91 -29
- package/dist/confirm.js +24 -5
- package/dist/skills.js +29 -19
- package/dist/tui/App.js +15 -0
- package/dist/tui/ConfirmBar.js +13 -0
- package/dist/tui/InputBar.js +83 -0
- package/dist/tui/MessageList.js +33 -0
- package/dist/tui/Spinner.js +6 -0
- package/dist/tui/StatusBar.js +16 -0
- package/dist/tui/index.js +94 -0
- package/dist/tui/types.js +1 -0
- package/dist/tui-chat.js +384 -0
- package/package.json +5 -1
package/dist/agent.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readFileSync, existsSync } from "fs";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { resolveModel } from "./provider.js";
|
|
5
5
|
import { createChatTools, createCodeTools } from "./tools/index.js";
|
|
6
|
-
import { initMcp, shutdownMcp, getMcpTools
|
|
6
|
+
import { initMcp, shutdownMcp, getMcpTools } from "./mcp.js";
|
|
7
7
|
import { discoverSkills, getSkillsTool, getSkillsSystemPrompt, getSkills } from "./skills.js";
|
|
8
8
|
import { loadInstructions } from "./instructions.js";
|
|
9
9
|
import { getMemorySystemPrompt, getMemoryTools } from "./memory.js";
|
|
@@ -11,12 +11,8 @@ import { needsCompaction, compactMessages, estimateTokens, TokenTracker } from "
|
|
|
11
11
|
import { loadPluginTools } from "./plugins.js";
|
|
12
12
|
import { MarkdownRenderer } from "./markdown.js";
|
|
13
13
|
import { DoomLoopDetector } from "./doom-loop.js";
|
|
14
|
-
import { scanProject, buildCodeSystemPrompt } from "./code-mode.js";
|
|
15
14
|
import { ThinkingBodySplitter, stripThinkingFromAssistantText } from "./assistant-stream.js";
|
|
16
15
|
import { printHeader, printDivider, printToolCall, printToolResult, printDone } from "./output.js";
|
|
17
|
-
import { killActiveProcesses } from "./tools/bash.js";
|
|
18
|
-
import { setConfirmReadline } from "./confirm.js";
|
|
19
|
-
import readline from "readline";
|
|
20
16
|
const MAX_STEPS = 30;
|
|
21
17
|
function dimStyle() {
|
|
22
18
|
if ("NO_COLOR" in process.env)
|
|
@@ -117,245 +113,6 @@ export async function runAgent(message, modelId, imagePaths) {
|
|
|
117
113
|
await runOnce(messages, instructions, modelId, undefined, undefined, tracker);
|
|
118
114
|
await shutdownMcp();
|
|
119
115
|
}
|
|
120
|
-
/** Interactive multi-turn chat session */
|
|
121
|
-
export async function runChat(modelId, resumeSessionId) {
|
|
122
|
-
printHeader(modelId);
|
|
123
|
-
printDivider();
|
|
124
|
-
printInitLoading();
|
|
125
|
-
await initMcp();
|
|
126
|
-
discoverSkills();
|
|
127
|
-
const instructions = await loadInstructions();
|
|
128
|
-
printInitReady();
|
|
129
|
-
let messages = [];
|
|
130
|
-
let sessionId = resumeSessionId;
|
|
131
|
-
const tracker = new TokenTracker();
|
|
132
|
-
// Resume existing session
|
|
133
|
-
if (resumeSessionId) {
|
|
134
|
-
const { loadSession } = await import("./sessions.js");
|
|
135
|
-
const session = loadSession(resumeSessionId);
|
|
136
|
-
if (session) {
|
|
137
|
-
messages = session.messages;
|
|
138
|
-
sessionId = resumeSessionId;
|
|
139
|
-
console.log(`\x1b[90m Resumed session: ${session.meta.title} (${messages.length} messages)\x1b[0m`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
const rl = readline.createInterface({
|
|
143
|
-
input: process.stdin,
|
|
144
|
-
output: process.stdout,
|
|
145
|
-
prompt: "\x1b[36m> \x1b[0m",
|
|
146
|
-
});
|
|
147
|
-
setConfirmReadline(rl);
|
|
148
|
-
// Handle Ctrl+C: abort current generation, don't exit
|
|
149
|
-
let abortController = null;
|
|
150
|
-
process.on("SIGINT", () => {
|
|
151
|
-
if (abortController) {
|
|
152
|
-
killActiveProcesses();
|
|
153
|
-
abortController.abort();
|
|
154
|
-
abortController = null;
|
|
155
|
-
console.log("\n\x1b[90m(cancelled)\x1b[0m\n");
|
|
156
|
-
rl.prompt();
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
// No active generation, exit
|
|
160
|
-
console.log();
|
|
161
|
-
rl.close();
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
// Listen for ESC key to cancel running agent task via keypress events
|
|
165
|
-
if (process.stdin.isTTY) {
|
|
166
|
-
readline.emitKeypressEvents(process.stdin, rl);
|
|
167
|
-
process.stdin.on("keypress", (_ch, key) => {
|
|
168
|
-
if (key && key.name === "escape" && abortController) {
|
|
169
|
-
killActiveProcesses();
|
|
170
|
-
abortController.abort();
|
|
171
|
-
abortController = null;
|
|
172
|
-
console.log("\n\x1b[90m(esc cancelled)\x1b[0m\n");
|
|
173
|
-
rl.prompt();
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
console.log("\x1b[90m输入消息开始对话,输入 /help 查看命令,Esc 取消运行,/exit 退出\x1b[0m\n");
|
|
178
|
-
rl.prompt();
|
|
179
|
-
// Multi-line paste detection: collect rapid successive lines
|
|
180
|
-
let pasteBuffer = [];
|
|
181
|
-
let pasteTimer = null;
|
|
182
|
-
const PASTE_DEBOUNCE_MS = 50;
|
|
183
|
-
const processInput = async (text) => {
|
|
184
|
-
const input = text.trim();
|
|
185
|
-
if (!input) {
|
|
186
|
-
rl.prompt();
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
if (input.startsWith("/")) {
|
|
190
|
-
const handled = await handleSlashCommand(input, messages, instructions, modelId, rl, tracker);
|
|
191
|
-
if (handled === "exit") {
|
|
192
|
-
rl.close();
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
if (handled === "paste") {
|
|
196
|
-
console.log();
|
|
197
|
-
abortController = new AbortController();
|
|
198
|
-
await runOnce(messages, instructions, modelId, abortController.signal, undefined, tracker);
|
|
199
|
-
abortController = null;
|
|
200
|
-
console.log();
|
|
201
|
-
}
|
|
202
|
-
rl.prompt();
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
// Show paste feedback for large inputs
|
|
206
|
-
const { processPastedInput, printPasteFeedback } = await import("./paste-handler.js");
|
|
207
|
-
const pasteResult = processPastedInput(input);
|
|
208
|
-
printPasteFeedback(pasteResult);
|
|
209
|
-
console.log();
|
|
210
|
-
messages.push({ role: "user", content: pasteResult.fullText });
|
|
211
|
-
abortController = new AbortController();
|
|
212
|
-
await runOnce(messages, instructions, modelId, abortController.signal, undefined, tracker);
|
|
213
|
-
abortController = null;
|
|
214
|
-
console.log();
|
|
215
|
-
rl.prompt();
|
|
216
|
-
};
|
|
217
|
-
rl.on("line", (line) => {
|
|
218
|
-
pasteBuffer.push(line);
|
|
219
|
-
if (pasteTimer)
|
|
220
|
-
clearTimeout(pasteTimer);
|
|
221
|
-
pasteTimer = setTimeout(() => {
|
|
222
|
-
const combined = pasteBuffer.join("\n");
|
|
223
|
-
pasteBuffer = [];
|
|
224
|
-
pasteTimer = null;
|
|
225
|
-
processInput(combined);
|
|
226
|
-
}, PASTE_DEBOUNCE_MS);
|
|
227
|
-
});
|
|
228
|
-
// Wait for close
|
|
229
|
-
await new Promise((resolve) => rl.on("close", resolve));
|
|
230
|
-
// Auto-save session on exit
|
|
231
|
-
if (messages.length > 0) {
|
|
232
|
-
const { saveSession } = await import("./sessions.js");
|
|
233
|
-
sessionId = saveSession(messages, sessionId);
|
|
234
|
-
console.log(`\x1b[90m Session saved: ${sessionId}\x1b[0m`);
|
|
235
|
-
}
|
|
236
|
-
printDivider();
|
|
237
|
-
console.log("\x1b[90mBye!\x1b[0m");
|
|
238
|
-
await shutdownMcp();
|
|
239
|
-
}
|
|
240
|
-
/** AI Coding mode: project-aware interactive session */
|
|
241
|
-
export async function runCode(modelId, resumeSessionId) {
|
|
242
|
-
printHeader(modelId);
|
|
243
|
-
console.log("\x1b[90m Mode: code\x1b[0m");
|
|
244
|
-
printDivider();
|
|
245
|
-
// Scan project
|
|
246
|
-
console.log("\x1b[90m⟳ Scanning project...\x1b[0m");
|
|
247
|
-
const project = scanProject();
|
|
248
|
-
console.log(`\x1b[90m ${project.languages.join(", ") || "Unknown language"}${project.framework ? ` (${project.framework})` : ""} | ${project.isGitRepo ? `git:${project.branch}` : "no git"}\x1b[0m`);
|
|
249
|
-
await initMcp();
|
|
250
|
-
discoverSkills();
|
|
251
|
-
const instructions = await loadInstructions();
|
|
252
|
-
const codePrompt = buildCodeSystemPrompt(project, instructions);
|
|
253
|
-
console.log("\x1b[90m✓ Ready\x1b[0m");
|
|
254
|
-
let messages = [];
|
|
255
|
-
let sessionId = resumeSessionId;
|
|
256
|
-
const tracker = new TokenTracker();
|
|
257
|
-
if (resumeSessionId) {
|
|
258
|
-
const { loadSession } = await import("./sessions.js");
|
|
259
|
-
const session = loadSession(resumeSessionId);
|
|
260
|
-
if (session) {
|
|
261
|
-
messages = session.messages;
|
|
262
|
-
sessionId = resumeSessionId;
|
|
263
|
-
console.log(`\x1b[90m Resumed: ${session.meta.title} (${messages.length} msgs)\x1b[0m`);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
const rl = readline.createInterface({
|
|
267
|
-
input: process.stdin,
|
|
268
|
-
output: process.stdout,
|
|
269
|
-
prompt: "\x1b[32m❯ \x1b[0m",
|
|
270
|
-
});
|
|
271
|
-
setConfirmReadline(rl);
|
|
272
|
-
let abortController = null;
|
|
273
|
-
process.on("SIGINT", () => {
|
|
274
|
-
if (abortController) {
|
|
275
|
-
killActiveProcesses();
|
|
276
|
-
abortController.abort();
|
|
277
|
-
abortController = null;
|
|
278
|
-
console.log("\n\x1b[90m(cancelled)\x1b[0m\n");
|
|
279
|
-
rl.prompt();
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
console.log();
|
|
283
|
-
rl.close();
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
// Listen for ESC key to cancel running agent task via keypress events
|
|
287
|
-
if (process.stdin.isTTY) {
|
|
288
|
-
readline.emitKeypressEvents(process.stdin, rl);
|
|
289
|
-
process.stdin.on("keypress", (_ch, key) => {
|
|
290
|
-
if (key && key.name === "escape" && abortController) {
|
|
291
|
-
killActiveProcesses();
|
|
292
|
-
abortController.abort();
|
|
293
|
-
abortController = null;
|
|
294
|
-
console.log("\n\x1b[90m(esc cancelled)\x1b[0m\n");
|
|
295
|
-
rl.prompt();
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
console.log("\x1b[90m输入任务开始编码,/help 查看命令,Esc 取消运行,Ctrl+C 中断\x1b[0m\n");
|
|
300
|
-
rl.prompt();
|
|
301
|
-
let pasteBuffer = [];
|
|
302
|
-
let pasteTimer = null;
|
|
303
|
-
const PASTE_DEBOUNCE_MS = 50;
|
|
304
|
-
const processCodeInput = async (text) => {
|
|
305
|
-
const input = text.trim();
|
|
306
|
-
if (!input) {
|
|
307
|
-
rl.prompt();
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
if (input.startsWith("/")) {
|
|
311
|
-
const handled = await handleSlashCommand(input, messages, [codePrompt], modelId, rl, tracker);
|
|
312
|
-
if (handled === "exit") {
|
|
313
|
-
rl.close();
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
if (handled === "paste") {
|
|
317
|
-
console.log();
|
|
318
|
-
abortController = new AbortController();
|
|
319
|
-
await runOnceWithSystem(messages, codePrompt, modelId, abortController.signal, undefined, tracker);
|
|
320
|
-
abortController = null;
|
|
321
|
-
console.log();
|
|
322
|
-
}
|
|
323
|
-
rl.prompt();
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
const { processPastedInput, printPasteFeedback } = await import("./paste-handler.js");
|
|
327
|
-
const pasteResult = processPastedInput(input);
|
|
328
|
-
printPasteFeedback(pasteResult);
|
|
329
|
-
console.log();
|
|
330
|
-
messages.push({ role: "user", content: pasteResult.fullText });
|
|
331
|
-
abortController = new AbortController();
|
|
332
|
-
await runOnceWithSystem(messages, codePrompt, modelId, abortController.signal, undefined, tracker);
|
|
333
|
-
abortController = null;
|
|
334
|
-
console.log();
|
|
335
|
-
rl.prompt();
|
|
336
|
-
};
|
|
337
|
-
rl.on("line", (line) => {
|
|
338
|
-
pasteBuffer.push(line);
|
|
339
|
-
if (pasteTimer)
|
|
340
|
-
clearTimeout(pasteTimer);
|
|
341
|
-
pasteTimer = setTimeout(() => {
|
|
342
|
-
const combined = pasteBuffer.join("\n");
|
|
343
|
-
pasteBuffer = [];
|
|
344
|
-
pasteTimer = null;
|
|
345
|
-
processCodeInput(combined);
|
|
346
|
-
}, PASTE_DEBOUNCE_MS);
|
|
347
|
-
});
|
|
348
|
-
await new Promise((resolve) => rl.on("close", resolve));
|
|
349
|
-
if (messages.length > 0) {
|
|
350
|
-
const { saveSession } = await import("./sessions.js");
|
|
351
|
-
sessionId = saveSession(messages, sessionId);
|
|
352
|
-
console.log(`\x1b[90m Session saved: ${sessionId}\x1b[0m`);
|
|
353
|
-
}
|
|
354
|
-
rl.close();
|
|
355
|
-
printDivider();
|
|
356
|
-
console.log("\x1b[90mBye!\x1b[0m");
|
|
357
|
-
await shutdownMcp();
|
|
358
|
-
}
|
|
359
116
|
/** runOnce variant that accepts a pre-built system prompt (for code mode) */
|
|
360
117
|
export async function runOnceWithSystem(messages, systemPrompt, modelId, abortSignal, callbacks, tracker) {
|
|
361
118
|
const model = resolveModel(modelId);
|
|
@@ -367,7 +124,8 @@ export async function runOnceWithSystem(messages, systemPrompt, modelId, abortSi
|
|
|
367
124
|
messages.length = 0;
|
|
368
125
|
messages.push(...result.messages);
|
|
369
126
|
if (result.shouldContinue) {
|
|
370
|
-
|
|
127
|
+
const continueText = result.replayText || "Continue with your task.";
|
|
128
|
+
messages.push({ role: "user", content: continueText });
|
|
371
129
|
}
|
|
372
130
|
if (tracker)
|
|
373
131
|
tracker.resetContext();
|
|
@@ -558,180 +316,6 @@ export async function runOnceWithSystem(messages, systemPrompt, modelId, abortSi
|
|
|
558
316
|
}
|
|
559
317
|
}
|
|
560
318
|
}
|
|
561
|
-
async function handleSlashCommand(input, messages, instructions, modelId, rl, tracker) {
|
|
562
|
-
const [cmd, ...rest] = input.slice(1).split(/\s+/);
|
|
563
|
-
const arg = rest.join(" ");
|
|
564
|
-
switch (cmd) {
|
|
565
|
-
case "exit":
|
|
566
|
-
case "quit":
|
|
567
|
-
case "q":
|
|
568
|
-
console.log("\x1b[90m⟳ Exiting...\x1b[0m");
|
|
569
|
-
return "exit";
|
|
570
|
-
case "clear":
|
|
571
|
-
messages.length = 0;
|
|
572
|
-
console.log("\x1b[90m ✓ Conversation cleared\x1b[0m");
|
|
573
|
-
return "handled";
|
|
574
|
-
case "compact":
|
|
575
|
-
if (messages.length < 4) {
|
|
576
|
-
console.log("\x1b[90m Not enough messages to compact\x1b[0m");
|
|
577
|
-
}
|
|
578
|
-
else {
|
|
579
|
-
console.log("\x1b[90m ⟳ Compacting...\x1b[0m");
|
|
580
|
-
const model = resolveModel(modelId);
|
|
581
|
-
const result = await compactMessages(messages, model, { keepRecentTurns: 2, autoContinue: false });
|
|
582
|
-
messages.length = 0;
|
|
583
|
-
messages.push(...result.messages);
|
|
584
|
-
if (tracker)
|
|
585
|
-
tracker.resetContext();
|
|
586
|
-
console.log(`\x1b[90m ✓ Compacted (${estimateTokens(messages)} tokens estimated)\x1b[0m`);
|
|
587
|
-
}
|
|
588
|
-
return "handled";
|
|
589
|
-
case "model":
|
|
590
|
-
if (arg) {
|
|
591
|
-
const config = await import("./config.js");
|
|
592
|
-
const cfg = config.loadConfig();
|
|
593
|
-
if (cfg.provider) {
|
|
594
|
-
cfg.provider.defaultModel = arg;
|
|
595
|
-
config.saveConfig(cfg);
|
|
596
|
-
console.log(`\x1b[90m ✓ Default model set to: ${arg}\x1b[0m`);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
else {
|
|
600
|
-
const config = await import("./config.js");
|
|
601
|
-
const cfg = config.loadConfig();
|
|
602
|
-
console.log(`\x1b[90m Current model: ${cfg.provider?.defaultModel ?? "not set"}\x1b[0m`);
|
|
603
|
-
}
|
|
604
|
-
return "handled";
|
|
605
|
-
case "models": {
|
|
606
|
-
const config = await import("./config.js");
|
|
607
|
-
const cfg = config.loadConfig();
|
|
608
|
-
if (!cfg.provider?.baseURL || !cfg.provider?.apiKey) {
|
|
609
|
-
console.log("\x1b[90m Not configured. Run: min-agent setup\x1b[0m");
|
|
610
|
-
return "handled";
|
|
611
|
-
}
|
|
612
|
-
console.log("\x1b[90m Fetching models...\x1b[0m");
|
|
613
|
-
const models = await config.fetchModels(cfg.provider.baseURL, cfg.provider.apiKey);
|
|
614
|
-
if (models.length === 0) {
|
|
615
|
-
console.log("\x1b[90m No models found or unable to fetch model list\x1b[0m");
|
|
616
|
-
}
|
|
617
|
-
else {
|
|
618
|
-
console.log(`\x1b[90m Available models (${models.length}):\x1b[0m`);
|
|
619
|
-
for (const m of models) {
|
|
620
|
-
const marker = m === cfg.provider.defaultModel ? " ← default" : "";
|
|
621
|
-
console.log(`\x1b[90m - ${m}${marker}\x1b[0m`);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
return "handled";
|
|
625
|
-
}
|
|
626
|
-
case "memory":
|
|
627
|
-
if (arg) {
|
|
628
|
-
const { addMemory } = await import("./memory.js");
|
|
629
|
-
addMemory(arg);
|
|
630
|
-
console.log(`\x1b[90m ✓ Memory saved: "${arg}"\x1b[0m`);
|
|
631
|
-
}
|
|
632
|
-
else {
|
|
633
|
-
const { loadMemories } = await import("./memory.js");
|
|
634
|
-
const memories = loadMemories();
|
|
635
|
-
if (memories.length === 0) {
|
|
636
|
-
console.log("\x1b[90m No memories stored\x1b[0m");
|
|
637
|
-
}
|
|
638
|
-
else {
|
|
639
|
-
for (let i = 0; i < memories.length; i++) {
|
|
640
|
-
const m = memories[i];
|
|
641
|
-
const tags = m.tags.length > 0 ? ` [${m.tags.join(", ")}]` : "";
|
|
642
|
-
console.log(`\x1b[90m #${i + 1}: ${m.content}${tags}\x1b[0m`);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
return "handled";
|
|
647
|
-
case "skills": {
|
|
648
|
-
discoverSkills();
|
|
649
|
-
const skills = getSkills();
|
|
650
|
-
if (skills.length === 0) {
|
|
651
|
-
console.log("\x1b[90m No skills available\x1b[0m");
|
|
652
|
-
}
|
|
653
|
-
else {
|
|
654
|
-
for (const skill of skills) {
|
|
655
|
-
console.log(`\x1b[90m ${skill.name} enabled\x1b[0m`);
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
return "handled";
|
|
659
|
-
}
|
|
660
|
-
case "mcp": {
|
|
661
|
-
const config = loadMcpConfig();
|
|
662
|
-
const servers = Object.entries(config.mcpServers);
|
|
663
|
-
if (servers.length === 0) {
|
|
664
|
-
console.log("\x1b[90m No MCP servers configured\x1b[0m");
|
|
665
|
-
}
|
|
666
|
-
else {
|
|
667
|
-
const status = getMcpStatus();
|
|
668
|
-
console.log(`\x1b[90m MCP servers (${servers.length}):\x1b[0m`);
|
|
669
|
-
for (const [name, cfg] of servers) {
|
|
670
|
-
const disabled = cfg.enabled === false;
|
|
671
|
-
const connected = status[name]?.connected ?? false;
|
|
672
|
-
const toolCount = status[name]?.tools.length ?? 0;
|
|
673
|
-
const state = disabled ? "disabled" : connected ? "connected" : "disconnected";
|
|
674
|
-
const toolsText = toolCount > 0 ? `, ${toolCount} tools` : "";
|
|
675
|
-
console.log(`\x1b[90m - ${name}: ${state}${toolsText}\x1b[0m`);
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
return "handled";
|
|
679
|
-
}
|
|
680
|
-
case "tokens": {
|
|
681
|
-
if (tracker && tracker.lastInputTokens > 0) {
|
|
682
|
-
const { getContextWindow } = await import("./context-window.js");
|
|
683
|
-
const ctxWindow = await getContextWindow(modelId);
|
|
684
|
-
const pct = Math.round((tracker.lastInputTokens / ctxWindow) * 100);
|
|
685
|
-
console.log(`\x1b[90m Context: ${tracker.lastInputTokens} / ${ctxWindow} tokens (${pct}%)\x1b[0m`);
|
|
686
|
-
console.log(`\x1b[90m Total: ${tracker.totalInputTokens} in / ${tracker.totalOutputTokens} out\x1b[0m`);
|
|
687
|
-
}
|
|
688
|
-
else {
|
|
689
|
-
console.log(`\x1b[90m Estimated tokens in context: ${estimateTokens(messages)}\x1b[0m`);
|
|
690
|
-
}
|
|
691
|
-
console.log(`\x1b[90m Messages: ${messages.length}\x1b[0m`);
|
|
692
|
-
return "handled";
|
|
693
|
-
}
|
|
694
|
-
case "path":
|
|
695
|
-
case "pwd":
|
|
696
|
-
console.log(`\x1b[90m ${process.cwd()}\x1b[0m`);
|
|
697
|
-
return "handled";
|
|
698
|
-
case "paste": {
|
|
699
|
-
const { getClipboardImage } = await import("./clipboard.js");
|
|
700
|
-
const img = getClipboardImage();
|
|
701
|
-
if (!img) {
|
|
702
|
-
console.log("\x1b[90m No image found in clipboard\x1b[0m");
|
|
703
|
-
return "handled";
|
|
704
|
-
}
|
|
705
|
-
console.log(`\x1b[90m 📎 Clipboard image (${(img.data.length / 1024).toFixed(1)} KB)\x1b[0m`);
|
|
706
|
-
const text = arg || "What's in this image?";
|
|
707
|
-
const content = [
|
|
708
|
-
{ type: "text", text },
|
|
709
|
-
{ type: "image", image: img.data, mimeType: img.mimeType },
|
|
710
|
-
];
|
|
711
|
-
messages.push({ role: "user", content });
|
|
712
|
-
// Return a special signal to trigger runOnce
|
|
713
|
-
return "paste";
|
|
714
|
-
}
|
|
715
|
-
case "help":
|
|
716
|
-
console.log(`\x1b[90m Slash commands:
|
|
717
|
-
/clear Clear conversation history
|
|
718
|
-
/compact Force context compaction
|
|
719
|
-
/model [name] Show or change current model
|
|
720
|
-
/models List available models from provider
|
|
721
|
-
/memory [text] List memories or save a new one
|
|
722
|
-
/skills List discovered skills
|
|
723
|
-
/mcp List MCP servers and connection status
|
|
724
|
-
/tokens Show estimated token usage
|
|
725
|
-
/path Show current working directory
|
|
726
|
-
/paste [text] Paste clipboard image + optional prompt
|
|
727
|
-
/help Show this help
|
|
728
|
-
/exit Exit the chat\x1b[0m`);
|
|
729
|
-
return "handled";
|
|
730
|
-
default:
|
|
731
|
-
console.log(`\x1b[90m Unknown command: /${cmd}. Type /help for available commands.\x1b[0m`);
|
|
732
|
-
return "handled";
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
319
|
export async function runOnce(messages, instructions, modelId, abortSignal, callbacks, tracker) {
|
|
736
320
|
const model = resolveModel(modelId);
|
|
737
321
|
const api = !!callbacks;
|
|
@@ -749,10 +333,9 @@ export async function runOnce(messages, instructions, modelId, abortSignal, call
|
|
|
749
333
|
messages.push(...result.messages);
|
|
750
334
|
// Auto-continue: inject a message so the agent keeps working
|
|
751
335
|
if (result.shouldContinue) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
});
|
|
336
|
+
const continueText = result.replayText ||
|
|
337
|
+
"Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.";
|
|
338
|
+
messages.push({ role: "user", content: continueText });
|
|
756
339
|
}
|
|
757
340
|
if (tracker)
|
|
758
341
|
tracker.resetContext();
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runAgent
|
|
1
|
+
import { runAgent } from "./agent.js";
|
|
2
2
|
import { loadMcpConfig, saveMcpConfig, checkMcpServer, checkAllMcpServers, formatMcpServerBinding, } from "./mcp.js";
|
|
3
3
|
import { discoverSkills, getSkills } from "./skills.js";
|
|
4
4
|
import { loadMemories, addMemory, deleteMemory, searchMemories } from "./memory.js";
|
|
@@ -21,6 +21,7 @@ function printUsage() {
|
|
|
21
21
|
min-agent - Minimal AI coding agent
|
|
22
22
|
|
|
23
23
|
Usage:
|
|
24
|
+
min-agent init Initialize .min-agent/ in current directory
|
|
24
25
|
min-agent chat <message> Send a message to the agent
|
|
25
26
|
min-agent chat Start interactive multi-turn chat
|
|
26
27
|
min-agent chat --resume <id> Resume a previous session
|
|
@@ -47,6 +48,10 @@ Options:
|
|
|
47
48
|
--image, -i <path> Attach an image (can be used multiple times)
|
|
48
49
|
--yes, -y Auto-approve all confirmations (dangerous commands, file overwrites)
|
|
49
50
|
|
|
51
|
+
Permission modes (set in ~/.min-agent/config.json):
|
|
52
|
+
"permission": "ask" Prompt before dangerous operations (default)
|
|
53
|
+
"permission": "allow-all" Auto-approve everything (same as --yes permanently)
|
|
54
|
+
|
|
50
55
|
Rules (loaded as system instructions):
|
|
51
56
|
Global: ~/.min-agent/rules.md
|
|
52
57
|
Project: ./AGENTS.md or ./RULES.md or ./.min-agent/AGENTS.md
|
|
@@ -103,7 +108,6 @@ function ensureProjectDefaults() {
|
|
|
103
108
|
}
|
|
104
109
|
}
|
|
105
110
|
async function main() {
|
|
106
|
-
ensureProjectDefaults();
|
|
107
111
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
108
112
|
printUsage();
|
|
109
113
|
process.exit(0);
|
|
@@ -166,7 +170,8 @@ async function main() {
|
|
|
166
170
|
}
|
|
167
171
|
const message = chatArgs.join(" ");
|
|
168
172
|
if (!message) {
|
|
169
|
-
await
|
|
173
|
+
const { runTui } = await import("./tui-chat.js");
|
|
174
|
+
await runTui({ modelId: modelOverride, resumeSessionId: resumeId, mode: "chat" });
|
|
170
175
|
}
|
|
171
176
|
else {
|
|
172
177
|
await runAgent(message, modelOverride, images.length > 0 ? images : undefined);
|
|
@@ -188,8 +193,8 @@ async function main() {
|
|
|
188
193
|
resumeId = args[++i];
|
|
189
194
|
}
|
|
190
195
|
}
|
|
191
|
-
const {
|
|
192
|
-
await
|
|
196
|
+
const { runTui } = await import("./tui-chat.js");
|
|
197
|
+
await runTui({ modelId: modelOverride, resumeSessionId: resumeId, mode: "code" });
|
|
193
198
|
break;
|
|
194
199
|
}
|
|
195
200
|
case "serve": {
|
|
@@ -580,6 +585,11 @@ Commands:
|
|
|
580
585
|
}
|
|
581
586
|
break;
|
|
582
587
|
}
|
|
588
|
+
case "init": {
|
|
589
|
+
ensureProjectDefaults();
|
|
590
|
+
console.log(`✓ Initialized .min-agent/ in ${process.cwd()}`);
|
|
591
|
+
break;
|
|
592
|
+
}
|
|
583
593
|
case "rules": {
|
|
584
594
|
const subcommand = args[1];
|
|
585
595
|
if (subcommand === "edit") {
|