min-agent 0.5.1 → 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 (169) hide show
  1. package/README.md +18 -18
  2. package/dist/cli.js +538 -12
  3. package/docs/API.md +6 -6
  4. package/package.json +6 -6
  5. package/skills/self-config/SKILL.md +4 -2
  6. package/skills/self-config/reference.md +4 -1
  7. package/dist/agent.js +0 -1388
  8. package/dist/assistant-stream.js +0 -131
  9. package/dist/cli/commands/chat.js +0 -13
  10. package/dist/cli/commands/ctx.js +0 -7
  11. package/dist/cli/commands/exec.js +0 -35
  12. package/dist/cli/commands/history.js +0 -58
  13. package/dist/cli/commands/index.js +0 -249
  14. package/dist/cli/commands/init.js +0 -18
  15. package/dist/cli/commands/mcp.js +0 -173
  16. package/dist/cli/commands/memory.js +0 -87
  17. package/dist/cli/commands/models.js +0 -21
  18. package/dist/cli/commands/permission.js +0 -12
  19. package/dist/cli/commands/rules.js +0 -33
  20. package/dist/cli/commands/sandbox.js +0 -13
  21. package/dist/cli/commands/serve.js +0 -9
  22. package/dist/cli/commands/setup.js +0 -56
  23. package/dist/cli/commands/shared.js +0 -25
  24. package/dist/cli/commands/skills.js +0 -119
  25. package/dist/cli/commands/think.js +0 -12
  26. package/dist/cli/commands/update.js +0 -7
  27. package/dist/cli/commands/write-config.js +0 -52
  28. package/dist/cli/errors.js +0 -36
  29. package/dist/cli/exec-prompt.js +0 -26
  30. package/dist/cli/option-helpers.js +0 -65
  31. package/dist/cli/program.js +0 -223
  32. package/dist/cli/setup/detect.js +0 -17
  33. package/dist/cli/setup/flags.js +0 -12
  34. package/dist/cli/setup/flow.js +0 -108
  35. package/dist/cli/setup/provider-form.js +0 -102
  36. package/dist/cli/setup/ui.js +0 -534
  37. package/dist/clipboard.js +0 -142
  38. package/dist/code-mode.js +0 -184
  39. package/dist/compaction.js +0 -644
  40. package/dist/config.js +0 -363
  41. package/dist/confirm.js +0 -172
  42. package/dist/context-window.js +0 -256
  43. package/dist/ctx-cli.js +0 -30
  44. package/dist/ctx.js +0 -80
  45. package/dist/doom-loop.js +0 -278
  46. package/dist/fetch-timeout.js +0 -152
  47. package/dist/http-approvals.js +0 -60
  48. package/dist/http.js +0 -119
  49. package/dist/instructions.js +0 -196
  50. package/dist/logger.js +0 -95
  51. package/dist/markdown.js +0 -259
  52. package/dist/mcp.js +0 -1036
  53. package/dist/memory-cli.js +0 -33
  54. package/dist/memory.js +0 -295
  55. package/dist/model-catalog.js +0 -285
  56. package/dist/ollama-model.js +0 -234
  57. package/dist/ollama-openai-bridge.js +0 -383
  58. package/dist/output.js +0 -76
  59. package/dist/paste-handler.js +0 -41
  60. package/dist/permission-cli.js +0 -40
  61. package/dist/plugins.js +0 -131
  62. package/dist/pricing.js +0 -119
  63. package/dist/provider.js +0 -63
  64. package/dist/question-format.js +0 -60
  65. package/dist/reasoning-stream.js +0 -158
  66. package/dist/sandbox-cli.js +0 -79
  67. package/dist/sandbox.js +0 -403
  68. package/dist/save-throttle.js +0 -45
  69. package/dist/scope.js +0 -23
  70. package/dist/serve/common.js +0 -425
  71. package/dist/serve/routes-chat.js +0 -367
  72. package/dist/serve/routes-mcp.js +0 -212
  73. package/dist/serve/routes-memory.js +0 -95
  74. package/dist/serve/routes-meta.js +0 -268
  75. package/dist/serve/routes-sessions.js +0 -61
  76. package/dist/serve/routes-skills.js +0 -70
  77. package/dist/serve.js +0 -138
  78. package/dist/sessions.js +0 -265
  79. package/dist/skills.js +0 -599
  80. package/dist/structured-output.js +0 -29
  81. package/dist/synthetic.js +0 -7
  82. package/dist/think-cli.js +0 -36
  83. package/dist/thinking-wire.js +0 -239
  84. package/dist/thinking.js +0 -166
  85. package/dist/title-gen.js +0 -55
  86. package/dist/token-display.js +0 -39
  87. package/dist/tool-display.js +0 -178
  88. package/dist/tool-output.js +0 -126
  89. package/dist/tools/apply_patch.js +0 -265
  90. package/dist/tools/atomic-file.js +0 -35
  91. package/dist/tools/backend.js +0 -61
  92. package/dist/tools/bash.js +0 -231
  93. package/dist/tools/code_search.js +0 -98
  94. package/dist/tools/edit.js +0 -68
  95. package/dist/tools/explore.js +0 -232
  96. package/dist/tools/glob.js +0 -40
  97. package/dist/tools/grep.js +0 -174
  98. package/dist/tools/index.js +0 -34
  99. package/dist/tools/question.js +0 -54
  100. package/dist/tools/read.js +0 -109
  101. package/dist/tools/search-searxng.js +0 -223
  102. package/dist/tools/search-serper.js +0 -189
  103. package/dist/tools/task.js +0 -165
  104. package/dist/tools/todo.js +0 -213
  105. package/dist/tools/web_fetch.js +0 -195
  106. package/dist/tools/web_search.js +0 -228
  107. package/dist/tools/write.js +0 -55
  108. package/dist/tui/App.js +0 -138
  109. package/dist/tui/ConfirmBar.js +0 -66
  110. package/dist/tui/CtxPicker.js +0 -68
  111. package/dist/tui/InputBar.js +0 -618
  112. package/dist/tui/MessageList.js +0 -718
  113. package/dist/tui/ModelPicker.js +0 -113
  114. package/dist/tui/QuestionBar.js +0 -136
  115. package/dist/tui/SessionPicker.js +0 -79
  116. package/dist/tui/Spinner.js +0 -6
  117. package/dist/tui/StatusBar.js +0 -22
  118. package/dist/tui/ThinkPicker.js +0 -75
  119. package/dist/tui/agent-runner.js +0 -223
  120. package/dist/tui/bracketed-paste.js +0 -37
  121. package/dist/tui/caret-pos.js +0 -179
  122. package/dist/tui/caret.js +0 -69
  123. package/dist/tui/click-count.js +0 -13
  124. package/dist/tui/diff-view.js +0 -61
  125. package/dist/tui/drag-state.js +0 -49
  126. package/dist/tui/hydrate.js +0 -129
  127. package/dist/tui/index.js +0 -264
  128. package/dist/tui/input-history.js +0 -125
  129. package/dist/tui/layout.js +0 -105
  130. package/dist/tui/mouse.js +0 -46
  131. package/dist/tui/overlay-input.js +0 -12
  132. package/dist/tui/paste-draft.js +0 -173
  133. package/dist/tui/prompt-queue.js +0 -24
  134. package/dist/tui/selection.js +0 -232
  135. package/dist/tui/session-switch.js +0 -28
  136. package/dist/tui/slash-commands.js +0 -129
  137. package/dist/tui/slash-handler.js +0 -615
  138. package/dist/tui/text-width.js +0 -113
  139. package/dist/tui/theme.js +0 -12
  140. package/dist/tui/token-info.js +0 -7
  141. package/dist/tui/tool-children.js +0 -19
  142. package/dist/tui/types.js +0 -1
  143. package/dist/tui/undo-stack.js +0 -14
  144. package/dist/tui/use-sgr-mouse.js +0 -29
  145. package/dist/tui-chat.js +0 -478
  146. package/dist/updater.js +0 -116
  147. package/dist/xml-search.js +0 -194
  148. package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +0 -1510
  149. package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +0 -2105
  150. package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +0 -1595
  151. package/docs/superpowers/plans/2026-08-16-input-caret.md +0 -782
  152. package/docs/superpowers/plans/2026-08-20-tui-completeness.md +0 -873
  153. package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +0 -631
  154. package/docs/superpowers/plans/2026-08-23-cli-setup.md +0 -501
  155. package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +0 -475
  156. package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +0 -450
  157. package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +0 -183
  158. package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +0 -220
  159. package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +0 -196
  160. package/docs/superpowers/specs/2026-08-16-input-caret-design.md +0 -63
  161. package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +0 -116
  162. package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +0 -47
  163. package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +0 -37
  164. package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +0 -68
  165. package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +0 -273
  166. package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +0 -165
  167. package/docs/superpowers/specs/2026-08-23-cli-setup-design.md +0 -282
  168. package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +0 -174
  169. package/docs/superpowers/specs/2026-08-23-thinking-wire-profile-design.md +0 -140
@@ -1,26 +0,0 @@
1
- /**
2
- * Resolve the final prompt text for `min-agent exec`, combining positional
3
- * arguments with piped stdin according to these rules:
4
- * - `exec -` reads the entire prompt from stdin.
5
- * - `exec` (no args) requires piped stdin (non-TTY); errors otherwise.
6
- * - `exec <msg>` + pipe appends piped stdin after the message.
7
- * - `exec <msg>` uses the message as-is.
8
- */
9
- export function resolveExecPrompt(positionals, stdinText, stdinIsTty) {
10
- const joined = positionals.join(" ");
11
- const piped = stdinText.length > 0;
12
- if (joined === "-") {
13
- const prompt = stdinText;
14
- if (!prompt.trim())
15
- return { ok: false, error: "Usage: min-agent exec <message>" };
16
- return { ok: true, prompt };
17
- }
18
- if (!joined) {
19
- if (stdinIsTty || !piped)
20
- return { ok: false, error: "Usage: min-agent exec <message>" };
21
- return { ok: true, prompt: stdinText };
22
- }
23
- if (piped && !stdinIsTty)
24
- return { ok: true, prompt: `${joined}\n\n${stdinText}` };
25
- return { ok: true, prompt: joined };
26
- }
@@ -1,65 +0,0 @@
1
- import { Option } from "commander";
2
- import { parseNetworkPolicy, parseSandboxMode } from "../sandbox.js";
3
- import { parsePermissionMode, parseThinkingEffort, parseMemoryMode, } from "../config.js";
4
- import { userError } from "./errors.js";
5
- /** Reusable --project / --global mutually exclusive scope selector. */
6
- export function scopeOptions() {
7
- return [
8
- new Option("--project", "Apply to this project only (.min-agent/config.json)").conflicts("global"),
9
- new Option("--global", "Apply to every project (~/.min-agent/config.json)").conflicts("project"),
10
- ];
11
- }
12
- /** Resolve --project/--global into a Scope, given an optional fallback. */
13
- export function resolveScope(opts, fallback = "global") {
14
- if (opts.project)
15
- return "project";
16
- if (opts.global)
17
- return "global";
18
- return fallback;
19
- }
20
- export function parseSandboxModeArg(value) {
21
- const parsed = parseSandboxMode(value);
22
- if (!parsed)
23
- throw userError(`Invalid --sandbox value: "${value}"`, "use one of: off, workspace, strict");
24
- return parsed;
25
- }
26
- export function parseNetworkPolicyArg(value) {
27
- const parsed = parseNetworkPolicy(value);
28
- if (!parsed)
29
- throw userError(`Invalid --network value: "${value}"`, "use one of: allow, deny");
30
- return parsed;
31
- }
32
- export function parsePermissionModeArg(value) {
33
- const parsed = parsePermissionMode(value);
34
- if (!parsed)
35
- throw userError(`Invalid --permission value: "${value}"`, "use one of: ask, accept-edits, allow-all");
36
- return parsed;
37
- }
38
- export function parseThinkingEffortArg(value) {
39
- const parsed = parseThinkingEffort(value);
40
- if (!parsed)
41
- throw userError(`Invalid --think value: "${value}"`, "use one of: off, low, medium, high, max");
42
- return parsed;
43
- }
44
- export function parseMemoryModeArg(value) {
45
- const parsed = parseMemoryMode(value);
46
- if (!parsed)
47
- throw userError(`Invalid --memory value: "${value}"`, "use one of: on, off");
48
- return parsed;
49
- }
50
- export function parsePortArg(value) {
51
- const n = Number.parseInt(value, 10);
52
- if (!Number.isFinite(n) || n <= 0 || n > 65535) {
53
- throw userError(`Invalid --port value: "${value}"`, "use an integer between 1 and 65535");
54
- }
55
- return n;
56
- }
57
- export function parsePositiveIntArg(flagName) {
58
- return (value) => {
59
- const n = Number.parseInt(value, 10);
60
- if (!Number.isFinite(n) || n <= 0) {
61
- throw userError(`Invalid ${flagName} value: "${value}"`, "use a positive integer");
62
- }
63
- return n;
64
- };
65
- }
@@ -1,223 +0,0 @@
1
- import { Command, Option } from "commander";
2
- import { setAutoApprove } from "../confirm.js";
3
- import { CliError, formatCliError, exitCodeFor } from "./errors.js";
4
- import { parseNetworkPolicyArg, parsePermissionModeArg, parseSandboxModeArg, parseThinkingEffortArg, parseMemoryModeArg, } from "./option-helpers.js";
5
- import { localVersion } from "../updater.js";
6
- function collectImage(value, previous) {
7
- return [...previous, value];
8
- }
9
- /** Options shared across the root command and `exec` (session-affecting flags). */
10
- export function addSessionOptions(cmd) {
11
- return cmd
12
- .option("-m, --model <model>", "Override model for this request")
13
- .option("--provider <name>", "Use a specific configured provider")
14
- .option("--resume <id>", "Resume a previous session by id")
15
- .option("-i, --image <path>", "Attach an image (repeatable)", collectImage, [])
16
- .addOption(new Option("--sandbox <mode>", "Isolation mode (off, workspace, strict)").argParser(parseSandboxModeArg))
17
- .addOption(new Option("--network <policy>", "Network policy (allow, deny)").argParser(parseNetworkPolicyArg))
18
- .addOption(new Option("--permission <mode>", "Confirmation mode (ask, accept-edits, allow-all)").argParser(parsePermissionModeArg))
19
- .addOption(new Option("--think <level>", "Thinking intensity (off, low, medium, high, max)").argParser(parseThinkingEffortArg))
20
- .addOption(new Option("--memory <mode>", "Memory across sessions (on, off; default off)").argParser(parseMemoryModeArg))
21
- .option("--project", "Write --permission/--sandbox/--think/--memory to this repo's config")
22
- .option("--global", "Write --permission/--sandbox/--think/--memory to the global config")
23
- .option("-y, --yes", "This session only: same as --permission allow-all");
24
- }
25
- export function buildProgram() {
26
- const program = new Command("min-agent");
27
- program
28
- .description("min-agent - Minimal AI coding agent")
29
- .version(localVersion(), "-v, --version", "Show version number")
30
- .helpOption("-h, --help", "Show help")
31
- .allowUnknownOption()
32
- .enablePositionalOptions()
33
- .configureOutput({
34
- writeErr: (str) => process.stderr.write(str),
35
- outputError: (str) => process.stderr.write(str),
36
- })
37
- .exitOverride();
38
- addSessionOptions(program);
39
- program.argument("[prompt...]", "Prompt to send in an interactive session");
40
- program.addHelpText("after", `
41
- Session:
42
- min-agent Start interactive session
43
- min-agent <message> Start interactive session and send message
44
- min-agent --resume <id> Resume a previous interactive session
45
- min-agent chat | code Same as interactive session (compat)
46
- min-agent exec <message> Run one message non-interactively
47
- min-agent exec --resume <id> <msg> Continue a saved exec session
48
-
49
- Setup & config:
50
- min-agent setup [--type T] Configure providers (interactively, or with flags)
51
- min-agent init Initialize .min-agent/ in current directory
52
- min-agent models List available models
53
- min-agent rules [edit] Show or edit instruction rules
54
- min-agent memory [on|off] Show or set memory (off by default)
55
- min-agent memory <add|search|delete> Manage saved memories
56
- min-agent sandbox [off|workspace|strict] Show or set isolation mode
57
- min-agent sandbox network <allow|deny> Show or set network policy
58
- min-agent permission [ask|accept-edits|allow-all] Show or set confirmation mode
59
- min-agent think [off|low|medium|high|max] Show or set thinking intensity
60
- min-agent ctx [2k|4k|8k|12k|16k|32k|64k|128k|256k|auto] Show or set Ollama context window
61
-
62
- Integrations:
63
- min-agent mcp <list|add|remove|info|check|enable|disable> Manage MCP servers
64
- min-agent skills <list|info|new|enable|disable> Manage skills
65
-
66
- Operations:
67
- min-agent serve [--host H] [--port P] Start the HTTP API (see docs/API.md)
68
- min-agent history [delete|rename|export] Manage saved sessions
69
- min-agent update Upgrade to the latest npm release
70
-
71
- Confirmation modes (default: ask; independent of isolation):
72
- ask Prompt before dangerous operations
73
- accept-edits Auto-approve file writes; still prompt for dangerous commands
74
- allow-all Auto-approve everything
75
-
76
- Thinking intensity (default: medium):
77
- off Disable thinking
78
- low Light reasoning
79
- medium Standard reasoning
80
- high Strong reasoning
81
- max Highest reasoning
82
-
83
- Ollama context window:
84
- 2k … 256k Loaded window size
85
- auto Use the model's default
86
-
87
- Memory (default: off):
88
- off Do not inject memories or expose memory tools
89
- on Remember facts across sessions
90
-
91
- Isolation modes (default: off; independent of confirmation):
92
- off No sandboxing
93
- workspace Restrict file writes to the project workspace
94
- strict Restrict file writes and deny risky commands
95
- Env: MIN_AGENT_SANDBOX, MIN_AGENT_SANDBOX_NETWORK
96
-
97
- Rules (loaded as system instructions):
98
- Global: ~/.min-agent/rules.md
99
- Project: ./AGENTS.md or ./RULES.md or ./.min-agent/AGENTS.md
100
- Config: "instructions" array in ~/.min-agent/config.json
101
-
102
- Examples:
103
- min-agent setup
104
- min-agent setup --type ollama
105
- min-agent
106
- min-agent "hello"
107
- min-agent exec "hello"
108
- min-agent exec --resume <id> "continue"
109
- min-agent --resume <id>
110
- min-agent --permission ask
111
- min-agent --think max
112
- min-agent --memory on
113
- min-agent serve --port 8787
114
- min-agent update
115
- min-agent rules edit
116
- min-agent mcp add filesystem npx -y @modelcontextprotocol/server-filesystem /tmp
117
- min-agent mcp add remote --url https://example.com/mcp --token "$TOKEN"
118
- `);
119
- return program;
120
- }
121
- /** Apply --yes/-y for this process. Sandbox/permission overrides are applied per-invocation-kind. */
122
- export function applySessionOverrides(opts) {
123
- if (opts.yes)
124
- setAutoApprove(true);
125
- }
126
- export async function applySessionRuntimeOverrides(opts) {
127
- if (opts.sandbox !== undefined || opts.network !== undefined) {
128
- const { setSandboxOverride } = await import("../sandbox.js");
129
- if (opts.sandbox !== undefined)
130
- setSandboxOverride({ mode: opts.sandbox });
131
- if (opts.network !== undefined)
132
- setSandboxOverride({ network: opts.network });
133
- }
134
- if (opts.permission !== undefined) {
135
- const { setPermissionOverride } = await import("../confirm.js");
136
- setPermissionOverride(opts.permission);
137
- }
138
- if (opts.think !== undefined) {
139
- const { setThinkingOverride } = await import("../thinking.js");
140
- setThinkingOverride(opts.think);
141
- }
142
- if (opts.memory !== undefined) {
143
- const { setMemoryOverride } = await import("../memory.js");
144
- setMemoryOverride(opts.memory);
145
- }
146
- }
147
- export function hasSessionIntent(opts, prompt) {
148
- return (prompt.length > 0 || Boolean(opts.resume) || opts.image.length > 0 || Boolean(opts.model) || Boolean(opts.provider));
149
- }
150
- /** Extract `--unknown` style tokens left over in positionals (from allowUnknownOption) as errors. */
151
- export function extractUnknownOptionErrors(positionals) {
152
- const rest = [];
153
- const errors = [];
154
- for (const p of positionals) {
155
- if (p.startsWith("--"))
156
- errors.push(`Unknown option: ${p}`);
157
- else
158
- rest.push(p);
159
- }
160
- return { rest, errors };
161
- }
162
- async function runRootAction(promptTokens, opts) {
163
- applySessionOverrides(opts);
164
- const { rest, errors } = extractUnknownOptionErrors(promptTokens);
165
- const sessionIntent = hasSessionIntent(opts, rest);
166
- const wantsConfigWrite = opts.sandbox !== undefined ||
167
- opts.network !== undefined ||
168
- opts.permission !== undefined ||
169
- opts.think !== undefined ||
170
- opts.memory !== undefined;
171
- if (wantsConfigWrite && !sessionIntent) {
172
- const { runWriteConfig } = await import("./commands/write-config.js");
173
- await runWriteConfig({
174
- sandbox: opts.sandbox,
175
- network: opts.network,
176
- permission: opts.permission,
177
- think: opts.think,
178
- memory: opts.memory,
179
- scope: opts.project ? "project" : opts.global ? "global" : undefined,
180
- });
181
- return;
182
- }
183
- await applySessionRuntimeOverrides(opts);
184
- if (errors.length > 0) {
185
- console.error(errors[0]);
186
- process.exitCode = 1;
187
- return;
188
- }
189
- const { startTuiSession } = await import("./commands/shared.js");
190
- await startTuiSession({
191
- model: opts.model,
192
- provider: opts.provider,
193
- resume: opts.resume,
194
- images: opts.image,
195
- positionals: rest,
196
- });
197
- }
198
- export async function runProgram(argv, registerCommands) {
199
- const program = buildProgram();
200
- program.action((promptTokens, opts) => runRootAction(promptTokens, opts));
201
- registerCommands(program);
202
- try {
203
- await program.parseAsync(argv, { from: "user" });
204
- }
205
- catch (err) {
206
- if (err instanceof CliError) {
207
- console.error(formatCliError(err));
208
- process.exitCode = exitCodeFor(err);
209
- return;
210
- }
211
- // Commander throws a CommanderError for --help/--version and for its own
212
- // usage errors; those already printed output and set process.exitCode.
213
- if (isCommanderExit(err)) {
214
- process.exitCode = err.exitCode;
215
- return;
216
- }
217
- console.error(formatCliError(err));
218
- process.exitCode = 2;
219
- }
220
- }
221
- function isCommanderExit(err) {
222
- return typeof err === "object" && err !== null && "exitCode" in err && "code" in err;
223
- }
@@ -1,17 +0,0 @@
1
- import { normalizeOllamaBaseURL } from "../../config.js";
2
- const DEFAULT_TAGS_URL = "http://localhost:11434/api/tags";
3
- const DEFAULT_TIMEOUT_MS = 800;
4
- const DEFAULT_BASE = "http://localhost:11434/v1";
5
- export async function detectLocalOllama(opts) {
6
- const fetchImpl = opts?.fetchImpl ?? fetch;
7
- const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
8
- try {
9
- const response = await fetchImpl(DEFAULT_TAGS_URL, { signal: AbortSignal.timeout(timeoutMs) });
10
- if (!response.ok)
11
- return { found: false };
12
- return { found: true, baseURL: normalizeOllamaBaseURL(DEFAULT_BASE) };
13
- }
14
- catch {
15
- return { found: false };
16
- }
17
- }
@@ -1,12 +0,0 @@
1
- import { userError } from "../errors.js";
2
- export function parseProviderType(value) {
3
- if (value === "openai-compatible" || value === "openai" || value === "ollama")
4
- return value;
5
- throw userError(`Invalid --type value: "${value}"`, "use one of: openai-compatible, openai, ollama");
6
- }
7
- export function isNonInteractive(flags) {
8
- return Boolean(flags.type || flags.switch || flags.remove);
9
- }
10
- export function exclusiveActionCount(flags) {
11
- return [flags.type, flags.switch, flags.remove].filter(Boolean).length;
12
- }
@@ -1,108 +0,0 @@
1
- import { getActiveProvider, normalizeOllamaBaseURL } from "../../config.js";
2
- import { CliError, runtimeError } from "../errors.js";
3
- import { applyAddProvider, applyRemoveProvider, applySwitchProvider, suggestName, } from "./provider-form.js";
4
- import { exclusiveActionCount } from "./flags.js";
5
- const OPENAI_URL = "https://api.openai.com/v1";
6
- const OLLAMA_URL = "http://localhost:11434/v1";
7
- const NON_INTERACTIVE_HINT = "使用 --type、--switch 或 --remove。例如: min-agent setup --type ollama";
8
- export function shouldOfferSetupWizard(configured, isTty) {
9
- return !configured && isTty;
10
- }
11
- export async function runSetup(flags, deps, wizardMode) {
12
- if (exclusiveActionCount(flags) > 1) {
13
- throw new CliError("一次只能执行一种操作。");
14
- }
15
- if (flags.remove) {
16
- await runRemove(flags.remove, flags.yes === true, deps);
17
- return;
18
- }
19
- if (flags.switch) {
20
- persist(applySwitchProvider(deps.load(), flags.switch), deps);
21
- return;
22
- }
23
- if (flags.type) {
24
- await runAdd(flags, deps);
25
- return;
26
- }
27
- if (!deps.isTty) {
28
- throw new CliError("无法在非交互环境完成配置。", { hint: NON_INTERACTIVE_HINT });
29
- }
30
- const config = deps.load();
31
- const mode = wizardMode ?? (hasUsableProvider(config) ? "hub" : "first-run");
32
- await deps.runWizard(mode);
33
- }
34
- function hasUsableProvider(config) {
35
- const provider = getActiveProvider(config);
36
- return Boolean(provider?.baseURL && provider?.apiKey);
37
- }
38
- async function runRemove(name, yes, deps) {
39
- if (!yes) {
40
- throw new CliError("删除需要确认。", { hint: "加上 --yes" });
41
- }
42
- persist(applyRemoveProvider(deps.load(), name), deps);
43
- }
44
- async function runAdd(flags, deps) {
45
- const type = flags.type;
46
- const baseURL = resolveBaseURL(type, flags.url);
47
- const apiKey = resolveApiKey(type, flags.apiKey);
48
- const defaultModel = await resolveDefaultModel(type, baseURL, apiKey, flags.defaultModel, deps.fetchLive);
49
- const config = deps.load();
50
- const existing = (config.providers ?? []).map((p) => p.name).filter((name) => Boolean(name));
51
- const name = (flags.name?.trim() || suggestName(type, baseURL, existing)).trim();
52
- const draft = {
53
- name,
54
- type,
55
- baseURL,
56
- apiKey,
57
- defaultModel,
58
- ...(flags.contextWindow != null ? { contextWindow: flags.contextWindow } : {}),
59
- };
60
- const overwrite = flags.yes === true;
61
- const result = applyAddProvider(config, draft, { overwrite });
62
- if (isApplyError(result) && result.error.startsWith("已存在同名服务商") && !overwrite) {
63
- throw new CliError(result.error, { hint: "加上 --yes 以覆盖" });
64
- }
65
- persist(result, deps);
66
- }
67
- function resolveBaseURL(type, url) {
68
- if (type === "openai")
69
- return OPENAI_URL;
70
- if (type === "ollama")
71
- return normalizeOllamaBaseURL(url?.trim() || OLLAMA_URL);
72
- const trimmed = url?.trim() ?? "";
73
- if (!trimmed)
74
- throw new CliError("API 地址不能为空。");
75
- return trimmed.replace(/\/$/, "");
76
- }
77
- function resolveApiKey(type, apiKey) {
78
- if (type === "ollama")
79
- return apiKey?.trim() || "ollama";
80
- const trimmed = apiKey?.trim() ?? "";
81
- if (!trimmed)
82
- throw new CliError("API 密钥不能为空。");
83
- return trimmed;
84
- }
85
- async function resolveDefaultModel(type, baseURL, apiKey, explicit, fetchLive) {
86
- const given = explicit?.trim() ?? "";
87
- if (given)
88
- return given;
89
- const live = await fetchLive(baseURL, apiKey);
90
- if (live.models[0])
91
- return live.models[0];
92
- if (type === "ollama")
93
- return "llama3";
94
- if (!live.ok && live.status == null) {
95
- throw runtimeError("无法连接到该服务商。");
96
- }
97
- throw new CliError("默认模型不能为空。");
98
- }
99
- function persist(result, deps) {
100
- if (isApplyError(result))
101
- throw new CliError(result.error);
102
- deps.save(result);
103
- deps.log(`已保存到 ${deps.configPath()}`);
104
- }
105
- function isApplyError(result) {
106
- return Object.keys(result).length === 1 && "error" in result;
107
- }
108
- export { isNonInteractive } from "./flags.js";
@@ -1,102 +0,0 @@
1
- export function suggestName(type, baseURL, existing) {
2
- const base = type === "openai" ? "openai" : type === "ollama" ? "ollama" : (hostSlug(baseURL) ?? "provider");
3
- return uniqueName(base, existing);
4
- }
5
- export function validateProviderName(name) {
6
- const trimmed = name.trim();
7
- if (!trimmed || trimmed.includes("/") || trimmed.includes(":")) {
8
- return "名称不能为空,且不能包含 / 或 :。";
9
- }
10
- return null;
11
- }
12
- export function maskApiKey(key) {
13
- if (key.length < 4)
14
- return "****";
15
- return `…${key.slice(-4)}`;
16
- }
17
- export function filterChoices(query, items) {
18
- const q = query.trim().toLowerCase();
19
- if (!q)
20
- return [...items];
21
- return items.filter((item) => item.toLowerCase().includes(q));
22
- }
23
- export function applyAddProvider(config, draft, opts) {
24
- const nameError = validateProviderName(draft.name);
25
- if (nameError)
26
- return { error: nameError };
27
- const name = draft.name.trim();
28
- const providers = [...(config.providers ?? [])];
29
- const idx = providers.findIndex((p) => p.name === name);
30
- if (idx >= 0 && !opts.overwrite)
31
- return { error: `已存在同名服务商 "${name}"。` };
32
- const entry = toProvider(draft, name);
33
- if (idx >= 0)
34
- providers[idx] = entry;
35
- else
36
- providers.push(entry);
37
- return { ...config, providers, activeProvider: name };
38
- }
39
- export function applySwitchProvider(config, name) {
40
- const target = (config.providers ?? []).find((p) => p.name === name);
41
- if (!target)
42
- return { error: `找不到服务商 "${name}"。` };
43
- return { ...config, activeProvider: name };
44
- }
45
- export function applyUpdateProvider(config, oldName, draft) {
46
- const nameError = validateProviderName(draft.name);
47
- if (nameError)
48
- return { error: nameError };
49
- const name = draft.name.trim();
50
- const providers = [...(config.providers ?? [])];
51
- const idx = providers.findIndex((p) => p.name === oldName);
52
- if (idx < 0)
53
- return { error: `找不到服务商 "${oldName}"。` };
54
- if (name !== oldName && providers.some((p) => p.name === name)) {
55
- return { error: `已存在同名服务商 "${name}"。` };
56
- }
57
- providers[idx] = toProvider(draft, name);
58
- const activeProvider = config.activeProvider === oldName ? name : config.activeProvider;
59
- return { ...config, providers, activeProvider };
60
- }
61
- export function applyRemoveProvider(config, name) {
62
- const providers = config.providers ?? [];
63
- if (!providers.some((p) => p.name === name))
64
- return { error: `找不到服务商 "${name}"。` };
65
- const remaining = providers.filter((p) => p.name !== name);
66
- if (remaining.length === 0) {
67
- const { activeProvider: _dropped, ...rest } = config;
68
- return { ...rest, providers: [] };
69
- }
70
- const activeProvider = config.activeProvider === name ? remaining[0]?.name : config.activeProvider;
71
- return { ...config, providers: remaining, activeProvider };
72
- }
73
- function toProvider(draft, name) {
74
- return {
75
- name,
76
- type: draft.type,
77
- baseURL: draft.baseURL,
78
- apiKey: draft.apiKey,
79
- defaultModel: draft.defaultModel,
80
- ...(draft.contextWindow != null ? { contextWindow: draft.contextWindow } : {}),
81
- };
82
- }
83
- function hostSlug(baseURL) {
84
- try {
85
- const host = new URL(baseURL).hostname.toLowerCase();
86
- const trimmed = host.startsWith("www.") ? host.slice(4) : host;
87
- if (!trimmed)
88
- return undefined;
89
- return trimmed.replace(/\./g, "-");
90
- }
91
- catch {
92
- return undefined;
93
- }
94
- }
95
- function uniqueName(base, existing) {
96
- if (!existing.includes(base))
97
- return base;
98
- let n = 2;
99
- while (existing.includes(`${base}-${n}`))
100
- n += 1;
101
- return `${base}-${n}`;
102
- }