min-agent 0.3.0 → 0.4.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/README.md +111 -28
- package/dist/agent.js +1119 -256
- package/dist/cli/commands/chat.js +10 -0
- package/dist/cli/commands/exec.js +32 -0
- package/dist/cli/commands/history.js +58 -0
- package/dist/cli/commands/index.js +224 -0
- package/dist/cli/commands/init.js +18 -0
- package/dist/cli/commands/mcp.js +173 -0
- package/dist/cli/commands/memory.js +69 -0
- package/dist/cli/commands/models.js +21 -0
- package/dist/cli/commands/permission.js +12 -0
- package/dist/cli/commands/rules.js +33 -0
- package/dist/cli/commands/sandbox.js +13 -0
- package/dist/cli/commands/serve.js +9 -0
- package/dist/cli/commands/setup.js +4 -0
- package/dist/cli/commands/shared.js +16 -0
- package/dist/cli/commands/skills.js +119 -0
- package/dist/cli/commands/update.js +7 -0
- package/dist/cli/commands/write-config.js +30 -0
- package/dist/cli/errors.js +36 -0
- package/dist/cli/exec-prompt.js +26 -0
- package/dist/cli/option-helpers.js +53 -0
- package/dist/cli/program.js +180 -0
- package/dist/cli.js +5 -888
- package/dist/code-mode.js +32 -14
- package/dist/compaction.js +347 -160
- package/dist/config.js +119 -10
- package/dist/confirm.js +56 -9
- package/dist/context-window.js +107 -39
- package/dist/doom-loop.js +264 -29
- package/dist/fetch-timeout.js +152 -0
- package/dist/http-approvals.js +60 -0
- package/dist/instructions.js +21 -0
- package/dist/logger.js +33 -4
- package/dist/markdown.js +37 -11
- package/dist/mcp.js +328 -30
- package/dist/memory.js +97 -56
- package/dist/output.js +7 -5
- package/dist/permission-cli.js +43 -0
- package/dist/plugins.js +46 -8
- package/dist/pricing.js +4 -4
- package/dist/provider.js +23 -6
- package/dist/question-format.js +60 -0
- package/dist/sandbox-cli.js +82 -0
- package/dist/sandbox.js +403 -0
- package/dist/save-throttle.js +45 -0
- package/dist/serve/common.js +404 -0
- package/dist/serve/routes-chat.js +347 -0
- package/dist/serve/routes-mcp.js +212 -0
- package/dist/serve/routes-memory.js +66 -0
- package/dist/serve/routes-meta.js +205 -0
- package/dist/serve/routes-sessions.js +61 -0
- package/dist/serve/routes-skills.js +70 -0
- package/dist/serve.js +33 -883
- package/dist/sessions.js +53 -9
- package/dist/skills.js +82 -18
- package/dist/title-gen.js +8 -2
- package/dist/token-display.js +36 -0
- package/dist/tool-display.js +5 -0
- package/dist/tool-output.js +1 -3
- package/dist/tools/apply_patch.js +85 -11
- package/dist/tools/atomic-file.js +35 -0
- package/dist/tools/backend.js +2 -2
- package/dist/tools/bash.js +57 -19
- package/dist/tools/code_search.js +7 -1
- package/dist/tools/edit.js +11 -10
- package/dist/tools/explore.js +74 -14
- package/dist/tools/glob.js +4 -0
- package/dist/tools/grep.js +17 -10
- package/dist/tools/index.js +6 -21
- package/dist/tools/question.js +28 -9
- package/dist/tools/read.js +6 -4
- package/dist/tools/search-searxng.js +223 -0
- package/dist/tools/search-serper.js +189 -0
- package/dist/tools/task.js +84 -30
- package/dist/tools/todo.js +120 -19
- package/dist/tools/web_fetch.js +11 -3
- package/dist/tools/web_search.js +66 -556
- package/dist/tools/write.js +23 -6
- package/dist/tui/App.js +63 -14
- package/dist/tui/ConfirmBar.js +45 -13
- package/dist/tui/InputBar.js +150 -35
- package/dist/tui/MessageList.js +266 -125
- package/dist/tui/ModelPicker.js +8 -3
- package/dist/tui/QuestionBar.js +51 -19
- package/dist/tui/SessionPicker.js +79 -0
- package/dist/tui/StatusBar.js +8 -14
- package/dist/tui/agent-runner.js +142 -22
- package/dist/tui/caret-pos.js +48 -5
- package/dist/tui/caret.js +1 -1
- package/dist/tui/click-count.js +13 -0
- package/dist/tui/drag-state.js +8 -3
- package/dist/tui/hydrate.js +129 -0
- package/dist/tui/index.js +42 -13
- package/dist/tui/input-history.js +92 -11
- package/dist/tui/layout.js +75 -4
- package/dist/tui/prompt-queue.js +24 -0
- package/dist/tui/selection.js +113 -21
- package/dist/tui/session-switch.js +28 -0
- package/dist/tui/slash-commands.js +22 -6
- package/dist/tui/slash-handler.js +233 -58
- package/dist/tui/text-width.js +38 -16
- package/dist/tui/token-info.js +7 -0
- package/dist/tui/tool-children.js +19 -0
- package/dist/tui/undo-stack.js +1 -1
- package/dist/tui/use-sgr-mouse.js +3 -1
- package/dist/tui-chat.js +276 -40
- package/dist/updater.js +88 -29
- package/dist/xml-search.js +194 -0
- package/docs/API.md +257 -25
- package/docs/superpowers/plans/2026-08-20-tui-completeness.md +873 -0
- package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +631 -0
- package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +47 -0
- package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +37 -0
- package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +68 -0
- package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +273 -0
- package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +165 -0
- package/package.json +6 -1
- package/skills/self-config/SKILL.md +90 -0
- package/skills/self-config/reference.md +149 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { runAgent } from "../../agent.js";
|
|
2
|
+
import { isConfigured } from "../../config.js";
|
|
3
|
+
import { resolveExecPrompt } from "../exec-prompt.js";
|
|
4
|
+
import { CliError } from "../errors.js";
|
|
5
|
+
export async function runExec(messageTokens, opts) {
|
|
6
|
+
if (!isConfigured()) {
|
|
7
|
+
throw new CliError("Not configured.", { hint: "Run: min-agent setup" });
|
|
8
|
+
}
|
|
9
|
+
const stdinIsTty = Boolean(process.stdin.isTTY);
|
|
10
|
+
const joined = messageTokens.join(" ");
|
|
11
|
+
const shouldReadStdin = joined === "-" || !stdinIsTty;
|
|
12
|
+
let stdinText = "";
|
|
13
|
+
if (shouldReadStdin) {
|
|
14
|
+
try {
|
|
15
|
+
const chunks = [];
|
|
16
|
+
for await (const chunk of process.stdin) {
|
|
17
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
18
|
+
}
|
|
19
|
+
stdinText = Buffer.concat(chunks).toString("utf-8");
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
throw new CliError("Failed to read stdin");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const resolved = resolveExecPrompt(messageTokens, stdinText, stdinIsTty);
|
|
26
|
+
if (!resolved.ok) {
|
|
27
|
+
throw new CliError(resolved.error);
|
|
28
|
+
}
|
|
29
|
+
const failed = await runAgent(resolved.prompt, opts.model, opts.image.length > 0 ? opts.image : undefined, opts.provider, opts.resume);
|
|
30
|
+
if (failed)
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { writeFileSync } from "fs";
|
|
2
|
+
import { CliError } from "../errors.js";
|
|
3
|
+
export async function runHistoryList() {
|
|
4
|
+
const { listSessions } = await import("../../sessions.js");
|
|
5
|
+
const sessions = listSessions();
|
|
6
|
+
if (sessions.length === 0) {
|
|
7
|
+
console.log("No saved sessions.");
|
|
8
|
+
console.log("Sessions are auto-saved when you exit interactive chat.");
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
console.log(`Sessions (${sessions.length}):`);
|
|
12
|
+
for (const s of sessions.slice(0, 20)) {
|
|
13
|
+
const date = s.updated.split("T")[0];
|
|
14
|
+
console.log(` ${s.id} ${date} ${s.title} (${s.messageCount} msgs)`);
|
|
15
|
+
}
|
|
16
|
+
console.log("\nResume with: min-agent --resume <id>");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function runHistoryDelete(id) {
|
|
20
|
+
const { deleteSession } = await import("../../sessions.js");
|
|
21
|
+
if (deleteSession(id)) {
|
|
22
|
+
console.log(`✓ Session ${id} deleted`);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
throw new CliError(`Session ${id} not found`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export async function runHistoryRename(id, titleTokens) {
|
|
29
|
+
const title = titleTokens.join(" ");
|
|
30
|
+
if (!title)
|
|
31
|
+
throw new CliError("Usage: min-agent history rename <id> <title>");
|
|
32
|
+
const { renameSession } = await import("../../sessions.js");
|
|
33
|
+
if (renameSession(id, title)) {
|
|
34
|
+
console.log(`✓ Session ${id} renamed to: ${title}`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
throw new CliError(`Session ${id} not found`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export async function runHistoryExport(id, opts) {
|
|
41
|
+
const { loadSession } = await import("../../sessions.js");
|
|
42
|
+
const session = loadSession(id);
|
|
43
|
+
if (!session)
|
|
44
|
+
throw new CliError(`Session ${id} not found`);
|
|
45
|
+
const json = JSON.stringify(session, null, 2);
|
|
46
|
+
if (opts.output) {
|
|
47
|
+
try {
|
|
48
|
+
writeFileSync(opts.output, json, "utf-8");
|
|
49
|
+
console.log(`✓ Session ${id} exported to ${opts.output}`);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
throw new CliError(`Export failed: ${err.message}`, { exitCode: 2 });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
console.log(json);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { Option } from "commander";
|
|
2
|
+
import { resolveScope, parsePositiveIntArg } from "../option-helpers.js";
|
|
3
|
+
import { runSetupCommand } from "./setup.js";
|
|
4
|
+
import { runModelsCommand } from "./models.js";
|
|
5
|
+
import { runChat } from "./chat.js";
|
|
6
|
+
import { runExec } from "./exec.js";
|
|
7
|
+
import { runServeCommand } from "./serve.js";
|
|
8
|
+
import { runSandboxCommand } from "./sandbox.js";
|
|
9
|
+
import { runPermissionCommand } from "./permission.js";
|
|
10
|
+
import { runInitCommand } from "./init.js";
|
|
11
|
+
import { runRulesShow, runRulesEdit } from "./rules.js";
|
|
12
|
+
import { runUpdateCommand } from "./update.js";
|
|
13
|
+
import { runHistoryList, runHistoryDelete, runHistoryRename, runHistoryExport } from "./history.js";
|
|
14
|
+
import { runMemoryList, runMemoryAdd, runMemorySearch, runMemoryDelete } from "./memory.js";
|
|
15
|
+
import { runMcpAdd, runMcpRemove, runMcpList, runMcpInfo, runMcpCheck, runMcpToggle, } from "./mcp.js";
|
|
16
|
+
import { runSkillsList, runSkillsInfo, runSkillsToggle, runSkillsNew } from "./skills.js";
|
|
17
|
+
/**
|
|
18
|
+
* All `--project`/`--global`/`--model`/`--provider`/`--resume`/`--image`/`--sandbox`/
|
|
19
|
+
* `--network`/`--permission` flags live on the root `program` (see addSessionOptions),
|
|
20
|
+
* so they can appear before or after a subcommand name. Subcommands read them via
|
|
21
|
+
* `program.opts<SessionOptions>()` instead of re-declaring the same options — commander
|
|
22
|
+
* resolves options against the first command in the chain that declares them, so a
|
|
23
|
+
* subcommand-local redeclaration would silently shadow the root option instead of merging.
|
|
24
|
+
*/
|
|
25
|
+
export function registerCommands(program) {
|
|
26
|
+
const rootOpts = () => program.opts();
|
|
27
|
+
// --- Session aliases -----------------------------------------------------
|
|
28
|
+
for (const alias of ["chat", "code"]) {
|
|
29
|
+
program
|
|
30
|
+
.command(alias)
|
|
31
|
+
.description("Same as interactive session (compat)")
|
|
32
|
+
.argument("[prompt...]", "Prompt to send")
|
|
33
|
+
.allowUnknownOption()
|
|
34
|
+
.action(async (prompt) => {
|
|
35
|
+
await runChat(prompt, rootOpts());
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
program
|
|
39
|
+
.command("exec")
|
|
40
|
+
.description("Run one message non-interactively")
|
|
41
|
+
.argument("[message...]", 'Message to send (use "-" to read from stdin)')
|
|
42
|
+
.allowUnknownOption()
|
|
43
|
+
.action(async (message) => {
|
|
44
|
+
await runExec(message, rootOpts());
|
|
45
|
+
});
|
|
46
|
+
// --- Setup & config ----------------------------------------------------
|
|
47
|
+
program.command("setup").description("Configure providers (interactively)").action(runSetupCommand);
|
|
48
|
+
program.command("init").description("Initialize .min-agent/ in current directory").action(runInitCommand);
|
|
49
|
+
program
|
|
50
|
+
.command("models")
|
|
51
|
+
.description("List available models")
|
|
52
|
+
.action(async () => {
|
|
53
|
+
await runModelsCommand(rootOpts());
|
|
54
|
+
});
|
|
55
|
+
const rules = program.command("rules").description("Show or edit instruction rules");
|
|
56
|
+
rules.action(runRulesShow);
|
|
57
|
+
rules.command("edit").description("Edit the global rules file").action(runRulesEdit);
|
|
58
|
+
const memory = program.command("memory").description("Manage saved memories (uses --project/--global)");
|
|
59
|
+
memory.action(async () => {
|
|
60
|
+
const opts = rootOpts();
|
|
61
|
+
await runMemoryList(opts.project ? "project" : opts.global ? "global" : undefined);
|
|
62
|
+
});
|
|
63
|
+
memory
|
|
64
|
+
.command("add")
|
|
65
|
+
.argument("<text...>")
|
|
66
|
+
.description("Add a memory")
|
|
67
|
+
.action(async (text) => {
|
|
68
|
+
const opts = rootOpts();
|
|
69
|
+
await runMemoryAdd(text, opts.project ? "project" : opts.global ? "global" : undefined);
|
|
70
|
+
});
|
|
71
|
+
memory
|
|
72
|
+
.command("search")
|
|
73
|
+
.argument("<query...>")
|
|
74
|
+
.description("Search memories")
|
|
75
|
+
.action(async (query) => {
|
|
76
|
+
const opts = rootOpts();
|
|
77
|
+
await runMemorySearch(query, opts.project ? "project" : opts.global ? "global" : undefined);
|
|
78
|
+
});
|
|
79
|
+
memory
|
|
80
|
+
.command("delete")
|
|
81
|
+
.argument("<index>")
|
|
82
|
+
.description("Delete a memory by index")
|
|
83
|
+
.action(async (index) => {
|
|
84
|
+
const opts = rootOpts();
|
|
85
|
+
await runMemoryDelete(index, opts.project ? "project" : opts.global ? "global" : undefined);
|
|
86
|
+
});
|
|
87
|
+
const sandbox = program
|
|
88
|
+
.command("sandbox")
|
|
89
|
+
.description("Show or set isolation mode (uses --project/--global)")
|
|
90
|
+
.argument("[mode]", "off, workspace, or strict");
|
|
91
|
+
sandbox.action(async (mode) => {
|
|
92
|
+
const opts = rootOpts();
|
|
93
|
+
await runSandboxCommand(mode ? [mode] : [], opts, opts);
|
|
94
|
+
});
|
|
95
|
+
sandbox
|
|
96
|
+
.command("network")
|
|
97
|
+
.description("Show or set network policy")
|
|
98
|
+
.argument("[policy]", "allow or deny")
|
|
99
|
+
.action(async (policy) => {
|
|
100
|
+
const opts = rootOpts();
|
|
101
|
+
await runSandboxCommand(["network", ...(policy ? [policy] : [])], opts, opts);
|
|
102
|
+
});
|
|
103
|
+
program
|
|
104
|
+
.command("permission")
|
|
105
|
+
.description("Show or set confirmation mode (uses --project/--global)")
|
|
106
|
+
.argument("[mode]", "ask, accept-edits, or allow-all")
|
|
107
|
+
.action(async (mode) => {
|
|
108
|
+
const opts = rootOpts();
|
|
109
|
+
await runPermissionCommand(mode ? [mode] : [], opts, opts);
|
|
110
|
+
});
|
|
111
|
+
// --- Integrations --------------------------------------------------------
|
|
112
|
+
const mcp = program.command("mcp").description("Manage MCP servers");
|
|
113
|
+
mcp
|
|
114
|
+
.command("add")
|
|
115
|
+
.description("Add a new MCP server (stdio or remote)")
|
|
116
|
+
.usage("[options] <name> [command...]")
|
|
117
|
+
.argument("<name>")
|
|
118
|
+
.argument("[cmd...]", "Command to launch a stdio server (npx, node, ...)")
|
|
119
|
+
.option("--url <url>", "Remote server URL")
|
|
120
|
+
.option("--sse", "Use SSE transport for the remote server")
|
|
121
|
+
.option("--streamable-http", "Use streamable-http transport for the remote server")
|
|
122
|
+
.option("--token <token>", "Bearer token for remote servers")
|
|
123
|
+
.option("--env <KEY=VALUE>", "Extra env for stdio servers (repeatable)", (v, prev) => [...prev, v], [])
|
|
124
|
+
.addOption(new Option("--timeout <ms>", "Connection/call timeout").argParser(parsePositiveIntArg("--timeout")))
|
|
125
|
+
.option("--skip-check", "Skip validation before saving")
|
|
126
|
+
.addHelpText("after", "\nAll options above must come before <name>. Everything after <name> (including flags meant for the\n" +
|
|
127
|
+
"launched command, like `npx -y`) is passed through as-is.\n" +
|
|
128
|
+
"Scope: pass --project on `min-agent` before `mcp add` to write to .min-agent/mcp.json.")
|
|
129
|
+
.allowUnknownOption()
|
|
130
|
+
.passThroughOptions()
|
|
131
|
+
.action(async (name, cmd, opts) => {
|
|
132
|
+
await runMcpAdd(name, cmd, { ...opts, project: rootOpts().project });
|
|
133
|
+
});
|
|
134
|
+
mcp
|
|
135
|
+
.command("remove")
|
|
136
|
+
.argument("<name>")
|
|
137
|
+
.description("Remove a server")
|
|
138
|
+
.action(async (name) => {
|
|
139
|
+
await runMcpRemove(name, { project: rootOpts().project });
|
|
140
|
+
});
|
|
141
|
+
mcp.command("list").description("List configured MCP servers").action(runMcpList);
|
|
142
|
+
mcp.command("info").argument("<name>").description("Show details of a server").action(runMcpInfo);
|
|
143
|
+
mcp.command("check").description("Test connectivity of all servers").action(runMcpCheck);
|
|
144
|
+
mcp
|
|
145
|
+
.command("enable")
|
|
146
|
+
.argument("<names...>")
|
|
147
|
+
.description("Enable disabled servers (use --project to target project scope)")
|
|
148
|
+
.action(async (names) => {
|
|
149
|
+
await runMcpToggle(names, true, { project: rootOpts().project });
|
|
150
|
+
});
|
|
151
|
+
mcp
|
|
152
|
+
.command("disable")
|
|
153
|
+
.argument("<names...>")
|
|
154
|
+
.description("Disable servers (use --project to target project scope)")
|
|
155
|
+
.action(async (names) => {
|
|
156
|
+
await runMcpToggle(names, false, { project: rootOpts().project });
|
|
157
|
+
});
|
|
158
|
+
const skills = program.command("skills").description("Manage skills");
|
|
159
|
+
skills
|
|
160
|
+
.command("list")
|
|
161
|
+
.option("--json", "Machine-readable JSON output")
|
|
162
|
+
.description("List skills")
|
|
163
|
+
.action(async (opts) => {
|
|
164
|
+
await runSkillsList(Boolean(opts.json));
|
|
165
|
+
});
|
|
166
|
+
skills
|
|
167
|
+
.command("info")
|
|
168
|
+
.argument("<name>")
|
|
169
|
+
.option("--json", "Machine-readable JSON output")
|
|
170
|
+
.description("Show details of a skill")
|
|
171
|
+
.action(async (name, opts) => {
|
|
172
|
+
await runSkillsInfo(name, Boolean(opts.json));
|
|
173
|
+
});
|
|
174
|
+
skills
|
|
175
|
+
.command("new")
|
|
176
|
+
.argument("<name>")
|
|
177
|
+
.description("Create a new skill scaffold (use --global on `min-agent` for ~/.agents/skills/)")
|
|
178
|
+
.action(async (name) => {
|
|
179
|
+
await runSkillsNew(name, Boolean(rootOpts().global));
|
|
180
|
+
});
|
|
181
|
+
skills
|
|
182
|
+
.command("enable")
|
|
183
|
+
.argument("<names...>")
|
|
184
|
+
.description("Enable disabled skills (use --project to target this project only)")
|
|
185
|
+
.action(async (names) => {
|
|
186
|
+
await runSkillsToggle(names, true, resolveScope(rootOpts()));
|
|
187
|
+
});
|
|
188
|
+
skills
|
|
189
|
+
.command("disable")
|
|
190
|
+
.argument("<names...>")
|
|
191
|
+
.description("Disable skills (use --project to target this project only)")
|
|
192
|
+
.action(async (names) => {
|
|
193
|
+
await runSkillsToggle(names, false, resolveScope(rootOpts()));
|
|
194
|
+
});
|
|
195
|
+
// --- Operations -----------------------------------------------------------
|
|
196
|
+
program
|
|
197
|
+
.command("serve")
|
|
198
|
+
.description("Start the HTTP API (see docs/API.md)")
|
|
199
|
+
.option("--host <host>", "Bind host")
|
|
200
|
+
.addOption(new Option("--port <port>", "Bind port").argParser(parsePositiveIntArg("--port")))
|
|
201
|
+
.action(async (opts) => {
|
|
202
|
+
await runServeCommand(opts);
|
|
203
|
+
});
|
|
204
|
+
const history = program.command("history").description("Manage saved sessions");
|
|
205
|
+
history.action(runHistoryList);
|
|
206
|
+
history.command("delete").argument("<id>").description("Delete a saved session").action(runHistoryDelete);
|
|
207
|
+
history
|
|
208
|
+
.command("rename")
|
|
209
|
+
.argument("<id>")
|
|
210
|
+
.argument("<title...>")
|
|
211
|
+
.description("Rename a saved session")
|
|
212
|
+
.action(async (id, title) => {
|
|
213
|
+
await runHistoryRename(id, title);
|
|
214
|
+
});
|
|
215
|
+
history
|
|
216
|
+
.command("export")
|
|
217
|
+
.argument("<id>")
|
|
218
|
+
.option("-o, --output <file>", "Write to a file instead of stdout")
|
|
219
|
+
.description("Export a session as JSON")
|
|
220
|
+
.action(async (id, opts) => {
|
|
221
|
+
await runHistoryExport(id, opts);
|
|
222
|
+
});
|
|
223
|
+
program.command("update").description("Upgrade to the latest npm release").action(runUpdateCommand);
|
|
224
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { existsSync, writeFileSync, mkdirSync } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
function ensureProjectDefaults() {
|
|
4
|
+
const projectConfigDir = path.join(process.cwd(), ".min-agent");
|
|
5
|
+
const projectSkillsDir = path.join(projectConfigDir, "skills");
|
|
6
|
+
const projectMcpFile = path.join(projectConfigDir, "mcp.json");
|
|
7
|
+
if (!existsSync(projectSkillsDir)) {
|
|
8
|
+
mkdirSync(projectSkillsDir, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
if (!existsSync(projectMcpFile)) {
|
|
11
|
+
mkdirSync(projectConfigDir, { recursive: true });
|
|
12
|
+
writeFileSync(projectMcpFile, JSON.stringify({ mcpServers: {} }, null, 2), "utf-8");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export async function runInitCommand() {
|
|
16
|
+
ensureProjectDefaults();
|
|
17
|
+
console.log(`✓ Initialized .min-agent/ in ${process.cwd()}`);
|
|
18
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { loadMcpConfigFile, loadMcpConfigEntries, saveMcpConfig, checkMcpServer, checkAllMcpServers, formatMcpServerBinding, getMcpStatus, listMcpCatalog, } from "../../mcp.js";
|
|
2
|
+
import { CliError } from "../errors.js";
|
|
3
|
+
function parseEnvPairs(pairs) {
|
|
4
|
+
const environment = {};
|
|
5
|
+
for (const kv of pairs ?? []) {
|
|
6
|
+
const eq = kv.indexOf("=");
|
|
7
|
+
if (eq <= 0)
|
|
8
|
+
throw new CliError(`Invalid --env format: ${kv}`, { hint: "use KEY=VALUE" });
|
|
9
|
+
environment[kv.slice(0, eq)] = kv.slice(eq + 1);
|
|
10
|
+
}
|
|
11
|
+
return environment;
|
|
12
|
+
}
|
|
13
|
+
export async function runMcpAdd(name, cmd, opts) {
|
|
14
|
+
const scope = opts.project ? "project" : "global";
|
|
15
|
+
if (opts.sse && opts.streamableHttp) {
|
|
16
|
+
throw new CliError("--sse and --streamable-http are mutually exclusive");
|
|
17
|
+
}
|
|
18
|
+
const usage = "Usage: min-agent mcp add <name> [--project] [--skip-check] <command...>\n" +
|
|
19
|
+
" or: min-agent mcp add <name> --url <https://host/mcp> [--sse|--streamable-http] [--token <bearer>] [--project] [--skip-check]";
|
|
20
|
+
let entry;
|
|
21
|
+
if (opts.url?.trim()) {
|
|
22
|
+
entry = {
|
|
23
|
+
url: opts.url.trim(),
|
|
24
|
+
enabled: true,
|
|
25
|
+
remoteTransport: opts.streamableHttp ? "streamable-http" : opts.sse ? "sse" : "auto",
|
|
26
|
+
...(opts.timeout ? { timeout: opts.timeout } : {}),
|
|
27
|
+
};
|
|
28
|
+
if (opts.token?.trim())
|
|
29
|
+
entry.token = opts.token.trim();
|
|
30
|
+
}
|
|
31
|
+
else if (cmd.length > 0) {
|
|
32
|
+
entry = { command: cmd, enabled: true, ...(opts.timeout ? { timeout: opts.timeout } : {}) };
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
throw new CliError(usage);
|
|
36
|
+
}
|
|
37
|
+
const environment = parseEnvPairs(opts.env);
|
|
38
|
+
if (Object.keys(environment).length > 0)
|
|
39
|
+
entry.environment = environment;
|
|
40
|
+
let detectedTools = 0;
|
|
41
|
+
if (!opts.skipCheck) {
|
|
42
|
+
console.log(`Validating MCP server "${name}"...`);
|
|
43
|
+
const checkResult = await checkMcpServer(name, entry);
|
|
44
|
+
if (!checkResult.ok) {
|
|
45
|
+
throw new CliError(`MCP server "${name}" validation failed: ${checkResult.error ?? "unknown error"}`, {
|
|
46
|
+
exitCode: 2,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
detectedTools = checkResult.toolCount;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.log(`Skipping MCP validation for "${name}" (--skip-check).`);
|
|
53
|
+
}
|
|
54
|
+
const config = loadMcpConfigFile(scope);
|
|
55
|
+
config.mcpServers[name] = entry;
|
|
56
|
+
saveMcpConfig(config, scope);
|
|
57
|
+
const toolsSuffix = opts.skipCheck ? "" : ` (${detectedTools} tools detected)`;
|
|
58
|
+
const scopeSuffix = opts.project ? " [project]" : "";
|
|
59
|
+
console.log(`✓ MCP server "${name}" added${scopeSuffix}: ${formatMcpServerBinding(entry)}${toolsSuffix}`);
|
|
60
|
+
}
|
|
61
|
+
export async function runMcpRemove(name, opts) {
|
|
62
|
+
const scope = opts.project ? "project" : "global";
|
|
63
|
+
const config = loadMcpConfigFile(scope);
|
|
64
|
+
if (!config.mcpServers[name]) {
|
|
65
|
+
const elsewhere = loadMcpConfigEntries().find((e) => e.name === name);
|
|
66
|
+
throw new CliError(elsewhere
|
|
67
|
+
? `MCP server "${name}" is defined in ${elsewhere.scope} scope, not ${scope}.`
|
|
68
|
+
: `MCP server "${name}" not found`);
|
|
69
|
+
}
|
|
70
|
+
delete config.mcpServers[name];
|
|
71
|
+
saveMcpConfig(config, scope);
|
|
72
|
+
console.log(`✓ MCP server "${name}" removed${opts.project ? " [project]" : ""}`);
|
|
73
|
+
}
|
|
74
|
+
export async function runMcpList() {
|
|
75
|
+
const entries = loadMcpConfigEntries();
|
|
76
|
+
if (entries.length === 0) {
|
|
77
|
+
console.log("No MCP servers configured.");
|
|
78
|
+
console.log("Add one with: min-agent mcp add <name> <command...>");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
console.log("MCP Servers:");
|
|
82
|
+
for (const { name, config, scope } of entries) {
|
|
83
|
+
const status = config.enabled === false ? "\x1b[90mdisabled\x1b[0m" : "\x1b[32menabled\x1b[0m";
|
|
84
|
+
const scopeMark = scope === "project" ? " \x1b[90m[project]\x1b[0m" : "";
|
|
85
|
+
console.log(` ${name} ${status}${scopeMark}`);
|
|
86
|
+
}
|
|
87
|
+
console.log("\nUse: min-agent mcp info <name> for details");
|
|
88
|
+
}
|
|
89
|
+
export async function runMcpInfo(name) {
|
|
90
|
+
const found = loadMcpConfigEntries().find((e) => e.name === name);
|
|
91
|
+
if (!found)
|
|
92
|
+
throw new CliError(`MCP server "${name}" not found`);
|
|
93
|
+
const cfg = found.config;
|
|
94
|
+
console.log(`Name: ${name}`);
|
|
95
|
+
console.log(`Scope: ${found.scope}`);
|
|
96
|
+
console.log(`Status: ${cfg.enabled === false ? "disabled" : "enabled"}`);
|
|
97
|
+
console.log(`Binding: ${formatMcpServerBinding(cfg)}`);
|
|
98
|
+
if (cfg.command)
|
|
99
|
+
console.log(`Command: ${Array.isArray(cfg.command) ? cfg.command.join(" ") : `${cfg.command} ${(cfg.args ?? []).join(" ")}`.trim()}`);
|
|
100
|
+
if (cfg.url) {
|
|
101
|
+
console.log(`URL: ${cfg.url}`);
|
|
102
|
+
if (cfg.remoteTransport)
|
|
103
|
+
console.log(`Transport: ${cfg.remoteTransport}`);
|
|
104
|
+
if (cfg.oauth === false)
|
|
105
|
+
console.log(`OAuth: disabled`);
|
|
106
|
+
else if (cfg.oauth)
|
|
107
|
+
console.log(`OAuth: enabled${cfg.oauth.clientId ? ` (clientId set)` : ""}`);
|
|
108
|
+
}
|
|
109
|
+
if (cfg.headers)
|
|
110
|
+
console.log(`Headers: ${Object.keys(cfg.headers).join(", ")} (values hidden)`);
|
|
111
|
+
if (cfg.token)
|
|
112
|
+
console.log(`Token: ***`);
|
|
113
|
+
if (cfg.environment)
|
|
114
|
+
console.log(`Env: ${Object.keys(cfg.environment).join(", ")}`);
|
|
115
|
+
if (cfg.timeout)
|
|
116
|
+
console.log(`Timeout: ${cfg.timeout}ms`);
|
|
117
|
+
if (cfg.connectTimeout)
|
|
118
|
+
console.log(`ConnectTimeout: ${cfg.connectTimeout}ms`);
|
|
119
|
+
if (cfg.callTimeout)
|
|
120
|
+
console.log(`CallTimeout: ${cfg.callTimeout}ms`);
|
|
121
|
+
const st = getMcpStatus()[name];
|
|
122
|
+
if (st?.connected) {
|
|
123
|
+
const catalog = listMcpCatalog(name).servers[0];
|
|
124
|
+
if (catalog) {
|
|
125
|
+
console.log(`Tools: ${st.tools.join(", ") || "(none)"}`);
|
|
126
|
+
console.log(`Resources: ${catalog.resources.length === 0 ? "(none)" : catalog.resources.map((r) => r.uri).join(", ")}`);
|
|
127
|
+
console.log(`Prompts: ${catalog.prompts.length === 0 ? "(none)" : catalog.prompts.map((p) => p.name).join(", ")}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export async function runMcpCheck() {
|
|
132
|
+
const results = await checkAllMcpServers();
|
|
133
|
+
if (results.length === 0) {
|
|
134
|
+
console.log("No MCP servers configured.");
|
|
135
|
+
console.log("Add one with: min-agent mcp add <name> <command...> or --url <https://...>");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
let failed = 0;
|
|
139
|
+
console.log("MCP Check Results:");
|
|
140
|
+
for (const result of results) {
|
|
141
|
+
if (!result.enabled) {
|
|
142
|
+
console.log(` ${result.name}: skipped (disabled)`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (result.ok) {
|
|
146
|
+
console.log(` ${result.name}: ok (${result.toolCount} tools)`);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
failed++;
|
|
150
|
+
console.log(` ${result.name}: failed${result.error ? ` - ${result.error}` : ""}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (failed > 0) {
|
|
154
|
+
throw new CliError(`${failed} MCP server(s) failed.`, { exitCode: 1 });
|
|
155
|
+
}
|
|
156
|
+
console.log("\nAll enabled MCP servers are available.");
|
|
157
|
+
}
|
|
158
|
+
export async function runMcpToggle(names, enabled, opts) {
|
|
159
|
+
const scope = opts.project ? "project" : "global";
|
|
160
|
+
const verb = enabled ? "enable" : "disable";
|
|
161
|
+
if (names.length === 0)
|
|
162
|
+
throw new CliError(`Usage: min-agent mcp ${verb} <name...> [--project]`);
|
|
163
|
+
const config = loadMcpConfigFile(scope);
|
|
164
|
+
for (const name of names) {
|
|
165
|
+
if (!config.mcpServers[name]) {
|
|
166
|
+
console.error(`MCP server "${name}" not found${opts.project ? " in project scope" : ""}`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
config.mcpServers[name].enabled = enabled;
|
|
170
|
+
console.log(`✓ MCP server "${name}" ${verb}d${opts.project ? " [project]" : ""}`);
|
|
171
|
+
}
|
|
172
|
+
saveMcpConfig(config, scope);
|
|
173
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { loadMemories, addMemory, deleteMemory, searchMemories, defaultMemoryScope, } from "../../memory.js";
|
|
2
|
+
import { CliError } from "../errors.js";
|
|
3
|
+
function printList(which) {
|
|
4
|
+
const memories = loadMemories(which);
|
|
5
|
+
const label = which === "project" ? "Project memories" : "Global memories";
|
|
6
|
+
if (memories.length === 0) {
|
|
7
|
+
console.log(`${label}: none`);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
console.log(`${label} (${memories.length}):`);
|
|
11
|
+
for (let i = 0; i < memories.length; i++) {
|
|
12
|
+
const m = memories[i];
|
|
13
|
+
const tags = m.tags.length > 0 ? ` [${m.tags.join(", ")}]` : "";
|
|
14
|
+
const date = m.created.split("T")[0];
|
|
15
|
+
console.log(` #${i + 1}: ${m.content}${tags} (${date})`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function runMemoryList(scope) {
|
|
19
|
+
if (!scope && loadMemories("project").length === 0 && loadMemories("global").length === 0) {
|
|
20
|
+
console.log("No memories stored.");
|
|
21
|
+
console.log("The agent will automatically save memories during conversations.");
|
|
22
|
+
console.log('Or add manually: min-agent memory add "prefer TypeScript over JavaScript"');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const scopes = scope ? [scope] : ["project", "global"];
|
|
26
|
+
for (const s of scopes)
|
|
27
|
+
printList(s);
|
|
28
|
+
}
|
|
29
|
+
export async function runMemoryAdd(textTokens, scope) {
|
|
30
|
+
const text = textTokens.join(" ");
|
|
31
|
+
if (!text)
|
|
32
|
+
throw new CliError("Usage: min-agent memory add <text> [--project|--global]");
|
|
33
|
+
const resolved = scope ?? defaultMemoryScope();
|
|
34
|
+
addMemory(text, [], resolved);
|
|
35
|
+
console.log(`✓ ${resolved === "project" ? "Project" : "Global"} memory saved: "${text}"`);
|
|
36
|
+
}
|
|
37
|
+
export async function runMemorySearch(queryTokens, scope) {
|
|
38
|
+
const query = queryTokens.join(" ");
|
|
39
|
+
if (!query)
|
|
40
|
+
throw new CliError("Usage: min-agent memory search <query> [--project|--global]");
|
|
41
|
+
const scopes = scope ? [scope] : ["project", "global"];
|
|
42
|
+
const lines = [];
|
|
43
|
+
for (const s of scopes) {
|
|
44
|
+
for (const m of searchMemories(query, s)) {
|
|
45
|
+
const tags = m.tags.length > 0 ? ` [${m.tags.join(", ")}]` : "";
|
|
46
|
+
lines.push(` [${s}] #${m.index + 1}: ${m.content}${tags}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (lines.length === 0) {
|
|
50
|
+
console.log(`No memories matching "${query}"`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
console.log(`Found ${lines.length} memory(s):`);
|
|
54
|
+
for (const line of lines)
|
|
55
|
+
console.log(line);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export async function runMemoryDelete(indexArg, scope) {
|
|
59
|
+
const idx = Number.parseInt(indexArg, 10);
|
|
60
|
+
if (Number.isNaN(idx))
|
|
61
|
+
throw new CliError("Usage: min-agent memory delete <number> [--project|--global]");
|
|
62
|
+
const resolved = scope ?? defaultMemoryScope();
|
|
63
|
+
if (deleteMemory(idx - 1, resolved)) {
|
|
64
|
+
console.log(`✓ ${resolved === "project" ? "Project" : "Global"} memory #${idx} deleted`);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
throw new CliError(`${resolved === "project" ? "Project" : "Global"} memory #${idx} not found`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { loadConfig, fetchModels, getActiveProvider } from "../../config.js";
|
|
2
|
+
import { CliError } from "../errors.js";
|
|
3
|
+
export async function runModelsCommand(opts) {
|
|
4
|
+
const config = loadConfig();
|
|
5
|
+
const provider = opts.provider ? config.providers?.find((p) => p.name === opts.provider) : getActiveProvider(config);
|
|
6
|
+
if (!provider?.baseURL || !provider?.apiKey) {
|
|
7
|
+
throw new CliError("Not configured.", { hint: "Run: min-agent setup" });
|
|
8
|
+
}
|
|
9
|
+
console.log(`Fetching models (${provider.name ?? "default"})...`);
|
|
10
|
+
const models = await fetchModels(provider.baseURL, provider.apiKey);
|
|
11
|
+
if (models.length === 0) {
|
|
12
|
+
console.log("No models found or unable to fetch model list.");
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
console.log(`\nAvailable models (${models.length}):`);
|
|
16
|
+
for (const m of models) {
|
|
17
|
+
const marker = m === provider.defaultModel ? " ← default" : "";
|
|
18
|
+
console.log(` ${m}${marker}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { runPermissionCli } from "../../permission-cli.js";
|
|
2
|
+
export async function runPermissionCommand(args, opts, parentOpts) {
|
|
3
|
+
const scope = opts.project ? "project" : opts.global ? "global" : undefined;
|
|
4
|
+
const result = runPermissionCli({
|
|
5
|
+
positionals: args,
|
|
6
|
+
flagMode: parentOpts.permission,
|
|
7
|
+
scope,
|
|
8
|
+
});
|
|
9
|
+
console.log(result.lines.join("\n"));
|
|
10
|
+
if (!result.ok)
|
|
11
|
+
process.exitCode = 1;
|
|
12
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { existsSync, writeFileSync, mkdirSync } from "fs";
|
|
2
|
+
import { getConfigDir, getRulesFile } from "../../config.js";
|
|
3
|
+
export async function runRulesShow() {
|
|
4
|
+
const { loadInstructions } = await import("../../instructions.js");
|
|
5
|
+
const instructions = await loadInstructions();
|
|
6
|
+
if (instructions.length === 0) {
|
|
7
|
+
console.log("No instruction rules loaded.");
|
|
8
|
+
console.log("");
|
|
9
|
+
console.log("Add rules by creating:");
|
|
10
|
+
console.log(` Global: ${getRulesFile()}`);
|
|
11
|
+
console.log(" Project: ./AGENTS.md or ./RULES.md");
|
|
12
|
+
console.log("");
|
|
13
|
+
console.log("Or add paths/URLs in ~/.min-agent/config.json:");
|
|
14
|
+
console.log(' { "instructions": ["./docs/rules.md", "https://..."] }');
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
console.log(`Loaded ${instructions.length} instruction source(s):\n`);
|
|
18
|
+
for (const inst of instructions) {
|
|
19
|
+
const firstLine = inst.split("\n")[0];
|
|
20
|
+
console.log(` ${firstLine}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export async function runRulesEdit() {
|
|
25
|
+
const rulesFile = getRulesFile();
|
|
26
|
+
if (!existsSync(rulesFile)) {
|
|
27
|
+
mkdirSync(getConfigDir(), { recursive: true });
|
|
28
|
+
writeFileSync(rulesFile, `# Global Agent Rules\n\n<!-- Add your custom instructions here. They will be included in every conversation. -->\n`, "utf-8");
|
|
29
|
+
}
|
|
30
|
+
const editor = process.env.EDITOR || "vi";
|
|
31
|
+
const { execSync } = await import("child_process");
|
|
32
|
+
execSync(`${editor} "${rulesFile}"`, { stdio: "inherit" });
|
|
33
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { runSandboxCli } from "../../sandbox-cli.js";
|
|
2
|
+
export async function runSandboxCommand(args, opts, parentOpts) {
|
|
3
|
+
const scope = opts.project ? "project" : opts.global ? "global" : undefined;
|
|
4
|
+
const result = runSandboxCli({
|
|
5
|
+
positionals: args,
|
|
6
|
+
flagMode: parentOpts.sandbox,
|
|
7
|
+
flagNetwork: parentOpts.network ?? opts.network,
|
|
8
|
+
scope,
|
|
9
|
+
});
|
|
10
|
+
console.log(result.lines.join("\n"));
|
|
11
|
+
if (!result.ok)
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
}
|