min-agent 0.5.0 → 0.6.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.
Files changed (156) hide show
  1. package/README.md +32 -18
  2. package/dist/cli.js +538 -12
  3. package/docs/API.md +25 -8
  4. package/package.json +6 -6
  5. package/skills/self-config/SKILL.md +7 -3
  6. package/skills/self-config/reference.md +8 -4
  7. package/dist/agent.js +0 -1342
  8. package/dist/assistant-stream.js +0 -131
  9. package/dist/cli/commands/chat.js +0 -13
  10. package/dist/cli/commands/exec.js +0 -35
  11. package/dist/cli/commands/history.js +0 -58
  12. package/dist/cli/commands/index.js +0 -241
  13. package/dist/cli/commands/init.js +0 -18
  14. package/dist/cli/commands/mcp.js +0 -173
  15. package/dist/cli/commands/memory.js +0 -87
  16. package/dist/cli/commands/models.js +0 -21
  17. package/dist/cli/commands/permission.js +0 -12
  18. package/dist/cli/commands/rules.js +0 -33
  19. package/dist/cli/commands/sandbox.js +0 -13
  20. package/dist/cli/commands/serve.js +0 -9
  21. package/dist/cli/commands/setup.js +0 -4
  22. package/dist/cli/commands/shared.js +0 -16
  23. package/dist/cli/commands/skills.js +0 -119
  24. package/dist/cli/commands/think.js +0 -12
  25. package/dist/cli/commands/update.js +0 -7
  26. package/dist/cli/commands/write-config.js +0 -52
  27. package/dist/cli/errors.js +0 -36
  28. package/dist/cli/exec-prompt.js +0 -26
  29. package/dist/cli/option-helpers.js +0 -65
  30. package/dist/cli/program.js +0 -217
  31. package/dist/clipboard.js +0 -142
  32. package/dist/code-mode.js +0 -184
  33. package/dist/compaction.js +0 -644
  34. package/dist/config.js +0 -470
  35. package/dist/confirm.js +0 -172
  36. package/dist/context-window.js +0 -246
  37. package/dist/doom-loop.js +0 -278
  38. package/dist/fetch-timeout.js +0 -152
  39. package/dist/http-approvals.js +0 -60
  40. package/dist/http.js +0 -119
  41. package/dist/instructions.js +0 -196
  42. package/dist/logger.js +0 -95
  43. package/dist/markdown.js +0 -259
  44. package/dist/mcp.js +0 -1036
  45. package/dist/memory-cli.js +0 -33
  46. package/dist/memory.js +0 -295
  47. package/dist/model-catalog.js +0 -285
  48. package/dist/output.js +0 -76
  49. package/dist/paste-handler.js +0 -41
  50. package/dist/permission-cli.js +0 -40
  51. package/dist/plugins.js +0 -131
  52. package/dist/pricing.js +0 -119
  53. package/dist/provider.js +0 -63
  54. package/dist/question-format.js +0 -60
  55. package/dist/reasoning-stream.js +0 -158
  56. package/dist/sandbox-cli.js +0 -79
  57. package/dist/sandbox.js +0 -403
  58. package/dist/save-throttle.js +0 -45
  59. package/dist/scope.js +0 -23
  60. package/dist/serve/common.js +0 -425
  61. package/dist/serve/routes-chat.js +0 -367
  62. package/dist/serve/routes-mcp.js +0 -212
  63. package/dist/serve/routes-memory.js +0 -95
  64. package/dist/serve/routes-meta.js +0 -233
  65. package/dist/serve/routes-sessions.js +0 -61
  66. package/dist/serve/routes-skills.js +0 -70
  67. package/dist/serve.js +0 -138
  68. package/dist/sessions.js +0 -265
  69. package/dist/skills.js +0 -599
  70. package/dist/structured-output.js +0 -29
  71. package/dist/synthetic.js +0 -7
  72. package/dist/think-cli.js +0 -36
  73. package/dist/thinking-wire.js +0 -228
  74. package/dist/thinking.js +0 -142
  75. package/dist/title-gen.js +0 -55
  76. package/dist/token-display.js +0 -39
  77. package/dist/tool-display.js +0 -178
  78. package/dist/tool-output.js +0 -126
  79. package/dist/tools/apply_patch.js +0 -265
  80. package/dist/tools/atomic-file.js +0 -35
  81. package/dist/tools/backend.js +0 -61
  82. package/dist/tools/bash.js +0 -231
  83. package/dist/tools/code_search.js +0 -98
  84. package/dist/tools/edit.js +0 -68
  85. package/dist/tools/explore.js +0 -232
  86. package/dist/tools/glob.js +0 -40
  87. package/dist/tools/grep.js +0 -174
  88. package/dist/tools/index.js +0 -34
  89. package/dist/tools/question.js +0 -54
  90. package/dist/tools/read.js +0 -109
  91. package/dist/tools/search-searxng.js +0 -223
  92. package/dist/tools/search-serper.js +0 -189
  93. package/dist/tools/task.js +0 -165
  94. package/dist/tools/todo.js +0 -213
  95. package/dist/tools/web_fetch.js +0 -195
  96. package/dist/tools/web_search.js +0 -228
  97. package/dist/tools/write.js +0 -55
  98. package/dist/tui/App.js +0 -126
  99. package/dist/tui/ConfirmBar.js +0 -66
  100. package/dist/tui/InputBar.js +0 -616
  101. package/dist/tui/MessageList.js +0 -718
  102. package/dist/tui/ModelPicker.js +0 -113
  103. package/dist/tui/QuestionBar.js +0 -136
  104. package/dist/tui/SessionPicker.js +0 -79
  105. package/dist/tui/Spinner.js +0 -6
  106. package/dist/tui/StatusBar.js +0 -22
  107. package/dist/tui/ThinkPicker.js +0 -77
  108. package/dist/tui/agent-runner.js +0 -223
  109. package/dist/tui/bracketed-paste.js +0 -37
  110. package/dist/tui/caret-pos.js +0 -179
  111. package/dist/tui/caret.js +0 -69
  112. package/dist/tui/click-count.js +0 -13
  113. package/dist/tui/diff-view.js +0 -61
  114. package/dist/tui/drag-state.js +0 -49
  115. package/dist/tui/hydrate.js +0 -129
  116. package/dist/tui/index.js +0 -258
  117. package/dist/tui/input-history.js +0 -125
  118. package/dist/tui/layout.js +0 -105
  119. package/dist/tui/mouse.js +0 -46
  120. package/dist/tui/overlay-input.js +0 -12
  121. package/dist/tui/paste-draft.js +0 -173
  122. package/dist/tui/prompt-queue.js +0 -24
  123. package/dist/tui/selection.js +0 -232
  124. package/dist/tui/session-switch.js +0 -28
  125. package/dist/tui/slash-commands.js +0 -123
  126. package/dist/tui/slash-handler.js +0 -589
  127. package/dist/tui/text-width.js +0 -113
  128. package/dist/tui/theme.js +0 -12
  129. package/dist/tui/token-info.js +0 -7
  130. package/dist/tui/tool-children.js +0 -19
  131. package/dist/tui/types.js +0 -1
  132. package/dist/tui/undo-stack.js +0 -14
  133. package/dist/tui/use-sgr-mouse.js +0 -29
  134. package/dist/tui-chat.js +0 -456
  135. package/dist/updater.js +0 -116
  136. package/dist/xml-search.js +0 -194
  137. package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +0 -1510
  138. package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +0 -2105
  139. package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +0 -1595
  140. package/docs/superpowers/plans/2026-08-16-input-caret.md +0 -782
  141. package/docs/superpowers/plans/2026-08-20-tui-completeness.md +0 -873
  142. package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +0 -631
  143. package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +0 -475
  144. package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +0 -450
  145. package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +0 -183
  146. package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +0 -220
  147. package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +0 -196
  148. package/docs/superpowers/specs/2026-08-16-input-caret-design.md +0 -63
  149. package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +0 -116
  150. package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +0 -47
  151. package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +0 -37
  152. package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +0 -68
  153. package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +0 -273
  154. package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +0 -165
  155. package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +0 -174
  156. package/docs/superpowers/specs/2026-08-23-thinking-wire-profile-design.md +0 -140
@@ -1,131 +0,0 @@
1
- /**
2
- * Split "thinking" fragments (inline XML-style blocks some models leak into text)
3
- * from displayable assistant text. Thinking is emitted to stderr; display goes to Markdown.
4
- */
5
- const BT = String.fromCharCode(96); // backtick — some models wrap thinking in `think` / `redacted_thinking`
6
- const THINKING_BLOCKS = [
7
- { open: "<think>", close: "</think>" },
8
- { open: "<thinking>", close: "</thinking>" },
9
- { open: `<${BT}think${BT}>`, close: `<${BT}/think${BT}>` },
10
- { open: `<${BT}redacted_thinking${BT}>`, close: `<${BT}/redacted_thinking${BT}>` },
11
- ];
12
- /** Max tail to keep when looking for a partial opening tag. */
13
- const PARTIAL_TAG_HOLD = 72;
14
- /**
15
- * If an opening tag never gets closed (truncated / misbehaving model), the
16
- * buffer would grow without bound. Beyond this size, treat the remainder as
17
- * thinking text and move on.
18
- */
19
- const UNCLOSED_THINK_MAX = 16 * 1024;
20
- function lower(s) {
21
- return s.toLowerCase();
22
- }
23
- function findFirstOpen(buf) {
24
- const l = lower(buf);
25
- let best = null;
26
- for (const tag of THINKING_BLOCKS) {
27
- const i = l.indexOf(tag.open);
28
- if (i >= 0 && (!best || i < best.index))
29
- best = { index: i, tag };
30
- }
31
- return best;
32
- }
33
- /**
34
- * Incremental filter: feed raw model text deltas; get display text (for stdout / history)
35
- * and thinking text (for stderr). Handles tags split across chunks.
36
- */
37
- export class ThinkingBodySplitter {
38
- buf = "";
39
- feed(chunk) {
40
- this.buf += chunk;
41
- return this.drain(false);
42
- }
43
- /** End of stream: flush remainder; incomplete thinking block → stderr only. */
44
- flush() {
45
- return this.drain(true);
46
- }
47
- drain(isFinal) {
48
- let display = "";
49
- let thinking = "";
50
- let hadThinking = false;
51
- while (this.buf.length > 0) {
52
- const open = findFirstOpen(this.buf);
53
- if (!open) {
54
- if (isFinal) {
55
- display += this.buf;
56
- this.buf = "";
57
- }
58
- else {
59
- const holdFrom = lastPotentialPartialOpen(this.buf);
60
- if (holdFrom >= 0) {
61
- display += this.buf.slice(0, holdFrom);
62
- this.buf = this.buf.slice(holdFrom);
63
- }
64
- else {
65
- display += this.buf;
66
- this.buf = "";
67
- }
68
- }
69
- break;
70
- }
71
- if (open.index > 0) {
72
- // Strip trailing newlines before thinking block
73
- let pre = this.buf.slice(0, open.index);
74
- pre = pre.replace(/\n+$/, "");
75
- if (pre)
76
- display += pre;
77
- this.buf = this.buf.slice(open.index);
78
- }
79
- const low = lower(this.buf);
80
- const afterOpen = open.tag.open.length;
81
- const closeRel = low.indexOf(open.tag.close, afterOpen);
82
- if (closeRel < 0) {
83
- if (isFinal) {
84
- thinking += this.buf.slice(afterOpen);
85
- this.buf = "";
86
- }
87
- else if (this.buf.length - afterOpen > UNCLOSED_THINK_MAX) {
88
- thinking += this.buf.slice(afterOpen);
89
- this.buf = "";
90
- hadThinking = true;
91
- }
92
- break;
93
- }
94
- const inner = this.buf.slice(afterOpen, closeRel);
95
- thinking += inner;
96
- hadThinking = true;
97
- this.buf = this.buf.slice(closeRel + open.tag.close.length);
98
- }
99
- // Strip leading newlines from display that follow a thinking block
100
- if (hadThinking) {
101
- display = display.replace(/^\n+/, "");
102
- }
103
- return { display, thinking };
104
- }
105
- }
106
- /** If `buf` ends with `<...` that could still become a thinking open tag, return index of `<` to hold from; else -1. */
107
- function lastPotentialPartialOpen(buf) {
108
- const start = Math.max(0, buf.length - PARTIAL_TAG_HOLD);
109
- const tail = buf.slice(start);
110
- const lt = tail.lastIndexOf("<");
111
- if (lt < 0)
112
- return -1;
113
- const globalLt = start + lt;
114
- const cand = buf.slice(globalLt).toLowerCase();
115
- if (cand.length > 64)
116
- return -1;
117
- for (const tag of THINKING_BLOCKS) {
118
- if (tag.open.startsWith(cand))
119
- return globalLt;
120
- }
121
- return -1;
122
- }
123
- /** Final strip for assistant message (complete tags only). */
124
- export function stripThinkingFromAssistantText(text) {
125
- let s = text;
126
- for (const { open, close } of THINKING_BLOCKS) {
127
- const re = new RegExp(open.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "[\\s\\S]*?" + close.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
128
- s = s.replace(re, "");
129
- }
130
- return s;
131
- }
@@ -1,13 +0,0 @@
1
- import { startTuiSession } from "./shared.js";
2
- import { applySessionOverrides, applySessionRuntimeOverrides } from "../program.js";
3
- export async function runChat(promptTokens, opts) {
4
- applySessionOverrides(opts);
5
- await applySessionRuntimeOverrides(opts);
6
- await startTuiSession({
7
- model: opts.model,
8
- provider: opts.provider,
9
- resume: opts.resume,
10
- images: opts.image,
11
- positionals: promptTokens,
12
- });
13
- }
@@ -1,35 +0,0 @@
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
- import { applySessionOverrides, applySessionRuntimeOverrides } from "../program.js";
6
- export async function runExec(messageTokens, opts) {
7
- if (!isConfigured()) {
8
- throw new CliError("Not configured.", { hint: "Run: min-agent setup" });
9
- }
10
- applySessionOverrides(opts);
11
- await applySessionRuntimeOverrides(opts);
12
- const stdinIsTty = Boolean(process.stdin.isTTY);
13
- const joined = messageTokens.join(" ");
14
- const shouldReadStdin = joined === "-" || !stdinIsTty;
15
- let stdinText = "";
16
- if (shouldReadStdin) {
17
- try {
18
- const chunks = [];
19
- for await (const chunk of process.stdin) {
20
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
21
- }
22
- stdinText = Buffer.concat(chunks).toString("utf-8");
23
- }
24
- catch {
25
- throw new CliError("Failed to read stdin");
26
- }
27
- }
28
- const resolved = resolveExecPrompt(messageTokens, stdinText, stdinIsTty);
29
- if (!resolved.ok) {
30
- throw new CliError(resolved.error);
31
- }
32
- const failed = await runAgent(resolved.prompt, opts.model, opts.image.length > 0 ? opts.image : undefined, opts.provider, opts.resume);
33
- if (failed)
34
- process.exitCode = 1;
35
- }
@@ -1,58 +0,0 @@
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
- }
@@ -1,241 +0,0 @@
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 { runThinkCommand } from "./think.js";
11
- import { runInitCommand } from "./init.js";
12
- import { runRulesShow, runRulesEdit } from "./rules.js";
13
- import { runUpdateCommand } from "./update.js";
14
- import { runHistoryList, runHistoryDelete, runHistoryRename, runHistoryExport } from "./history.js";
15
- import { runMemoryList, runMemoryAdd, runMemorySearch, runMemoryDelete, runMemoryMode } from "./memory.js";
16
- import { runMcpAdd, runMcpRemove, runMcpList, runMcpInfo, runMcpCheck, runMcpToggle, } from "./mcp.js";
17
- import { runSkillsList, runSkillsInfo, runSkillsToggle, runSkillsNew } from "./skills.js";
18
- /**
19
- * All `--project`/`--global`/`--model`/`--provider`/`--resume`/`--image`/`--sandbox`/
20
- * `--network`/`--permission`/`--think`/`--memory` flags live on the root `program` (see addSessionOptions),
21
- * so they can appear before or after a subcommand name. Subcommands read them via
22
- * `program.opts<SessionOptions>()` instead of re-declaring the same options — commander
23
- * resolves options against the first command in the chain that declares them, so a
24
- * subcommand-local redeclaration would silently shadow the root option instead of merging.
25
- */
26
- export function registerCommands(program) {
27
- const rootOpts = () => program.opts();
28
- // --- Session aliases -----------------------------------------------------
29
- for (const alias of ["chat", "code"]) {
30
- program
31
- .command(alias)
32
- .description("Same as interactive session (compat)")
33
- .argument("[prompt...]", "Prompt to send")
34
- .allowUnknownOption()
35
- .action(async (prompt) => {
36
- await runChat(prompt, rootOpts());
37
- });
38
- }
39
- program
40
- .command("exec")
41
- .description("Run one message non-interactively")
42
- .argument("[message...]", 'Message to send (use "-" to read from stdin)')
43
- .allowUnknownOption()
44
- .action(async (message) => {
45
- await runExec(message, rootOpts());
46
- });
47
- // --- Setup & config ----------------------------------------------------
48
- program.command("setup").description("Configure providers (interactively)").action(runSetupCommand);
49
- program.command("init").description("Initialize .min-agent/ in current directory").action(runInitCommand);
50
- program
51
- .command("models")
52
- .description("List available models")
53
- .action(async () => {
54
- await runModelsCommand(rootOpts());
55
- });
56
- const rules = program.command("rules").description("Show or edit instruction rules");
57
- rules.action(runRulesShow);
58
- rules.command("edit").description("Edit the global rules file").action(runRulesEdit);
59
- const memory = program.command("memory").description("Show or set memory, and manage saved memories");
60
- memory.argument("[state]", "on or off");
61
- memory.action(async (state) => {
62
- const opts = rootOpts();
63
- const scope = opts.project ? "project" : opts.global ? "global" : undefined;
64
- if (await runMemoryMode(state, scope, opts.memory))
65
- return;
66
- if (state && state !== "list") {
67
- const { CliError } = await import("../errors.js");
68
- throw new CliError("Usage: min-agent memory [on|off|list|add|search|delete] [--project|--global]");
69
- }
70
- await runMemoryList(scope);
71
- });
72
- memory
73
- .command("add")
74
- .argument("<text...>")
75
- .description("Add a memory")
76
- .action(async (text) => {
77
- const opts = rootOpts();
78
- await runMemoryAdd(text, opts.project ? "project" : opts.global ? "global" : undefined);
79
- });
80
- memory
81
- .command("search")
82
- .argument("<query...>")
83
- .description("Search memories")
84
- .action(async (query) => {
85
- const opts = rootOpts();
86
- await runMemorySearch(query, opts.project ? "project" : opts.global ? "global" : undefined);
87
- });
88
- memory
89
- .command("delete")
90
- .argument("<index>")
91
- .description("Delete a memory by index")
92
- .action(async (index) => {
93
- const opts = rootOpts();
94
- await runMemoryDelete(index, opts.project ? "project" : opts.global ? "global" : undefined);
95
- });
96
- const sandbox = program
97
- .command("sandbox")
98
- .description("Show or set isolation mode (uses --project/--global)")
99
- .argument("[mode]", "off, workspace, or strict");
100
- sandbox.action(async (mode) => {
101
- const opts = rootOpts();
102
- await runSandboxCommand(mode ? [mode] : [], opts, opts);
103
- });
104
- sandbox
105
- .command("network")
106
- .description("Show or set network policy")
107
- .argument("[policy]", "allow or deny")
108
- .action(async (policy) => {
109
- const opts = rootOpts();
110
- await runSandboxCommand(["network", ...(policy ? [policy] : [])], opts, opts);
111
- });
112
- program
113
- .command("permission")
114
- .description("Show or set confirmation mode (uses --project/--global)")
115
- .argument("[mode]", "ask, accept-edits, or allow-all")
116
- .action(async (mode) => {
117
- const opts = rootOpts();
118
- await runPermissionCommand(mode ? [mode] : [], opts, opts);
119
- });
120
- program
121
- .command("think")
122
- .description("Show or set thinking intensity (uses --project/--global)")
123
- .argument("[level]", "off, low, medium, high, or max")
124
- .action(async (level) => {
125
- const opts = rootOpts();
126
- await runThinkCommand(level ? [level] : [], opts, opts);
127
- });
128
- // --- Integrations --------------------------------------------------------
129
- const mcp = program.command("mcp").description("Manage MCP servers");
130
- mcp
131
- .command("add")
132
- .description("Add a new MCP server (stdio or remote)")
133
- .usage("[options] <name> [command...]")
134
- .argument("<name>")
135
- .argument("[cmd...]", "Command to launch a stdio server (npx, node, ...)")
136
- .option("--url <url>", "Remote server URL")
137
- .option("--sse", "Use SSE transport for the remote server")
138
- .option("--streamable-http", "Use streamable-http transport for the remote server")
139
- .option("--token <token>", "Bearer token for remote servers")
140
- .option("--env <KEY=VALUE>", "Extra env for stdio servers (repeatable)", (v, prev) => [...prev, v], [])
141
- .addOption(new Option("--timeout <ms>", "Connection/call timeout").argParser(parsePositiveIntArg("--timeout")))
142
- .option("--skip-check", "Skip validation before saving")
143
- .addHelpText("after", "\nAll options above must come before <name>. Everything after <name> (including flags meant for the\n" +
144
- "launched command, like `npx -y`) is passed through as-is.\n" +
145
- "Scope: pass --project on `min-agent` before `mcp add` to write to .min-agent/mcp.json.")
146
- .allowUnknownOption()
147
- .passThroughOptions()
148
- .action(async (name, cmd, opts) => {
149
- await runMcpAdd(name, cmd, { ...opts, project: rootOpts().project });
150
- });
151
- mcp
152
- .command("remove")
153
- .argument("<name>")
154
- .description("Remove a server")
155
- .action(async (name) => {
156
- await runMcpRemove(name, { project: rootOpts().project });
157
- });
158
- mcp.command("list").description("List configured MCP servers").action(runMcpList);
159
- mcp.command("info").argument("<name>").description("Show details of a server").action(runMcpInfo);
160
- mcp.command("check").description("Test connectivity of all servers").action(runMcpCheck);
161
- mcp
162
- .command("enable")
163
- .argument("<names...>")
164
- .description("Enable disabled servers (use --project to target project scope)")
165
- .action(async (names) => {
166
- await runMcpToggle(names, true, { project: rootOpts().project });
167
- });
168
- mcp
169
- .command("disable")
170
- .argument("<names...>")
171
- .description("Disable servers (use --project to target project scope)")
172
- .action(async (names) => {
173
- await runMcpToggle(names, false, { project: rootOpts().project });
174
- });
175
- const skills = program.command("skills").description("Manage skills");
176
- skills
177
- .command("list")
178
- .option("--json", "Machine-readable JSON output")
179
- .description("List skills")
180
- .action(async (opts) => {
181
- await runSkillsList(Boolean(opts.json));
182
- });
183
- skills
184
- .command("info")
185
- .argument("<name>")
186
- .option("--json", "Machine-readable JSON output")
187
- .description("Show details of a skill")
188
- .action(async (name, opts) => {
189
- await runSkillsInfo(name, Boolean(opts.json));
190
- });
191
- skills
192
- .command("new")
193
- .argument("<name>")
194
- .description("Create a new skill scaffold (use --global on `min-agent` for ~/.agents/skills/)")
195
- .action(async (name) => {
196
- await runSkillsNew(name, Boolean(rootOpts().global));
197
- });
198
- skills
199
- .command("enable")
200
- .argument("<names...>")
201
- .description("Enable disabled skills (use --project to target this project only)")
202
- .action(async (names) => {
203
- await runSkillsToggle(names, true, resolveScope(rootOpts()));
204
- });
205
- skills
206
- .command("disable")
207
- .argument("<names...>")
208
- .description("Disable skills (use --project to target this project only)")
209
- .action(async (names) => {
210
- await runSkillsToggle(names, false, resolveScope(rootOpts()));
211
- });
212
- // --- Operations -----------------------------------------------------------
213
- program
214
- .command("serve")
215
- .description("Start the HTTP API (see docs/API.md)")
216
- .option("--host <host>", "Bind host")
217
- .addOption(new Option("--port <port>", "Bind port").argParser(parsePositiveIntArg("--port")))
218
- .action(async (opts) => {
219
- await runServeCommand(opts);
220
- });
221
- const history = program.command("history").description("Manage saved sessions");
222
- history.action(runHistoryList);
223
- history.command("delete").argument("<id>").description("Delete a saved session").action(runHistoryDelete);
224
- history
225
- .command("rename")
226
- .argument("<id>")
227
- .argument("<title...>")
228
- .description("Rename a saved session")
229
- .action(async (id, title) => {
230
- await runHistoryRename(id, title);
231
- });
232
- history
233
- .command("export")
234
- .argument("<id>")
235
- .option("-o, --output <file>", "Write to a file instead of stdout")
236
- .description("Export a session as JSON")
237
- .action(async (id, opts) => {
238
- await runHistoryExport(id, opts);
239
- });
240
- program.command("update").description("Upgrade to the latest npm release").action(runUpdateCommand);
241
- }
@@ -1,18 +0,0 @@
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
- }
@@ -1,173 +0,0 @@
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
- }