min-agent 0.1.4 → 0.1.6
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 +95 -194
- package/bin/min-agent.js +0 -0
- package/dist/agent.js +431 -28
- package/dist/cli.js +177 -27
- package/dist/clipboard.js +106 -0
- package/dist/code-mode.js +166 -0
- package/dist/compaction.js +243 -48
- package/dist/config.js +34 -7
- package/dist/context-window.js +185 -0
- package/dist/doom-loop.js +36 -0
- package/dist/instructions.js +42 -0
- package/dist/mcp.js +82 -28
- package/dist/output.js +15 -2
- package/dist/paste-handler.js +41 -0
- package/dist/serve.js +351 -3
- package/dist/sessions.js +13 -4
- package/dist/skills.js +15 -7
- package/dist/structured-output.js +29 -0
- package/dist/title-gen.js +48 -0
- package/dist/tools/bash.js +85 -74
- package/dist/tools/code_search.js +91 -0
- package/dist/tools/explore.js +104 -0
- package/dist/tools/index.js +10 -1
- package/dist/tools/question.js +53 -0
- package/dist/tools/read.js +14 -3
- package/dist/tools/task.js +98 -0
- package/dist/tools/todo.js +88 -0
- package/docs/API.md +219 -123
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@ import { runAgent, runChat } 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";
|
|
5
|
-
import { runSetup, isConfigured, loadConfig, fetchModels, getConfigDir, getRulesFile } from "./config.js";
|
|
5
|
+
import { runSetup, isConfigured, loadConfig, saveConfig, fetchModels, getConfigDir, getRulesFile } from "./config.js";
|
|
6
6
|
import { setAutoApprove } from "./confirm.js";
|
|
7
7
|
import { existsSync, writeFileSync, mkdirSync } from "fs";
|
|
8
8
|
import path from "path";
|
|
@@ -24,6 +24,7 @@ Usage:
|
|
|
24
24
|
min-agent chat <message> Send a message to the agent
|
|
25
25
|
min-agent chat Start interactive multi-turn chat
|
|
26
26
|
min-agent chat --resume <id> Resume a previous session
|
|
27
|
+
min-agent code AI coding mode (project-aware)
|
|
27
28
|
min-agent setup Configure API provider (interactive)
|
|
28
29
|
min-agent models List available models
|
|
29
30
|
min-agent history List saved sessions
|
|
@@ -159,7 +160,6 @@ async function main() {
|
|
|
159
160
|
}
|
|
160
161
|
const message = chatArgs.join(" ");
|
|
161
162
|
if (!message) {
|
|
162
|
-
// No message provided — enter interactive multi-turn mode
|
|
163
163
|
await runChat(modelOverride, resumeId);
|
|
164
164
|
}
|
|
165
165
|
else {
|
|
@@ -167,6 +167,25 @@ async function main() {
|
|
|
167
167
|
}
|
|
168
168
|
break;
|
|
169
169
|
}
|
|
170
|
+
case "code": {
|
|
171
|
+
if (!isConfigured()) {
|
|
172
|
+
console.error("Not configured. Run: min-agent setup");
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
let modelOverride;
|
|
176
|
+
let resumeId;
|
|
177
|
+
for (let i = 1; i < args.length; i++) {
|
|
178
|
+
if ((args[i] === "--model" || args[i] === "-m") && args[i + 1]) {
|
|
179
|
+
modelOverride = args[++i];
|
|
180
|
+
}
|
|
181
|
+
else if (args[i] === "--resume" && args[i + 1]) {
|
|
182
|
+
resumeId = args[++i];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const { runCode } = await import("./agent.js");
|
|
186
|
+
await runCode(modelOverride, resumeId);
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
170
189
|
case "serve": {
|
|
171
190
|
if (!isConfigured()) {
|
|
172
191
|
console.error("Not configured. Run: min-agent setup");
|
|
@@ -188,6 +207,19 @@ async function main() {
|
|
|
188
207
|
}
|
|
189
208
|
case "mcp": {
|
|
190
209
|
const subcommand = args[1];
|
|
210
|
+
if (subcommand === "-h" || subcommand === "--help" || !subcommand) {
|
|
211
|
+
console.log(`Usage: min-agent mcp <command>
|
|
212
|
+
|
|
213
|
+
Commands:
|
|
214
|
+
list List configured MCP servers (name + status)
|
|
215
|
+
info <name> Show details of a server
|
|
216
|
+
add <name> ... Add a new server (stdio or remote)
|
|
217
|
+
remove <name> Remove a server
|
|
218
|
+
enable <name> Enable a disabled server
|
|
219
|
+
disable <name> Disable a server
|
|
220
|
+
check Test connectivity of all servers`);
|
|
221
|
+
process.exit(0);
|
|
222
|
+
}
|
|
191
223
|
switch (subcommand) {
|
|
192
224
|
case "add": {
|
|
193
225
|
const name = args[2];
|
|
@@ -256,17 +288,45 @@ async function main() {
|
|
|
256
288
|
const servers = Object.entries(config.mcpServers);
|
|
257
289
|
if (servers.length === 0) {
|
|
258
290
|
console.log("No MCP servers configured.");
|
|
259
|
-
console.log("Add one with: min-agent mcp add <name> <command...>
|
|
291
|
+
console.log("Add one with: min-agent mcp add <name> <command...>");
|
|
260
292
|
}
|
|
261
293
|
else {
|
|
262
294
|
console.log("MCP Servers:");
|
|
263
295
|
for (const [name, cfg] of servers) {
|
|
264
|
-
const status = cfg.enabled === false ? "
|
|
265
|
-
console.log(` ${name}
|
|
296
|
+
const status = cfg.enabled === false ? "\x1b[90mdisabled\x1b[0m" : "\x1b[32menabled\x1b[0m";
|
|
297
|
+
console.log(` ${name} ${status}`);
|
|
266
298
|
}
|
|
299
|
+
console.log("\nUse: min-agent mcp info <name> for details");
|
|
267
300
|
}
|
|
268
301
|
break;
|
|
269
302
|
}
|
|
303
|
+
case "info": {
|
|
304
|
+
const name = args[2];
|
|
305
|
+
if (!name) {
|
|
306
|
+
console.error("Usage: min-agent mcp info <name>");
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
const config = loadMcpConfig();
|
|
310
|
+
const cfg = config.mcpServers[name];
|
|
311
|
+
if (!cfg) {
|
|
312
|
+
console.error(`MCP server "${name}" not found`);
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
console.log(`Name: ${name}`);
|
|
316
|
+
console.log(`Status: ${cfg.enabled === false ? "disabled" : "enabled"}`);
|
|
317
|
+
console.log(`Binding: ${formatMcpServerBinding(cfg)}`);
|
|
318
|
+
if (cfg.command)
|
|
319
|
+
console.log(`Command: ${Array.isArray(cfg.command) ? cfg.command.join(" ") : `${cfg.command} ${(cfg.args ?? []).join(" ")}`.trim()}`);
|
|
320
|
+
if (cfg.url)
|
|
321
|
+
console.log(`URL: ${cfg.url}`);
|
|
322
|
+
if (cfg.token)
|
|
323
|
+
console.log(`Token: ***`);
|
|
324
|
+
if (cfg.environment)
|
|
325
|
+
console.log(`Env: ${Object.keys(cfg.environment).join(", ")}`);
|
|
326
|
+
if (cfg.timeout)
|
|
327
|
+
console.log(`Timeout: ${cfg.timeout}ms`);
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
270
330
|
case "check": {
|
|
271
331
|
const results = await checkAllMcpServers();
|
|
272
332
|
if (results.length === 0) {
|
|
@@ -298,8 +358,44 @@ async function main() {
|
|
|
298
358
|
}
|
|
299
359
|
break;
|
|
300
360
|
}
|
|
361
|
+
case "enable": {
|
|
362
|
+
const names = args.slice(2);
|
|
363
|
+
if (names.length === 0) {
|
|
364
|
+
console.error("Usage: min-agent mcp enable <name...>");
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
const config = loadMcpConfig();
|
|
368
|
+
for (const name of names) {
|
|
369
|
+
if (!config.mcpServers[name]) {
|
|
370
|
+
console.error(`MCP server "${name}" not found`);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
config.mcpServers[name].enabled = true;
|
|
374
|
+
console.log(`✓ MCP server "${name}" enabled`);
|
|
375
|
+
}
|
|
376
|
+
saveMcpConfig(config);
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
case "disable": {
|
|
380
|
+
const names = args.slice(2);
|
|
381
|
+
if (names.length === 0) {
|
|
382
|
+
console.error("Usage: min-agent mcp disable <name...>");
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
const config = loadMcpConfig();
|
|
386
|
+
for (const name of names) {
|
|
387
|
+
if (!config.mcpServers[name]) {
|
|
388
|
+
console.error(`MCP server "${name}" not found`);
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
config.mcpServers[name].enabled = false;
|
|
392
|
+
console.log(`✓ MCP server "${name}" disabled`);
|
|
393
|
+
}
|
|
394
|
+
saveMcpConfig(config);
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
301
397
|
default:
|
|
302
|
-
console.error("Usage: min-agent mcp
|
|
398
|
+
console.error("Usage: min-agent mcp -h");
|
|
303
399
|
process.exit(1);
|
|
304
400
|
}
|
|
305
401
|
break;
|
|
@@ -391,35 +487,89 @@ async function main() {
|
|
|
391
487
|
}
|
|
392
488
|
case "skills": {
|
|
393
489
|
const subcommand = args[1];
|
|
490
|
+
if (subcommand === "-h" || subcommand === "--help" || !subcommand) {
|
|
491
|
+
console.log(`Usage: min-agent skills <command>
|
|
492
|
+
|
|
493
|
+
Commands:
|
|
494
|
+
list List skills (name + status)
|
|
495
|
+
info <name> Show details of a skill
|
|
496
|
+
enable <name> Enable a disabled skill
|
|
497
|
+
disable <name> Disable a skill`);
|
|
498
|
+
process.exit(0);
|
|
499
|
+
}
|
|
394
500
|
switch (subcommand) {
|
|
501
|
+
case "info": {
|
|
502
|
+
const name = args[2];
|
|
503
|
+
if (!name) {
|
|
504
|
+
console.error("Usage: min-agent skills info <name>");
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
507
|
+
discoverSkills({ silent: true });
|
|
508
|
+
const { getSkill } = await import("./skills.js");
|
|
509
|
+
const skill = getSkill(name);
|
|
510
|
+
if (!skill) {
|
|
511
|
+
console.error(`Skill "${name}" not found`);
|
|
512
|
+
process.exit(1);
|
|
513
|
+
}
|
|
514
|
+
const config = loadConfig();
|
|
515
|
+
const isDisabled = (config.disabledSkills ?? []).includes(name);
|
|
516
|
+
console.log(`Name: ${skill.name}`);
|
|
517
|
+
console.log(`Status: ${isDisabled ? "disabled" : "enabled"}`);
|
|
518
|
+
console.log(`Description: ${skill.description}`);
|
|
519
|
+
console.log(`Location: ${skill.location}`);
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
case "enable": {
|
|
523
|
+
const names = args.slice(2);
|
|
524
|
+
if (names.length === 0) {
|
|
525
|
+
console.error("Usage: min-agent skills enable <name...>");
|
|
526
|
+
process.exit(1);
|
|
527
|
+
}
|
|
528
|
+
const config = loadConfig();
|
|
529
|
+
config.disabledSkills = (config.disabledSkills ?? []).filter((s) => !names.includes(s));
|
|
530
|
+
saveConfig(config);
|
|
531
|
+
for (const name of names)
|
|
532
|
+
console.log(`✓ Skill "${name}" enabled`);
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
case "disable": {
|
|
536
|
+
const names = args.slice(2);
|
|
537
|
+
if (names.length === 0) {
|
|
538
|
+
console.error("Usage: min-agent skills disable <name...>");
|
|
539
|
+
process.exit(1);
|
|
540
|
+
}
|
|
541
|
+
const config = loadConfig();
|
|
542
|
+
const disabled = config.disabledSkills ?? [];
|
|
543
|
+
for (const name of names) {
|
|
544
|
+
if (!disabled.includes(name))
|
|
545
|
+
disabled.push(name);
|
|
546
|
+
console.log(`✓ Skill "${name}" disabled`);
|
|
547
|
+
}
|
|
548
|
+
config.disabledSkills = disabled;
|
|
549
|
+
saveConfig(config);
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
395
552
|
case "list": {
|
|
396
|
-
discoverSkills();
|
|
553
|
+
discoverSkills({ silent: true });
|
|
397
554
|
const skills = getSkills();
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
console.log("
|
|
402
|
-
|
|
403
|
-
console.log(" .opencode/skills/<name>/SKILL.md");
|
|
404
|
-
console.log("");
|
|
405
|
-
console.log("SKILL.md format:");
|
|
406
|
-
console.log(" ---");
|
|
407
|
-
console.log(" name: my-skill");
|
|
408
|
-
console.log(" description: What this skill does");
|
|
409
|
-
console.log(" ---");
|
|
410
|
-
console.log(" # Instructions content...");
|
|
555
|
+
const config = loadConfig();
|
|
556
|
+
const disabledNames = config.disabledSkills ?? [];
|
|
557
|
+
if (skills.length === 0 && disabledNames.length === 0) {
|
|
558
|
+
console.log("No skills found. Add SKILL.md files in .min-agent/skills/<name>/");
|
|
559
|
+
break;
|
|
411
560
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
561
|
+
console.log("Skills:");
|
|
562
|
+
for (const skill of skills) {
|
|
563
|
+
console.log(` ${skill.name} \x1b[32menabled\x1b[0m`);
|
|
564
|
+
}
|
|
565
|
+
for (const n of disabledNames) {
|
|
566
|
+
console.log(` ${n} \x1b[90mdisabled\x1b[0m`);
|
|
418
567
|
}
|
|
568
|
+
console.log("\nUse: min-agent skills info <name> for details");
|
|
419
569
|
break;
|
|
420
570
|
}
|
|
421
571
|
default:
|
|
422
|
-
console.error("Usage: min-agent skills
|
|
572
|
+
console.error("Usage: min-agent skills -h");
|
|
423
573
|
process.exit(1);
|
|
424
574
|
}
|
|
425
575
|
break;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
export function getClipboardImage() {
|
|
5
|
+
switch (process.platform) {
|
|
6
|
+
case "darwin":
|
|
7
|
+
return getClipboardImageMac();
|
|
8
|
+
case "linux":
|
|
9
|
+
return getClipboardImageLinux();
|
|
10
|
+
case "win32":
|
|
11
|
+
return getClipboardImageWindows();
|
|
12
|
+
default:
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function getClipboardImageMac() {
|
|
17
|
+
const tmpFile = path.join(os.tmpdir(), `min-agent-paste-${Date.now()}.png`);
|
|
18
|
+
try {
|
|
19
|
+
// Try pngpaste first (brew install pngpaste)
|
|
20
|
+
execSync(`pngpaste "${tmpFile}" 2>/dev/null`, { stdio: "pipe" });
|
|
21
|
+
const { readFileSync } = require("fs");
|
|
22
|
+
const data = readFileSync(tmpFile);
|
|
23
|
+
try {
|
|
24
|
+
require("fs").unlinkSync(tmpFile);
|
|
25
|
+
}
|
|
26
|
+
catch { }
|
|
27
|
+
if (data.length > 0)
|
|
28
|
+
return { data, mimeType: "image/png" };
|
|
29
|
+
}
|
|
30
|
+
catch { }
|
|
31
|
+
try {
|
|
32
|
+
// Fallback: osascript to save clipboard image
|
|
33
|
+
const script = `
|
|
34
|
+
set tmpFile to POSIX file "${tmpFile}"
|
|
35
|
+
try
|
|
36
|
+
set imgData to the clipboard as «class PNGf»
|
|
37
|
+
set fp to open for access tmpFile with write permission
|
|
38
|
+
write imgData to fp
|
|
39
|
+
close access fp
|
|
40
|
+
return "ok"
|
|
41
|
+
on error
|
|
42
|
+
return "no_image"
|
|
43
|
+
end try
|
|
44
|
+
`;
|
|
45
|
+
const result = execSync(`osascript -e '${script.replace(/'/g, "'\\''")}'`, { encoding: "utf-8" }).trim();
|
|
46
|
+
if (result === "ok") {
|
|
47
|
+
const { readFileSync, unlinkSync } = require("fs");
|
|
48
|
+
const data = readFileSync(tmpFile);
|
|
49
|
+
try {
|
|
50
|
+
unlinkSync(tmpFile);
|
|
51
|
+
}
|
|
52
|
+
catch { }
|
|
53
|
+
if (data.length > 0)
|
|
54
|
+
return { data, mimeType: "image/png" };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch { }
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
function getClipboardImageLinux() {
|
|
61
|
+
try {
|
|
62
|
+
// xclip
|
|
63
|
+
const data = execSync("xclip -selection clipboard -t image/png -o 2>/dev/null", {
|
|
64
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
65
|
+
});
|
|
66
|
+
if (data.length > 0)
|
|
67
|
+
return { data, mimeType: "image/png" };
|
|
68
|
+
}
|
|
69
|
+
catch { }
|
|
70
|
+
try {
|
|
71
|
+
// xsel fallback
|
|
72
|
+
const data = execSync("xsel --clipboard --output 2>/dev/null", {
|
|
73
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
74
|
+
});
|
|
75
|
+
// Check if it's actually image data (PNG magic bytes)
|
|
76
|
+
if (data.length > 8 && data[0] === 0x89 && data[1] === 0x50) {
|
|
77
|
+
return { data, mimeType: "image/png" };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch { }
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function getClipboardImageWindows() {
|
|
84
|
+
const tmpFile = path.join(os.tmpdir(), `min-agent-paste-${Date.now()}.png`);
|
|
85
|
+
try {
|
|
86
|
+
const ps = `
|
|
87
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
88
|
+
$img = [System.Windows.Forms.Clipboard]::GetImage()
|
|
89
|
+
if ($img) { $img.Save('${tmpFile.replace(/\\/g, "\\\\")}', [System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'ok' }
|
|
90
|
+
else { Write-Output 'no_image' }
|
|
91
|
+
`;
|
|
92
|
+
const result = execSync(`powershell -NoProfile -Command "${ps}"`, { encoding: "utf-8" }).trim();
|
|
93
|
+
if (result === "ok") {
|
|
94
|
+
const { readFileSync, unlinkSync } = require("fs");
|
|
95
|
+
const data = readFileSync(tmpFile);
|
|
96
|
+
try {
|
|
97
|
+
unlinkSync(tmpFile);
|
|
98
|
+
}
|
|
99
|
+
catch { }
|
|
100
|
+
if (data.length > 0)
|
|
101
|
+
return { data, mimeType: "image/png" };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch { }
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
export function scanProject() {
|
|
5
|
+
const cwd = process.cwd();
|
|
6
|
+
const isGitRepo = existsSync(path.join(cwd, ".git"));
|
|
7
|
+
let branch;
|
|
8
|
+
if (isGitRepo) {
|
|
9
|
+
try {
|
|
10
|
+
branch = execSync("git branch --show-current", { encoding: "utf-8", cwd }).trim();
|
|
11
|
+
}
|
|
12
|
+
catch { }
|
|
13
|
+
}
|
|
14
|
+
const languages = [];
|
|
15
|
+
const configFiles = [];
|
|
16
|
+
const entryFiles = [];
|
|
17
|
+
// Detect by config files
|
|
18
|
+
const checks = [
|
|
19
|
+
{ file: "package.json", lang: "TypeScript/JavaScript", pm: "npm" },
|
|
20
|
+
{ file: "bun.lock", lang: "TypeScript/JavaScript", pm: "bun" },
|
|
21
|
+
{ file: "yarn.lock", lang: "TypeScript/JavaScript", pm: "yarn" },
|
|
22
|
+
{ file: "pnpm-lock.yaml", lang: "TypeScript/JavaScript", pm: "pnpm" },
|
|
23
|
+
{ file: "tsconfig.json", lang: "TypeScript" },
|
|
24
|
+
{ file: "Cargo.toml", lang: "Rust", pm: "cargo" },
|
|
25
|
+
{ file: "go.mod", lang: "Go" },
|
|
26
|
+
{ file: "pyproject.toml", lang: "Python", pm: "pip/uv" },
|
|
27
|
+
{ file: "requirements.txt", lang: "Python", pm: "pip" },
|
|
28
|
+
{ file: "Gemfile", lang: "Ruby", pm: "bundler" },
|
|
29
|
+
{ file: "pom.xml", lang: "Java", pm: "maven" },
|
|
30
|
+
{ file: "build.gradle", lang: "Java/Kotlin", pm: "gradle" },
|
|
31
|
+
{ file: "composer.json", lang: "PHP", pm: "composer" },
|
|
32
|
+
{ file: "Makefile", lang: "" },
|
|
33
|
+
{ file: "Dockerfile", lang: "" },
|
|
34
|
+
];
|
|
35
|
+
let packageManager;
|
|
36
|
+
let framework;
|
|
37
|
+
for (const check of checks) {
|
|
38
|
+
if (existsSync(path.join(cwd, check.file))) {
|
|
39
|
+
configFiles.push(check.file);
|
|
40
|
+
if (check.lang && !languages.includes(check.lang))
|
|
41
|
+
languages.push(check.lang);
|
|
42
|
+
if (check.pm && !packageManager)
|
|
43
|
+
packageManager = check.pm;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Detect framework from package.json
|
|
47
|
+
if (existsSync(path.join(cwd, "package.json"))) {
|
|
48
|
+
try {
|
|
49
|
+
const pkg = JSON.parse(readFileSync(path.join(cwd, "package.json"), "utf-8"));
|
|
50
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
51
|
+
if (allDeps["next"])
|
|
52
|
+
framework = "Next.js";
|
|
53
|
+
else if (allDeps["nuxt"])
|
|
54
|
+
framework = "Nuxt";
|
|
55
|
+
else if (allDeps["@angular/core"])
|
|
56
|
+
framework = "Angular";
|
|
57
|
+
else if (allDeps["vue"])
|
|
58
|
+
framework = "Vue";
|
|
59
|
+
else if (allDeps["react"])
|
|
60
|
+
framework = "React";
|
|
61
|
+
else if (allDeps["svelte"])
|
|
62
|
+
framework = "Svelte";
|
|
63
|
+
else if (allDeps["express"])
|
|
64
|
+
framework = "Express";
|
|
65
|
+
else if (allDeps["fastify"])
|
|
66
|
+
framework = "Fastify";
|
|
67
|
+
else if (allDeps["hono"])
|
|
68
|
+
framework = "Hono";
|
|
69
|
+
else if (allDeps["effect"])
|
|
70
|
+
framework = "Effect";
|
|
71
|
+
}
|
|
72
|
+
catch { }
|
|
73
|
+
}
|
|
74
|
+
// Find entry files
|
|
75
|
+
const entryPatterns = [
|
|
76
|
+
"src/index.ts", "src/index.js", "src/main.ts", "src/main.js",
|
|
77
|
+
"src/app.ts", "src/app.js", "index.ts", "index.js",
|
|
78
|
+
"main.ts", "main.js", "app.ts", "app.js",
|
|
79
|
+
"src/lib.rs", "main.go", "main.py", "app.py",
|
|
80
|
+
];
|
|
81
|
+
for (const p of entryPatterns) {
|
|
82
|
+
if (existsSync(path.join(cwd, p)))
|
|
83
|
+
entryFiles.push(p);
|
|
84
|
+
}
|
|
85
|
+
// Build summary
|
|
86
|
+
const parts = [];
|
|
87
|
+
parts.push(`Directory: ${cwd}`);
|
|
88
|
+
if (isGitRepo)
|
|
89
|
+
parts.push(`Git: yes (branch: ${branch ?? "unknown"})`);
|
|
90
|
+
if (languages.length)
|
|
91
|
+
parts.push(`Languages: ${languages.join(", ")}`);
|
|
92
|
+
if (framework)
|
|
93
|
+
parts.push(`Framework: ${framework}`);
|
|
94
|
+
if (packageManager)
|
|
95
|
+
parts.push(`Package manager: ${packageManager}`);
|
|
96
|
+
if (configFiles.length)
|
|
97
|
+
parts.push(`Config files: ${configFiles.join(", ")}`);
|
|
98
|
+
if (entryFiles.length)
|
|
99
|
+
parts.push(`Entry points: ${entryFiles.join(", ")}`);
|
|
100
|
+
// List top-level directory structure
|
|
101
|
+
try {
|
|
102
|
+
const items = readdirSync(cwd)
|
|
103
|
+
.filter((f) => !f.startsWith(".") || f === ".env.example")
|
|
104
|
+
.filter((f) => f !== "node_modules" && f !== ".git")
|
|
105
|
+
.slice(0, 30)
|
|
106
|
+
.map((f) => {
|
|
107
|
+
const stat = statSync(path.join(cwd, f));
|
|
108
|
+
return stat.isDirectory() ? `${f}/` : f;
|
|
109
|
+
});
|
|
110
|
+
parts.push(`Structure: ${items.join(", ")}`);
|
|
111
|
+
}
|
|
112
|
+
catch { }
|
|
113
|
+
return {
|
|
114
|
+
directory: cwd,
|
|
115
|
+
isGitRepo,
|
|
116
|
+
branch,
|
|
117
|
+
languages,
|
|
118
|
+
framework,
|
|
119
|
+
packageManager,
|
|
120
|
+
entryFiles,
|
|
121
|
+
configFiles,
|
|
122
|
+
summary: parts.join("\n"),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export function buildCodeSystemPrompt(project, instructions) {
|
|
126
|
+
const parts = [
|
|
127
|
+
"You are an expert AI coding assistant. You help users with software engineering tasks including writing code, debugging, refactoring, and architecture decisions.",
|
|
128
|
+
"",
|
|
129
|
+
"# Principles",
|
|
130
|
+
"- Be concise and direct. Minimize output tokens while maintaining quality.",
|
|
131
|
+
"- NEVER add comments to code unless asked. Write self-documenting code.",
|
|
132
|
+
"- Follow existing code conventions in the project. Mimic style, libraries, and patterns.",
|
|
133
|
+
"- NEVER assume a library is available — check package.json/Cargo.toml/etc first.",
|
|
134
|
+
"- Prefer editing existing files over creating new ones.",
|
|
135
|
+
"- After making changes, run lint/typecheck/build if available.",
|
|
136
|
+
"- NEVER commit unless explicitly asked.",
|
|
137
|
+
"",
|
|
138
|
+
"# Workflow",
|
|
139
|
+
"- Use search tools (grep, glob) to understand the codebase before making changes.",
|
|
140
|
+
"- Use the edit tool for precise changes instead of rewriting entire files.",
|
|
141
|
+
"- When fixing bugs: read the relevant code, understand the issue, fix it, verify.",
|
|
142
|
+
"- When adding features: explore existing patterns first, then implement consistently.",
|
|
143
|
+
"- Run tests after changes when a test command is available.",
|
|
144
|
+
"",
|
|
145
|
+
"# Environment",
|
|
146
|
+
`<env>`,
|
|
147
|
+
` Working directory: ${project.directory}`,
|
|
148
|
+
` Git repo: ${project.isGitRepo ? `yes (branch: ${project.branch ?? "unknown"})` : "no"}`,
|
|
149
|
+
` Platform: ${process.platform}`,
|
|
150
|
+
` Date: ${new Date().toDateString()}`,
|
|
151
|
+
project.languages.length ? ` Languages: ${project.languages.join(", ")}` : "",
|
|
152
|
+
project.framework ? ` Framework: ${project.framework}` : "",
|
|
153
|
+
project.packageManager ? ` Package manager: ${project.packageManager}` : "",
|
|
154
|
+
`</env>`,
|
|
155
|
+
"",
|
|
156
|
+
"# Project Structure",
|
|
157
|
+
"```",
|
|
158
|
+
project.summary,
|
|
159
|
+
"```",
|
|
160
|
+
].filter(Boolean);
|
|
161
|
+
if (instructions.length > 0) {
|
|
162
|
+
parts.push("", "# User Instructions", "");
|
|
163
|
+
parts.push(...instructions);
|
|
164
|
+
}
|
|
165
|
+
return parts.join("\n");
|
|
166
|
+
}
|