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.
- package/README.md +32 -18
- package/dist/cli.js +538 -12
- package/docs/API.md +25 -8
- package/package.json +6 -6
- package/skills/self-config/SKILL.md +7 -3
- package/skills/self-config/reference.md +8 -4
- package/dist/agent.js +0 -1342
- package/dist/assistant-stream.js +0 -131
- package/dist/cli/commands/chat.js +0 -13
- package/dist/cli/commands/exec.js +0 -35
- package/dist/cli/commands/history.js +0 -58
- package/dist/cli/commands/index.js +0 -241
- package/dist/cli/commands/init.js +0 -18
- package/dist/cli/commands/mcp.js +0 -173
- package/dist/cli/commands/memory.js +0 -87
- package/dist/cli/commands/models.js +0 -21
- package/dist/cli/commands/permission.js +0 -12
- package/dist/cli/commands/rules.js +0 -33
- package/dist/cli/commands/sandbox.js +0 -13
- package/dist/cli/commands/serve.js +0 -9
- package/dist/cli/commands/setup.js +0 -4
- package/dist/cli/commands/shared.js +0 -16
- package/dist/cli/commands/skills.js +0 -119
- package/dist/cli/commands/think.js +0 -12
- package/dist/cli/commands/update.js +0 -7
- package/dist/cli/commands/write-config.js +0 -52
- package/dist/cli/errors.js +0 -36
- package/dist/cli/exec-prompt.js +0 -26
- package/dist/cli/option-helpers.js +0 -65
- package/dist/cli/program.js +0 -217
- package/dist/clipboard.js +0 -142
- package/dist/code-mode.js +0 -184
- package/dist/compaction.js +0 -644
- package/dist/config.js +0 -470
- package/dist/confirm.js +0 -172
- package/dist/context-window.js +0 -246
- package/dist/doom-loop.js +0 -278
- package/dist/fetch-timeout.js +0 -152
- package/dist/http-approvals.js +0 -60
- package/dist/http.js +0 -119
- package/dist/instructions.js +0 -196
- package/dist/logger.js +0 -95
- package/dist/markdown.js +0 -259
- package/dist/mcp.js +0 -1036
- package/dist/memory-cli.js +0 -33
- package/dist/memory.js +0 -295
- package/dist/model-catalog.js +0 -285
- package/dist/output.js +0 -76
- package/dist/paste-handler.js +0 -41
- package/dist/permission-cli.js +0 -40
- package/dist/plugins.js +0 -131
- package/dist/pricing.js +0 -119
- package/dist/provider.js +0 -63
- package/dist/question-format.js +0 -60
- package/dist/reasoning-stream.js +0 -158
- package/dist/sandbox-cli.js +0 -79
- package/dist/sandbox.js +0 -403
- package/dist/save-throttle.js +0 -45
- package/dist/scope.js +0 -23
- package/dist/serve/common.js +0 -425
- package/dist/serve/routes-chat.js +0 -367
- package/dist/serve/routes-mcp.js +0 -212
- package/dist/serve/routes-memory.js +0 -95
- package/dist/serve/routes-meta.js +0 -233
- package/dist/serve/routes-sessions.js +0 -61
- package/dist/serve/routes-skills.js +0 -70
- package/dist/serve.js +0 -138
- package/dist/sessions.js +0 -265
- package/dist/skills.js +0 -599
- package/dist/structured-output.js +0 -29
- package/dist/synthetic.js +0 -7
- package/dist/think-cli.js +0 -36
- package/dist/thinking-wire.js +0 -228
- package/dist/thinking.js +0 -142
- package/dist/title-gen.js +0 -55
- package/dist/token-display.js +0 -39
- package/dist/tool-display.js +0 -178
- package/dist/tool-output.js +0 -126
- package/dist/tools/apply_patch.js +0 -265
- package/dist/tools/atomic-file.js +0 -35
- package/dist/tools/backend.js +0 -61
- package/dist/tools/bash.js +0 -231
- package/dist/tools/code_search.js +0 -98
- package/dist/tools/edit.js +0 -68
- package/dist/tools/explore.js +0 -232
- package/dist/tools/glob.js +0 -40
- package/dist/tools/grep.js +0 -174
- package/dist/tools/index.js +0 -34
- package/dist/tools/question.js +0 -54
- package/dist/tools/read.js +0 -109
- package/dist/tools/search-searxng.js +0 -223
- package/dist/tools/search-serper.js +0 -189
- package/dist/tools/task.js +0 -165
- package/dist/tools/todo.js +0 -213
- package/dist/tools/web_fetch.js +0 -195
- package/dist/tools/web_search.js +0 -228
- package/dist/tools/write.js +0 -55
- package/dist/tui/App.js +0 -126
- package/dist/tui/ConfirmBar.js +0 -66
- package/dist/tui/InputBar.js +0 -616
- package/dist/tui/MessageList.js +0 -718
- package/dist/tui/ModelPicker.js +0 -113
- package/dist/tui/QuestionBar.js +0 -136
- package/dist/tui/SessionPicker.js +0 -79
- package/dist/tui/Spinner.js +0 -6
- package/dist/tui/StatusBar.js +0 -22
- package/dist/tui/ThinkPicker.js +0 -77
- package/dist/tui/agent-runner.js +0 -223
- package/dist/tui/bracketed-paste.js +0 -37
- package/dist/tui/caret-pos.js +0 -179
- package/dist/tui/caret.js +0 -69
- package/dist/tui/click-count.js +0 -13
- package/dist/tui/diff-view.js +0 -61
- package/dist/tui/drag-state.js +0 -49
- package/dist/tui/hydrate.js +0 -129
- package/dist/tui/index.js +0 -258
- package/dist/tui/input-history.js +0 -125
- package/dist/tui/layout.js +0 -105
- package/dist/tui/mouse.js +0 -46
- package/dist/tui/overlay-input.js +0 -12
- package/dist/tui/paste-draft.js +0 -173
- package/dist/tui/prompt-queue.js +0 -24
- package/dist/tui/selection.js +0 -232
- package/dist/tui/session-switch.js +0 -28
- package/dist/tui/slash-commands.js +0 -123
- package/dist/tui/slash-handler.js +0 -589
- package/dist/tui/text-width.js +0 -113
- package/dist/tui/theme.js +0 -12
- package/dist/tui/token-info.js +0 -7
- package/dist/tui/tool-children.js +0 -19
- package/dist/tui/types.js +0 -1
- package/dist/tui/undo-stack.js +0 -14
- package/dist/tui/use-sgr-mouse.js +0 -29
- package/dist/tui-chat.js +0 -456
- package/dist/updater.js +0 -116
- package/dist/xml-search.js +0 -194
- package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +0 -1510
- package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +0 -2105
- package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +0 -1595
- package/docs/superpowers/plans/2026-08-16-input-caret.md +0 -782
- package/docs/superpowers/plans/2026-08-20-tui-completeness.md +0 -873
- package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +0 -631
- package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +0 -475
- package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +0 -450
- package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +0 -183
- package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +0 -220
- package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +0 -196
- package/docs/superpowers/specs/2026-08-16-input-caret-design.md +0 -63
- package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +0 -116
- package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +0 -47
- package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +0 -37
- package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +0 -68
- package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +0 -273
- package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +0 -165
- package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +0 -174
- package/docs/superpowers/specs/2026-08-23-thinking-wire-profile-design.md +0 -140
package/dist/config.js
DELETED
|
@@ -1,470 +0,0 @@
|
|
|
1
|
-
import { readFileSync, mkdirSync, existsSync, statSync } from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import os from "os";
|
|
4
|
-
import readline from "readline";
|
|
5
|
-
import { atomicWriteFileSync } from "./tools/atomic-file.js";
|
|
6
|
-
import { getEffectiveSandboxPolicy, mergeSandboxConfig, parseSandboxConfig, sandboxLaunchSource, sandboxStatusLabel, } from "./sandbox.js";
|
|
7
|
-
const DEFAULT_CONFIG_DIR = path.join(os.homedir(), ".min-agent");
|
|
8
|
-
const CONFIG_FILE_NAME = "config.json";
|
|
9
|
-
const RULES_FILE_NAME = "rules.md";
|
|
10
|
-
export function getConfigDir() {
|
|
11
|
-
return process.env.MIN_AGENT_CONFIG_DIR ?? DEFAULT_CONFIG_DIR;
|
|
12
|
-
}
|
|
13
|
-
export function getRulesFile() {
|
|
14
|
-
return path.join(getConfigDir(), RULES_FILE_NAME);
|
|
15
|
-
}
|
|
16
|
-
let cachedConfig = null;
|
|
17
|
-
const projectCache = new Map();
|
|
18
|
-
export function getProjectConfigDir() {
|
|
19
|
-
return path.join(process.cwd(), ".min-agent");
|
|
20
|
-
}
|
|
21
|
-
export function getProjectConfigFile() {
|
|
22
|
-
return path.join(getProjectConfigDir(), CONFIG_FILE_NAME);
|
|
23
|
-
}
|
|
24
|
-
export function loadProjectConfig() {
|
|
25
|
-
const file = getProjectConfigFile();
|
|
26
|
-
let st;
|
|
27
|
-
try {
|
|
28
|
-
st = statSync(file);
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
return {};
|
|
32
|
-
}
|
|
33
|
-
const cached = projectCache.get(file);
|
|
34
|
-
if (cached && cached.mtimeMs === st.mtimeMs && cached.size === st.size)
|
|
35
|
-
return cached.config;
|
|
36
|
-
let config;
|
|
37
|
-
try {
|
|
38
|
-
config = JSON.parse(readFileSync(file, "utf-8"));
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
console.warn(`\x1b[33m ⚠ Project config ${file} is not valid JSON; ignoring\x1b[0m`);
|
|
42
|
-
return {};
|
|
43
|
-
}
|
|
44
|
-
projectCache.set(file, { mtimeMs: st.mtimeMs, size: st.size, config });
|
|
45
|
-
return config;
|
|
46
|
-
}
|
|
47
|
-
export function saveProjectConfig(config) {
|
|
48
|
-
const dir = getProjectConfigDir();
|
|
49
|
-
mkdirSync(dir, { recursive: true });
|
|
50
|
-
// Keep unknown keys, drop empty skill arrays so the file stays minimal.
|
|
51
|
-
const { disabledSkills, enabledSkills, ...rest } = config;
|
|
52
|
-
atomicWriteFileSync(getProjectConfigFile(), JSON.stringify({
|
|
53
|
-
...rest,
|
|
54
|
-
...(disabledSkills?.length ? { disabledSkills } : {}),
|
|
55
|
-
...(enabledSkills?.length ? { enabledSkills } : {}),
|
|
56
|
-
}, null, 2));
|
|
57
|
-
projectCache.delete(getProjectConfigFile());
|
|
58
|
-
}
|
|
59
|
-
export function loadConfigFile(configFile) {
|
|
60
|
-
let st;
|
|
61
|
-
try {
|
|
62
|
-
st = statSync(configFile);
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
return {};
|
|
66
|
-
}
|
|
67
|
-
if (cachedConfig &&
|
|
68
|
-
cachedConfig.file === configFile &&
|
|
69
|
-
cachedConfig.mtimeMs === st.mtimeMs &&
|
|
70
|
-
cachedConfig.size === st.size) {
|
|
71
|
-
return cachedConfig.config;
|
|
72
|
-
}
|
|
73
|
-
let config;
|
|
74
|
-
try {
|
|
75
|
-
config = JSON.parse(readFileSync(configFile, "utf-8"));
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
console.warn(`\x1b[33m ⚠ Config file ${configFile} is not valid JSON; using defaults\x1b[0m`);
|
|
79
|
-
return {};
|
|
80
|
-
}
|
|
81
|
-
cachedConfig = { file: configFile, mtimeMs: st.mtimeMs, size: st.size, config };
|
|
82
|
-
return config;
|
|
83
|
-
}
|
|
84
|
-
export function loadConfig() {
|
|
85
|
-
const raw = loadConfigFile(path.join(getConfigDir(), CONFIG_FILE_NAME));
|
|
86
|
-
const { config, migrated } = migrateLegacyConfig(raw);
|
|
87
|
-
if (migrated) {
|
|
88
|
-
try {
|
|
89
|
-
saveConfig(config);
|
|
90
|
-
}
|
|
91
|
-
catch { }
|
|
92
|
-
}
|
|
93
|
-
return config;
|
|
94
|
-
}
|
|
95
|
-
export function migrateLegacyConfig(config) {
|
|
96
|
-
const legacy = config.provider;
|
|
97
|
-
if (!legacy)
|
|
98
|
-
return { config, migrated: false };
|
|
99
|
-
const { provider, ...rest } = config;
|
|
100
|
-
return {
|
|
101
|
-
config: { ...rest, providers: [{ name: "default", ...provider }], activeProvider: "default" },
|
|
102
|
-
migrated: true,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
export function mergeProjectOverGlobal(global, project) {
|
|
106
|
-
const activeProvider = project.activeProvider ?? global.activeProvider;
|
|
107
|
-
const sampling = project.sampling ? { ...global.sampling, ...project.sampling } : global.sampling;
|
|
108
|
-
const budget = project.budget ? { ...global.budget, ...project.budget } : global.budget;
|
|
109
|
-
const permission = parsePermissionMode(project.permission) ?? parsePermissionMode(global.permission);
|
|
110
|
-
const thinking = parseThinkingEffort(project.thinking) ?? parseThinkingEffort(global.thinking);
|
|
111
|
-
const memory = parseMemoryMode(project.memory) ?? parseMemoryMode(global.memory);
|
|
112
|
-
const sandbox = mergeSandboxConfig(parseSandboxConfig(global.sandbox), parseSandboxConfig(project.sandbox));
|
|
113
|
-
const compaction = project.compaction ? { ...global.compaction, ...project.compaction } : global.compaction;
|
|
114
|
-
const agent = project.agent ? { ...global.agent, ...project.agent } : global.agent;
|
|
115
|
-
let providers = global.providers;
|
|
116
|
-
if (project.defaultModel && providers && providers.length > 0) {
|
|
117
|
-
const idx = activeProvider ? providers.findIndex((p) => p.name === activeProvider) : 0;
|
|
118
|
-
const target = idx >= 0 ? idx : 0;
|
|
119
|
-
providers = providers.map((p, i) => (i === target ? { ...p, defaultModel: project.defaultModel } : p));
|
|
120
|
-
}
|
|
121
|
-
return {
|
|
122
|
-
...global,
|
|
123
|
-
activeProvider,
|
|
124
|
-
sampling,
|
|
125
|
-
budget,
|
|
126
|
-
permission,
|
|
127
|
-
...(thinking ? { thinking } : {}),
|
|
128
|
-
...(memory ? { memory } : {}),
|
|
129
|
-
...(sandbox ? { sandbox } : {}),
|
|
130
|
-
compaction,
|
|
131
|
-
agent,
|
|
132
|
-
...(providers ? { providers } : {}),
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
export function getEffectiveConfig() {
|
|
136
|
-
return mergeProjectOverGlobal(loadConfig(), loadProjectConfig());
|
|
137
|
-
}
|
|
138
|
-
export function getBudgetSnapshot() {
|
|
139
|
-
const global = loadConfig();
|
|
140
|
-
const project = loadProjectConfig();
|
|
141
|
-
const maxCostUSD = mergeProjectOverGlobal(global, project).budget?.maxCostUSD;
|
|
142
|
-
const source = project.budget?.maxCostUSD != null ? "project" : global.budget?.maxCostUSD != null ? "global" : null;
|
|
143
|
-
return { maxCostUSD, source };
|
|
144
|
-
}
|
|
145
|
-
export function parsePermissionMode(value) {
|
|
146
|
-
if (value === "ask" || value === "accept-edits" || value === "allow-all")
|
|
147
|
-
return value;
|
|
148
|
-
return undefined;
|
|
149
|
-
}
|
|
150
|
-
const THINKING_ALIASES = {
|
|
151
|
-
off: "off",
|
|
152
|
-
none: "off",
|
|
153
|
-
low: "low",
|
|
154
|
-
minimal: "low",
|
|
155
|
-
medium: "medium",
|
|
156
|
-
high: "high",
|
|
157
|
-
max: "max",
|
|
158
|
-
xhigh: "max",
|
|
159
|
-
"extra-high": "max",
|
|
160
|
-
extra_high: "max",
|
|
161
|
-
extrahigh: "max",
|
|
162
|
-
};
|
|
163
|
-
export function parseThinkingEffort(value) {
|
|
164
|
-
if (typeof value !== "string")
|
|
165
|
-
return undefined;
|
|
166
|
-
return THINKING_ALIASES[value.trim().toLowerCase()];
|
|
167
|
-
}
|
|
168
|
-
const MEMORY_ALIASES = {
|
|
169
|
-
on: "on",
|
|
170
|
-
true: "on",
|
|
171
|
-
enable: "on",
|
|
172
|
-
enabled: "on",
|
|
173
|
-
off: "off",
|
|
174
|
-
false: "off",
|
|
175
|
-
disable: "off",
|
|
176
|
-
disabled: "off",
|
|
177
|
-
};
|
|
178
|
-
export function parseMemoryMode(value) {
|
|
179
|
-
if (value === true)
|
|
180
|
-
return "on";
|
|
181
|
-
if (value === false)
|
|
182
|
-
return "off";
|
|
183
|
-
if (typeof value !== "string")
|
|
184
|
-
return undefined;
|
|
185
|
-
return MEMORY_ALIASES[value.trim().toLowerCase()];
|
|
186
|
-
}
|
|
187
|
-
export function permissionModeLabel(mode) {
|
|
188
|
-
if (mode === "allow-all")
|
|
189
|
-
return "allow all";
|
|
190
|
-
if (mode === "accept-edits")
|
|
191
|
-
return "accept edits";
|
|
192
|
-
return "ask";
|
|
193
|
-
}
|
|
194
|
-
export function getPermissionSnapshot() {
|
|
195
|
-
const global = loadConfig();
|
|
196
|
-
const project = loadProjectConfig();
|
|
197
|
-
const permission = parsePermissionMode(project.permission) ?? parsePermissionMode(global.permission) ?? "ask";
|
|
198
|
-
const source = parsePermissionMode(project.permission) != null
|
|
199
|
-
? "project"
|
|
200
|
-
: parsePermissionMode(global.permission) != null
|
|
201
|
-
? "global"
|
|
202
|
-
: null;
|
|
203
|
-
return { permission, source };
|
|
204
|
-
}
|
|
205
|
-
export function setPermissionMode(permission, scope) {
|
|
206
|
-
if (scope === "project") {
|
|
207
|
-
const project = loadProjectConfig();
|
|
208
|
-
saveProjectConfig({ ...project, permission });
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
saveConfig({ ...loadConfig(), permission });
|
|
212
|
-
}
|
|
213
|
-
export function setSandboxConfig(partial, scope) {
|
|
214
|
-
if (scope === "project") {
|
|
215
|
-
const project = loadProjectConfig();
|
|
216
|
-
const current = parseSandboxConfig(project.sandbox) ?? {};
|
|
217
|
-
saveProjectConfig({ ...project, sandbox: { ...current, ...partial } });
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
const cfg = loadConfig();
|
|
221
|
-
const current = parseSandboxConfig(cfg.sandbox) ?? {};
|
|
222
|
-
saveConfig({ ...cfg, sandbox: { ...current, ...partial } });
|
|
223
|
-
}
|
|
224
|
-
export function getSandboxSnapshot() {
|
|
225
|
-
const policy = getEffectiveSandboxPolicy();
|
|
226
|
-
const launch = sandboxLaunchSource();
|
|
227
|
-
const project = parseSandboxConfig(loadProjectConfig().sandbox);
|
|
228
|
-
const global = parseSandboxConfig(loadConfig().sandbox);
|
|
229
|
-
const projectSet = Boolean(project?.mode || project?.network || project?.extraWriteRoots?.length || project?.extraReadRoots?.length);
|
|
230
|
-
const globalSet = Boolean(global?.mode || global?.network || global?.extraWriteRoots?.length || global?.extraReadRoots?.length);
|
|
231
|
-
const source = launch ?? (projectSet ? "project" : globalSet ? "global" : null);
|
|
232
|
-
return {
|
|
233
|
-
mode: policy.mode,
|
|
234
|
-
network: policy.network,
|
|
235
|
-
extraWriteRoots: policy.extraWriteRoots,
|
|
236
|
-
extraReadRoots: policy.extraReadRoots,
|
|
237
|
-
source,
|
|
238
|
-
label: sandboxStatusLabel(policy),
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
export function setBudgetMaxCost(maxCostUSD, scope) {
|
|
242
|
-
if (scope === "project") {
|
|
243
|
-
const project = loadProjectConfig();
|
|
244
|
-
saveProjectConfig({ ...project, budget: { ...project.budget, maxCostUSD } });
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
const cfg = loadConfig();
|
|
248
|
-
saveConfig({ ...cfg, budget: { ...cfg.budget, maxCostUSD } });
|
|
249
|
-
}
|
|
250
|
-
export function getActiveProvider(config) {
|
|
251
|
-
if (!config.providers || config.providers.length === 0)
|
|
252
|
-
return undefined;
|
|
253
|
-
if (config.activeProvider) {
|
|
254
|
-
const found = config.providers.find((p) => p.name === config.activeProvider);
|
|
255
|
-
if (found)
|
|
256
|
-
return found;
|
|
257
|
-
}
|
|
258
|
-
return config.providers[0];
|
|
259
|
-
}
|
|
260
|
-
/** Appends /v1 to an Ollama base URL unless already present */
|
|
261
|
-
export function normalizeOllamaBaseURL(baseURL) {
|
|
262
|
-
const trimmed = baseURL.replace(/\/$/, "");
|
|
263
|
-
return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
|
|
264
|
-
}
|
|
265
|
-
export function saveConfig(config) {
|
|
266
|
-
const dir = getConfigDir();
|
|
267
|
-
mkdirSync(dir, { recursive: true });
|
|
268
|
-
atomicWriteFileSync(path.join(dir, CONFIG_FILE_NAME), JSON.stringify(config, null, 2));
|
|
269
|
-
cachedConfig = null;
|
|
270
|
-
}
|
|
271
|
-
export function isConfigured() {
|
|
272
|
-
const config = loadConfig();
|
|
273
|
-
const provider = getActiveProvider(config);
|
|
274
|
-
return !!(provider?.baseURL && provider?.apiKey);
|
|
275
|
-
}
|
|
276
|
-
async function fetchModelsFromURL(url, apiKey) {
|
|
277
|
-
const noCacheURL = new URL(url);
|
|
278
|
-
noCacheURL.searchParams.set("_t", Date.now().toString());
|
|
279
|
-
const response = await fetch(noCacheURL.toString(), {
|
|
280
|
-
headers: {
|
|
281
|
-
Authorization: `Bearer ${apiKey}`,
|
|
282
|
-
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
283
|
-
Pragma: "no-cache",
|
|
284
|
-
Expires: "0",
|
|
285
|
-
},
|
|
286
|
-
cache: "no-store",
|
|
287
|
-
signal: AbortSignal.timeout(10000),
|
|
288
|
-
});
|
|
289
|
-
if (!response.ok)
|
|
290
|
-
return [];
|
|
291
|
-
const data = (await response.json());
|
|
292
|
-
const models = (data.data ?? data ?? []);
|
|
293
|
-
return models.map((m) => m.id).sort();
|
|
294
|
-
}
|
|
295
|
-
function ask(rl, question, defaultValue) {
|
|
296
|
-
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
297
|
-
return new Promise((resolve) => {
|
|
298
|
-
rl.question(`${question}${suffix}: `, (answer) => {
|
|
299
|
-
resolve(answer.trim() || defaultValue || "");
|
|
300
|
-
});
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
const MODELS_CACHE_FILE = "models-cache.json";
|
|
304
|
-
function modelsCachePath() {
|
|
305
|
-
return path.join(getConfigDir(), MODELS_CACHE_FILE);
|
|
306
|
-
}
|
|
307
|
-
function loadModelsCache() {
|
|
308
|
-
const file = modelsCachePath();
|
|
309
|
-
if (!existsSync(file))
|
|
310
|
-
return [];
|
|
311
|
-
try {
|
|
312
|
-
return JSON.parse(readFileSync(file, "utf-8"));
|
|
313
|
-
}
|
|
314
|
-
catch {
|
|
315
|
-
return [];
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
function saveModelsCache(models) {
|
|
319
|
-
mkdirSync(getConfigDir(), { recursive: true });
|
|
320
|
-
atomicWriteFileSync(modelsCachePath(), JSON.stringify(models));
|
|
321
|
-
}
|
|
322
|
-
export async function fetchModels(baseURL, apiKey) {
|
|
323
|
-
try {
|
|
324
|
-
const trimmed = baseURL.replace(/\/$/, "");
|
|
325
|
-
let models = await fetchModelsFromURL(`${trimmed}/models`, apiKey);
|
|
326
|
-
// Ollama users often provide host without /v1; auto-retry that variant.
|
|
327
|
-
if (models.length === 0 && !trimmed.endsWith("/v1")) {
|
|
328
|
-
models = await fetchModelsFromURL(`${trimmed}/v1/models`, apiKey);
|
|
329
|
-
}
|
|
330
|
-
if (models.length > 0) {
|
|
331
|
-
saveModelsCache(models);
|
|
332
|
-
return models;
|
|
333
|
-
}
|
|
334
|
-
// Fallback to cache if live fetch returned nothing
|
|
335
|
-
const cached = loadModelsCache();
|
|
336
|
-
if (cached.length > 0) {
|
|
337
|
-
console.log("\x1b[90m (using cached model list)\x1b[0m");
|
|
338
|
-
}
|
|
339
|
-
return cached;
|
|
340
|
-
}
|
|
341
|
-
catch {
|
|
342
|
-
// Network error — fallback to cache
|
|
343
|
-
const cached = loadModelsCache();
|
|
344
|
-
if (cached.length > 0) {
|
|
345
|
-
console.log("\x1b[90m (using cached model list — network unavailable)\x1b[0m");
|
|
346
|
-
}
|
|
347
|
-
return cached;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
export async function runSetup() {
|
|
351
|
-
const config = loadConfig();
|
|
352
|
-
const providers = config.providers ?? [];
|
|
353
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
354
|
-
console.log("\n🔧 min-agent 配置\n");
|
|
355
|
-
if (providers.length > 0) {
|
|
356
|
-
console.log("已配置的 Provider:");
|
|
357
|
-
providers.forEach((p, i) => {
|
|
358
|
-
const marker = p.name === config.activeProvider ? " ← 当前" : "";
|
|
359
|
-
console.log(` ${i + 1}. ${p.name ?? "(未命名)"} ${p.baseURL} (${p.defaultModel ?? "未设置模型"})${marker}`);
|
|
360
|
-
});
|
|
361
|
-
console.log();
|
|
362
|
-
console.log("选项:");
|
|
363
|
-
console.log(" 1. 添加新 Provider");
|
|
364
|
-
console.log(" 2. 切换当前 Provider");
|
|
365
|
-
console.log(" 3. 修改当前 Provider");
|
|
366
|
-
console.log(" 4. 完成");
|
|
367
|
-
const choice = await ask(rl, "选择 (1/2/3/4)", "4");
|
|
368
|
-
if (choice === "1") {
|
|
369
|
-
const provider = await collectProvider(rl, undefined);
|
|
370
|
-
provider.name =
|
|
371
|
-
(await ask(rl, "Provider 名称", `provider-${providers.length + 1}`)).trim() ||
|
|
372
|
-
`provider-${providers.length + 1}`;
|
|
373
|
-
providers.push(provider);
|
|
374
|
-
config.activeProvider = provider.name;
|
|
375
|
-
config.providers = providers;
|
|
376
|
-
saveConfig(config);
|
|
377
|
-
console.log(`\n✓ Provider "${provider.name}" 已添加并设为当前`);
|
|
378
|
-
}
|
|
379
|
-
else if (choice === "2") {
|
|
380
|
-
const sel = await ask(rl, "输入序号选择当前 Provider");
|
|
381
|
-
const idx = parseInt(sel, 10) - 1;
|
|
382
|
-
const target = providers[idx];
|
|
383
|
-
if (!target?.name) {
|
|
384
|
-
console.error("无效序号");
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
config.activeProvider = target.name;
|
|
388
|
-
saveConfig(config);
|
|
389
|
-
console.log(`\n✓ 当前 Provider 已切换为: ${target.name}`);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
else if (choice === "3") {
|
|
393
|
-
const name = getActiveProvider(config)?.name ?? providers[0]?.name;
|
|
394
|
-
if (!name) {
|
|
395
|
-
console.error("无可用 Provider");
|
|
396
|
-
rl.close();
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
const idx = providers.findIndex((p) => p.name === name);
|
|
400
|
-
providers[idx] = { ...(await collectProvider(rl, providers[idx])), name };
|
|
401
|
-
config.providers = providers;
|
|
402
|
-
saveConfig(config);
|
|
403
|
-
console.log(`\n✓ Provider "${name}" 已更新`);
|
|
404
|
-
}
|
|
405
|
-
else {
|
|
406
|
-
console.log("\n配置未变更");
|
|
407
|
-
}
|
|
408
|
-
rl.close();
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
// 首次配置
|
|
412
|
-
const provider = await collectProvider(rl, undefined);
|
|
413
|
-
provider.name = "default";
|
|
414
|
-
config.providers = [provider];
|
|
415
|
-
config.activeProvider = "default";
|
|
416
|
-
saveConfig(config);
|
|
417
|
-
rl.close();
|
|
418
|
-
console.log(`\n✓ 配置已保存到 ${path.join(getConfigDir(), CONFIG_FILE_NAME)}`);
|
|
419
|
-
}
|
|
420
|
-
async function collectProvider(rl, existing) {
|
|
421
|
-
console.log("Provider 类型:");
|
|
422
|
-
console.log(" 1. openai-compatible (默认,兼容 OpenAI API 的任意服务)");
|
|
423
|
-
console.log(" 2. openai (OpenAI 官方)");
|
|
424
|
-
console.log(" 3. ollama (本地 Ollama)");
|
|
425
|
-
console.log();
|
|
426
|
-
const typeChoice = await ask(rl, "选择 Provider (1/2/3)", existing?.type === "ollama" ? "3" : existing?.type === "openai" ? "2" : "1");
|
|
427
|
-
const providerType = typeChoice === "3" ? "ollama" : typeChoice === "2" ? "openai" : "openai-compatible";
|
|
428
|
-
let baseURL;
|
|
429
|
-
let apiKey;
|
|
430
|
-
if (providerType === "ollama") {
|
|
431
|
-
baseURL = await ask(rl, "Ollama API URL", existing?.baseURL || "http://localhost:11434/v1");
|
|
432
|
-
baseURL = normalizeOllamaBaseURL(baseURL);
|
|
433
|
-
apiKey = "ollama";
|
|
434
|
-
}
|
|
435
|
-
else if (providerType === "openai") {
|
|
436
|
-
baseURL = "https://api.openai.com/v1";
|
|
437
|
-
apiKey = await ask(rl, "OpenAI API Key", existing?.apiKey);
|
|
438
|
-
}
|
|
439
|
-
else {
|
|
440
|
-
baseURL = await ask(rl, "API Base URL", existing?.baseURL || "https://api.openai.com/v1");
|
|
441
|
-
apiKey = await ask(rl, "API Key", existing?.apiKey);
|
|
442
|
-
}
|
|
443
|
-
if (!baseURL || (!apiKey && providerType !== "ollama")) {
|
|
444
|
-
console.error("URL 和 Key 不能为空");
|
|
445
|
-
process.exit(1);
|
|
446
|
-
}
|
|
447
|
-
console.log("\n正在获取模型列表...");
|
|
448
|
-
const models = await fetchModels(baseURL, apiKey);
|
|
449
|
-
let defaultModel = existing?.defaultModel ?? "";
|
|
450
|
-
if (models.length > 0) {
|
|
451
|
-
console.log(`\n可用模型 (${models.length}):`);
|
|
452
|
-
models.forEach((m, i) => {
|
|
453
|
-
const marker = m === defaultModel ? " ← 当前默认" : "";
|
|
454
|
-
console.log(` ${i + 1}. ${m}${marker}`);
|
|
455
|
-
});
|
|
456
|
-
console.log();
|
|
457
|
-
const choice = await ask(rl, "选择默认模型 (输入序号或模型名)", defaultModel);
|
|
458
|
-
const idx = parseInt(choice, 10) - 1;
|
|
459
|
-
if (idx >= 0 && idx < models.length)
|
|
460
|
-
defaultModel = models[idx];
|
|
461
|
-
else if (choice)
|
|
462
|
-
defaultModel = choice;
|
|
463
|
-
}
|
|
464
|
-
else {
|
|
465
|
-
console.log(" ⚠ 无法获取模型列表,请手动输入模型名");
|
|
466
|
-
const hint = providerType === "ollama" ? "llama3" : providerType === "openai" ? "gpt-4o" : "";
|
|
467
|
-
defaultModel = await ask(rl, "默认模型", defaultModel || hint);
|
|
468
|
-
}
|
|
469
|
-
return { type: providerType, baseURL, apiKey, defaultModel };
|
|
470
|
-
}
|
package/dist/confirm.js
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
2
|
-
import { getEffectiveConfig } from "./config.js";
|
|
3
|
-
import { normalizeQuestionOptions, normalizeQuestionPrompt } from "./question-format.js";
|
|
4
|
-
import readline from "readline";
|
|
5
|
-
let autoApprove = false;
|
|
6
|
-
export function setAutoApprove(value) {
|
|
7
|
-
autoApprove = value;
|
|
8
|
-
}
|
|
9
|
-
export function isAutoApprove() {
|
|
10
|
-
const ctx = confirmAls.getStore();
|
|
11
|
-
if (ctx)
|
|
12
|
-
return ctx.autoApprove;
|
|
13
|
-
if (autoApprove)
|
|
14
|
-
return true;
|
|
15
|
-
return getPermissionMode() === "allow-all";
|
|
16
|
-
}
|
|
17
|
-
let permissionOverride;
|
|
18
|
-
export function setPermissionOverride(mode) {
|
|
19
|
-
permissionOverride = mode;
|
|
20
|
-
}
|
|
21
|
-
export function getPermissionOverride() {
|
|
22
|
-
return permissionOverride;
|
|
23
|
-
}
|
|
24
|
-
export function getPermissionMode() {
|
|
25
|
-
if (permissionOverride)
|
|
26
|
-
return permissionOverride;
|
|
27
|
-
const config = getEffectiveConfig();
|
|
28
|
-
return config.permission === "allow-all" || config.permission === "accept-edits" || config.permission === "ask"
|
|
29
|
-
? config.permission
|
|
30
|
-
: "ask";
|
|
31
|
-
}
|
|
32
|
-
export function isEditAutoApprove() {
|
|
33
|
-
if (isAutoApprove())
|
|
34
|
-
return true;
|
|
35
|
-
return getPermissionMode() === "accept-edits";
|
|
36
|
-
}
|
|
37
|
-
const confirmAls = new AsyncLocalStorage();
|
|
38
|
-
export function runWithConfirmContext(ctx, fn) {
|
|
39
|
-
return confirmAls.run(ctx, fn);
|
|
40
|
-
}
|
|
41
|
-
/** Optional readline interface to pause/resume during confirmation prompts. */
|
|
42
|
-
let _rl = null;
|
|
43
|
-
export function setConfirmReadline(rl) {
|
|
44
|
-
_rl = rl;
|
|
45
|
-
}
|
|
46
|
-
/** Optional TUI confirm handler — when set, confirm() delegates to the TUI overlay. */
|
|
47
|
-
let _tuiConfirm = null;
|
|
48
|
-
export function setTuiConfirm(handler) {
|
|
49
|
-
_tuiConfirm = handler;
|
|
50
|
-
}
|
|
51
|
-
/** Optional TUI question handler — when set, askQuestion() delegates to the TUI. Returns null on cancel. */
|
|
52
|
-
let _tuiQuestion = null;
|
|
53
|
-
export function setTuiQuestion(handler) {
|
|
54
|
-
_tuiQuestion = handler;
|
|
55
|
-
}
|
|
56
|
-
/** Ask the user a free-form question. Returns the answer, or null if cancelled. */
|
|
57
|
-
export async function askQuestion(prompt, options) {
|
|
58
|
-
const safePrompt = normalizeQuestionPrompt(prompt);
|
|
59
|
-
const choices = normalizeQuestionOptions(options);
|
|
60
|
-
const safeOptions = choices.length > 0 ? choices : undefined;
|
|
61
|
-
const ctx = confirmAls.getStore();
|
|
62
|
-
if (ctx) {
|
|
63
|
-
if (ctx.autoApprove)
|
|
64
|
-
return null;
|
|
65
|
-
if (ctx.onQuestion)
|
|
66
|
-
return ctx.onQuestion(safePrompt, safeOptions);
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
if (_tuiQuestion)
|
|
70
|
-
return _tuiQuestion(safePrompt, safeOptions);
|
|
71
|
-
if (!process.stdin.isTTY) {
|
|
72
|
-
console.log(`\n\x1b[33m❓ ${safePrompt} (no interactive terminal — skipped)\x1b[0m`);
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
console.log();
|
|
76
|
-
console.log(`\x1b[33m❓ ${safePrompt}\x1b[0m`);
|
|
77
|
-
if (safeOptions) {
|
|
78
|
-
for (let i = 0; i < safeOptions.length; i++) {
|
|
79
|
-
console.log(`\x1b[90m ${i + 1}. ${safeOptions[i]}\x1b[0m`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return new Promise((resolve) => {
|
|
83
|
-
const rl = readline.createInterface({
|
|
84
|
-
input: process.stdin,
|
|
85
|
-
output: process.stdout,
|
|
86
|
-
});
|
|
87
|
-
rl.question("\x1b[36m → \x1b[0m", (answer) => {
|
|
88
|
-
rl.close();
|
|
89
|
-
resolve(answer.trim() || "(no answer)");
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Ask user for confirmation. Returns true if approved.
|
|
95
|
-
* Display order: detail first, then [y/N] prompt at the bottom.
|
|
96
|
-
*/
|
|
97
|
-
export async function confirm(message) {
|
|
98
|
-
const ctx = confirmAls.getStore();
|
|
99
|
-
if (ctx) {
|
|
100
|
-
if (ctx.autoApprove)
|
|
101
|
-
return true;
|
|
102
|
-
if (ctx.onConfirm)
|
|
103
|
-
return ctx.onConfirm(message);
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
if (isAutoApprove())
|
|
107
|
-
return true;
|
|
108
|
-
// If TUI mode is active, delegate to the TUI overlay
|
|
109
|
-
if (_tuiConfirm)
|
|
110
|
-
return _tuiConfirm(message);
|
|
111
|
-
// Non-interactive stdin can never answer — reject instead of hanging
|
|
112
|
-
if (!process.stdin.isTTY) {
|
|
113
|
-
console.log(`\n\x1b[33m⚠ Rejected (no interactive terminal): ${message}\x1b[0m`);
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
// Pause readline so it doesn't consume/echo the keystroke
|
|
117
|
-
_rl?.pause();
|
|
118
|
-
// Detail on top, prompt at the bottom
|
|
119
|
-
process.stdout.write(`\n\x1b[90m┌─ 即将执行 ─────────────────────────────────\x1b[0m\n`);
|
|
120
|
-
process.stdout.write(`\x1b[90m│\x1b[0m ${message}\n`);
|
|
121
|
-
process.stdout.write(`\x1b[90m└────────────────────────────────────────────\x1b[0m\n`);
|
|
122
|
-
process.stdout.write(`\n\x1b[1;34m? 是否允许执行? [y/N] \x1b[0m`);
|
|
123
|
-
return new Promise((resolve) => {
|
|
124
|
-
const wasRaw = process.stdin.isRaw;
|
|
125
|
-
if (process.stdin.isTTY)
|
|
126
|
-
process.stdin.setRawMode(true);
|
|
127
|
-
// Ensure stdin is flowing so we can receive data even after readline pause
|
|
128
|
-
process.stdin.resume();
|
|
129
|
-
const onData = (buf) => {
|
|
130
|
-
const ch = buf.toString();
|
|
131
|
-
process.stdin.removeListener("data", onData);
|
|
132
|
-
if (process.stdin.isTTY)
|
|
133
|
-
process.stdin.setRawMode(wasRaw ?? false);
|
|
134
|
-
// Echo the character and newline
|
|
135
|
-
process.stdout.write(ch === "\r" || ch === "\n" ? "\n\n" : `${ch}\n\n`);
|
|
136
|
-
// Resume readline after confirmation
|
|
137
|
-
_rl?.resume();
|
|
138
|
-
const answer = ch.trim().toLowerCase();
|
|
139
|
-
resolve(answer === "y");
|
|
140
|
-
};
|
|
141
|
-
process.stdin.on("data", onData);
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
/** Check if a shell command is potentially dangerous */
|
|
145
|
-
export function isDangerousCommand(command) {
|
|
146
|
-
const dangerous = [
|
|
147
|
-
/\brm\s+(-[a-z]*[rf][a-z]*|--recursive)\s/,
|
|
148
|
-
/\bsudo\b/,
|
|
149
|
-
/\bmkfs\b/,
|
|
150
|
-
/\bdd\s+/,
|
|
151
|
-
/\b(shutdown|reboot|halt|poweroff)\b/,
|
|
152
|
-
/\bgit\s+(push|reset\s+--hard|clean\s+-[a-z]*f|rebase)/,
|
|
153
|
-
/\bnpm\s+(publish|unpublish)\b/,
|
|
154
|
-
/\b(drop|truncate)\s+(table|database)\b/i,
|
|
155
|
-
/\bformat\b.*\b[a-z]:\b/i,
|
|
156
|
-
/\bfind\b[^|;&]*\s+-delete\b/,
|
|
157
|
-
/\|\s*(ba|z|fi)?sh\b/,
|
|
158
|
-
/\bchmod\s+(-[a-zA-Z]+\s+)?[0-7]{3,4}\b/,
|
|
159
|
-
/\bchown\s+-R\b/,
|
|
160
|
-
/\bkill(all)?\s+(-9|-[a-z]*9)/,
|
|
161
|
-
/\b(pkill|killall)\b/,
|
|
162
|
-
/\bgit\s+checkout\s+\./,
|
|
163
|
-
/\bgit\s+stash\b/,
|
|
164
|
-
/\b(docker|podman)\s+(rm|prune|system)\b/,
|
|
165
|
-
/\b(shred|wipefs)\b/,
|
|
166
|
-
/\b(cp|mv)\s+-[a-z]*f\b/,
|
|
167
|
-
/[^<>=!&]\s*>>?\s*[^<>=&]|^\s*>>?\s*[^<>=&]/,
|
|
168
|
-
/:\(\)\s*\{/,
|
|
169
|
-
/>+\s*\/dev\/(sd[a-z]+|nvme\d|disk\d)/,
|
|
170
|
-
];
|
|
171
|
-
return dangerous.some((re) => re.test(command));
|
|
172
|
-
}
|